From 459d3feb694f6e08ef58560f5e96802ca646e60a Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Thu, 11 Jun 2026 18:00:00 +0300 Subject: [PATCH] =?UTF-8?q?feat(form-decompile,form-compile):=20appearance?= =?UTF-8?q?=20=D0=A2=D0=B5=D0=BA=D1=81=D1=82/=D0=97=D0=B0=D0=B3=D0=BE?= =?UTF-8?q?=D0=BB=D0=BE=D0=B2=D0=BE=D0=BA/=D0=A4=D0=BE=D1=80=D0=BC=D0=B0?= =?UTF-8?q?=D1=82=20=E2=80=94=20xs:string/Field/typed=20LocalString=20(?= =?UTF-8?q?=D0=BA=D0=B0=D1=82=D0=B5=D0=B3=D0=BE=D1=80=D0=B8=D1=8F=20=D0=BA?= =?UTF-8?q?=D0=B0=D1=80=D1=82=D0=B8=D0=BD=D0=BE=D0=BA=E2=86=92=D1=82=D0=B5?= =?UTF-8?q?=D0=BA=D1=81=D1=82=20=D0=BE=D1=84=D0=BE=D1=80=D0=BC=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В значениях параметров оформления (dcsset:appearance) компилятор форсил мультиязычный LocalStringType по имени ключа (Текст/Заголовок/Формат), теряя плоскую xs:string и dcscor:Field. Корпус 8.3.24 (Текст/Заголовок/Формат): xs:string 823, LocalString одноязычный 658, многоязычный 188, dcscor:Field 105. Контекст (форменный CA vs Settings дин-списка) НЕ детерминирует (целевая форма СправкаРасчётПостоянныхИВременныхРазниц — форменный CA с xs:string). Решение (scoped-различие по форме значения, в этом конкретном контексте — не общая конвенция DSL): голая строка → плоский xs:string (нелокализ. литерал; "" → самозакрывающийся тег); объект {ru,en} → LocalStringType; объект {field:"путь"} → dcscor:Field. Декомпилятор перестаёт схлопывать одноязычный LocalString здесь (всегда объект-карта языков → различим от xs:string). Заодно пред-существующий баг: LocalString-значение параметра несёт xsi:type="v8:LocalStringType" на теге dcscor:value (846 случаев) — Emit-MLText эмитил голый тег; добавлен опц. параметр xsiType (ps+py). Зеркало py (байт-в-байт). Выборка 57 CA-форм (xs:string/LocalString/Field, вкл. целевую): appearance-потерь 0, целевая → match (match 26→40). Кейс input-fields (+CA Текст: плоский/multilang/пустой/field) сертифицирован в 1С. Регресс 43/43. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../form-compile/scripts/form-compile.ps1 | 21 +++++-- .../form-compile/scripts/form-compile.py | 22 +++++-- .../form-decompile/scripts/form-decompile.ps1 | 18 +++++- docs/form-dsl-spec.md | 1 + .../cases/form-compile/input-fields.json | 7 ++- .../ПоляВвода/Forms/Форма/Ext/Form.xml | 61 +++++++++++++++++++ 6 files changed, 114 insertions(+), 16 deletions(-) diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index 1a8d3cf7..f24ec997 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.115 — Compile 1C managed form from JSON or object metadata +# form-compile v1.116 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -1570,8 +1570,9 @@ function Emit-MLItems { } function Emit-MLText { - param([string]$tag, $text, [string]$indent) - X "$indent<$tag>" + param([string]$tag, $text, [string]$indent, [string]$xsiType) + $attr = if ($xsiType) { " xsi:type=`"$xsiType`"" } else { "" } + X "$indent<$tag$attr>" Emit-MLItems -val $text -indent "$indent`t" X "$indent" } @@ -1860,8 +1861,12 @@ function Emit-AppearanceValue { if ($null -ne $av) { $attrParts += "$attrName=`"$(Esc-Xml "$av")`"" } } X "$indent`t" + } elseif ($isDict -and (_HasKey $innerVal 'field')) { + # Ссылка на поле (dcscor:Field) — значение параметра оформления = поле компоновки + X "$indent`t$(Esc-Xml "$(_Get $innerVal 'field')")" } elseif ($isDict) { - Emit-MLText -tag "dcscor:value" -text $innerVal -indent "$indent`t" + # Локализуемый текст параметра оформления: платформа объявляет xsi:type на dcscor:value + Emit-MLText -tag "dcscor:value" -text $innerVal -indent "$indent`t" -xsiType "v8:LocalStringType" } else { $actualVal = "$innerVal" $keyTypeMap = @{ @@ -1876,7 +1881,13 @@ function Emit-AppearanceValue { if ($keyType) { X "$indent`t$(Esc-Xml $actualVal)" } elseif ($actualVal -match '^(style|web|win):') { X "$indent`t$(Esc-Xml $actualVal)" } elseif ($actualVal -eq "true" -or $actualVal -eq "false") { X "$indent`t$actualVal" } - elseif ($key -eq "Текст" -or $key -eq "Заголовок" -or $key -eq "Формат") { Emit-MLText -tag "dcscor:value" -text $actualVal -indent "$indent`t" } + elseif ($key -eq "Текст" -or $key -eq "Заголовок" -or $key -eq "Формат") { + # Текст/Заголовок/Формат: голая строка = плоский xs:string (так платформа хранит + # нелокализованный литерал). Локализуемый текст → объект {ru,en} (ветка isDict выше). + # Пустая строка → самозакрывающийся тег (как у платформы). + if ($actualVal -eq '') { X "$indent`t" } + else { X "$indent`t$(Esc-Xml $actualVal)" } + } elseif ($actualVal -match '^-?\d+(\.\d+)?$') { X "$indent`t$actualVal" } elseif ($key -eq 'ЦветТекста' -or $key -eq 'ЦветФона' -or $key -eq 'ЦветГраницы') { X "$indent`t$(Esc-Xml $actualVal)" } else { X "$indent`t$(Esc-Xml $actualVal)" } diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index 429881a8..73abe5dd 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.115 — Compile 1C managed form from JSON or object metadata +# form-compile v1.116 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -1300,11 +1300,12 @@ def emit_ml_items(lines, indent, val): lines.append(f"{indent}") -def emit_mltext(lines, indent, tag, text): +def emit_mltext(lines, indent, tag, text, xsi_type=None): + attr = f' xsi:type="{xsi_type}"' if xsi_type else '' if not text: - lines.append(f"{indent}<{tag}/>") + lines.append(f"{indent}<{tag}{attr}/>") return - lines.append(f"{indent}<{tag}>") + lines.append(f"{indent}<{tag}{attr}>") emit_ml_items(lines, f"{indent}\t", text) lines.append(f"{indent}") @@ -1606,8 +1607,12 @@ def emit_appearance_value(lines, key, val, indent): if av is not None: attr_parts.append(f'{attr_name}="{esc_xml(str(av))}"') lines.append(f'{indent}\t') + elif is_dict and _has_key(inner_val, 'field'): + # Ссылка на поле (dcscor:Field) — значение параметра оформления = поле компоновки + lines.append(f'{indent}\t{esc_xml(str(_get(inner_val, "field")))}') elif is_dict: - emit_mltext(lines, f'{indent}\t', 'dcscor:value', inner_val) + # Локализуемый текст параметра оформления: платформа объявляет xsi:type на dcscor:value + emit_mltext(lines, f'{indent}\t', 'dcscor:value', inner_val, xsi_type='v8:LocalStringType') else: actual_val = str(inner_val) key_type_map = { @@ -1626,7 +1631,12 @@ def emit_appearance_value(lines, key, val, indent): elif actual_val == 'true' or actual_val == 'false': lines.append(f'{indent}\t{actual_val}') elif key == 'Текст' or key == 'Заголовок' or key == 'Формат': - emit_mltext(lines, f'{indent}\t', 'dcscor:value', actual_val) + # Голая строка = плоский xs:string (нелокализованный литерал). Локализуемый → объект {ru,en}. + # Пустая строка → самозакрывающийся тег (как у платформы). + if actual_val == '': + lines.append(f'{indent}\t') + else: + lines.append(f'{indent}\t{esc_xml(actual_val)}') elif re.match(r'^-?\d+(\.\d+)?$', actual_val): lines.append(f'{indent}\t{actual_val}') elif key == 'ЦветТекста' or key == 'ЦветФона' or key == 'ЦветГраницы': diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1 index 86be4675..3bd38d04 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.91 — Decompile 1C managed Form.xml to JSON DSL (draft) +# form-decompile v0.92 — Decompile 1C managed Form.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. param( @@ -407,14 +407,26 @@ function Get-LineValue { return $obj } -# Прочитать в JSON-значение: Font/Line/multilang/raw text. +# Прочитать в JSON-значение: Font/Line/Field/multilang/raw text. function Read-AppearanceValueNode { param($valNode) if (-not $valNode) { return $null } $vt = Get-LocalXsiType $valNode - if ($vt -eq 'LocalStringType') { return (Get-MLText $valNode) } + if ($vt -eq 'LocalStringType') { + # НЕ схлопываем одноязычный в строку: значение параметра оформления различает + # xs:string (плоская строка) и LocalStringType (локализуемый текст) — обе формы + # одноязычно дают одну строку. Всегда объект-карта языков → компилятор эмитит LocalStringType. + $map = [ordered]@{} + foreach ($it in @($valNode.SelectNodes("v8:item", $ns))) { + $lang = $it.SelectSingleNode("v8:lang", $ns); $content = $it.SelectSingleNode("v8:content", $ns) + if ($lang) { $map[$lang.InnerText] = if ($content) { $content.InnerText } else { "" } } + } + return $map + } if ($vt -eq 'Font') { return (Get-FontValue $valNode) } if ($vt -eq 'Line') { return (Get-LineValue $valNode) } + # dcscor:Field — значение = ссылка на поле компоновки → объект {field:путь} + if ($vt -eq 'Field') { return [ordered]@{ field = $valNode.InnerText } } return $valNode.InnerText } diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index 2bd6f8c2..ced1e68b 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -976,6 +976,7 @@ Forgiving-синонимы типа: XML-имя (`SpreadSheetDocumentField`) и - **order** — строка `"Поле"` (asc) / `"Поле desc"` (синонимы `убыв`/`desc`, `возр`/`asc`) / `"Auto"`, либо объект `{ field, direction?, use?, viewMode? }`. - **filter** — shorthand `"Поле оператор значение @флаги"` (`@off`, `@user`, `@quickAccess`, `@normal`, `@inaccessible`; `_` = пусто) или объект `{ field, op, value?, use?, userSettingID? }` или группа `{ group: "And"|"Or"|"Not", items: [...] }`. - **conditionalAppearance** — объект `{ selection?, filter?, appearance?, presentation?, viewMode?, userSettingID?, use? }`. `appearance` — словарь «параметр: значение» платформы (`ЦветТекста`, `ЦветФона`, `Шрифт` и т.п.). + - Значение текстовых параметров (`Текст`/`Заголовок`/`Формат`) ведётся **по форме значения**: голая строка → плоский `xs:string` (нелокализованный литерал; `""` → самозакрывающийся тег); объект `{ru,en}` → локализуемый `LocalStringType`; объект `{field:"путь"}` → ссылка на поле компоновки (`dcscor:Field`). (В отличие от `title`/`tooltip`, где голая строка = `LocalStringType` — здесь это намеренное scoped-различие: платформа хранит обе формы, и их надо различать.) `userSettingID: "auto"` → платформа сгенерирует идентификатор пользовательской настройки. Пустые контейнеры (без правил) эмитируются автоматически. diff --git a/tests/skills/cases/form-compile/input-fields.json b/tests/skills/cases/form-compile/input-fields.json index 4e62789a..85f4a877 100644 --- a/tests/skills/cases/form-compile/input-fields.json +++ b/tests/skills/cases/form-compile/input-fields.json @@ -69,8 +69,11 @@ { "name": "ЧисловоеПоле", "type": "number(10,2)" } ], "conditionalAppearance": [ - { "selection": ["ОбычноеПоле"], "filter": ["ЧисловоеПоле > 100"], "appearance": { "ЦветФона": "style:FormBackColor" }, - "presentation": { "ru": "Подсветка", "en": "Highlight" } } + { "selection": ["ОбычноеПоле"], "filter": ["ЧисловоеПоле > 100"], "appearance": { "ЦветФона": "style:FormBackColor", "Текст": "Плоский текст" }, + "presentation": { "ru": "Подсветка", "en": "Highlight" } }, + { "filter": ["ЧисловоеПоле = 0"], "appearance": { "Текст": { "ru": "Локализованный", "en": "Localized" } } }, + { "filter": ["ЧисловоеПоле < 0"], "appearance": { "Текст": "" } }, + { "filter": ["ЧисловоеПоле = 1"], "appearance": { "Текст": { "field": "ОбычноеПоле" } } } ] } } diff --git a/tests/skills/cases/form-compile/snapshots/input-fields/DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/input-fields/DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml index ad0535c2..6b9f9437 100644 --- a/tests/skills/cases/form-compile/snapshots/input-fields/DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/input-fields/DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml @@ -629,6 +629,10 @@ ЦветФона style:FormBackColor + + Текст + Плоский текст + @@ -641,6 +645,63 @@ + + + + + ЧисловоеПоле + Equal + 0 + + + + + Текст + + + ru + Локализованный + + + en + Localized + + + + + + + + + + ЧисловоеПоле + Less + 0 + + + + + Текст + + + + + + + + + ЧисловоеПоле + Equal + 1 + + + + + Текст + ОбычноеПоле + + +