mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-08 12:33:21 +03:00
Три остатка волны #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 вставлял <Role>/<Form>/<Subsystem> с пятью табами вместо трёх — подстановка по голому </ChildObjects> удваивала отступ строки; снэпшоты этого не видели, так как схлопывают пробелы между тегами; - 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) <noreply@anthropic.com>
140 lines
5.5 KiB
Python
140 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
|
# template-remove v1.6 — Remove template from 1C object
|
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import shutil
|
|
import sys
|
|
|
|
from lxml import etree
|
|
|
|
NSMAP = {"md": "http://v8.1c.ru/8.3/MDClasses"}
|
|
|
|
|
|
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) — к канону
|
|
выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
|
|
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"))
|
|
# Финальный перенос — как в оригинале (новый файл → нет, канон #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 — как в оригинале (новый файл → CRLF, канон #57)
|
|
if (style["crlf"] if style else True):
|
|
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
|
return xml_bytes
|
|
|
|
|
|
def save_xml_with_bom(tree, path):
|
|
"""Save XML tree preserving the existing file's BOM/EOL/encoding-case/final-newline."""
|
|
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:
|
|
if style is None or style["bom"]:
|
|
f.write(b"\xef\xbb\xbf")
|
|
f.write(xml_bytes)
|
|
|
|
|
|
def main():
|
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
sys.stderr.reconfigure(encoding="utf-8")
|
|
parser = argparse.ArgumentParser(description="Remove template from 1C object", allow_abbrev=False)
|
|
parser.add_argument("-ObjectName", "-ProcessorName", required=True)
|
|
parser.add_argument("-TemplateName", required=True)
|
|
parser.add_argument("-SrcDir", default="src")
|
|
args = parser.parse_args()
|
|
|
|
object_name = args.ObjectName
|
|
template_name = args.TemplateName
|
|
src_dir = args.SrcDir
|
|
|
|
# --- Checks ---
|
|
|
|
root_xml_path = os.path.join(src_dir, f"{object_name}.xml")
|
|
if not os.path.exists(root_xml_path):
|
|
print(f"Корневой файл обработки не найден: {root_xml_path}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
processor_dir = os.path.join(src_dir, object_name)
|
|
templates_dir = os.path.join(processor_dir, "Templates")
|
|
template_meta_path = os.path.join(templates_dir, f"{template_name}.xml")
|
|
template_dir = os.path.join(templates_dir, template_name)
|
|
|
|
if not os.path.exists(template_meta_path):
|
|
print(f"Метаданные макета не найдены: {template_meta_path}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# --- Delete files ---
|
|
|
|
if os.path.isdir(template_dir):
|
|
shutil.rmtree(template_dir)
|
|
print(f"[OK] Удалён каталог: {template_dir}")
|
|
|
|
os.remove(template_meta_path)
|
|
print(f"[OK] Удалён файл: {template_meta_path}")
|
|
|
|
# --- Modify root XML ---
|
|
|
|
root_xml_full = os.path.abspath(root_xml_path)
|
|
parser_xml = etree.XMLParser(remove_blank_text=False)
|
|
tree = etree.parse(root_xml_full, parser_xml)
|
|
root = tree.getroot()
|
|
|
|
# Remove <Template>TemplateName</Template> from ChildObjects
|
|
for node in root.findall(".//md:ChildObjects/md:Template", NSMAP):
|
|
if node.text and node.text.strip() == template_name:
|
|
parent = node.getparent()
|
|
prev = node.getprevious()
|
|
if prev is not None:
|
|
# Whitespace is in prev.tail
|
|
if prev.tail and prev.tail.strip() == "":
|
|
prev.tail = ""
|
|
else:
|
|
# First child — whitespace is in parent.text
|
|
if parent.text and parent.text.strip() == "":
|
|
parent.text = ""
|
|
parent.remove(node)
|
|
break
|
|
|
|
# Clear MainDataCompositionSchema if it pointed to this template
|
|
main_dcs = root.find(".//md:MainDataCompositionSchema", NSMAP)
|
|
if main_dcs is not None and main_dcs.text:
|
|
if re.search(rf"Template\.{re.escape(template_name)}$", main_dcs.text):
|
|
main_dcs.text = ""
|
|
print("[OK] Очищён MainDataCompositionSchema")
|
|
|
|
# Save with BOM
|
|
save_xml_with_bom(tree, root_xml_full)
|
|
|
|
print(f"[OK] Макет {template_name} удалён из {root_xml_path}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|