#!/usr/bin/env python3 # form-remove v1.9 — Remove form 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 # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # регистр не различают, в argparse совпадение точное. def ci_parse_args(parser, argv=None): """parse_args по правилам PS: имена параметров и значения choices регистронезависимы.""" argv = list(sys.argv[1:] if argv is None else argv) names = {s.lower(): s for a in parser._actions for s in a.option_strings} for i, tok in enumerate(argv): if tok.startswith('-') and tok.lower() in names: argv[i] = names[tok.lower()] # choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр choice_map = {} for a in parser._actions: if a.choices: for s in a.option_strings: choice_map[s] = {str(c).lower(): c for c in a.choices} for i in range(len(argv) - 1): m = choice_map.get(argv[i]) if m and argv[i + 1].lower() in m: argv[i + 1] = m[argv[i + 1].lower()] return parser.parse_args(argv) 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"", 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 form from 1C object", allow_abbrev=False) parser.add_argument("-ObjectName", "-ProcessorName", required=True) parser.add_argument("-FormName", required=True) parser.add_argument("-SrcDir", default="src") args = ci_parse_args(parser) object_name = args.ObjectName form_name = args.FormName 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) forms_dir = os.path.join(processor_dir, "Forms") form_meta_path = os.path.join(forms_dir, f"{form_name}.xml") form_dir = os.path.join(forms_dir, form_name) if not os.path.exists(form_meta_path): print(f"Метаданные формы не найдены: {form_meta_path}", file=sys.stderr) sys.exit(1) # --- Delete files --- if os.path.isdir(form_dir): shutil.rmtree(form_dir) print(f"[OK] Удалён каталог: {form_dir}") os.remove(form_meta_path) print(f"[OK] Удалён файл: {form_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
from ChildObjects for node in root.findall(".//md:ChildObjects/md:Form", NSMAP): if node.text and node.text.strip() == form_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) # Опустевший контейнер: text="" сериализуется парой