PlantUML Converter¶
Converts PlantUML diagrams (class, component, sequence, use case) to RDF.
Ontology¶
Types align to the published UML 2.5.1 Ontology (uml: prefix).
Default type mapping (built-in, no config needed):
| PlantUML keyword | UML ontology type |
|---|---|
class |
uml:Class |
interface |
uml:Interface |
enum |
uml:Enumeration |
abstract |
uml:Class + uml:isAbstract true |
component |
uml:Component |
package |
uml:Package |
actor |
uml:Actor |
usecase |
uml:UseCase |
node |
uml:Node |
device |
uml:Device |
artifact |
uml:Artifact |
database |
uml:Node |
boundary, control, entity, collections, queue |
uml:Class |
state |
uml:State — simple and composite alike |
initialState |
uml:Pseudostate + uml:pseudostateKind uml:InitialPseudostate |
finalState |
uml:FinalState |
In a sequence diagram every participant is uml:Lifeline, whichever keyword asked for it —
actor, database and boundary included. UML 2.5.1 §17.3: a participant in an Interaction is a
Lifeline. Outside an Interaction the table above applies, so actor in a use case diagram is a
uml:Actor. See ADR 0007.
The keyword itself¶
Where a keyword says something its UML class cannot, it is published as schema:keywords — a plain
label, not a type claim:
<…/element/Ui> a …, uml:Class ; schema:keywords "boundary" .
<…/element/Order_DB> a …, uml:Lifeline ; schema:keywords "database" .
<…/element/Order> a …, uml:Class . # `class` says nothing more
A keyword that names its own class is dropped rather than restated as a weaker string, and abstract
becomes uml:isAbstract instead. This replaced uml:stereotype, which nothing upstream declared and
which implied a UML profile application that never happened — the values are PlantUML keywords, and a
user-written <<stereotype>> is not read by this converter.
| PlantUML relationship | UML ontology type |
|---|---|
--> (association) |
uml:Association |
*-- (composition) |
uml:Composition |
o-- (aggregation) |
uml:Aggregation |
--|> (generalization) |
uml:Generalization |
..\|> (realization) |
uml:Realization |
..> (dependency) |
uml:Dependency |
<<include>> |
uml:Include |
<<extend>> |
uml:Extend |
| Sequence messages | uml:Message, with a uml:messageSort — see below |
Sequence message sort¶
All three sequence arrow kinds are uml:Message, so the kind is carried by uml:messageSort
instead of by the class. The values are the published uml:MessageSort individuals in
uml/onto:
| PlantUML | uml:messageSort |
also |
|---|---|---|
A -> B |
SynchCall — UML's own default for an unqualified message |
|
A ->> B |
AsynchCall |
|
A --> B (dotted) |
Reply |
|
A -->> B (dotted async) |
Reply |
see below |
B <- A |
SynchCall |
source A, target B — direction of travel, not writing order |
A ->x B |
SynchCall |
uml:messageKind uml:Lost |
<…/relationship/ClientApp__OrderService__create_order__60f79e38…> a uml:Message ;
uml:messageSort <https://meta.linked.archi/uml/onto#SynchCall> .
asynchCall rather than asynchSignal for ->>: PlantUML does not distinguish a call from a
signal, and a call is the commoner reading of an async arrow between participants.
umlsh:MessageShape requires exactly one messageSort per message and constrains it with
sh:class uml:MessageSort, so this is also what makes a converted sequence diagram conform.
-->> is both dotted and async — an asynchronous reply — and messageSort cannot say so, because
reply is a sort in its own right rather than a modifier that combines with asynchCall. One of the
two facts has to go, and the asynchrony is the one that goes: it keeps the arrow out of the set of
calls, which is what a consumer asking "what calls what" needs, whereas dropping the return semantics
would put a reply into that set.
The values are owl:NamedIndividuals of uml:MessageSort in the ontology namespace, not in a
separate reference-data one. That is UML-DD-12 upstream: the nine UML Enumerations moved into
uml/onto, closed with owl:oneOf, and uml/reference-data was retired. They are not affected by
--type-mapping, which remaps classes rather than individuals — a value moved into another
namespace would no longer be a member of the enumeration.
Lost messages¶
A ->x B is a message that was sent and never arrived. UML records that as uml:messageKind, not as
a different sort — a lost synchronous call is still a synchCall — so it is emitted alongside:
<…/relationship/A__B__ping__cb3e0d2c…> a uml:Message ;
uml:messageSort uml:SynchCall ;
uml:messageKind uml:Lost .
Only Lost is emitted. Complete is derived in UML from a message having both a sendEvent and a
receiveEvent, and this converter emits no uml:MessageEnd resources, so it has no grounds to claim
it. Found would need a message arriving from outside the diagram, which PlantUML writes with a gate
([-> B) — and gates are reported by the library as MessageExo rather than Message, so the
parser does not read them at all and those arrows are absent from the graph.
State diagrams¶
A state diagram converts to a state machine. The diagram kind is what selects this, so nothing here affects class or component diagrams.
| Source | Becomes |
|---|---|
state Idle or a state named by a transition |
uml:State |
state Running { … } |
uml:State — composite states were always right |
[*] at the source of a transition |
uml:Pseudostate + uml:pseudostateKind uml:InitialPseudostate |
[*] at the target of a transition |
uml:FinalState |
Idle --> Running: start |
uml:Transition, notation Idle__Running__start |
[*] --> Idle |
uml:Transition, notation start__Idle__transition |
The two ends of [*] are different things in UML, and PlantUML reports which is which, so it is not
inferred from the direction of the transition: the initial vertex is transient and the machine passes
through it, the final one is a State the machine rests in.
A nested region gets its own pair, because PlantUML scopes the name to the region:
[*] --> Idle → element/start
state Running {
[*] --> Warming → element/start_Running
Hot --> [*] → element/end_Running
}
Running --> [*]: done → element/end
Both vertices carry a skos:prefLabel of initial or final, because uml:Pseudostate and
uml:FinalState are uml:Vertex and so uml:NamedElement, which umlsh:NamedElementShape requires
to be labelled. Those are descriptions, not identifiers — the id comes from the region-scoped name
above.
uml:Transition is declared a subclass of arch:QualifiedRelationship upstream, so a transition is
still a qualified relationship with arch:source and arch:target, and the core shapes still apply.
Relationship ids¶
A relationship is named after its label, because the endpoints name a pair and two participants exchange many messages:
| Source | Relationship notation |
|---|---|
ClientApp -> OrderService: create order |
ClientApp__OrderService__create_order |
ClientApp --> OrderService: order created |
ClientApp__OrderService__order_created |
A -> B (unlabelled) |
A__B__call |
A --> B (unlabelled) |
A__B__reply |
The label is lower-cased into the notation and left as written in skos:prefLabel, so fixing a capital
does not move a published IRI. One arrow drawn in two views is therefore one relationship with a link in
each view; a call and a reply between one pair are two.
The id is that notation with a digest appended¶
<…/relationship/ClientApp__OrderService__create_order__60f79e38d012a705ddc3>
skos:notation "ClientApp__OrderService__create_order" .
The notation has no length limit — it composes two endpoint ids and a label, all author-supplied — and a path segment becomes a filename wherever the graph is written out as one document per resource, where 255 bytes is the ceiling. A diagram naming participants by the URL of their API contract, which is what you do when the participant is an API with a published spec, reached 644 bytes.
So the id is the notation truncated to 180 characters with a 20-character SHA-256 digest of the whole of
it appended, and the untruncated notation is published as skos:notation. Identity is unchanged, because
the digest is over exactly the notation: two views still merge, a call still separates from a reply.
Read skos:notation, not the IRI, when you need the endpoints and label back — beyond 180 characters
the IRI holds only a prefix.
Turning off --emit-skos-notation removes the only complete copy, so a long id becomes unreadable. It
stays on by default for that reason.
Full rules, the alternatives considered and the migration note are in ADR 0005.
Usage¶
# Class diagram → TriG
java -jar plantuml2linkedarchi.jar convert \
diagram.puml \
--base-iri https://example.org/la/ \
--model-id demo \
--format TRIG \
-o out.trig
# Sequence diagram → Turtle
java -jar plantuml2linkedarchi.jar convert \
sequence.puml \
--base-iri https://example.org/la/ \
--model-id demo \
--format TURTLE \
-o out.ttl
# Custom domain mapping
java -jar plantuml2linkedarchi.jar convert \
diagram.puml \
--base-iri https://example.org/la/ \
--model-id demo \
--format TRIG \
--type-mapping config/type-mapping-plantuml-custom.yml \
-o out.trig
CLI options¶
| Option | Default | Description |
|---|---|---|
<inputs> |
required | PlantUML file(s) (.puml) |
-o, --output |
required | Output artifact, path[:FORMAT[:PROFILE]]. Repeatable — several artifacts are written from one conversion, which is faster than re-running the converter and is what makes them share one prov:generatedAtTime. PROFILE is full (default), no-geometry, no-views or no-diagrams; see output serialization |
--base-iri |
required | Base IRI for minting all resource IRIs, e.g. https://example.org/la/. An http/https IRI, so the resources it mints dereference |
--model-id |
filename | Model identifier for a single input. An index id wins over it, and giving it with several inputs is refused — see one model per --model-id. Unlike an index id, the value is not validated |
--format |
inferred | Default format for any --output that names none: TRIG / TURTLE / JSONLD / RDFXML / NTRIPLES / NQUADS. A file extension such as ttl is also understood |
--ns-puml |
https://meta.linked.archi/uml/onto# |
Notation namespace |
--ns-core |
https://meta.linked.archi/core# |
Override the core namespace |
--ns-core-vis |
https://meta.linked.archi/core-vis# |
Override the core-vis namespace |
--type-mapping |
none | YAML type overrides |
--include-views |
true |
Emit archvis: view nodes/links |
--emit-skos-labels |
true |
Emit skos:prefLabel |
--label-language |
en |
BCP-47 tag for skos:prefLabel literals |
--emit-skos-notation |
true |
Emit skos:notation |
--require-title |
false |
Fail instead of labelling a view with its own id when neither the index nor the diagram declares a title — see View titles |
--emit-direct-rel-triples |
false |
Emit {src} {pred} {tgt} shortcuts, each bridged back to its relationship with rdf:reifies. Needs predicates: in a --type-mapping |
--emit-extension-data |
false |
Map extension data onto the elements it annotates, from '!la- comments and from index elements: entries. See Extension data |
--ns-global-id |
none | Base IRI for link targets written without a prefix, so a bare id resolves into a cross-model graph. Requires --emit-extension-data |
--svg-base-iri |
none | Base IRI of published SVG renders. When set, each view gets a schema:image pointing at {base}/{path}/{modelId}.svg, so converted RDF links to the diagram rendered by render |
--diagrams-index |
none | Diagram index for batch processing. When given, only indexed files are converted — in every lifecycle state unless --exclude-states says otherwise — and each one's id becomes its model ID |
--diagrams-root |
index location | Root directory that index file paths are resolved against |
--exclude-states |
none | Leave index entries in these lifecycle states out of the run. Every state is converted by default, so this is the only option that withholds one. Comma-separated, and it cannot name all five. See Lifecycle states |
--include-states |
none | Deprecated and ignored: every state is converted by default. Use --exclude-states |
--include-drafts |
false |
Deprecated and ignored: drafts are converted by default |
Extension data¶
A .puml says what UML can express. To say which capability a component realizes, or which LeanIX
factsheet it corresponds to, use extension data — see
Extension data for the concepts, which are shared with BPMN.
PlantUML has no extension point of its own, so there are two routes in and they are additive.
In the source file¶
Comment lines, which every renderer ignores and no tool rewrites — the same mechanism this converter already uses for identity declarations:
'!la-prefix am: https://meta.linked.archi/archimate3/onto#
'!la-prefix kg: https://example.org/graph/
'!la-prefix lx: https://leanix.example.com/factsheet/
'!la-prefix x: https://example.org/vocab#
@startuml
class OrderService
'!la-link OrderService am:realizes kg:CAP-OrderManagement
'!la-data OrderService x:costCentre CC-4711
class "Payment Gateway" as PayGw
'!la-link PayGw am:serves lx:APP-payments Backward
@enduml
plantuml2linkedarchi convert orders.puml \
--base-iri https://example.org/la/ --model-id orders \
--emit-extension-data \
--format TRIG -o out.trig
<…/plantuml/orders/element/OrderService>
am:realizes <https://example.org/graph/CAP-OrderManagement> ;
vocab:costCentre "CC-4711" .
<https://leanix.example.com/factsheet/APP-payments>
am:serves <…/plantuml/orders/element/PayGw> .
class "Payment Gateway" as PayGw is addressed as PayGw, because that is the name the source
refers to it by — the same name the annotation and any arrow use. Payment Gateway is its
skos:prefLabel. See Naming an element.
| Directive | Form |
|---|---|
'!la-prefix |
<prefix> <namespace> — the trailing : on the prefix is optional |
'!la-link |
<element> <predicate> <target> [Forward\|Backward\|Both] |
'!la-data |
<element> <predicate> <literal> — the literal is the rest of the line, so it may contain spaces |
'!la-view-link |
<predicate> <target> [Forward\|Backward\|Both] — no subject, see below |
'!la-view-data |
<predicate> <literal> — no subject |
'!la-architecture-state |
<baseline\|target\|transitional> — a checked value, not a predicate; see Architecture state |
-link and -data are separate because the difference is not inferable from the value: CC-4711 is
a literal and CAP-1 is an element id, and both are bare words.
Unlike identity declarations, these are read from the whole file, not just the header — a statement about an element belongs beside that element, which is the only reason to prefer this route over the index. A misspelled directive is reported rather than silently ignored:
[WARN] orders.puml:3: unknown directive '!la-lnik'. Accepted: !la-prefix, !la-link, !la-data,
!la-view-link, !la-view-data (and !la-model, !la-view for identity).
Statements about the diagram itself¶
'!la-view-link and '!la-view-data take no subject: a .puml file is one view, so there is nothing
else they could be about. This is how an author says something true of the diagram as a whole rather
than of anything drawn in it — which architecture state it depicts, which viewpoint it conforms to,
which decision option it articulates:
'!la-prefix arch: https://meta.linked.archi/core#
'!la-prefix archvp: https://meta.linked.archi/core-viewpoints#
'!la-prefix kg: https://example.org/graph/
'!la-prefix x: https://example.org/vocab#
'!la-view-link arch:architectureState arch:Target
'!la-view-link arch:viewConformsToViewpoint archvp:Roadmap
'!la-view-data x:reviewedBy Jane Doe
@startuml
class OrderService
@enduml
<…/plantuml/orders/view/orders>
arch:architectureState arch:Target ;
arch:viewConformsToViewpoint archvp:Roadmap ;
vocab:reviewedBy "Jane Doe" .
arch:inView runs from a concept to the view, so pointing at a decision option needs the
statement reversed — the same Backward token the element directives use:
There is no '!la-model-link. A model spans several files, so a model-wide claim written in one of
them would have no evident owner; the index names a model exactly once, so model-level statements go
there. See statements about a view or a model.
View titles¶
skos:prefLabel is what a consumer shows in a listing or a search result, and a view id is not a
substitute: order-flow-v2 is an address, chosen to be stable and URL-safe rather than to be read.
Two places can declare a title, and the index wins with the diagram filling the gap — the rule
identity already follows. Using the index title also keeps skos:prefLabel and dct:title from
disagreeing, since the index title becomes the latter.
With neither, the view id is used and the run says so:
[WARN] flow.puml declares no title, so the view id 'flow' is published as its skos:prefLabel. Add
'title:' to the diagram index entry, or a 'title' line to the diagram — or pass --require-title to
make this an error.
--require-title makes it an error, for a publishing pipeline that should not ship a diagram labelled
with its own address. Either route satisfies it.
Architecture state¶
Which reality the diagram describes — baseline, target, or a plateau between them:
A checked value rather than a predicate and an IRI, so a typo fails the run:
[ERROR] Conversion failed: Unknown architectureState 'Targt' for orders.puml:1.
Accepted: baseline, target, transitional. … Note this is not the publication status: 'draft' and
'publish' belong under 'status:'.
That is the reason it is its own directive rather than one more '!la-view-link: written generically,
'!la-view-link arch:architectureState arch:Targt emits a link to a term nothing defines and says
nothing about it.
Three things worth knowing:
- It is not gated by
--emit-extension-data. Which reality a diagram describes is a property of the diagram, like its status — not data borrowed from a modelling tool's extension point, which is what that flag is about. - The spellings the ontology records are accepted:
as-is,current,to-be,future,transition,intermediate, plus any casing and-/_/space mix.As Isworks. - The index can declare it too, and if both do they must agree — a disagreement is refused rather than settled by precedence, because baseline and target are opposite claims about one diagram.
Full semantics, including why it is orthogonal to status:, are in
Architecture state.
Naming an element¶
A PlantUML entity has two names, and an annotation may use either:
PayGw is the code — the name the source refers to the entity by, in every arrow and in every
annotation, and the one the element IRI is built from: …/element/PayGw. Payment Gateway is the
display name — what the picture shows, and the element's skos:prefLabel.
Write the code. It is what the rest of the file already uses, and it does not move when someone retitles the box, so neither the IRI nor an annotation keyed on it needs revisiting. The display name resolves as well; quote it when it contains a space:
'!la-link PayGw arch:refines kg:CAP-Payments ' by code — prefer this
'!la-link "Payment Gateway" arch:refines kg:CAP-Payments ' by display name — same element
Where an entity has no as clause the two names are one string and there is nothing to choose:
class OrderService mints …/element/OrderService, and participant "Book API" mints
…/element/Book_API.
A name matching two elements is refused rather than guessed at, and one matching none is reported:
[WARN] orders.puml:12: no element 'Nowhere' in this model, so <am:realizes> was not attached to
anything. Check the name against the diagram.
Why identity comes from the code rather than the label is ADR 0010; the shared rules for both routes are in referring to an element.
Matching sequence diagram participants to known applications¶
A sequence diagram's participants are usually applications, so the same annotation matches each one to its record in a CMDB, an ArchiMate model, or wherever else the organization already describes it:
'!la-prefix skos: http://www.w3.org/2004/02/skos/core#
'!la-prefix cmdb: https://example.org/cmdb/application/
@startuml
actor Customer
participant "Web Shop" as Shop
participant "Payment Gateway" as PayGw
participant "Order Service" as Orders
database "Order DB" as DB
Customer -> Shop : checkout()
Shop -> PayGw : authorize(amount)
Shop -> Orders : createOrder()
Orders -> DB : persist(order)
'!la-link Shop skos:exactMatch cmdb:APP-webshop
'!la-link PayGw skos:exactMatch cmdb:APP-payment-gateway
'!la-link Orders skos:exactMatch cmdb:APP-order-service
@enduml
<…/plantuml/checkout/element/Shop>
skos:prefLabel "Web Shop"@en ;
skos:exactMatch <https://example.org/cmdb/application/APP-webshop> .
<…/plantuml/checkout/element/PayGw>
skos:prefLabel "Payment Gateway"@en ;
skos:exactMatch <https://example.org/cmdb/application/APP-payment-gateway> .
<…/plantuml/checkout/element/Orders>
skos:prefLabel "Order Service"@en ;
skos:exactMatch <https://example.org/cmdb/application/APP-order-service> .
Each participant is addressed by its alias and labelled with its display name, so the annotations
read exactly as the arrows above them do — and retitling "Web Shop" to "Web Shop (EU)" changes
the label without touching the IRI or the skos:exactMatch that now points at it.
skos:exactMatch, not owl:sameAs, for this. The two say different things and a reasoner
treats them differently:
skos:exactMatchsays the participant and the CMDB entry describe the same real-world application, without claiming they are one RDF resource. Each keeps its own properties, and nothing is inferred onto the other. This is the normal case:Payment Gatewayis this diagram's mention of an application that has its own, separately maintained, description elsewhere.owl:sameAsasserts that two IRIs denote one resource. A reasoner is entitled to merge everything known about both — every property of the CMDB entry becomes true of the participant and vice versa. That is the right tool only when the two IRIs really are alternate names for one node, e.g. reconciling an id after a rename (seeformerIds:in the diagram index), not when relating a diagram's mention of a thing to that thing's record elsewhere.
Getting this backwards is how a graph ends up merging two resources that were only ever meant to
correspond. Same rule as BPMN's — see
terms worth knowing for the fuller table, including
skos:closeMatch and skos:relatedMatch for a looser correspondence than exact, and
Stating a correspondence for the
substitutability test and the instance-versus-type distinction.
In the diagram index¶
For a team that owns the pipeline rather than the diagrams. The .puml needs no annotations at all —
see Element entries for the schema.
prefixes:
am: https://meta.linked.archi/archimate3/onto#
arch: https://meta.linked.archi/core#
kg: https://example.org/graph/
model:
id: orders
links:
arch:architectureState: arch:Baseline # about the model
views:
- id: order-flow
file: orders.puml
links:
arch:architectureState: arch:Target # about this view
elements:
OrderService: # about an element in it
links:
am:realizes: kg:CAP-OrderManagement
links: and data: written directly on an entry are about that entry — the view, or the model —
rather than about anything drawn in it, and they are the only route for model-level statements. See
statements about a view or a model.
Both routes apply in the same run. Where both declare the same prefix, the source file wins.
Architecture¶
PlantUML source (.puml)
→ PlantUmlParser (plantuml-mit library AST)
→ PumlModel (elements, relationships, views)
→ LinkedArchiEmitter (extends BaseLinkedArchiEmitter)
→ TriG / Turtle output
Render¶
Renders PlantUML sources to SVG using PlantUML's own layout engine. Pair it with
--svg-base-iri on convert so the RDF links each view to its rendered diagram.
java -jar plantuml2linkedarchi.jar render \
diagram.puml -o svg/
# Hyperlinked to the knowledge graph, driven by the shared index
java -jar plantuml2linkedarchi.jar render models/*.puml \
--base-iri https://example.org/la/ \
--diagrams-root models/ \
--diagrams-index models/diagram-index.yaml \
-o out/svg/
| Option | Default | Description |
|---|---|---|
<inputs> |
required | Input PlantUML file(s) (.puml, .plantuml, .pu) |
-o, --output-dir |
required | Directory the SVG files are written to |
--base-iri |
none | When given, each element becomes a link to its element IRI ({base}plantuml/{modelId}/element/{id}), the same IRI convert mints |
--diagrams-index |
none | YAML index selecting which diagrams to render and their model IDs |
--diagrams-root |
index location | Root directory that index file paths are resolved against |
--exclude-states |
none | Leave index entries in these lifecycle states out of the run. Every state is rendered by default. Comma-separated, and it cannot name all five. Resolved exactly as convert resolves it, so a diagram in the graph has a picture |
--include-states |
none | Deprecated and ignored: every state is rendered by default. Use --exclude-states |
--include-drafts |
false |
Deprecated and ignored: drafts are rendered by default |
--[no-]render-archived |
true |
Draw entries with status: archived. --no-render-archived leaves them out; convert is unaffected, so the diagram keeps its IRIs but its image URL stops resolving |
--layout |
AUTO |
Layout engine: AUTO, DOT or SMETANA — see Layout engine |
Output is <modelId>.svg, mirroring any sub-directory of the index file: path.
Per-file failures are reported as [WARN] Skipped … and the command exits 1. This is
the shared render behaviour,
identical to the BPMN converter.
modelId is the index id, and it names two things at once: this SVG and the IRI path
segment convert mints ({base}plantuml/{id}/…), which is what makes the [[iri]] links
in the diagram resolve against the graph. It is used unescaped in both, so it must be a
slug — ^[a-z0-9][a-z0-9._-]*$ — and id and file: must each appear once in the index.
A violation fails the command rather than being silently rewritten or dropped; see
id rules. Without an index the model ID
falls back to the source filename, which is not validated, so an index is what gets you
predictable URLs. Changing an id renames the published SVG and moves every element IRI
for that model.
View identity¶
A view ID can come from two places — the index views[].id, or a '!la-view: comment in
the source (see Declaring identity in the source file)
— and convert and render resolve it the same way:
- Only one declares it — that value is used.
- Both declare it, and agree — that value is used.
- Both declare it, and disagree — the run fails, naming both values and both files, rather than one silently winning.
- Neither declares it — it falls back to the model ID. A
.pumlfile has no diagram ID of its own to fall back to instead, so this is also what a plainconvert diagram.pumlwith no index and no'!la-view:gets: one file, one model, one view, all under the same ID.
This is the general identity-reconciliation rule (IdentityAgreement), applied to the view
level — model IDs are reconciled the same way. See
ADR 0001 for why identity is
split between the two sites at all.
Hyperlinks¶
Links are added by annotating a copy of the source with PlantUML's own [[iri]]
syntax — the .puml file on disk is not touched, and links the author already wrote
are kept. Both declaration styles are annotated: keyword (component Foo,
class "Foo" as F) and bracket shorthand ([Foo], (Use case), :Actor:,
() Interface). Elements declared only inside a relationship ([A] --> [B]) have no
declaration to annotate and stay unlinked. If annotation would break the diagram, it
renders without links and logs a warning. See
SVG Rendering → Hyperlinks.
Embed the SVG with <object>, <iframe> or inline <svg> — through an <img> tag or a
Markdown  image, browsers render SVG without interactivity and no link is clickable.
Layout engine¶
Sequence, activity, mindmap, gantt, JSON and WBS diagrams are laid out by PlantUML
itself. Class, component, use case, state and deployment diagrams go through Graphviz's
dot algorithm — either the real dot binary or Smetana, the pure-Java port bundled
inside the PlantUML JAR. --layout picks which:
--layout |
Behaviour |
|---|---|
AUTO (default) |
Use dot when it is installed, otherwise fall back to Smetana and log a warning |
DOT |
Require the dot binary; skip the file with an explanatory message if it is missing |
SMETANA |
Always use the bundled engine — no external dependency, identical layout on every machine |
# No Graphviz anywhere, fully self-contained
java -jar plantuml2linkedarchi.jar render diagram.puml --layout SMETANA -o svg/
# Fail rather than silently produce a different layout
java -jar plantuml2linkedarchi.jar render diagram.puml --layout DOT -o svg/
Smetana is implemented by inserting !pragma layout smetana into a copy of the source;
a layout pragma the author already wrote always wins.
Which to choose: dot is the reference implementation and lays complex diagrams out
better, so install it (brew install graphviz / apt-get install graphviz, or point
GRAPHVIZ_DOT at the binary) where diagram quality matters. Prefer SMETANA in CI and
containers, where a self-contained build and byte-stable output across machines are
worth more — note that under AUTO the same source renders differently depending on
whether the build agent happens to have Graphviz installed.
Validate¶
Runs SHACL validation on converted output. Shapes and ontologies are fetched from
meta.linked.archi at runtime.
Default shapes are uml-shapes and core-shapes. PlantUML is a syntax rather than a
metamodel, and this converter types its output in UML 2.5.1 — so UML is the notation
whose shapes apply, and uml-shapes carries the naming rule
(umlsh:NamedElementShape, a skos:prefLabel on every uml:NamedElement). Core covers
what is not notation-specific: a QualifiedRelationship having exactly one arch:source
and arch:target. The uml and core ontologies are loaded for rdfs:subClassOf
reasoning.
Switch the naming rule off for a run with --without-shape labels.
java -jar plantuml2linkedarchi.jar validate -i out.ttl
# Validate against your own shapes instead
java -jar plantuml2linkedarchi.jar validate -i out.ttl --shapes ./shapes/my-rules.ttl
# Offline, reusing documents fetched by an earlier run
java -jar plantuml2linkedarchi.jar validate -i out.ttl --asset-dir .assets --offline
Exits 0 when the data conforms, 1 when violations are found, 2 on error.
See Validation for the shared engine, all options, coverage reporting and report format.