mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-19 17:19:42 +03:00
feat(form-decompile,form-compile): tooltip элемента + фикс экранирования текста (кластер ToolTip)
Два дефекта вокруг текста <v8:content>, оба вскрылись на формах с подсказками.
1. ToolTip элемента (484 LOST в корпусе). <ToolTip> — прямой мультиязычный
текст подсказки на элементе (UsualGroup 42150, Popup, Page, InputField,
и почти все типы). Декомпилятор пропускал (как companion), компилятор не
эмитил. Введён общий ключ tooltip (string|{ru,en}), как title:
- декомпилятор: захват в Add-CommonProps;
- компилятор: эмиссия в Emit-Title (сразу после Title) — покрывает все
эмиттеры, зовущие Emit-Title.
Попутно выяснилось, что Emit-Pages/Emit-CommandBar вовсе не звали Emit-Title
(теряли и Title, и ToolTip), а Emit-Label эмитит Title по-своему — во все три
добавлена обработка title/tooltip.
2. Экранирование кавычек. Esc-Xml экранировал " → " в тексте элемента,
но 1С в <v8:content> пишет " литерально (экранирует только & < >).
Это ломало раундтрип любого текста с кавычками. Убрано экранирование " .
Декомпилятор (ps1) + компилятор (ps1+py) + spec (§4.1 tooltip). Покрытие:
input-fields (input+tooltip), pages (pages/page tooltip, page с кавычкой в
тексте — проверяет литеральность) — сертифицировано в 1С 8.3.24. Раундтрип
БанковскиеСчета/Wildberries/АдреснаяКнига: ToolTip и " остаток = 0.
Регресс ps+py 33/33.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c43041c0b7
commit
22e929ecb3
@@ -1,4 +1,4 @@
|
|||||||
# form-compile v1.34 — Compile 1C managed form from JSON or object metadata
|
# form-compile v1.36 — Compile 1C managed form from JSON or object metadata
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[string]$JsonPath,
|
[string]$JsonPath,
|
||||||
@@ -1515,8 +1515,11 @@ function X {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Esc-Xml {
|
function Esc-Xml {
|
||||||
|
# Экранирование ТЕКСТА элемента (<v8:content>, <Value>): только & < > .
|
||||||
|
# Кавычки/апострофы в тексте экранировать НЕ нужно (1С их не экранирует — пишет литерально);
|
||||||
|
# " ломал бы раундтрип. Кавычки спецсимвольны лишь в значениях атрибутов.
|
||||||
param([string]$s)
|
param([string]$s)
|
||||||
return $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"')
|
return $s.Replace('&','&').Replace('<','<').Replace('>','>')
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- 4. Multilang helper ---
|
# --- 4. Multilang helper ---
|
||||||
@@ -1956,7 +1959,7 @@ function Emit-Element {
|
|||||||
# radio-specific
|
# radio-specific
|
||||||
"radioButtonType"=1;"choiceList"=1;"columnsCount"=1;"checkBoxType"=1;"editMode"=1
|
"radioButtonType"=1;"choiceList"=1;"columnsCount"=1;"checkBoxType"=1;"editMode"=1
|
||||||
# naming & binding
|
# naming & binding
|
||||||
"name"=1;"path"=1;"title"=1
|
"name"=1;"path"=1;"title"=1;"tooltip"=1
|
||||||
# visibility & state
|
# visibility & state
|
||||||
"visible"=1;"hidden"=1;"enabled"=1;"disabled"=1;"readOnly"=1;"userVisible"=1
|
"visible"=1;"hidden"=1;"enabled"=1;"disabled"=1;"readOnly"=1;"userVisible"=1
|
||||||
# events ("events" — основной формат; on/handlers — legacy, принимаются ради совместимости)
|
# events ("events" — основной формат; on/handlers — legacy, принимаются ради совместимости)
|
||||||
@@ -2091,6 +2094,8 @@ function Emit-Title {
|
|||||||
} elseif ($auto -and $name) {
|
} elseif ($auto -and $name) {
|
||||||
Emit-MLText -tag "Title" -text (Title-FromName -name $name) -indent $indent
|
Emit-MLText -tag "Title" -text (Title-FromName -name $name) -indent $indent
|
||||||
}
|
}
|
||||||
|
# ToolTip элемента (всплывающая подсказка) — по схеме сразу после Title.
|
||||||
|
if ($el.tooltip) { Emit-MLText -tag "ToolTip" -text $el.tooltip -indent $indent }
|
||||||
}
|
}
|
||||||
|
|
||||||
function Map-TitleLoc {
|
function Map-TitleLoc {
|
||||||
@@ -2525,6 +2530,7 @@ function Emit-Label {
|
|||||||
Emit-MLItems -val $labelTitle -indent "$inner`t"
|
Emit-MLItems -val $labelTitle -indent "$inner`t"
|
||||||
X "$inner</Title>"
|
X "$inner</Title>"
|
||||||
}
|
}
|
||||||
|
if ($el.tooltip) { Emit-MLText -tag "ToolTip" -text $el.tooltip -indent $inner }
|
||||||
|
|
||||||
Emit-CommonFlags -el $el -indent $inner
|
Emit-CommonFlags -el $el -indent $inner
|
||||||
|
|
||||||
@@ -2644,6 +2650,8 @@ function Emit-Pages {
|
|||||||
X "$indent<Pages name=`"$name`" id=`"$id`">"
|
X "$indent<Pages name=`"$name`" id=`"$id`">"
|
||||||
$inner = "$indent`t"
|
$inner = "$indent`t"
|
||||||
|
|
||||||
|
Emit-Title -el $el -name $name -indent $inner
|
||||||
|
|
||||||
if ($el.pagesRepresentation) {
|
if ($el.pagesRepresentation) {
|
||||||
X "$inner<PagesRepresentation>$($el.pagesRepresentation)</PagesRepresentation>"
|
X "$inner<PagesRepresentation>$($el.pagesRepresentation)</PagesRepresentation>"
|
||||||
}
|
}
|
||||||
@@ -2906,6 +2914,8 @@ function Emit-CommandBar {
|
|||||||
X "$indent<CommandBar name=`"$name`" id=`"$id`">"
|
X "$indent<CommandBar name=`"$name`" id=`"$id`">"
|
||||||
$inner = "$indent`t"
|
$inner = "$indent`t"
|
||||||
|
|
||||||
|
Emit-Title -el $el -name $name -indent $inner
|
||||||
|
|
||||||
if ($el.autofill -eq $true) { X "$inner<Autofill>true</Autofill>" }
|
if ($el.autofill -eq $true) { X "$inner<Autofill>true</Autofill>" }
|
||||||
|
|
||||||
Emit-CommonFlags -el $el -indent $inner
|
Emit-CommonFlags -el $el -indent $inner
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# form-compile v1.34 — Compile 1C managed form from JSON or object metadata
|
# form-compile v1.36 — Compile 1C managed form from JSON or object metadata
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
import argparse
|
import argparse
|
||||||
import copy
|
import copy
|
||||||
@@ -1251,7 +1251,9 @@ def generate_chart_of_accounts_choice_dsl(meta, preset_data):
|
|||||||
|
|
||||||
|
|
||||||
def esc_xml(s):
|
def esc_xml(s):
|
||||||
return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
|
# Экранирование ТЕКСТА элемента (<v8:content>, <Value>): только & < > .
|
||||||
|
# Кавычки/апострофы в тексте 1С не экранирует (пишет литерально) — " ломал бы раундтрип.
|
||||||
|
return s.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||||
|
|
||||||
|
|
||||||
def emit_ml_items(lines, indent, val):
|
def emit_ml_items(lines, indent, val):
|
||||||
@@ -1354,7 +1356,7 @@ KNOWN_KEYS = {
|
|||||||
"button", "picture", "picField", "calendar", "cmdBar", "popup",
|
"button", "picture", "picField", "calendar", "cmdBar", "popup",
|
||||||
"showInHeader",
|
"showInHeader",
|
||||||
"radioButtonType", "choiceList", "columnsCount", "checkBoxType", "editMode",
|
"radioButtonType", "choiceList", "columnsCount", "checkBoxType", "editMode",
|
||||||
"name", "path", "title",
|
"name", "path", "title", "tooltip",
|
||||||
"visible", "hidden", "enabled", "disabled", "readOnly", "userVisible",
|
"visible", "hidden", "enabled", "disabled", "readOnly", "userVisible",
|
||||||
"events", "on", "handlers",
|
"events", "on", "handlers",
|
||||||
"selectionMode", "showCurrentDate", "widthInMonths", "heightInMonths", "showMonthsPanel",
|
"selectionMode", "showCurrentDate", "widthInMonths", "heightInMonths", "showMonthsPanel",
|
||||||
@@ -1673,6 +1675,9 @@ def emit_title(lines, el, name, indent, auto=False):
|
|||||||
emit_mltext(lines, indent, 'Title', el['title'])
|
emit_mltext(lines, indent, 'Title', el['title'])
|
||||||
elif auto and name:
|
elif auto and name:
|
||||||
emit_mltext(lines, indent, 'Title', title_from_name(name))
|
emit_mltext(lines, indent, 'Title', title_from_name(name))
|
||||||
|
# ToolTip элемента (всплывающая подсказка) — по схеме сразу после Title.
|
||||||
|
if el.get('tooltip'):
|
||||||
|
emit_mltext(lines, indent, 'ToolTip', el['tooltip'])
|
||||||
|
|
||||||
|
|
||||||
_TITLE_LOC_MAP = {'none': 'None', 'left': 'Left', 'right': 'Right', 'top': 'Top', 'bottom': 'Bottom', 'auto': 'Auto'}
|
_TITLE_LOC_MAP = {'none': 'None', 'left': 'Left', 'right': 'Right', 'top': 'Top', 'bottom': 'Bottom', 'auto': 'Auto'}
|
||||||
@@ -2173,6 +2178,8 @@ def emit_label(lines, el, name, eid, indent):
|
|||||||
lines.append(f'{inner}<Title formatted="{formatted}">')
|
lines.append(f'{inner}<Title formatted="{formatted}">')
|
||||||
emit_ml_items(lines, f'{inner}\t', label_title)
|
emit_ml_items(lines, f'{inner}\t', label_title)
|
||||||
lines.append(f'{inner}</Title>')
|
lines.append(f'{inner}</Title>')
|
||||||
|
if el.get('tooltip'):
|
||||||
|
emit_mltext(lines, inner, 'ToolTip', el['tooltip'])
|
||||||
|
|
||||||
emit_common_flags(lines, el, inner)
|
emit_common_flags(lines, el, inner)
|
||||||
|
|
||||||
@@ -2301,6 +2308,8 @@ def emit_pages(lines, el, name, eid, indent):
|
|||||||
lines.append(f'{indent}<Pages name="{name}" id="{eid}">')
|
lines.append(f'{indent}<Pages name="{name}" id="{eid}">')
|
||||||
inner = f'{indent}\t'
|
inner = f'{indent}\t'
|
||||||
|
|
||||||
|
emit_title(lines, el, name, inner)
|
||||||
|
|
||||||
if el.get('pagesRepresentation'):
|
if el.get('pagesRepresentation'):
|
||||||
lines.append(f'{inner}<PagesRepresentation>{el["pagesRepresentation"]}</PagesRepresentation>')
|
lines.append(f'{inner}<PagesRepresentation>{el["pagesRepresentation"]}</PagesRepresentation>')
|
||||||
|
|
||||||
@@ -2535,6 +2544,8 @@ def emit_command_bar(lines, el, name, eid, indent):
|
|||||||
lines.append(f'{indent}<CommandBar name="{name}" id="{eid}">')
|
lines.append(f'{indent}<CommandBar name="{name}" id="{eid}">')
|
||||||
inner = f'{indent}\t'
|
inner = f'{indent}\t'
|
||||||
|
|
||||||
|
emit_title(lines, el, name, inner)
|
||||||
|
|
||||||
if el.get('autofill') is True:
|
if el.get('autofill') is True:
|
||||||
lines.append(f'{inner}<Autofill>true</Autofill>')
|
lines.append(f'{inner}<Autofill>true</Autofill>')
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# form-decompile v0.14 — Decompile 1C managed Form.xml to JSON DSL (draft)
|
# form-decompile v0.16 — Decompile 1C managed Form.xml to JSON DSL (draft)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
|
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
|
||||||
param(
|
param(
|
||||||
@@ -239,6 +239,8 @@ function Add-CommonProps {
|
|||||||
if ($null -ne $t) { $obj['title'] = $t }
|
if ($null -ne $t) { $obj['title'] = $t }
|
||||||
# formatted у LabelDecoration выводится компилятором из hyperlink — отдельный ключ не нужен (#16 хвост)
|
# formatted у LabelDecoration выводится компилятором из hyperlink — отдельный ключ не нужен (#16 хвост)
|
||||||
}
|
}
|
||||||
|
$ttNode = $node.SelectSingleNode("lf:ToolTip", $ns)
|
||||||
|
if ($ttNode) { $tt = Get-LangText $ttNode; if ($null -ne $tt) { $obj['tooltip'] = $tt } }
|
||||||
$ev = Get-Events $node $elName
|
$ev = Get-Events $node $elName
|
||||||
if ($ev) { $obj['events'] = $ev }
|
if ($ev) { $obj['events'] = $ev }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,7 @@
|
|||||||
| `readOnly` | bool | `true` → `<ReadOnly>true</ReadOnly>` |
|
| `readOnly` | bool | `true` → `<ReadOnly>true</ReadOnly>` |
|
||||||
| `events` | object | Обработчики событий: `{ "ИмяСобытия": "ИмяОбработчика" }` — тот же формат, что у событий формы (§3). Значение `null` → имя по конвенции (§4.2). См. §4.2 |
|
| `events` | object | Обработчики событий: `{ "ИмяСобытия": "ИмяОбработчика" }` — тот же формат, что у событий формы (§3). Значение `null` → имя по конвенции (§4.2). См. §4.2 |
|
||||||
| `titleLocation` | string | Расположение заголовка: `none`/`left`/`right`/`top`/`bottom`/`auto`. Эмитится при наличии (input, labelField, picField, table, calendar). У `check`/`radio` — особая семантика с умным дефолтом (см. их разделы) |
|
| `titleLocation` | string | Расположение заголовка: `none`/`left`/`right`/`top`/`bottom`/`auto`. Эмитится при наличии (input, labelField, picField, table, calendar). У `check`/`radio` — особая семантика с умным дефолтом (см. их разделы) |
|
||||||
|
| `tooltip` | string/object | Всплывающая подсказка элемента (`<ToolTip>`). Строка → ru, объект `{ "ru": …, "en": … }` → мультиязычный (как `title`). Эмитится сразу после `title` |
|
||||||
|
|
||||||
### 4.1a. Общие layout-свойства
|
### 4.1a. Общие layout-свойства
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"input": {
|
"input": {
|
||||||
"title": "Поля ввода",
|
"title": "Поля ввода",
|
||||||
"elements": [
|
"elements": [
|
||||||
{ "input": "ОбычноеПоле", "path": "ОбычноеПоле", "title": "Обычное поле", "editMode": "EnterOnInput" },
|
{ "input": "ОбычноеПоле", "path": "ОбычноеПоле", "title": "Обычное поле", "tooltip": "Введите значение поля", "editMode": "EnterOnInput" },
|
||||||
{ "labelField": "Ссылка", "path": "ОбычноеПоле", "titleLocation": "left", "hyperlink": true },
|
{ "labelField": "Ссылка", "path": "ОбычноеПоле", "titleLocation": "left", "hyperlink": true },
|
||||||
{ "input": "МногострочноеПоле", "path": "МногострочноеПоле", "multiLine": true, "height": 5, "title": "Комментарий" },
|
{ "input": "МногострочноеПоле", "path": "МногострочноеПоле", "multiLine": true, "height": 5, "title": "Комментарий" },
|
||||||
{ "input": "ПолеПароля", "path": "ПолеПароля", "passwordMode": true, "title": "Пароль" },
|
{ "input": "ПолеПароля", "path": "ПолеПароля", "passwordMode": true, "title": "Пароль" },
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
"title": "Мастер настройки",
|
"title": "Мастер настройки",
|
||||||
"properties": { "autoTitle": false },
|
"properties": { "autoTitle": false },
|
||||||
"elements": [
|
"elements": [
|
||||||
{ "pages": "СтраницыМастера", "pagesRepresentation": "None", "children": [
|
{ "pages": "СтраницыМастера", "pagesRepresentation": "None", "tooltip": "Страницы мастера настройки", "children": [
|
||||||
{ "page": "Шаг1", "title": "", "children": [
|
{ "page": "Шаг1", "title": "", "children": [
|
||||||
{ "input": "Параметр1", "path": "Параметр1" }
|
{ "input": "Параметр1", "path": "Параметр1" }
|
||||||
]},
|
]},
|
||||||
{ "page": "Шаг2", "title": "Результат", "children": [
|
{ "page": "Шаг2", "title": "Результат", "tooltip": "Шаг \"Результат\"", "children": [
|
||||||
{ "input": "Итог", "path": "Итог", "readOnly": true }
|
{ "input": "Итог", "path": "Итог", "readOnly": true }
|
||||||
]}
|
]}
|
||||||
]},
|
]},
|
||||||
|
|||||||
+6
@@ -17,6 +17,12 @@
|
|||||||
<v8:content>Обычное поле</v8:content>
|
<v8:content>Обычное поле</v8:content>
|
||||||
</v8:item>
|
</v8:item>
|
||||||
</Title>
|
</Title>
|
||||||
|
<ToolTip>
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>Введите значение поля</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</ToolTip>
|
||||||
<EditMode>EnterOnInput</EditMode>
|
<EditMode>EnterOnInput</EditMode>
|
||||||
<ContextMenu name="ОбычноеПолеКонтекстноеМеню" id="2"/>
|
<ContextMenu name="ОбычноеПолеКонтекстноеМеню" id="2"/>
|
||||||
<ExtendedTooltip name="ОбычноеПолеРасширеннаяПодсказка" id="3"/>
|
<ExtendedTooltip name="ОбычноеПолеРасширеннаяПодсказка" id="3"/>
|
||||||
|
|||||||
+12
@@ -10,6 +10,12 @@
|
|||||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1"/>
|
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1"/>
|
||||||
<ChildItems>
|
<ChildItems>
|
||||||
<Pages name="СтраницыМастера" id="1">
|
<Pages name="СтраницыМастера" id="1">
|
||||||
|
<ToolTip>
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>Страницы мастера настройки</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</ToolTip>
|
||||||
<PagesRepresentation>None</PagesRepresentation>
|
<PagesRepresentation>None</PagesRepresentation>
|
||||||
<ExtendedTooltip name="СтраницыМастераРасширеннаяПодсказка" id="2"/>
|
<ExtendedTooltip name="СтраницыМастераРасширеннаяПодсказка" id="2"/>
|
||||||
<ChildItems>
|
<ChildItems>
|
||||||
@@ -30,6 +36,12 @@
|
|||||||
<v8:content>Результат</v8:content>
|
<v8:content>Результат</v8:content>
|
||||||
</v8:item>
|
</v8:item>
|
||||||
</Title>
|
</Title>
|
||||||
|
<ToolTip>
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>Шаг "Результат"</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</ToolTip>
|
||||||
<ExtendedTooltip name="Шаг2РасширеннаяПодсказка" id="9"/>
|
<ExtendedTooltip name="Шаг2РасширеннаяПодсказка" id="9"/>
|
||||||
<ChildItems>
|
<ChildItems>
|
||||||
<InputField name="Итог" id="10">
|
<InputField name="Итог" id="10">
|
||||||
|
|||||||
Reference in New Issue
Block a user