Extract managed form elements from XML
This commit is contained in:
@@ -171,7 +171,7 @@ def parse_one_c_xml_file(path: str | Path) -> list[OneCXmlObject]:
|
||||
source_path = normalize_source_path(path)
|
||||
root = ET.fromstring(_read_text_file(Path(path)))
|
||||
result: list[OneCXmlObject] = []
|
||||
_walk_xml_objects(source_path, root, result, current_role=None, parent_qualified_name=None)
|
||||
_walk_xml_objects(source_path, root, result, current_role=None, parent_qualified_name=None, parent_object_kind=None)
|
||||
return result
|
||||
|
||||
|
||||
@@ -351,10 +351,12 @@ def _walk_xml_objects(
|
||||
*,
|
||||
current_role: OneCXmlObject | None,
|
||||
parent_qualified_name: str | None,
|
||||
parent_object_kind: str | None,
|
||||
) -> None:
|
||||
role_context = current_role
|
||||
child_parent_qualified_name = parent_qualified_name
|
||||
object_kind = _xml_object_kind(element)
|
||||
child_parent_object_kind = parent_object_kind
|
||||
object_kind = _xml_object_kind(element, parent_object_kind=parent_object_kind)
|
||||
if object_kind == "RIGHT":
|
||||
right = _xml_right_object(source_path, element, role_context)
|
||||
if right is not None:
|
||||
@@ -371,6 +373,7 @@ def _walk_xml_objects(
|
||||
)
|
||||
result.append(xml_object)
|
||||
child_parent_qualified_name = xml_object.qualified_name
|
||||
child_parent_object_kind = object_kind
|
||||
if object_kind == "ROLE":
|
||||
role_context = xml_object
|
||||
|
||||
@@ -381,6 +384,7 @@ def _walk_xml_objects(
|
||||
result,
|
||||
current_role=role_context,
|
||||
parent_qualified_name=child_parent_qualified_name,
|
||||
parent_object_kind=child_parent_object_kind,
|
||||
)
|
||||
|
||||
|
||||
@@ -695,6 +699,25 @@ _OBJECT_KIND_BY_TAG = {
|
||||
"предопределенный": "PREDEFINED",
|
||||
}
|
||||
|
||||
_FORM_ELEMENT_TAGS = {
|
||||
"item",
|
||||
"items",
|
||||
"element",
|
||||
"elements",
|
||||
"formitem",
|
||||
"formitems",
|
||||
"field",
|
||||
"fields",
|
||||
"group",
|
||||
"groups",
|
||||
"table",
|
||||
"tables",
|
||||
"button",
|
||||
"buttons",
|
||||
"page",
|
||||
"pages",
|
||||
}
|
||||
|
||||
|
||||
_QUALIFIED_PREFIX_BY_KIND = {
|
||||
"CATALOG": "Справочник",
|
||||
@@ -1288,8 +1311,10 @@ _PATH_METADATA_ALIASES = {
|
||||
}
|
||||
|
||||
|
||||
def _xml_object_kind(element: ET.Element) -> str | None:
|
||||
def _xml_object_kind(element: ET.Element, *, parent_object_kind: str | None = None) -> str | None:
|
||||
tag = _local_name(element.tag).lower()
|
||||
if parent_object_kind in {"FORM", "ELEMENT"} and tag in _FORM_ELEMENT_TAGS and _xml_name(element):
|
||||
return "ELEMENT"
|
||||
if tag in {"metadataobject", "object"}:
|
||||
type_name = _xml_type_name(element)
|
||||
if type_name:
|
||||
@@ -1384,6 +1409,11 @@ def _xml_qualified_name(
|
||||
|
||||
def _xml_attributes(element: ET.Element) -> dict:
|
||||
attributes = dict(element.attrib)
|
||||
for key, value in element.attrib.items():
|
||||
local_key = _local_name(key)
|
||||
attributes.setdefault(local_key, value)
|
||||
if local_key.lower() == "type":
|
||||
attributes.setdefault("control_kind", value.split(":")[-1].split(".")[-1])
|
||||
attribute_role = _xml_attribute_role(element)
|
||||
if attribute_role:
|
||||
attributes.setdefault("attribute_role", attribute_role)
|
||||
|
||||
@@ -983,7 +983,9 @@ def _xml_edge_kind(kind: NodeKind) -> EdgeKind:
|
||||
return EdgeKind.HAS_TABULAR_SECTION
|
||||
if kind == NodeKind.ROLE:
|
||||
return EdgeKind.HAS_ROLE
|
||||
return EdgeKind.HAS_ELEMENT
|
||||
if kind == NodeKind.FORM_ELEMENT:
|
||||
return EdgeKind.HAS_ELEMENT
|
||||
return EdgeKind.CONTAINS
|
||||
|
||||
|
||||
def _find_xml_parent(parents: dict[str, SemanticNode], qualified_name: str) -> SemanticNode | None:
|
||||
|
||||
@@ -346,6 +346,33 @@ def test_index_project_links_form_command_to_handler(tmp_path: Path):
|
||||
assert any(edge.kind == EdgeKind.HANDLES for edge in snapshot.edges)
|
||||
|
||||
|
||||
def test_index_project_extracts_managed_form_items_without_layouts(tmp_path: Path):
|
||||
xml = tmp_path / "form.xml"
|
||||
xml.write_text(
|
||||
"""
|
||||
<Form name="ФормаДокумента" qualifiedName="Документ.Заказ.ФормаДокумента">
|
||||
<items xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="form:FormGroup" name="Основное">
|
||||
<caption>Основное</caption>
|
||||
<items xsi:type="form:FormField" name="Номер">
|
||||
<caption>Номер</caption>
|
||||
<dataPath>Объект.Номер</dataPath>
|
||||
</items>
|
||||
</items>
|
||||
<Layout name="ПечатнаяФорма" qualifiedName="Документ.Заказ.ФормаДокумента.ПечатнаяФорма" />
|
||||
</Form>
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
snapshot = index_project(tmp_path, project_id="ui-form-items")
|
||||
|
||||
form = form_semantics(snapshot)[0]
|
||||
assert [element.name for element in form.elements] == ["Основное", "Номер"]
|
||||
assert all(element.kind == NodeKind.FORM_ELEMENT for element in form.elements)
|
||||
assert form.elements[1].attributes["dataPath"] == "Объект.Номер"
|
||||
assert not any(element.name == "ПечатнаяФорма" for element in form.elements)
|
||||
|
||||
|
||||
def test_index_project_links_form_events_to_handlers(tmp_path: Path):
|
||||
xml = tmp_path / "form.xml"
|
||||
xml.write_text(
|
||||
|
||||
@@ -19,15 +19,20 @@ def form_semantics(snapshot: SirSnapshot) -> list[FormSemantics]:
|
||||
for node in snapshot.nodes
|
||||
if node.kind == NodeKind.FORM
|
||||
}
|
||||
element_children: dict[str, list[SemanticNode]] = {}
|
||||
for edge in snapshot.edges:
|
||||
form = forms.get(edge.source_lineage)
|
||||
target = nodes.get(edge.target_lineage)
|
||||
if form is None or target is None:
|
||||
if target is None:
|
||||
continue
|
||||
if edge.kind == EdgeKind.HAS_ELEMENT and target.kind == NodeKind.FORM_ELEMENT:
|
||||
element_children.setdefault(edge.source_lineage, []).append(target)
|
||||
if form is None:
|
||||
continue
|
||||
if edge.kind == EdgeKind.HAS_COMMAND:
|
||||
form.commands.append(target)
|
||||
elif edge.kind == EdgeKind.HAS_ELEMENT:
|
||||
form.elements.append(target)
|
||||
for form in forms.values():
|
||||
form.elements.extend(_flatten_form_elements(form.form.lineage_id, element_children))
|
||||
command_to_form = {
|
||||
command.lineage_id: form
|
||||
for form in forms.values()
|
||||
@@ -43,4 +48,20 @@ def form_semantics(snapshot: SirSnapshot) -> list[FormSemantics]:
|
||||
return sorted(forms.values(), key=lambda item: item.form.qualified_name)
|
||||
|
||||
|
||||
def _flatten_form_elements(root_lineage: str, element_children: dict[str, list[SemanticNode]]) -> list[SemanticNode]:
|
||||
result: list[SemanticNode] = []
|
||||
seen: set[str] = set()
|
||||
|
||||
def visit(parent_lineage: str) -> None:
|
||||
for element in element_children.get(parent_lineage, []):
|
||||
if element.lineage_id in seen:
|
||||
continue
|
||||
seen.add(element.lineage_id)
|
||||
result.append(element)
|
||||
visit(element.lineage_id)
|
||||
|
||||
visit(root_lineage)
|
||||
return result
|
||||
|
||||
|
||||
__all__ = ["FormSemantics", "form_semantics"]
|
||||
|
||||
Reference in New Issue
Block a user