Classify appearance templates by XML root

This commit is contained in:
2026-08-14 12:09:13 +03:00
parent 61c0ed1726
commit 5615e262a8
3 changed files with 31 additions and 2 deletions
+12 -2
View File
@@ -172,7 +172,7 @@ ONEC_TEMPLATE_PLATFORM_TYPES = [
"xml_type": "DataCompositionAppearanceTemplate", "xml_type": "DataCompositionAppearanceTemplate",
"decoder": "data_composition_appearance", "decoder": "data_composition_appearance",
"status": "not_implemented", "status": "not_implemented",
"aliases": ["МакетОформленияКомпоновкиДанных", "DataCompositionAppearanceTemplate"], "aliases": ["МакетОформленияКомпоновкиДанных", "DataCompositionAppearanceTemplate", "AppearanceTemplate"],
}, },
{ {
"id": "external_component", "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("template_type"),
classification.get("platform_type_id"), classification.get("platform_type_id"),
classification.get("platform_type"), classification.get("platform_type"),
classification.get("xml_root"),
] ]
) )
candidates.extend(classification.get("strings_sample") or []) 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]] = [] candidates: list[dict[str, Any]] = []
declared = normalize_onec_template_platform_type(features.get("declared_platform_type_id") or features.get("declared_platform_type")) declared = normalize_onec_template_platform_type(features.get("declared_platform_type_id") or features.get("declared_platform_type"))
if declared: 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) candidates.append(declared)
def append_candidate(candidate: dict[str, Any]) -> None: 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), (template_type for template_type in (declared_template_platform_type_from_part(part) for part in parts) if template_type),
None, 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 = { features = {
"tabular_document": any(((part.get("features") or {}).get("tabular_document")) for part in content_parts), "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), "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": declared_platform_type.get("name"),
"declared_platform_type_id": declared_platform_type.get("id"), "declared_platform_type_id": declared_platform_type.get("id"),
"declared_platform_xml_type": declared_platform_type.get("xml_type"), "declared_platform_xml_type": declared_platform_type.get("xml_type"),
"declared_platform_type_evidence": declared_type_evidence,
} }
if declared_platform_type if declared_platform_type
else {} else {}
+9
View File
@@ -7,6 +7,9 @@ import hashlib
import re import re
from typing import Any 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 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"] = declared_prefix["text"]
stream["text_preview"] = str(declared_prefix["text"] or "").replace("\x00", "")[:500] stream["text_preview"] = str(declared_prefix["text"] or "").replace("\x00", "")[:500]
text = decoded.get("text") 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 tree = None
root = None root = None
base64_decoded: list[dict[str, Any]] = [] 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), "sha1": sha1_hex(data),
"payload_sha1": sha1_hex(bytes(payload)), "payload_sha1": sha1_hex(bytes(payload)),
"markers": markers, "markers": markers,
**({"xml_root": xml_root} if xml_root else {}),
"root": root, "root": root,
"role": classify_role(root_marker, markers, text, stream_blocks, base64_decoded), "role": classify_role(root_marker, markers, text, stream_blocks, base64_decoded),
"strings_sample": strings_sample, "strings_sample": strings_sample,
+10
View File
@@ -6949,6 +6949,16 @@ def test_public_template_summary_uses_declared_template_type_alias() -> None:
assert summary["template_type_candidates"][0]["confidence"] == "high" 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: def test_moxel_template_artifact_kind_tracks_coordinate_hint_schema() -> None:
assert adapter_server.MOXEL_TEMPLATE_ARTIFACT_KIND == "template_part_moxel_v11" assert adapter_server.MOXEL_TEMPLATE_ARTIFACT_KIND == "template_part_moxel_v11"