From 0737a79522d77af80b5c5b59493b61ad868a563a Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Fri, 14 Aug 2026 13:10:53 +0300 Subject: [PATCH] =?UTF-8?q?fix(cfe-borrow):=20=D1=81=D0=B5=D0=BA=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20=D1=84=D0=BE=D1=80=D0=BC=D1=8B=20=D0=BE=D1=82?= =?UTF-8?q?=D0=B1=D0=B8=D1=80=D0=B0=D1=8E=D1=82=D1=81=D1=8F=20=D0=BF=D0=BE?= =?UTF-8?q?=20=D0=B8=D0=BC=D0=B5=D0=BD=D0=B8,=20=D0=B0=20=D0=BD=D0=B5=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D0=BF=D0=BE=D0=B7=D0=B8=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Свойства уровня формы собирались до первой «визуальной» секции. В этот список входил CommandSet, который в реальных формах стоит В СЕРЕДИНЕ блока свойств: у всех 794 форм документов ERP с CommandSet он предшествует AutoCommandBar, а AutoTime/UsePostingMode/RepostOnWrite идут после него. Отсечка теряла весь хвост — в одном ERP это 2062 формы. Отказа загрузки это не давало: платформа принимает форму и молча подставляет дефолты. На Document.ЧекККМ.ФормаДокумента (УТ) AutoTime DontUse становился CurrentOrLast, UsePostingMode Regular — Auto; исключённые стандартные команды (Провести, Записать, Отменить проведение) возвращались в панель. Граница теперь — AutoCommandBar: он есть в 100% из 30223 форм корпуса, и ни одно свойство никогда не стоит после него. Отбрасываются явным списком Events/Attributes/Commands/Parameters/CommandInterface и свойства, значение которых — имя реквизита формы (ReportResult, DetailsData, VariantAppearance, GroupList): реквизиты не заимствуются, ссылка повисла бы. Заодно разобран конфаунд коммита 7abe26af, менявшего три вещи разом с одной атрибуцией ошибки. По эталонам Конфигуратора («Добавить в расширение»): - корневой CommandSet переносится дословно — вырезание было ошибкой; - вложенный CommandSet выбрасывается ЦЕЛИКОМ, а не опустошается до пустого контейнера, как делалось; - RowPictureDataPath — путь к данным, а не индекс картинки: сохраняется при заимствовании основного реквизита (Объект.Товары.РасхождениеЗаказ) и вырезается без него (Список.DefaultPicture). Переехал в formBindingDataTags. Спецификация форм утверждала, что свойства идут до CommandSet/AutoCommandBar — именно это заблуждение и было закодировано. Заменено фактическим порядком секций, выведенным из корпуса (0 нарушений на 30223 формах). Там же поправлены значения перечислений AutoTime/UsePostingMode/ReportFormType, которых в корпусе нет ни одного. Co-Authored-By: Claude Opus 5 (1M context) --- .../skills/cfe-borrow/scripts/cfe-borrow.ps1 | 51 ++++++++++++------ .../skills/cfe-borrow/scripts/cfe-borrow.py | 53 +++++++++++++------ docs/1c-form-spec.md | 41 +++++++++++--- docs/form-dsl-spec.md | 4 +- 4 files changed, 108 insertions(+), 41 deletions(-) diff --git a/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 b/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 index 2eccb3ba..9a766a1c 100644 --- a/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 +++ b/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 @@ -1,4 +1,4 @@ -# cfe-borrow v1.25 — Borrow objects from configuration into extension (CFE) (не переносить FoldersOnTop — платформа его не хранит) +# cfe-borrow v1.26 — Borrow objects from configuration into extension (CFE) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)][string]$ExtensionPath, @@ -16,9 +16,18 @@ function Warn([string]$msg) { Write-Host "[WARN] $msg" } # Form data-binding tags (value = attribute path). A binding survives only if its root # attribute is borrowed into the form's ; otherwise it must be stripped or the # platform rejects the form with "Неверный путь к данным" on load. -$script:formBindingDataTags = @('DataPath','TitleDataPath','FooterDataPath','HeaderDataPath','MultipleValueDataPath','MultipleValuePresentDataPath') +# RowPictureDataPath тоже путь к данным («Объект.Товары.РасхождениеЗаказ», «Список.DefaultPicture»), +# а не индекс картинки: эталон Конфигуратора сохраняет его с заимствованным основным реквизитом +# и выбрасывает без него — то же правило, что у остальных путей. +$script:formBindingDataTags = @('DataPath','TitleDataPath','FooterDataPath','HeaderDataPath','MultipleValueDataPath','MultipleValuePresentDataPath','RowPictureDataPath') # Picture-path binding tags (value = picture index path, never a data attribute) — always stripped in the skeleton. -$script:formBindingPictureTags = @('RowPictureDataPath','MultipleValuePictureDataPath') +$script:formBindingPictureTags = @('MultipleValuePictureDataPath') + +# Прямые дети
, которые в заимствованную форму не переносятся. +# Структурные секции: AutoCommandBar и ChildItems забираются отдельно, остальные выбрасываются целиком. +$script:formStructuralSections = @('Events','Attributes','Commands','Parameters','CommandInterface') +# Свойства формы, значение которых — имя реквизита формы (реквизиты не заимствуются, ссылка повиснет). +$script:formAttributeRefProps = @('ReportResult','DetailsData','VariantAppearance','GroupList') # Strip data-binding tags whose root attribute isn't borrowed. # $keepObjekt=$true (BorrowMainAttribute): keep Объект.* data bindings, strip the rest. @@ -687,25 +696,30 @@ function Borrow-Form { # (e.g. a 2.13 form inside a 2.17 extension). The platform itself upgrades the form to the root version. $formVersion = $script:formatVersion - # Find direct children: form properties, AutoCommandBar, ChildItems + # Find direct children: form properties, AutoCommandBar, ChildItems. + # Секции формы отбираются по имени, а не по позиции: свойства лежат и до, и после + # (корпусная проверка: у всех 794 форм документов ERP с CommandSet он стоит раньше AutoCommandBar, + # а AutoTime/UsePostingMode/RepostOnWrite — после него). Позиционная отсечка теряла весь хвост, + # и платформа молча подставляла дефолты вместо потерянных свойств. $srcAutoCmd = $null $srcChildItems = $null $formProps = @() - $reachedVisual = $false foreach ($fc in $srcFormEl.ChildNodes) { if ($fc.NodeType -ne 'Element') { continue } if ($fc.LocalName -eq 'AutoCommandBar' -and -not $srcAutoCmd) { - $reachedVisual = $true; $srcAutoCmd = $fc; continue + $srcAutoCmd = $fc; continue } if ($fc.LocalName -eq 'ChildItems' -and -not $srcChildItems) { - $reachedVisual = $true; $srcChildItems = $fc; continue - } - if ($fc.LocalName -eq 'Events' -or $fc.LocalName -eq 'Attributes' -or $fc.LocalName -eq 'Commands' -or $fc.LocalName -eq 'Parameters' -or $fc.LocalName -eq 'CommandSet') { - $reachedVisual = $true; continue - } - if (-not $reachedVisual) { - $formProps += $fc.OuterXml + $srcChildItems = $fc; continue } + # Структурные секции: в расширении их содержимое недействительно (обработчики, команды и + # параметры базовой формы, ссылки командного интерфейса на команды базовой конфигурации). + if ($script:formStructuralSections -ccontains $fc.LocalName) { continue } + # Свойства, значение которых — имя реквизита формы. Реквизиты в заимствованную форму не + # переносятся, поэтому Конфигуратор такие свойства выбрасывает (проверено на форме отчёта: + # ReportResult и DetailsData выброшены, CustomSettingsFolder — имя элемента — сохранён). + if ($script:formAttributeRefProps -ccontains $fc.LocalName) { continue } + $formProps += $fc.OuterXml } # Get OuterXml and strip redundant namespace redeclarations (they're on root ) @@ -723,8 +737,10 @@ function Borrow-Form { $autoCmdXml = [regex]::Replace($autoCmdXml, $nsStripPattern, '') $autoCmdXml = [regex]::Replace($autoCmdXml, '[^<]*', '0') $autoCmdXml = $autoCmdXml -replace 'true', 'false' - # Strip ExcludedCommand (references to standard commands invalid in extension) - $autoCmdXml = [regex]::Replace($autoCmdXml, '\s*[^<]*', '') + # Вложенный CommandSet выбрасывается целиком, а не опустошается: Конфигуратор в заимствованной + # форме оставляет только корневой (тот идёт свойством формы, здесь его нет). + $autoCmdXml = [regex]::Replace($autoCmdXml, '(?s)\s*.*?', '') + $autoCmdXml = [regex]::Replace($autoCmdXml, '\s*', '') # Strip data-binding tags whose root attribute isn't borrowed $autoCmdXml = Strip-FormBindings $autoCmdXml ([bool]$BorrowMainAttr) if (-not $BorrowMainAttr) { $autoCmdXml = Rewrite-ChoiceParameterLinks $autoCmdXml $srcAttrUuids } @@ -741,8 +757,9 @@ function Borrow-Form { # (DataPath/TitleDataPath/FooterDataPath/HeaderDataPath/MultipleValue*/RowPicture*) $childItemsXml = Strip-FormBindings $childItemsXml ([bool]$BorrowMainAttr) if (-not $BorrowMainAttr) { $childItemsXml = Rewrite-ChoiceParameterLinks $childItemsXml $srcAttrUuids } - # Strip ExcludedCommand in nested AutoCommandBars (references to standard commands invalid in extension) - $childItemsXml = [regex]::Replace($childItemsXml, '\s*[^<]*', '') + # Вложенные CommandSet (у таблиц, полей табличного документа и т.п.) — целиком, см. выше + $childItemsXml = [regex]::Replace($childItemsXml, '(?s)\s*.*?', '') + $childItemsXml = [regex]::Replace($childItemsXml, '\s*', '') # Strip TypeLink blocks with human-readable DataPath (Items.XXX — can't convert to UUID) $childItemsXml = [regex]::Replace($childItemsXml, '(?s)\s*\s*Items\.[^<]*.*?', '') # Strip element-level Events (base form handlers not in extension) diff --git a/.claude/skills/cfe-borrow/scripts/cfe-borrow.py b/.claude/skills/cfe-borrow/scripts/cfe-borrow.py index 2e8eb7ac..8eaa312f 100644 --- a/.claude/skills/cfe-borrow/scripts/cfe-borrow.py +++ b/.claude/skills/cfe-borrow/scripts/cfe-borrow.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# cfe-borrow v1.25 — Borrow objects from configuration into extension (CFE) (не переносить FoldersOnTop — платформа его не хранит) +# cfe-borrow v1.26 — Borrow objects from configuration into extension (CFE) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -39,9 +39,18 @@ V8_NS = "http://v8.1c.ru/8.1/data/core" # Form data-binding tags (value = attribute path). A binding survives only if its root # attribute is borrowed into the form's ; otherwise it must be stripped or the # platform rejects the form with "Неверный путь к данным" on load. -FORM_BINDING_DATA_TAGS = ["DataPath", "TitleDataPath", "FooterDataPath", "HeaderDataPath", "MultipleValueDataPath", "MultipleValuePresentDataPath"] +# RowPictureDataPath тоже путь к данным («Объект.Товары.РасхождениеЗаказ», «Список.DefaultPicture»), +# а не индекс картинки: эталон Конфигуратора сохраняет его с заимствованным основным реквизитом +# и выбрасывает без него — то же правило, что у остальных путей. +FORM_BINDING_DATA_TAGS = ["DataPath", "TitleDataPath", "FooterDataPath", "HeaderDataPath", "MultipleValueDataPath", "MultipleValuePresentDataPath", "RowPictureDataPath"] # Picture-path binding tags (value = picture index path, never a data attribute) — always stripped in the skeleton. -FORM_BINDING_PICTURE_TAGS = ["RowPictureDataPath", "MultipleValuePictureDataPath"] +FORM_BINDING_PICTURE_TAGS = ["MultipleValuePictureDataPath"] + +# Прямые дети , которые в заимствованную форму не переносятся. +# Структурные секции: AutoCommandBar и ChildItems забираются отдельно, остальные выбрасываются целиком. +FORM_STRUCTURAL_SECTIONS = ("Events", "Attributes", "Commands", "Parameters", "CommandInterface") +# Свойства формы, значение которых — имя реквизита формы (реквизиты не заимствуются, ссылка повиснет). +FORM_ATTRIBUTE_REF_PROPS = ("ReportResult", "DetailsData", "VariantAppearance", "GroupList") def strip_form_bindings(xml, keep_objekt): @@ -1500,25 +1509,34 @@ def main(): # (e.g. a 2.13 form inside a 2.17 extension). The platform upgrades the form to the root version. form_version = format_version + # Секции формы отбираются по имени, а не по позиции: свойства лежат и до, и после + # (корпусная проверка: у всех 794 форм документов ERP с CommandSet он стоит + # раньше AutoCommandBar, а AutoTime/UsePostingMode/RepostOnWrite — после него). Позиционная + # отсечка теряла весь хвост, и платформа молча подставляла дефолты вместо потерянных свойств. src_auto_cmd = None form_props = [] - reached_visual = False for fc in src_form_el: if not isinstance(fc.tag, str): continue ln = localname(fc) if ln == "AutoCommandBar" and src_auto_cmd is None: - reached_visual = True src_auto_cmd = fc continue - if ln in ("ChildItems", "Events", "Attributes", "Commands", "Parameters", "CommandSet"): - reached_visual = True + # ChildItems забирается отдельным поиском ниже + if ln == "ChildItems": continue - if not reached_visual: - # Form-level properties before AutoCommandBar (WindowOpeningMode, AutoFillCheck, etc.) - # with_tail=False — хвостовой пробел принадлежит родителю; с ним в вывод попадали - # пустые строки, которых нет у PS-порта (OuterXml хвост не включает). - form_props.append(decode_numeric_entities(etree.tostring(fc, encoding="unicode", with_tail=False))) + # Структурные секции: в расширении их содержимое недействительно (обработчики, команды и + # параметры базовой формы, ссылки командного интерфейса на команды базовой конфигурации). + if ln in FORM_STRUCTURAL_SECTIONS: + continue + # Свойства, значение которых — имя реквизита формы. Реквизиты в заимствованную форму не + # переносятся, поэтому Конфигуратор такие свойства выбрасывает (проверено на форме отчёта: + # ReportResult и DetailsData выброшены, CustomSettingsFolder — имя элемента — сохранён). + if ln in FORM_ATTRIBUTE_REF_PROPS: + continue + # with_tail=False — хвостовой пробел принадлежит родителю; с ним в вывод попадали + # пустые строки, которых нет у PS-порта (OuterXml хвост не включает). + form_props.append(decode_numeric_entities(etree.tostring(fc, encoding="unicode", with_tail=False))) ns_strip_pattern = re.compile(r'\s+xmlns(?::\w+)?="[^"]*"') @@ -1533,8 +1551,10 @@ def main(): auto_cmd_xml = ns_strip_pattern.sub("", auto_cmd_xml) auto_cmd_xml = re.sub(r'[^<]*', '0', auto_cmd_xml) auto_cmd_xml = auto_cmd_xml.replace('true', 'false') - # Strip ExcludedCommand (references to standard commands invalid in extension) - auto_cmd_xml = re.sub(r'\s*[^<]*', '', auto_cmd_xml) + # Вложенный CommandSet выбрасывается целиком, а не опустошается: Конфигуратор в заимствованной + # форме оставляет только корневой (тот идёт свойством формы, здесь его нет). + auto_cmd_xml = re.sub(r'(?s)\s*.*?', '', auto_cmd_xml) + auto_cmd_xml = re.sub(r'\s*', '', auto_cmd_xml) # Strip data-binding tags whose root attribute isn't borrowed auto_cmd_xml = strip_form_bindings(auto_cmd_xml, borrow_main_attr) if not borrow_main_attr: @@ -1557,8 +1577,9 @@ def main(): child_items_xml = strip_form_bindings(child_items_xml, borrow_main_attr) if not borrow_main_attr: child_items_xml = rewrite_choice_parameter_links(child_items_xml, src_attr_uuids) - # Strip ExcludedCommand in nested AutoCommandBars (references to standard commands invalid in extension) - child_items_xml = re.sub(r'\s*[^<]*', '', child_items_xml) + # Вложенные CommandSet (у таблиц, полей табличного документа и т.п.) — целиком, см. выше + child_items_xml = re.sub(r'(?s)\s*.*?', '', child_items_xml) + child_items_xml = re.sub(r'\s*', '', child_items_xml) # Strip TypeLink blocks with human-readable DataPath (Items.XXX) child_items_xml = re.sub(r'\s*\s*Items\.[^<]*.*?', '', child_items_xml, flags=re.DOTALL) # Strip element-level Events diff --git a/docs/1c-form-spec.md b/docs/1c-form-spec.md index 1793f2cf..b4cbe353 100644 --- a/docs/1c-form-spec.md +++ b/docs/1c-form-spec.md @@ -212,22 +212,51 @@ CommonForms регистрируются в `Configuration.xml`: ``` - ┌─ Свойства формы (необязательные, в произвольном порядке) + ┌─ Свойства формы (необязательные) — часть до , часть после ├─ — исключённые стандартные команды + ├─ …продолжение свойств формы (AutoTime, UsePostingMode, RepostOnWrite, ReportResult…) ├─ — главная командная панель (обязательный, id="-1") ├─ — обработчики событий формы ├─ — дерево UI-элементов ├─ — реквизиты формы + ├─ — пользовательские команды ├─ — параметры открытия формы - └─ — пользовательские команды + └─ — командный интерфейс формы ``` +Порядок **строгий**, а не произвольный: на 30223 формах корпуса (ERP, УТ, УНФ 8.5, БП, общие +формы) не встретилось ни одной пары элементов, идущей в разном порядке в разных формах. + +Важно: `` стоит **в середине блока свойств**, а не перед ним — свойства есть и до, и +после него. У всех 794 форм документов ERP с `CommandSet` он предшествует `AutoCommandBar`, а +`AutoTime`/`UsePostingMode`/`RepostOnWrite` идут **после** `CommandSet`. + +Надёжная граница между свойствами и структурными секциями — **``**: он присутствует +в 100% форм корпуса, и ни одно свойство никогда не стоит после него. Код, отделяющий свойства от +структуры, должен опираться на него, а не на «первую встреченную секцию». + +Полный наблюдаемый порядок: + +``` +Title · Width · Height · WindowOpeningMode · EnterKeyBehavior · AutoSaveDataInSettings · +SaveDataInSettings · SaveWindowSettings · SettingsStorage · AutoTitle · AutoURL · Group · +ChildItemsWidth · ChildrenAlign · HorizontalSpacing · Scale · VerticalSpacing · HorizontalAlign · +VerticalAlign · AutoFillCheck · Customizable · Enabled · CommandBarLocation · VerticalScroll · +ScalingMode · ConversationsRepresentation · MobileDeviceCommandBarContent · ScaleVariant · +CommandSet · GroupList · ShowTitle · CreateButtonsGroupPicture · CreateButtonsGroupTitle · +ShowCloseButton · CollapseItemsByImportanceVariant · WindowViewMode · ShowCommandBar · AutoTime · +ReportResult · DetailsData · ReportFormType · UseForFoldersAndItems · UsePostingMode · +RepostOnWrite · VariantAppearance · AutoShowState · CustomSettingsFolder · ReportResultViewMode · +ViewModeApplicationOnSetReportResult · AutoCommandBar · Events · ChildItems · Attributes · +Commands · Parameters · CommandInterface +``` + --- ## 3. Свойства формы -Прямые дочерние элементы `
` (все необязательные, указываются до ``/``): +Прямые дочерние элементы `` — все необязательные, порядок между ними фиксирован (см. §2): ### Общие свойства (все типы форм) @@ -258,8 +287,8 @@ CommonForms регистрируются в `Configuration.xml`: | Элемент | Значения | Описание | |---------|----------|----------| -| `` | `CurrentOrLast`, `Current`, `Last` | Управление временем документа | -| `` | `Auto`, `Postings`, `Movements` | Режим проведения | +| `` | `CurrentOrLast`, `DontUse`, `Current`, `Last` | Управление временем документа. В корпусе встречаются только `CurrentOrLast` и `DontUse` | +| `` | `Auto`, `Regular` | Режим проведения | | `` | `true`/`false` | Перепроведение при записи | ### Свойства справочников (Catalogs, ChartsOfAccounts) @@ -274,7 +303,7 @@ CommonForms регистрируются в `Configuration.xml`: |---------|----------|----------| | `` | string | Имя реквизита результата (`Результат`) | | `` | string | Имя реквизита расшифровки (`ДанныеРасшифровки`) | -| `` | `Main`, `Settings`, `Choice` | Тип формы отчёта | +| `` | `Main`, `Settings`, `Variant` | Тип формы отчёта | | `` | `Auto`, `Show`, `Hide` | Автоотображение состояния | | `` | `Auto`, `Table`, `Spreadsheet` | Режим отображения результата | | `` | `Auto`, `Always`, `Never` | Применение режима | diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index 25a6a5a8..82f250b8 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -55,8 +55,8 @@ | `commandBarLocation` | `` | `Top`, `Bottom`, `None` | | `saveDataInSettings` | `` | `UseList`, `Use`, `DontUse` | | `autoSaveDataInSettings` | `` | `Use`, `DontUse` | -| `autoTime` | `` | `CurrentOrLast`, `Current`, `Last` | -| `usePostingMode` | `` | `Auto`, `Postings`, `Movements` | +| `autoTime` | `` | `CurrentOrLast`, `DontUse`, `Current`, `Last` | +| `usePostingMode` | `` | `Auto`, `Regular` | | `repostOnWrite` | `` | `true` / `false` | | `autoURL` | `` | `true` / `false` | | `enabled` | `` | `true` / `false` — доступность всей формы (редкое; форма-уровень) |