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;
}