Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
"""Small XML metadata extractor used as validation oracle for SQL payloads."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
def local_name(tag: str) -> str:
|
||||
return tag.rsplit("}", 1)[-1] if "}" in tag else tag
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class XmlMetadataItem:
|
||||
category: str
|
||||
name: str
|
||||
synonym: str
|
||||
uuid: str | None
|
||||
value_type: dict[str, Any] | None = None
|
||||
parent_category: str | None = None
|
||||
parent_name: str | None = None
|
||||
parent_uuid: str | None = None
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
def direct_child(parent: ET.Element, name: str) -> ET.Element | None:
|
||||
for child in list(parent):
|
||||
if local_name(child.tag) == name:
|
||||
return child
|
||||
return None
|
||||
|
||||
|
||||
def child_text(parent: ET.Element, name: str) -> str:
|
||||
child = direct_child(parent, name)
|
||||
return (child.text or "").strip() if child is not None else ""
|
||||
|
||||
|
||||
def synonym_text(properties: ET.Element | None) -> str:
|
||||
if properties is None:
|
||||
return ""
|
||||
synonym = direct_child(properties, "Synonym")
|
||||
if synonym is None:
|
||||
return ""
|
||||
for node in synonym.iter():
|
||||
if local_name(node.tag) == "content" and node.text:
|
||||
return node.text.strip()
|
||||
return ""
|
||||
|
||||
|
||||
def value_type(properties: ET.Element | None) -> dict[str, Any] | None:
|
||||
if properties is None:
|
||||
return None
|
||||
type_node = direct_child(properties, "Type")
|
||||
if type_node is None:
|
||||
return None
|
||||
types = []
|
||||
qualifiers: dict[str, Any] = {}
|
||||
for node in type_node.iter():
|
||||
name = local_name(node.tag)
|
||||
text = (node.text or "").strip()
|
||||
if name == "Type" and node is not type_node and text:
|
||||
types.append(text)
|
||||
elif name in {"Length", "AllowedLength", "Digits", "FractionDigits", "AllowedSign", "DateFractions"} and text:
|
||||
qualifiers[name] = text
|
||||
if not types and not qualifiers:
|
||||
return None
|
||||
return {
|
||||
"types": types,
|
||||
"qualifiers": qualifiers,
|
||||
"is_composite": len(types) > 1,
|
||||
}
|
||||
|
||||
|
||||
def item_from_properties(
|
||||
category: str,
|
||||
node: ET.Element,
|
||||
properties: ET.Element | None,
|
||||
*,
|
||||
parent: XmlMetadataItem | None = None,
|
||||
) -> XmlMetadataItem:
|
||||
return XmlMetadataItem(
|
||||
category=category,
|
||||
name=child_text(properties, "Name") if properties is not None else node.attrib.get("name", ""),
|
||||
synonym=synonym_text(properties),
|
||||
uuid=node.attrib.get("uuid"),
|
||||
value_type=value_type(properties),
|
||||
parent_category=parent.category if parent else None,
|
||||
parent_name=parent.name if parent else None,
|
||||
parent_uuid=parent.uuid.lower() if parent and parent.uuid else None,
|
||||
)
|
||||
|
||||
|
||||
def extract_xml_metadata_items(path: Path) -> list[XmlMetadataItem]:
|
||||
root = ET.parse(path).getroot()
|
||||
metadata_node = next(iter(list(root)), None)
|
||||
if metadata_node is None:
|
||||
return []
|
||||
|
||||
result: list[XmlMetadataItem] = []
|
||||
properties = direct_child(metadata_node, "Properties")
|
||||
root_item = item_from_properties(local_name(metadata_node.tag), metadata_node, properties)
|
||||
result.append(root_item)
|
||||
|
||||
internal = direct_child(metadata_node, "InternalInfo")
|
||||
if internal is not None:
|
||||
for generated in internal.iter():
|
||||
if local_name(generated.tag) == "GeneratedType":
|
||||
result.append(
|
||||
XmlMetadataItem(
|
||||
category="GeneratedType",
|
||||
name=generated.attrib.get("name", ""),
|
||||
synonym=generated.attrib.get("category", ""),
|
||||
uuid=None,
|
||||
)
|
||||
)
|
||||
|
||||
if properties is not None:
|
||||
standard = direct_child(properties, "StandardAttributes")
|
||||
if standard is not None:
|
||||
for node in list(standard):
|
||||
if local_name(node.tag) == "StandardAttribute":
|
||||
result.append(
|
||||
XmlMetadataItem(
|
||||
category="StandardAttribute",
|
||||
name=node.attrib.get("name", ""),
|
||||
synonym=synonym_text(node),
|
||||
uuid=None,
|
||||
)
|
||||
)
|
||||
|
||||
child_objects = direct_child(metadata_node, "ChildObjects")
|
||||
if child_objects is not None:
|
||||
for node in list(child_objects):
|
||||
category = local_name(node.tag)
|
||||
props = direct_child(node, "Properties")
|
||||
child_item = item_from_properties(category, node, props, parent=root_item)
|
||||
result.append(child_item)
|
||||
append_nested_child_objects(result, node, child_item)
|
||||
return result
|
||||
|
||||
|
||||
def append_nested_child_objects(result: list[XmlMetadataItem], parent_node: ET.Element, parent_item: XmlMetadataItem) -> None:
|
||||
child_objects = direct_child(parent_node, "ChildObjects")
|
||||
if child_objects is None:
|
||||
return
|
||||
for node in list(child_objects):
|
||||
category = local_name(node.tag)
|
||||
props = direct_child(node, "Properties")
|
||||
child_item = item_from_properties(category, node, props, parent=parent_item)
|
||||
result.append(child_item)
|
||||
append_nested_child_objects(result, node, child_item)
|
||||
|
||||
|
||||
def group_xml_items(items: list[XmlMetadataItem]) -> dict[str, list[XmlMetadataItem]]:
|
||||
grouped: dict[str, list[XmlMetadataItem]] = {}
|
||||
for item in items:
|
||||
grouped.setdefault(item.category, []).append(item)
|
||||
return grouped
|
||||
Reference in New Issue
Block a user