Skip to content

Commit b666fdc

Browse files
committed
feat: Re-type MelodyLoader uses in preparation for alternative backends
1 parent af32a48 commit b666fdc

File tree

18 files changed

+181
-88
lines changed

18 files changed

+181
-88
lines changed

src/capellambse/aird/__init__.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,23 @@ class _DiagramDescriptor(t.NamedTuple):
6262
target: etree._Element
6363

6464

65+
def _iter_views(model: loader.Loader, /) -> cabc.Iterator:
66+
# cf. C.XP_VIEWS
67+
for tree in model.trees.values():
68+
if tree.fragment_type != loader.FragmentType.VISUAL:
69+
continue
70+
if tree.root.tag == helpers.TAG_XMI:
71+
roots = tree.root.iterchildren()
72+
else:
73+
roots = iter([tree.root])
74+
for root in roots:
75+
if root.tag != f"{{{_n.NAMESPACES['viewpoint']}}}DAnalysis":
76+
continue
77+
yield from root.iterchildren("ownedViews")
78+
79+
6580
def enumerate_descriptors(
66-
model: loader.MelodyLoader,
81+
model: loader.Loader,
6782
*,
6883
viewpoint: str | None = None,
6984
) -> cabc.Iterator[DRepresentationDescriptor]:
@@ -77,7 +92,7 @@ def enumerate_descriptors(
7792
Only return diagrams of the given viewpoint. If not given, all
7893
diagrams are returned.
7994
"""
80-
for view in model.xpath(C.XP_VIEWS):
95+
for view in _iter_views(model):
8196
if viewpoint and _viewpoint_of(view) != viewpoint:
8297
continue
8398

@@ -89,7 +104,7 @@ def enumerate_descriptors(
89104
raise RuntimeError(
90105
f"Malformed diagram reference: {rep_path!r}"
91106
)
92-
diag_root = model[rep_path]
107+
diag_root = model.follow_link(d, rep_path)
93108
if diag_root.tag not in DIAGRAM_ROOTS:
94109
continue
95110

@@ -130,12 +145,12 @@ def parse_diagrams(
130145

131146

132147
def _build_descriptor(
133-
model: loader.MelodyLoader,
148+
model: loader.Loader,
134149
descriptor: DRepresentationDescriptor,
135150
) -> _DiagramDescriptor:
136151
assert isinstance(descriptor, etree._Element)
137152

138-
diag_root = model[descriptor.attrib["repPath"]]
153+
diag_root = model.follow_link(descriptor, descriptor.attrib["repPath"])
139154
styleclass = get_styleclass(descriptor)
140155
target = find_target(model, descriptor)
141156

@@ -151,7 +166,7 @@ def _build_descriptor(
151166

152167

153168
def find_target(
154-
model: loader.MelodyLoader, descriptor: DRepresentationDescriptor
169+
model: loader.Loader, descriptor: DRepresentationDescriptor
155170
) -> etree._Element:
156171
assert isinstance(descriptor, etree._Element)
157172
target_anchors = list(descriptor.iterchildren("target"))
@@ -180,7 +195,7 @@ def get_styleclass(descriptor: DRepresentationDescriptor) -> str | None:
180195

181196

182197
def parse_diagram(
183-
model: loader.MelodyLoader,
198+
model: loader.Loader,
184199
descriptor: DRepresentationDescriptor,
185200
**params: t.Any,
186201
) -> diagram.Diagram:
@@ -255,7 +270,11 @@ def parse_diagram(
255270
def _element_from_xml(ebd: C.ElementBuilder) -> diagram.DiagramElement:
256271
"""Construct a single diagram element from the model XML."""
257272
element = ebd.data_element.get("element")
258-
tag = ebd.melodyloader[element].tag if element else None
273+
tag = (
274+
ebd.melodyloader.follow_link(ebd.data_element, element).tag
275+
if element
276+
else None
277+
)
259278
if element is not None and tag != "ownedRepresentationDescriptors":
260279
factory = _semantic.from_xml
261280
else:
@@ -264,7 +283,7 @@ def _element_from_xml(ebd: C.ElementBuilder) -> diagram.DiagramElement:
264283

265284

266285
def iter_visible(
267-
model: loader.MelodyLoader,
286+
model: loader.Loader,
268287
descriptor: DRepresentationDescriptor,
269288
) -> cabc.Iterator[etree._Element]:
270289
r"""Iterate over all semantic elements that are visible in a diagram.

src/capellambse/aird/_box_factories.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,9 @@ def statemode_activities_factory(seb: C.SemanticElementBuilder) -> diagram.Box:
427427
for elm in seb.diag_element.iterchildren("ownedElements"):
428428
elm_id = elm.get("uid")
429429
try:
430-
target_id = next(elm.iterchildren("target")).attrib["href"]
431-
target = seb.melodyloader[target_id]
430+
target_link = next(elm.iterchildren("target"))
431+
target_id = target_link.attrib["href"]
432+
target = seb.melodyloader.follow_link(target_link, target_id)
432433
mapping_id = next(elm.iterchildren("actualMapping")).attrib["href"]
433434
except (KeyError, StopIteration):
434435
C.LOGGER.error("No usable target or mapping for %r", elm_id)

src/capellambse/aird/_common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class ElementBuilder:
5252
target_diagram: diagram.Diagram
5353
diagram_tree: etree._Element
5454
data_element: etree._Element
55-
melodyloader: capellambse.loader.MelodyLoader
55+
melodyloader: capellambse.loader.Loader
5656
fragment: pathlib.PurePosixPath
5757

5858

src/capellambse/aird/_edge_factories.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,9 @@ def req_relation_factory(seb: C.SemanticElementBuilder) -> diagram.Edge:
608608
if not label:
609609
try:
610610
reltype_id = seb.melodyobjs[0].attrib["relationType"]
611-
reltype = seb.melodyloader[reltype_id]
611+
reltype = seb.melodyloader.follow_link(
612+
seb.melodyobjs[0], reltype_id
613+
)
612614
label = reltype.attrib["ReqIFLongName"]
613615
except KeyError:
614616
C.LOGGER.warning(

src/capellambse/aird/_filters/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ class FilterArguments:
142142

143143
target_diagram: diagram.Diagram
144144
diagram_root: etree._Element
145-
melodyloader: capellambse.loader.MelodyLoader
145+
melodyloader: capellambse.loader.Loader
146146
params: dict[str, t.Any]
147147

148148

@@ -174,7 +174,7 @@ def applyfilters(args: FilterArguments) -> None:
174174
dgobject.styleclass or dgobject.__class__.__name__,
175175
dgobject.uuid,
176176
)
177-
data_element = args.melodyloader[dgobject.uuid]
177+
data_element = args.melodyloader.follow_link(None, dgobject.uuid)
178178
p2flt(
179179
c.ElementBuilder(
180180
target_diagram=args.target_diagram,
@@ -280,7 +280,9 @@ def __init__(
280280
self._model = model
281281
self._diagram = diagram
282282
assert isinstance(diagram._element, etree._Element)
283-
self._target = self._model._loader[diagram._element.attrib["repPath"]]
283+
self._target = self._model._loader.follow_link(
284+
diagram._element, diagram._element.attrib["repPath"]
285+
)
284286

285287
@property
286288
def _elements(self) -> t.Iterator[etree._Element]:

src/capellambse/aird/_filters/global.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ def hide_alloc_func_exch(
179179
for cex in component_exchanges:
180180
assert cex.uuid is not None
181181
# Find all allocated functional exchanges
182-
for fex in args.melodyloader[cex.uuid].iterchildren(
182+
cex_elem = args.melodyloader.follow_link(None, cex.uuid)
183+
for fex in cex_elem.iterchildren(
183184
"ownedComponentExchangeFunctionalExchangeAllocations"
184185
):
185186
target = fex.attrib["targetElement"].rsplit("#", 1)[-1]
@@ -189,7 +190,7 @@ def hide_alloc_func_exch(
189190

190191
def _stringify_exchange_items(
191192
obj: diagram.DiagramElement,
192-
melodyloader: capellambse.loader.MelodyLoader,
193+
melodyloader: capellambse.loader.Loader,
193194
sort_items: bool = False,
194195
) -> str:
195196
assert obj.uuid is not None
@@ -209,11 +210,11 @@ def _stringify_exchange_items(
209210
def _get_allocated_exchangeitem_names(
210211
*try_ids: str,
211212
alloc_attr: str,
212-
melodyloader: capellambse.loader.MelodyLoader,
213+
melodyloader: capellambse.loader.Loader,
213214
) -> tuple[lxml.etree._Element | None, list[str]]:
214215
for obj_id in try_ids:
215216
try:
216-
elm = melodyloader[obj_id]
217+
elm = melodyloader.follow_link(None, obj_id)
217218
except KeyError:
218219
pass
219220
else:
@@ -223,7 +224,7 @@ def _get_allocated_exchangeitem_names(
223224

224225
if elm.tag == "ownedDiagramElements":
225226
targetlink = next(elm.iterchildren("target"))
226-
elm = melodyloader[targetlink.attrib["href"]]
227+
elm = melodyloader.follow_link(targetlink, targetlink.attrib["href"])
227228

228229
names = []
229230
for elem in melodyloader.follow_links(

src/capellambse/aird/_visual.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,10 @@ def shape_factory(ebd: c.ElementBuilder) -> diagram.Box:
6666
assert ebd.target_diagram.styleclass is not None
6767

6868
uid = ebd.data_element.attrib[c.ATT_XMID]
69-
element = ebd.data_element.get("element")
70-
if element is not None:
71-
label = ebd.melodyloader[element].attrib["name"]
69+
element_id = ebd.data_element.get("element")
70+
if element_id is not None:
71+
element = ebd.melodyloader.follow_link(ebd.data_element, element_id)
72+
label = element.attrib["name"]
7273
description = ebd.data_element.get("description", "")
7374
else:
7475
label = ebd.data_element.get("description", "")
@@ -108,7 +109,7 @@ def shape_factory(ebd: c.ElementBuilder) -> diagram.Box:
108109
int(layout.attrib.get("height", "0")),
109110
)
110111

111-
if element is not None:
112+
if element_id is not None:
112113
styleclass = "RepresentationLink"
113114
else:
114115
styleclass = "Note"

src/capellambse/helpers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -845,7 +845,7 @@ def resolve_namespace(tag: str) -> str:
845845

846846

847847
def unescape_linked_text(
848-
loader: capellambse.loader.MelodyLoader, attr_text: str | None
848+
loader: capellambse.loader.Loader, attr_text: str | None
849849
) -> markupsafe.Markup:
850850
"""Transform the ``linkedText`` into regular HTML."""
851851

@@ -863,7 +863,7 @@ def flatten_element(
863863
ehref = html.escape(href)
864864

865865
try:
866-
target = loader[href]
866+
target = loader.follow_link(None, href)
867867
except KeyError:
868868
yield f"<deleted element {ehref}>"
869869
else:
@@ -887,7 +887,7 @@ def flatten_element(
887887

888888

889889
def escape_linked_text(
890-
loader: capellambse.loader.MelodyLoader, attr_text: str
890+
loader: capellambse.loader.Loader, attr_text: str
891891
) -> str:
892892
"""Transform simple HTML with object links into ``LinkedText``.
893893

src/capellambse/loader/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99
.. _LXML Documentation: https://lxml.de/
1010
"""
1111

12+
from ._typing import *
1213
from .core import *
13-
from .modelinfo import ModelInfo as ModelInfo

src/capellambse/loader/_typing.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# SPDX-FileCopyrightText: Copyright DB InfraGO AG
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
__all__ = [
5+
"FragmentType",
6+
"Loader",
7+
"ModelInfo",
8+
]
9+
10+
import dataclasses
11+
import enum
12+
import pathlib
13+
import typing as t
14+
15+
from capellambse import filehandler
16+
17+
if t.TYPE_CHECKING:
18+
from . import core
19+
20+
Loader: t.TypeAlias = "core.MelodyLoader"
21+
22+
23+
class FragmentType(enum.Enum):
24+
"""The type of an XML fragment."""
25+
26+
SEMANTIC = enum.auto()
27+
VISUAL = enum.auto()
28+
OTHER = enum.auto()
29+
30+
31+
@dataclasses.dataclass
32+
class ModelInfo:
33+
url: str | None
34+
title: str | None
35+
entrypoint: pathlib.PurePosixPath
36+
resources: dict[str, filehandler.abc.HandlerInfo]
37+
capella_version: str
38+
viewpoints: dict[str, str]

0 commit comments

Comments
 (0)