diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1
index 8c35aaff..d48fa5d9 100644
--- a/.claude/skills/form-compile/scripts/form-compile.ps1
+++ b/.claude/skills/form-compile/scripts/form-compile.ps1
@@ -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$(Esc-Xml "$($item.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$vStr"
}
- 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$(Esc-Xml "$($item.viewMode)")" }
if ($item.userSettingID) {
$uid = if ("$($item.userSettingID)" -eq "auto") { New-Guid-String } else { "$($item.userSettingID)" }
diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py
index ecf281fc..2c710409 100644
--- a/.claude/skills/form-compile/scripts/form-compile.py
+++ b/.claude/skills/form-compile/scripts/form-compile.py
@@ -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{esc_xml(str(item["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{v_str}')
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{esc_xml(str(item["viewMode"]))}')
if item.get('userSettingID'):
diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1
index 28a96037..4fc4d2cd 100644
--- a/.claude/skills/form-decompile/scripts/form-decompile.ps1
+++ b/.claude/skills/form-decompile/scripts/form-decompile.ps1
@@ -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 }
diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md
index ce48b68b..fee776c2 100644
--- a/docs/form-dsl-spec.md
+++ b/docs/form-dsl-spec.md
@@ -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` — массив имён форматируемых полей (``).
- `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` (привязка к области) в формах
diff --git a/tests/skills/cases/form-compile/dynamic-list-form.json b/tests/skills/cases/form-compile/dynamic-list-form.json
index cfa135d4..46503cfb 100644
--- a/tests/skills/cases/form-compile/dynamic-list-form.json
+++ b/tests/skills/cases/form-compile/dynamic-list-form.json
@@ -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": [
diff --git a/tests/skills/cases/form-compile/snapshots/dynamic-list-form/Catalogs/Товары/Forms/ФормаСписка/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/dynamic-list-form/Catalogs/Товары/Forms/ФормаСписка/Ext/Form.xml
index b9d743b6..8ef53a14 100644
--- a/tests/skills/cases/form-compile/snapshots/dynamic-list-form/Catalogs/Товары/Forms/ФормаСписка/Ext/Form.xml
+++ b/tests/skills/cases/form-compile/snapshots/dynamic-list-form/Catalogs/Товары/Forms/ФормаСписка/Ext/Form.xml
@@ -210,12 +210,19 @@
Code
Equal
+
+
+ ru
+ Код равно
+
+
UUID-003
Код товара
Description
Equal
+ Имя равно
UUID-004
@@ -264,6 +271,12 @@
web:Red
+
+
+ ru
+ Выделение пустых
+
+
Normal
UUID-007