From 3ef0d74158361e28834a869a5b07e02ca7ca75fe Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Wed, 5 Aug 2026 21:41:55 +0300 Subject: [PATCH] =?UTF-8?q?fix(19=20=D0=BD=D0=B0=D0=B2=D1=8B=D0=BA=D0=BE?= =?UTF-8?q?=D0=B2):=20=D0=B5=D0=B4=D0=B8=D0=BD=D0=B0=D1=8F=20=D1=84=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20XML=20?= =?UTF-8?q?=E2=80=94=20=D1=81=D1=82=D0=B8=D0=BB=D1=8C=20=D0=B8=D1=81=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=D0=BD=D0=B8=D0=BA=D0=B0=20=D0=B8=20=D0=BA=D0=BE?= =?UTF-8?q?=D1=80=D1=80=D0=B5=D0=BA=D1=82=D0=BD=D1=8B=D0=B9=20CDATA=20(#57?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Три остатка волны #57, все в одной точке — блоке записи XML. 1. Порты расходились по EOL. PS гонял документ через XmlWriter с дефолтным NewLineHandling.Replace и принудительно переводил весь файл в CRLF, а py сохранял стиль исходника: role-compile на LF-ном Configuration.xml давал 10311 байт против 10062. Ещё в пяти навыках None стоял, но финальной нормализации не было, и на LF-исходнике выходил смешанный EOL (meta-edit: 40 CRLF + 136 одиночных LF) — та же форма, что у исходного дефекта #57. Приведено к форме cf-edit.ps1: None + канонизация к LF + целевой перевод строки (стиль файла-назначения, для создаваемого файла — канон CRLF). Replace не годится как замена: он превращает переводы строк внутри значений атрибутов в . 2. Гард `if (-notmatch CDATA)` не обрабатывал CDATA, а отказывался от канона во всём файле — то есть деградировал до «не сделал ничего». Заменён альтернацией: участки CDATA и комментариев возвращаются как есть, замена идёт только вне них. На реальных данных поведение не меняется — в корпусе из 476 942 XML нет ни одного CDATA и ни одного комментария. 3. py: три реализации одного правила детекта EOL сведены к одной. Мажоритарное правило в role-compile/subsystem-compile давало ДРУГОЙ ответ на смешанном входе. Дефолты _finalize_xml_bytes для нового файла приведены к канону (UTF-8, CRLF, без хвостового перевода); meta-edit срезает хвост в обеих ветках, а не только при создании файла. Попутно, найдено байтовой сверкой портов: - py вставлял /
/ с пятью табами вместо трёх — подстановка по голому удваивала отступ строки; снэпшоты этого не видели, так как схлопывают пробелы между тегами; - py-порты xdto-* писали XML-декларацию одинарными кавычками (так отдаёт lxml), платформа и PS пишут двойные. Регресс: runner 647/647 ps1, 644/647 py (3 skipped), дрейфа снэпшотов нет. Корпусный раундтрип метаданных: 4897 объектов, match 100%, совпадает с эталонами захода #57. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/skills/cf-edit/scripts/cf-edit.ps1 | 23 +++++---- .claude/skills/cf-edit/scripts/cf-edit.py | 15 +++--- .../skills/cfe-borrow/scripts/cfe-borrow.ps1 | 48 ++++++++++++------- .../skills/cfe-borrow/scripts/cfe-borrow.py | 15 +++--- .claude/skills/form-add/scripts/form-add.ps1 | 15 ++++-- .claude/skills/form-add/scripts/form-add.py | 15 +++--- .../form-compile/scripts/form-compile.ps1 | 15 ++++-- .../form-compile/scripts/form-compile.py | 10 ++-- .../skills/form-edit/scripts/form-edit.ps1 | 14 ++++-- .claude/skills/form-edit/scripts/form-edit.py | 2 +- .../form-remove/scripts/remove-form.ps1 | 15 ++++-- .../skills/form-remove/scripts/remove-form.py | 15 +++--- .claude/skills/help-add/scripts/add-help.ps1 | 15 ++++-- .claude/skills/help-add/scripts/add-help.py | 15 +++--- .../interface-edit/scripts/interface-edit.ps1 | 14 ++++-- .../interface-edit/scripts/interface-edit.py | 15 +++--- .../meta-compile/scripts/meta-compile.ps1 | 15 ++++-- .../meta-compile/scripts/meta-compile.py | 2 +- .../skills/meta-edit/scripts/meta-edit.ps1 | 14 ++++-- .claude/skills/meta-edit/scripts/meta-edit.py | 18 ++++--- .../meta-remove/scripts/meta-remove.ps1 | 28 +++++++---- .../skills/meta-remove/scripts/meta-remove.py | 15 +++--- .../role-compile/scripts/role-compile.ps1 | 15 ++++-- .../role-compile/scripts/role-compile.py | 15 ++++-- .claude/skills/skd-edit/scripts/skd-edit.ps1 | 23 ++++----- .claude/skills/skd-edit/scripts/skd-edit.py | 12 ++--- .../scripts/subsystem-compile.ps1 | 14 ++++-- .../scripts/subsystem-compile.py | 16 +++++-- .../subsystem-edit/scripts/subsystem-edit.ps1 | 14 ++++-- .../subsystem-edit/scripts/subsystem-edit.py | 15 +++--- .../template-add/scripts/add-template.ps1 | 15 ++++-- .../template-add/scripts/add-template.py | 15 +++--- .../scripts/remove-template.ps1 | 15 ++++-- .../scripts/remove-template.py | 15 +++--- .../xdto-compile/scripts/xdto-compile.ps1 | 15 ++++-- .../xdto-compile/scripts/xdto-compile.py | 9 ++-- .../skills/xdto-edit/scripts/xdto-edit.ps1 | 28 +++++++---- .claude/skills/xdto-edit/scripts/xdto-edit.py | 11 +++-- 38 files changed, 366 insertions(+), 234 deletions(-) diff --git a/.claude/skills/cf-edit/scripts/cf-edit.ps1 b/.claude/skills/cf-edit/scripts/cf-edit.ps1 index 49bfa74e..a4f63f1b 100644 --- a/.claude/skills/cf-edit/scripts/cf-edit.ps1 +++ b/.claude/skills/cf-edit/scripts/cf-edit.ps1 @@ -1,4 +1,4 @@ -# cf-edit v1.15 — Edit 1C configuration root (Configuration.xml) +# cf-edit v1.16 — Edit 1C configuration root (Configuration.xml) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)][Alias('Path')][string]$ConfigPath, @@ -163,11 +163,8 @@ function Assert-EditAllowed([string]$targetPath, [string]$require) { Assert-EditAllowed $resolvedPath 'editable' # --- Load XML with PreserveWhitespace --- -# EOL исходного файла запоминаем ДО разбора: парсер XML по спецификации схлопывает -# CRLF в LF, а вставки ниже собираются с явным CRLF — без восстановления в точке -# записи LF-файл стал бы смешанным. Канон CRLF относится к файлам, которые мы -# СОЗДАЁМ; правка существующего сохраняет его стиль (#44/#46/#47). -$script:srcEol = if (([System.IO.File]::ReadAllText($resolvedPath)) -match "`r`n") { "`r`n" } else { "`n" } +# NB: парсер XML по спецификации схлопывает CRLF в LF, а вставки ниже собираются с +# явным CRLF — поэтому EOL приводится к целевому в точке записи (см. финализацию). $script:xmlDoc = New-Object System.Xml.XmlDocument $script:xmlDoc.PreserveWhitespace = $true $script:xmlDoc.Load($resolvedPath) @@ -991,12 +988,14 @@ $memStream.Close() $text = [System.Text.Encoding]::UTF8.GetString($bytes) if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) } $text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"') -# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на -# CDATA/комментарии: только там `>` не экранируется, и ` />` может быть -# содержимым, а не концом тега. -if ($text -notmatch '', '/>') } -# Возвращаем EOL исходного файла: вставки собраны с CRLF, а сам документ мог быть LF. -$text = ($text -replace "`r`n", "`n") -replace "`n", $script:srcEol +# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри +# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), +# поэтому они идут первыми ветками альтернации и возвращаются как есть. +$text = [regex]::Replace($text, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) +# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), +# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. +$targetEol = if ((Test-Path -LiteralPath $resolvedPath) -and ([System.IO.File]::ReadAllText($resolvedPath) -notmatch "`r`n")) { "`n" } else { "`r`n" } +$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol $utf8Bom = New-Object System.Text.UTF8Encoding($true) [System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom) diff --git a/.claude/skills/cf-edit/scripts/cf-edit.py b/.claude/skills/cf-edit/scripts/cf-edit.py index 9dc3439f..a9d8e1df 100644 --- a/.claude/skills/cf-edit/scripts/cf-edit.py +++ b/.claude/skills/cf-edit/scripts/cf-edit.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# cf-edit v1.15 — Edit 1C configuration root (Configuration.xml) +# cf-edit v1.16 — Edit 1C configuration root (Configuration.xml) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -341,21 +341,22 @@ def _detect_xml_style(path): def _finalize_xml_bytes(xml_bytes, style): - """Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" - enc_decl = style["enc"] if style else "utf-8" + """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону + выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце.""" + enc_decl = style["enc"] if style else "UTF-8" xml_bytes = xml_bytes.replace( b"", b'') # Канонизировать переносы к LF (убирает от \r в tail'ах) xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"") .replace(b"\r\n", b"\n").replace(b"\r", b"\n")) - # Финальный перенос — как в оригинале (новый файл → есть) - want_final_nl = style["final_nl"] if style else True + # Финальный перенос — как в оригинале (новый файл → нет, канон #57) + want_final_nl = style["final_nl"] if style else False xml_bytes = xml_bytes.rstrip(b"\n") if want_final_nl: xml_bytes += b"\n" - # EOL — как в оригинале (новый файл → LF, текущее поведение) - if style and style["crlf"]: + # EOL — как в оригинале (новый файл → CRLF, канон #57) + if (style["crlf"] if style else True): xml_bytes = xml_bytes.replace(b"\n", b"\r\n") return xml_bytes diff --git a/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 b/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 index 799740d9..db8ce08a 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.14 — Borrow objects from configuration into extension (CFE) +# cfe-borrow v1.15 — Borrow objects from configuration into extension (CFE) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)][string]$ExtensionPath, @@ -917,12 +917,14 @@ function Borrow-Form { New-Item -ItemType Directory -Path $formXmlDir -Force | Out-Null } $formXmlFile = Join-Path $formXmlDir "Form.xml" - # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на - # CDATA/комментарии: только там `>` не экранируется, и ` />` может быть - # содержимым, а не концом тега. + # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри + # CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), + # поэтому они идут первыми ветками альтернации и возвращаются как есть. # Здесь источник не XmlWriter, а OuterXml исходного документа — спацовывает так же. $formXmlText = $formXmlSb.ToString() - if ($formXmlText -notmatch '', '/>') } + $formXmlText = [regex]::Replace($formXmlText, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) + # Файл создаём мы — канон выгрузки: CRLF в разделителях строк. + $formXmlText = ($formXmlText -replace "`r`n", "`n") -replace "`n", "`r`n" [System.IO.File]::WriteAllText($formXmlFile, $formXmlText, $enc) Info " Created: $formXmlFile" @@ -1029,12 +1031,16 @@ function Register-FormInObject { $text2 = [System.Text.Encoding]::UTF8.GetString($bytes2) if ($text2.Length -gt 0 -and $text2[0] -eq [char]0xFEFF) { $text2 = $text2.Substring(1) } $text2 = $text2.Replace('encoding="utf-8"', 'encoding="UTF-8"') - # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на - # CDATA/комментарии: только там `>` не экранируется, и ` />` может быть - # содержимым, а не концом тега. - if ($text2 -notmatch '', '/>') } + # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри + # CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), + # поэтому они идут первыми ветками альтернации и возвращаются как есть. + $text2 = [regex]::Replace($text2, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) $utf8Bom2 = New-Object System.Text.UTF8Encoding($true) + # Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), + # новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. + $targetEol = if ((Test-Path -LiteralPath $objFile) -and ([System.IO.File]::ReadAllText($objFile) -notmatch "`r`n")) { "`n" } else { "`r`n" } + $text2 = ($text2 -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($objFile, $text2, $utf8Bom2) Info " Registered form in: $objFile" } @@ -1423,13 +1429,17 @@ function Merge-AttributesIntoObject { # Insert attributes before $text3 = $text3 -replace '', "${allAttrXml}`r`n`t`t" - # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на - # CDATA/комментарии: только там `>` не экранируется, и ` />` может быть - # содержимым, а не концом тега. + # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри + # CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), + # поэтому они идут первыми ветками альтернации и возвращаются как есть. # Стоит ПОСЛЕ вставки реквизитов, чтобы накрыть и их. - if ($text3 -notmatch '', '/>') } + $text3 = [regex]::Replace($text3, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) $utf8Bom3 = New-Object System.Text.UTF8Encoding($true) + # Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), + # новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. + $targetEol = if ((Test-Path -LiteralPath $objFile) -and ([System.IO.File]::ReadAllText($objFile) -notmatch "`r`n")) { "`n" } else { "`r`n" } + $text3 = ($text3 -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($objFile, $text3, $utf8Bom3) Info " Merged $added attribute(s) into: $objFile" } @@ -1893,12 +1903,16 @@ $memStream.Close() $text = [System.Text.Encoding]::UTF8.GetString($bytes) if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) } $text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"') -# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на -# CDATA/комментарии: только там `>` не экранируется, и ` />` может быть -# содержимым, а не концом тега. -if ($text -notmatch '', '/>') } +# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри +# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), +# поэтому они идут первыми ветками альтернации и возвращаются как есть. +$text = [regex]::Replace($text, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) $utf8Bom = New-Object System.Text.UTF8Encoding($true) +# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), +# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. +$targetEol = if ((Test-Path -LiteralPath $extResolvedPath) -and ([System.IO.File]::ReadAllText($extResolvedPath) -notmatch "`r`n")) { "`n" } else { "`r`n" } +$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($extResolvedPath, $text, $utf8Bom) Info "Saved: $extResolvedPath" diff --git a/.claude/skills/cfe-borrow/scripts/cfe-borrow.py b/.claude/skills/cfe-borrow/scripts/cfe-borrow.py index b6b1612b..5383948e 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.14 — Borrow objects from configuration into extension (CFE) +# cfe-borrow v1.15 — Borrow objects from configuration into extension (CFE) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -389,21 +389,22 @@ def _detect_xml_style(path): def _finalize_xml_bytes(xml_bytes, style): - """Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" - enc_decl = style["enc"] if style else "utf-8" + """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону + выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце.""" + enc_decl = style["enc"] if style else "UTF-8" xml_bytes = xml_bytes.replace( b"", b'') # Канонизировать переносы к LF (убирает от \r в tail'ах) xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"") .replace(b"\r\n", b"\n").replace(b"\r", b"\n")) - # Финальный перенос — как в оригинале (новый файл → есть) - want_final_nl = style["final_nl"] if style else True + # Финальный перенос — как в оригинале (новый файл → нет, канон #57) + want_final_nl = style["final_nl"] if style else False xml_bytes = xml_bytes.rstrip(b"\n") if want_final_nl: xml_bytes += b"\n" - # EOL — как в оригинале (новый файл → LF, текущее поведение) - if style and style["crlf"]: + # EOL — как в оригинале (новый файл → CRLF, канон #57) + if (style["crlf"] if style else True): xml_bytes = xml_bytes.replace(b"\n", b"\r\n") return xml_bytes diff --git a/.claude/skills/form-add/scripts/form-add.ps1 b/.claude/skills/form-add/scripts/form-add.ps1 index e396eb76..3e9d4be6 100644 --- a/.claude/skills/form-add/scripts/form-add.ps1 +++ b/.claude/skills/form-add/scripts/form-add.ps1 @@ -1,4 +1,4 @@ -# form-add v1.18 — Add managed form to 1C config object +# form-add v1.19 — Add managed form to 1C config object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -601,6 +601,7 @@ if ($SetDefault -or $isFirstFormForPurpose) { $settings = New-Object System.Xml.XmlWriterSettings $settings.Encoding = $encBom $settings.Indent = $false +$settings.NewLineHandling = [System.Xml.NewLineHandling]::None # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки. $memStream = New-Object System.IO.MemoryStream @@ -612,10 +613,14 @@ $xmlText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray()) $memStream.Close() if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) } $xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"') -# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на -# CDATA/комментарии: только там `>` не экранируется, и ` />` может быть -# содержимым, а не концом тега. -if ($xmlText -notmatch '', '/>') } +# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри +# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), +# поэтому они идут первыми ветками альтернации и возвращаются как есть. +$xmlText = [regex]::Replace($xmlText, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) +# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), +# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. +$targetEol = if ((Test-Path -LiteralPath $objectXmlFull.Path) -and ([System.IO.File]::ReadAllText($objectXmlFull.Path) -notmatch "`r`n")) { "`n" } else { "`r`n" } +$xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($objectXmlFull.Path, $xmlText, $encBom) # --- Фаза 5: Вывод --- diff --git a/.claude/skills/form-add/scripts/form-add.py b/.claude/skills/form-add/scripts/form-add.py index 0b0dce0c..94a7f603 100644 --- a/.claude/skills/form-add/scripts/form-add.py +++ b/.claude/skills/form-add/scripts/form-add.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# form-add v1.18 — Add managed form to 1C config object +# form-add v1.19 — Add managed form to 1C config object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -227,21 +227,22 @@ def _detect_xml_style(path): def _finalize_xml_bytes(xml_bytes, style): - """Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" - enc_decl = style["enc"] if style else "utf-8" + """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону + выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце.""" + enc_decl = style["enc"] if style else "UTF-8" xml_bytes = xml_bytes.replace( b"", b'') # Канонизировать переносы к LF (убирает от \r в tail'ах) xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"") .replace(b"\r\n", b"\n").replace(b"\r", b"\n")) - # Финальный перенос — как в оригинале (новый файл → есть) - want_final_nl = style["final_nl"] if style else True + # Финальный перенос — как в оригинале (новый файл → нет, канон #57) + want_final_nl = style["final_nl"] if style else False xml_bytes = xml_bytes.rstrip(b"\n") if want_final_nl: xml_bytes += b"\n" - # EOL — как в оригинале (новый файл → LF, текущее поведение) - if style and style["crlf"]: + # EOL — как в оригинале (новый файл → CRLF, канон #57) + if (style["crlf"] if style else True): xml_bytes = xml_bytes.replace(b"\n", b"\r\n") return xml_bytes diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index 8d4adb51..7033e8c5 100644 --- a/.claude/skills/form-compile/scripts/form-compile.ps1 +++ b/.claude/skills/form-compile/scripts/form-compile.ps1 @@ -1,4 +1,4 @@ -# form-compile v1.181 — Compile 1C managed form from JSON or object metadata +# form-compile v1.182 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -6677,6 +6677,7 @@ if ($formsLeaf -eq 'Forms') { $regSettings = New-Object System.Xml.XmlWriterSettings $regSettings.Encoding = $regEnc $regSettings.Indent = $false + $regSettings.NewLineHandling = [System.Xml.NewLineHandling]::None # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки. $regMem = New-Object System.IO.MemoryStream $regWriter = [System.Xml.XmlWriter]::Create($regMem, $regSettings) @@ -6687,10 +6688,14 @@ if ($formsLeaf -eq 'Forms') { $regMem.Close() if ($regText.Length -gt 0 -and $regText[0] -eq [char]0xFEFF) { $regText = $regText.Substring(1) } $regText = $regText.Replace('encoding="utf-8"', 'encoding="UTF-8"') - # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на - # CDATA/комментарии: только там `>` не экранируется, и ` />` может быть - # содержимым, а не концом тега. - if ($regText -notmatch '', '/>') } + # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри + # CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), + # поэтому они идут первыми ветками альтернации и возвращаются как есть. + $regText = [regex]::Replace($regText, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) + # Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), + # новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. + $targetEol = if ((Test-Path -LiteralPath $objectXmlPath) -and ([System.IO.File]::ReadAllText($objectXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" } + $regText = ($regText -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($objectXmlPath, $regText, $regEnc) Write-Host " Registered: $formName in $objectName.xml" diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index 7bfa6139..7d36b0cb 100644 --- a/.claude/skills/form-compile/scripts/form-compile.py +++ b/.claude/skills/form-compile/scripts/form-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# form-compile v1.181 — Compile 1C managed form from JSON or object metadata +# form-compile v1.182 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -6610,8 +6610,12 @@ def main(): if f'
{form_name}
' not in raw_text: # Insert before if '' in raw_text: - insert_line = f'\t\t\t
{form_name}
' + eol - raw_text = raw_text.replace('', insert_line + '\t\t', 1) + # Отступ вставки берём у закрывающего тега +1 уровень: подстановка + # по голому '' удваивала бы уже присутствующий отступ + # строки (получалось 5 табов вместо 3 — PS-порт через DOM даёт 3). + raw_text = re.sub(r'([ \t]*)', + lambda m: m.group(1) + '\t' + f'
{form_name}
' + eol + m.group(1) + '', + raw_text, count=1) elif '' in raw_text: replacement = ('' + eol + f'\t\t\t
{form_name}
' + eol + '\t\t
') raw_text = raw_text.replace('', replacement, 1) diff --git a/.claude/skills/form-edit/scripts/form-edit.ps1 b/.claude/skills/form-edit/scripts/form-edit.ps1 index b5e3c064..7cc68de9 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.8 — Edit 1C managed form elements +# form-edit v1.9 — Edit 1C managed form elements # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -1387,12 +1387,16 @@ 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"\?>', '' -# Пустой элемент: XmlWriter отдаёт `
`, Конфигуратор пишет ``. Гард на -# CDATA/комментарии: только там `>` не экранируется, и ` />` может быть -# содержимым, а не концом тега. -if ($content -notmatch '', '/>') } +# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри +# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), +# поэтому они идут первыми ветками альтернации и возвращаются как есть. +$content = [regex]::Replace($content, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) $enc = New-Object System.Text.UTF8Encoding($true) +# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), +# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. +$targetEol = if ((Test-Path -LiteralPath $resolvedFormPath) -and ([System.IO.File]::ReadAllText($resolvedFormPath) -notmatch "`r`n")) { "`n" } else { "`r`n" } +$content = ($content -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($resolvedFormPath, $content, $enc) # === 14. Summary === diff --git a/.claude/skills/form-edit/scripts/form-edit.py b/.claude/skills/form-edit/scripts/form-edit.py index c6aa3123..9577cadd 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.8 — Edit 1C managed form elements (Python port) +# form-edit v1.9 — 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/form-remove/scripts/remove-form.ps1 b/.claude/skills/form-remove/scripts/remove-form.ps1 index 9183acf2..b3dd0673 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.6 — Remove form from 1C object +# form-remove v1.7 — Remove form from 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -83,6 +83,7 @@ $encBom = New-Object System.Text.UTF8Encoding($true) $settings = New-Object System.Xml.XmlWriterSettings $settings.Encoding = $encBom $settings.Indent = $false +$settings.NewLineHandling = [System.Xml.NewLineHandling]::None # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки. $memStream = New-Object System.IO.MemoryStream @@ -94,10 +95,14 @@ $xmlText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray()) $memStream.Close() if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) } $xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"') -# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на -# CDATA/комментарии: только там `>` не экранируется, и ` />` может быть -# содержимым, а не концом тега. -if ($xmlText -notmatch '', '/>') } +# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри +# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), +# поэтому они идут первыми ветками альтернации и возвращаются как есть. +$xmlText = [regex]::Replace($xmlText, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) +# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), +# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. +$targetEol = if ((Test-Path -LiteralPath $rootXmlFull.Path) -and ([System.IO.File]::ReadAllText($rootXmlFull.Path) -notmatch "`r`n")) { "`n" } else { "`r`n" } +$xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($rootXmlFull.Path, $xmlText, $encBom) Write-Host "[OK] Форма $FormName удалена из $rootXmlPath" diff --git a/.claude/skills/form-remove/scripts/remove-form.py b/.claude/skills/form-remove/scripts/remove-form.py index 0c4d21d4..924f28b3 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.6 — Remove form from 1C object +# form-remove v1.7 — Remove form from 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -30,21 +30,22 @@ def _detect_xml_style(path): def _finalize_xml_bytes(xml_bytes, style): - """Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" - enc_decl = style["enc"] if style else "utf-8" + """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону + выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце.""" + enc_decl = style["enc"] if style else "UTF-8" xml_bytes = xml_bytes.replace( b"", b'') # Канонизировать переносы к LF (убирает от \r в tail'ах) xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"") .replace(b"\r\n", b"\n").replace(b"\r", b"\n")) - # Финальный перенос — как в оригинале (новый файл → есть) - want_final_nl = style["final_nl"] if style else True + # Финальный перенос — как в оригинале (новый файл → нет, канон #57) + want_final_nl = style["final_nl"] if style else False xml_bytes = xml_bytes.rstrip(b"\n") if want_final_nl: xml_bytes += b"\n" - # EOL — как в оригинале (новый файл → LF, текущее поведение) - if style and style["crlf"]: + # EOL — как в оригинале (новый файл → CRLF, канон #57) + if (style["crlf"] if style else True): xml_bytes = xml_bytes.replace(b"\n", b"\r\n") return xml_bytes diff --git a/.claude/skills/help-add/scripts/add-help.ps1 b/.claude/skills/help-add/scripts/add-help.ps1 index 23447523..e590bc00 100644 --- a/.claude/skills/help-add/scripts/add-help.ps1 +++ b/.claude/skills/help-add/scripts/add-help.ps1 @@ -1,4 +1,4 @@ -# help-add v1.14 — Add built-in help to 1C object +# help-add v1.15 — Add built-in help to 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -266,6 +266,7 @@ if (Test-Path $formsDir) { $settings = New-Object System.Xml.XmlWriterSettings $settings.Encoding = $encBom $settings.Indent = $false + $settings.NewLineHandling = [System.Xml.NewLineHandling]::None # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки. $memStream = New-Object System.IO.MemoryStream $writer = [System.Xml.XmlWriter]::Create($memStream, $settings) @@ -276,10 +277,14 @@ if (Test-Path $formsDir) { $memStream.Close() if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) } $xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"') - # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на - # CDATA/комментарии: только там `>` не экранируется, и ` />` может быть - # содержимым, а не концом тега. - if ($xmlText -notmatch '', '/>') } + # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри + # CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), + # поэтому они идут первыми ветками альтернации и возвращаются как есть. + $xmlText = [regex]::Replace($xmlText, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) + # Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), + # новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. + $targetEol = if ((Test-Path -LiteralPath $formMeta.FullName) -and ([System.IO.File]::ReadAllText($formMeta.FullName) -notmatch "`r`n")) { "`n" } else { "`r`n" } + $xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($formMeta.FullName, $xmlText, $encBom) Write-Host " IncludeHelpInContents добавлен: $($formMeta.Name)" diff --git a/.claude/skills/help-add/scripts/add-help.py b/.claude/skills/help-add/scripts/add-help.py index f28844f8..5c3fb51e 100644 --- a/.claude/skills/help-add/scripts/add-help.py +++ b/.claude/skills/help-add/scripts/add-help.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# help-add v1.14 — Add built-in help to 1C object +# help-add v1.15 — Add built-in help to 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -222,21 +222,22 @@ def _detect_xml_style(path): def _finalize_xml_bytes(xml_bytes, style): - """Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" - enc_decl = style["enc"] if style else "utf-8" + """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону + выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце.""" + enc_decl = style["enc"] if style else "UTF-8" xml_bytes = xml_bytes.replace( b"", b'') # Канонизировать переносы к LF (убирает от \r в tail'ах) xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"") .replace(b"\r\n", b"\n").replace(b"\r", b"\n")) - # Финальный перенос — как в оригинале (новый файл → есть) - want_final_nl = style["final_nl"] if style else True + # Финальный перенос — как в оригинале (новый файл → нет, канон #57) + want_final_nl = style["final_nl"] if style else False xml_bytes = xml_bytes.rstrip(b"\n") if want_final_nl: xml_bytes += b"\n" - # EOL — как в оригинале (новый файл → LF, текущее поведение) - if style and style["crlf"]: + # EOL — как в оригинале (новый файл → CRLF, канон #57) + if (style["crlf"] if style else True): xml_bytes = xml_bytes.replace(b"\n", b"\r\n") return xml_bytes diff --git a/.claude/skills/interface-edit/scripts/interface-edit.ps1 b/.claude/skills/interface-edit/scripts/interface-edit.ps1 index e27aa13d..9171539f 100644 --- a/.claude/skills/interface-edit/scripts/interface-edit.ps1 +++ b/.claude/skills/interface-edit/scripts/interface-edit.ps1 @@ -1,4 +1,4 @@ -# interface-edit v1.12 — Edit 1C CommandInterface.xml +# interface-edit v1.13 — Edit 1C CommandInterface.xml # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)][Alias('Path')][string]$CIPath, @@ -679,12 +679,16 @@ $memStream.Close() $text = [System.Text.Encoding]::UTF8.GetString($bytes) if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) } $text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"') -# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на -# CDATA/комментарии: только там `>` не экранируется, и ` />` может быть -# содержимым, а не концом тега. -if ($text -notmatch '', '/>') } +# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри +# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), +# поэтому они идут первыми ветками альтернации и возвращаются как есть. +$text = [regex]::Replace($text, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) $utf8Bom = New-Object System.Text.UTF8Encoding($true) +# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), +# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. +$targetEol = if ((Test-Path -LiteralPath $resolvedPath) -and ([System.IO.File]::ReadAllText($resolvedPath) -notmatch "`r`n")) { "`n" } else { "`r`n" } +$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom) Info "Saved: $resolvedPath" diff --git a/.claude/skills/interface-edit/scripts/interface-edit.py b/.claude/skills/interface-edit/scripts/interface-edit.py index f3328c0d..0596385e 100644 --- a/.claude/skills/interface-edit/scripts/interface-edit.py +++ b/.claude/skills/interface-edit/scripts/interface-edit.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# interface-edit v1.12 — Edit 1C CommandInterface.xml +# interface-edit v1.13 — Edit 1C CommandInterface.xml # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -304,21 +304,22 @@ def _detect_xml_style(path): def _finalize_xml_bytes(xml_bytes, style): - """Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" - enc_decl = style["enc"] if style else "utf-8" + """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону + выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце.""" + enc_decl = style["enc"] if style else "UTF-8" xml_bytes = xml_bytes.replace( b"", b'') # Канонизировать переносы к LF (убирает от \r в tail'ах) xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"") .replace(b"\r\n", b"\n").replace(b"\r", b"\n")) - # Финальный перенос — как в оригинале (новый файл → есть) - want_final_nl = style["final_nl"] if style else True + # Финальный перенос — как в оригинале (новый файл → нет, канон #57) + want_final_nl = style["final_nl"] if style else False xml_bytes = xml_bytes.rstrip(b"\n") if want_final_nl: xml_bytes += b"\n" - # EOL — как в оригинале (новый файл → LF, текущее поведение) - if style and style["crlf"]: + # EOL — как в оригинале (новый файл → CRLF, канон #57) + if (style["crlf"] if style else True): xml_bytes = xml_bytes.replace(b"\n", b"\r\n") return xml_bytes diff --git a/.claude/skills/meta-compile/scripts/meta-compile.ps1 b/.claude/skills/meta-compile/scripts/meta-compile.ps1 index 1b43f173..fd93a5b3 100644 --- a/.claude/skills/meta-compile/scripts/meta-compile.ps1 +++ b/.claude/skills/meta-compile/scripts/meta-compile.ps1 @@ -1,4 +1,4 @@ -# meta-compile v1.82 — Compile 1C metadata object from JSON +# meta-compile v1.83 — Compile 1C metadata object from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -5015,6 +5015,7 @@ if (Test-Path $configXmlPath) { $cfgSettings = New-Object System.Xml.XmlWriterSettings $cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true) $cfgSettings.Indent = $false + $cfgSettings.NewLineHandling = [System.Xml.NewLineHandling]::None $memStream = New-Object System.IO.MemoryStream $writer = [System.Xml.XmlWriter]::Create($memStream, $cfgSettings) $configDoc.Save($writer) @@ -5024,10 +5025,14 @@ if (Test-Path $configXmlPath) { $memStream.Close() if ($cfgText.Length -gt 0 -and $cfgText[0] -eq [char]0xFEFF) { $cfgText = $cfgText.Substring(1) } $cfgText = $cfgText.Replace('encoding="utf-8"', 'encoding="UTF-8"') - # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на - # CDATA/комментарии: только там `>` не экранируется, и ` />` может быть - # содержимым, а не концом тега. - if ($cfgText -notmatch '', '/>') } + # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри + # CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), + # поэтому они идут первыми ветками альтернации и возвращаются как есть. + $cfgText = [regex]::Replace($cfgText, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) + # Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), + # новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. + $targetEol = if ((Test-Path -LiteralPath $configXmlPath) -and ([System.IO.File]::ReadAllText($configXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" } + $cfgText = ($cfgText -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($configXmlPath, $cfgText, (New-Object System.Text.UTF8Encoding($true))) $regResult = "added" diff --git a/.claude/skills/meta-compile/scripts/meta-compile.py b/.claude/skills/meta-compile/scripts/meta-compile.py index cd44b08c..48db5c48 100644 --- a/.claude/skills/meta-compile/scripts/meta-compile.py +++ b/.claude/skills/meta-compile/scripts/meta-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# meta-compile v1.82 — Compile 1C metadata object from JSON +# meta-compile v1.83 — Compile 1C metadata object from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse diff --git a/.claude/skills/meta-edit/scripts/meta-edit.ps1 b/.claude/skills/meta-edit/scripts/meta-edit.ps1 index 2fd4f94b..6f946c05 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.28 — Edit existing 1C metadata object XML +# meta-edit v1.29 — Edit existing 1C metadata object XML # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$DefinitionFile, @@ -3209,13 +3209,17 @@ if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) } $text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"') -# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на -# CDATA/комментарии: только там `>` не экранируется, и ` />` может быть -# содержимым, а не концом тега. -if ($text -notmatch '', '/>') } +# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри +# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), +# поэтому они идут первыми ветками альтернации и возвращаются как есть. +$text = [regex]::Replace($text, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) # Write with BOM $utf8Bom = New-Object System.Text.UTF8Encoding($true) +# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), +# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. +$targetEol = if ((Test-Path -LiteralPath $resolvedPath) -and ([System.IO.File]::ReadAllText($resolvedPath) -notmatch "`r`n")) { "`n" } else { "`r`n" } +$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom) Info "Saved: $resolvedPath" diff --git a/.claude/skills/meta-edit/scripts/meta-edit.py b/.claude/skills/meta-edit/scripts/meta-edit.py index 9933adae..c76a7af2 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.28 — Edit existing 1C metadata object XML +# meta-edit v1.29 — Edit existing 1C metadata object XML # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -2962,21 +2962,22 @@ def _detect_xml_style(path): def _finalize_xml_bytes(xml_bytes, style): - """Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" - enc_decl = style["enc"] if style else "utf-8" + """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону + выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце.""" + enc_decl = style["enc"] if style else "UTF-8" xml_bytes = xml_bytes.replace( b"", b'') # Канонизировать переносы к LF (убирает от \r в tail'ах) xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"") .replace(b"\r\n", b"\n").replace(b"\r", b"\n")) - # Финальный перенос — как в оригинале (новый файл → есть) - want_final_nl = style["final_nl"] if style else True + # Финальный перенос — как в оригинале (новый файл → нет, канон #57) + want_final_nl = style["final_nl"] if style else False xml_bytes = xml_bytes.rstrip(b"\n") if want_final_nl: xml_bytes += b"\n" - # EOL — как в оригинале (новый файл → LF, текущее поведение) - if style and style["crlf"]: + # EOL — как в оригинале (новый файл → CRLF, канон #57) + if (style["crlf"] if style else True): xml_bytes = xml_bytes.replace(b"\n", b"\r\n") return xml_bytes @@ -3106,6 +3107,9 @@ def add_predefined_items(items): '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 + '' + # Без перевода строки в конце — канон #57. Срезаем в ОБЕИХ ветках: файл, созданный + # прежней версией навыка, мог унести хвост, а PS-порт срезает безусловно. + text = text.rstrip('\r\n') with open(path, 'wb') as f: f.write(b'\xef\xbb\xbf') f.write(text.encode('utf-8')) diff --git a/.claude/skills/meta-remove/scripts/meta-remove.ps1 b/.claude/skills/meta-remove/scripts/meta-remove.ps1 index ca23349d..17008256 100644 --- a/.claude/skills/meta-remove/scripts/meta-remove.ps1 +++ b/.claude/skills/meta-remove/scripts/meta-remove.ps1 @@ -1,4 +1,4 @@ -# meta-remove v1.7 — Remove metadata object from 1C configuration dump +# meta-remove v1.8 — Remove metadata object from 1C configuration dump # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -497,6 +497,7 @@ if (-not $cfgNode) { $settings = New-Object System.Xml.XmlWriterSettings $settings.Encoding = $enc $settings.Indent = $false + $settings.NewLineHandling = [System.Xml.NewLineHandling]::None $memStream = New-Object System.IO.MemoryStream $writer = [System.Xml.XmlWriter]::Create($memStream, $settings) $xmlDoc.Save($writer) @@ -506,10 +507,14 @@ if (-not $cfgNode) { $memStream.Close() if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) } $xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"') - # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на - # CDATA/комментарии: только там `>` не экранируется, и ` />` может быть - # содержимым, а не концом тега. - if ($xmlText -notmatch '', '/>') } + # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри + # CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), + # поэтому они идут первыми ветками альтернации и возвращаются как есть. + $xmlText = [regex]::Replace($xmlText, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) + # Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), + # новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. + $targetEol = if ((Test-Path -LiteralPath $configXml) -and ([System.IO.File]::ReadAllText($configXml) -notmatch "`r`n")) { "`n" } else { "`r`n" } + $xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($configXml, $xmlText, $enc) Write-Host "[OK] Configuration.xml saved" } @@ -578,6 +583,7 @@ function Remove-FromSubsystems { $settings = New-Object System.Xml.XmlWriterSettings $settings.Encoding = $enc $settings.Indent = $false + $settings.NewLineHandling = [System.Xml.NewLineHandling]::None $memStream = New-Object System.IO.MemoryStream $writer = [System.Xml.XmlWriter]::Create($memStream, $settings) $ssDoc.Save($writer) @@ -587,10 +593,14 @@ function Remove-FromSubsystems { $memStream.Close() if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) } $xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"') - # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на - # CDATA/комментарии: только там `>` не экранируется, и ` />` может быть - # содержимым, а не концом тега. - if ($xmlText -notmatch '', '/>') } + # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри + # CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), + # поэтому они идут первыми ветками альтернации и возвращаются как есть. + $xmlText = [regex]::Replace($xmlText, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) + # Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), + # новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. + $targetEol = if ((Test-Path -LiteralPath $xmlFile.FullName) -and ([System.IO.File]::ReadAllText($xmlFile.FullName) -notmatch "`r`n")) { "`n" } else { "`r`n" } + $xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($xmlFile.FullName, $xmlText, $enc) } diff --git a/.claude/skills/meta-remove/scripts/meta-remove.py b/.claude/skills/meta-remove/scripts/meta-remove.py index 1da00b7b..a8a49c26 100644 --- a/.claude/skills/meta-remove/scripts/meta-remove.py +++ b/.claude/skills/meta-remove/scripts/meta-remove.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# meta-remove v1.7 — Remove metadata object from 1C configuration dump +# meta-remove v1.8 — Remove metadata object from 1C configuration dump # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -292,21 +292,22 @@ def _detect_xml_style(path): def _finalize_xml_bytes(xml_bytes, style): - """Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" - enc_decl = style["enc"] if style else "utf-8" + """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону + выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце.""" + enc_decl = style["enc"] if style else "UTF-8" xml_bytes = xml_bytes.replace( b"", b'') # Канонизировать переносы к LF (убирает от \r в tail'ах) xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"") .replace(b"\r\n", b"\n").replace(b"\r", b"\n")) - # Финальный перенос — как в оригинале (новый файл → есть) - want_final_nl = style["final_nl"] if style else True + # Финальный перенос — как в оригинале (новый файл → нет, канон #57) + want_final_nl = style["final_nl"] if style else False xml_bytes = xml_bytes.rstrip(b"\n") if want_final_nl: xml_bytes += b"\n" - # EOL — как в оригинале (новый файл → LF, текущее поведение) - if style and style["crlf"]: + # EOL — как в оригинале (новый файл → CRLF, канон #57) + if (style["crlf"] if style else True): xml_bytes = xml_bytes.replace(b"\n", b"\r\n") return xml_bytes diff --git a/.claude/skills/role-compile/scripts/role-compile.ps1 b/.claude/skills/role-compile/scripts/role-compile.ps1 index e83674ce..239b389c 100644 --- a/.claude/skills/role-compile/scripts/role-compile.ps1 +++ b/.claude/skills/role-compile/scripts/role-compile.ps1 @@ -1,4 +1,4 @@ -# role-compile v1.15 — Compile 1C role from JSON +# role-compile v1.16 — Compile 1C role from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -856,6 +856,7 @@ if (Test-Path $configXmlPath) { $cfgSettings = New-Object System.Xml.XmlWriterSettings $cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true) $cfgSettings.Indent = $false + $cfgSettings.NewLineHandling = [System.Xml.NewLineHandling]::None # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки. $memStream = New-Object System.IO.MemoryStream $writer = [System.Xml.XmlWriter]::Create($memStream, $cfgSettings) @@ -866,10 +867,14 @@ if (Test-Path $configXmlPath) { $memStream.Close() if ($cfgText.Length -gt 0 -and $cfgText[0] -eq [char]0xFEFF) { $cfgText = $cfgText.Substring(1) } $cfgText = $cfgText.Replace('encoding="utf-8"', 'encoding="UTF-8"') - # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на - # CDATA/комментарии: только там `>` не экранируется, и ` />` может быть - # содержимым, а не концом тега. - if ($cfgText -notmatch '', '/>') } + # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри + # CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), + # поэтому они идут первыми ветками альтернации и возвращаются как есть. + $cfgText = [regex]::Replace($cfgText, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) + # Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), + # новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. + $targetEol = if ((Test-Path -LiteralPath $configXmlPath) -and ([System.IO.File]::ReadAllText($configXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" } + $cfgText = ($cfgText -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($configXmlPath, $cfgText, (New-Object System.Text.UTF8Encoding($true))) $regResult = "added" diff --git a/.claude/skills/role-compile/scripts/role-compile.py b/.claude/skills/role-compile/scripts/role-compile.py index 73763fcf..f8d78a13 100644 --- a/.claude/skills/role-compile/scripts/role-compile.py +++ b/.claude/skills/role-compile/scripts/role-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# role-compile v1.15 — Compile 1C role from JSON +# role-compile v1.16 — Compile 1C role from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -206,8 +206,10 @@ def detect_format_version(d): def detect_eol(text): # Перевод строки ВСТАВКИ берём из самого файла: канон CRLF относится к файлам, # которые мы создаём, а правка существующего сохраняет его стиль (#44/#46/#47). - crlf = text.count('\r\n') - return '\r\n' if crlf and crlf >= text.count('\n') - crlf else '\n' + # Семантика та же, что у _detect_xml_style в остальных портах: есть CRLF → CRLF. + # Мажоритарное правило здесь было расхождением — на смешанном входе оно давало + # другой ответ, чем канон, при том же назначении. + return '\r\n' if '\r\n' in text else '\n' def esc_xml(s): """Экранирование ТЕКСТА элемента: только & < > . Кавычки платформа в тексте не экранирует @@ -820,7 +822,12 @@ def main(): raw_text = raw_text[:insert_pos] + eol + f'\t\t\t{new_role_tag}' + raw_text[insert_pos:] else: # No existing roles — insert before - raw_text = raw_text.replace('', f'\t\t\t{new_role_tag}' + eol + '\t\t') + # Отступ вставки берём у закрывающего тега +1 уровень: подстановка + # по голому '' удваивала бы уже присутствующий отступ + # строки (получалось 5 табов вместо 3 — PS-порт через DOM даёт 3). + raw_text = re.sub(r'([ \t]*)', + lambda m: m.group(1) + '\t' + new_role_tag + eol + m.group(1) + '', + raw_text, count=1) write_utf8_bom(config_xml_path, raw_text) reg_result = 'added' diff --git a/.claude/skills/skd-edit/scripts/skd-edit.ps1 b/.claude/skills/skd-edit/scripts/skd-edit.ps1 index 412d8158..6f55a46f 100644 --- a/.claude/skills/skd-edit/scripts/skd-edit.ps1 +++ b/.claude/skills/skd-edit/scripts/skd-edit.ps1 @@ -1,4 +1,4 @@ -# skd-edit v1.31 — Atomic 1C DCS editor +# skd-edit v1.32 — Atomic 1C DCS editor # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # NB: парный .py собирает выражения автодат вне f-string ради совместимости с python 3.9 (PEP 701). param( @@ -2192,7 +2192,6 @@ if ($rootOpenMatch.Success) { $script:RawRootOpening = $rootOpenMatch.Value } el # Detect line ending convention so save can normalize back to whatever the source used. # 1С Designer writes CRLF on Windows; LF-edited files should stay LF. -$script:LineEnding = if ($script:RawOriginal.Contains("`r`n")) { "`r`n" } else { "`n" } $xmlDoc = New-Object System.Xml.XmlDocument $xmlDoc.PreserveWhitespace = $true @@ -4045,18 +4044,16 @@ if ($script:RawRootOpening) { $content = [regex]::Replace($content, ']*>', { param($m) $script:RawRootOpening }) } -# (2) Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на -# CDATA/комментарии: только там `>` не экранируется, и ` />` может быть -# содержимым, а не концом тега. -if ($content -notmatch '', '/>') } +# (2) Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри +# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), +# поэтому они идут первыми ветками альтернации и возвращаются как есть. +$content = [regex]::Replace($content, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) -# (3) normalize line endings to match source — operations may mix LF (from new -# fragments) with whatever the source used (CRLF on Windows, LF on Linux/git). -if ($script:LineEnding -eq "`r`n") { - $content = $content -replace '(?]*>", lambda m: raw_root_opening, xml_text, count=1, flags=re.DOTALL) # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет `` (lxml и так -# пишет плотно — правка защитная, чтобы порты оставались байт-эквивалентны). Гард на -# CDATA/комментарии: только там `>` не экранируется, и ` />` может быть содержимым, -# а не концом тега. -if "", "/>", xml_text) +# пишет плотно — правка защитная, чтобы порты оставались байт-эквивалентны). Внутри +# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), +# поэтому они идут первыми ветками альтернации и возвращаются как есть. +xml_text = re.sub(r"(?s)||(?<=\S) />", + lambda m: "/>" if m.group(0) == " />" else m.group(0), xml_text) # Канонизировать переносы к LF (убирает возможный ), затем к стилю источника. xml_text = xml_text.replace(" \n", "\n").replace(" ", "").replace("\r\n", "\n").replace("\r", "\n") diff --git a/.claude/skills/subsystem-compile/scripts/subsystem-compile.ps1 b/.claude/skills/subsystem-compile/scripts/subsystem-compile.ps1 index 34b70235..b59192e5 100644 --- a/.claude/skills/subsystem-compile/scripts/subsystem-compile.ps1 +++ b/.claude/skills/subsystem-compile/scripts/subsystem-compile.ps1 @@ -1,4 +1,4 @@ -# subsystem-compile v1.16 — Create 1C subsystem from JSON definition +# subsystem-compile v1.17 — Create 1C subsystem from JSON definition # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$DefinitionFile, @@ -657,10 +657,14 @@ if ($parentXmlPath -and (Test-Path $parentXmlPath)) { $text = [System.Text.Encoding]::UTF8.GetString($bytes) if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) } $text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"') - # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на - # CDATA/комментарии: только там `>` не экранируется, и ` />` может быть - # содержимым, а не концом тега. - if ($text -notmatch '', '/>') } + # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри + # CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), + # поэтому они идут первыми ветками альтернации и возвращаются как есть. + $text = [regex]::Replace($text, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) + # Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), + # новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. + $targetEol = if ((Test-Path -LiteralPath $parentXmlPath) -and ([System.IO.File]::ReadAllText($parentXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" } + $text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($parentXmlPath, $text, $utf8Bom) Write-Host "[OK] Registered in: $parentXmlPath" diff --git a/.claude/skills/subsystem-compile/scripts/subsystem-compile.py b/.claude/skills/subsystem-compile/scripts/subsystem-compile.py index 873762b7..59c1ea48 100644 --- a/.claude/skills/subsystem-compile/scripts/subsystem-compile.py +++ b/.claude/skills/subsystem-compile/scripts/subsystem-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# subsystem-compile v1.16 — Create 1C subsystem from JSON definition +# subsystem-compile v1.17 — Create 1C subsystem from JSON definition # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -207,8 +207,10 @@ def detect_format_version(d): def detect_eol(text): # Перевод строки ВСТАВКИ берём из самого файла: канон CRLF относится к файлам, # которые мы создаём, а правка существующего сохраняет его стиль (#44/#46/#47). - crlf = text.count('\r\n') - return '\r\n' if crlf and crlf >= text.count('\n') - crlf else '\n' + # Семантика та же, что у _detect_xml_style в остальных портах: есть CRLF → CRLF. + # Мажоритарное правило здесь было расхождением — на смешанном входе оно давало + # другой ответ, чем канон, при том же назначении. + return '\r\n' if '\r\n' in text else '\n' def esc_xml(s): """Экранирование ТЕКСТА элемента: только & < > . Кавычки платформа в тексте не экранирует @@ -605,8 +607,12 @@ def main(): replacement = ('' + eol + f'\t\t\t{esc_xml(obj_name)}' + eol + '\t\t') raw_text = raw_text.replace('', replacement, 1) elif '' in raw_text: - insert_line = f'\t\t\t{esc_xml(obj_name)}' + eol - raw_text = raw_text.replace('', insert_line + '\t\t', 1) + # Отступ вставки берём у закрывающего тега +1 уровень: подстановка + # по голому '' удваивала бы уже присутствующий отступ + # строки (получалось 5 табов вместо 3 — PS-порт через DOM даёт 3). + raw_text = re.sub(r'([ \t]*)', + lambda m: m.group(1) + '\t' + f'{esc_xml(obj_name)}' + eol + m.group(1) + '', + raw_text, count=1) write_utf8_bom(parent_xml_path, raw_text) print(f"[OK] Registered in: {parent_xml_path}") diff --git a/.claude/skills/subsystem-edit/scripts/subsystem-edit.ps1 b/.claude/skills/subsystem-edit/scripts/subsystem-edit.ps1 index cb77e17b..66086fe9 100644 --- a/.claude/skills/subsystem-edit/scripts/subsystem-edit.ps1 +++ b/.claude/skills/subsystem-edit/scripts/subsystem-edit.ps1 @@ -1,4 +1,4 @@ -# subsystem-edit v1.12 — Edit existing 1C subsystem XML +# subsystem-edit v1.13 — Edit existing 1C subsystem XML # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)][Alias('Path')][string]$SubsystemPath, @@ -655,12 +655,16 @@ $memStream.Close() $text = [System.Text.Encoding]::UTF8.GetString($bytes) if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) } $text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"') -# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на -# CDATA/комментарии: только там `>` не экранируется, и ` />` может быть -# содержимым, а не концом тега. -if ($text -notmatch '', '/>') } +# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри +# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), +# поэтому они идут первыми ветками альтернации и возвращаются как есть. +$text = [regex]::Replace($text, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) $utf8Bom = New-Object System.Text.UTF8Encoding($true) +# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), +# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. +$targetEol = if ((Test-Path -LiteralPath $resolvedPath) -and ([System.IO.File]::ReadAllText($resolvedPath) -notmatch "`r`n")) { "`n" } else { "`r`n" } +$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom) Info "Saved: $resolvedPath" diff --git a/.claude/skills/subsystem-edit/scripts/subsystem-edit.py b/.claude/skills/subsystem-edit/scripts/subsystem-edit.py index b4011370..5771ddf0 100644 --- a/.claude/skills/subsystem-edit/scripts/subsystem-edit.py +++ b/.claude/skills/subsystem-edit/scripts/subsystem-edit.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# subsystem-edit v1.12 — Edit existing 1C subsystem XML +# subsystem-edit v1.13 — Edit existing 1C subsystem XML # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -452,21 +452,22 @@ def _detect_xml_style(path): def _finalize_xml_bytes(xml_bytes, style): - """Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" - enc_decl = style["enc"] if style else "utf-8" + """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону + выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце.""" + enc_decl = style["enc"] if style else "UTF-8" xml_bytes = xml_bytes.replace( b"", b'') # Канонизировать переносы к LF (убирает от \r в tail'ах) xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"") .replace(b"\r\n", b"\n").replace(b"\r", b"\n")) - # Финальный перенос — как в оригинале (новый файл → есть) - want_final_nl = style["final_nl"] if style else True + # Финальный перенос — как в оригинале (новый файл → нет, канон #57) + want_final_nl = style["final_nl"] if style else False xml_bytes = xml_bytes.rstrip(b"\n") if want_final_nl: xml_bytes += b"\n" - # EOL — как в оригинале (новый файл → LF, текущее поведение) - if style and style["crlf"]: + # EOL — как в оригинале (новый файл → CRLF, канон #57) + if (style["crlf"] if style else True): xml_bytes = xml_bytes.replace(b"\n", b"\r\n") return xml_bytes diff --git a/.claude/skills/template-add/scripts/add-template.ps1 b/.claude/skills/template-add/scripts/add-template.ps1 index b6b35fa0..44b015d9 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.16 — Add template to 1C object +# template-add v1.17 — Add template to 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -402,6 +402,7 @@ if ($TemplateType -eq "DataCompositionSchema") { $settings = New-Object System.Xml.XmlWriterSettings $settings.Encoding = $encBom $settings.Indent = $false +$settings.NewLineHandling = [System.Xml.NewLineHandling]::None # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки. $memStream = New-Object System.IO.MemoryStream @@ -413,10 +414,14 @@ $xmlText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray()) $memStream.Close() if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) } $xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"') -# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на -# CDATA/комментарии: только там `>` не экранируется, и ` />` может быть -# содержимым, а не концом тега. -if ($xmlText -notmatch '', '/>') } +# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри +# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), +# поэтому они идут первыми ветками альтернации и возвращаются как есть. +$xmlText = [regex]::Replace($xmlText, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) +# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), +# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. +$targetEol = if ((Test-Path -LiteralPath $rootXmlFull.Path) -and ([System.IO.File]::ReadAllText($rootXmlFull.Path) -notmatch "`r`n")) { "`n" } else { "`r`n" } +$xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($rootXmlFull.Path, $xmlText, $encBom) Write-Host "[OK] Создан макет: $TemplateName ($TemplateType)" diff --git a/.claude/skills/template-add/scripts/add-template.py b/.claude/skills/template-add/scripts/add-template.py index f2d46ef4..ec874598 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.16 — Add template to 1C object +# template-add v1.17 — Add template to 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -215,21 +215,22 @@ def _detect_xml_style(path): def _finalize_xml_bytes(xml_bytes, style): - """Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" - enc_decl = style["enc"] if style else "utf-8" + """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону + выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце.""" + enc_decl = style["enc"] if style else "UTF-8" xml_bytes = xml_bytes.replace( b"", b'') # Канонизировать переносы к LF (убирает от \r в tail'ах) xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"") .replace(b"\r\n", b"\n").replace(b"\r", b"\n")) - # Финальный перенос — как в оригинале (новый файл → есть) - want_final_nl = style["final_nl"] if style else True + # Финальный перенос — как в оригинале (новый файл → нет, канон #57) + want_final_nl = style["final_nl"] if style else False xml_bytes = xml_bytes.rstrip(b"\n") if want_final_nl: xml_bytes += b"\n" - # EOL — как в оригинале (новый файл → LF, текущее поведение) - if style and style["crlf"]: + # EOL — как в оригинале (новый файл → CRLF, канон #57) + if (style["crlf"] if style else True): xml_bytes = xml_bytes.replace(b"\n", b"\r\n") return xml_bytes diff --git a/.claude/skills/template-remove/scripts/remove-template.ps1 b/.claude/skills/template-remove/scripts/remove-template.ps1 index fea617c0..02543908 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.5 — Remove template from 1C object +# template-remove v1.6 — Remove template from 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -80,6 +80,7 @@ $encBom = New-Object System.Text.UTF8Encoding($true) $settings = New-Object System.Xml.XmlWriterSettings $settings.Encoding = $encBom $settings.Indent = $false +$settings.NewLineHandling = [System.Xml.NewLineHandling]::None # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки. $memStream = New-Object System.IO.MemoryStream @@ -91,10 +92,14 @@ $xmlText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray()) $memStream.Close() if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) } $xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"') -# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на -# CDATA/комментарии: только там `>` не экранируется, и ` />` может быть -# содержимым, а не концом тега. -if ($xmlText -notmatch '', '/>') } +# Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри +# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), +# поэтому они идут первыми ветками альтернации и возвращаются как есть. +$xmlText = [regex]::Replace($xmlText, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) +# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), +# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. +$targetEol = if ((Test-Path -LiteralPath $rootXmlFull.Path) -and ([System.IO.File]::ReadAllText($rootXmlFull.Path) -notmatch "`r`n")) { "`n" } else { "`r`n" } +$xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($rootXmlFull.Path, $xmlText, $encBom) Write-Host "[OK] Макет $TemplateName удалён из $rootXmlPath" diff --git a/.claude/skills/template-remove/scripts/remove-template.py b/.claude/skills/template-remove/scripts/remove-template.py index 069c925b..ba1212e8 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.5 — Remove template from 1C object +# template-remove v1.6 — Remove template from 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -30,21 +30,22 @@ def _detect_xml_style(path): def _finalize_xml_bytes(xml_bytes, style): - """Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" - enc_decl = style["enc"] if style else "utf-8" + """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону + выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце.""" + enc_decl = style["enc"] if style else "UTF-8" xml_bytes = xml_bytes.replace( b"", b'') # Канонизировать переносы к LF (убирает от \r в tail'ах) xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"") .replace(b"\r\n", b"\n").replace(b"\r", b"\n")) - # Финальный перенос — как в оригинале (новый файл → есть) - want_final_nl = style["final_nl"] if style else True + # Финальный перенос — как в оригинале (новый файл → нет, канон #57) + want_final_nl = style["final_nl"] if style else False xml_bytes = xml_bytes.rstrip(b"\n") if want_final_nl: xml_bytes += b"\n" - # EOL — как в оригинале (новый файл → LF, текущее поведение) - if style and style["crlf"]: + # EOL — как в оригинале (новый файл → CRLF, канон #57) + if (style["crlf"] if style else True): xml_bytes = xml_bytes.replace(b"\n", b"\r\n") return xml_bytes diff --git a/.claude/skills/xdto-compile/scripts/xdto-compile.ps1 b/.claude/skills/xdto-compile/scripts/xdto-compile.ps1 index e592952a..d9512614 100644 --- a/.claude/skills/xdto-compile/scripts/xdto-compile.ps1 +++ b/.claude/skills/xdto-compile/scripts/xdto-compile.ps1 @@ -1,4 +1,4 @@ -# xdto-compile v1.5 — Build a 1C XDTO package from an XML Schema (XSD) +# xdto-compile v1.6 — Build a 1C XDTO package from an XML Schema (XSD) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory=$true, ParameterSetName='File')] @@ -948,6 +948,7 @@ if (Test-Path $configXmlPath) { $cfgSettings = New-Object System.Xml.XmlWriterSettings $cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true) $cfgSettings.Indent = $false + $cfgSettings.NewLineHandling = [System.Xml.NewLineHandling]::None # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки. $memStream = New-Object System.IO.MemoryStream $writer = [System.Xml.XmlWriter]::Create($memStream, $cfgSettings) @@ -958,10 +959,14 @@ if (Test-Path $configXmlPath) { $memStream.Close() if ($cfgText.Length -gt 0 -and $cfgText[0] -eq [char]0xFEFF) { $cfgText = $cfgText.Substring(1) } $cfgText = $cfgText.Replace('encoding="utf-8"', 'encoding="UTF-8"') - # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на - # CDATA/комментарии: только там `>` не экранируется, и ` />` может быть - # содержимым, а не концом тега. - if ($cfgText -notmatch '', '/>') } + # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри + # CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), + # поэтому они идут первыми ветками альтернации и возвращаются как есть. + $cfgText = [regex]::Replace($cfgText, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) + # Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), + # новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. + $targetEol = if ((Test-Path -LiteralPath $configXmlPath) -and ([System.IO.File]::ReadAllText($configXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" } + $cfgText = ($cfgText -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($configXmlPath, $cfgText, (New-Object System.Text.UTF8Encoding($true))) $regResult = "added" } diff --git a/.claude/skills/xdto-compile/scripts/xdto-compile.py b/.claude/skills/xdto-compile/scripts/xdto-compile.py index e75a7192..d4a07281 100644 --- a/.claude/skills/xdto-compile/scripts/xdto-compile.py +++ b/.claude/skills/xdto-compile/scripts/xdto-compile.py @@ -1,4 +1,4 @@ -# xdto-compile v1.5 — Build a 1C XDTO package from an XML Schema (XSD) (Python port) +# xdto-compile v1.6 — Build a 1C XDTO package from an XML Schema (XSD) (Python port) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -960,10 +960,13 @@ if os.path.exists(config_xml): else: new_elem.tail = child_objects.text data = etree.tostring(cfg_doc, xml_declaration=True, encoding="UTF-8") + # lxml пишет декларацию в ОДИНАРНЫХ кавычках, платформа и PS-порт — в двойных. + data = data.replace(b"", + b'') # Парсер XML по спецификации схлопывает CRLF в LF, поэтому tostring отдаёт # LF-документ. Возвращаем EOL исходного файла: правка существующего файла - # сохраняет его стиль (#44/#46/#47), а .NET-порт делает это через - # NewLineHandling — иначе порты расходятся побайтово. + # сохраняет его стиль (#44/#46/#47). Правило то же, что у _detect_xml_style + # и у $targetEol в PS-порту: есть CRLF → CRLF. src_eol = b"\r\n" if b"\r\n" in raw else b"\n" data = data.replace(b"\r\n", b"\n").replace(b"\n", src_eol) if had_bom: diff --git a/.claude/skills/xdto-edit/scripts/xdto-edit.ps1 b/.claude/skills/xdto-edit/scripts/xdto-edit.ps1 index 11534cb7..d4570e49 100644 --- a/.claude/skills/xdto-edit/scripts/xdto-edit.ps1 +++ b/.claude/skills/xdto-edit/scripts/xdto-edit.ps1 @@ -1,4 +1,4 @@ -# xdto-edit v1.3 — Point edits of a 1C XDTO package +# xdto-edit v1.4 — Point edits of a 1C XDTO package # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory=$true)] @@ -221,6 +221,7 @@ function Edit-Metadata([string]$field, [string]$newValue) { $settings = New-Object System.Xml.XmlWriterSettings $settings.Encoding = $encBom $settings.Indent = $false + $settings.NewLineHandling = [System.Xml.NewLineHandling]::None # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки. $memStream = New-Object System.IO.MemoryStream $writer = [System.Xml.XmlWriter]::Create($memStream, $settings) @@ -231,10 +232,14 @@ function Edit-Metadata([string]$field, [string]$newValue) { $memStream.Close() if ($mdText.Length -gt 0 -and $mdText[0] -eq [char]0xFEFF) { $mdText = $mdText.Substring(1) } $mdText = $mdText.Replace('encoding="utf-8"', 'encoding="UTF-8"') - # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на - # CDATA/комментарии: только там `>` не экранируется, и ` />` может быть - # содержимым, а не концом тега. - if ($mdText -notmatch '', '/>') } + # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри + # CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), + # поэтому они идут первыми ветками альтернации и возвращаются как есть. + $mdText = [regex]::Replace($mdText, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) + # Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), + # новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. + $targetEol = if ((Test-Path -LiteralPath $mdFile) -and ([System.IO.File]::ReadAllText($mdFile) -notmatch "`r`n")) { "`n" } else { "`r`n" } + $mdText = ($mdText -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($mdFile, $mdText, $encBom) } @@ -263,6 +268,7 @@ function Rename-Package([string]$newName) { if ($found) { $s = New-Object System.Xml.XmlWriterSettings $s.Encoding = $encBom; $s.Indent = $false + $s.NewLineHandling = [System.Xml.NewLineHandling]::None # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки. $mem = New-Object System.IO.MemoryStream $w = [System.Xml.XmlWriter]::Create($mem, $s) @@ -271,10 +277,14 @@ function Rename-Package([string]$newName) { $mem.Close() if ($cfgText.Length -gt 0 -and $cfgText[0] -eq [char]0xFEFF) { $cfgText = $cfgText.Substring(1) } $cfgText = $cfgText.Replace('encoding="utf-8"', 'encoding="UTF-8"') - # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Гард на - # CDATA/комментарии: только там `>` не экранируется, и ` />` может быть - # содержимым, а не концом тега. - if ($cfgText -notmatch '', '/>') } + # Пустой элемент: XmlWriter отдаёт ``, Конфигуратор пишет ``. Внутри + # CDATA/комментария ` />` может быть содержимым (там `>` не экранируется), + # поэтому они идут первыми ветками альтернации и возвращаются как есть. + $cfgText = [regex]::Replace($cfgText, '(?s)||(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } }) + # Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47), + # новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту. + $targetEol = if ((Test-Path -LiteralPath $configXml) -and ([System.IO.File]::ReadAllText($configXml) -notmatch "`r`n")) { "`n" } else { "`r`n" } + $cfgText = ($cfgText -replace "`r`n", "`n") -replace "`n", $targetEol [System.IO.File]::WriteAllText($configXml, $cfgText, $encBom) Write-Host " Configuration.xml: переименован в $newName" } else { diff --git a/.claude/skills/xdto-edit/scripts/xdto-edit.py b/.claude/skills/xdto-edit/scripts/xdto-edit.py index 6c1507ad..8bc01bc5 100644 --- a/.claude/skills/xdto-edit/scripts/xdto-edit.py +++ b/.claude/skills/xdto-edit/scripts/xdto-edit.py @@ -1,4 +1,4 @@ -# xdto-edit v1.3 — Point edits of a 1C XDTO package (Python port) +# xdto-edit v1.4 — Point edits of a 1C XDTO package (Python port) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -194,10 +194,13 @@ def invoke_sibling(script, argv, what): def save_xml(doc, path): raw = etree.tostring(doc, xml_declaration=True, encoding="UTF-8") + # lxml пишет декларацию в ОДИНАРНЫХ кавычках, платформа и PS-порт — в двойных. + raw = raw.replace(b"", + b'') # Парсер XML по спецификации схлопывает CRLF в LF, поэтому tostring отдаёт - # LF-документ. Возвращаем EOL исходного файла: правка существующего файла - # сохраняет его стиль (#44/#46/#47), а .NET-порт делает это через - # NewLineHandling — иначе порты расходятся побайтово. + # LF-документ. Возвращаем EOL файла-назначения: правка существующего файла + # сохраняет его стиль (#44/#46/#47), новый получает канон CRLF. Правило то же, + # что у _detect_xml_style и у $targetEol в PS-порту: есть CRLF → CRLF. src_eol = b"\r\n" try: with open(path, "rb") as f: