Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions src/capellambse/aird/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,23 @@ class _DiagramDescriptor(t.NamedTuple):
target: etree._Element


def _iter_views(model: loader.Loader, /) -> cabc.Iterator:
# cf. C.XP_VIEWS
for tree in model.trees.values():
if tree.fragment_type != loader.FragmentType.VISUAL:
continue
if tree.root.tag == helpers.TAG_XMI:
roots = tree.root.iterchildren()
else:
roots = iter([tree.root])
for root in roots:
if root.tag != f"{{{_n.NAMESPACES['viewpoint']}}}DAnalysis":
continue
yield from root.iterchildren("ownedViews")


def enumerate_descriptors(
model: loader.MelodyLoader,
model: loader.Loader,
*,
viewpoint: str | None = None,
) -> cabc.Iterator[DRepresentationDescriptor]:
Expand All @@ -77,7 +92,7 @@ def enumerate_descriptors(
Only return diagrams of the given viewpoint. If not given, all
diagrams are returned.
"""
for view in model.xpath(C.XP_VIEWS):
for view in _iter_views(model):
if viewpoint and _viewpoint_of(view) != viewpoint:
continue

Expand All @@ -89,7 +104,7 @@ def enumerate_descriptors(
raise RuntimeError(
f"Malformed diagram reference: {rep_path!r}"
)
diag_root = model[rep_path]
diag_root = model.follow_link(d, rep_path)
if diag_root.tag not in DIAGRAM_ROOTS:
continue

Expand Down Expand Up @@ -130,12 +145,12 @@ def parse_diagrams(


def _build_descriptor(
model: loader.MelodyLoader,
model: loader.Loader,
descriptor: DRepresentationDescriptor,
) -> _DiagramDescriptor:
assert isinstance(descriptor, etree._Element)

diag_root = model[descriptor.attrib["repPath"]]
diag_root = model.follow_link(descriptor, descriptor.attrib["repPath"])
styleclass = get_styleclass(descriptor)
target = find_target(model, descriptor)

Expand All @@ -151,7 +166,7 @@ def _build_descriptor(


def find_target(
model: loader.MelodyLoader, descriptor: DRepresentationDescriptor
model: loader.Loader, descriptor: DRepresentationDescriptor
) -> etree._Element:
assert isinstance(descriptor, etree._Element)
target_anchors = list(descriptor.iterchildren("target"))
Expand Down Expand Up @@ -180,7 +195,7 @@ def get_styleclass(descriptor: DRepresentationDescriptor) -> str | None:


def parse_diagram(
model: loader.MelodyLoader,
model: loader.Loader,
descriptor: DRepresentationDescriptor,
**params: t.Any,
) -> diagram.Diagram:
Expand Down Expand Up @@ -255,7 +270,11 @@ def parse_diagram(
def _element_from_xml(ebd: C.ElementBuilder) -> diagram.DiagramElement:
"""Construct a single diagram element from the model XML."""
element = ebd.data_element.get("element")
tag = ebd.melodyloader[element].tag if element else None
tag = (
ebd.melodyloader.follow_link(ebd.data_element, element).tag
if element
else None
)
if element is not None and tag != "ownedRepresentationDescriptors":
factory = _semantic.from_xml
else:
Expand All @@ -264,7 +283,7 @@ def _element_from_xml(ebd: C.ElementBuilder) -> diagram.DiagramElement:


def iter_visible(
model: loader.MelodyLoader,
model: loader.Loader,
descriptor: DRepresentationDescriptor,
) -> cabc.Iterator[etree._Element]:
r"""Iterate over all semantic elements that are visible in a diagram.
Expand Down
5 changes: 3 additions & 2 deletions src/capellambse/aird/_box_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,8 +427,9 @@ def statemode_activities_factory(seb: C.SemanticElementBuilder) -> diagram.Box:
for elm in seb.diag_element.iterchildren("ownedElements"):
elm_id = elm.get("uid")
try:
target_id = next(elm.iterchildren("target")).attrib["href"]
target = seb.melodyloader[target_id]
target_link = next(elm.iterchildren("target"))
target_id = target_link.attrib["href"]
target = seb.melodyloader.follow_link(target_link, target_id)
mapping_id = next(elm.iterchildren("actualMapping")).attrib["href"]
except (KeyError, StopIteration):
C.LOGGER.error("No usable target or mapping for %r", elm_id)
Expand Down
2 changes: 1 addition & 1 deletion src/capellambse/aird/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class ElementBuilder:
target_diagram: diagram.Diagram
diagram_tree: etree._Element
data_element: etree._Element
melodyloader: capellambse.loader.MelodyLoader
melodyloader: capellambse.loader.Loader
fragment: pathlib.PurePosixPath


Expand Down
4 changes: 3 additions & 1 deletion src/capellambse/aird/_edge_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,9 @@ def req_relation_factory(seb: C.SemanticElementBuilder) -> diagram.Edge:
if not label:
try:
reltype_id = seb.melodyobjs[0].attrib["relationType"]
reltype = seb.melodyloader[reltype_id]
reltype = seb.melodyloader.follow_link(
seb.melodyobjs[0], reltype_id
)
label = reltype.attrib["ReqIFLongName"]
except KeyError:
C.LOGGER.warning(
Expand Down
8 changes: 5 additions & 3 deletions src/capellambse/aird/_filters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ class FilterArguments:

target_diagram: diagram.Diagram
diagram_root: etree._Element
melodyloader: capellambse.loader.MelodyLoader
melodyloader: capellambse.loader.Loader
params: dict[str, t.Any]


Expand Down Expand Up @@ -174,7 +174,7 @@ def applyfilters(args: FilterArguments) -> None:
dgobject.styleclass or dgobject.__class__.__name__,
dgobject.uuid,
)
data_element = args.melodyloader[dgobject.uuid]
data_element = args.melodyloader.follow_link(None, dgobject.uuid)
p2flt(
c.ElementBuilder(
target_diagram=args.target_diagram,
Expand Down Expand Up @@ -280,7 +280,9 @@ def __init__(
self._model = model
self._diagram = diagram
assert isinstance(diagram._element, etree._Element)
self._target = self._model._loader[diagram._element.attrib["repPath"]]
self._target = self._model._loader.follow_link(
diagram._element, diagram._element.attrib["repPath"]
)

@property
def _elements(self) -> t.Iterator[etree._Element]:
Expand Down
11 changes: 6 additions & 5 deletions src/capellambse/aird/_filters/global.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ def hide_alloc_func_exch(
for cex in component_exchanges:
assert cex.uuid is not None
# Find all allocated functional exchanges
for fex in args.melodyloader[cex.uuid].iterchildren(
cex_elem = args.melodyloader.follow_link(None, cex.uuid)
for fex in cex_elem.iterchildren(
"ownedComponentExchangeFunctionalExchangeAllocations"
):
target = fex.attrib["targetElement"].rsplit("#", 1)[-1]
Expand All @@ -189,7 +190,7 @@ def hide_alloc_func_exch(

def _stringify_exchange_items(
obj: diagram.DiagramElement,
melodyloader: capellambse.loader.MelodyLoader,
melodyloader: capellambse.loader.Loader,
sort_items: bool = False,
) -> str:
assert obj.uuid is not None
Expand All @@ -209,11 +210,11 @@ def _stringify_exchange_items(
def _get_allocated_exchangeitem_names(
*try_ids: str,
alloc_attr: str,
melodyloader: capellambse.loader.MelodyLoader,
melodyloader: capellambse.loader.Loader,
) -> tuple[lxml.etree._Element | None, list[str]]:
for obj_id in try_ids:
try:
elm = melodyloader[obj_id]
elm = melodyloader.follow_link(None, obj_id)
except KeyError:
pass
else:
Expand All @@ -223,7 +224,7 @@ def _get_allocated_exchangeitem_names(

if elm.tag == "ownedDiagramElements":
targetlink = next(elm.iterchildren("target"))
elm = melodyloader[targetlink.attrib["href"]]
elm = melodyloader.follow_link(targetlink, targetlink.attrib["href"])

names = []
for elem in melodyloader.follow_links(
Expand Down
9 changes: 5 additions & 4 deletions src/capellambse/aird/_visual.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,10 @@ def shape_factory(ebd: c.ElementBuilder) -> diagram.Box:
assert ebd.target_diagram.styleclass is not None

uid = ebd.data_element.attrib[c.ATT_XMID]
element = ebd.data_element.get("element")
if element is not None:
label = ebd.melodyloader[element].attrib["name"]
element_id = ebd.data_element.get("element")
if element_id is not None:
element = ebd.melodyloader.follow_link(ebd.data_element, element_id)
label = element.attrib["name"]
description = ebd.data_element.get("description", "")
else:
label = ebd.data_element.get("description", "")
Expand Down Expand Up @@ -108,7 +109,7 @@ def shape_factory(ebd: c.ElementBuilder) -> diagram.Box:
int(layout.attrib.get("height", "0")),
)

if element is not None:
if element_id is not None:
styleclass = "RepresentationLink"
else:
styleclass = "Note"
Expand Down
6 changes: 3 additions & 3 deletions src/capellambse/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -845,7 +845,7 @@ def resolve_namespace(tag: str) -> str:


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

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

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


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

Expand Down
2 changes: 1 addition & 1 deletion src/capellambse/loader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
.. _LXML Documentation: https://lxml.de/
"""

from ._typing import *
from .core import *
from .modelinfo import ModelInfo as ModelInfo
38 changes: 38 additions & 0 deletions src/capellambse/loader/_typing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# SPDX-FileCopyrightText: Copyright DB InfraGO AG
# SPDX-License-Identifier: Apache-2.0

__all__ = [
"FragmentType",
"Loader",
"ModelInfo",
]

import dataclasses
import enum
import pathlib
import typing as t

from capellambse import filehandler

if t.TYPE_CHECKING:
from . import core

Loader: t.TypeAlias = "core.MelodyLoader"


class FragmentType(enum.Enum):
"""The type of an XML fragment."""

SEMANTIC = enum.auto()
VISUAL = enum.auto()
OTHER = enum.auto()


@dataclasses.dataclass
class ModelInfo:
url: str | None
title: str | None
entrypoint: pathlib.PurePosixPath
resources: dict[str, filehandler.abc.HandlerInfo]
capella_version: str
viewpoints: dict[str, str]
24 changes: 13 additions & 11 deletions src/capellambse/loader/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@

__all__ = [
"CorruptModelError",
"FragmentType",
"MelodyLoader",
"ModelFile",
]

import collections
import collections.abc as cabc
import contextlib
import enum
import itertools
import logging
import operator
Expand All @@ -33,7 +31,8 @@
import capellambse._namespaces as _n
from capellambse import filehandler, helpers
from capellambse.loader import exs
from capellambse.loader.modelinfo import ModelInfo

from ._typing import FragmentType, ModelInfo

if sys.version_info >= (3, 13):
from warnings import deprecated
Expand Down Expand Up @@ -160,14 +159,6 @@ def _round_version(v: str, prec: int) -> str:
return v[:pos] + re.sub(r"[^.]+", "0", v[pos:])


class FragmentType(enum.Enum):
"""The type of an XML fragment."""

SEMANTIC = enum.auto()
VISUAL = enum.auto()
OTHER = enum.auto()


class MissingResourceLocationError(KeyError):
"""Raised when a model needs an additional resource location."""

Expand Down Expand Up @@ -1294,6 +1285,17 @@ def follow_links(
raise
return targets

def find_references(self, target_id: str) -> cabc.Iterator[etree._Element]:
for i in self.xpath(
f"//*[@*[contains(., '#{target_id}')] | */@*[contains(., '#{target_id}')]]",
roots=[
i.root
for i in self.trees.values()
if i.fragment_type != FragmentType.VISUAL
],
):
yield self._follow_href(i)

def _find_fragment(
self, element: etree._Element
) -> tuple[pathlib.PurePosixPath, ModelFile]:
Expand Down
18 changes: 6 additions & 12 deletions src/capellambse/loader/modelinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,11 @@

from __future__ import annotations

import dataclasses
import pathlib
import warnings

from capellambse import filehandler
from ._typing import ModelInfo as ModelInfo


@dataclasses.dataclass
class ModelInfo:
url: str | None
title: str | None
entrypoint: pathlib.PurePosixPath
resources: dict[str, filehandler.abc.HandlerInfo]
capella_version: str
viewpoints: dict[str, str]
warnings.warn(
f"{__name__} is deprecated, import ModelInfo from capellambse.loader directly instead",
stacklevel=2,
)
Loading
Loading