diff --git a/.claude/skills/form-edit/scripts/form-edit.ps1 b/.claude/skills/form-edit/scripts/form-edit.ps1
index ae684300..09e71fda 100644
--- a/.claude/skills/form-edit/scripts/form-edit.ps1
+++ b/.claude/skills/form-edit/scripts/form-edit.ps1
@@ -1,4 +1,4 @@
-# form-edit v1.6 — Edit 1C managed form elements
+# form-edit v1.7 — Edit 1C managed form elements
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -1387,6 +1387,9 @@ if ($def.elementEvents -and $def.elementEvents.Count -gt 0) {
$content = $xmlDoc.OuterXml
# Ensure encoding declaration is uppercase UTF-8
$content = $content -replace '^<\?xml version="1.0" encoding="utf-8"\?>', ''
+# Пустой элемент: OuterXml (как и XmlWriter) пишет ``, Конфигуратор — ``.
+# Гард на CDATA/комментарии: только там ` />` может быть содержимым, а не концом тега.
+if ($content -notmatch '', '/>') }
$enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($resolvedFormPath, $content, $enc)
diff --git a/.claude/skills/form-edit/scripts/form-edit.py b/.claude/skills/form-edit/scripts/form-edit.py
index 5e684077..1d0c6daa 100644
--- a/.claude/skills/form-edit/scripts/form-edit.py
+++ b/.claude/skills/form-edit/scripts/form-edit.py
@@ -1,4 +1,4 @@
-# form-edit v1.6 — Edit 1C managed form elements (Python port)
+# form-edit v1.7 — Edit 1C managed form elements (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import json
diff --git a/.claude/skills/meta-edit/scripts/meta-edit.ps1 b/.claude/skills/meta-edit/scripts/meta-edit.ps1
index 377e05b2..5cf448b4 100644
--- a/.claude/skills/meta-edit/scripts/meta-edit.ps1
+++ b/.claude/skills/meta-edit/scripts/meta-edit.ps1
@@ -1,4 +1,4 @@
-# meta-edit v1.26 — Edit existing 1C metadata object XML
+# meta-edit v1.27 — Edit existing 1C metadata object XML
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$DefinitionFile,
diff --git a/.claude/skills/meta-edit/scripts/meta-edit.py b/.claude/skills/meta-edit/scripts/meta-edit.py
index 1c693702..de20f404 100644
--- a/.claude/skills/meta-edit/scripts/meta-edit.py
+++ b/.claude/skills/meta-edit/scripts/meta-edit.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# meta-edit v1.26 — Edit existing 1C metadata object XML
+# meta-edit v1.27 — Edit existing 1C metadata object XML
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -3094,7 +3094,9 @@ def add_predefined_items(items):
item_list = items if isinstance(items, list) else [items]
items_xml = ''.join(build_predef_item_xml('\t', it, code_type) for it in item_list)
if os.path.exists(path):
- with open(path, 'r', encoding='utf-8-sig') as f:
+ # newline='' => без трансляции переводов строк: иначе CRLF молча схлопнется
+ # в LF при чтении и файл будет переписан в LF (#44/#46/#47).
+ with open(path, 'r', encoding='utf-8-sig', newline='') as f:
text = f.read()
text = text.replace('', items_xml + '')
else:
@@ -3103,7 +3105,7 @@ def add_predefined_items(items):
'xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" '
'xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
f'xsi:type="{xsi_type}" version="{version}">\r\n')
- text = hdr + items_xml + '\r\n'
+ text = hdr + items_xml + ''
with open(path, 'wb') as f:
f.write(b'\xef\xbb\xbf')
f.write(text.encode('utf-8'))
diff --git a/.claude/skills/skd-info/scripts/skd-info.ps1 b/.claude/skills/skd-info/scripts/skd-info.ps1
index 10be42c1..dc3fe7e5 100644
--- a/.claude/skills/skd-info/scripts/skd-info.ps1
+++ b/.claude/skills/skd-info/scripts/skd-info.ps1
@@ -1,4 +1,4 @@
-# skd-info v1.8 — Analyze 1C DCS structure
+# skd-info v1.9 — Analyze 1C DCS structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory=$true)]
diff --git a/.claude/skills/skd-info/scripts/skd-info.py b/.claude/skills/skd-info/scripts/skd-info.py
index bec0476e..a537618a 100644
--- a/.claude/skills/skd-info/scripts/skd-info.py
+++ b/.claude/skills/skd-info/scripts/skd-info.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# skd-info v1.8 — Analyze 1C DCS structure
+# skd-info v1.9 — Analyze 1C DCS structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1802,7 +1802,10 @@ def main():
if not os.path.isabs(out_path):
out_path = os.path.join(os.getcwd(), out_path)
with open(out_path, "w", encoding="utf-8-sig") as fh:
- fh.write("\n".join(result))
+ # Хвостовой перевод строки — как у PS-порта (WriteAllLines его добавляет).
+ # Это текстовый отчёт, а не XML метаданных: канон Конфигуратора сюда не
+ # относится, важен лишь паритет портов.
+ fh.write("\n".join(result) + "\n")
print(f"Written {total_lines} lines to {args.OutFile}")
sys.exit(0)
diff --git a/tests/skills/README.md b/tests/skills/README.md
index 92ca6db6..a1cf5f94 100644
--- a/tests/skills/README.md
+++ b/tests/skills/README.md
@@ -252,10 +252,18 @@ ibcmd-проход автоматически `○ skipped`, если рядом
|---|---|
| `file` | Путь к файлу относительно `workDir` (обязателен) |
| `bom` | `true`/`false` — наличие UTF-8 BOM |
-| `eol` | `"crlf"` / `"lf"` |
+| `eol` | `"crlf"` / `"lf"` — проверяются ОДИНОЧНЫЕ переводы строк, поэтому смешанный выход падает |
| `encoding` | Ожидаемое значение в XML-декларации, напр. `"UTF-8"` |
| `finalNewline` | `true`/`false` — перевод строки в конце файла |
| `noCR13` | `true` — в выходе не должно быть литерала `
` |
+| `selfClose` | `"tight"` — пустой элемент только как ``, без пробела перед `/>` |
+| `noEmptyPairs` | `true` — пустого элемента в форме `` быть не должно |
+
+Канон выгрузки Конфигуратора (issue #57), измеренный на чистой выгрузке пустой ИБ на Windows
+и macOS и на 8 выгрузках в `cfsrc/`: **CRLF, BOM, последний байт `>` (без перевода строки),
+`` без пробела, ноль пустых пар, `encoding="UTF-8"`.** Для файла, который навык СОЗДАЁТ,
+ожидается канон; для файла, который он ПРАВИТ, — стиль входного файла (контракт #44/#46/#47),
+поэтому в кейсах `roundtrip-crlf-preserve` ожидания могут отличаться от канона.
`preserves` и эталон **дополняют** друг друга: первый следит за байтовым стилем файла, второй — за
структурой содержимого. Наличие одного не отменяет необходимости другого.
diff --git a/tests/skills/cases/cf-edit/fixtures/add-bot/Configuration.xml b/tests/skills/cases/cf-edit/fixtures/add-bot/Configuration.xml
index 3a873eda..5eef6df1 100644
--- a/tests/skills/cases/cf-edit/fixtures/add-bot/Configuration.xml
+++ b/tests/skills/cases/cf-edit/fixtures/add-bot/Configuration.xml
@@ -48,8 +48,8 @@
Russian
-
-
+
+
Version8_3_24
Language.Русский
Managed
diff --git a/tests/skills/cases/cf-edit/fixtures/crlf-config/Configuration.xml b/tests/skills/cases/cf-edit/fixtures/crlf-config/Configuration.xml
index a45d0db9..79a9e13b 100644
--- a/tests/skills/cases/cf-edit/fixtures/crlf-config/Configuration.xml
+++ b/tests/skills/cases/cf-edit/fixtures/crlf-config/Configuration.xml
@@ -48,8 +48,8 @@
Russian
-
-
+
+
false
false
diff --git a/tests/skills/cases/cf-edit/snapshots/add-bot/Configuration.xml b/tests/skills/cases/cf-edit/snapshots/add-bot/Configuration.xml
index 2e9a7fce..0f4ddd5e 100644
--- a/tests/skills/cases/cf-edit/snapshots/add-bot/Configuration.xml
+++ b/tests/skills/cases/cf-edit/snapshots/add-bot/Configuration.xml
@@ -48,8 +48,8 @@
Russian
-
-
+
+
Version8_3_24
Language.Русский
Managed
diff --git a/tests/skills/cases/cf-edit/snapshots/roundtrip-crlf-preserve/Configuration.xml b/tests/skills/cases/cf-edit/snapshots/roundtrip-crlf-preserve/Configuration.xml
index 319f0d90..18132008 100644
--- a/tests/skills/cases/cf-edit/snapshots/roundtrip-crlf-preserve/Configuration.xml
+++ b/tests/skills/cases/cf-edit/snapshots/roundtrip-crlf-preserve/Configuration.xml
@@ -48,7 +48,7 @@
Russian
-
+
1.0.0.2
false
diff --git a/tests/skills/cases/cf-info/fixtures/with-bot/Configuration.xml b/tests/skills/cases/cf-info/fixtures/with-bot/Configuration.xml
index f4ad7c07..81fb29d8 100644
--- a/tests/skills/cases/cf-info/fixtures/with-bot/Configuration.xml
+++ b/tests/skills/cases/cf-info/fixtures/with-bot/Configuration.xml
@@ -48,8 +48,8 @@
Russian
-
-
+
+
Version8_3_24
Language.Русский
Managed
diff --git a/tests/skills/cases/cf-info/snapshots/with-bot/Configuration.xml b/tests/skills/cases/cf-info/snapshots/with-bot/Configuration.xml
index 2e9a7fce..0f4ddd5e 100644
--- a/tests/skills/cases/cf-info/snapshots/with-bot/Configuration.xml
+++ b/tests/skills/cases/cf-info/snapshots/with-bot/Configuration.xml
@@ -48,8 +48,8 @@
Russian
-
-
+
+
Version8_3_24
Language.Русский
Managed
diff --git a/tests/skills/cases/cf-init/basic.json b/tests/skills/cases/cf-init/basic.json
index c2a18c01..330825fb 100644
--- a/tests/skills/cases/cf-init/basic.json
+++ b/tests/skills/cases/cf-init/basic.json
@@ -8,6 +8,16 @@
"Configuration.xml",
"Languages/Русский.xml",
"Ext/ClientApplicationInterface.xml"
- ]
+ ],
+ "preserves": {
+ "file": "Configuration.xml",
+ "bom": true,
+ "eol": "crlf",
+ "encoding": "UTF-8",
+ "finalNewline": false,
+ "noCR13": true,
+ "selfClose": "tight",
+ "noEmptyPairs": true
+ }
}
-}
\ No newline at end of file
+}
diff --git a/tests/skills/cases/cf-validate/fixtures/missing-language/Configuration.xml b/tests/skills/cases/cf-validate/fixtures/missing-language/Configuration.xml
index 718fc3b1..293d140f 100644
--- a/tests/skills/cases/cf-validate/fixtures/missing-language/Configuration.xml
+++ b/tests/skills/cases/cf-validate/fixtures/missing-language/Configuration.xml
@@ -32,7 +32,7 @@
-
+
ru
@@ -48,8 +48,8 @@
Russian
-
-
+
+
Version8_3_24
Language.Русский
Managed
diff --git a/tests/skills/cases/cf-validate/fixtures/with-bot/Configuration.xml b/tests/skills/cases/cf-validate/fixtures/with-bot/Configuration.xml
index f4ad7c07..81fb29d8 100644
--- a/tests/skills/cases/cf-validate/fixtures/with-bot/Configuration.xml
+++ b/tests/skills/cases/cf-validate/fixtures/with-bot/Configuration.xml
@@ -48,8 +48,8 @@
Russian
-
-
+
+
Version8_3_24
Language.Русский
Managed
diff --git a/tests/skills/cases/cf-validate/snapshots/with-bot/Configuration.xml b/tests/skills/cases/cf-validate/snapshots/with-bot/Configuration.xml
index 2e9a7fce..0f4ddd5e 100644
--- a/tests/skills/cases/cf-validate/snapshots/with-bot/Configuration.xml
+++ b/tests/skills/cases/cf-validate/snapshots/with-bot/Configuration.xml
@@ -48,8 +48,8 @@
Russian
-
-
+
+
Version8_3_24
Language.Русский
Managed
diff --git a/tests/skills/cases/cfe-init/basic.json b/tests/skills/cases/cfe-init/basic.json
index db6f680c..d0d07159 100644
--- a/tests/skills/cases/cfe-init/basic.json
+++ b/tests/skills/cases/cfe-init/basic.json
@@ -1,8 +1,23 @@
{
"name": "Пустое расширение",
- "params": { "name": "МоёРасширение", "outputDir": "ext" },
+ "params": {
+ "name": "МоёРасширение",
+ "outputDir": "ext"
+ },
"validatePath": "ext",
"expect": {
- "files": ["ext/Configuration.xml"]
+ "files": [
+ "ext/Configuration.xml"
+ ],
+ "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/cfe-validate/fixtures/broken-extension/Configuration.xml b/tests/skills/cases/cfe-validate/fixtures/broken-extension/Configuration.xml
index e54fcbb9..017eb8c7 100644
--- a/tests/skills/cases/cfe-validate/fixtures/broken-extension/Configuration.xml
+++ b/tests/skills/cases/cfe-validate/fixtures/broken-extension/Configuration.xml
@@ -8,7 +8,7 @@
-
+
Version8_3_24
diff --git a/tests/skills/cases/epf-init/basic.json b/tests/skills/cases/epf-init/basic.json
index 5682aa79..2cbf64aa 100644
--- a/tests/skills/cases/epf-init/basic.json
+++ b/tests/skills/cases/epf-init/basic.json
@@ -1,8 +1,22 @@
{
"name": "Пустая внешняя обработка",
- "params": { "name": "ТестоваяОбработка" },
+ "params": {
+ "name": "ТестоваяОбработка"
+ },
"validatePath": "ТестоваяОбработка",
"expect": {
- "files": ["ТестоваяОбработка.xml"]
+ "files": [
+ "ТестоваяОбработка.xml"
+ ],
+ "preserves": {
+ "file": "ТестоваяОбработка.xml",
+ "bom": true,
+ "eol": "crlf",
+ "encoding": "UTF-8",
+ "finalNewline": false,
+ "noCR13": true,
+ "selfClose": "tight",
+ "noEmptyPairs": true
+ }
}
}
diff --git a/tests/skills/cases/erf-init/basic.json b/tests/skills/cases/erf-init/basic.json
index 5ddcd366..41fc86a4 100644
--- a/tests/skills/cases/erf-init/basic.json
+++ b/tests/skills/cases/erf-init/basic.json
@@ -1,8 +1,22 @@
{
"name": "Пустой внешний отчёт",
- "params": { "name": "ТестовыйОтчёт" },
+ "params": {
+ "name": "ТестовыйОтчёт"
+ },
"validatePath": "ТестовыйОтчёт",
"expect": {
- "files": ["ТестовыйОтчёт.xml"]
+ "files": [
+ "ТестовыйОтчёт.xml"
+ ],
+ "preserves": {
+ "file": "ТестовыйОтчёт.xml",
+ "bom": true,
+ "eol": "crlf",
+ "encoding": "UTF-8",
+ "finalNewline": false,
+ "noCR13": true,
+ "selfClose": "tight",
+ "noEmptyPairs": true
+ }
}
}
diff --git a/tests/skills/cases/form-add/basic.json b/tests/skills/cases/form-add/basic.json
index f4fa1ad5..ca4d9bda 100644
--- a/tests/skills/cases/form-add/basic.json
+++ b/tests/skills/cases/form-add/basic.json
@@ -3,10 +3,31 @@
"preRun": [
{
"script": "meta-compile/scripts/meta-compile",
- "input": { "type": "Catalog", "name": "Товары" },
- "args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
+ "input": {
+ "type": "Catalog",
+ "name": "Товары"
+ },
+ "args": {
+ "-JsonPath": "{inputFile}",
+ "-OutputDir": "{workDir}"
+ }
}
],
- "params": { "objectPath": "Catalogs/Товары.xml", "formName": "ФормаЭлемента" },
- "validatePath": "Catalogs/Товары/Forms/ФормаЭлемента"
+ "params": {
+ "objectPath": "Catalogs/Товары.xml",
+ "formName": "ФормаЭлемента"
+ },
+ "validatePath": "Catalogs/Товары/Forms/ФормаЭлемента",
+ "expect": {
+ "preserves": {
+ "file": "Catalogs/Товары/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-compile/minimal.json b/tests/skills/cases/form-compile/minimal.json
index de66ff6a..5d7d8318 100644
--- a/tests/skills/cases/form-compile/minimal.json
+++ b/tests/skills/cases/form-compile/minimal.json
@@ -3,17 +3,40 @@
"preRun": [
{
"script": "meta-compile/scripts/meta-compile",
- "input": { "type": "DataProcessor", "name": "Минимальная" },
- "args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
+ "input": {
+ "type": "DataProcessor",
+ "name": "Минимальная"
+ },
+ "args": {
+ "-JsonPath": "{inputFile}",
+ "-OutputDir": "{workDir}"
+ }
},
{
"script": "form-add/scripts/form-add",
- "args": { "-ObjectPath": "{workDir}/DataProcessors/Минимальная.xml", "-FormName": "Форма" }
+ "args": {
+ "-ObjectPath": "{workDir}/DataProcessors/Минимальная.xml",
+ "-FormName": "Форма"
+ }
}
],
- "params": { "outputPath": "DataProcessors/Минимальная/Forms/Форма/Ext/Form.xml" },
+ "params": {
+ "outputPath": "DataProcessors/Минимальная/Forms/Форма/Ext/Form.xml"
+ },
"validatePath": "DataProcessors/Минимальная/Forms/Форма/Ext/Form.xml",
"input": {
"title": "Минимальная форма"
+ },
+ "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-edit/snapshots/add-attribute/DataProcessors/Реквизиты/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-edit/snapshots/add-attribute/DataProcessors/Реквизиты/Forms/Форма/Ext/Form.xml
index e6ea540c..0fa499b0 100644
--- a/tests/skills/cases/form-edit/snapshots/add-attribute/DataProcessors/Реквизиты/Forms/Форма/Ext/Form.xml
+++ b/tests/skills/cases/form-edit/snapshots/add-attribute/DataProcessors/Реквизиты/Forms/Форма/Ext/Form.xml
@@ -7,7 +7,7 @@
false
-
+
diff --git a/tests/skills/cases/form-edit/snapshots/add-command/DataProcessors/КомандыТест/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-edit/snapshots/add-command/DataProcessors/КомандыТест/Forms/Форма/Ext/Form.xml
index faf7a246..21140f06 100644
--- a/tests/skills/cases/form-edit/snapshots/add-command/DataProcessors/КомандыТест/Forms/Форма/Ext/Form.xml
+++ b/tests/skills/cases/form-edit/snapshots/add-command/DataProcessors/КомандыТест/Forms/Форма/Ext/Form.xml
@@ -7,12 +7,12 @@
false
-
+
diff --git a/tests/skills/cases/form-edit/snapshots/add-element/DataProcessors/Тест/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-edit/snapshots/add-element/DataProcessors/Тест/Forms/Форма/Ext/Form.xml
index e1548bad..35b20101 100644
--- a/tests/skills/cases/form-edit/snapshots/add-element/DataProcessors/Тест/Forms/Форма/Ext/Form.xml
+++ b/tests/skills/cases/form-edit/snapshots/add-element/DataProcessors/Тест/Forms/Форма/Ext/Form.xml
@@ -7,7 +7,7 @@
false
-
+
Поле1
@@ -17,8 +17,8 @@
Поле 1
-
-
+
+
Поле2
@@ -28,8 +28,8 @@
Поле 2
-
-
+
+
diff --git a/tests/skills/cases/form-edit/snapshots/add-group-with-fields/DataProcessors/Группа/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-edit/snapshots/add-group-with-fields/DataProcessors/Группа/Forms/Форма/Ext/Form.xml
index cf0b1995..1861f7f9 100644
--- a/tests/skills/cases/form-edit/snapshots/add-group-with-fields/DataProcessors/Группа/Forms/Форма/Ext/Form.xml
+++ b/tests/skills/cases/form-edit/snapshots/add-group-with-fields/DataProcessors/Группа/Forms/Форма/Ext/Form.xml
@@ -7,7 +7,7 @@
false
-
+
Поле1
@@ -17,8 +17,8 @@
Существующее поле
-
-
+
+
@@ -28,7 +28,7 @@
Horizontal
-
+
Поле2
@@ -38,8 +38,8 @@
Поле 2
-
-
+
+
Поле3
@@ -49,8 +49,8 @@
Поле 3
-
-
+
+
diff --git a/tests/skills/cases/help-add/basic.json b/tests/skills/cases/help-add/basic.json
index d3fae3d2..4e8ea472 100644
--- a/tests/skills/cases/help-add/basic.json
+++ b/tests/skills/cases/help-add/basic.json
@@ -3,8 +3,25 @@
"preRun": [
{
"script": "epf-init/scripts/init",
- "args": { "-Name": "МояОбработка", "-SrcDir": "{workDir}" }
+ "args": {
+ "-Name": "МояОбработка",
+ "-SrcDir": "{workDir}"
+ }
}
],
- "params": { "objectName": "МояОбработка" }
+ "params": {
+ "objectName": "МояОбработка"
+ },
+ "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/meta-compile/catalog-basic.json b/tests/skills/cases/meta-compile/catalog-basic.json
index abb73e93..1a3d7b5f 100644
--- a/tests/skills/cases/meta-compile/catalog-basic.json
+++ b/tests/skills/cases/meta-compile/catalog-basic.json
@@ -1,8 +1,24 @@
{
"name": "Простой справочник без реквизитов",
- "input": { "type": "Catalog", "name": "Валюты" },
+ "input": {
+ "type": "Catalog",
+ "name": "Валюты"
+ },
"validatePath": "Catalogs/Валюты",
"expect": {
- "files": ["Catalogs/Валюты.xml", "Catalogs/Валюты/Ext/ObjectModule.bsl"]
+ "files": [
+ "Catalogs/Валюты.xml",
+ "Catalogs/Валюты/Ext/ObjectModule.bsl"
+ ],
+ "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/meta-edit/fixtures/crlf-catalog/Configuration.xml b/tests/skills/cases/meta-edit/fixtures/crlf-catalog/Configuration.xml
index e088855a..63ea4930 100644
--- a/tests/skills/cases/meta-edit/fixtures/crlf-catalog/Configuration.xml
+++ b/tests/skills/cases/meta-edit/fixtures/crlf-catalog/Configuration.xml
@@ -48,8 +48,8 @@
Russian
-
-
+
+
false
false
diff --git a/tests/skills/cases/meta-edit/snapshots/roundtrip-crlf-preserve/Configuration.xml b/tests/skills/cases/meta-edit/snapshots/roundtrip-crlf-preserve/Configuration.xml
index 431235cf..ee88a27c 100644
--- a/tests/skills/cases/meta-edit/snapshots/roundtrip-crlf-preserve/Configuration.xml
+++ b/tests/skills/cases/meta-edit/snapshots/roundtrip-crlf-preserve/Configuration.xml
@@ -48,8 +48,8 @@
Russian
-
-
+
+
false
false
diff --git a/tests/skills/cases/meta-validate/fixtures/catalog-command-no-group/Catalogs/ТестКоманд.xml b/tests/skills/cases/meta-validate/fixtures/catalog-command-no-group/Catalogs/ТестКоманд.xml
index 6837f4dc..f313f3d0 100644
--- a/tests/skills/cases/meta-validate/fixtures/catalog-command-no-group/Catalogs/ТестКоманд.xml
+++ b/tests/skills/cases/meta-validate/fixtures/catalog-command-no-group/Catalogs/ТестКоманд.xml
@@ -97,7 +97,7 @@
-
+
d5p1:CatalogRef.ТестКоманд
diff --git a/tests/skills/cases/mxl-compile/minimal.json b/tests/skills/cases/mxl-compile/minimal.json
index 44ea94f1..1dde26c4 100644
--- a/tests/skills/cases/mxl-compile/minimal.json
+++ b/tests/skills/cases/mxl-compile/minimal.json
@@ -6,14 +6,35 @@
{
"name": "Ячейка",
"rows": [
- { "cells": [{ "col": 1, "text": "Значение" }] }
+ {
+ "cells": [
+ {
+ "col": 1,
+ "text": "Значение"
+ }
+ ]
+ }
]
}
]
},
- "params": { "outputPath": "Template.xml" },
+ "params": {
+ "outputPath": "Template.xml"
+ },
"validatePath": "Template.xml",
"expect": {
- "files": ["Template.xml"]
+ "files": [
+ "Template.xml"
+ ],
+ "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/role-compile/basic-role.json b/tests/skills/cases/role-compile/basic-role.json
index 8ed4937c..88463da6 100644
--- a/tests/skills/cases/role-compile/basic-role.json
+++ b/tests/skills/cases/role-compile/basic-role.json
@@ -24,6 +24,16 @@
"files": [
"Roles/Кладовщик.xml",
"Roles/Кладовщик/Ext/Rights.xml"
- ]
+ ],
+ "preserves": {
+ "file": "Roles/Кладовщик/Ext/Rights.xml",
+ "bom": true,
+ "eol": "crlf",
+ "encoding": "UTF-8",
+ "finalNewline": false,
+ "noCR13": true,
+ "selfClose": "tight",
+ "noEmptyPairs": true
+ }
}
}
diff --git a/tests/skills/cases/skd-compile/minimal.json b/tests/skills/cases/skd-compile/minimal.json
index 8d260ea3..3e8d4499 100644
--- a/tests/skills/cases/skd-compile/minimal.json
+++ b/tests/skills/cases/skd-compile/minimal.json
@@ -1,14 +1,32 @@
{
"name": "Минимальная СКД — один набор, один запрос",
- "params": { "outputPath": "Template.xml" },
+ "params": {
+ "outputPath": "Template.xml"
+ },
"input": {
- "dataSets": [{
- "query": "ВЫБРАТЬ Номенклатура.Наименование КАК Наименование ИЗ Справочник.Номенклатура КАК Номенклатура",
- "fields": ["Наименование"]
- }]
+ "dataSets": [
+ {
+ "query": "ВЫБРАТЬ Номенклатура.Наименование КАК Наименование ИЗ Справочник.Номенклатура КАК Номенклатура",
+ "fields": [
+ "Наименование"
+ ]
+ }
+ ]
},
"validatePath": "Template.xml",
"expect": {
- "files": ["Template.xml"]
+ "files": [
+ "Template.xml"
+ ],
+ "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/skd-validate/fixtures/empty-field/Template.xml b/tests/skills/cases/skd-validate/fixtures/empty-field/Template.xml
index 9b431c82..20cab85f 100644
--- a/tests/skills/cases/skd-validate/fixtures/empty-field/Template.xml
+++ b/tests/skills/cases/skd-validate/fixtures/empty-field/Template.xml
@@ -16,8 +16,8 @@
ИсточникДанных1
SELECT 1
-
-
+
+
diff --git a/tests/skills/cases/subsystem-compile/basic.json b/tests/skills/cases/subsystem-compile/basic.json
index 0931586d..4d8fad68 100644
--- a/tests/skills/cases/subsystem-compile/basic.json
+++ b/tests/skills/cases/subsystem-compile/basic.json
@@ -3,17 +3,37 @@
"preRun": [
{
"script": "meta-compile/scripts/meta-compile",
- "input": { "type": "Catalog", "name": "Товары" },
- "args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
+ "input": {
+ "type": "Catalog",
+ "name": "Товары"
+ },
+ "args": {
+ "-JsonPath": "{inputFile}",
+ "-OutputDir": "{workDir}"
+ }
}
],
"input": {
"name": "Склад",
"synonym": "Склад",
- "content": ["Catalogs.Товары"]
+ "content": [
+ "Catalogs.Товары"
+ ]
},
"validatePath": "Subsystems/Склад",
"expect": {
- "files": ["Subsystems/Склад.xml"]
+ "files": [
+ "Subsystems/Склад.xml"
+ ],
+ "preserves": {
+ "file": "Subsystems/Склад.xml",
+ "bom": true,
+ "eol": "crlf",
+ "encoding": "UTF-8",
+ "finalNewline": false,
+ "noCR13": true,
+ "selfClose": "tight",
+ "noEmptyPairs": true
+ }
}
}
diff --git a/tests/skills/cases/subsystem-edit/fixtures/crlf-subsystem/Configuration.xml b/tests/skills/cases/subsystem-edit/fixtures/crlf-subsystem/Configuration.xml
index 19aa5597..da643e02 100644
--- a/tests/skills/cases/subsystem-edit/fixtures/crlf-subsystem/Configuration.xml
+++ b/tests/skills/cases/subsystem-edit/fixtures/crlf-subsystem/Configuration.xml
@@ -48,8 +48,8 @@
Russian
-
-
+
+
false
false
diff --git a/tests/skills/cases/subsystem-edit/snapshots/roundtrip-crlf-preserve/Configuration.xml b/tests/skills/cases/subsystem-edit/snapshots/roundtrip-crlf-preserve/Configuration.xml
index 8d77fef8..7d6b7b98 100644
--- a/tests/skills/cases/subsystem-edit/snapshots/roundtrip-crlf-preserve/Configuration.xml
+++ b/tests/skills/cases/subsystem-edit/snapshots/roundtrip-crlf-preserve/Configuration.xml
@@ -48,8 +48,8 @@
Russian
-
-
+
+
false
false
diff --git a/tests/skills/cases/xdto-compile/minimal.json b/tests/skills/cases/xdto-compile/minimal.json
index 81a07ea2..143cfd71 100644
--- a/tests/skills/cases/xdto-compile/minimal.json
+++ b/tests/skills/cases/xdto-compile/minimal.json
@@ -1,9 +1,26 @@
{
"name": "минимальный пакет: один objectType, два свойства",
- "caseFiles": ["minimal.xsd"],
- "params": { "xsdFile": "minimal.xsd" },
+ "caseFiles": [
+ "minimal.xsd"
+ ],
+ "params": {
+ "xsdFile": "minimal.xsd"
+ },
"validatePath": "XDTOPackages/minimal",
"expect": {
- "files": ["XDTOPackages/minimal.xml", "XDTOPackages/minimal/Ext/Package.bin"]
+ "files": [
+ "XDTOPackages/minimal.xml",
+ "XDTOPackages/minimal/Ext/Package.bin"
+ ],
+ "preserves": {
+ "file": "XDTOPackages/minimal.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 0e2b614e..f1e716bb 100644
--- a/tests/skills/runner.mjs
+++ b/tests/skills/runner.mjs
@@ -303,6 +303,14 @@ function buildArgs(skillConfig, caseData, workDir, inputFilePath, runtime) {
const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
+// Applied to the python runtime only: irons out ElementTree/lxml serialization quirks
+// so a snapshot recorded from PowerShell can still be compared against the python port.
+//
+// Three former steps — space before `/>`, empty pair ``, trailing whitespace —
+// were REMOVED with the fix for issue #57. They masked exactly the divergence that issue
+// was about (PS wrote `` plus a trailing newline, python wrote `` without one),
+// so port parity was being checked through the mask. Do not bring them back: the byte
+// canon itself is now asserted per case via `preserves`.
function normalizeXmlContent(text, opts = {}) {
let s = text;
// 1. XML declaration: normalize quotes and encoding case
@@ -317,14 +325,9 @@ function normalizeXmlContent(text, opts = {}) {
if (!opts.keepXmlns) {
s = s.replace(/\s+xmlns(?::[\w]+)?="[^"]*"/g, '');
}
- // 4. Normalize self-closing tags: remove space before />
- s = s.replace(/\s*\/>/g, '/>');
- // 5. Collapse whitespace between tags: "> \n\t <" → "><"
+ // 3. Collapse whitespace between tags: "> \n\t <" → "><". Kept: the ports indent a
+ // few blocks differently, which is formatting rather than the byte canon.
s = s.replace(/>\s+<');
- // 6. Normalize empty elements: →
- s = s.replace(/<([\w:.]+)([^>]*)><\/\1>/g, '<$1$2/>');
- // 7. Strip trailing whitespace
- s = s.trimEnd();
return s;
}
@@ -356,10 +359,11 @@ function normalizeContent(text, config, relFile) {
return s;
}
-// ─── Byte-style preservation check (round-trip #44/#46/#47) ─────────────────
+// ─── Byte-style preservation check (round-trip #44/#46/#47, канон #57) ──────
// Проверяет СЫРЫЕ байты файла (в обход normalizeContent): BOM / EOL / регистр
-// encoding / финальный перенос / отсутствие
. spec: { file, bom, eol:"crlf"|"lf",
-// encoding, finalNewline, noCR13 }. Возвращает массив ошибок.
+// encoding / финальный перенос / отсутствие
/ форма пустого элемента.
+// spec: { file, bom, eol:"crlf"|"lf", encoding, finalNewline, noCR13,
+// selfClose:"tight", noEmptyPairs }. Возвращает массив ошибок.
function checkPreserves(workDir, spec) {
const errs = [];
const target = join(workDir, spec.file);
@@ -371,9 +375,18 @@ function checkPreserves(workDir, spec) {
if (spec.bom !== undefined && hasBom !== spec.bom)
errs.push(`preserves: BOM expected ${spec.bom}, got ${hasBom}`);
if (spec.eol) {
- const hasCR = body.includes(0x0d);
- const wantCR = spec.eol === 'crlf';
- if (hasCR !== wantCR) errs.push(`preserves: EOL expected ${spec.eol} (CR=${wantCR}), got CR=${hasCR}`);
+ // Считаем ОДИНОЧНЫЕ LF, а не «есть ли хоть один CR»: прежняя проверка пропускала
+ // смешанный выход (cfe-init давал 10 CR на 70 строк и проходил её) — то есть
+ // главный дефект #57 был ей невидим.
+ let lf = 0, crlf = 0;
+ for (let i = 0; i < body.length; i++) {
+ if (body[i] === 0x0a) { lf++; if (i > 0 && body[i - 1] === 0x0d) crlf++; }
+ }
+ const loneLF = lf - crlf;
+ if (spec.eol === 'crlf' && loneLF > 0)
+ errs.push(`preserves: EOL expected crlf, got mixed (${crlf} CRLF + ${loneLF} lone LF)`);
+ if (spec.eol === 'lf' && crlf > 0)
+ errs.push(`preserves: EOL expected lf, got mixed (${crlf} CRLF + ${loneLF} lone LF)`);
}
if (spec.encoding) {
const m = /encoding="([^"]+)"/.exec(text);
@@ -384,6 +397,16 @@ function checkPreserves(workDir, spec) {
if (endsNL !== spec.finalNewline) errs.push(`preserves: finalNewline expected ${spec.finalNewline}, got ${endsNL}`);
}
if (spec.noCR13 && text.includes('
')) errs.push(`preserves: unexpected
literal in output`);
+ // Канон Конфигуратора (#57): пустой элемент — , не и не .
+ // Проверено на 8 выгрузках в cfsrc: 21 294 119 самозакрывающихся тегов, пробельных 0.
+ if (spec.selfClose === 'tight') {
+ const spaced = text.match(/<[\w:.]+[^<>]*?\s\/>/g);
+ 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)})`);
+ }
return errs;
}