mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-16 02:43:14 +03:00
feat(form-decompile,form-compile): presentation элемента CA/фильтра — сохранение формы xs:string vs LocalStringType
Топ-кластер нового baseline (~190 impact). <dcsset:presentation> элемента условного
оформления и групп/сравнений фильтра: платформа хранит ru-only текст и как xs:string
(плоский), и как LocalStringType (мультиязык-обёртка с одним ru). Декомпилятор схлопывал
ru-only LocalStringType в строку (Get-MLText) → компилятор писал xs:string → mismatch.
Плюс компилятор-баг: filter-item presentation эмитился через Emit-MLText (всегда мультиязык
БЕЗ xsi:type), даже для плоской строки.
Фикс:
- Декомпилятор: Get-PresByType — ветвь по xsi:type, сохраняет {lang:text} объект для
LocalStringType (даже один ru) vs плоскую строку для xs:string. Применён к presentation
элемента CA (Build-ConditionalAppearance) и фильтра (group + comparison, Build-FilterItem).
- Компилятор (ps1+py): filter-item presentation через by-form Emit-USPresentation/
emit_us_presentation (строка→xs:string, объект→LocalStringType с xsi:type). CA-item
presentation компилятор уже эмитил by-form — не трогаем.
Выборка 45 форм с LocalStringType-presentation: presentation-потерь 0, match 27→33,
TOTAL 127→63, регрессий 0 (сверка с baseline). Кейс dynamic-list-form (+CA presentation
{ru} ru-only + filter presentation объект/строка) сертифицирован загрузкой в 1С. Регресс
43/43, ps1==py (общий снэпшот на обоих рантаймах).
baseline после кластера ListSettings/DataSet (A+B+C): match 1869→1975, TOTAL 3495→2557.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# form-compile v1.142 — Compile 1C managed form from JSON or object metadata
|
||||
# form-compile v1.143 — Compile 1C managed form from JSON or object metadata
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[string]$JsonPath,
|
||||
@@ -1696,7 +1696,7 @@ function Emit-FilterItem {
|
||||
Emit-FilterItem -item $sub -indent "$indent`t"
|
||||
}
|
||||
}
|
||||
if ($item.presentation) { Emit-MLText -tag "dcsset:presentation" -text $item.presentation -indent "$indent`t" }
|
||||
if ($item.presentation) { Emit-USPresentation -val $item.presentation -tag "dcsset:presentation" -indent "$indent`t" }
|
||||
if ($item.viewMode) { X "$indent`t<dcsset:viewMode>$(Esc-Xml "$($item.viewMode)")</dcsset:viewMode>" }
|
||||
if ($item.userSettingID) {
|
||||
$guid = if ("$($item.userSettingID)" -eq "auto") { New-Guid-String } else { "$($item.userSettingID)" }
|
||||
@@ -1777,7 +1777,7 @@ function Emit-FilterItem {
|
||||
$vStr = if ($item.value -is [bool]) { "$($item.value)".ToLower() } else { Esc-Xml "$($item.value)" }
|
||||
X "$indent`t<dcsset:right xsi:type=`"$vt`">$vStr</dcsset:right>"
|
||||
}
|
||||
if ($item.presentation) { Emit-MLText -tag "dcsset:presentation" -text $item.presentation -indent "$indent`t" }
|
||||
if ($item.presentation) { Emit-USPresentation -val $item.presentation -tag "dcsset:presentation" -indent "$indent`t" }
|
||||
if ($item.viewMode) { X "$indent`t<dcsset:viewMode>$(Esc-Xml "$($item.viewMode)")</dcsset:viewMode>" }
|
||||
if ($item.userSettingID) {
|
||||
$uid = if ("$($item.userSettingID)" -eq "auto") { New-Guid-String } else { "$($item.userSettingID)" }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-compile v1.142 — Compile 1C managed form from JSON or object metadata
|
||||
# form-compile v1.143 — Compile 1C managed form from JSON or object metadata
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import copy
|
||||
@@ -1449,7 +1449,7 @@ def emit_filter_item(lines, item, indent):
|
||||
sub = obj
|
||||
emit_filter_item(lines, sub, f'{indent}\t')
|
||||
if item.get('presentation'):
|
||||
emit_mltext(lines, f'{indent}\t', 'dcsset:presentation', item['presentation'])
|
||||
emit_us_presentation(lines, f'{indent}\t', 'dcsset:presentation', item['presentation'])
|
||||
if item.get('viewMode'):
|
||||
lines.append(f'{indent}\t<dcsset:viewMode>{esc_xml(str(item["viewMode"]))}</dcsset:viewMode>')
|
||||
if item.get('userSettingID'):
|
||||
@@ -1509,7 +1509,7 @@ def emit_filter_item(lines, item, indent):
|
||||
v_str = str(val).lower() if isinstance(val, bool) else esc_xml(str(val))
|
||||
lines.append(f'{indent}\t<dcsset:right xsi:type="{vt}">{v_str}</dcsset:right>')
|
||||
if item.get('presentation'):
|
||||
emit_mltext(lines, f'{indent}\t', 'dcsset:presentation', item['presentation'])
|
||||
emit_us_presentation(lines, f'{indent}\t', 'dcsset:presentation', item['presentation'])
|
||||
if item.get('viewMode'):
|
||||
lines.append(f'{indent}\t<dcsset:viewMode>{esc_xml(str(item["viewMode"]))}</dcsset:viewMode>')
|
||||
if item.get('userSettingID'):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# form-decompile v0.115 — Decompile 1C managed Form.xml to JSON DSL (draft)
|
||||
# form-decompile v0.116 — Decompile 1C managed Form.xml to JSON DSL (draft)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
|
||||
param(
|
||||
@@ -450,6 +450,26 @@ function Get-PresText {
|
||||
return $null
|
||||
}
|
||||
|
||||
# Presentation, сохраняющий ФОРМУ по xsi:type (не схлопывает ru-only LocalStringType в строку):
|
||||
# <… xsi:type="v8:LocalStringType"> → объект {lang:text} (даже один ru), иначе xs:string → плоская строка.
|
||||
# Нужно, чтобы компилятор воспроизвёл точный xsi:type (LocalStringType vs xs:string). Пустой LocalStringType → $null.
|
||||
function Get-PresByType {
|
||||
param($node)
|
||||
if (-not $node) { return $null }
|
||||
$xt = $node.GetAttribute("type", $NS_XSI)
|
||||
if ($xt -match 'LocalStringType$') {
|
||||
$d = [ordered]@{}
|
||||
foreach ($it in $node.SelectNodes("v8:item", $ns)) {
|
||||
$lang = Get-Text $it "v8:lang"; $content = Get-Text $it "v8:content"
|
||||
if ($lang) { $d[$lang] = $content }
|
||||
}
|
||||
if ($d.Count -gt 0) { return $d }
|
||||
return $null
|
||||
}
|
||||
if ($node.InnerText) { return $node.InnerText }
|
||||
return $null
|
||||
}
|
||||
|
||||
# Снять namespace-префикс с xsi:type ("dcsset:Foo" → "Foo")
|
||||
function Get-LocalXsiType {
|
||||
param($node)
|
||||
@@ -583,9 +603,9 @@ function Build-FilterItem {
|
||||
$gObj = [ordered]@{ group = $groupName; items = $items }
|
||||
$gPresNode = $itemNode.SelectSingleNode("dcsset:presentation", $ns)
|
||||
if ($gPresNode) {
|
||||
$gPres = Get-MLText $gPresNode
|
||||
if (-not $gPres) { $gPres = $gPresNode.InnerText }
|
||||
if ($gPres) { $gObj['presentation'] = $gPres }
|
||||
# Сохраняем форму по xsi:type (LocalStringType ru-only ≠ xs:string)
|
||||
$gPres = Get-PresByType $gPresNode
|
||||
if ($null -ne $gPres -and $gPres -ne '') { $gObj['presentation'] = $gPres }
|
||||
}
|
||||
$gVMNode = $itemNode.SelectSingleNode("dcsset:viewMode", $ns)
|
||||
if ($gVMNode) { $gObj['viewMode'] = $gVMNode.InnerText }
|
||||
@@ -654,8 +674,8 @@ function Build-FilterItem {
|
||||
$fiPresNode = $itemNode.SelectSingleNode("dcsset:presentation", $ns)
|
||||
$fiPres = $null
|
||||
if ($fiPresNode) {
|
||||
$fiPres = Get-MLText $fiPresNode
|
||||
if (-not $fiPres) { $fiPres = $fiPresNode.InnerText }
|
||||
# Сохраняем форму по xsi:type (LocalStringType ru-only ≠ xs:string)
|
||||
$fiPres = Get-PresByType $fiPresNode
|
||||
}
|
||||
|
||||
$flags = @()
|
||||
@@ -874,9 +894,9 @@ function Build-ConditionalAppearance {
|
||||
if ($ap -and $ap.Count -gt 0) { $entry['appearance'] = $ap }
|
||||
$presNode = $it.SelectSingleNode("dcsset:presentation", $ns)
|
||||
if ($presNode) {
|
||||
$pres = Get-MLText $presNode
|
||||
if (-not $pres) { $pres = $presNode.InnerText }
|
||||
if ($pres) { $entry['presentation'] = $pres }
|
||||
# Сохраняем форму по xsi:type: LocalStringType (даже ru-only) → объект → компилятор эмитит LocalStringType.
|
||||
$pres = Get-PresByType $presNode
|
||||
if ($null -ne $pres -and $pres -ne '') { $entry['presentation'] = $pres }
|
||||
}
|
||||
$vmN = $it.SelectSingleNode("dcsset:viewMode", $ns)
|
||||
if ($vmN) { $entry['viewMode'] = $vmN.InnerText }
|
||||
|
||||
@@ -1052,7 +1052,7 @@ Shorthand: `"Имя [Заголовок]: тип = Выражение #noField #
|
||||
```
|
||||
|
||||
- **order** — строка `"Поле"` (asc) / `"Поле desc"` (синонимы `убыв`/`desc`, `возр`/`asc`) / `"Auto"`, либо объект `{ field, direction?, use?, viewMode? }`.
|
||||
- **filter** — shorthand `"Поле оператор значение @флаги"` (`@off`, `@user`, `@quickAccess`, `@normal`, `@inaccessible`; `_` = пусто) или объект `{ field, op, value?, use?, userSettingID?, userSettingPresentation? }` или группа `{ group: "And"|"Or"|"Not", items: [...] }`. `userSettingPresentation` (кастомная подпись настройки) ведётся **по форме значения**: голая строка → `xsi:type="xs:string"`; объект `{ru,en}` → `v8:LocalStringType` (так же у `order`/`conditionalAppearance`/`dataParameters`).
|
||||
- **filter** — shorthand `"Поле оператор значение @флаги"` (`@off`, `@user`, `@quickAccess`, `@normal`, `@inaccessible`; `_` = пусто) или объект `{ field, op, value?, use?, userSettingID?, userSettingPresentation?, presentation? }` или группа `{ group: "And"|"Or"|"Not", items: [...] }`. `userSettingPresentation` (кастомная подпись настройки) и `presentation` (подпись условия/элемента) ведутся **по форме значения**: голая строка → `xsi:type="xs:string"`; объект `{ru,en}` (в т.ч. один `{ru}`) → `v8:LocalStringType` (так же у `order`/`conditionalAppearance`/`dataParameters` — и у `presentation` элемента условного оформления).
|
||||
- **Операторы:** `=` `<>` `>` `>=` `<` `<=`, `in`/`notIn`, `inHierarchy`/`inListByHierarchy`, `contains`/`notContains`, `beginsWith`/`notBeginsWith`, `like`/`notLike` (подобно; `%`-шаблон в значении, напр. `"КодВалют like %/ %"`), `filled`/`notFilled`. Регистр оператора не важен; у `like`/`notLike` есть рус. синоним `подобно`/`неподобно`.
|
||||
- **Дата в фильтре = `StandardBeginningDate`** (так платформа хранит дату-значение почти всегда — корпус 268 vs 2 `xs:dateTime`). Формы значения (от компактной к полной):
|
||||
- **голая ISO-дата** `"2020-01-01T00:00:00"` (без `valueType`) → `Custom` + эта дата. Работает и в shorthand: `"ДатаЗаказа > 2020-01-01T00:00:00"`. Это дефолт даты в фильтре.
|
||||
@@ -1167,7 +1167,7 @@ Shorthand: `"Имя [Заголовок]: тип = Выражение #noField #
|
||||
- `selection` — массив имён форматируемых полей (`<dcsset:field>`).
|
||||
- `filter` — условие (filter-shorthand, как в СКД).
|
||||
- `appearance` — словарь «параметр-DCS: значение» (рус. verbatim: `ЦветТекста`/`ЦветФона`/`Шрифт`/…). Цвет → `v8ui:Color`.
|
||||
- `presentation` — мультиязык → `xsi:type="v8:LocalStringType"`.
|
||||
- `presentation` — подпись элемента оформления, **по форме значения**: голая строка → `xsi:type="xs:string"`; объект `{ru,en}` (в т.ч. один `{ru}`) → `v8:LocalStringType`. (Платформа хранит обе формы для одного ru-текста — различаем, чтобы не ломать раундтрип.)
|
||||
|
||||
Декомпилятор/компилятор переиспользуют `Build-ConditionalAppearance`/`Emit-ConditionalAppearance` настроек списка
|
||||
(отличие — тег-обёртка `ConditionalAppearance` без `dcsset:` и без блок-мета). `scope` (привязка к области) в формах
|
||||
|
||||
@@ -34,10 +34,10 @@
|
||||
],
|
||||
"order": [ "Description", "Code desc" ],
|
||||
"filter": [ "Артикул = _ @off @user", "Артикул like %тест%", "Description подобно %abc%",
|
||||
{ "field": "Code", "op": "=", "userSettingID": "auto", "userSettingPresentation": "Код товара" },
|
||||
{ "field": "Description", "op": "=", "userSettingID": "auto", "userSettingPresentation": { "ru": "Наименование", "en": "Name" } } ],
|
||||
{ "field": "Code", "op": "=", "userSettingID": "auto", "userSettingPresentation": "Код товара", "presentation": { "ru": "Код равно" } },
|
||||
{ "field": "Description", "op": "=", "userSettingID": "auto", "userSettingPresentation": { "ru": "Наименование", "en": "Name" }, "presentation": "Имя равно" } ],
|
||||
"dataParameters": [ "ВыборТовара @off" ],
|
||||
"conditionalAppearance": [ { "filter": ["Артикул = _"], "appearance": { "ЦветТекста": "web:Red" } } ]
|
||||
"conditionalAppearance": [ { "filter": ["Артикул = _"], "appearance": { "ЦветТекста": "web:Red" }, "presentation": { "ru": "Выделение пустых" } } ]
|
||||
} }
|
||||
],
|
||||
"elements": [
|
||||
|
||||
+13
@@ -210,12 +210,19 @@
|
||||
<dcsset:item xsi:type="dcsset:FilterItemComparison">
|
||||
<dcsset:left xsi:type="dcscor:Field">Code</dcsset:left>
|
||||
<dcsset:comparisonType>Equal</dcsset:comparisonType>
|
||||
<dcsset:presentation xsi:type="v8:LocalStringType">
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Код равно</v8:content>
|
||||
</v8:item>
|
||||
</dcsset:presentation>
|
||||
<dcsset:userSettingID>UUID-003</dcsset:userSettingID>
|
||||
<dcsset:userSettingPresentation xsi:type="xs:string">Код товара</dcsset:userSettingPresentation>
|
||||
</dcsset:item>
|
||||
<dcsset:item xsi:type="dcsset:FilterItemComparison">
|
||||
<dcsset:left xsi:type="dcscor:Field">Description</dcsset:left>
|
||||
<dcsset:comparisonType>Equal</dcsset:comparisonType>
|
||||
<dcsset:presentation xsi:type="xs:string">Имя равно</dcsset:presentation>
|
||||
<dcsset:userSettingID>UUID-004</dcsset:userSettingID>
|
||||
<dcsset:userSettingPresentation xsi:type="v8:LocalStringType">
|
||||
<v8:item>
|
||||
@@ -264,6 +271,12 @@
|
||||
<dcscor:value xsi:type="v8ui:Color">web:Red</dcscor:value>
|
||||
</dcscor:item>
|
||||
</dcsset:appearance>
|
||||
<dcsset:presentation xsi:type="v8:LocalStringType">
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Выделение пустых</v8:content>
|
||||
</v8:item>
|
||||
</dcsset:presentation>
|
||||
</dcsset:item>
|
||||
<dcsset:viewMode>Normal</dcsset:viewMode>
|
||||
<dcsset:userSettingID>UUID-007</dcsset:userSettingID>
|
||||
|
||||
Reference in New Issue
Block a user