Decode DCS appearance template XML
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
"""Read-only decoder for 1C DCS appearance-template payloads.
|
||||
|
||||
Appearance templates are a distinct XML family (``AppearanceTemplate``), not
|
||||
DataCompositionSchema and not a spreadsheet document. This decoder returns
|
||||
only XML-backed rules and preserves the original carrier for any future
|
||||
lossless writer work.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
from collections import Counter
|
||||
from typing import Any
|
||||
|
||||
from .payload import decode_payload_lossless
|
||||
|
||||
|
||||
def _local_name(tag: str) -> str:
|
||||
return tag.rsplit("}", 1)[-1]
|
||||
|
||||
|
||||
def _child_text(node: ET.Element, name: str) -> str:
|
||||
child = next((item for item in node if _local_name(item.tag) == name), None)
|
||||
return "".join(child.itertext()).strip() if child is not None else ""
|
||||
|
||||
|
||||
def inspect_appearance_payload(data: bytes) -> dict[str, Any]:
|
||||
"""Decode a compressed appearance-template carrier without changing it."""
|
||||
|
||||
decoded = decode_payload_lossless(data)
|
||||
payload = bytes(decoded.get("payload") or b"")
|
||||
start = payload.find(b"<?xml")
|
||||
if start < 0:
|
||||
start = payload.find(b"<AppearanceTemplate")
|
||||
end_marker = b"</AppearanceTemplate>"
|
||||
end = payload.find(end_marker, start) if start >= 0 else -1
|
||||
if start < 0 or end < 0:
|
||||
return {
|
||||
"status": "undecodable",
|
||||
"container": {"compression": decoded.get("compression"), "raw_bytes": decoded.get("raw_bytes"), "payload_bytes": decoded.get("payload_bytes")},
|
||||
"diagnostics": {"code": "APPEARANCE_XML_NOT_FOUND"},
|
||||
}
|
||||
xml_bytes = payload[start : end + len(end_marker)]
|
||||
try:
|
||||
root = ET.fromstring(xml_bytes.decode("utf-8-sig"))
|
||||
except (UnicodeDecodeError, ET.ParseError) as exc:
|
||||
return {"status": "undecodable", "diagnostics": {"code": "APPEARANCE_XML_INVALID", "message": str(exc)}}
|
||||
if _local_name(root.tag) != "AppearanceTemplate":
|
||||
return {"status": "unsupported", "diagnostics": {"code": "APPEARANCE_ROOT_NOT_CONFIRMED", "xml_root": _local_name(root.tag)}}
|
||||
items = []
|
||||
for ordinal, item in enumerate((node for node in root if _local_name(node.tag) == "item"), start=1):
|
||||
parameter, value = _child_text(item, "parameter"), _child_text(item, "value")
|
||||
items.append({"ordinal": ordinal, "parameter": parameter, "value": value})
|
||||
tag_counts = Counter(_local_name(node.tag) for node in root.iter())
|
||||
return {
|
||||
"status": "ok",
|
||||
"container": {"compression": decoded.get("compression"), "raw_bytes": decoded.get("raw_bytes"), "payload_bytes": decoded.get("payload_bytes"), "xml_offset": start, "xml_bytes": len(xml_bytes), "xml_root": "AppearanceTemplate"},
|
||||
"items": items,
|
||||
"outline": {"tag_counts": dict(sorted(tag_counts.items(), key=lambda item: item[0].casefold()))},
|
||||
"diagnostics": {"write": "unsupported_until_reverse_codec_fixture"},
|
||||
}
|
||||
Reference in New Issue
Block a user