From 5615e262a86b47ca84b1f29e56ed22c3917a2335 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 14 Aug 2026 12:09:13 +0300 Subject: [PATCH] Classify appearance templates by XML root --- plugins/1c/connector/adapter_1c_server.py | 14 ++++++++++++-- plugins/1c/parser/cas_payload.py | 9 +++++++++ tests/1c/test_payload_codec.py | 10 ++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/plugins/1c/connector/adapter_1c_server.py b/plugins/1c/connector/adapter_1c_server.py index 700b7e7..3a3b150 100644 --- a/plugins/1c/connector/adapter_1c_server.py +++ b/plugins/1c/connector/adapter_1c_server.py @@ -172,7 +172,7 @@ ONEC_TEMPLATE_PLATFORM_TYPES = [ "xml_type": "DataCompositionAppearanceTemplate", "decoder": "data_composition_appearance", "status": "not_implemented", - "aliases": ["МакетОформленияКомпоновкиДанных", "DataCompositionAppearanceTemplate"], + "aliases": ["МакетОформленияКомпоновкиДанных", "DataCompositionAppearanceTemplate", "AppearanceTemplate"], }, { "id": "external_component", @@ -4087,6 +4087,7 @@ def declared_template_platform_type_from_part(part: dict[str, Any]) -> dict[str, classification.get("template_type"), classification.get("platform_type_id"), classification.get("platform_type"), + classification.get("xml_root"), ] ) candidates.extend(classification.get("strings_sample") or []) @@ -4101,7 +4102,7 @@ def template_type_candidates_from_features(features: dict[str, Any], content_par candidates: list[dict[str, Any]] = [] declared = normalize_onec_template_platform_type(features.get("declared_platform_type_id") or features.get("declared_platform_type")) if declared: - declared.update({"confidence": "high", "evidence": ["TemplateType metadata"]}) + declared.update({"confidence": "high", "evidence": [str(features.get("declared_platform_type_evidence") or "TemplateType metadata")]}) candidates.append(declared) def append_candidate(candidate: dict[str, Any]) -> None: @@ -4140,6 +4141,14 @@ def public_template_summary( (template_type for template_type in (declared_template_platform_type_from_part(part) for part in parts) if template_type), None, ) + declared_type_evidence = "TemplateType metadata" + if any( + normalize_onec_template_platform_type( + ((part.get("classification") or {}).get("xml_root") if isinstance(part.get("classification"), dict) else None) + ) + for part in parts + ): + declared_type_evidence = "payload XML root" features = { "tabular_document": any(((part.get("features") or {}).get("tabular_document")) for part in content_parts), "html": any(((part.get("features") or {}).get("html")) for part in content_parts), @@ -4149,6 +4158,7 @@ def public_template_summary( "declared_platform_type": declared_platform_type.get("name"), "declared_platform_type_id": declared_platform_type.get("id"), "declared_platform_xml_type": declared_platform_type.get("xml_type"), + "declared_platform_type_evidence": declared_type_evidence, } if declared_platform_type else {} diff --git a/plugins/1c/parser/cas_payload.py b/plugins/1c/parser/cas_payload.py index 73c5fb2..2e57b0d 100644 --- a/plugins/1c/parser/cas_payload.py +++ b/plugins/1c/parser/cas_payload.py @@ -7,6 +7,9 @@ import hashlib import re from typing import Any + +XML_ROOT_RE = re.compile(r"^\s*(?:<\?xml[^>]*>\s*)?<([A-Za-z_][\w.-]*(?::[A-Za-z_][\w.-]*)?)\b") + from .payload import collect_strings, decode_payload_lossless, encode_text, parse_brace_text, root_signature, scalar @@ -483,6 +486,11 @@ def classify_payload(data: bytes, *, include_text: bool = False, include_tree: b stream["text"] = declared_prefix["text"] stream["text_preview"] = str(declared_prefix["text"] or "").replace("\x00", "")[:500] text = decoded.get("text") + xml_root = None + if isinstance(text, str): + match = XML_ROOT_RE.match(text.lstrip("\ufeff")) + if match: + xml_root = match.group(1).rsplit(":", 1)[-1] tree = None root = None base64_decoded: list[dict[str, Any]] = [] @@ -505,6 +513,7 @@ def classify_payload(data: bytes, *, include_text: bool = False, include_tree: b "sha1": sha1_hex(data), "payload_sha1": sha1_hex(bytes(payload)), "markers": markers, + **({"xml_root": xml_root} if xml_root else {}), "root": root, "role": classify_role(root_marker, markers, text, stream_blocks, base64_decoded), "strings_sample": strings_sample, diff --git a/tests/1c/test_payload_codec.py b/tests/1c/test_payload_codec.py index 22c056a..86099a7 100644 --- a/tests/1c/test_payload_codec.py +++ b/tests/1c/test_payload_codec.py @@ -6949,6 +6949,16 @@ def test_public_template_summary_uses_declared_template_type_alias() -> None: assert summary["template_type_candidates"][0]["confidence"] == "high" +def test_public_template_summary_recognizes_live_appearance_xml_root() -> None: + summary = adapter_server.public_template_summary( + [{"role": "content", "classification": {"xml_root": "AppearanceTemplate"}, "features": {}}] + ) + + assert summary["platform_type"] == "Макет оформления компоновки данных" + assert summary["template_type_candidates"][0]["id"] == "data_composition_appearance_template" + assert summary["template_type_candidates"][0]["evidence"] == ["payload XML root"] + + def test_moxel_template_artifact_kind_tracks_coordinate_hint_schema() -> None: assert adapter_server.MOXEL_TEMPLATE_ARTIFACT_KIND == "template_part_moxel_v11"