From 684cd17d5f88f3c6dab82ad7326d92cc92ee796a Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sat, 6 Jun 2026 18:16:32 +0300 Subject: [PATCH] =?UTF-8?q?feat(form-decompile,form-compile):=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D1=82=D0=B5=D0=BD=D1=82=20=D1=80=D0=B0=D1=81=D1=88?= =?UTF-8?q?=D0=B8=D1=80=D0=B5=D0=BD=D0=BD=D0=BE=D0=B9=20=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=D1=81=D0=BA=D0=B0=D0=B7=D0=BA=D0=B8=20extendedTooltip=20+=20?= =?UTF-8?q?=D0=B5=D0=B4=D0=B8=D0=BD=D0=B0=D1=8F=20ML-text=20=D1=84=D0=BE?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=20=D1=81=20formatted=20(=D0=BA=D0=BB=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D0=B5=D1=80=20companion-content=20tier=201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion несёт (текст расширенной подсказки) — декомпилятор пропускал companion целиком, текст терялся. Теперь companion = свойство родителя: ключ extendedTooltip на элементе (синоним extTooltip). Единая форма ML-текста (для title/tooltip/extendedTooltip): - строка → ru; {ru,en} → многоязычно; {text, formatted: true} → форматированный. - formatted: текст несётся RAW (1С inline-разметка <b>/<color>/<link>/</> — часть строки, round-trip через XML-экранирование, спаны не моделируем). - Гибрид: флаг formatted авто-детектится по известной разметке/</> (детектор идентичен в ps1+py+декомпиляторе); явный {text,formatted} — только когда авто-детект неверен (~2% корпуса: formatted без разметки / литеральные <…>-плейсхолдеры). Авто-детект подтверждён данными (98% верно, мисматч 1003 из 53612). Декомпилятор (v0.26): извлекает Title из companion <ExtendedTooltip> на родителя (гибрид). Компилятор (ps1+py v1.44): Emit-Companion с опциональным контентом; 14 call-site'ов ExtendedTooltip передают el.extendedTooltip; синоним extTooltip→extendedTooltip; whitelist. Валидация: content-bearing round-trip CLEAN (Банки/ФормаЭлемента byte-identical, formatted=true round-trip); регресс 33/33 ps+py; py==ps1 идентичны; harness 8368→8202. Хвосты (в BACKLOG): пустое присутствие companion (Table/CommandBar/Popup не генерят ExtendedTooltip — ~437, вскрыто фиксом метрики) и ExtendedTooltip с own-layout (<Width>). Spec: extendedTooltip + раздел ML-text/formatted/markup-словарь. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --- .../form-compile/scripts/form-compile.ps1 | 78 ++++++++++++++----- .../form-compile/scripts/form-compile.py | 66 +++++++++++----- .../form-decompile/scripts/form-decompile.ps1 | 26 ++++++- docs/form-dsl-spec.md | 12 +++ 4 files changed, 143 insertions(+), 39 deletions(-) diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index f850aded..756feff8 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.43 — Compile 1C managed form from JSON or object metadata +# form-compile v1.44 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -1563,6 +1563,34 @@ function Emit-MLText { X "$indent</$tag>" } +# Детектор «настоящей» inline-разметки форматированного текста (1С: <link>/<b>/<color>/… +# и закрывающий </>). Плейсхолдеры вида <не заполнен> НЕ срабатывают (нет известного тега/</>). +# ВАЖНО: regex должен быть идентичен в form-decompile (иначе гибрид-раундтрип поедет). +$script:fmtMarkupRe = '</>|<\s*(?:link|b|i|u|s|color|colorStyle|bgColor|bgColorStyle|font|fontSize|fontStyle|img)(?:\s|>)' +function Test-HasRealMarkup { + param($text) + if ($null -eq $text) { return $false } + $vals = if ($text -is [System.Collections.IDictionary]) { @($text.Values) } + elseif ($text -is [System.Management.Automation.PSCustomObject]) { @($text.PSObject.Properties.Value) } + else { @("$text") } + foreach ($v in $vals) { if ("$v" -match $script:fmtMarkupRe) { return $true } } + return $false +} +# DSL-значение ML-поля → @{ text; formatted }. Форма {text, formatted} = явный override; +# строка/мапа → авто-детект formatted по разметке. +function Resolve-MLFormatted { + param($val) + $hasText = $false + if ($val -is [System.Management.Automation.PSCustomObject]) { $hasText = [bool]$val.PSObject.Properties['text'] } + elseif ($val -is [System.Collections.IDictionary]) { $hasText = $val.Contains('text') } + if ($hasText) { + $t = if ($val -is [System.Collections.IDictionary]) { $val['text'] } else { $val.text } + $f = if ($val -is [System.Collections.IDictionary]) { $val['formatted'] } else { $val.formatted } + return @{ text = $t; formatted = [bool]$f } + } + return @{ text = $val; formatted = (Test-HasRealMarkup $val) } +} + # Каноничные GUID пустых контейнеров ListSettings (умолчание платформы, ~90% форм). # Декомпилятор опускает пустые настройки → компилятор регенерит этот скелет → раундтрип # (harness нормализует GUID для хвоста с иными идентификаторами). @@ -2219,9 +2247,21 @@ function Emit-Events { } function Emit-Companion { - param([string]$tag, [string]$name, [string]$indent) + param([string]$tag, [string]$name, [string]$indent, $content = $null) $id = New-Id - X "$indent<$tag name=`"$name`" id=`"$id`"/>" + $hasContent = $null -ne $content -and -not ($content -is [string] -and "$content" -eq '') + if (-not $hasContent) { + X "$indent<$tag name=`"$name`" id=`"$id`"/>" + return + } + # Companion с контентом: <Title formatted="…"> (расширенная подсказка) + X "$indent<$tag name=`"$name`" id=`"$id`">" + $r = Resolve-MLFormatted $content + $fmt = if ($r.formatted) { 'true' } else { 'false' } + X "$indent`t<Title formatted=`"$fmt`">" + Emit-MLItems -val $r.text -indent "$indent`t`t" + X "$indent`t" + X "$indent" } # Табличный addition (СтрокаПоиска/СостояниеПросмотра/УправлениеПоиском) с AdditionSource. @@ -2316,7 +2356,7 @@ function Emit-Element { # radio-specific "radioButtonType"=1;"choiceList"=1;"columnsCount"=1;"checkBoxType"=1;"editMode"=1 # naming & binding - "name"=1;"path"=1;"title"=1;"tooltip"=1;"tooltipRepresentation"=1 + "name"=1;"path"=1;"title"=1;"tooltip"=1;"tooltipRepresentation"=1;"extendedTooltip"=1 # visibility & state "visible"=1;"hidden"=1;"enabled"=1;"disabled"=1;"readOnly"=1;"userVisible"=1 # events ("events" — основной формат; on/handlers — legacy, принимаются ради совместимости) @@ -2548,7 +2588,7 @@ function Emit-Group { Emit-Layout -el $el -indent $inner # Companion: ExtendedTooltip - Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip # Children if ($el.children -and $el.children.Count -gt 0) { @@ -2590,7 +2630,7 @@ function Emit-ColumnGroup { Emit-Layout -el $el -indent $inner # Companion: ExtendedTooltip - Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip # Children if ($el.children -and $el.children.Count -gt 0) { @@ -2645,7 +2685,7 @@ function Emit-Input { # Companions Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner - Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip Emit-Events -el $el -elementName $name -indent $inner -typeKey "input" @@ -2678,7 +2718,7 @@ function Emit-Check { # Companions Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner - Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip Emit-Events -el $el -elementName $name -indent $inner -typeKey "check" @@ -2903,7 +2943,7 @@ function Emit-Radio { # Companions Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner - Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip Emit-Events -el $el -elementName $name -indent $inner -typeKey "radio" @@ -2935,7 +2975,7 @@ function Emit-Label { # Companions Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner - Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip Emit-Events -el $el -elementName $name -indent $inner -typeKey "label" @@ -2961,7 +3001,7 @@ function Emit-LabelField { # Companions Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner - Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip Emit-Events -el $el -elementName $name -indent $inner -typeKey "labelField" @@ -3108,7 +3148,7 @@ function Emit-Pages { Emit-Layout -el $el -indent $inner # Companion - Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip Emit-Events -el $el -elementName $name -indent $inner -typeKey "pages" @@ -3146,7 +3186,7 @@ function Emit-Page { Emit-Layout -el $el -indent $inner # Companion - Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip # Children if ($el.children -and $el.children.Count -gt 0) { @@ -3244,7 +3284,7 @@ function Emit-Button { Emit-Layout -el $el -indent $inner # Companion - Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip Emit-Events -el $el -elementName $name -indent $inner -typeKey "button" @@ -3274,7 +3314,7 @@ function Emit-PictureDecoration { # Companions Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner - Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip Emit-Events -el $el -elementName $name -indent $inner -typeKey "picture" @@ -3308,7 +3348,7 @@ function Emit-PictureField { # Companions Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner - Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip Emit-Events -el $el -elementName $name -indent $inner -typeKey "picField" @@ -3350,7 +3390,7 @@ function Emit-Calendar { # Companions Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner - Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip Emit-Events -el $el -elementName $name -indent $inner -typeKey "calendar" @@ -3398,7 +3438,7 @@ function Emit-ButtonGroup { Emit-Layout -el $el -indent $inner # Companion: ExtendedTooltip - Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip # Children (кнопки в контексте командной панели) if ($el.children -and $el.children.Count -gt 0) { @@ -3676,7 +3716,7 @@ function Emit-Properties { function Normalize-ElementSynonyms { param($el) if ($null -eq $el) { return } - $synonyms = @{ "commandBar" = "cmdBar"; "autoCommandBar" = "autoCmdBar" } + $synonyms = @{ "commandBar" = "cmdBar"; "autoCommandBar" = "autoCmdBar"; "extTooltip" = "extendedTooltip" } foreach ($pair in $synonyms.GetEnumerator()) { if ($null -ne $el.PSObject.Properties[$pair.Key] -and $null -eq $el.PSObject.Properties[$pair.Value]) { $val = $el.($pair.Key) diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index fec14f8c..4e991026 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.43 — Compile 1C managed form from JSON or object metadata +# form-compile v1.44 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -1761,7 +1761,7 @@ KNOWN_KEYS = { "button", "picture", "picField", "calendar", "cmdBar", "popup", "showInHeader", "radioButtonType", "choiceList", "columnsCount", "checkBoxType", "editMode", - "name", "path", "title", "tooltip", "tooltipRepresentation", + "name", "path", "title", "tooltip", "tooltipRepresentation", "extendedTooltip", "visible", "hidden", "enabled", "disabled", "readOnly", "userVisible", "events", "on", "handlers", "selectionMode", "showCurrentDate", "widthInMonths", "heightInMonths", "showMonthsPanel", @@ -2012,9 +2012,37 @@ def emit_events(lines, el, element_name, indent, type_key): lines.append(f"{indent}") -def emit_companion(lines, tag, name, indent): +# Детектор «настоящей» inline-разметки (1С: ///… и ). Должен быть +# идентичен form-decompile/form-compile.ps1, иначе гибрид-раундтрип поедет. +_FMT_MARKUP_RE = re.compile(r'|<\s*(?:link|b|i|u|s|color|colorStyle|bgColor|bgColorStyle|font|fontSize|fontStyle|img)(?:\s|>)', re.I) + + +def _has_real_markup(text): + if text is None: + return False + vals = list(text.values()) if isinstance(text, dict) else [text] + return any(_FMT_MARKUP_RE.search(str(v)) for v in vals) + + +def resolve_ml_formatted(val): + # {text, formatted} = явный override; строка/мапа → авто-детект formatted + if isinstance(val, dict) and 'text' in val: + return val['text'], bool(val.get('formatted')) + return val, _has_real_markup(val) + + +def emit_companion(lines, tag, name, indent, content=None): cid = new_id() - lines.append(f'{indent}<{tag} name="{name}" id="{cid}"/>') + has_content = content is not None and not (isinstance(content, str) and content == '') + if not has_content: + lines.append(f'{indent}<{tag} name="{name}" id="{cid}"/>') + return + lines.append(f'{indent}<{tag} name="{name}" id="{cid}">') + text, fmt = resolve_ml_formatted(content) + lines.append(f'{indent}\t') + emit_ml_items(lines, f'{indent}\t\t', text) + lines.append(f'{indent}\t') + lines.append(f'{indent}') def emit_table_addition(lines, tag, table_name, name_suffix, src_type, indent): @@ -2422,7 +2450,7 @@ def emit_group(lines, el, name, eid, indent): emit_layout(lines, el, inner) # Companion: ExtendedTooltip - emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner) + emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner, el.get('extendedTooltip')) # Children if el.get('children') and len(el['children']) > 0: @@ -2459,7 +2487,7 @@ def emit_column_group(lines, el, name, eid, indent): emit_common_flags(lines, el, inner) emit_layout(lines, el, inner) - emit_companion(lines, 'ExtendedTooltip', f'{name}РасширеннаяПодсказка', inner) + emit_companion(lines, 'ExtendedTooltip', f'{name}РасширеннаяПодсказка', inner, el.get('extendedTooltip')) if el.get('children') and len(el['children']) > 0: lines.append(f'{inner}') @@ -2512,7 +2540,7 @@ def emit_input(lines, el, name, eid, indent): # Companions emit_companion(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner) - emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner) + emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner, el.get('extendedTooltip')) emit_events(lines, el, name, inner, 'input') @@ -2545,7 +2573,7 @@ def emit_check(lines, el, name, eid, indent): # Companions emit_companion(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner) - emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner) + emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner, el.get('extendedTooltip')) emit_events(lines, el, name, inner, 'check') @@ -2604,7 +2632,7 @@ def emit_radio_button_field(lines, el, name, eid, indent): emit_layout(lines, el, inner) emit_companion(lines, 'ContextMenu', f'{name}КонтекстноеМеню', inner) - emit_companion(lines, 'ExtendedTooltip', f'{name}РасширеннаяПодсказка', inner) + emit_companion(lines, 'ExtendedTooltip', f'{name}РасширеннаяПодсказка', inner, el.get('extendedTooltip')) emit_events(lines, el, name, inner, 'radio') @@ -2635,7 +2663,7 @@ def emit_label(lines, el, name, eid, indent): # Companions emit_companion(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner) - emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner) + emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner, el.get('extendedTooltip')) emit_events(lines, el, name, inner, 'label') @@ -2663,7 +2691,7 @@ def emit_label_field(lines, el, name, eid, indent): # Companions emit_companion(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner) - emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner) + emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner, el.get('extendedTooltip')) emit_events(lines, el, name, inner, 'labelField') @@ -2815,7 +2843,7 @@ def emit_pages(lines, el, name, eid, indent): emit_layout(lines, el, inner) # Companion - emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner) + emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner, el.get('extendedTooltip')) emit_events(lines, el, name, inner, 'pages') @@ -2849,7 +2877,7 @@ def emit_page(lines, el, name, eid, indent): emit_layout(lines, el, inner) # Companion - emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner) + emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner, el.get('extendedTooltip')) # Children if el.get('children') and len(el['children']) > 0: @@ -2932,7 +2960,7 @@ def emit_button(lines, el, name, eid, indent, in_cmd_bar=False): emit_layout(lines, el, inner) # Companion - emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner) + emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner, el.get('extendedTooltip')) emit_events(lines, el, name, inner, 'button') @@ -2960,7 +2988,7 @@ def emit_picture_decoration(lines, el, name, eid, indent): # Companions emit_companion(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner) - emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner) + emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner, el.get('extendedTooltip')) emit_events(lines, el, name, inner, 'picture') @@ -2994,7 +3022,7 @@ def emit_picture_field(lines, el, name, eid, indent): # Companions emit_companion(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner) - emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner) + emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner, el.get('extendedTooltip')) emit_events(lines, el, name, inner, 'picField') @@ -3032,7 +3060,7 @@ def emit_calendar(lines, el, name, eid, indent): # Companions emit_companion(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner) - emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner) + emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner, el.get('extendedTooltip')) emit_events(lines, el, name, inner, 'calendar') @@ -3101,7 +3129,7 @@ def emit_button_group(lines, el, name, eid, indent): emit_layout(lines, el, inner) # Companion: ExtendedTooltip - emit_companion(lines, 'ExtendedTooltip', f'{name}РасширеннаяПодсказка', inner) + emit_companion(lines, 'ExtendedTooltip', f'{name}РасширеннаяПодсказка', inner, el.get('extendedTooltip')) # Children (кнопки в контексте командной панели) if el.get('children') and len(el['children']) > 0: @@ -3575,7 +3603,7 @@ def main(): def _normalize_synonyms(el): if not isinstance(el, dict): return - synonyms = {'commandBar': 'cmdBar', 'autoCommandBar': 'autoCmdBar'} + synonyms = {'commandBar': 'cmdBar', 'autoCommandBar': 'autoCmdBar', 'extTooltip': 'extendedTooltip'} for src, dst in synonyms.items(): if src in el and dst not in el: el[dst] = el.pop(src) diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1 index 751b0510..74595cb6 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.25 — Decompile 1C managed Form.xml to JSON DSL (draft) +# form-decompile v0.26 — Decompile 1C managed Form.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. param( @@ -230,6 +230,27 @@ function Get-LangText { return $map } +# Детектор «настоящей» inline-разметки форматированного текста (идентичен form-compile!). +$script:fmtMarkupRe = '|<\s*(?:link|b|i|u|s|color|colorStyle|bgColor|bgColorStyle|font|fontSize|fontStyle|img)(?:\s|>)' +function Test-HasRealMarkup { + param($text) + if ($null -eq $text) { return $false } + $vals = if ($text -is [System.Collections.IDictionary]) { @($text.Values) } else { @("$text") } + foreach ($v in $vals) { if ("$v" -match $script:fmtMarkupRe) { return $true } } + return $false +} +# Title-узел → DSL-значение ML-поля (гибрид): строка/мапа когда авто-детект formatted +# совпал с атрибутом; иначе явный {text, formatted}. +function Get-MLFormattedValue { + param($titleNode) + if (-not $titleNode) { return $null } + $text = Get-LangText $titleNode + if ($null -eq $text) { return $null } + $fmtAttr = ($titleNode.GetAttribute('formatted') -eq 'true') + if ($fmtAttr -eq (Test-HasRealMarkup $text)) { return $text } + $o = [ordered]@{}; $o['text'] = $text; $o['formatted'] = $fmtAttr; return $o +} + # Прочитать дочерний скаляр (по local-name, без namespace) function Get-Child { param($node, [string]$name) @@ -1086,6 +1107,9 @@ function Decompile-Element { if ($autoTitle) { $obj['title'] = '' } } Add-Layout $obj $node + # extendedTooltip: контент companion (любой элемент) + $etTitle = $node.SelectSingleNode("lf:ExtendedTooltip/lf:Title", $ns) + if ($etTitle) { $et = Get-MLFormattedValue $etTitle; if ($null -ne $et) { $obj['extendedTooltip'] = $et } } return $obj } diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index e17b8671..bd800883 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -118,6 +118,18 @@ | `titleLocation` | string | Расположение заголовка: `none`/`left`/`right`/`top`/`bottom`/`auto`. Эмитится при наличии (input, labelField, picField, table, calendar). У `check`/`radio` — особая семантика с умным дефолтом (см. их разделы) | | `tooltip` | string/object | Всплывающая подсказка элемента (`<ToolTip>`). Строка → ru, объект `{ "ru": …, "en": … }` → мультиязычный (как `title`). Эмитится сразу после `title` | | `tooltipRepresentation` | string | Режим показа подсказки (`<ToolTipRepresentation>`): `None`, `Button`, `ShowBottom`, `ShowTop`, `ShowLeft`, `ShowRight`, `ShowAuto`, `Balloon`. Эмитится при наличии | +| `extendedTooltip` | string/object | Расширенная подсказка (контент companion `<ExtendedTooltip>`). См. форму ML-текста ниже. Синоним: `extTooltip` | + +#### Форма ML-текста и `formatted` + +`title`/`tooltip`/`extendedTooltip` принимают: +- `"строка"` — ru-текст; +- `{ "ru": "…", "en": "…" }` — многоязычный; +- `{ "text": <строка|мапа>, "formatted": true }` — **форматированный** текст (атрибут `<Title formatted="true">`). + +**`formatted`** включает интерпретацию inline-разметки в тексте (1С-формат, похож на BBCode): `<b>…</>`, `<i>`, `<u>`, `<color web:Red>…</>`, `<bgColor …>`, `<font …>`, `<fontSize …>`, `<link URL>…</>`, `<img …>`; закрывающий тег — `</>`. Текст несётся **raw** (разметка — часть строки), парсинг не требуется. + +Флаг авто-детектится по наличию известной разметки/`</>`: для plain-строки объект не нужен. Явная форма `{text, formatted}` — только когда авто-детект неверен (formatted-текст без разметки, либо буквальные `<…>`-плейсхолдеры в неформатированном). ### 4.1a. Общие layout-свойства