From cfce4860047eecda793900b30a4abe2473c0695e Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 7 Jun 2026 16:33:02 +0300 Subject: [PATCH] =?UTF-8?q?feat(form-decompile,form-compile):=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE=D0=B2=D0=BE=D0=BA=20=D1=80=D0=B5?= =?UTF-8?q?=D0=BA=D0=B2=D0=B8=D0=B7=D0=B8=D1=82=D0=B0=20=E2=80=94=20=D1=81?= =?UTF-8?q?=D1=83=D0=BF=D0=BF=D1=80=D0=B5=D1=81=D1=81-=D0=BC=D0=B0=D1=80?= =?UTF-8?q?=D0=BA=D0=B5=D1=80=20""=20+=20omit=20=D0=B0=D0=B2=D1=82=D0=BE-?= =?UTF-8?q?=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=D0=B0=20(=D0=BA=D0=BB=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D0=B5=D1=80=20Attribute>Title)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Компилятор для не-main реквизита БЕЗ ключа title додумывал из имени, хотя платформа реквизит без синонима хранит без <Title>. На корпусе (295609 реквизитов): 22% без <Title> — всем додумывался заголовок (ADDED Attribute>Title = 170 в выборке). Компилятор (ps1+py): эмиссия Title реквизита приведена к логике Emit-Title — нет ключа → авто-вывод (кроме main); title "" → подавить (раньше "" был falsy и уходил в авто-вывод — это и был баг); непустой → как есть. Декомпилятор (ps1): нет <Title> → title:"" (суппресс-маркер); ru-only заголовок, равный авто-выводу из имени → опускаем ключ (компилятор воспроизведёт, 35% = 103908 реквизитов корпуса); иначе → явный. Скопировано точное зеркало Title-FromName для сверки. Регресс: attributes-types.json — реквизит с title:"" (подавление) рядом с авто-выводом + снэпшот. spec §реквизиты обновлён. TOTAL diff lines выборки 2.17: 2255 → 1750 (−505); cascade ADDED 292 → 33. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --- .../form-compile/scripts/form-compile.ps1 | 12 ++++--- .../form-compile/scripts/form-compile.py | 14 ++++---- .../form-decompile/scripts/form-decompile.ps1 | 35 +++++++++++++++++-- docs/form-dsl-spec.md | 2 +- .../cases/form-compile/attributes-types.json | 2 +- .../Типы/Forms/Форма/Ext/Form.xml | 6 ---- 6 files changed, 51 insertions(+), 20 deletions(-) diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index 59a022a0..5b09f4e0 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.61 — Compile 1C managed form from JSON or object metadata +# form-compile v1.62 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -3664,9 +3664,13 @@ function Emit-Attributes { X "$indent`t<Attribute name=`"$attrName`" id=`"$attrId`">" $inner = "$indent`t`t" - $attrTitle = if ($attr.title) { $attr.title } elseif ($attr.main -ne $true) { Title-FromName -name $attrName } else { '' } - if ($attrTitle) { - Emit-MLText -tag "Title" -text $attrTitle -indent $inner + # Title атрибута (зеркало Emit-Title): нет ключа → авто-вывод из имени (кроме main); + # title "" → подавить; непустой → эмитить как есть. + $hasTitleKey = $null -ne $attr.PSObject.Properties['title'] + if ($hasTitleKey) { + if ($attr.title) { Emit-MLText -tag "Title" -text $attr.title -indent $inner } + } elseif ($attr.main -ne $true) { + Emit-MLText -tag "Title" -text (Title-FromName -name $attrName) -indent $inner } # Type diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index 42bcd81b..0e14f3af 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.61 — Compile 1C managed form from JSON or object metadata +# form-compile v1.62 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -3342,11 +3342,13 @@ def emit_attributes(lines, attrs, indent): lines.append(f'{indent}\t<Attribute name="{attr_name}" id="{attr_id}">') inner = f'{indent}\t\t' - attr_title = attr.get('title') - if not attr_title and attr.get('main') is not True: - attr_title = title_from_name(attr_name) - if attr_title: - emit_mltext(lines, inner, 'Title', attr_title) + # Title атрибута (зеркало emit_title): нет ключа → авто-вывод из имени (кроме main); + # title "" → подавить; непустой → эмитить как есть. + if 'title' in attr: + if attr.get('title'): + emit_mltext(lines, inner, 'Title', attr['title']) + elif attr.get('main') is not True: + emit_mltext(lines, inner, 'Title', title_from_name(attr_name)) # Type if attr.get('type'): diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1 index 425db921..7fed5922 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.42 — Decompile 1C managed Form.xml to JSON DSL (draft) +# form-decompile v0.43 — Decompile 1C managed Form.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. param( @@ -230,6 +230,25 @@ function Get-LangText { return $map } +# Авто-вывод заголовка из имени — ТОЧНОЕ зеркало Title-FromName из form-compile. +# Нужен, чтобы опускать ru-only заголовки, которые компилятор воспроизведёт сам. +function Title-FromName { + param([string]$name) + if (-not $name) { return '' } + $s = [regex]::Replace($name, '([А-ЯA-Z])([А-ЯA-Z][а-яa-z])', '$1 $2') + $s = [regex]::Replace($s, '([а-яa-z0-9])([А-ЯA-Z])', '$1 $2') + $parts = $s -split ' ' + if ($parts.Count -eq 0) { return $s } + $out = New-Object System.Collections.ArrayList + [void]$out.Add($parts[0]) + for ($i = 1; $i -lt $parts.Count; $i++) { + $p = $parts[$i] + if ($p.Length -gt 1 -and $p -ceq $p.ToUpper()) { [void]$out.Add($p) } + else { [void]$out.Add($p.ToLower()) } + } + return ($out -join ' ') +} + # Детектор «настоящей» inline-разметки форматированного текста (идентичен form-compile!). $script:fmtMarkupRe = '</>|<\s*(?:link|b|i|u|s|color|colorStyle|bgColor|bgColorStyle|font|fontSize|fontStyle|img)(?:\s|>)' function Test-HasRealMarkup { @@ -1343,7 +1362,19 @@ if ($attrsNode) { if ((Get-Child $a 'MainAttribute') -eq 'true') { $ao['main'] = $true } $vw = Decompile-XrFlag $a 'View'; if ($null -ne $vw) { $ao['view'] = $vw } $ed = Decompile-XrFlag $a 'Edit'; if ($null -ne $ed) { $ao['edit'] = $ed } - $tNode = $a.SelectSingleNode("lf:Title", $ns); if ($tNode) { $t = Get-LangText $tNode; if ($null -ne $t) { $ao['title'] = $t } } + # Title атрибута. Компилятор для не-main атрибута без ключа title додумывает заголовок + # из имени. Поэтому: нет <Title> → суппресс-маркер ''; ru-only == авто-вывод → опускаем + # ключ (компилятор воспроизведёт); иначе → явный заголовок. + $isMain = $ao.Contains('main') + $tNode = $a.SelectSingleNode("lf:Title", $ns) + if ($tNode) { + $t = Get-LangText $tNode + if ($null -ne $t) { + if ($isMain -or -not ($t -is [string]) -or $t -ne (Title-FromName $ao['name'])) { $ao['title'] = $t } + } + } elseif (-not $isMain) { + $ao['title'] = '' + } if ((Get-Child $a 'SavedData') -eq 'true') { $ao['savedData'] = $true } $fc = Get-Child $a 'FillChecking'; if ($fc) { $ao['fillChecking'] = $fc } $afo = Decompile-FunctionalOptions $a; if ($afo) { $ao['functionalOptions'] = $afo } diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index d5c15dad..09f11282 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -623,7 +623,7 @@ Pages поддерживает `pagesRepresentation`: `None`, `TabsOnTop`, `Tabs | `name` | string | Имя реквизита (обязательно) | | `type` | string | Тип (shorthand) | | `main` | bool | Основной реквизит формы | -| `title` | string | Заголовок | +| `title` | string/object | Заголовок. **Нет ключа** → авто-вывод из имени (как у элементов; кроме `main`). **`""`** → подавить (`<Title>` не эмитится — так платформа и хранит реквизит без синонима). Строка → ru; объект `{ru,en}` → мультиязычный. Декомпилятор опускает ключ, когда ru-заголовок совпадает с авто-выводом из имени | | `view` | bool/object | Просмотр по ролям (`<View>`). См. §4.1c | | `edit` | bool/object | Редактирование по ролям (`<Edit>`). См. §4.1c | | `functionalOptions` | array | Функциональные опции (`<FunctionalOptions><Item>FunctionalOption.X</Item>…`). Массив имён; forgiving: `"X"`/`"FunctionalOption.X"`. Также у колонок (`columns[*]`) и команд (§7) | diff --git a/tests/skills/cases/form-compile/attributes-types.json b/tests/skills/cases/form-compile/attributes-types.json index dda6aa3c..5c02b96f 100644 --- a/tests/skills/cases/form-compile/attributes-types.json +++ b/tests/skills/cases/form-compile/attributes-types.json @@ -25,7 +25,7 @@ { "name": "Объект", "type": "DataProcessorObject.Типы", "main": true }, { "name": "Строка", "type": "string(200)", "view": false }, { "name": "Число", "type": "decimal(10,0,nonneg)", "edit": false }, - { "name": "Дата", "type": "dateTime" }, + { "name": "Дата", "type": "dateTime", "title": "" }, { "name": "Булево", "type": "boolean" }, { "name": "СписокЗначений", "type": "ValueList" }, { "name": "Идентификатор", "type": "v8:UUID" } diff --git a/tests/skills/cases/form-compile/snapshots/attributes-types/DataProcessors/Типы/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/attributes-types/DataProcessors/Типы/Forms/Форма/Ext/Form.xml index ee0219a5..77c25f32 100644 --- a/tests/skills/cases/form-compile/snapshots/attributes-types/DataProcessors/Типы/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/attributes-types/DataProcessors/Типы/Forms/Форма/Ext/Form.xml @@ -78,12 +78,6 @@ </Edit> </Attribute> <Attribute name="Дата" id="16"> - <Title> - <v8:item> - <v8:lang>ru</v8:lang> - <v8:content>Дата</v8:content> - </v8:item> - xs:dateTime