-
Notifications
You must be signed in to change notification settings - Fork 0
Xmi Writer
Auriga.Xmi serializes a loaded Capella object graph back to XMI with IXmiWriter
(Auriga.Xmi/Writers/). It is the exact inverse of the reader: a thin
XmiElementWriter<T> base plus one generated XxxWriter per concrete class
(AutoGenXmiWriters/) and a generated XmiElementWriterFacade that
dispatches an element's runtime type to its writer. The writers are produced by the
XmiWriterGenerator from the same vendored .ecore metamodel as the readers, so the two stay in lock-step.
using Auriga.Xmi;
var reader = XmiReaderBuilder.Create().Build();
var result = reader.Read("model.capella");
// ... inspect or manipulate result.Root ...
var writer = XmiWriterBuilder.Create().Build();
writer.Write(result.Root, "out/model.capella"); // writes the main file + any fragmentsWrite(root, mainFilePath) is fragment-aware: it partitions the graph by each element's
SourceDocument, writes the main semantic file to mainFilePath and every .capellafragment to a
sibling path relative to it, and serializes references between documents as relative hrefs.
WriteDocument(root, stream, documentName) writes a single document to a stream.
-
Namespaces. Each generated writer knows its package's
xmlnsprefix, URI and XMI type name (from the ecorensPrefix/nsURI). Before writing a document's root, the writer walks the document's subtree, collects the packages it uses, and declares exactly thosexmlns:prefixes on the root — the same set Capella declares. -
Elements. A contained element is written under its containment feature's role name
(
<ownedFunctions xsi:type="…:PhysicalFunction" …>); the document root is written with the package prefix (<org.polarsys.capella.core.data.capellamodeller:Project …>) and noxsi:type. -
Attributes. Scalars and enumerations are written as attributes (Capella's enum literals are
upper-case, so the C# member name round-trips verbatim). Non-containment references are written as
#idattributes, multi-valued ones as a whitespace-delimited list. -
References prefer the collected token. When an element was read, the reader keeps each reference's
verbatim token; the writer re-emits that token, so a reference round-trips exactly — including a
cross-document
path#idand a genuinely external, unresolved reference. For an element built in memory (never read), the writer derives the#id(or relativehref) from the resolved target instead. -
Fragments. A containment child that belongs to another document is written as an
hrefproxy (<ownedX href="fragments/…capellafragment#id"/>); the child itself is written, in full, as the root of its own document. -
New-element placement. An element with no
SourceDocument— one built in memory rather than read — is written into its container's document: it is emitted inline wherever its container is written. So a new child added to an element that lives in a fragment is written into that fragment, and a new child under the main document's tree is written into the main file. (Only an element read from disk carries aSourceDocument; the reader sets it, and a fragment-preserving write routes it back to that file.)
The writer targets semantic fidelity, not byte-for-byte output: the goal is that reading a model,
writing it, and reading the result again reproduces an equivalent object graph, and that the output
reopens in the Capella tool. The round-trip is proven by
XmiWriterTestFixture against the minimal fixture and the
fragmented sysmodel fixture.
The following differences from Capella's own serialization are benign (they do not change meaning and reopen cleanly). Each is the reason byte-for-byte equality is not attainable, and each is neutralized by the normalized comparison in the round-trip regression suite (below):
-
Tag wrapping. EMF wraps long start-tags across several lines (one
xmlns:/attribute per line); the writer relies onXmlWriter, which keeps a tag's attributes on one line. - Ordering. Attribute and child ordering is alphabetical by feature name (a stable, deterministic order that matches the reader), which does not always match Capella's ordering.
-
Encoding case. The XML declaration encoding renders as
utf-8(lower-case) rather thanUTF-8. - Namespace declarations. The writer declares exactly the packages a document uses; Capella's declared set and prefix order can differ.
-
Default values. Capella omits an attribute whose value equals its Ecore default (e.g.
actor="false"); the writer emits it. The value is identical, so the meaning is unchanged. -
Multi-valued features as elements. Capella serializes some multi-valued features as repeated child
elements (e.g.
bodies/languagesof anOpaqueExpression,unsynchronizedFeatures); the writer emits them as a single whitespace-delimited attribute. The values are the same. -
Typed-reference spelling. A reference that carries an
xsi:typehint (e.g.exchangedItems,triggers) and same-/cross-document#id/path#idtokens are spelled differently but resolve to the same target. -
Inline vs. href proxy. At a fragment boundary an element may be inlined on one side and written as an
hrefproxy on the other; both denote the same element in the same document.
Verifying that a written file opens in Capella 7.0.0 is a manual step; the automated proxy is the round-trip equivalence, well-formedness, and correct namespace declarations.
RoundTripRegressionTestFixture runs read → write
→ compare over every fixture under TestData/, skipping any model the v1
reader does not support — either its metamodel version (the 6.0.0 coffee-machine fixture) or an add-on
viewpoint outside the vendored metamodel (the Cybersecurity viewpoint in the Crowd Surveillance sample).
Two complementary checks:
-
Semantic round-trip (CI gate). Read → write → re-read → compare object graphs (element set, types,
containment, source document, resolved references, and dangling references). This is the enforced check
— it runs on every CI build through the normal
dotnet testand catches any change to what a model round-trips to. The reader is the normalizer here: every benign textual difference above is invisible to the object graph, so a green run means no semantic regression. -
Normalized text diff (on-demand audit). An
[Explicit]test compares the written files against the originals after normalizing the benign differences away, and buckets every residual difference into the categories above. It is not a CI gate — byte-level fidelity is not a v1 goal — but running it produces the auditable list of accepted differences and flags any new,UNCLASSIFIEDdivergence for investigation.
Project background
Metamodel & design
- Metamodel Inventory
- Sirius Metamodel Inventory
- Arcadia Notes
- ECoreNetto Validation
- Sirius ECoreNetto Validation
Code generation
Reading & writing models
Diagrams
API