-
Notifications
You must be signed in to change notification settings - Fork 0
Fragment Loading
Capella models are frequently split across several files: a main .capella (formerly
.melodymodeller) and one or more .capellafragment files, with references crossing the boundaries
as href="relative/path.capellafragment#uuid". This note records how the reference implementation —
py-capellambse — handles this, and the design Auriga
adopts. No code is ported; these are design notes.
A fragmented project is a set of sibling files with distinct roles:
| File | Role | Element id attribute |
|---|---|---|
.aird |
Visual model + manifest: lists all sibling semantic files (semanticResources) and visual sub-fragments (referencedAnalysis). The natural entry point. |
uid / xmi:id
|
.airdfragment |
Visual fragment (diagram layout for a split-out portion) |
uid / xmi:id
|
.capella / .melodymodeller
|
The semantic root model | id |
.capellafragment / .melodyfragment
|
A semantic fragment — a contained subtree lifted into its own file. Its root element is the element the parent file points at via a containment href. |
id |
.afm |
Viewpoint / Capella-version metadata only; carries no element ids | — |
The key structural fact: when a subtree is fragmented out, the parent file keeps only a proxy
<ownedX href="fragments/F.capellafragment#uuid"/> where uuid is the id of the fragment file's
root element. Every semantic fragment is therefore reachable by following containment hrefs from
the main file.
Primary source: capellambse/loader/core.py (MelodyLoader, ModelFile) and capellambse/helpers.py.
-
Discovery. The entry point is the
.aird.MelodyLoaderreads itssemanticResources/referencedAnalysismanifest and recurses into every referenced file (_find_refs), buildingself.trees: dict[PurePosixPath, ModelFile]— one parsed tree per fragment, keyed by its normalized path. -
href format. The link grammar (
helpers.CROSS_FRAGMENT_LINK) is(?:(xtype )?(fragment))?#(uuid)— an optionalxsi:type, an optional relative fragment path, then theuuid. Three shapes occur:#uuid(same file),type path#uuid(cross-fragment), andtype ../main.capella#uuid(back to root). Relative paths are resolved against the referring file's directory and URL-decoded (%20→ space); Eclipseplatform:/resource/…library links are rewritten to../. -
Global UUID space. Although each
ModelFilehas its own id cache, resolution treats UUIDs as unique across the whole model — duplicates are rejected as corruption (check_duplicate_uuids). The fragment path in an href is effectively advisory:follow_linkresolves by UUID alone (del fragment # TODO use to disambiguate) and merely verifies the target'sxsi:typematches the optionalxtype. -
Resolution.
follow_link(uuid)fans out over every loaded tree's id cache and expects exactly one hit (ambiguous ⇒ error).loader[uuid]is model-wide lookup. -
Source tracking. There is no per-element source field; the owning fragment is found on demand by
walking to the tree root (
_find_fragment). This is what routes a new element into its parent's file (generate_uuid(parent)) and letscreate_linkchoose a bare#uuidvs atype path#uuidcross-fragment link on write. -
Round-trip.
save()writes eachModelFileback to its own path with an EMF-compatible serializer (loader/exs.py) for byte-level diff fidelity; because new elements were inserted into a specific tree, they serialize back to the correct file automatically.
Auriga's reader (Auriga.Xmi.XmiReader) implements the load + cross-fragment resolution for the
semantic graph, following the uml4net XMI-reader
pattern: every Read carries the document it is reading (Read(XmlReader, string documentName, string namespaceUri)), references are stored raw at read time and only resolved in a second pass, and
elements are cached under a document-scoped key.
-
Document-scoped cache. Every element from the main file and all fragments goes into one
IXmiElementCache, but keyed bydocumentName#id(XmiElementCache.Key), wheredocumentNameis the reading document's path relative to the main file. This is the faithful uml4net key: it lets the same physical file be identified consistently no matter which document referenced it, so a fragment that references another fragment — or references back into the main file — resolves to a single, stable key. The publicXmiReaderResult.Elementsindex is still built by the bareid(Capella UUIDs are globally unique), so the public API andElements[bareId]lookups are unchanged. -
Discovery by following hrefs, not the
.airdmanifest.Read(string path)takes the main document as the entry point and discovers fragments by scanning the collected references for path-qualifiedhreftokens, loading each transitively. The followed extensions are derived from the main document's family —.capellafragmentfor a.capella/.melodymodellersession,.airdfragmentfor an.airdsession — and every fragment is reachable through anhref, so this finds the whole graph without parsing the.aird'ssemanticResourcesmanifest. TheRead(string, IReadOnlyCollection<string>)overload accepts an explicit extension set: passing the union of both families co-loads the Capella semantic documents an.airdhrefs into, which is howAirdModelLoaderresolves the cross-metamodeltarget/semanticElementslinks. -
Referring-document-relative href resolution. A collected token keeps its full
hrefverbatim. Both discovery and resolution parse it throughHrefReference(strip the optionalxsi:typeprefix, split on#) and canonicalize the document part relative to the referring element's ownSourceDocument— URL-decoded (Uri.UnescapeDataString),..-collapsed, normalized to a path relative to the main file.ReferenceResolver.ResolveKeythen builds thedocumentName#idcache key: a bare intra-document#uuidis qualified with the owner's own document; a cross-filetype path#uuidis qualified with the resolved target document. Resolving against the referring document (not the main directory) is what makes fragment→fragment and back-to-main links resolve to the same key the target was cached under. Discovery is filtered to the session's extension set, sohlink://rich-text links,platform:/resourcelibrary links andplatform:/plugintooling references are ignored (and reported as unresolved when they are reference tokens). -
Known Capella namespaces.
INamespaceResolver.RegisterNamespacelets the known Capella namespace URIs (the generatedAutoGenNamespaceRegistry) be seeded up front, mirroring uml4net's registration, and each document's root namespace URI is threaded through everyReadasnamespaceUri. -
Explicit per-element source tracking. Rather than an on-demand ancestor walk, Auriga records the
originating document on each element (
IAurigaElement.SourceDocument, relative to the main file, e.g.sysmodel.capellaorfragments/SA-Data.capellafragment) — set by the reader from thedocumentNameit was read under. This is simpler to query, is the document part of the cache key, and is the hook a fragment-preserving write will use to route each element back to its file. -
Containment navigation. Both single- and multi-valued containment re-parent their targets:
a multi-valued containment collection is a bypass-proof
ContainerList<T>(built onCollection<T>, so every mutation path — including the resolver's non-genericIList.Add— setsContainer), and a single-valued containment property's setter setsContaineron assignment. The reader and resolver therefore produce a fully navigable graph:IAurigaElement.Containerwalks up to the root across fragment boundaries, and generatedQueryContainedElements()/QueryAllContainedElements()walk down (result.Root.QueryAllContainedElements().OfType<…>()). -
Robustness. The main document's failures propagate; a missing or unparsable fragment is logged
and skipped. References whose target is absent from every provided file remain in
XmiReaderResult.UnresolvedReferences(see two-pass resolution) rather than aborting the load — the vendoredfragmented-sysmodelfixture is a near-complete export with two such genuinely-external targets.
-
.airdmanifest and visual fragments. Not read; diagram-only fragments are not loaded. -
Library / cross-project links.
platform:/resource/…references to other projects are treated as external (reported unresolved), not loaded. -
Writing. Fragment-preserving round-trip is future work;
SourceDocumentis the groundwork.
- py-capellambse:
capellambse/loader/core.py(MelodyLoader,ModelFile,FragmentType),capellambse/helpers.py(CROSS_FRAGMENT_LINK,normalize_pure_path),capellambse/loader/exs.py. - Auriga:
Auriga.Xmi/XmiReader.cs,Auriga.Xmi/ReferenceResolver/ReferenceResolver.cs,Auriga.Xmi/HrefReference.cs,Auriga.Xmi/Cache/XmiElementCache.cs,Auriga.Xmi/Namespaces/NamespaceResolver.cs,Auriga/IAurigaElement.cs(SourceDocument,QueryContainedElements),Auriga/ContainerList.cs. - Follows the uml4net XMI-reader pattern (
STARIONGROUP/uml4net). - Built on two-pass reference resolution and containment navigation; related work includes the test fixtures and fragment-preserving writes.
Project background
Metamodel & design
- Metamodel Inventory
- Sirius Metamodel Inventory
- Arcadia Notes
- ECoreNetto Validation
- Sirius ECoreNetto Validation
Code generation
Reading & writing models
Diagrams
API