diff --git a/.claude/skills/form-remove/scripts/remove-form.ps1 b/.claude/skills/form-remove/scripts/remove-form.ps1 index b3dd0673..3795e04b 100644 --- a/.claude/skills/form-remove/scripts/remove-form.ps1 +++ b/.claude/skills/form-remove/scripts/remove-form.ps1 @@ -1,4 +1,4 @@ -# form-remove v1.7 — Remove form from 1C object +# form-remove v1.8 — Remove form from 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -64,6 +64,10 @@ foreach ($node in $formNodes) { $parent.RemoveChild($prev) | Out-Null } $parent.RemoveChild($node) | Out-Null + # Опустевший контейнер: остаётся отступ-whitespace, и XmlWriter пишет пару + # \n\t\t. Платформа пишет только + # (1394 самозакрывающихся на acc+erp, пустых пар ни в одной форме — 0). + if ($parent.SelectNodes("*").Count -eq 0) { $parent.IsEmpty = $true } break } } @@ -74,7 +78,9 @@ foreach ($node in $formNodes) { $formRefRe = "Form\.$([regex]::Escape($FormName))$" foreach ($node in $xmlDoc.SelectNodes("//md:*", $nsMgr)) { if ($node.LocalName -like "*Form" -and $node.InnerText -and $node.InnerText -match $formRefRe) { - $node.InnerText = "" + # IsEmpty, а не InnerText="": пустая строка сериализуется парой , а + # Конфигуратор пустых пар не пишет (0 на 476 942 XML корпуса) — нужен . + $node.IsEmpty = $true } } diff --git a/.claude/skills/form-remove/scripts/remove-form.py b/.claude/skills/form-remove/scripts/remove-form.py index 924f28b3..a433f25b 100644 --- a/.claude/skills/form-remove/scripts/remove-form.py +++ b/.claude/skills/form-remove/scripts/remove-form.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# form-remove v1.7 — Remove form from 1C object +# form-remove v1.8 — Remove form from 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -120,6 +120,10 @@ def main(): if parent.text and parent.text.strip() == "": parent.text = "" parent.remove(node) + # Опустевший контейнер: text="" сериализуется парой , + # а нужен — PS-порт через DOM даёт именно его. + if len(parent) == 0 and not (parent.text or "").strip(): + parent.text = None break # Clear any Default*/Auxiliary* form slot that pointed to the removed form @@ -130,7 +134,9 @@ def main(): if not isinstance(el.tag, str): continue if etree.QName(el).localname.endswith("Form") and el.text and ref_re.search(el.text): - el.text = "" + # text=None, а не "": пустая строка сериализуется парой , а + # Конфигуратор пустых пар не пишет (0 на 476 942 XML корпуса) — нужен . + el.text = None # Save with BOM save_xml_with_bom(tree, root_xml_full) diff --git a/.claude/skills/template-add/scripts/add-template.ps1 b/.claude/skills/template-add/scripts/add-template.ps1 index 44b015d9..5a6fbb19 100644 --- a/.claude/skills/template-add/scripts/add-template.ps1 +++ b/.claude/skills/template-add/scripts/add-template.ps1 @@ -1,4 +1,4 @@ -# template-add v1.17 — Add template to 1C object +# template-add v1.18 — Add template to 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -296,10 +296,12 @@ switch ($TemplateType) { [System.IO.File]::WriteAllText($templateFilePath, "", $encBom) } "SpreadsheetDocument" { + # Пустой макет — самозакрывающимся корнем: пустых пар платформа не пишет + # ни в одной форме (0 на 65 040 XML выгрузки acc_8.3.27, включая разнесённые + # по строкам). Для XML `` и `` тождественны по спецификации. $content = @" - - + "@ Write-XmlFile $templateFilePath $content $encBom } diff --git a/.claude/skills/template-add/scripts/add-template.py b/.claude/skills/template-add/scripts/add-template.py index ec874598..639b69d5 100644 --- a/.claude/skills/template-add/scripts/add-template.py +++ b/.claude/skills/template-add/scripts/add-template.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# template-add v1.17 — Add template to 1C object +# template-add v1.18 — Add template to 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -418,13 +418,15 @@ def main(): write_text_with_bom(template_file_path, "") elif template_type == "SpreadsheetDocument": + # Пустой макет — самозакрывающимся корнем: пустых пар платформа не пишет + # ни в одной форме (0 на 65 040 XML выгрузки acc_8.3.27, включая разнесённые + # по строкам). Для XML `` и `` тождественны по спецификации. content = ( '\n' '\n' - '' + ' xmlns:xs="http://www.w3.org/2001/XMLSchema"/>' ) write_xml_file(template_file_path, content) diff --git a/.claude/skills/template-remove/scripts/remove-template.ps1 b/.claude/skills/template-remove/scripts/remove-template.ps1 index 02543908..721fa4b6 100644 --- a/.claude/skills/template-remove/scripts/remove-template.ps1 +++ b/.claude/skills/template-remove/scripts/remove-template.ps1 @@ -1,4 +1,4 @@ -# template-remove v1.6 — Remove template from 1C object +# template-remove v1.7 — Remove template from 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -64,6 +64,10 @@ foreach ($node in $templateNodes) { $parent.RemoveChild($prev) | Out-Null } $parent.RemoveChild($node) | Out-Null + # Опустевший контейнер: остаётся отступ-whitespace, и XmlWriter пишет пару + # \n\t\t. Платформа пишет только + # (1394 самозакрывающихся на acc+erp, пустых пар ни в одной форме — 0). + if ($parent.SelectNodes("*").Count -eq 0) { $parent.IsEmpty = $true } break } } diff --git a/.claude/skills/template-remove/scripts/remove-template.py b/.claude/skills/template-remove/scripts/remove-template.py index ba1212e8..5b910fc5 100644 --- a/.claude/skills/template-remove/scripts/remove-template.py +++ b/.claude/skills/template-remove/scripts/remove-template.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# template-remove v1.6 — Remove template from 1C object +# template-remove v1.7 — Remove template from 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -120,6 +120,10 @@ def main(): if parent.text and parent.text.strip() == "": parent.text = "" parent.remove(node) + # Опустевший контейнер: text="" сериализуется парой , + # а нужен — PS-порт через DOM даёт именно его. + if len(parent) == 0 and not (parent.text or "").strip(): + parent.text = None break # Clear MainDataCompositionSchema if it pointed to this template diff --git a/tests/skills/cases/cfe-patch-method/before-handler.json b/tests/skills/cases/cfe-patch-method/before-handler.json index b69e31a2..80a7dcd3 100644 --- a/tests/skills/cases/cfe-patch-method/before-handler.json +++ b/tests/skills/cases/cfe-patch-method/before-handler.json @@ -27,5 +27,12 @@ "methodName": "ПриЗаписи", "interceptorType": "Before" }, - "expect": { "stdoutContains": "&Перед(\"ПриЗаписи\")" } + "expect": { + "stdoutContains": "&Перед(\"ПриЗаписи\")", + "preserves": { + "file": "Ext/Catalogs/Товары/Ext/ObjectModule.bsl", + "bom": true, + "eol": "crlf" + } + } } diff --git a/tests/skills/cases/epf-validate/snapshots/valid-with-template/МояОбработка/Templates/Макет/Ext/Template.xml b/tests/skills/cases/epf-validate/snapshots/valid-with-template/МояОбработка/Templates/Макет/Ext/Template.xml index fe6c3ab9..d16dc67b 100644 --- a/tests/skills/cases/epf-validate/snapshots/valid-with-template/МояОбработка/Templates/Макет/Ext/Template.xml +++ b/tests/skills/cases/epf-validate/snapshots/valid-with-template/МояОбработка/Templates/Макет/Ext/Template.xml @@ -1,3 +1,2 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/tests/skills/cases/form-edit/add-attribute.json b/tests/skills/cases/form-edit/add-attribute.json index 42df59f7..d95973ac 100644 --- a/tests/skills/cases/form-edit/add-attribute.json +++ b/tests/skills/cases/form-edit/add-attribute.json @@ -25,5 +25,17 @@ { "name": "СуммаИтого", "type": "decimal(15,2)" }, { "name": "ДатаНачала", "type": "date" } ] + }, + "expect": { + "preserves": { + "file": "DataProcessors/Реквизиты/Forms/Форма/Ext/Form.xml", + "bom": true, + "eol": "crlf", + "encoding": "UTF-8", + "finalNewline": false, + "noCR13": true, + "selfClose": "tight", + "noEmptyPairs": true + } } } diff --git a/tests/skills/cases/form-remove/basic.json b/tests/skills/cases/form-remove/basic.json index cb1c1505..7d7bc8b4 100644 --- a/tests/skills/cases/form-remove/basic.json +++ b/tests/skills/cases/form-remove/basic.json @@ -14,6 +14,16 @@ "objectName": "Catalogs/Товары", "formName": "ФормаЭлемента", "expect": { - "stdoutContains": "Форма ФормаЭлемента удалена" + "stdoutContains": "Форма ФормаЭлемента удалена", + "preserves": { + "file": "Catalogs/Товары.xml", + "bom": true, + "eol": "crlf", + "encoding": "UTF-8", + "finalNewline": false, + "noCR13": true, + "selfClose": "tight", + "noEmptyPairs": true + } } } diff --git a/tests/skills/cases/form-remove/snapshots/basic/Catalogs/Товары.xml b/tests/skills/cases/form-remove/snapshots/basic/Catalogs/Товары.xml index 5742f3a0..d56cd7a0 100644 --- a/tests/skills/cases/form-remove/snapshots/basic/Catalogs/Товары.xml +++ b/tests/skills/cases/form-remove/snapshots/basic/Catalogs/Товары.xml @@ -60,7 +60,7 @@ Begin DontUse Directly - + @@ -86,7 +86,6 @@ false false - - + \ No newline at end of file diff --git a/tests/skills/cases/meta-remove/basic.json b/tests/skills/cases/meta-remove/basic.json index 4bc80a70..34d8e4ad 100644 --- a/tests/skills/cases/meta-remove/basic.json +++ b/tests/skills/cases/meta-remove/basic.json @@ -8,5 +8,17 @@ } ], "params": { "object": "Catalog.Удалить" }, - "args_extra": ["-Force"] + "args_extra": ["-Force"], + "expect": { + "preserves": { + "file": "Configuration.xml", + "bom": true, + "eol": "crlf", + "encoding": "UTF-8", + "finalNewline": false, + "noCR13": true, + "selfClose": "tight", + "noEmptyPairs": true + } + } } diff --git a/tests/skills/cases/skd-edit/add-field.json b/tests/skills/cases/skd-edit/add-field.json index 65b1a479..ce78d118 100644 --- a/tests/skills/cases/skd-edit/add-field.json +++ b/tests/skills/cases/skd-edit/add-field.json @@ -17,5 +17,17 @@ "templatePath": "Template.xml", "operation": "add-field", "value": "Цена: decimal(15,2)" + }, + "expect": { + "preserves": { + "file": "Template.xml", + "bom": true, + "eol": "crlf", + "encoding": "UTF-8", + "finalNewline": false, + "noCR13": true, + "selfClose": "tight", + "noEmptyPairs": true + } } } diff --git a/tests/skills/cases/support-edit/set-editable.json b/tests/skills/cases/support-edit/set-editable.json index 23961e4d..319c7549 100644 --- a/tests/skills/cases/support-edit/set-editable.json +++ b/tests/skills/cases/support-edit/set-editable.json @@ -9,6 +9,10 @@ "editable" ], "expect": { - "stdoutContains": "редактируется с сохранением поддержки" + "stdoutContains": "редактируется с сохранением поддержки", + "preserves": { + "file": "Ext/ParentConfigurations.bin", + "bom": true + } } } diff --git a/tests/skills/cases/template-add/snapshots/basic/МояОбработка/Templates/Макет/Ext/Template.xml b/tests/skills/cases/template-add/snapshots/basic/МояОбработка/Templates/Макет/Ext/Template.xml index fe6c3ab9..d16dc67b 100644 --- a/tests/skills/cases/template-add/snapshots/basic/МояОбработка/Templates/Макет/Ext/Template.xml +++ b/tests/skills/cases/template-add/snapshots/basic/МояОбработка/Templates/Макет/Ext/Template.xml @@ -1,3 +1,2 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/tests/skills/cases/template-remove/basic.json b/tests/skills/cases/template-remove/basic.json index f77f76a1..7df0f78c 100644 --- a/tests/skills/cases/template-remove/basic.json +++ b/tests/skills/cases/template-remove/basic.json @@ -10,5 +10,17 @@ "args": { "-ObjectName": "МояОбработка", "-TemplateName": "Макет", "-TemplateType": "SpreadsheetDocument", "-SrcDir": "{workDir}" } } ], - "params": { "objectName": "МояОбработка", "templateName": "Макет" } + "params": { "objectName": "МояОбработка", "templateName": "Макет" }, + "expect": { + "preserves": { + "file": "МояОбработка.xml", + "bom": true, + "eol": "crlf", + "encoding": "UTF-8", + "finalNewline": false, + "noCR13": true, + "selfClose": "tight", + "noEmptyPairs": true + } + } } diff --git a/tests/skills/cases/template-remove/snapshots/basic/МояОбработка.xml b/tests/skills/cases/template-remove/snapshots/basic/МояОбработка.xml index 1d067969..7074d760 100644 --- a/tests/skills/cases/template-remove/snapshots/basic/МояОбработка.xml +++ b/tests/skills/cases/template-remove/snapshots/basic/МояОбработка.xml @@ -23,7 +23,6 @@ - - + \ No newline at end of file diff --git a/tests/skills/cases/template-remove/snapshots/remove-from-report/МойОтчёт.xml b/tests/skills/cases/template-remove/snapshots/remove-from-report/МойОтчёт.xml index a205f8eb..cfd5023f 100644 --- a/tests/skills/cases/template-remove/snapshots/remove-from-report/МойОтчёт.xml +++ b/tests/skills/cases/template-remove/snapshots/remove-from-report/МойОтчёт.xml @@ -29,7 +29,6 @@ - - + \ No newline at end of file diff --git a/tests/skills/cases/xdto-edit/set-synonym.json b/tests/skills/cases/xdto-edit/set-synonym.json index bdf9fea8..953c7227 100644 --- a/tests/skills/cases/xdto-edit/set-synonym.json +++ b/tests/skills/cases/xdto-edit/set-synonym.json @@ -25,6 +25,16 @@ "expect": { "files": [ "XDTOPackages/types.xml" - ] + ], + "preserves": { + "file": "XDTOPackages/types.xml", + "bom": true, + "eol": "crlf", + "encoding": "UTF-8", + "finalNewline": false, + "noCR13": true, + "selfClose": "tight", + "noEmptyPairs": true + } } } diff --git a/tests/skills/runner.mjs b/tests/skills/runner.mjs index f1e716bb..6256b073 100644 --- a/tests/skills/runner.mjs +++ b/tests/skills/runner.mjs @@ -404,8 +404,20 @@ function checkPreserves(workDir, spec) { if (spaced) errs.push(`preserves: expected tight self-closing, got ${spaced.length}× spaced (e.g. ${spaced[0].slice(0, 60)})`); } if (spec.noEmptyPairs) { - const pairs = text.match(/<([\w:.]+)([^<>]*)><\/\1>/g); - if (pairs) errs.push(`preserves: expected self-closing, got ${pairs.length}× empty pair (e.g. ${pairs[0].slice(0, 60)})`); + const pairs = text.match(/<([\w:.]+)([^<>]*)><\/\1>/g) || []; + // Плюс пара, разнесённая по строкам: опустевший контейнер выглядит как + // `\n\t\t` и смежной проверкой НЕ ловился — так + // прошёл незамеченным дефект form-remove/template-remove. Платформа пишет + // только `` (1394 на acc+erp, пустых пар 0 в обеих формах). + // Дискриминатор — перевод строки внутри: значащий пробельный текст-узел + // (` `) его не содержит, + // поэтому под проверку не попадает. + const multiline = text.match(/<([\w:.]+)([^<>]*)>[ \t]*\r?\n\s*<\/\1>/g) || []; + const all = [...pairs, ...multiline]; + if (all.length) { + const sample = all[0].replace(/\s+/g, ' ').slice(0, 60); + errs.push(`preserves: expected self-closing, got ${all.length}× empty pair (e.g. ${sample})`); + } } return errs; }