80 lines
2.3 KiB
Python
80 lines
2.3 KiB
Python
"""Mechanical child-record detection for 1C Config section containers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import asdict, dataclass
|
|
from typing import Any
|
|
|
|
from .payload import GUID_RE, scalar
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ChildRecord:
|
|
index: int
|
|
path: str
|
|
node: Any
|
|
evidence: dict[str, set[str]]
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
data = asdict(self)
|
|
data.pop("node", None)
|
|
data["evidence"] = {key: sorted(value) for key, value in self.evidence.items()}
|
|
return data
|
|
|
|
|
|
def children(node: Any) -> list[Any]:
|
|
if isinstance(node, dict) and node.get("type") in {"list", "sequence"}:
|
|
return node.get("items") or []
|
|
return []
|
|
|
|
|
|
def collect_evidence(node: Any) -> dict[str, set[str]]:
|
|
strings: set[str] = set()
|
|
guids: set[str] = set()
|
|
|
|
def walk(value: Any) -> None:
|
|
if isinstance(value, dict) and value.get("type") in {"atom", "string"}:
|
|
text = scalar(value)
|
|
if not text:
|
|
return
|
|
if value.get("type") == "string":
|
|
strings.add(text)
|
|
if GUID_RE.fullmatch(text):
|
|
guids.add(text.lower())
|
|
return
|
|
for child in children(value):
|
|
walk(child)
|
|
|
|
walk(node)
|
|
return {"strings": strings, "guids": guids}
|
|
|
|
|
|
def declared_child_records(section: Any, section_path: str, *, include_evidence: bool = True) -> list[ChildRecord]:
|
|
"""Return records for the common `{marker, count, record...}` container.
|
|
|
|
The function is deliberately structural. It does not assume that records are
|
|
attributes, dimensions, enum values, or any other metadata category.
|
|
"""
|
|
|
|
items = children(section)
|
|
if len(items) < 2:
|
|
return []
|
|
try:
|
|
declared_count = int(scalar(items[1]))
|
|
except ValueError:
|
|
return []
|
|
if declared_count < 0:
|
|
return []
|
|
candidates = items[2 : 2 + declared_count]
|
|
if len(candidates) != declared_count:
|
|
return []
|
|
return [
|
|
ChildRecord(
|
|
index=index,
|
|
path=f"{section_path}.{index + 2}",
|
|
node=record,
|
|
evidence=collect_evidence(record) if include_evidence else {"strings": set(), "guids": set()},
|
|
)
|
|
for index, record in enumerate(candidates)
|
|
]
|