mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-22 04:31:02 +03:00
fix(skills): round-trip сохранение EOL/BOM/encoding в py edit-портах (#44/#46/#47)
Py-порты (lxml) при точечном редактировании существующего 1С-XML переписывали весь файл: CRLF→LF, encoding="UTF-8"→"utf-8", добавляли финальный перенос, плодили литерал (сериализация \r из tail'ов). Результат — широкий шумовой diff и скрытый лишний текст-узел при exit 0 и зелёной валидации. PS1-порты (XmlDocument) багу не подвержены — эталон. Фикс во всех 13 round-trip re-serialize py-навыках: перед записью детектится стиль существующего файла (BOM / EOL / регистр encoding / финальный перенос) и восстанавливается при сохранении; переносы канонизируются к LF (убирает ), затем приводятся к EOL источника. Новый файл (путь не существует) → прежнее поведение, снапшоты не двигаются. Навыки: cf-edit, meta-edit, meta-remove, interface-edit, subsystem-edit, skd-edit, form-edit, form-add, help-add, template-add, template-remove, form-remove, cfe-borrow. Версии py+ps1 подняты синхронно. Harness (tests/skills/runner.mjs): снята маска в normalizeXmlContent (порты её больше не порождают → гвардия ловит регресс); добавлен expect.preserves — raw-байтовая проверка BOM/EOL/encoding/финального переноса/отсутствия в обход нормализации. Регрессионные round-trip кейсы на CRLF+BOM+UTF-8 фикстурах для cf-edit/meta-edit/subsystem-edit. Верификация: py 556/556, ps1 556/556; платформа 1С 8.3.24 (verify-snapshots) cf-edit 12/12, meta-edit/subsystem-edit round-trip загружаются; негатив-тест подтверждает, что harness ловит дефект на старом коде. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
fbdf07e18a
commit
57e99d144e
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# interface-edit v1.7 — Edit 1C CommandInterface.xml
|
||||
# interface-edit v1.8 — Edit 1C CommandInterface.xml
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -287,13 +287,49 @@ def parse_value_list(val):
|
||||
return [val]
|
||||
|
||||
|
||||
def save_xml_bom(tree, path):
|
||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
||||
xml_bytes = xml_bytes.replace(b"<?xml version='1.0' encoding='UTF-8'?>", b'<?xml version="1.0" encoding="utf-8"?>')
|
||||
if not xml_bytes.endswith(b"\n"):
|
||||
def _detect_xml_style(path):
|
||||
"""Стиль существующего файла для round-trip-сохранения: BOM / EOL / регистр encoding /
|
||||
финальный перенос. None → файл новый (сохранить текущее поведение)."""
|
||||
try:
|
||||
raw = open(path, "rb").read()
|
||||
except OSError:
|
||||
return None
|
||||
bom = raw.startswith(b"\xef\xbb\xbf")
|
||||
body = raw[3:] if bom else raw
|
||||
crlf = b"\r\n" in body
|
||||
m = re.search(rb'encoding="([^"]+)"', body[:200])
|
||||
enc = m.group(1).decode("ascii") if m else "utf-8"
|
||||
final_nl = body.endswith(b"\n")
|
||||
return {"bom": bom, "crlf": crlf, "enc": enc, "final_nl": final_nl}
|
||||
|
||||
|
||||
def _finalize_xml_bytes(xml_bytes, style):
|
||||
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None)."""
|
||||
enc_decl = style["enc"] if style else "utf-8"
|
||||
xml_bytes = xml_bytes.replace(
|
||||
b"<?xml version='1.0' encoding='UTF-8'?>",
|
||||
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + 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
|
||||
xml_bytes = xml_bytes.rstrip(b"\n")
|
||||
if want_final_nl:
|
||||
xml_bytes += b"\n"
|
||||
# EOL — как в оригинале (новый файл → LF, текущее поведение)
|
||||
if style and style["crlf"]:
|
||||
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
||||
return xml_bytes
|
||||
|
||||
|
||||
def save_xml_bom(tree, path):
|
||||
style = _detect_xml_style(path)
|
||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
||||
xml_bytes = _finalize_xml_bytes(xml_bytes, style)
|
||||
with open(path, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf")
|
||||
if style is None or style["bom"]:
|
||||
f.write(b"\xef\xbb\xbf")
|
||||
f.write(xml_bytes)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user