Files
llm/scripts/build_1c_enum_presentation_map.py
T

154 lines
4.7 KiB
Python

#!/usr/bin/env python3
"""Build enum order -> presentation map from 1C XML routes."""
from __future__ import annotations
import argparse
import json
import xml.etree.ElementTree as ET
from pathlib import Path
from typing import Any
NS = {
"md": "http://v8.3/MDClasses",
"v8": "http://v8.1c.ru/8.1/data/core",
}
def load_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8-sig"))
def local_name(tag: str) -> str:
return tag.rsplit("}", 1)[-1]
def child_text(parent: ET.Element, name: str) -> str | None:
for child in list(parent):
if local_name(child.tag) == name:
return child.text or ""
return None
def properties(element: ET.Element) -> ET.Element | None:
for child in list(element):
if local_name(child.tag) == "Properties":
return child
return None
def synonym(props: ET.Element | None) -> str | None:
if props is None:
return None
for syn in list(props):
if local_name(syn.tag) != "Synonym":
continue
for item in list(syn):
lang = None
content = None
for child in list(item):
if local_name(child.tag) == "lang":
lang = child.text
elif local_name(child.tag) == "content":
content = child.text
if lang == "ru" and content:
return content
return None
def enum_values(path: Path) -> list[dict[str, Any]]:
root = ET.parse(path).getroot()
enum = next((node for node in root.iter() if local_name(node.tag) == "Enum"), None)
if enum is None:
return []
child_objects = next((node for node in list(enum) if local_name(node.tag) == "ChildObjects"), None)
if child_objects is None:
return []
values = []
order = 0
for node in list(child_objects):
if local_name(node.tag) != "EnumValue":
continue
props = properties(node)
name = child_text(props, "Name") if props is not None else None
values.append(
{
"order": order,
"uuid": (node.attrib.get("uuid") or "").lower() or None,
"name": name,
"synonym": synonym(props),
}
)
order += 1
return values
def is_base_config(top: dict[str, Any]) -> bool:
relative = str(top.get("relative_path") or "")
return relative.startswith("Enums\\")
def route_score(row: dict[str, Any]) -> tuple[int, str]:
return (1 if str(row.get("relative_path") or "").startswith("Enums\\") else 0, str(row.get("relative_path") or ""))
def main() -> int:
parser = argparse.ArgumentParser(description="Build 1C enum presentation map.")
parser.add_argument("--index", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
index = load_json(args.index)
enums: dict[str, dict[str, Any]] = {}
for guid, item in (index.get("objects") or {}).items():
tops = [
top
for top in item.get("xml_top_objects") or []
if top.get("xml_kind") == "Enum" and top.get("name") and top.get("path")
]
if not tops:
continue
tops.sort(key=lambda top: (not is_base_config(top), top.get("relative_path") or ""))
top = tops[0]
path = Path(top["path"])
if not path.is_file():
continue
try:
values = enum_values(path)
except ET.ParseError as error:
values = []
parse_error = str(error)
else:
parse_error = None
key = str(top["name"])
candidate = {
"guid": guid,
"name": top.get("name"),
"synonym": top.get("synonym"),
"relative_path": top.get("relative_path"),
"path": top.get("path"),
"parse_error": parse_error,
"values": values,
"by_order": {str(value["order"]): value for value in values},
"by_uuid": {value["uuid"]: value for value in values if value.get("uuid")},
}
existing = enums.get(key)
if existing is None or route_score(candidate) > route_score(existing):
enums[key] = candidate
result = {
"schema": "onec_enum_presentation_map.v1",
"index": str(args.index),
"enum_count": len(enums),
"enums": dict(sorted(enums.items())),
}
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(json.dumps({"output": str(args.output), "enum_count": len(enums)}, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())