diff --git a/changes/unreleased/migrate-tables-and-documents.added.md b/changes/unreleased/migrate-tables-and-documents.added.md new file mode 100644 index 0000000000..e91bff2ebb --- /dev/null +++ b/changes/unreleased/migrate-tables-and-documents.added.md @@ -0,0 +1,4 @@ +- **The SysML v1 migrator writes a Cameo/MagicDraw table, dependency matrix or relation map as an executable query and a renderable document.** A diagram carrying «InstanceTable», «DiagramTable» or «RelationMap» from the MagicDraw profile, or «DependencyMatrix» with its «MatrixFilter», is written beside its `view` as a `calc def ' Rows' :> DocumentQueries::Query` — the scope as `Descendants` of the named roots, explicit rows in one `Union`, the row type as `WhereType` (and `isIndividual` for an instance table) or `WhereMetadata` for a migrated user stereotype, columns as `Project` and `Column`, sorts as `OrderBy(missing = "last", multiple = "first")`, a matrix criterion as a `RelatedColumn` over the column scope, a relation map as `RelatedElements` — and a `part def ' Document' :> DocumentQueries::Document` holding the `Table`, so `-run-query` lists the rows and `-render-document` renders the table. Only the exact profile namespaces define a table; a same-named user stereotype elsewhere is ordinary metadata. A «DeriveReqt» criterion is walked from the original requirement, as the v2 `derivation` runs, and a criterion excluding subtypes of a stereotype the model specializes is approximated with the specializing stereotypes named, since their relationships are written as the same v2 relationship. A criterion no relationship kind spells, a malformed scope, sort, depth or criterion XML, or a table naming no row type is refused with every fault stated and the view kept. +- **The migrator writes an MDK DocGen «Document» as a `DocumentQueries::Document`.** The document's view tree becomes nested `Section`s in declaration order, and each view's viewpoint method activity is lowered from its initial node along control flow: the «Expose» suppliers are the root, `CollectOwnedElements`, `CollectOwners`, `CollectByDirectedRelationshipStereotypes`, `FilterByMetaclasses`, `FilterByStereotypes` (`include = false` as `Except`), `FilterByNames`, `SortByName`, `SortByAttribute`, `Union` forks and nested groups wrap the query, and `TableStructure`, `BulletedList`, `Paragraph`, collaborator paragraphs, `Image` and `Dynamic View` end it as a `Table`, `List`, `Paragraph`, view-backed `Diagram` or nested `Section`. A step with no query spelling (`CollectTypes`, OCL expressions, user scripts…), a recursive dynamic view, a viewpoint that names no method or a collaborator paragraph that names no view is refused with the construct quoted, and the sections around it are still written. +- **`DocumentQueries` gains `Named`, unbounded walks, matrix targets and `isIndividual`.** `Named(qualifiedName = (…))` resolves qualified names to elements, so a query can be rooted at a package or definition; `maxDepth` on `Descendants`, `Ancestors`, `RelatedElements`, `WhereRelated` and `RelatedColumn` may be omitted or `null` for no bound; `RelatedColumn(targets = …)` keeps only the related elements a second query lists, which is a dependency matrix's cell; and `WhereFeature('feature' = "isIndividual", …)` selects individuals. A declared `satisfy`/`verify` assertion typed by a requirement definition now relates its subject to that definition as well as to the assertion usage, so a matrix over requirement definitions finds its satisfiers. +- **The migrator reads MagicDraw's «typeModifier».** `[]` on a property or parameter with no collection multiplicity writes `[0..*] ordered nonunique`, `[n]` writes `[n] ordered nonunique`, and `*` on a part or item property writes it `ref`; a two-dimensional shape, `[]` on an existing collection and `*` on an attribute or parameter stay comments with the reason reported. diff --git a/cmd/sysml/manual_examples_test.go b/cmd/sysml/manual_examples_test.go index ef6945ec6c..bea0c30f7b 100644 --- a/cmd/sysml/manual_examples_test.go +++ b/cmd/sysml/manual_examples_test.go @@ -47,6 +47,20 @@ func TestManualCookbookModelAnalysesCleanly(t *testing.T) { t.Fatalf("cookbook query %s: %v\n%s", query, err, output) } } + named := exec.Command(binary, source, "-run-query", "Cookbook::NamedParts") + output, err := named.CombinedOutput() + if err != nil { + t.Fatalf("cookbook query Cookbook::NamedParts: %v\n%s", err, output) + } + for _, want := range []string{ + "returned 12 rows", + "Row 1: Cookbook::telescope::primaryMirror", + "Row 6: Cookbook::Traceability::gimbal", + } { + if !strings.Contains(string(output), want) { + t.Errorf("cookbook query Cookbook::NamedParts output is missing %q:\n%s", want, output) + } + } } // TestManualCookbookObjectRecipes runs the cookbook's recipes over the objects diff --git a/docs/manual/examples/cookbook.sysml b/docs/manual/examples/cookbook.sysml index 38d8036df1..413a016a4b 100644 --- a/docs/manual/examples/cookbook.sysml +++ b/docs/manual/examples/cookbook.sysml @@ -106,6 +106,13 @@ package Cookbook { Ancestors(source = leaf, maxDepth = 2) } + calc def NamedParts :> Query { + WhereType( + source = Descendants(source = Named(qualifiedName = ("Cookbook::telescope", "Cookbook::Traceability"))), + type = "PartUsage" + ) + } + calc def Connections :> Query { in root : Element; WhereType( diff --git a/docs/manual/query-cookbook.md b/docs/manual/query-cookbook.md index 582190828b..ae773107b2 100644 --- a/docs/manual/query-cookbook.md +++ b/docs/manual/query-cookbook.md @@ -223,7 +223,9 @@ $ sysml cookbook.sysml -run-query "Cookbook::AllParts root=Cookbook::telescope" Row 5: Cookbook::telescope::dataPath ``` -`maxDepth` bounds the walk; each level is visited in declaration order. +`maxDepth` bounds the walk; each level is visited in declaration order. Omit +it (or pass `null`) to walk the whole subtree — `Ancestors` likewise walks to +the root when unbounded. Note that the connections are still here: a `connection` usage *is* a `PartUsage` in the SysML metamodel (its metaclass conforms to it). Use a feature or name filter, or `type = "ConnectionUsage"`, to separate them — @@ -247,13 +249,43 @@ $ sysml cookbook.sysml -run-query "Cookbook::Enclosing leaf=Cookbook::telescope: Owners are returned nearest-first, up to `maxDepth` levels. +### Elements by qualified name: `Named` + +```sysml +calc def NamedParts :> Query { + WhereType( + source = Descendants(source = Named(qualifiedName = ("Cookbook::telescope", "Cookbook::Traceability"))), + type = "PartUsage" + ) +} +``` + +```console +$ sysml cookbook.sysml -run-query "Cookbook::NamedParts" +✓ Query Cookbook::NamedParts returned 12 rows + Row 1: Cookbook::telescope::primaryMirror + Row 2: Cookbook::telescope::instrumentCluster + ... + Row 6: Cookbook::Traceability::gimbal + ... +``` + +A query parameter must be bound to a feature, so a walk rooted at a *package* +or a *definition* has nothing to bind `root` to. `Named` resolves qualified +names — spelled as strings, like the types `WhereType` takes — to the elements +they name, in the order given, and any element may be named, a package or +definition included. A name that resolves to nothing, or to more than one +element, fails the query with the name quoted rather than returning fewer rows. +The SysML v1 migration roots every table scope this way. + ## Type filters `WhereType` keeps elements whose *metamodel* type matches — `"PartUsage"`, `"ConnectionUsage"`, `"RequirementUsage"`, `"AttributeUsage"`, `"PortUsage"`, `"PartDefinition"` and so on — including metaclass conformance, so -`type = "Usage"` keeps every kind of usage. A name that is neither a known -metamodel type nor resolvable in the model is a typed +`type = "Usage"` keeps every kind of usage. Several names keep the elements of +any of them: `type = ("PartUsage", "PortUsage")`. A name that is neither a +known metamodel type nor resolvable in the model is a typed `unknown-classification` error rather than a silently-empty result. ```sysml @@ -281,8 +313,8 @@ attribute ([property filters](#property-filters)). ## Metadata filters `WhereMetadata` keeps elements annotated with a metadata definition, matching -specializations of it too. The model marks `primaryMirror` with -`@Critical`: +specializations of it too; several names keep the elements annotated with any +of them. The model marks `primaryMirror` with `@Critical`: ```sysml calc def CriticalParts :> Query { @@ -467,6 +499,7 @@ are always projectable: | `@type` | The metamodel type (`PartUsage`, ...) | | `type` | The declared type's qualified name | | `isAbstract` | Boolean | +| `isIndividual` | Boolean: whether a definition or usage carries the `individual` modifier | | `multiplicityLower`, `multiplicityUpper` | Integers, `*` as unbounded | ```sysml @@ -742,15 +775,15 @@ RelatedElements( // satisfaction, verification, // derivation or refinement direction = "", // outgoing or incoming - maxDepth = + maxDepth = // omit, or null, for no bound ) ``` Direction is from the relationship's own point of view — `outgoing` follows it as declared, `incoming` follows it backwards. Traversal is breadth-first -to `maxDepth`, deduplicated, in declaration order, and bounded by a visit -budget so a pathological model terminates with a typed error rather than -hanging. +to `maxDepth` (unbounded when omitted or `null`), deduplicated, in +declaration order, and bounded by a visit budget so a pathological model +terminates with a typed error rather than hanging. ### Connections @@ -1170,12 +1203,15 @@ as the last table of its report, [`requirements.md`](examples/requirements.md). `RelatedElements` answers one requirement at a time. To put every requirement in one table with its satisfiers and verifiers beside it, derive the columns from the relationships instead: a `RelatedColumn(name, relationshipKind, -direction, maxDepth, aggregate = "list")` entry of `columns` traverses the -named relationship from each row's element — the same kinds, directions and -depth bound as `RelatedElements` — and fills a cell with what it reaches. -The `aggregate` chooses the cell's shape: `"list"` (the default) holds the -related elements, `"count"` how many there are, `"any"` whether there is at -least one — an existence test that stops at the first element it reaches. +direction, maxDepth, aggregate = "list", targets)` entry of `columns` +traverses the named relationship from each row's element — the same kinds, +directions and depth bound as `RelatedElements` — and fills a cell with what +it reaches. The `aggregate` chooses the cell's shape: `"list"` (the default) +holds the related elements, `"count"` how many there are, `"any"` whether +there is at least one — an existence test that stops at the first element it +reaches. `targets`, when given, keeps only the reached elements among them: +a dependency matrix whose columns are one query and whose rows are another +is `Project(source = , columns = (RelatedColumn(..., targets = )))`. The cookbook model's `Traceability` package holds three requirements, a `spacecraft` whose parts satisfy them and three verification cases, two of diff --git a/docs/reference/api.md b/docs/reference/api.md index f2816bcb88..5ccde798d3 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -905,7 +905,8 @@ answer. | `declaredShortName` | `shortName`, absent when the short name is borrowed from a redefined or subsetted feature | | | `documentation` | The body text of the element's `doc` comment, delimiters and indentation removed; absent when undocumented. This single-valued record reports the first body of an element declaring several — a document query's `Project` carries every body | | | `owner` | Qualified name of the owning element; absent for a top-level element, whose owner is the document root | | -| `isAbstract` | `true`/`false` for a definition or usage; absent for anything else, and for a standard-library element restored from cache, which carries no declaration | | +| `isAbstract` | `true`/`false` for a definition or usage; absent for anything else. A standard-library element carries its declaration on every load path (parsed, restored from the on-disk cache or decoded from the bundled snapshot), so it answers too | | +| `isIndividual` | `true`/`false` for a definition or usage (the `individual` modifier); absent for anything else, and present for a standard-library element as `isAbstract` is | | | `type` | Qualified name of the resolved type of a typed feature; absent when untyped or unresolved | | | `multiplicityLower` | Declared lower bound | ✅ | | `multiplicityUpper` | Declared upper bound, `*` when unbounded | ✅ | diff --git a/docs/reference/oslc-query.md b/docs/reference/oslc-query.md index 0545243a35..54ecbdc21e 100644 --- a/docs/reference/oslc-query.md +++ b/docs/reference/oslc-query.md @@ -63,6 +63,7 @@ bound predicate that is not in this table: | `sysml:documentation` | `documentation` | | `sysml:owner` | `owner` | | `sysml:isAbstract` | `isAbstract` | +| `sysml:isIndividual` | `isIndividual` | | `sysml:type` | `type` | | `sysml:multiplicityLower` | `multiplicityLower` | | `sysml:multiplicityUpper` | `multiplicityUpper` | diff --git a/docs/reference/sysml-v1-migration.md b/docs/reference/sysml-v1-migration.md index 8a64965ef2..b61b041f6b 100644 --- a/docs/reference/sysml-v1-migration.md +++ b/docs/reference/sysml-v1-migration.md @@ -423,6 +423,174 @@ Views the export does not cover are a normal case of export scope and are report `` at all refuses with the mismatch stated; without `-layout` the migration's output is byte-identical. +### Tables, matrices and relation maps + +A Cameo/MagicDraw table is a diagram with a definition: the «InstanceTable», «DiagramTable» +(generic table) or «RelationMap» stereotype of the MagicDraw profile +(`http://www.omg.org/spec/UML/20131001/MagicDrawProfile`), or the «DependencyMatrix» of the +Dependency Matrix profile (`http://www.magicdraw.com/schemas/Dependency_Matrix_Profile.xmi`) +with the «MatrixFilter» application naming the same diagram, applied to the `uml:Diagram`. +The [view](#diagrams) is still written for the diagram; the definition is written beside it, +in the same body, as an executable query and a renderable document: + +```sysml +view 'Pump Table' { + expose p1; + expose p2; + expose 'Pump Table Document'; + render Views::asElementTable; +} +calc def 'Pump Table Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::OrderBy( + source = DocumentQueries::WhereFeature( + source = DocumentQueries::WhereType( + source = DocumentQueries::Union( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant::Inventory"))), + other = DocumentQueries::Named(qualifiedName = ("Plant::Spares::s1", "Plant::Spares::s2"))), + type = ("Plant::Structure::Pump")), + 'feature' = "isIndividual", operator = "=", value = "true"), + property = "mass", direction = "descending", missing = "last", multiple = "first"), + properties = ("name"), + columns = (DocumentQueries::Column(name = "mass", expression = Plant::Structure::Pump::mass ?? ""))) +} +part def 'Pump Table Document' :> DocumentQueries::Document { + attribute redefines title = "Pump Table"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Pump Table"; + calc rows : 'Pump Table Rows'; + } +} +``` + +The query is a `calc def` specializing `DocumentQueries::Query`, named ` Rows`, and the +document a `part def` specializing `DocumentQueries::Document`, named ` Document` +(`Name 2`… past a taken name); the view exposes the document, so `-render-document +Plant::Inventory::'Pump Table Document'` renders the table and `-run-query` its rows. Only +the exact profile namespaces define a table: a user stereotype named `InstanceTable` or +`TableStructure` under any other URI is ordinary [user-profile](#profiles-and-stereotypes) +metadata, and a look-alike application from an unbundled profile stays a comment. The +[query cookbook](../manual/query-cookbook.md) documents every operation; the table's parts map: + +| Table definition | Query | +|---|---| +| `scope` (the packages or classifiers whose subtree the table lists); `takeWholeModelAsScope` | `Descendants(source = Named(qualifiedName = (…)))`, unbounded; the whole model is the union of the top-level members and their descendants | +| `rowElements`, `additionalElements` (explicit rows) | one `Union(source = , other = Named(qualifiedName = (row, row, …)))`, the rows in their v1 order after the scope's | +| an instance table's `classifiers` | `WhereType(type = ())` then `WhereFeature('feature' = "isIndividual", operator = "=", value = "true")`, so the rows are the individuals of the classifier and, as in Cameo, of its subtypes; `includeSubtypesOfRowTypes = false` is approximated with the note that subtypes are listed too | +| a generic table's `rowElementType` — a UML metaclass or a stereotype | `WhereType` on the v2 kind the metaclass or a standard stereotype [maps to](#mapping) (`Class` and «Block» → `PartDefinition`, «Requirement» → `RequirementDefinition`…); the abstract metaclasses list what they hold in UML, so `Type` and `Classifier` are every `Definition` plus the `ViewUsage`/`ViewpointUsage` a «View»/«Viewpoint» class became, `Namespace` adds `Package` and `StateUsage`, and `PackageableElement` adds `Package` and the dependencies — never the features a classifier owns; `Element` and `NamedElement` alone admit everything; a user stereotype the migration writes as a `metadata def` → `WhereMetadata('metadata' = (…))`, which honors specializations | +| `columnIds` `QPROP:Element:name`, `documentation`, `qualifiedName`, `owner`, `Id` | `Project(properties = (…))`, in column order; `hideColumns` omits a column; `QPROP:Element:classifier` and other tool properties are omitted with the note | +| `columnIds` `IColumn:` — a value property of the row classifier | `Column(name = "", expression = :: ?? "")`, an empty cell where a row has no slot, as the tool draws it; the property is kept reachable (never written private) because the column names it | +| built-in and value-property columns interleaved (`name`, `mass`, `qualifiedName`) | `Project(properties = ("name", "qualifiedName"), columns = (Column(…)))` — `Project` lists its properties before its columns, so the built-in columns move ahead of the value properties; approximated with the note. Column names are unique: the built-in properties claim theirs first, and a value property captioned like one (`Pump::name`) is written `name 2` with the note | +| `sort` `^Asc` / `^Desc` | `OrderBy(property, direction, missing = "last", multiple = "first")` — empty cells last and the first value of a multi-valued slot, the tool's own ordering; `-1`/`_EMPTY_` is no sort, a sort by tool identity is dropped with the note | +| a matrix's `rowScope`/`rowElementType` and `columnScope`/`columnElementType` | the rows are the row query; each `dependencyCriteria` becomes a `RelatedColumn(name, relationshipKind, direction, maxDepth = 1, aggregate = "list", targets = )`, whose cell lists the column elements the row is related to; `Row to column` is `"outgoing"`, `Column to row` `"incoming"` — the other way round for «DeriveReqt», whose v2 `derivation` runs from the original requirement to the derived one where the v1 dependency runs from the derived to the original — `Both` two columns (approximated); a second criterion with the same name is `Name 2` | +| a relation map's `contextElement`, `relationCriterion`, `depth`, `elementTypes` | `RelatedElements(source = Named(…), relationshipKind, direction, maxDepth = depth)` (0 = unbounded) filtered by `WhereType` over the element types, projected as `qualifiedName` and `@type` | + +A criterion is a relationship walk only for the kinds `RelatedElements` knows: «Satisfy», +«Verify», «Refine», «DeriveReqt», «Allocate» and UML `Generalization` (`specialization`); a +`Dependency`, an import, a user-profile relationship, a metachain or an OCL expression is +refused with the criterion named. A criterion's `includeSubtypes` has no query spelling: a +user stereotype specializing «Satisfy» is written as the same `satisfy`, so a walk of the kind +lists its relationships whether or not the criterion included subtypes. A criterion excluding +them is exact while the archive applies no such stereotype, and approximated — the stereotypes +named — when it does. Refused too is a table whose serialization is malformed — a `scope` +resolving to no element (a bare module id resolves through the href the document referenced +the element by; one that elements of several modules share names no element, and the refusal +lists the hrefs), a `sort` not of the form `^Asc|Desc`, a `depth` that is not a +whole number, an instance table naming no classifier, a matrix with no filter, a criterion +whose XML does not parse — with every fault stated at once. A refused table is an `unmapped` +report row and a `not migrated` comment beside its view, which is still written; the rest of the +model is unaffected. Presentation settings (`displayMode`, `showScopeAsRoot`, colors, widths, +legend, `rowsOrder`…) draw the table and are dropped without a report row. + +### DocGen documents + +An [MDK](https://github.com/Open-MBEE/mdk) DocGen document — a class stereotyped «Document» of +the Document profile (`http://www.magicdraw.com/schemas/manual/Document_Profile.xmi`; the +collaborator profile beside it holds the paragraphs) — is a tree of «view» classes, each +conforming to a viewpoint whose method activity says what the view shows. It is written, beside +the class, as a `part def ' Document' :> DocumentQueries::Document` whose sections are +the view tree in declaration order, and each view's method is lowered into the section's +content, so `-render-document` produces the document DocGen would have. The tree is the one +DocGen walks: every property of a view typed by a view is a section, and a view is entered +for its own sections only through a composite or shared property — a plain reference places +the view as a section without its children, and the «Expose» dependencies of a property feed +its view only when the property is composite. + +```sysml +part def 'Fleet Handbook Document' :> DocumentQueries::Document { + attribute redefines title = "Fleet Handbook"; + part Requirements : DocumentQueries::Section { + attribute redefines title = "Requirements"; + part paragraph : DocumentQueries::Paragraph { + attribute redefines text = "Every truck of the fleet satisfies these requirements."; + } + part list : DocumentQueries::List { + attribute redefines style = "number"; + calc items : 'Fleet Handbook Requirement List Rows'; + } + part Safety : DocumentQueries::Section { … } + } + part Figures : DocumentQueries::Section { + attribute redefines title = "Figures"; + part diagram : DocumentQueries::Diagram { + attribute redefines caption = "Truck Structure"; + ref redefines source = Fleet::Structure::'Truck Structure'; + } + part paragraph : DocumentQueries::Paragraph { + attribute redefines text = "The truck and what it hauls"; + } + } +} +``` + +The method activity is walked from its initial node along control flow — object flows between +pins carry data and are not followed; forks whose branches rejoin are walked branch by branch. +The «Expose» suppliers (and the view's element and package imports) are the chain's root, +`Named(qualifiedName = (…))`, each once however many times it is exposed, and each collect, +filter and sort step wraps the query so far; each presentation step ends one +`calc def ' Rows' :> Query` beside the document and one content part in the +section, in the activity's order: + +| DocGen step | Query or content | +|---|---| +| `CollectOwnedElements(depth)`, `CollectOwners(depth)` | `Descendants` / `Ancestors(source, maxDepth = depth)`; `depth` 0 or absent is unbounded | +| `CollectByDirectedRelationshipStereotypes(stereotypes, directionOut, depth)` | one `RelatedElements(relationshipKind, direction, maxDepth)` per stereotype the kinds above cover, `Union`ed | +| `FilterByMetaclasses`, `FilterByStereotypes` | `WhereType` on the v2 kinds, or `WhereMetadata` for a user stereotype written as a `metadata def`; `include = false` is `Except(source, exclude = …)`; `considerDerived = false` is approximated, since `WhereMetadata` honors specializations | +| `FilterByNames(names)` | one `WhereName(operator = "matches", value = "^(?:<pattern>)$\|^(?:<pattern>)$\|…")` keeping the elements in their order; every pattern must compile as an RE2 regular expression | +| `SortByName`, `SortByAttribute(Name / Documentation)` | `OrderBy(property = "name" / "documentation", …)`, `reverse` descending | +| a fork whose branches rejoin at `Union` | `Union` of the branches' queries; a rejoin by `Intersection` or `XOR` is refused, and `RemoveDuplicates` is implicit in every operation and dropped | +| `CollectionAndFilterGroup`, `StructuredQuery` | the group's chain, inlined | +| `TableStructure` with `TableAttributeColumn` (`Name`, `Documentation`), `TablePropertyColumn` (a value property of the rows' definition), `TableExpressionColumn` naming a bare query property | `part table : Table { attribute redefines caption = …; calc rows : …; }` over `Project(properties, columns = (Column(…)))`, the built-in properties first (a built-in column behind a value property is moved ahead of it with the note) and a value property captioned like a built-in property as `<caption> 2`; `includeDoc` adds `documentation`; a column beyond these is omitted with the note, and a table with no writable column is refused. The caption is the table's title (`titles`, between `titlePrefix` and `titleSuffix`), and its `captions` text follows the table as a `Paragraph` unless `showCaptions` is false | +| `BulletedList(orderedList, includeDoc)` | `part list : List { attribute redefines style = "number" / "bullet"; calc items : …; }`; `includeDoc` follows each item's name with its documentation | +| `Paragraph(body)`; a «CollaboratorParagraph» reading the comment body | `part paragraph : Paragraph { attribute redefines text = "…"; }`, tool HTML reduced to text; a paragraph over the targets' documentation is `calc values : …` over `Project(properties = ("documentation"))` | +| `Image` | one `part diagram : Diagram { attribute redefines caption = "<title>"; ref redefines source = <its view>; }` per diagram the step targets or the view exposes, captioned by its `titles` entry (else the diagram's name) between `titlePrefix` and `titleSuffix`, its `captions` entry following as a `Paragraph` unless `showCaptions` is false; a diagram whose view renders as textual notation (an activity, state machine or sequence diagram) is refused, since a document draws no text view; a «CollaboratorImageParagraph»'s attached bitmap is not a view, so its caption stands as a paragraph and the report says the image is not written | +| `Dynamic View` | a nested `Section` with the called activity's title, lowered the same way; an activity that calls itself is refused, since a recursive section has no static spelling | + +The diagrams among the root elements are no query's rows — a migrated diagram is a view — but +each step transforms them beside the query so an `Image` shows what the chain kept: a name +filter matches the diagram's name, a metaclass or stereotype filter keeps a diagram for +`Element`, `NamedElement`, `Diagram` or the stereotype that is its diagram type, a sort by name +orders them (a diagram has no documentation, so a sort by it leaves them as they are), a +rejoin unites the branches' diagrams once each, `CollectOwners` adds the diagrams' owners to +the query as `Named(qualifiedName = (…))`, and any other collect drops them. An `Image` after +a filter that kept no diagram draws nothing and the report says which filter emptied it. + +A step with no query spelling — `CollectTypes`, `CollectByAssociation`, `CollectThingsOnDiagram`, +`FilterByDiagramType`, `SortByAttribute(Value)`, `SortByProperty`, a `*ByExpression` or +`TableExpressionColumn` beyond a bare query property (`owner.name`, `allInstances()`, OCL), a +`CollectFilterUserScript`, a user script — is refused with the offending construct quoted, +and so is every presentation step downstream of it, while the section and its independent +siblings are still written. A malformed document — a view whose `Conform` names no viewpoint, +a viewpoint whose `method` names no element or one that is not an activity, a method with no +initial node or a dangling control flow, a `depth` that is not a whole number, a +collaborator paragraph whose `viewId` or `ownerId` names no view, an empty paragraph — is +reported the same way. Where a model member named `DocumentQueries` would shadow the library, +every reference is written `$::DocumentQueries::…`. A section, paragraph, table, list or diagram +block declares members of its own (`title`, `rows`, the nested sections), and a reference written +inside it is qualified past whichever of those it would otherwise resolve to — a section named +like a top-level package names that package's view as `$::<package>::…`. + The mapping has been run over the XMI of the [OpenMBEE TMT SysML model](https://github.com/Open-MBEE/TMT-SysML-Model) (27 MB; 44,600 elements once the nodes and edges of its behaviors are counted): it writes 7 MB of notation that passes the gate below in a few seconds, and its Turtle in a few more. Five @@ -503,6 +671,19 @@ literal, a reference to an element not written, a tag the stereotype does not de a comment inside the usage, approximating the element with the reason. Rich text a tool stores as `<html><body>…</body></html>` becomes plain text, as a requirement's `Text` does. +One tool stereotype is read as a type, not kept as a comment: MagicDraw's «typeModifier» on a +property or parameter, whose tag spells a C-style shape after the type. `[]` on a feature +whose declared multiplicity is `[1]` or absent writes `[0..*] ordered nonunique`, and `[n]` +writes `[n] ordered nonunique`, so the feature is the sequence the tool meant; `*` (and `&`) +on a part or item property held by value writes it `ref`, a reference rather than a +containment. A shape with no v2 form is kept as the applied-stereotype comment with the reason +in the report: `[][]`, `[n*m]` and other two-dimensional shapes (a multiplicity has one +dimension), `[]` on a feature already declared a collection (a collection of collections has +no multiplicity) or whose declared bounds are not natural numbers, `*` on an attribute or on +a parameter (neither is held by reference), and a tag that is not one of these spellings. A +same-named user stereotype outside the MagicDraw profile namespace is a `metadata def` like +any other. + ## Behaviors A behavior is migrated so that it *runs*: the `action def` an activity becomes is a token flow diff --git a/docs/reference/wire-contract.md b/docs/reference/wire-contract.md index 40d633f2b2..774681b6a4 100644 --- a/docs/reference/wire-contract.md +++ b/docs/reference/wire-contract.md @@ -802,7 +802,7 @@ HTTP/1.1 400 Bad Request $ … /Query -d '{"modelHash":"2af5…dea2","query":{"where":{"primitive":{"property":"colour","operator":"PRIMITIVE_OPERATOR_EQUAL","value":["red"]}}}}' HTTP/1.1 400 Bad Request -{"code":"invalid_argument","message":"unknown query property \"colour\"; queryable properties are @id, @type, declaredName, declaredShortName, documentation, isAbstract, multiplicityLower, multiplicityUpper, name, owner, qualifiedName, shortName, type"} +{"code":"invalid_argument","message":"unknown query property \"colour\"; queryable properties are @id, @type, declaredName, declaredShortName, documentation, isAbstract, isIndividual, multiplicityLower, multiplicityUpper, name, owner, qualifiedName, shortName, type"} $ … /ApplyEdits -d '{"modelHash":"997e…6134","acceptDocuments":true,"document":"nope.sysml","operations":[{"rename":{"target":"EngineUser::Car","newName":"Automobile"}}]}' HTTP/1.1 400 Bad Request diff --git a/internal/doc/queryexec/computed_test.go b/internal/doc/queryexec/computed_test.go index 4d8069151e..90f15d5592 100644 --- a/internal/doc/queryexec/computed_test.go +++ b/internal/doc/queryexec/computed_test.go @@ -547,3 +547,38 @@ calc def Bad :> Query { }) } } + +func TestExecuteWhereFeatureIsIndividualOverUnboundedDescendants(t *testing.T) { + fixture := loadExecutionFixture(t, ` +part def Wheel; +part def SpareWheel :> Wheel; +package Config { + package Nested { + individual part def FrontLeft :> Wheel; + } + individual part def Spare :> SpareWheel; +} +calc def Instances :> Query { + in root : Element; + Project( + source = WhereFeature( + source = WhereType(source = Descendants(source = root), type = "Wheel"), + 'feature' = "isIndividual", + operator = "=", + value = "true" + ), + properties = ("name") + ) +}`) + result, err := fixture.execute(t, "Instances", Bindings{ + "root": {ElementValue(fixture.symbol(t, "Config"))}, + }, Options{}) + if err != nil { + t.Fatalf("execute Instances: %v", err) + } + // Nested is two levels down, so a bounded depth of 1 would miss FrontLeft; + // SpareWheel is a plain subdefinition and is not an individual. + if names := rowNames(result); !equalStrings(names, []string{"Observatory::Config::Spare", "Observatory::Config::Nested::FrontLeft"}) { + t.Fatalf("rows = %v", names) + } +} diff --git a/internal/doc/queryexec/errors.go b/internal/doc/queryexec/errors.go index b3a6e8a510..fcd6f2d9b7 100644 --- a/internal/doc/queryexec/errors.go +++ b/internal/doc/queryexec/errors.go @@ -24,6 +24,7 @@ const ( ErrorInvalidOrder ErrorKind = "invalid-order" ErrorUnknownProperty ErrorKind = "unknown-property" ErrorUnknownClassification ErrorKind = "unknown-classification" + ErrorUnknownElement ErrorKind = "unknown-element" ErrorUnknownRelationship ErrorKind = "unknown-relationship" ErrorUnevaluableFeature ErrorKind = "unevaluable-feature" ErrorUnknownInvocation ErrorKind = "unknown-invocation" @@ -133,6 +134,8 @@ func (e *Error) Error() string { return fmt.Sprintf("query %s references unknown property %s", e.Query, e.Property) case ErrorUnknownClassification: return fmt.Sprintf("query %s references unknown classification %s", e.Query, e.Actual) + case ErrorUnknownElement: + return fmt.Sprintf("query %s names no single element %s", e.Query, e.Actual) case ErrorUnknownRelationship: return fmt.Sprintf("query %s%s does not support relationship kind %q", e.Query, e.column(), e.Actual) case ErrorUnevaluableFeature: diff --git a/internal/doc/queryexec/execute.go b/internal/doc/queryexec/execute.go index 5a2f14a21f..f2198109d8 100644 --- a/internal/doc/queryexec/execute.go +++ b/internal/doc/queryexec/execute.go @@ -395,6 +395,8 @@ func (e *executor) evaluate(expression queryplan.Expression) (sequence, error) { return e.evaluateInvoke(expression) case queryplan.OperationRelatedElements: return e.evaluateRelated(expression) + case queryplan.OperationNamed: + return e.evaluateNamed(expression) case queryplan.OperationObjects: return e.evaluateObjects(expression) case queryplan.OperationVerdicts: @@ -715,21 +717,6 @@ func (e *executor) stringsArgument(expression queryplan.Expression, name string) return texts, nil } -func (e *executor) integerArgument(expression queryplan.Expression, name string) (int64, error) { - value, err := e.argument(expression, name) - if err != nil { - return 0, err - } - if len(value.values) != 1 { - return 0, e.invalidArgument(expression, name, strconv.Itoa(len(value.values))) - } - integer, ok := value.values[0].Integer() - if !ok || integer < 0 { - return 0, e.invalidArgument(expression, name, string(value.values[0].Kind())) - } - return integer, nil -} - func (e *executor) booleanArgument(expression queryplan.Expression, name string) (bool, error) { value, err := e.argument(expression, name) if err != nil { diff --git a/internal/doc/queryexec/named.go b/internal/doc/queryexec/named.go new file mode 100644 index 0000000000..2f6a91b23d --- /dev/null +++ b/internal/doc/queryexec/named.go @@ -0,0 +1,30 @@ +package queryexec + +import ( + "github.com/Open-MBEE/OpenSysML/internal/ir/queryplan" +) + +// evaluateNamed resolves each qualified name, in argument order, to the one +// element it denotes, as WhereType resolves a type name; a name denoting no +// single element is an unknown-element error. +func (e *executor) evaluateNamed(expression queryplan.Expression) (sequence, error) { + names, err := e.stringsArgument(expression, "qualifiedName") + if err != nil { + return sequence{}, err + } + var result sequence + for _, name := range names { + element, ok := e.context.Resolver.ResolveAliasTarget(e.resolveClassification(name)) + if !ok || element == nil { + return sequence{}, &Error{ + Kind: ErrorUnknownElement, + Query: e.definition.Name(), + Operation: expression.Operation(), + Actual: name, + Origin: expression.Origin(), + } + } + result.values = append(result.values, valueAt(ElementValue(element), expression.Origin())) + } + return result, nil +} diff --git a/internal/doc/queryexec/named_test.go b/internal/doc/queryexec/named_test.go new file mode 100644 index 0000000000..1696687aac --- /dev/null +++ b/internal/doc/queryexec/named_test.go @@ -0,0 +1,99 @@ +package queryexec + +import ( + "strings" + "testing" +) + +const namedBody = ` +package Vehicle { + package Config { + part def Wheel; + individual part def FrontLeft :> Wheel; + individual part def Spare :> Wheel; + } + package 'Spare Parts' { + part def Pad; + } + alias Wheels for Config; +} +package Other { + package Config; +} +package Aliases { + alias Broken for Missing; + alias Loop1 for Loop2; + alias Loop2 for Loop1; +} +calc def ByName :> Query { + in qualifiedName : String[1..*] ordered; + Named(qualifiedName = qualifiedName) +} +calc def ConfigWheels :> Query { + WhereFeature( + source = WhereType(source = Descendants(source = Named(qualifiedName = "Vehicle::Config")), type = "Vehicle::Config::Wheel"), + 'feature' = "isIndividual", operator = "=", value = "true" + ) +} +` + +func TestExecuteNamedResolvesQualifiedNamesInOrder(t *testing.T) { + fixture := loadExecutionFixture(t, namedBody) + run := func(names ...string) *RowSet { + t.Helper() + values := make([]Value, len(names)) + for i, name := range names { + values[i] = StringValue(name) + } + result, err := fixture.execute(t, "ByName", Bindings{"qualifiedName": values}, Options{}) + if err != nil { + t.Fatalf("ByName(%v): %v", names, err) + } + return result + } + // Argument order is row order, a quoted-in-notation name is spelled raw, a + // full or unique partial qualified name both resolve, and a name given twice + // yields its element once per mention. + got := rowNames(run("Observatory::Vehicle::Spare Parts::Pad", "Vehicle::Config::Wheel", "Vehicle::Config::Wheel")) + want := "Observatory::Vehicle::Spare Parts::Pad,Observatory::Vehicle::Config::Wheel,Observatory::Vehicle::Config::Wheel" + if strings.Join(got, ",") != want { + t.Fatalf("Named rows = %v, want %v", got, want) + } + // An alias names its target. + if got := rowNames(run("Vehicle::Wheels")); strings.Join(got, ",") != "Observatory::Vehicle::Config" { + t.Fatalf("Named alias = %v", got) + } + // A unique simple name resolves as it does for WhereType. + if got := rowNames(run("Pad")); strings.Join(got, ",") != "Observatory::Vehicle::Spare Parts::Pad" { + t.Fatalf("Named simple = %v", got) + } + for _, row := range run("Vehicle::Config").Rows() { + if !row.Origin().Located() { + t.Fatalf("row %v without provenance", row.Element()) + } + } + // The named element roots a traversal. + wheels, err := fixture.execute(t, "ConfigWheels", nil, Options{}) + if err != nil { + t.Fatalf("ConfigWheels: %v", err) + } + if got := rowNames(wheels); strings.Join(got, ",") != "Observatory::Vehicle::Config::FrontLeft,Observatory::Vehicle::Config::Spare" { + t.Fatalf("Descendants(Named) = %v", got) + } +} + +// A name that resolves to nothing, to several elements, or to an alias that +// denotes no element (dangling or cyclic) is an unknown-element error, never a row. +func TestExecuteNamedRejectsUnknownAndAmbiguousNames(t *testing.T) { + fixture := loadExecutionFixture(t, namedBody) + for _, name := range []string{"Vehicle::Missing", "Config", "Aliases::Broken", "Aliases::Loop1", "Aliases::Loop2"} { + _, err := fixture.execute(t, "ByName", Bindings{"qualifiedName": {StringValue(name)}}, Options{}) + unknown := executionError(t, err, ErrorUnknownElement) + if unknown.Actual != name || unknown.Operation != "named" { + t.Fatalf("Named(%q) error = %v", name, unknown) + } + if !strings.Contains(err.Error(), "names no single element "+name) { + t.Fatalf("Named(%q) message = %v", name, err) + } + } +} diff --git a/internal/doc/queryexec/operations.go b/internal/doc/queryexec/operations.go index 06cd9f2bfa..2918d523e6 100644 --- a/internal/doc/queryexec/operations.go +++ b/internal/doc/queryexec/operations.go @@ -92,12 +92,46 @@ func (e *executor) evaluateOwned(expression queryplan.Expression) (sequence, err return result, nil } +// depthLimit is a maxDepth argument: so many levels, or unbounded when the +// argument is null or omitted. +type depthLimit struct { + bounded bool + levels int64 +} + +// reached reports whether a row at depth is not to be walked past. +func (d depthLimit) reached(depth int64) bool { return d.bounded && depth >= d.levels } + +// depthArgument reads an operation's maxDepth: a non-negative integer, or +// unbounded when null or omitted. +func (e *executor) depthArgument(expression queryplan.Expression) (depthLimit, error) { + if !hasArgument(expression, "maxDepth") { + return depthLimit{}, nil + } + value, err := e.argument(expression, "maxDepth") + if err != nil { + return depthLimit{}, err + } + switch len(value.values) { + case 0: + return depthLimit{}, nil + case 1: + default: + return depthLimit{}, e.invalidArgument(expression, "maxDepth", strconv.Itoa(len(value.values))) + } + levels, ok := value.values[0].Integer() + if !ok || levels < 0 { + return depthLimit{}, e.invalidArgument(expression, "maxDepth", string(value.values[0].Kind())) + } + return depthLimit{bounded: true, levels: levels}, nil +} + func (e *executor) evaluateDescendants(expression queryplan.Expression) (sequence, error) { source, err := e.ownershipArgument(expression, "source") if err != nil { return sequence{}, err } - maxDepth, err := e.integerArgument(expression, "maxDepth") + maxDepth, err := e.depthArgument(expression) if err != nil { return sequence{}, err } @@ -115,7 +149,7 @@ func (e *executor) evaluateDescendants(expression queryplan.Expression) (sequenc for len(queue) > 0 { next := queue[0] queue = queue[1:] - if next.depth >= maxDepth { + if maxDepth.reached(next.depth) { continue } owned, err := e.ownedRows(expression, next.row) @@ -143,7 +177,7 @@ func (e *executor) evaluateAncestors(expression queryplan.Expression) (sequence, if err != nil { return sequence{}, err } - maxDepth, err := e.integerArgument(expression, "maxDepth") + maxDepth, err := e.depthArgument(expression) if err != nil { return sequence{}, err } @@ -161,7 +195,7 @@ func (e *executor) evaluateAncestors(expression queryplan.Expression) (sequence, for len(queue) > 0 { next := queue[0] queue = queue[1:] - if next.depth >= maxDepth { + if maxDepth.reached(next.depth) { continue } owner, ok := e.ownerRow(next.row) @@ -182,72 +216,97 @@ func (e *executor) evaluateAncestors(expression queryplan.Expression) (sequence, return result, nil } +// typeTest is one resolved name WhereType keeps rows conforming to. +type typeTest struct { + name string + target *symbols.Symbol + classification string +} + func (e *executor) evaluateWhereType(expression queryplan.Expression) (sequence, error) { source, err := e.rowArgument(expression, "source") if err != nil { return sequence{}, err } - typeName, err := e.stringArgument(expression, "type") + typeNames, err := e.stringsArgument(expression, "type") if err != nil { return sequence{}, err } - target := e.resolveClassification(typeName) - classification := typeName - if target != nil { - classification = symbols.FQNOf(target) + if len(typeNames) == 0 { + return sequence{}, e.invalidArgument(expression, "type", "0") + } + tests := make([]typeTest, len(typeNames)) + matched := make([]bool, len(typeNames)) + for i, typeName := range typeNames { + tests[i] = typeTest{name: typeName, target: e.resolveClassification(typeName), classification: typeName} + if tests[i].target != nil { + tests[i].classification = symbols.FQNOf(tests[i].target) + } } result := filtered(source) for i, value := range source.values { - if _, _, isObject := value.Object(); isObject { - if e.objectIsA(value, typeName, target) { + for j, test := range tests { + if e.valueIsA(value, test) { + matched[j] = true appendSelected(&result, source, i) + break } - continue - } - sym := value.Declaration() - if sym == nil { - continue - } - matches := query.MetamodelTypeNameOf(sym) == typeName - if target != nil { - matches = matches || - e.context.Model.MetaclassConforms(sym, classification) || - symbols.SameElement(sym, target) || - e.context.Model.Conforms(sym, target) - } - if matches { - appendSelected(&result, source, i) } } - if target == nil && len(result.values) == 0 && !query.IsMetamodelTypeName(typeName) { - return sequence{}, &Error{ - Kind: ErrorUnknownClassification, - Query: e.definition.Name(), - Operation: expression.Operation(), - Actual: typeName, - Origin: expression.Origin(), + for j, test := range tests { + if test.target == nil && !matched[j] && !query.IsMetamodelTypeName(test.name) { + return sequence{}, &Error{ + Kind: ErrorUnknownClassification, + Query: e.definition.Name(), + Operation: expression.Operation(), + Actual: test.name, + Origin: expression.Origin(), + } } } return result, nil } +// valueIsA reports whether a row is an object or declaration of the type. +func (e *executor) valueIsA(value Value, test typeTest) bool { + if _, _, isObject := value.Object(); isObject { + return e.objectIsA(value, test.name, test.target) + } + sym := value.Declaration() + if sym == nil { + return false + } + if query.MetamodelTypeNameOf(sym) == test.name { + return true + } + return test.target != nil && + (e.context.Model.MetaclassConforms(sym, test.classification) || + symbols.SameElement(sym, test.target) || + e.context.Model.Conforms(sym, test.target)) +} + func (e *executor) evaluateWhereMetadata(expression queryplan.Expression) (sequence, error) { source, err := e.rowArgument(expression, "source") if err != nil { return sequence{}, err } - name, err := e.stringArgument(expression, "metadata") + names, err := e.stringsArgument(expression, "metadata") if err != nil { return sequence{}, err } - target := e.resolveClassification(name) - if target == nil { - return sequence{}, &Error{ - Kind: ErrorUnknownClassification, - Query: e.definition.Name(), - Operation: expression.Operation(), - Actual: name, - Origin: expression.Origin(), + if len(names) == 0 { + return sequence{}, e.invalidArgument(expression, "metadata", "0") + } + targets := make([]*symbols.Symbol, len(names)) + for i, name := range names { + if targets[i] = e.resolveClassification(name); targets[i] == nil { + return sequence{}, &Error{ + Kind: ErrorUnknownClassification, + Query: e.definition.Name(), + Operation: expression.Operation(), + Actual: name, + Origin: expression.Origin(), + } } } result := filtered(source) @@ -257,9 +316,10 @@ func (e *executor) evaluateWhereMetadata(expression queryplan.Expression) (seque types := e.context.Index.LookupQualified(annotation.TypeFQN) matches := false for _, actual := range types { - if symbols.SameElement(actual, target) || e.context.Model.Conforms(actual, target) { - matches = true - break + for _, target := range targets { + if symbols.SameElement(actual, target) || e.context.Model.Conforms(actual, target) { + matches = true + } } } if matches { @@ -670,6 +730,7 @@ func isQueryableProperty(property string) bool { query.PropertyOwner, query.PropertyElementType, query.PropertyIsAbstract, + query.PropertyIsIndividual, query.PropertyMultiplicityLower, query.PropertyMultiplicityUpper: return true @@ -681,7 +742,7 @@ func isQueryableProperty(property string) bool { func typedPropertyValue(property, value string, sym *symbols.Symbol) Value { var result Value switch property { - case query.PropertyIsAbstract: + case query.PropertyIsAbstract, query.PropertyIsIndividual: boolean, _ := strconv.ParseBool(value) result = BooleanValue(boolean) case query.PropertyMultiplicityLower, query.PropertyMultiplicityUpper: diff --git a/internal/doc/queryexec/related.go b/internal/doc/queryexec/related.go index 7b884bd4c8..6eacdc7a9f 100644 --- a/internal/doc/queryexec/related.go +++ b/internal/doc/queryexec/related.go @@ -62,7 +62,7 @@ func newRelationshipTables() *relationshipTables { type relationshipWalk struct { kind string direction string - maxDepth int64 + maxDepth depthLimit } func (e *executor) evaluateRelated(expression queryplan.Expression) (sequence, error) { @@ -100,7 +100,7 @@ func (e *executor) relationshipArguments(expression queryplan.Expression) (relat if err != nil { return relationshipWalk{}, err } - maxDepth, err := e.integerArgument(expression, "maxDepth") + maxDepth, err := e.depthArgument(expression) if err != nil { return relationshipWalk{}, err } @@ -140,7 +140,7 @@ func (e *executor) traverseRelated( for len(queue) > 0 { next := queue[0] queue = queue[1:] - if next.depth >= walk.maxDepth { + if walk.maxDepth.reached(next.depth) { continue } neighbors, err := e.relatedNeighbors(expression, walk.kind, walk.direction, next.sym) @@ -321,7 +321,9 @@ func connectorRelationship(usage ast.UsageKind, kind string) bool { // scanSatisfaction records the edge a satisfy or verify assertion states: from // the subject its `by` clause names — else the element stating the assertion — // to the requirement it references, or to the assertion itself when it -// declares its requirement (`satisfy requirement r by v { ... }`). +// declares its requirement (`satisfy requirement r by v { ... }`) — and then +// also to the requirement definition the declaration is typed by, which is +// the requirement a v1 model states the satisfaction of. func (e *executor) scanSatisfaction(edges *relationshipEdges, kind string, sym *symbols.Symbol) { usage, ok := sym.Decl.(*ast.Usage) if !ok || usage.Kind != ast.UsageSatisfy { @@ -330,7 +332,7 @@ func (e *executor) scanSatisfaction(edges *relationshipEdges, kind string, sym * if (usage.Keyword == "verify") != (kind == relationshipVerification) { return } - var requirement, subject *symbols.Symbol + var requirement, definition, subject *symbols.Symbol for _, rel := range usage.Relationships { if rel == nil || rel.Target == nil { continue @@ -346,6 +348,10 @@ func (e *executor) scanSatisfaction(edges *relationshipEdges, kind string, sym * if !usage.DeclaresRequirement { requirement = target } + case ast.RelTyping: + if usage.DeclaresRequirement && target.Kind == symbols.SymbolRequirementDef { + definition = target + } case ast.RelSubject: subject = target } @@ -365,6 +371,9 @@ func (e *executor) scanSatisfaction(edges *relationshipEdges, kind string, sym * return } addEdge(edges, subject, requirement) + if definition != nil { + addEdge(edges, subject, definition) + } } func isObjectiveUsage(decl ast.Node) bool { diff --git a/internal/doc/queryexec/related_column.go b/internal/doc/queryexec/related_column.go index 7920d76d27..162ef4a43e 100644 --- a/internal/doc/queryexec/related_column.go +++ b/internal/doc/queryexec/related_column.go @@ -8,11 +8,22 @@ import ( ) // relatedColumn is one decoded RelatedColumn of a projection: a relationship -// traversal run from each row's declaration, reduced by aggregate. +// traversal run from each row's declaration, kept to targets when given and +// reduced by aggregate. type relatedColumn struct { plan queryplan.Expression walk relationshipWalk aggregate string + targets map[symbols.ElementKey]struct{} +} + +// keeps reports whether a reached element counts for the column. +func (c *relatedColumn) keeps(sym *symbols.Symbol) bool { + if c.targets == nil { + return true + } + _, ok := c.targets[symbols.KeyOf(sym)] + return ok } // relatedColumnOf evaluates a RelatedColumn's arguments once per projection @@ -31,6 +42,17 @@ func (e *executor) relatedColumnOf(plan queryplan.Expression) (*relatedColumn, e if !queryplan.RelatedAggregateSupported(column.aggregate) { return nil, columnScoped(e.invalidArgument(plan, "aggregate", column.aggregate), plan.Target()) } + if hasArgument(plan, "targets") { + targets, err := e.elementArgument(plan, "targets") + if err != nil { + return nil, columnScoped(err, plan.Target()) + } + column.targets = make(map[symbols.ElementKey]struct{}, len(targets.values)) + for _, value := range targets.values { + sym, _ := value.Element() + column.targets[symbols.KeyOf(sym)] = struct{}{} + } + } return column, nil } @@ -50,7 +72,11 @@ func (e *executor) evaluateRelatedCell(column computedColumn, row Value) ([]Valu } } if related.aggregate == queryplan.RelatedAggregateAny { - found, err := e.hasRelated(related.plan, related.walk, root) + found := false + err := e.traverseRelated(related.plan, related.walk, []*symbols.Symbol{root}, func(neighbor *symbols.Symbol) bool { + found = related.keeps(neighbor) + return !found + }) if err != nil { return nil, columnScoped(err, column.name) } @@ -58,7 +84,9 @@ func (e *executor) evaluateRelatedCell(column computedColumn, row Value) ([]Valu } var values []Value err := e.traverseRelated(related.plan, related.walk, []*symbols.Symbol{root}, func(neighbor *symbols.Symbol) bool { - values = append(values, ElementValue(neighbor)) + if related.keeps(neighbor) { + values = append(values, ElementValue(neighbor)) + } return true }) if err != nil { diff --git a/internal/doc/queryexec/related_column_test.go b/internal/doc/queryexec/related_column_test.go index df5c238b53..ab837b2c79 100644 --- a/internal/doc/queryexec/related_column_test.go +++ b/internal/doc/queryexec/related_column_test.go @@ -203,6 +203,37 @@ func TestExecuteRelatedColumnFollowsDepthAndDirection(t *testing.T) { assertColumn(t, cellsByColumn(t, reach), "related", [][]string{{"false"}}) } +func TestExecuteRelatedColumnKeepsOnlyTargets(t *testing.T) { + fixture := loadExecutionFixtureFile(t, traceMatrixFixture) + result, err := fixture.execute(t, "Targeted", Bindings{ + "root": {fixture.observatory(t)}, + "targets": { + ElementValue(fixture.symbol(t, "mount")), + ElementValue(fixture.symbol(t, "groundStation")), + }, + }, Options{}) + if err != nil { + t.Fatalf("execute: %v", err) + } + // telescope satisfies massRequirement but is not a target, so it is dropped. + assertColumn(t, cellsByColumn(t, result), "satisfiedBy", [][]string{ + {"groundStation"}, + {"mount"}, + nil, + }) +} + +func TestExecuteRelatedColumnNullDepthIsUnbounded(t *testing.T) { + fixture := loadExecutionFixtureFile(t, traceMatrixFixture) + result, err := fixture.execute(t, "Unbounded", Bindings{ + "root": {ElementValue(fixture.symbol(t, "MirrorAssembly"))}, + }, Options{}) + if err != nil { + t.Fatalf("execute: %v", err) + } + assertColumn(t, cellsByColumn(t, result), "generals", [][]string{{"OpticalSubsystem", "Subsystem"}}) +} + func TestExecuteRelatedColumnReportsItsColumnInErrors(t *testing.T) { fixture := loadExecutionFixtureFile(t, traceMatrixFixture) mirror := ElementValue(fixture.symbol(t, "MirrorAssembly")) diff --git a/internal/doc/queryexec/related_test.go b/internal/doc/queryexec/related_test.go index e5b9ad29e3..c94b31105f 100644 --- a/internal/doc/queryexec/related_test.go +++ b/internal/doc/queryexec/related_test.go @@ -149,11 +149,14 @@ func TestExecuteRelatedDeclaredRequirements(t *testing.T) { fixture := loadExecutionFixtureFile(t, "testdata/tmt_relationships.sysml") // A satisfy or verify assertion that declares its requirement relates the - // subject to the declared requirement usage itself. + // subject to the declared requirement usage itself and, when the + // declaration is typed, to its requirement definition. assertRelated(t, fixture, "scienceComputer", "satisfaction", "outgoing", 1, - []string{"dataArchive::archiveRequirement"}) + []string{"dataArchive::archiveRequirement", "DataRequirement"}) assertRelated(t, fixture, "dataArchive::archiveRequirement", "satisfaction", "incoming", 1, []string{"scienceComputer"}) + assertRelated(t, fixture, "DataRequirement", "satisfaction", "incoming", 1, + []string{"scienceComputer"}) assertRelated(t, fixture, "scienceComputer", "verification", "outgoing", 1, []string{"archiveVerification::archiveObjective::archiveCheck"}) assertRelated(t, fixture, "archiveVerification::archiveObjective::archiveCheck", "verification", "incoming", 1, diff --git a/internal/doc/queryexec/testdata/trace_matrix.sysml b/internal/doc/queryexec/testdata/trace_matrix.sysml index b6e16fa413..ea8c6d7035 100644 --- a/internal/doc/queryexec/testdata/trace_matrix.sysml +++ b/internal/doc/queryexec/testdata/trace_matrix.sysml @@ -72,6 +72,43 @@ package Observatory { ) } + calc def Targeted :> Query { + in root : Element; + in targets : Element[0..*] ordered; + Project( + source = WhereType( + source = Descendants(source = root, maxDepth = 1), + type = "RequirementUsage" + ), + properties = ("name"), + columns = ( + RelatedColumn( + name = "satisfiedBy", + relationshipKind = "satisfaction", + direction = "incoming", + aggregate = "list", + targets = targets + ) + ) + ) + } + + calc def Unbounded :> Query { + in root : Element; + Project( + source = root, + properties = ("name"), + columns = ( + RelatedColumn( + name = "generals", + relationshipKind = "specialization", + direction = "outgoing", + maxDepth = null + ) + ) + ) + } + calc def Uncovered :> Query { in root : Element; WhereFeature( diff --git a/internal/ir/queryplan/columns_test.go b/internal/ir/queryplan/columns_test.go index 100b0f5844..50f6ae42f6 100644 --- a/internal/ir/queryplan/columns_test.go +++ b/internal/ir/queryplan/columns_test.go @@ -406,7 +406,7 @@ calc def Bad :> Query { in root : Element; Project( source = Descendants(source = root, maxDepth = 1), - columns = (RelatedColumn("satisfiedBy", "satisfaction", "incoming")) + columns = (RelatedColumn("satisfiedBy", "satisfaction")) ) }`, }, @@ -418,7 +418,7 @@ calc def Bad :> Query { in root : Element; Project( source = Descendants(source = root, maxDepth = 1), - columns = (RelatedColumn("satisfiedBy", "satisfaction", "incoming", 1, "list", "extra")) + columns = (RelatedColumn("satisfiedBy", "satisfaction", "incoming", 1, "list", root, "extra")) ) }`, }, diff --git a/internal/ir/queryplan/compiler.go b/internal/ir/queryplan/compiler.go index 4bcb6ffed7..5a92a148ce 100644 --- a/internal/ir/queryplan/compiler.go +++ b/internal/ir/queryplan/compiler.go @@ -26,6 +26,7 @@ var builtins = map[string]builtin{ "DocumentQueries::OwnedElements": {OperationOwnedElements}, "DocumentQueries::Descendants": {OperationDescendants}, "DocumentQueries::Ancestors": {OperationAncestors}, + "DocumentQueries::Named": {OperationNamed}, "DocumentQueries::Objects": {OperationObjects}, "DocumentQueries::Verdicts": {OperationVerdicts}, "DocumentQueries::States": {OperationStates}, diff --git a/internal/ir/queryplan/compiler_setops_test.go b/internal/ir/queryplan/compiler_setops_test.go index acfef4be5d..0615a24060 100644 --- a/internal/ir/queryplan/compiler_setops_test.go +++ b/internal/ir/queryplan/compiler_setops_test.go @@ -82,7 +82,7 @@ calc def DuplicateArgument :> Query { } calc def TooFewPositional :> Query { in root : Element; - WhereRelated(root, "satisfaction", "incoming") + WhereRelated(root, "satisfaction") } calc def ExistsAsString :> Query { in root : Element; diff --git a/internal/ir/queryplan/plan.go b/internal/ir/queryplan/plan.go index b92af50eaf..b2523181d1 100644 --- a/internal/ir/queryplan/plan.go +++ b/internal/ir/queryplan/plan.go @@ -18,6 +18,8 @@ const ( OperationOwnedElements Operation = "owned-elements" OperationDescendants Operation = "descendants" OperationAncestors Operation = "ancestors" + // OperationNamed resolves qualified names to the model elements they denote. + OperationNamed Operation = "named" // OperationObjects enumerates the objects a session holds, by type. OperationObjects Operation = "objects" // OperationVerdicts checks the assertions about each source row's object. diff --git a/internal/semantic/query/oslc.go b/internal/semantic/query/oslc.go index f0680831a4..0dbc9c73d2 100644 --- a/internal/semantic/query/oslc.go +++ b/internal/semantic/query/oslc.go @@ -63,6 +63,7 @@ var oslcPropertyMappings = map[string]string{ sysmlNS + "documentation": PropertyDocumentation, sysmlNS + "owner": PropertyOwner, sysmlNS + "isAbstract": PropertyIsAbstract, + sysmlNS + "isIndividual": PropertyIsIndividual, sysmlNS + "type": PropertyElementType, sysmlNS + "multiplicityLower": PropertyMultiplicityLower, sysmlNS + "multiplicityUpper": PropertyMultiplicityUpper, diff --git a/internal/semantic/query/properties.go b/internal/semantic/query/properties.go index af2a0102c1..357a51cd8a 100644 --- a/internal/semantic/query/properties.go +++ b/internal/semantic/query/properties.go @@ -70,6 +70,13 @@ func (r *PropertyReader) Values(sym *symbols.Symbol, property string) ([]string, case *ast.Definition: return []string{strconv.FormatBool(decl.IsAbstract)}, true } + case PropertyIsIndividual: + switch decl := sym.Decl.(type) { + case *ast.Usage: + return []string{strconv.FormatBool(decl.IsIndividual)}, true + case *ast.Definition: + return []string{strconv.FormatBool(decl.IsIndividual)}, true + } case PropertyMultiplicityLower, PropertyMultiplicityUpper: if r.semantics == nil { return nil, false diff --git a/internal/semantic/query/query.go b/internal/semantic/query/query.go index b11a07211a..0782058f4f 100644 --- a/internal/semantic/query/query.go +++ b/internal/semantic/query/query.go @@ -36,6 +36,8 @@ const ( PropertyElementType = "type" // PropertyIsAbstract is the abstractness property. PropertyIsAbstract = "isAbstract" + // PropertyIsIndividual is the `individual` modifier of a definition or usage. + PropertyIsIndividual = "isIndividual" // PropertyMultiplicityLower is the lower multiplicity bound property. PropertyMultiplicityLower = "multiplicityLower" // PropertyMultiplicityUpper is the upper multiplicity bound property. @@ -46,7 +48,7 @@ const ( var propertyNames = []string{ PropertyID, PropertyType, PropertyName, PropertyDeclaredName, PropertyShortName, PropertyDeclaredShortName, PropertyDocumentation, - PropertyQualifiedName, PropertyOwner, PropertyIsAbstract, PropertyElementType, + PropertyQualifiedName, PropertyOwner, PropertyIsAbstract, PropertyIsIndividual, PropertyElementType, PropertyMultiplicityLower, PropertyMultiplicityUpper, } diff --git a/internal/syntax/parser/expr.go b/internal/syntax/parser/expr.go index 44c34e214d..cc789a6d30 100644 --- a/internal/syntax/parser/expr.go +++ b/internal/syntax/parser/expr.go @@ -226,6 +226,7 @@ func metadataAccessRef(expr ast.Node) *ast.QualifiedName { func (p *Parser) atExprStart() bool { t := p.peek() return p.atName() || + p.atGlobalName() || t.Kind == lexer.Decimal || t.Kind == lexer.Real || t.Kind == lexer.String || @@ -420,8 +421,8 @@ func (p *Parser) parseBase() ast.Node { e.NodeSpan = p.spanFrom(start) return setBase(e) - case p.atName(), p.at(lexer.Keyword): - // Parse qualified name or keyword-as-name + case p.atName(), p.atGlobalName(), p.at(lexer.Keyword): + // Parse qualified name (`$::`-rooted included) or keyword-as-name var qn *ast.QualifiedName if p.at(lexer.Keyword) { // Keywords can be used as feature references (e.g., `excluding(do)`) diff --git a/internal/syntax/parser/namespace.go b/internal/syntax/parser/namespace.go index ec4396d30a..1690a60e89 100644 --- a/internal/syntax/parser/namespace.go +++ b/internal/syntax/parser/namespace.go @@ -23,6 +23,11 @@ func (p *Parser) atName() bool { return false } +// atGlobalName reports whether a `$::`-rooted qualified name begins here. +func (p *Parser) atGlobalName() bool { + return p.at(lexer.Dollar) && p.peekN(1).Kind == lexer.ColonColon +} + // reservedWord reports whether the word is a literal of this file's grammar, // and so cannot spell a name in it. func (p *Parser) reservedWord(w string) bool { diff --git a/internal/translate/migrate/behavior.go b/internal/translate/migrate/behavior.go index 98661c58c7..6c1a385360 100644 --- a/internal/translate/migrate/behavior.go +++ b/internal/translate/migrate/behavior.go @@ -268,8 +268,12 @@ func (m *migration) parameter(p, scope *sysmlv1.Element, declared map[string]boo b.WriteString(" : " + typ) } mult, mnote := m.multiplicity(p) + tm := m.typeModifier(p) + if shape := tm.shape(); shape != "" { + mult, mnote = shape, "" + } b.WriteString(mult) - note = joinNotes(note, mnote) + note = joinNotes(joinNotes(note, mnote), tm.note()) var body []string bound, isBound := m.bound[p] if isBound { @@ -299,6 +303,7 @@ func (m *migration) parameter(p, scope *sysmlv1.Element, declared map[string]boo m.w.block(b.String(), func() { m.comments(p) m.w.lines(body) + m.stereotypeAnnotations(p) }) } diff --git a/internal/translate/migrate/classify.go b/internal/translate/migrate/classify.go index 34a14e5d35..664027e00d 100644 --- a/internal/translate/migrate/classify.go +++ b/internal/translate/migrate/classify.go @@ -460,7 +460,7 @@ func (m *migration) classify(e *sysmlv1.Element) (category, string) { return catPartDef, "" case has(e, "Stakeholder"): return catPartDef, "a v1 «Stakeholder» is written as a part def" - case has(e, "View"): + case has(e, "View") || e.DocGenView(): return catView, "" case has(e, "Viewpoint"): return catViewpoint, "" diff --git a/internal/translate/migrate/diagrams.go b/internal/translate/migrate/diagrams.go index 16dabf751e..c3aa5b8b66 100644 --- a/internal/translate/migrate/diagrams.go +++ b/internal/translate/migrate/diagrams.go @@ -67,11 +67,16 @@ type view struct { d *sysmlv1.Diagram // host is the element whose body holds the view; nil for the top level. host *sysmlv1.Element - name string + // placed is false when nothing written can hold the view. + placed bool + name string // note says what of the view's placement is approximated; "" when nothing. note string // entry is the report row, filled when the view is written. entry *Entry + // tables are the table definitions the diagram carries, written beside + // the view in definition order; empty for a diagram that defines none. + tables []*tableDoc } // planViews assigns every diagram the body its view is written in and reserves @@ -83,9 +88,8 @@ func (m *migration) planViews() { d := &m.model.Diagrams[i] v := &view{d: d} m.viewOf[d] = v - var placed bool - v.host, placed, v.note = m.viewHost(d) - if !placed { + v.host, v.placed, v.note = m.viewHost(d) + if !v.placed { v.entry = m.diagramEntry(d, Unmapped, "", v.note) continue } @@ -114,6 +118,8 @@ func (m *migration) planViews() { } m.hosted[v.host] = append(m.hosted[v.host], v) } + m.planTables() + m.planDocuments() } // viewName reserves the name a view takes in host's body, or at the top level @@ -267,14 +273,14 @@ func (m *migration) viewRef(v *view, scope *sysmlv1.Element) string { host := v.host for i, s := range chain { if s == host { - if !m.shadows(chain[:i], v.name) { + if !m.hidden(v.name) && !m.shadows(chain[:i], v.name) { return name } break } } if host == nil { - if m.shadows(chain, v.name) { + if m.hidden(v.name) || m.shadows(chain, v.name) { return "$::" + name } return name @@ -282,6 +288,30 @@ func (m *migration) viewRef(v *view, scope *sysmlv1.Element) string { return m.memberRef(host, scope) + "::" + name } +// viewSteps is the feature chain from the namespace above v's outermost usage +// down to v; def is that namespace when a definition, why says why no chain reaches v. +func (m *migration) viewSteps(v *view) (def *sysmlv1.Element, steps []segment, why string) { + path := m.path(v.host) + i := len(path) + for i > 0 && path[i-1].feature { + i-- + } + steps = append(append(steps, path[i:]...), segment{name: v.name, feature: true}) + if i == 0 { + return nil, steps, "" + } + ns := path[i-1] + if ns.elem != nil { + switch cat, _ := m.classify(ns.elem); { + case cat == catPackage: + return nil, steps, "" + case strings.HasSuffix(cat.keyword(), " def"): + return ns.elem, steps, "" + } + } + return nil, nil, "the view '" + v.name + "' is a member of " + m.qualified(m.segments(v.host)) + ", which no feature reaches" +} + // shadows reports whether one of scopes declares a member named name. func (m *migration) shadows(scopes []*sysmlv1.Element, name string) bool { for _, s := range scopes { @@ -403,6 +433,9 @@ func (m *migration) writeView(v *view) { if x.dangling > 0 { note = joinNotes(note, fmt.Sprintf("%d of %d shown ids resolve to no element", x.dangling, shown)) } + for _, td := range v.tables { + m.lowerTable(td) + } geo := m.viewGeometry(v, x, form) if geo.note != "" { note = joinNotes(note, geo.note) @@ -411,11 +444,19 @@ func (m *migration) writeView(v *view) { for _, ref := range x.refs { m.w.line("expose " + ref + ";") } + for _, td := range v.tables { + if td.written() { + m.w.line("expose " + writeName(td.doc) + ";") + } + } for _, line := range geo.lines { m.w.line(line) } m.w.line("render " + prefix + render + ";") }) + for _, td := range v.tables { + m.writeTable(td) + } verdict := Mapped if v.note != "" || untyped || shown == 0 || len(x.refs) == 0 || x.unwritten+x.dangling > 0 { verdict = Approximated @@ -566,6 +607,7 @@ func (m *migration) diagrams() { } m.report.Entries = append(m.report.Entries, *v.entry) } + m.unplacedTables() } // viewGeometry is the layout a matched MTIP diagram record writes into a view: diff --git a/internal/translate/migrate/documents.go b/internal/translate/migrate/documents.go new file mode 100644 index 0000000000..7927192085 --- /dev/null +++ b/internal/translate/migrate/documents.go @@ -0,0 +1,1441 @@ +package migrate + +import ( + "fmt" + "regexp" + "slices" + "strconv" + "strings" + + "github.com/Open-MBEE/OpenSysML/internal/translate/xmi/sysmlv1" +) + +// docPlan is one DocGen document planned as a Document definition beside its +// class in the class's owner, with the report row it earns. +type docPlan struct { + d *sysmlv1.DocGenDocument + host *sysmlv1.Element + root *sectionPlan + // anchors are the usages the Document declares to reach views held by + // definitions, one per definition in first-use order. + anchors []*anchor + // notes are the approximations the document as a whole carries. + notes []string +} + +// anchor is a Document's reference usage of a definition, through which a +// Diagram block reaches a view the definition holds. +type anchor struct { + def *sysmlv1.Element + name string +} + +// anchor returns the Document's anchor of def, adding it on first use. +func (dp *docPlan) anchor(def *sysmlv1.Element) *anchor { + for _, a := range dp.anchors { + if a.def == def { + return a + } + } + a := &anchor{def: def} + dp.anchors = append(dp.anchors, a) + return a +} + +// sectionPlan is one view of a document: the Document itself at the root, a +// Section below it, holding its content and its child views in order. +type sectionPlan struct { + v *sysmlv1.DocGenView + name string + title string + // content is what the view's method produces, in chain order; a nested + // Dynamic View is content too, so it keeps its place among the blocks. + content []*contentPlan + children []*sectionPlan + // names are the member names claimed in this section's body. + names columnNames + // refused says why the method produced nothing, when it was refused whole. + refused string +} + +// contentPlan is one content block a presentation node produces, or the +// comment standing for a node that could not be lowered. +type contentPlan struct { + kind string + name string + node *sysmlv1.Element + label string + text string + caption, + style string + // captionOf is the kind of the block a caption Paragraph follows. + captionOf string + // query and rows are the row query's reserved name and expression, for + // the query-backed kinds. + query string + rows qx + // source is the view a Diagram shows; anchor the Document's usage of the + // definition holding it, when one does. + source *view + anchor *anchor + section *sectionPlan + notes []string + refused string + // target is the block's qualified name once written, for its report row. + target string +} + +// docSuffix names a document's definition after its class. +const docSuffix = " Document" + +// planDocuments plans every DocGen document once views and tables are, so the +// names reserved account for each other and Diagram blocks find their views. +func (m *migration) planDocuments() { + for _, d := range m.model.Documents { + m.planDocument(d) + } + for _, p := range m.model.StrayParagraphs { + m.strayParagraph(p) + } +} + +// docHost is the element whose body a document's definition is written in: +// the nearest written body above the class, nil for the top level. +func (m *migration) docHost(class *sysmlv1.Element) (host *sysmlv1.Element, ok bool) { + for cur := class.Parent; cur != nil; cur = cur.Parent { + host := m.bodyOf(cur) + if !m.hostsViews(host) { + continue + } + if m.flattened(host) { + host = nil + } + return host, true + } + return nil, class.Parent == nil +} + +func (m *migration) planDocument(d *sysmlv1.DocGenDocument) { + class := d.Class + host, ok := m.docHost(class) + if !ok { + note := "neither its owner " + kindOf(class.Parent) + " " + qualifiedName(class.Parent) + " nor any ancestor of it is written" + m.report.Entries = append(m.report.Entries, *m.docEntry(d, Unmapped, "", note)) + return + } + title := strings.TrimSpace(class.Name) + if title == "" { + title = "Document" + } + dp := &docPlan{d: d, host: host} + dp.root = §ionPlan{v: d.Root, title: title, names: columnNames{}} + dp.root.name = m.viewName(host, title+docSuffix) + m.planSection(dp, dp.root) + m.nameAnchors(dp) + m.extras[host] = append(m.extras[host], func() { m.writeDocument(dp) }) +} + +// nameAnchors names the anchors after their definitions, clear of every +// member name the document declares, so a chain from one resolves anywhere in it. +func (m *migration) nameAnchors(dp *docPlan) { + used := columnNames{} + for _, names := range libraryMembers { + for _, n := range names { + used[n] = true + } + } + claimed(dp.root, used) + for _, a := range dp.anchors { + base := lowerFirst(m.nameFor(a.def)) + a.name = base + for i := 2; used[a.name]; i++ { + a.name = fmt.Sprintf("%s %d", base, i) + } + used[a.name] = true + dp.root.names[a.name] = true + } +} + +// claimed adds the member names of sec and every section under it to into. +func claimed(sec *sectionPlan, into columnNames) { + for n := range sec.names { + into[n] = true + } + for _, cp := range sec.content { + if cp.section != nil { + claimed(cp.section, into) + } + } + for _, child := range sec.children { + claimed(child, into) + } +} + +// planSection lowers a view's method into the section's content, then plans +// its child views as sections after the content, in declaration order. +func (m *migration) planSection(dp *docPlan, sec *sectionPlan) { + v := sec.v + dp.notes = append(dp.notes, v.Malformed...) + m.planMethod(dp, sec) + for _, p := range v.Paragraphs { + sec.content = append(sec.content, m.collaboratorParagraph(sec, p)) + } + for _, child := range v.Children { + title := strings.TrimSpace(child.Class.Name) + if title == "" { + title = "Section" + } + cs := §ionPlan{v: child, title: title, names: columnNames{}} + cs.name = sec.names.claim(title) + sec.children = append(sec.children, cs) + m.planSection(dp, cs) + } +} + +// planMethod walks the activity chain of the view's viewpoint method into +// content blocks; a view without a method contributes only its structure, +// unless its viewpoint's method tag names something that is not one. +func (m *migration) planMethod(dp *docPlan, sec *sectionPlan) { + v := sec.v + if v.Method == nil { + if v.MethodMalformed != "" { + sec.refused = "the viewpoint " + qualifiedName(v.Viewpoint) + "'s method is not migrated: " + v.MethodMalformed + m.report.Entries = append(m.report.Entries, *m.nodeEntry(v.Viewpoint, v.Viewpoint.Stereotype("Viewpoint"), Unmapped, sec.refused)) + } + return + } + steps, end := m.model.DocGenChain(v.Method) + if end != "" { + sec.refused = "the method " + qualifiedName(v.Method) + " is not migrated: " + end + m.report.Entries = append(m.report.Entries, *m.nodeEntry(v.Method, v.Method.DocGen(), Unmapped, sec.refused)) + return + } + c := &chain{m: m, dp: dp, sec: sec, active: []*sysmlv1.Element{v.Method}} + c.roots(v.Exposed, "the view "+qualifiedName(v.Class)+" exposes") + c.run(steps) +} + +// collaboratorParagraph plans a paragraph the View Editor attached to a view: +// its comment's body, verbatim, or a comment saying why not. +func (m *migration) collaboratorParagraph(sec *sectionPlan, p *sysmlv1.DocGenParagraph) *contentPlan { + cp := &contentPlan{kind: "Paragraph", node: p.Comment, label: "«Paragraph» Comment"} + if p.Image { + cp.label = "«Image Paragraph» Comment" + } + if p.Malformed != "" { + cp.refused = p.Malformed + } else if p.Comment != nil { + cp.text = commentBody(p.Comment) + } + switch { + case cp.refused != "": + case p.Image && cp.text == "": + cp.refused = "the attached image " + attachedFile(p.Comment) + " has no caption, and a Diagram shows a view, not an image file" + case p.Image: + cp.notes = append(cp.notes, "the attached image "+attachedFile(p.Comment)+" is not written, since a Diagram shows a view, not an image file; its caption stands as the paragraph") + case cp.text == "": + cp.refused = "the paragraph's comment has no body" + } + if cp.refused == "" { + cp.name = sec.names.claim("paragraph") + } + if cp.refused != "" { + m.report.Entries = append(m.report.Entries, *m.nodeEntry(p.Comment, p.Application, Unmapped, cp.refused)) + } + return cp +} + +// attachedFile names the file the tool attached to comment c, quoted; "an +// unnamed file" when the attachment names none. +func attachedFile(c *sysmlv1.Element) string { + for _, s := range c.Stereotypes { + if s.Name == "AttachedFile" && s.Namespace == sysmlv1.MagicDrawProfileNS { + if f := strings.TrimSpace(s.Tag("file")); f != "" { + return strconv.Quote(f) + } + } + } + return "an unnamed file" +} + +// strayParagraph reports a collaborator paragraph attached to no document view. +func (m *migration) strayParagraph(p *sysmlv1.DocGenParagraph) { + note := "the paragraph is attached to no view of a document" + if p.Malformed != "" { + note = p.Malformed + } + m.report.Entries = append(m.report.Entries, *m.nodeEntry(p.Comment, p.Application, Unmapped, note)) +} + +// chain lowers one activity chain: the elements the steps have collected so +// far, and the section its presentation nodes fill. +type chain struct { + m *migration + dp *docPlan + sec *sectionPlan + // ctx is the current elements; empty when the chain works on none. + ctx qx + // diagrams are the current elements that are diagrams, which no query + // names but an Image shows; each step transforms them as it does ctx. + diagrams []*sysmlv1.Diagram + // dropped names the filter that kept none of the diagrams, while none is current. + dropped string + // broken says why the current elements are unknown, once a step failed. + broken string + // notes are approximations the collected elements carry into what shows them. + notes []string + // active are the activities being lowered, outermost first, so a + // recursive call is refused rather than followed. + active []*sysmlv1.Element +} + +func (c *chain) sub() *chain { + s := *c + s.notes = append([]string(nil), c.notes...) + s.diagrams = append([]*sysmlv1.Diagram(nil), c.diagrams...) + s.active = append([]*sysmlv1.Element(nil), c.active...) + return &s +} + +// body is the activity or structured node a step's chain is read from, or +// why it cannot be entered. +func (c *chain) body(s *sysmlv1.DocGenStep) (*sysmlv1.Element, string) { + body := s.Node + if s.Behavior != nil { + body = s.Behavior + } + for _, a := range c.active { + if a == body { + return nil, "the " + kindOf(body) + " " + qualifiedName(body) + " calls itself, and a recursive section has no static spelling" + } + } + return body, "" +} + +func (c *chain) note(s string) { + if s != "" && !contains(c.notes, s) { + c.notes = append(c.notes, s) + } +} + +func contains(ss []string, s string) bool { + for _, x := range ss { + if x == s { + return true + } + } + return false +} + +// roots sets the chain's elements to refs, named by qualified name once each; +// a ref that resolves to no written element breaks the chain. +func (c *chain) roots(refs []sysmlv1.ElementRef, role string) { + c.ctx, c.diagrams, c.dropped, c.broken = qx{}, nil, "", "" + if len(refs) == 0 { + return + } + var names []string + for _, ref := range refs { + if d := c.m.model.Diagram(ref.ID); d != nil { + c.diagrams = appendDiagram(c.diagrams, d) + continue + } + if ref.Element == nil { + c.broken = role + " " + ref.ID + ", which " + c.m.unresolvedRef(ref) + return + } + name, why := c.m.namedRoot(ref, "element") + if why != "" { + c.broken = role + " " + kindOf(ref.Element) + " " + qualifiedName(ref.Element) + ", which is not migrated" + return + } + if !contains(names, name) { + names = append(names, name) + } + } + if len(names) > 0 { + c.ctx = qcall("Named", qstrs("qualifiedName", names...)) + } +} + +// appendDiagram adds d to ds unless it is there already. +func appendDiagram(ds []*sysmlv1.Diagram, d *sysmlv1.Diagram) []*sysmlv1.Diagram { + for _, x := range ds { + if x == d { + return ds + } + } + return append(ds, d) +} + +// keepDiagrams keeps the current diagrams keep admits, or the others when +// the step excludes instead of including. +func (c *chain) keepDiagrams(s *sysmlv1.DocGenStep, keep func(*sysmlv1.Diagram) bool) { + include := s.Application.Tag("include") != "false" + var kept []*sysmlv1.Diagram + for _, d := range c.diagrams { + if keep(d) == include { + kept = append(kept, d) + } + } + if len(kept) == 0 && len(c.diagrams) > 0 { + c.dropped = "«" + c.kind(s) + "» " + qualifiedName(s.Node) + } + c.diagrams = kept +} + +// empty reports whether the chain has no elements to work on. +func (c *chain) empty() bool { return c.ctx.op == "" && c.ctx.lit == "" } + +// idle reports whether a query step has nothing at all to transform. +func (c *chain) idle() bool { return c.broken != "" || (c.empty() && len(c.diagrams) == 0) } + +func (c *chain) run(steps []*sysmlv1.DocGenStep) { + for _, s := range steps { + c.step(s) + } +} + +// step lowers one node: a collect, filter or sort step transforms the +// elements; a presentation node adds a block; a group recurses. +func (c *chain) step(s *sysmlv1.DocGenStep) { + if s.Malformed != "" { + c.fail(s, s.Malformed) + return + } + if len(s.Targets) > 0 { + c.roots(s.Targets, "the node "+qualifiedName(s.Node)+" targets") + if c.broken != "" { + c.fail(s, c.broken) + return + } + } + switch s.Kind { + case "CollectOwnedElements": + c.collect(s, "Descendants") + case "CollectOwners": + c.collect(s, "Ancestors") + case "CollectByDirectedRelationshipStereotypes": + c.collectRelated(s) + case "FilterByMetaclasses": + c.filterTypes(s, "metaclasses") + case "FilterByStereotypes": + c.filterTypes(s, "stereotypes") + case "FilterByNames": + c.filterNames(s) + case "SortByName": + c.sort(s, "name") + case "SortByAttribute": + if attr, why := c.attribute(s, "desiredAttribute"); why != "" { + c.fail(s, why) + } else { + c.sort(s, attr) + } + case "RemoveDuplicates": + // Every query operation removes duplicates. + case "Union", "Intersection", "XOR": + c.join(s) + case "CollectionAndFilterGroup": + c.group(s, true) + case "StructuredQuery": + c.group(s, false) + case "TableStructure": + c.table(s) + case "BulletedList": + c.list(s) + case "Paragraph": + c.paragraph(s) + case "Image": + c.image(s) + case "Dynamic_View", "DynamicView": + c.dynamicView(s) + case "": + switch { + case s.Behavior != nil: + c.group(s, false) + case len(s.Targets) > 0: + // A node that only resets the targets. + default: + c.fail(s, "the node "+qualifiedName(s.Node)+" carries no DocGen stereotype") + } + default: + c.fail(s, "no query operation or content block stands for «"+s.Kind+"»") + } +} + +// fail records why a step is not migrated: a query step breaks the chain for +// what follows, a presentation node stands as a comment in the section. +func (c *chain) fail(s *sysmlv1.DocGenStep, why string) { + switch s.Kind { + case "TableStructure", "BulletedList", "Paragraph", "Image", "Dynamic_View", "DynamicView": + c.refuse(s, why) + default: + if c.broken == "" { + c.broken = "«" + c.kind(s) + "» " + qualifiedName(s.Node) + " is not migrated: " + why + } + c.m.report.Entries = append(c.m.report.Entries, *c.m.nodeEntry(s.Node, s.Application, Unmapped, why)) + } +} + +// kind names a step for a reader: its stereotype, else its metaclass. +func (c *chain) kind(s *sysmlv1.DocGenStep) string { + if s.Kind != "" { + return s.Kind + } + return s.Node.Type +} + +// refuse stands a comment in the section for a presentation node. +func (c *chain) refuse(s *sysmlv1.DocGenStep, why string) { + cp := &contentPlan{kind: c.kind(s), node: s.Node, label: "«" + c.kind(s) + "» " + s.Node.Type, refused: why} + c.sec.content = append(c.sec.content, cp) + c.m.report.Entries = append(c.m.report.Entries, *c.m.nodeEntry(s.Node, s.Application, Unmapped, why)) +} + +// ready reports whether a presentation node has elements to show, refusing it +// when the chain is broken or empty. +func (c *chain) ready(s *sysmlv1.DocGenStep) bool { + switch { + case c.broken != "": + c.refuse(s, "the elements it shows pass through "+c.broken) + return false + case c.empty() && len(c.diagrams) > 0: + c.refuse(s, "it shows no element: only diagrams are current, and a diagram is shown by an Image, not listed") + return false + case c.empty(): + c.refuse(s, "it shows no element: the view exposes nothing and the node targets nothing") + return false + } + return true +} + +// depth reads a collect step's depth: 0 or absent is unbounded. +func (c *chain) depth(s *sysmlv1.DocGenStep) (int, string) { + raw := strings.TrimSpace(s.Application.Tag("depth")) + if raw == "" { + return 0, "" + } + n, err := strconv.Atoi(raw) + if err != nil || n < 0 { + return 0, "the depth " + strconv.Quote(raw) + " is not a whole number" + } + return n, "" +} + +// collect lowers CollectOwnedElements and CollectOwners to a walk of the tree. +// A diagram owns no element; its owners join the elements, named. +func (c *chain) collect(s *sysmlv1.DocGenStep, op string) { + if c.idle() { + return + } + depth, why := c.depth(s) + if why != "" { + c.fail(s, why) + return + } + var owners []string + if op == "Ancestors" { + if owners, why = c.diagramOwners(depth); why != "" { + c.fail(s, why) + return + } + } + c.diagrams, c.dropped = nil, "" + var results []qx + if !c.empty() { + args := []qarg{qarg1("source", c.ctx)} + if depth > 0 { + args = append(args, qint1("maxDepth", depth)) + } + results = append(results, qcall(op, args...)) + } + if len(owners) > 0 { + results = append(results, qcall("Named", qstrs("qualifiedName", owners...))) + } + if len(results) == 0 { + c.ctx = qx{} + return + } + c.ctx = union(results) +} + +// diagramOwners names the owners of the current diagrams up to depth, all of +// them for 0, or says which owner is not migrated. The root Model, which is +// not written, ends the walk as it does for Ancestors. +func (c *chain) diagramOwners(depth int) (names []string, why string) { + for _, d := range c.diagrams { + owner := d.Owner + if owner == nil { + owner = d.Holder + } + for i := 0; owner != nil && (depth == 0 || i < depth); i, owner = i+1, owner.Parent { + if owner.Parent == nil && owner.Type == "Model" { + break + } + if !c.m.written(owner) { + return nil, "the diagram '" + d.Name + "' is owned by the " + kindOf(owner) + " " + qualifiedName(owner) + ", which is not migrated" + } + if name := c.m.plainName(owner); !contains(names, name) { + names = append(names, name) + } + } + } + return names, "" +} + +// collectRelated lowers CollectByDirectedRelationshipStereotypes to a walk of +// each supported relationship kind, united. +func (c *chain) collectRelated(s *sysmlv1.DocGenStep) { + if c.idle() { + return + } + // A migrated diagram is a view, which is the end of no relationship. + c.diagrams, c.dropped = nil, "" + if c.empty() { + return + } + refs := c.m.model.TagRefs(s.Application, "stereotypes") + if len(refs) == 0 { + c.fail(s, "it names no relationship stereotype") + return + } + depth, why := c.depth(s) + if why != "" { + c.fail(s, why) + return + } + dir := "outgoing" + if s.Application.Tag("directionOut") == "false" { + dir = "incoming" + } + var walks []qx + for _, ref := range refs { + kind, why := c.m.relationshipKind(ref) + if why != "" { + c.fail(s, why) + return + } + args := []qarg{qarg1("source", c.ctx), qarg1("relationshipKind", qstr(kind)), qarg1("direction", qstr(dir))} + if depth > 0 { + args = append(args, qint1("maxDepth", depth)) + } + walks = append(walks, qcall("RelatedElements", args...)) + } + c.ctx = union(walks) +} + +// relationshipKind names the RelatedElements kind a relationship stereotype +// walks, or why it has none. +func (m *migration) relationshipKind(ref sysmlv1.ElementRef) (kind, why string) { + s := m.model.StereotypeRef(ref.ID) + if s.Name == "" { + href := ref.ID + if ref.Element != nil && ref.Element.Href != "" { + href = ref.Element.Href + } + if doc, name, ok := standardHref(href); ok && doc == "SysML" { + s.Name = name + s.Namespace, _, _ = strings.Cut(href, "#") + } + } + switch { + case s.Name == "" && ref.Element != nil && ref.Element.Type == "Stereotype": + return "", "the relationship stereotype «" + ref.Element.Name + "» is the user's own: RelatedElements walks no user relationship" + case s.Name == "": + return "", "the relationship stereotype " + ref.ID + " is not described by the archive" + case !isStandardNamespace(s.Namespace): + return "", "the relationship stereotype «" + s.Name + "» is the user's own: RelatedElements walks no user relationship" + } + if kind, ok := relationKinds[s.Name]; ok { + return kind, "" + } + return "", "RelatedElements walks no «" + s.Name + "» relationship" +} + +// filterTypes lowers FilterByMetaclasses and FilterByStereotypes to the type +// filters tables use, kept or excepted. +func (c *chain) filterTypes(s *sysmlv1.DocGenStep, tag string) { + if c.idle() { + return + } + refs := c.m.model.TagRefs(s.Application, tag) + if len(refs) == 0 { + c.fail(s, "it names no "+strings.TrimSuffix(tag, "es")+"") + return + } + c.keepDiagrams(s, func(d *sysmlv1.Diagram) bool { + for _, ref := range refs { + if c.m.diagramOfType(ref, d) { + return true + } + } + return false + }) + if c.empty() { + return + } + l := &lowered{} + kept := c.m.typedRows(c.ctx, refs, true, false, l) + if l.refused != "" { + c.fail(s, l.refused) + return + } + for _, n := range l.notes { + c.note(n) + } + if tag == "stereotypes" && s.Application.Tag("considerDerived") == "false" { + c.note("elements of the stereotypes specializing " + strings.Join(c.labels(refs), ", ") + " are kept too") + } + if s.Application.Tag("include") == "false" { + c.ctx = qcall("Except", qarg1("source", c.ctx), qarg1("exclude", kept)) + return + } + c.ctx = kept +} + +// diagramOfType reports whether the element type ref admits diagram d: the +// UML Diagram metaclass or one above it, or the stereotype of its diagram kind. +func (m *migration) diagramOfType(ref sysmlv1.ElementRef, d *sysmlv1.Diagram) bool { + e := ref.Element + if e == nil { + return false + } + if doc, name, ok := standardHref(e.Href); ok { + return doc == "UML" && (name == "Element" || name == "NamedElement" || name == "Diagram") + } + if e.IsProxy() { + if s := m.model.StereotypeRef(e.ID); s.Name != "" { + return s.Name == d.Kind + } + return e.Name != "" && e.Name == d.Kind + } + return e.Type == "Stereotype" && e.Name == d.Kind +} + +// labels names the type refs as a reader knows them. +func (c *chain) labels(refs []sysmlv1.ElementRef) []string { + var out []string + for _, ref := range refs { + out = append(out, c.m.typeFilter(ref).label) + } + return out +} + +// filterNames lowers FilterByNames: each name is a whole-string regular +// expression, as DocGen matches them, and an element matching any is kept +// in its place, so the patterns become one alternation. +func (c *chain) filterNames(s *sysmlv1.DocGenStep) { + if c.idle() { + return + } + names := s.Application.Tags["names"] + if len(names) == 0 { + c.fail(s, "it names no name pattern") + return + } + var patterns []string + var compiled []*regexp.Regexp + for _, n := range names { + pattern := "^(?:" + n + ")$" + re, err := regexp.Compile(pattern) + if err != nil { + c.fail(s, "the name pattern "+strconv.Quote(n)+" is not a regular expression the query can match") + return + } + patterns = append(patterns, pattern) + compiled = append(compiled, re) + } + c.keepDiagrams(s, func(d *sysmlv1.Diagram) bool { + for _, re := range compiled { + if re.MatchString(d.Name) { + return true + } + } + return false + }) + if c.empty() { + return + } + kept := qcall("WhereName", qarg1("source", c.ctx), qarg1("operator", qstr("matches")), qarg1("value", qstr(strings.Join(patterns, "|")))) + if s.Application.Tag("include") == "false" { + c.ctx = qcall("Except", qarg1("source", c.ctx), qarg1("exclude", kept)) + return + } + c.ctx = kept +} + +// sort lowers a sort step to OrderBy over a query property. The diagrams sort +// by name the same way and keep their order for any other property. +func (c *chain) sort(s *sysmlv1.DocGenStep, property string) { + if c.idle() { + return + } + dir := "ascending" + if s.Application.Tag("reverse") == "true" { + dir = "descending" + } + if property == "name" { + slices.SortStableFunc(c.diagrams, func(a, b *sysmlv1.Diagram) int { + if dir == "descending" { + return strings.Compare(b.Name, a.Name) + } + return strings.Compare(a.Name, b.Name) + }) + } + if c.empty() { + return + } + c.ctx = qcall("OrderBy", qarg1("source", c.ctx), qarg1("property", qstr(property)), + qarg1("direction", qstr(dir)), qarg1("missing", qstr("last")), qarg1("multiple", qstr("first"))) +} + +// attribute reads a desiredAttribute tag as the query property it names. +func (c *chain) attribute(s *sysmlv1.DocGenStep, tag string) (property, why string) { + refs := c.m.model.TagRefs(s.Application, tag) + if len(refs) == 0 { + return "", "it names no attribute" + } + name := literalName(refs[0]) + if name == "" { + name = strings.TrimSpace(refs[0].ID) + } + switch name { + case "Name": + return "name", "" + case "Documentation": + return "documentation", "" + } + return "", "no query property stands for the attribute " + name +} + +// literalName is the name of an enumeration literal a tag refers to, read +// from the element or from the fragment of its href. +func literalName(ref sysmlv1.ElementRef) string { + if ref.Element != nil && ref.Element.Name != "" { + return ref.Element.Name + } + href := ref.ID + if ref.Element != nil && ref.Element.Href != "" { + href = ref.Element.Href + } + if _, name, ok := standardHref(href); ok { + return name + } + frag := href + if i := strings.LastIndexByte(frag, '#'); i >= 0 { + frag = frag[i+1:] + } + if i := strings.LastIndexByte(frag, '.'); i >= 0 && !strings.HasPrefix(frag, "_") { + return frag[i+1:] + } + return "" +} + +// join lowers a fork whose branches rejoin: each branch works on the current +// elements; a Union joins their results, other joins have no spelling. +func (c *chain) join(s *sysmlv1.DocGenStep) { + if c.broken != "" { + return + } + var results []qx + var diagrams []*sysmlv1.Diagram + dropped := c.dropped + for _, branch := range s.Branches { + sub := c.sub() + sub.run(branch) + if sub.broken != "" { + c.broken = sub.broken + return + } + for _, n := range sub.notes { + c.note(n) + } + if !sub.empty() { + results = append(results, sub.ctx) + } + for _, d := range sub.diagrams { + diagrams = appendDiagram(diagrams, d) + } + if sub.dropped != "" { + dropped = sub.dropped + } + } + if s.Kind != "Union" { + c.fail(s, "its branches rejoin by "+strings.ToLower(s.Kind)+", which only a Union spelling exists for") + return + } + c.diagrams, c.dropped = diagrams, dropped + if len(diagrams) > 0 { + c.dropped = "" + } + if len(results) == 0 { + c.ctx = qx{} + return + } + c.ctx = union(results) +} + +// group lowers a nested chain: a CollectionAndFilterGroup's result flows on, +// a StructuredQuery's or a plain call's does not. +func (c *chain) group(s *sysmlv1.DocGenStep, flows bool) { + body, why := c.body(s) + if why != "" { + c.fail(s, why) + return + } + steps, end := c.m.model.DocGenChain(body) + if end != "" { + c.fail(s, "its body "+qualifiedName(body)+" is not migrated: "+end) + return + } + if s.Application != nil && s.Application.Tag("loop") == "true" { + c.note("«" + c.kind(s) + "» " + qualifiedName(s.Node) + " loops over its elements one by one; the query works on them together") + } + sub := c.sub() + sub.active = append(sub.active, body) + sub.run(steps) + if !flows { + return + } + c.ctx, c.diagrams, c.broken = sub.ctx, sub.diagrams, sub.broken + for _, n := range sub.notes { + c.note(n) + } +} + +// caption is a presentation node's title: its titles tag, else its name. +func (c *chain) caption(s *sysmlv1.DocGenStep, fallback string) string { + for _, t := range s.Application.Tags["titles"] { + if t = strings.TrimSpace(t); t != "" { + return t + } + } + if t := strings.TrimSpace(s.Node.Name); t != "" { + return t + } + if s.Behavior != nil { + if t := strings.TrimSpace(s.Behavior.Name); t != "" { + return t + } + } + return fallback +} + +// title is a table's or figure's title as DocGen prints it: the given title +// between the node's titlePrefix and titleSuffix. +func (c *chain) title(s *sysmlv1.DocGenStep, title string) string { + return strings.TrimSpace(s.Application.Tag("titlePrefix") + title + s.Application.Tag("titleSuffix")) +} + +// captionText is the caption DocGen prints under a table's or figure's title: +// the i-th captions entry while showCaptions holds, else nothing. +func (c *chain) captionText(s *sysmlv1.DocGenStep, i int) string { + if s.Application.Tag("showCaptions") == "false" { + return "" + } + captions := s.Application.Tags["captions"] + if i >= len(captions) { + return "" + } + return commentText(captions[i]) +} + +// captionParagraph plans the Paragraph holding a block's caption, which a +// document prints under the block. +func (c *chain) captionParagraph(s *sysmlv1.DocGenStep, of, text string) { + cp := &contentPlan{kind: "Paragraph", node: s.Node, label: "«" + c.kind(s) + "» " + s.Node.Type, text: text, captionOf: of} + cp.name = c.sec.names.claim("paragraph") + c.sec.content = append(c.sec.content, cp) +} + +// block plans a query-backed block: its query name is reserved in the +// document's host, its member name in the section. +func (c *chain) block(s *sysmlv1.DocGenStep, kind, caption string, rows qx) *contentPlan { + cp := &contentPlan{kind: kind, node: s.Node, label: "«" + c.kind(s) + "» " + s.Node.Type, caption: caption, rows: rows} + cp.name = c.sec.names.claim(strings.ToLower(kind)) + cp.query = c.m.viewName(c.dp.host, c.dp.root.title+" "+caption+rowsSuffix) + cp.notes = append(cp.notes, c.notes...) + c.sec.content = append(c.sec.content, cp) + return cp +} + +// table lowers a TableStructure: the current elements projected by columns. +func (c *chain) table(s *sysmlv1.DocGenStep) { + if !c.ready(s) { + return + } + body := s.Node + if s.Behavior != nil { + body = s.Behavior + } + colSteps, end := c.m.model.DocGenChain(body) + if end != "" { + c.refuse(s, "its columns could not be read: "+end) + return + } + p := &projection{} + var notes []string + for _, col := range colSteps { + prop, expr, why := c.column(col) + switch { + case why != "": + notes = append(notes, "the column «"+c.kind(col)+"» "+qualifiedName(col.Node)+" is not written: "+why) + case prop != "": + p.property(prop) + default: + p.column(expr.name, qlit(expr.expression)) + } + } + if s.Application.Tag("includeDoc") == "true" { + p.property("documentation") + } + if p.empty() { + why := "none of its columns reads what a query can" + if len(notes) > 0 { + why += ": " + strings.Join(notes, "; ") + } + c.refuse(s, why) + return + } + project, projectNotes := p.build(c.ctx) + notes = append(notes, projectNotes...) + cp := c.block(s, "Table", c.title(s, c.caption(s, "Table")), project) + cp.notes = append(cp.notes, notes...) + if s.Application.Tag("loop") == "true" { + cp.notes = append(cp.notes, "the table loops over its elements one table each; one table lists them together") + } + if text := c.captionText(s, 0); text != "" { + c.captionParagraph(s, "Table", text) + } +} + +// columnExpr is a Column over a feature of the row's type. +type columnExpr struct { + name, expression string +} + +// column lowers one column node: a query property, or a Column reading a +// feature of the document's classifiers, or why neither. +func (c *chain) column(col *sysmlv1.DocGenStep) (prop string, expr columnExpr, why string) { + if col.Malformed != "" { + return "", expr, col.Malformed + } + if col.Application == nil { + return "", expr, "it carries no DocGen column stereotype" + } + if steps, _ := c.m.model.DocGenChain(col.Node); len(steps) > 0 { + return "", expr, "it collects elements before reading them, which a Column does not" + } + switch col.Kind { + case "TableAttributeColumn": + attr, why := c.attribute(col, "desiredAttribute") + return attr, expr, why + case "TablePropertyColumn": + refs := c.m.model.TagRefs(col.Application, "desiredProperty") + if len(refs) == 0 { + return "", expr, "it names no property" + } + key, f, why := c.m.columnKey(sysmlv1.Column{Kind: sysmlv1.ColumnFeature, Feature: refs[0], ID: refs[0].ID}, c.dp.host) + if why != "" { + return "", expr, why + } + c.m.expose(f, "a column of a document table reads it") + name := c.caption(col, key) + return "", columnExpr{name: name, expression: c.m.ref(f, c.dp.host) + " ?? \"\""}, "" + case "TableExpressionColumn": + e := strings.TrimSpace(col.Application.Tag("expression")) + if p, ok := queryProperties[e]; ok { + return p, expr, "" + } + return "", expr, "the expression " + strconv.Quote(e) + " is not a bare query property (name, documentation, qualifiedName, owner, id)" + } + return "", expr, "no Column stands for a «" + col.Kind + "»" +} + +// list lowers a BulletedList: the current elements' names, and documentation +// when asked, as a bullet or numbered list. +func (c *chain) list(s *sysmlv1.DocGenStep) { + if !c.ready(s) { + return + } + a := s.Application + if len(c.m.model.TagRefs(a, "stereotypeProperties")) > 0 { + c.refuse(s, "it lists stereotype properties, which the query cannot read") + return + } + var props []string + if a.Tag("showTargets") != "false" { + props = append(props, "name") + } + if a.Tag("includeDoc") == "true" { + props = append(props, "documentation") + } + if len(props) == 0 { + c.refuse(s, "it shows neither its elements nor their documentation") + return + } + rows := c.ctx + if a.Tag("sortElementsByName") == "true" { + rows = qcall("OrderBy", qarg1("source", rows), qarg1("property", qstr("name")), + qarg1("direction", qstr("ascending")), qarg1("missing", qstr("last")), qarg1("multiple", qstr("first"))) + } + style := "bullet" + if a.Tag("orderedList") == "true" { + style = "number" + } + cp := c.block(s, "List", c.caption(s, "List"), qcall("Project", qarg1("source", rows), qstrs("properties", props...))) + cp.style = style + if len(props) > 1 { + cp.notes = append(cp.notes, "each item's documentation follows its name") + } +} + +// paragraph lowers a Paragraph: its body verbatim, or the documentation of the +// current elements. +func (c *chain) paragraph(s *sysmlv1.DocGenStep) { + a := s.Application + if a.Tag("evaluateOcl") == "true" || a.Tag("tryOcl") == "true" { + c.refuse(s, "its body is evaluated as OCL, which no query evaluates") + return + } + if len(c.m.model.TagRefs(a, "stereotypeProperties")) > 0 { + c.refuse(s, "it reads stereotype properties, which the query cannot") + return + } + if body := commentText(a.Tag("body")); body != "" { + cp := &contentPlan{kind: "Paragraph", node: s.Node, label: "«Paragraph» " + s.Node.Type, text: body} + cp.name = c.sec.names.claim("paragraph") + c.sec.content = append(c.sec.content, cp) + return + } + if c.broken == "" && c.empty() && len(s.Targets) == 0 && a.Tag("body") == "" { + c.refuse(s, "it has no body and shows no element") + return + } + if !c.ready(s) { + return + } + prop := "documentation" + if len(c.m.model.TagRefs(a, "desiredAttribute")) > 0 { + attr, why := c.attribute(s, "desiredAttribute") + if why != "" { + c.refuse(s, why) + return + } + prop = attr + } + c.block(s, "Paragraph", c.caption(s, "Paragraph"), qcall("Project", qarg1("source", c.ctx), qstrs("properties", prop))) +} + +// image lowers an Image: one Diagram block per diagram among the current +// elements, showing its migrated view, captioned by the diagram's title and +// followed by its caption paragraph when DocGen shows captions. +func (c *chain) image(s *sysmlv1.DocGenStep) { + if c.broken != "" { + c.refuse(s, "the diagrams it shows pass through "+c.broken) + return + } + if len(c.diagrams) == 0 && c.dropped != "" { + c.m.report.Entries = append(c.m.report.Entries, *c.m.nodeEntry(s.Node, s.Application, Mapped, + "it draws nothing: "+c.dropped+" keeps none of the diagrams the view exposes or the node targets")) + return + } + if len(c.diagrams) == 0 { + c.refuse(s, "it shows no diagram: only a diagram the view exposes or the node targets directly has a view to show") + return + } + titles := s.Application.Tags["titles"] + for i, d := range c.diagrams { + v := c.m.viewOf[d] + if v == nil || !v.placed { + c.refuse(s, "the Diagram '"+d.Name+"' is not written as a view") + continue + } + if rendering(d) == textualRendering { + c.refuse(s, "the "+diagramKind(d)+" '"+d.Name+"' is a view rendered as textual notation, which a document does not draw") + continue + } + def, _, why := c.m.viewSteps(v) + if why != "" { + c.refuse(s, why) + continue + } + cp := &contentPlan{kind: "Diagram", node: s.Node, label: "«Image» " + s.Node.Type, source: v} + if def != nil { + cp.anchor = c.dp.anchor(def) + } + title := strings.TrimSpace(d.Name) + if i < len(titles) && strings.TrimSpace(titles[i]) != "" { + title = strings.TrimSpace(titles[i]) + } + cp.caption = c.title(s, title) + cp.name = c.sec.names.claim("diagram") + c.sec.content = append(c.sec.content, cp) + if text := c.captionText(s, i); text != "" { + c.captionParagraph(s, "Diagram", text) + } + } +} + +// dynamicView lowers a Dynamic View node: a Section titled after it, holding +// what its own chain produces over the current elements. +func (c *chain) dynamicView(s *sysmlv1.DocGenStep) { + a := s.Application + title := strings.TrimSpace(a.Tag("title")) + if title == "" { + title = c.caption(s, "Section") + } + title = a.Tag("titlePrefix") + title + a.Tag("titleSuffix") + sec := §ionPlan{title: title, names: columnNames{}} + sec.name = c.sec.names.claim(title) + cp := &contentPlan{kind: "Section", node: s.Node, label: "«Dynamic View» " + s.Node.Type, section: sec, name: sec.name} + c.sec.content = append(c.sec.content, cp) + body, why := c.body(s) + if why == "" { + var steps []*sysmlv1.DocGenStep + steps, why = c.m.model.DocGenChain(body) + if why != "" { + why = "its body " + qualifiedName(body) + " is not migrated: " + why + } else { + sub := c.sub() + sub.sec = sec + sub.active = append(sub.active, body) + if a.Tag("loop") == "true" { + sub.note("the section loops over its elements one section each; one section shows them together") + } + sub.run(steps) + return + } + } + sec.refused = why + c.m.report.Entries = append(c.m.report.Entries, *c.m.nodeEntry(s.Node, s.Application, Unmapped, sec.refused)) +} + +// writeDocument writes a planned document: its queries first, then the +// Document definition holding its sections and blocks. +func (m *migration) writeDocument(dp *docPlan) { + m.writeQueries(dp.root, m.queryPrefix(dp.host)) + var notes []string + target := m.qualified(append(m.segments(dp.host), dp.root.name)) + m.inside(blockNames("Document", dp.root.names), func() { + m.w.block("part def "+writeName(dp.root.name)+" :> "+m.queryPrefix(dp.host)+"Document", func() { + m.w.line("attribute redefines title = " + stringLiteral(dp.root.title) + ";") + for _, a := range dp.anchors { + m.w.line("ref " + writeName(a.name) + " : " + m.memberRef(a.def, dp.host) + ";") + } + notes = m.writeSectionBody(dp, dp.root, target) + }) + }) + notes = append(notes, dp.notes...) + note := "the «Document» is written as a Document definition of " + strconv.Itoa(len(dp.root.children)) + " section(s)" + note = joinNotes(note, strings.Join(notes, "; ")) + verdict := Mapped + if len(notes) > 0 { + verdict = Approximated + } + m.report.Entries = append(m.report.Entries, *m.docEntry(dp.d, verdict, "part def "+target, note)) + for _, cp := range m.blocks(dp.root) { + if cp.refused != "" { + continue + } + m.report.Entries = append(m.report.Entries, *m.blockEntry(cp)) + } +} + +// writeQueries writes the row queries of every query-backed block under sec. +func (m *migration) writeQueries(sec *sectionPlan, prefix string) { + for _, cp := range m.blocks(sec) { + if cp.query != "" && cp.refused == "" { + m.writeQueryDef(cp.query, prefix, cp.rows) + } + } +} + +// blocks lists every block under sec, nested Dynamic View sections included. +func (m *migration) blocks(sec *sectionPlan) []*contentPlan { + var out []*contentPlan + for _, cp := range sec.content { + out = append(out, cp) + if cp.section != nil { + out = append(out, m.blocks(cp.section)...) + } + } + for _, child := range sec.children { + out = append(out, m.blocks(child)...) + } + return out +} + +// libraryMembers lists the members each DocumentQueries block inherits, and +// the calc a query-backed one holds; a reference inside it steers clear of them. +var libraryMembers = map[string][]string{ + "Document": {"title"}, + "Section": {"title"}, + "Paragraph": {"text", "values"}, + "Table": {"caption", "groupBy", "rows"}, + "List": {"style", "items"}, + "Diagram": {"caption", "kind", "direction", "palette", "source"}, +} + +// blockNames is the member set of a block of the library kind whose own +// members are named own. +func blockNames(kind string, own columnNames) columnNames { + names := columnNames{} + for n := range own { + names[n] = true + } + for _, n := range libraryMembers[kind] { + names[n] = true + } + return names +} + +// blockPart writes a part usage of the library kind named name holding body, +// with the names it declares in scope for the references body writes. +func (m *migration) blockPart(host *sysmlv1.Element, name, kind string, own columnNames, body func()) { + m.inside(blockNames(kind, own), func() { + m.w.block("part "+writeName(name)+" : "+m.queryPrefix(host)+kind, body) + }) +} + +// writeSectionBody writes a section's blocks then its child sections under +// path, and returns the notes its blocks carry. +func (m *migration) writeSectionBody(dp *docPlan, sec *sectionPlan, path string) []string { + var notes []string + if sec.refused != "" { + m.w.lines(commentLines("not migrated: " + sec.refused)) + } + for _, cp := range sec.content { + notes = append(notes, m.writeBlock(dp, cp, path)...) + } + for _, child := range sec.children { + m.blockPart(dp.host, child.name, "Section", child.names, func() { + m.w.line("attribute redefines title = " + stringLiteral(child.title) + ";") + notes = append(notes, m.writeSectionBody(dp, child, path+"::"+writeName(child.name))...) + }) + } + return notes +} + +// writeBlock writes one block, or the comment standing for a refused node. +func (m *migration) writeBlock(dp *docPlan, cp *contentPlan, path string) []string { + if cp.refused != "" { + m.w.lines(commentLines("not migrated: " + cp.label + " '" + nodeLabel(cp.node) + "' — " + cp.refused)) + return nil + } + cp.target = path + "::" + writeName(cp.name) + switch cp.kind { + case "Section": + var notes []string + m.blockPart(dp.host, cp.name, "Section", cp.section.names, func() { + m.w.line("attribute redefines title = " + stringLiteral(cp.section.title) + ";") + notes = m.writeSectionBody(dp, cp.section, cp.target) + }) + return notes + case "Paragraph": + m.blockPart(dp.host, cp.name, "Paragraph", nil, func() { + if cp.query != "" { + m.w.line("calc values : " + m.siblingRef(dp.host, cp.query) + ";") + } else { + m.w.line("attribute redefines text = " + stringLiteral(cp.text) + ";") + } + }) + case "Table": + m.blockPart(dp.host, cp.name, "Table", nil, func() { + m.w.line("attribute redefines caption = " + stringLiteral(cp.caption) + ";") + m.w.line("calc rows : " + m.siblingRef(dp.host, cp.query) + ";") + }) + case "List": + m.blockPart(dp.host, cp.name, "List", nil, func() { + m.w.line("attribute redefines style = " + stringLiteral(cp.style) + ";") + m.w.line("calc items : " + m.siblingRef(dp.host, cp.query) + ";") + }) + case "Diagram": + m.blockPart(dp.host, cp.name, "Diagram", nil, func() { + m.w.line("attribute redefines caption = " + stringLiteral(cp.caption) + ";") + m.w.line("ref redefines source = " + m.diagramSource(dp, cp) + ";") + }) + } + return cp.notes +} + +// diagramSource names a Diagram block's view: by name where that reaches it, +// else by the feature chain from its anchor or the first usage under its package. +func (m *migration) diagramSource(dp *docPlan, cp *contentPlan) string { + _, steps, _ := m.viewSteps(cp.source) + var b strings.Builder + switch { + case cp.anchor != nil: + b.WriteString(writeName(cp.anchor.name)) + case len(steps) == 1: + return m.viewRef(cp.source, dp.host) + default: + b.WriteString(m.ref(steps[0].elem, dp.host)) + steps = steps[1:] + } + for _, s := range steps { + b.WriteString(".") + b.WriteString(writeName(s.name)) + } + return b.String() +} + +// nodeLabel names a node for a comment: its name, else its metaclass. +func nodeLabel(n *sysmlv1.Element) string { + if n == nil { + return "" + } + if n.Name != "" { + return n.Name + } + return "<" + n.Type + ">" +} + +// docEntry is the report row of a DocGen document, keyed by its application. +func (m *migration) docEntry(d *sysmlv1.DocGenDocument, v Verdict, target, note string) *Entry { + id := d.Class.ID + if d.Application != nil { + id = d.Application.ID + } + return &Entry{ID: id, Kind: "«Document» Class", Name: qualifiedName(d.Class), Target: target, Verdict: v, Note: note} +} + +// nodeEntry is the report row of a DocGen node or paragraph comment, keyed by +// its stereotype application so the node's own row is left alone. +func (m *migration) nodeEntry(n *sysmlv1.Element, app *sysmlv1.Stereotype, v Verdict, note string) *Entry { + e := &Entry{Verdict: v, Note: note} + if n != nil { + e.ID, e.Kind, e.Name = n.ID, kindOf(n), qualifiedName(n) + } + if app != nil { + e.ID = app.ID + if n != nil { + e.Kind = "«" + app.Name + "» " + n.Type + } else { + e.Kind = "«" + app.Name + "»" + } + } + return e +} + +// blockEntry is the report row of a written block: its node, mapped to the +// member of the Document standing for it. +func (m *migration) blockEntry(cp *contentPlan) *Entry { + verdict := Mapped + if len(cp.notes) > 0 { + verdict = Approximated + } + var app *sysmlv1.Stereotype + if cp.node != nil { + app = cp.node.DocGen() + } + e := m.nodeEntry(cp.node, app, verdict, strings.Join(cp.notes, "; ")) + e.Target = "part " + cp.target + if cp.query != "" { + e.Note = joinNotes("its rows are the query "+writeName(cp.query), e.Note) + } + if cp.captionOf != "" { + e.Note = joinNotes("the paragraph is the "+cp.captionOf+"'s caption", e.Note) + } + return e +} diff --git a/internal/translate/migrate/migrate.go b/internal/translate/migrate/migrate.go index bb17019eeb..fb5ec14da0 100644 --- a/internal/translate/migrate/migrate.go +++ b/internal/translate/migrate/migrate.go @@ -151,6 +151,7 @@ func FromModelOptions(name string, model *sysmlv1.Model, opts Options) *Result { opaque: map[*sysmlv1.Element]*opaqueResult{}, viewOf: map[*sysmlv1.Diagram]*view{}, hosted: map[*sysmlv1.Element][]*view{}, + tableOf: map[*sysmlv1.Table]*tableDoc{}, buried: map[*sysmlv1.Element]bool{}, actors: map[*sysmlv1.Element]*actorLink{}, monteCarlo: map[*sysmlv1.Element]*monteCarloCase{}, @@ -188,6 +189,9 @@ func FromModelOptions(name string, model *sysmlv1.Model, opts Options) *Result { for _, root := range model.Roots { m.root(root) } + for _, extra := range m.extras[nil] { + extra() + } m.views(nil) m.flushFlows() m.placeholderEnds() @@ -265,6 +269,8 @@ type migration struct { // viewOf plans each diagram's view; hosted lists the views each body opens with. viewOf map[*sysmlv1.Diagram]*view hosted map[*sysmlv1.Element][]*view + // tableOf plans each table definition's Document beside its diagram's view. + tableOf map[*sysmlv1.Table]*tableDoc // buried memoizes isBuried: whether an ancestor left out of the document takes e with it. buried map[*sysmlv1.Element]bool // flows lists the item flows each connector realizes. @@ -275,6 +281,9 @@ type migration struct { unplaced map[*sysmlv1.Element]*placement // taken holds synthesized names reserved in a body, by owner. taken map[*sysmlv1.Element]map[string]bool + // opened holds the member names of each synthesized declaration being + // written, outermost first; a reference written inside them avoids those names. + opened []columnNames // parallel names the parallel state each region of an orthogonal state is // written in; a lone region is written inline and has no name of its own. parallel map[*sysmlv1.Element]string @@ -1811,6 +1820,10 @@ func (m *migration) feature(p *sysmlv1.Element) { note = joinNotes(note, dnote) b.WriteString(dir) prefix, note = m.featureModifiers(&b, p, ownerCat, kw, dir, prefix, note) + tm := m.typeModifier(p) + if tm != nil && tm.ref { + prefix = "ref " + } b.WriteString(prefix) b.WriteString(kw) name := m.nameOf(p) @@ -1831,8 +1844,13 @@ func (m *migration) feature(p *sysmlv1.Element) { ind, indNote := m.typingIndividual(p, kw) m.featureTyping(&b, p, ind, payload, typ) mult, mnote := m.multiplicity(p) - b.WriteString(mult + collection(p)) - note = joinNotes(note, mnote) + if shape := tm.shape(); shape != "" { + mult, mnote = shape, "" + } else { + mult += collection(p) + } + b.WriteString(mult) + note = joinNotes(joinNotes(note, mnote), tm.note()) note = m.featureRedefinitions(&b, p, note) note = m.featureShadow(&b, p, kw, note) note = joinNotes(note, m.dangling(p, "redefinedProperty", "subsettedProperty")) @@ -3222,7 +3240,7 @@ func (m *migration) stereotypeAnnotations(e *sysmlv1.Element) { func (m *migration) annotated(e *sysmlv1.Element) []*sysmlv1.Stereotype { var out []*sysmlv1.Stereotype for _, s := range e.Stereotypes { - if m.isConstraintParameterMarker(e, s) || m.isPropertyKindMarker(e, s) || isSimulationConfig(s) { + if m.isConstraintParameterMarker(e, s) || m.isPropertyKindMarker(e, s) || isSimulationConfig(s) || m.writesTypeModifier(e, s) { continue } out = append(out, s) @@ -3281,7 +3299,7 @@ func (m *migration) stereotypeComments(e *sysmlv1.Element) { text += ": " + strings.Join(tags, "; ") } m.w.lines(commentLines(text)) - if s.Definition == nil && toolProfile(s.Namespace) == "" { + if s.Definition == nil && toolProfile(s.Namespace) == "" && !readsTypeModifier(e, s) && !sysmlv1.IsDocGenProfile(s.Namespace) { if byNamespace[s.Namespace] == nil { outside = append(outside, s.Namespace) } diff --git a/internal/translate/migrate/names.go b/internal/translate/migrate/names.go index 2c0c933b1f..5d5e07c67a 100644 --- a/internal/translate/migrate/names.go +++ b/internal/translate/migrate/names.go @@ -114,10 +114,12 @@ func (m *migration) segments(e *sysmlv1.Element) []string { return segs } -// segment is one step of a qualified name; feature marks a step that is a usage. +// segment is one step of a qualified name; feature marks a step that is a +// usage, elem the element it names, nil for a step no element stands for. type segment struct { name string feature bool + elem *sysmlv1.Element } // path returns the segments of e's qualified name, see segments. A behavior @@ -145,7 +147,7 @@ func (m *migration) path(e *sysmlv1.Element) []segment { if op := m.methodOf[cur]; op != nil { cur = op } - segs = append([]segment{{name: m.nameFor(cur), feature: m.isUsage(cur)}}, segs...) + segs = append([]segment{{name: m.nameFor(cur), feature: m.isUsage(cur), elem: cur}}, segs...) } return segs } @@ -175,6 +177,32 @@ func scopeChain(scope *sysmlv1.Element) []*sysmlv1.Element { return chain } +// inside writes body inside a synthesized declaration whose members are named +// names: a reference written there resolves through those members first, so +// one naming a member steers clear of them. +func (m *migration) inside(names columnNames, body func()) { + m.opened = append(m.opened, names) + body() + m.opened = m.opened[:len(m.opened)-1] +} + +// hidden reports whether a synthesized declaration being written declares a +// member named name, which hides the name outside it. +func (m *migration) hidden(name string) bool { + for _, names := range m.opened { + if names[name] { + return true + } + } + return false +} + +// siblingRef writes a reference to a synthesized declaration named name that +// is written beside host's members, from inside whatever is being written. +func (m *migration) siblingRef(host *sysmlv1.Element, name string) string { + return m.refMember(host, name, append(m.path(host), segment{name: name}), host, false) +} + // ref writes a reference to target from inside scope's body (nil for the top // level): the shortest qualified name that resolves there, which is the simple // name when target is a member of an enclosing scope no nearer scope shadows, @@ -206,7 +234,7 @@ func (m *migration) refMember(owner *sysmlv1.Element, name string, path []segmen if s != owner { continue } - shadowed := false + shadowed := m.hidden(name) for _, inner := range chain[:i] { if name != "" && m.nameTaken(inner, name) { shadowed = true @@ -219,6 +247,9 @@ func (m *migration) refMember(owner *sysmlv1.Element, name string, path []segmen } if owner == nil && len(chain) > 0 { // A top-level declaration: visible everywhere unless shadowed. + if m.hidden(name) { + return m.qualifiedFrom(path, chain, chained) + } for _, inner := range chain { if name != "" && m.nameTaken(inner, name) { return m.qualifiedFrom(path, chain, chained) @@ -246,11 +277,8 @@ func namespaces(segs []string) []segment { // is referred to; an import names them by qualified name alone. func (m *migration) qualifiedFrom(path []segment, chain []*sysmlv1.Element, chained bool) string { var b strings.Builder - for _, s := range chain { - if m.nameTaken(s, path[0].name) { - b.WriteString("$::") - break - } + if m.hidden(path[0].name) || m.shadows(chain, path[0].name) { + b.WriteString("$::") } for i, s := range path { switch { diff --git a/internal/translate/migrate/projection_internal_test.go b/internal/translate/migrate/projection_internal_test.go new file mode 100644 index 0000000000..4b9e7f1799 --- /dev/null +++ b/internal/translate/migrate/projection_internal_test.go @@ -0,0 +1,84 @@ +package migrate + +import ( + "slices" + "strings" + "testing" +) + +func projectText(p *projection) (string, []string) { + project, notes := p.build(qlit("rows")) + return strings.Join(project.lines(""), "\n"), notes +} + +// Properties ahead of the computed columns are written as they stand; a +// property behind a computed column is written first and noted. +func TestProjectionWritesPropertiesFirst(t *testing.T) { + plain := &projection{} + plain.property("name") + plain.property("qualifiedName") + plain.column("mass", qlit("Pump::mass")) + got, notes := projectText(plain) + want := `Project( + source = rows, + properties = ("name", "qualifiedName"), + columns = ( + Column(name = "mass", expression = Pump::mass)))` + if got != want || len(notes) != 0 { + t.Errorf("plain projection = \n%s\nnotes %v, want\n%s", got, notes, want) + } + + pure := &projection{} + pure.property("name") + if got, _ := projectText(pure); got != `Project(source = rows, properties = ("name"))` { + t.Errorf("pure projection = %s", got) + } + + mixed := &projection{} + mixed.property("name") + mixed.column("mass", qlit("Pump::mass")) + mixed.property("owner") + mixed.column("flow", qlit("Pump::flow")) + got, notes = projectText(mixed) + want = `Project( + source = rows, + properties = ("name", "owner"), + columns = ( + Column(name = "mass", expression = Pump::mass), + Column(name = "flow", expression = Pump::flow)))` + wantNotes := []string{"Project lists its properties first: name, owner precede the other columns"} + if got != want || !slices.Equal(notes, wantNotes) { + t.Errorf("mixed projection = \n%s\nnotes %v, want\n%s\nnotes %v", got, notes, want, wantNotes) + } +} + +// A computed caption that repeats a property, listed before or after it, or +// another computed caption, is suffixed; a repeated property is listed once. +func TestProjectionClaimsPropertyNamesFirst(t *testing.T) { + p := &projection{} + p.column("name", qlit("Pump::label")) + if !p.property("name") || p.property("name") { + t.Fatal("a property is listed exactly once") + } + p.column("mass", qlit("Pump::mass")) + p.column("mass", qlit("Pump::dryMass")) + got, notes := projectText(p) + want := `Project( + source = rows, + properties = ("name"), + columns = ( + Column(name = "name 2", expression = Pump::label), + Column(name = "mass", expression = Pump::mass), + Column(name = "mass 2", expression = Pump::dryMass)))` + if got != want { + t.Errorf("projection = \n%s\nwant\n%s", got, want) + } + wantNotes := []string{ + "the column name is written as name 2: column names are unique", + "the column mass is written as mass 2: column names are unique", + "Project lists its properties first: name precede the other columns", + } + if !slices.Equal(notes, wantNotes) { + t.Errorf("notes = %v, want %v", notes, wantNotes) + } +} diff --git a/internal/translate/migrate/query.go b/internal/translate/migrate/query.go new file mode 100644 index 0000000000..9dab014188 --- /dev/null +++ b/internal/translate/migrate/query.go @@ -0,0 +1,150 @@ +package migrate + +import ( + "strconv" + "strings" + + "github.com/Open-MBEE/OpenSysML/internal/translate/xmi/sysmlv1" +) + +// qx is a DocumentQueries expression to write: a library operation applied +// to named arguments, or a literal written as is. +type qx struct { + op string + args []qarg + lit string +} + +// qarg is one named argument: a single value or a list. +type qarg struct { + name string + val qx + list []qx + many bool +} + +func qlit(s string) qx { return qx{lit: s} } +func qstr(s string) qx { return qlit(stringLiteral(s)) } +func qint(n int) qx { return qlit(strconv.Itoa(n)) } +func qcall(op string, args ...qarg) qx { + return qx{op: op, args: args} +} +func qarg1(name string, v qx) qarg { return qarg{name: name, val: v} } +func qlist(name string, vs ...qx) qarg { return qarg{name: name, list: vs, many: true} } + +// qstrs writes a list argument of string literals. +func qstrs(name string, ss ...string) qarg { + vs := make([]qx, len(ss)) + for i, s := range ss { + vs[i] = qstr(s) + } + return qlist(name, vs...) +} + +// isCall reports whether the expression is an operation, not a literal. +func (q qx) isCall() bool { return q.op != "" } + +// flat reports whether the expression and its arguments hold no nested +// operation, so it fits on one line. +func (q qx) flat() bool { + for _, a := range q.args { + if a.val.isCall() { + return false + } + for _, v := range a.list { + if v.isCall() { + return false + } + } + } + return true +} + +// lines writes the expression, its operations qualified by prefix, as lines +// to indent one level deeper for each nested argument. +func (q qx) lines(prefix string) []string { + if !q.isCall() { + return []string{q.lit} + } + if q.flat() { + parts := make([]string, len(q.args)) + for i, a := range q.args { + parts[i] = a.name + " = " + a.flatValue(prefix) + } + return []string{prefix + q.op + "(" + strings.Join(parts, ", ") + ")"} + } + out := []string{prefix + q.op + "("} + for i, a := range q.args { + ls := a.lines(prefix) + ls[0] = a.name + " = " + ls[0] + if i < len(q.args)-1 { + ls[len(ls)-1] += "," + } + out = append(out, indentLines(ls)...) + } + out[len(out)-1] += ")" + return out +} + +// flatValue writes an argument whose value holds no operation. +func (a qarg) flatValue(prefix string) string { + if !a.many { + return a.val.lines(prefix)[0] + } + parts := make([]string, len(a.list)) + for i, v := range a.list { + parts[i] = v.lines(prefix)[0] + } + return "(" + strings.Join(parts, ", ") + ")" +} + +// lines writes an argument's value, a list as one element per line. +func (a qarg) lines(prefix string) []string { + if !a.many { + return a.val.lines(prefix) + } + flat := true + for _, v := range a.list { + if v.isCall() { + flat = false + } + } + if flat { + return []string{a.flatValue(prefix)} + } + out := []string{"("} + for i, v := range a.list { + ls := v.lines(prefix) + if i < len(a.list)-1 { + ls[len(ls)-1] += "," + } + out = append(out, indentLines(ls)...) + } + out[len(out)-1] += ")" + return out +} + +func indentLines(ls []string) []string { + out := make([]string, len(ls)) + for i, l := range ls { + out[i] = " " + l + } + return out +} + +// queryPrefix is how the DocumentQueries library is named from inside host: +// from the global namespace when a member of host's scopes, or of a +// synthesized declaration being written, shadows it. +func (m *migration) queryPrefix(host *sysmlv1.Element) string { + if m.hidden("DocumentQueries") || m.shadowsLibrary("DocumentQueries", host) { + return "$::DocumentQueries::" + } + return "DocumentQueries::" +} + +// writeQueryDef writes a query definition returning the expression. +func (m *migration) writeQueryDef(name string, prefix string, body qx) { + m.w.block("calc def "+writeName(name)+" :> "+prefix+"Query", func() { + m.w.lines(body.lines(prefix)) + }) +} diff --git a/internal/translate/migrate/tables.go b/internal/translate/migrate/tables.go new file mode 100644 index 0000000000..dcdf5a5e6b --- /dev/null +++ b/internal/translate/migrate/tables.go @@ -0,0 +1,851 @@ +package migrate + +import ( + "fmt" + "slices" + "strings" + + "github.com/Open-MBEE/OpenSysML/internal/translate/xmi/sysmlv1" +) + +// tableDoc is one table, matrix or relation map definition planned as a +// Document holding a Table, beside its diagram's view in the view's host. +type tableDoc struct { + t *sysmlv1.Table + v *view + // doc and query are the names reserved in the host for the Document + // definition and the row query; title is the diagram's name, trimmed. + doc, query, title string + // l is the definition lowered to its query, set by lowerTable. + l *lowered +} + +// written reports whether the Document is written, for the view to expose. +func (td *tableDoc) written() bool { + return td.l != nil && td.l.refused == "" +} + +// lowered is a table definition lowered to a query: the row expression, the +// notes that make it approximate, and why it was refused when it was. +type lowered struct { + rows qx + notes []string + refused string +} + +func (l *lowered) note(s string) { + if s != "" { + l.notes = append(l.notes, s) + } +} + +func (l *lowered) refuse(why string) { + if l.refused == "" { + l.refused = why + } +} + +// documentSuffix and rowsSuffix name the Document and query after the diagram. +const ( + documentSuffix = " Document" + rowsSuffix = " Rows" +) + +// planTables pairs every table definition with its diagram's view and reserves +// its names, once views are planned so the names account for each other. +func (m *migration) planTables() { + for _, t := range m.model.Tables { + if t.Diagram == nil { + continue + } + v := m.viewOf[t.Diagram] + if v == nil || !v.placed { + continue + } + name := strings.TrimSpace(t.Diagram.Name) + if name == "" { + name = "diagram" + } + td := &tableDoc{t: t, v: v, title: name} + td.doc = m.viewName(v.host, name+documentSuffix) + td.query = m.viewName(v.host, name+rowsSuffix) + m.tableOf[t] = td + v.tables = append(v.tables, td) + for _, c := range t.Columns { + if _, f, why := m.columnKey(c, v.host); f != nil && why == "" && !c.Hidden { + m.expose(f, "a column of the table '"+name+"' reads it") + } + } + } +} + +// tableEntry is the report row of a table definition, keyed by the stereotype +// application that defines it and named as its diagram is. +func (m *migration) tableEntry(t *sysmlv1.Table, v Verdict, target, note string) *Entry { + e := m.diagramEntry(t.Diagram, v, target, note) + e.ID = t.Application.ID + e.Kind = "«" + string(t.Kind) + "» Diagram" + return e +} + +// unplacedTables reports the table definitions no view was planned for: those +// naming no diagram, or a diagram nothing written can hold. +func (m *migration) unplacedTables() { + for _, t := range m.model.Tables { + switch { + case t.Diagram == nil: + e := &Entry{ID: t.Application.ID, Kind: "«" + string(t.Kind) + "»", Name: "<Diagram>", Verdict: Unmapped, + Note: "base_Diagram " + t.DiagramID + " names no diagram of the document"} + m.w.lines(commentLines("not migrated: " + e.Kind + " " + t.DiagramID + " — " + e.Note)) + m.report.Entries = append(m.report.Entries, *e) + case m.tableOf[t] == nil: + v := m.viewOf[t.Diagram] + e := m.tableEntry(t, Unmapped, "", "its diagram is not written as a view: "+v.entry.Note) + m.report.Entries = append(m.report.Entries, *e) + } + } +} + +// writeTable writes a table definition as a query and a Document holding one +// Table over it, or as a comment when the definition has no query form. +func (m *migration) writeTable(td *tableDoc) { + t, host, l := td.t, td.v.host, td.l + prefix := m.queryPrefix(host) + kind := string(t.Kind) + if l.refused != "" { + note := joinNotes(l.refused, strings.Join(l.notes, "; ")) + m.w.lines(commentLines("not migrated: «" + kind + "» '" + t.Diagram.Name + "' — " + note)) + m.report.Entries = append(m.report.Entries, *m.tableEntry(t, Unmapped, "", note)) + return + } + m.writeQueryDef(td.query, prefix, l.rows) + m.inside(blockNames("Document", columnNames{"rows": true}), func() { + m.w.block("part def "+writeName(td.doc)+" :> "+m.queryPrefix(host)+"Document", func() { + m.w.line("attribute redefines title = " + stringLiteral(td.title) + ";") + m.blockPart(host, "rows", "Table", nil, func() { + m.w.line("attribute redefines caption = " + stringLiteral(td.title) + ";") + m.w.line("calc rows : " + m.siblingRef(host, td.query) + ";") + }) + }) + }) + note := "the «" + kind + "» is written as a Document holding a Table over the query " + writeName(td.query) + note = joinNotes(note, strings.Join(l.notes, "; ")) + verdict := Mapped + if len(l.notes) > 0 { + verdict = Approximated + } + target := m.qualified(append(m.segments(host), td.doc)) + m.report.Entries = append(m.report.Entries, *m.tableEntry(t, verdict, "part def "+target, note)) +} + +// lowerTable lowers a table definition of any kind to its row query, ahead of +// writing, so the view knows whether a Document follows it. +func (m *migration) lowerTable(td *tableDoc) { + t, host := td.t, td.v.host + l := &lowered{} + td.l = l + if len(t.Malformed) > 0 { + l.refuse(strings.Join(t.Malformed, "; ")) + } + switch t.Kind { + case sysmlv1.InstanceTable, sysmlv1.DiagramTable: + m.lowerElementTable(t, host, l) + case sysmlv1.DependencyMatrix: + m.lowerMatrix(t, host, l) + case sysmlv1.RelationMap: + m.lowerRelationMap(t, host, l) + } +} + +// lowerElementTable lowers an instance or generic table: the scope's +// descendants and the explicit rows, filtered by row type, sorted, projected. +func (m *migration) lowerElementTable(t *sysmlv1.Table, host *sysmlv1.Element, l *lowered) { + src := m.scopeQuery(t.Scope, t.WholeModel, t.Rows, l) + if l.refused != "" { + return + } + rows := m.typedRows(src, t.RowTypes, t.IncludeSubtypes, t.Kind == sysmlv1.InstanceTable, l) + if l.refused != "" { + return + } + rows = m.sorted(rows, t, host, l) + l.rows = m.projected(rows, t, host, l) +} + +// scopeQuery is the elements a table draws rows from: every descendant of its +// scope, the whole model's descendants, and the rows it lists explicitly. +func (m *migration) scopeQuery(scope []sysmlv1.ElementRef, whole bool, rows []sysmlv1.ElementRef, l *lowered) qx { + var roots []string + if whole { + roots = m.topLevelNames() + } + for _, ref := range scope { + if name, why := m.namedRoot(ref, "scope"); why != "" { + l.refuse(why) + } else { + roots = append(roots, name) + } + } + var listed []string + var unresolved, ambiguous, unwritten []string + seen := map[string]bool{} + for _, ref := range rows { + if seen[ref.ID] { + continue + } + seen[ref.ID] = true + switch { + case ref.Element == nil: + if hrefs := m.model.Ambiguous(ref.ID); len(hrefs) > 0 { + ambiguous = append(ambiguous, ref.ID+" ("+strings.Join(hrefs, ", ")+")") + } else { + unresolved = append(unresolved, ref.ID) + } + case !m.written(ref.Element): + unwritten = append(unwritten, kindOf(ref.Element)+" "+qualifiedName(ref.Element)) + default: + listed = append(listed, m.plainName(ref.Element)) + } + } + missing := summarizeMissing(unresolved, "resolve to no element", "resolves to no element") + missing = append(missing, summarizeMissing(ambiguous, "name several module elements", "names several module elements")...) + missing = append(missing, summarizeMissing(unwritten, "are not migrated", "is not migrated")...) + var src qx + switch { + case len(roots) > 0: + named := qcall("Named", qstrs("qualifiedName", roots...)) + src = qcall("Descendants", qarg1("source", named)) + if whole { + src = qcall("Union", qarg1("source", named), qarg1("other", src)) + } + case len(listed) == 0 && len(missing) > 0: + l.refuse("none of the rows listed is an element of the document: " + strings.Join(missing, "; ")) + return qx{} + case len(listed) == 0: + l.refuse("the table names no scope and no rows") + return qx{} + } + for _, why := range missing { + if strings.HasPrefix(why, "the row ") { + l.note(why + ", and is not listed") + } else { + l.note(why + ", and are not listed") + } + } + if len(listed) > 0 { + extra := qcall("Named", qstrs("qualifiedName", listed...)) + if len(roots) == 0 { + return extra + } + src = qcall("Union", qarg1("source", src), qarg1("other", extra)) + } + return src +} + +// summarizeMissing words why listed rows are left out: one row by name, more +// by count with the first two named. +func summarizeMissing(items []string, many, one string) []string { + switch len(items) { + case 0: + return nil + case 1: + return []string{"the row " + items[0] + " " + one} + case 2: + return []string{"2 rows (" + items[0] + ", " + items[1] + ") " + many} + } + return []string{fmt.Sprintf("%d rows (%s, %s and %d more) %s", len(items), items[0], items[1], len(items)-2, many)} +} + +// topLevelNames lists the written top-level elements of the user model, the +// members of the global namespace a whole-model scope starts from: the roots' +// members and the views of the diagrams written there. +func (m *migration) topLevelNames() []string { + var names []string + add := func(c *sysmlv1.Element) { + if cat, _ := m.classify(c); m.written(c) && cat.keyword() != "" { + names = append(names, m.plainName(c)) + } + } + for _, r := range m.model.Roots { + switch { + case m.isLibrary(r): + case r.Type != "Model": + add(r) + default: + for _, c := range r.Children { + add(c) + } + } + } + for _, v := range m.hosted[nil] { + names = append(names, v.name) + } + return names +} + +// unresolvedRef says why ref names no element: no document defines its id, or +// module elements of several documents share it as their href fragment. +func (m *migration) unresolvedRef(ref sysmlv1.ElementRef) string { + if hrefs := m.model.Ambiguous(ref.ID); len(hrefs) > 0 { + return fmt.Sprintf("names %d module elements (%s)", len(hrefs), strings.Join(hrefs, ", ")) + } + return "resolves to no element" +} + +// namedRoot is the qualified name Named resolves ref by, or why it has none. +func (m *migration) namedRoot(ref sysmlv1.ElementRef, role string) (name, why string) { + switch { + case ref.Element == nil: + return "", "the " + role + " " + ref.ID + " " + m.unresolvedRef(ref) + case !m.written(ref.Element): + return "", "the " + role + " " + kindOf(ref.Element) + " " + qualifiedName(ref.Element) + " is not migrated" + } + return m.plainName(ref.Element), "" +} + +// plainName is e's migrated qualified name as a query string names it: the +// segments joined by ::, unquoted. +func (m *migration) plainName(e *sysmlv1.Element) string { + return strings.Join(m.segments(e), "::") +} + +// typedRows filters src by the row types; individuals only for an instance table. +func (m *migration) typedRows(src qx, types []sysmlv1.ElementRef, subtypes, individuals bool, l *lowered) qx { + if len(types) == 0 { + if individuals { + l.refuse("the instance table names no classifier") + } + return src + } + filters := make([]typeFilter, len(types)) + for i, ref := range types { + filters[i] = m.typeFilter(ref) + } + rows := src + if !typesAdmitAll(filters, l) { + var types, metadata uniqueNames + for _, f := range filters { + switch { + case f.refused != "" && len(filters) == 1: + l.refuse(f.refused) + return src + case f.refused != "": + l.note("elements of type " + f.label + " are not listed: " + f.refused) + case len(f.classifiers) > 0: + l.note(f.note) + for _, c := range f.classifiers { + types.add(m.plainName(c)) + } + case f.metadata != "": + metadata.add(f.metadata) + default: + l.note(f.note) + types.add(f.types...) + } + } + var qs []qx + if len(types) > 0 { + qs = append(qs, whereType(src, types...)) + } + if len(metadata) > 0 { + qs = append(qs, qcall("WhereMetadata", qarg1("source", src), qstrs("'metadata'", metadata...))) + } + if len(qs) == 0 { + l.refuse("none of the element types has a v2 form rows could be filtered by") + return src + } + rows = union(qs) + } + if !subtypes { + l.note("rows of subtypes of the row types are listed too: a type filter admits conforming elements") + } + if individuals { + rows = qcall("WhereFeature", qarg1("source", rows), qarg1("'feature'", qstr("isIndividual")), + qarg1("operator", qstr("=")), qarg1("value", qstr("true"))) + } + return rows +} + +// typesAdmitAll reports whether one of the filters admits every element, which +// makes the others moot. +func typesAdmitAll(filters []typeFilter, l *lowered) bool { + for _, f := range filters { + if f.all { + l.note(f.note) + return true + } + } + return false +} + +// uniqueNames are names in first-seen order, each once. +type uniqueNames []string + +func (u *uniqueNames) add(names ...string) { + for _, n := range names { + if !slices.Contains(*u, n) { + *u = append(*u, n) + } + } +} + +// whereType keeps the elements of src of any of the types. +func whereType(src qx, types ...string) qx { + return qcall("WhereType", qarg1("source", src), qstrs("type", types...)) +} + +// union joins queries with Union in order, as a balanced tree so the nesting +// grows with the logarithm of their number. +func union(qs []qx) qx { + if len(qs) == 1 { + return qs[0] + } + half := len(qs) / 2 + return qcall("Union", qarg1("source", union(qs[:half])), qarg1("other", union(qs[half:]))) +} + +// queryProperties maps the UML properties a column or sort reads to the query +// properties the row's migrated element has. +var queryProperties = map[string]string{ + "name": "name", + "documentation": "documentation", + "qualifiedName": "qualifiedName", + "owner": "owner", + "ID": "@id", + "Id": "@id", + "id": "@id", + "type": "type", + "isAbstract": "isAbstract", +} + +// columnKey is what a column reads of a row as a query property or feature +// name, or why it reads nothing a query can. +func (m *migration) columnKey(c sysmlv1.Column, host *sysmlv1.Element) (key string, feature *sysmlv1.Element, why string) { + switch c.Kind { + case sysmlv1.ColumnProperty: + if p, ok := queryProperties[c.Property]; ok { + return p, nil, "" + } + return "", nil, "no query property stands for the UML property " + c.Property + case sysmlv1.ColumnFeature: + f := c.Feature.Element + switch { + case f == nil: + return "", nil, "the column " + c.ID + " names no property of the document" + case !m.written(f): + return "", nil, "the column's " + kindOf(f) + " " + qualifiedName(f) + " is not migrated" + case f.Parent == nil || f.Type != "Property" || !m.isDefinition(f.Parent): + return "", nil, "the column's " + kindOf(f) + " " + qualifiedName(f) + " is not a property of a classifier" + } + return m.nameOf(f), f, "" + case sysmlv1.ColumnPropertyPair: + return "", nil, "the column " + c.ID + " reads a property of a property, which no Column expression reads" + } + return "", nil, "the column " + c.ID + " is of a form the migrator does not read" +} + +// sorted orders rows by the table's sort keys, least significant first so the +// stable sorts compose. +func (m *migration) sorted(rows qx, t *sysmlv1.Table, host *sysmlv1.Element, l *lowered) qx { + for i := len(t.Sorts) - 1; i >= 0; i-- { + s := t.Sorts[i] + col, ok := columnByID(t, s.Column) + if !ok { + switch { + case s.Column == "-1" || s.Column == "" || strings.HasPrefix(s.Column, "_"): + continue + case s.Column == "ID" || strings.HasSuffix(s.Column, ":hierarchyId"): + l.note("the sort by " + s.Column + " orders rows by a tool id, which is dropped") + continue + } + l.note("the sort by " + s.Column + " names no column and is dropped") + continue + } + if col.Kind == sysmlv1.ColumnTool { + continue + } + key, _, why := m.columnKey(col, host) + if why != "" { + l.note("the sort by " + s.Column + " is dropped: " + why) + continue + } + dir := "ascending" + if s.Descending { + dir = "descending" + } + rows = qcall("OrderBy", qarg1("source", rows), qarg1("property", qstr(key)), + qarg1("direction", qstr(dir)), qarg1("missing", qstr("last")), qarg1("multiple", qstr("first"))) + } + return rows +} + +// isDefinition reports whether e migrates to a definition a feature can belong to. +func (m *migration) isDefinition(e *sysmlv1.Element) bool { + cat, _ := m.classify(e) + return cat.keyword() != "" && cat != catPackage +} + +func columnByID(t *sysmlv1.Table, id string) (sysmlv1.Column, bool) { + for _, c := range t.Columns { + if c.ID == id { + return c, true + } + } + return sysmlv1.Column{}, false +} + +// projected selects the table's shown columns in their order: query +// properties as properties, features as Column expressions reading them. +func (m *migration) projected(rows qx, t *sysmlv1.Table, host *sysmlv1.Element, l *lowered) qx { + p := &projection{} + shown := 0 + for _, c := range t.Columns { + if c.Hidden || c.Kind == sysmlv1.ColumnTool { + continue + } + shown++ + key, f, why := m.columnKey(c, host) + if why != "" { + l.note("the column " + c.ID + " is omitted: " + why) + continue + } + if f == nil { + if !p.property(key) { + l.note("the column " + c.ID + " repeats the column " + key + " and is omitted") + } + continue + } + p.column(key, qlit(m.ref(f, host)+" ?? \"\"")) + } + if shown > 0 && p.empty() { + l.refuse("none of the table's columns reads what a query can") + return rows + } + if shown == 0 { + l.note("the table shows no column beyond the row number; rows are projected by name") + p.property("name") + } + project, notes := p.build(rows) + for _, n := range notes { + l.note(n) + } + return project +} + +// projection is a table's columns in source order: query properties and +// computed columns alike, written as one Project. +type projection struct { + entries []projectionEntry + listed columnNames +} + +// projectionEntry is a query property, or a computed column when computed. +type projectionEntry struct { + name string + computed bool + expression qx +} + +// property lists a query property once; false when it is listed already. +func (p *projection) property(name string) bool { + if p.listed[name] { + return false + } + if p.listed == nil { + p.listed = columnNames{} + } + p.listed[name] = true + p.entries = append(p.entries, projectionEntry{name: name}) + return true +} + +// column adds a computed column captioned name. +func (p *projection) column(name string, expression qx) { + p.entries = append(p.entries, projectionEntry{name: name, computed: true, expression: expression}) +} + +func (p *projection) empty() bool { + return len(p.entries) == 0 +} + +// reordered reports whether Project's properties-then-columns order moves a +// property column past a computed one. +func (p *projection) reordered() bool { + computed := false + for _, e := range p.entries { + if e.computed { + computed = true + } else if computed { + return true + } + } + return false +} + +// build writes the Project, properties before columns, with the notes that +// make it approximate: a reordered column, a repeating caption suffixed. +func (p *projection) build(source qx) (project qx, notes []string) { + names := columnNames{} + for n := range p.listed { + names[n] = true + } + var props []string + var cols []qx + for _, e := range p.entries { + if !e.computed { + props = append(props, e.name) + continue + } + name := names.claim(e.name) + if name != e.name { + notes = append(notes, "the column "+e.name+" is written as "+name+": column names are unique") + } + cols = append(cols, qcall("Column", qarg1("name", qstr(name)), qarg1("expression", e.expression))) + } + if p.reordered() { + notes = append(notes, "Project lists its properties first: "+strings.Join(props, ", ")+" precede the other columns") + } + args := []qarg{qarg1("source", source)} + if len(props) > 0 { + args = append(args, qstrs("properties", props...)) + } + if len(cols) > 0 { + args = append(args, qlist("columns", cols...)) + } + return qcall("Project", args...), notes +} + +// relationKinds maps the SysML relationship stereotypes and UML metaclasses a +// criterion may walk to the relationship kinds RelatedElements knows. +var relationKinds = map[string]string{ + "Refine": "refinement", + "Satisfy": "satisfaction", + "Verify": "verification", + "DeriveReqt": "derivation", + "Allocate": "allocation", + "Generalization": "specialization", +} + +// criterionLabel names a criterion in a note. +func criterionLabel(c sysmlv1.Criterion) string { + if c.Name == "" { + return "the unnamed criterion" + } + return "the criterion " + c.Name +} + +// criterionKind is the relationship kind a criterion walks, or why none does. +func criterionKind(c sysmlv1.Criterion) (kind, why string) { + label := criterionLabel(c) + switch { + case c.Malformed != "": + return "", label + " is malformed: " + c.Malformed + case c.Kind != sysmlv1.CriterionRelation: + return "", label + " is a " + c.Expression + ", which no relationship walk expresses" + case c.Metaclass != "": + if k, ok := relationKinds[c.Metaclass]; ok { + return k, "" + } + return "", label + " walks UML " + c.Metaclass + " relationships, which RelatedElements has no kind for" + case c.Stereotype.ID == "": + return "", label + " names no relationship" + case c.Stereotype.Name == "": + return "", label + " walks the relationship stereotype " + c.Stereotype.ID + ", which the archive does not describe" + case !isStandardNamespace(c.Stereotype.Namespace): + return "", label + " walks «" + c.Stereotype.Name + "» of a user profile, which RelatedElements has no kind for" + } + if k, ok := relationKinds[c.Stereotype.Name]; ok { + return k, "" + } + return "", label + " walks «" + c.Stereotype.Name + "», which RelatedElements has no kind for" +} + +// subtypesWalked names the user stereotypes specializing the one a criterion walks +// without subtypes: written as the same v2 relationship, they are walked too. "" when none. +func (m *migration) subtypesWalked(c sysmlv1.Criterion) string { + if c.IncludeSubtypes || c.Stereotype.Name == "" { + return "" + } + var names []string + for _, s := range m.model.Stereotypes { + if !isStandard(s) && appliesStandard(s, c.Stereotype.Name) && !slices.Contains(names, s.Name) { + names = append(names, s.Name) + } + } + if len(names) == 0 { + return "" + } + slices.Sort(names) + return criterionLabel(c) + " excludes subtypes of «" + c.Stereotype.Name + "», but the «" + + strings.Join(names, "», «") + "» relationships are walked too: they are written as the same relationship" +} + +// walkDirections are the directions a criterion walks, as RelatedElements spells +// them (outgoing runs from client to supplier); nil for a direction the tool did not write. +func walkDirections(direction string) []string { + switch direction { + case "DIRECT", "Row to column": + return []string{"outgoing"} + case "REVERSED", "Column to row": + return []string{"incoming"} + case "BOTH", "Both": + return []string{"outgoing", "incoming"} + } + return nil +} + +// kindDirection is dir as the v2 kind spells it: every kind runs client to +// supplier but derivation, which runs from the original requirement to the derived one. +func kindDirection(kind, dir string) string { + if kind != "derivation" { + return dir + } + if dir == "outgoing" { + return "incoming" + } + return "outgoing" +} + +// lowerMatrix lowers a dependency matrix: the typed rows projected by name, +// with one related column per criterion and direction listing the column +// elements each row is related to. +func (m *migration) lowerMatrix(t *sysmlv1.Table, host *sysmlv1.Element, l *lowered) { + rowSrc := m.scopeQuery(t.Scope, t.WholeModel, nil, l) + if l.refused != "" { + return + } + rows := m.typedRows(rowSrc, t.RowTypes, t.IncludeSubtypes, false, l) + if len(t.RowTypes) == 0 { + l.refuse("the matrix names no row element type") + } + colSrc := m.scopeQuery(t.ColumnScope, t.WholeModel, nil, l) + if l.refused != "" { + return + } + cols := m.typedRows(colSrc, t.ColumnTypes, t.IncludeColumnSubtypes, false, l) + if len(t.ColumnTypes) == 0 { + l.refuse("the matrix names no column element type") + } + if len(t.Criteria) == 0 { + l.refuse("the matrix names no dependency criterion") + } + if l.refused != "" { + return + } + dirs := walkDirections(t.Direction) + if dirs == nil { + dirs = []string{"outgoing"} + l.note("the matrix names no direction; rows are read as the clients of the relationships") + } + if len(dirs) > 1 { + l.note("the matrix reads relationships in both directions, which become two columns per criterion") + } + var related []qx + names := columnNames{"name": true} + for _, c := range t.Criteria { + kind, why := criterionKind(c) + if why != "" { + l.refuse(why) + return + } + l.note(m.subtypesWalked(c)) + for _, dir := range dirs { + name := c.Name + if name == "" { + name = kind + } + if len(dirs) > 1 { + name += " (" + dir + ")" + } + if unique := names.claim(name); unique != name { + l.note("the column " + name + " is written as " + unique + ": column names are unique") + name = unique + } + related = append(related, qcall("RelatedColumn", qarg1("name", qstr(name)), + qarg1("relationshipKind", qstr(kind)), qarg1("direction", qstr(kindDirection(kind, dir))), qint1("maxDepth", 1), + qarg1("aggregate", qstr("list")), qarg1("targets", cols))) + } + } + if t.ShowElements == "With relations" { + var kept []qx + for _, c := range t.Criteria { + kind, _ := criterionKind(c) + for _, dir := range dirs { + kept = append(kept, qcall("WhereRelated", qarg1("source", rows), qarg1("relationshipKind", qstr(kind)), + qarg1("direction", qstr(kindDirection(kind, dir))), qint1("maxDepth", 1), qarg1("exists", qlit("true")))) + } + } + rows = union(kept) + l.note("rows with a relationship of the criterion's kind to any element are listed, not only to a column element") + } + l.rows = qcall("Project", qarg1("source", rows), qstrs("properties", "name"), qlist("columns", related...)) +} + +func qint1(name string, n int) qarg { return qarg1(name, qint(n)) } + +// columnNames are the column names a projection has claimed. +type columnNames map[string]bool + +// claim returns name, or name with the first free numeric suffix once taken. +func (c columnNames) claim(name string) string { + unique := name + for i := 2; c[unique]; i++ { + unique = fmt.Sprintf("%s %d", name, i) + } + c[unique] = true + return unique +} + +// lowerRelationMap lowers a relation map: the elements reached from the +// context by the criteria within the depth, filtered by type, listed by +// qualified name and type. +func (m *migration) lowerRelationMap(t *sysmlv1.Table, host *sysmlv1.Element, l *lowered) { + var roots []string + for _, ref := range t.Scope { + if name, why := m.namedRoot(ref, "context element"); why != "" { + l.refuse(why) + } else { + roots = append(roots, name) + } + } + if len(roots) == 0 { + l.refuse("the relation map names no context element") + } + if len(t.Criteria) == 0 { + l.refuse("the relation map names no relation criterion") + } + if l.refused != "" { + return + } + ctx := qcall("Named", qstrs("qualifiedName", roots...)) + var walks []qx + for _, c := range t.Criteria { + kind, why := criterionKind(c) + if why != "" { + l.refuse(why) + return + } + l.note(m.subtypesWalked(c)) + dirs := walkDirections(c.Direction) + if dirs == nil { + dirs = []string{"outgoing"} + l.note("the criterion " + c.Name + " names no direction; the context is read as the client of the relationships") + } + for _, dir := range dirs { + args := []qarg{qarg1("source", ctx), qarg1("relationshipKind", qstr(kind)), qarg1("direction", qstr(kindDirection(kind, dir)))} + if t.Depth > 0 { + args = append(args, qint1("maxDepth", t.Depth)) + } + walks = append(walks, qcall("RelatedElements", args...)) + } + } + reached := union(walks) + if len(t.Criteria) > 1 { + l.note("each criterion is walked from the context on its own; a path mixing criteria is not followed") + } + rows := m.typedRows(reached, t.RowTypes, t.IncludeSubtypes, false, l) + if l.refused != "" { + return + } + l.rows = qcall("Project", qarg1("source", rows), qstrs("properties", "qualifiedName", "@type")) +} diff --git a/internal/translate/migrate/typefilter.go b/internal/translate/migrate/typefilter.go new file mode 100644 index 0000000000..4673f24409 --- /dev/null +++ b/internal/translate/migrate/typefilter.go @@ -0,0 +1,313 @@ +package migrate + +import ( + "strings" + + "github.com/Open-MBEE/OpenSysML/internal/translate/xmi/sysmlv1" +) + +// typeFilter is how one element type a table names selects migrated elements: +// by a user classifier, by v2 metaclass names, or not at all. +type typeFilter struct { + // classifiers are the written user classifiers rows are typed by; none + // when the type is a metaclass or stereotype. + classifiers []*sysmlv1.Element + // types are the v2 metaclass names a row must conform to one of. + types []string + // metadata is the written metadata def of a user stereotype rows carry. + metadata string + // all is set when the type admits every migrated element. + all bool + // note says why the filter is only an approximation; refused why it has + // no v2 form. label names the v1 type for both. + note, refused, label string +} + +// v2Types lists the v2 metaclasses a family of v1 elements migrates to, and +// what makes listing by them approximate. +type v2Types struct { + types []string + note string +} + +// The v2 metaclass names the migrator's declarations report as @type. +const ( + typePartDef = "PartDefinition" + typeRequirementDef = "RequirementDefinition" + typeConstraintDef = "ConstraintDefinition" + typePortDef = "PortDefinition" + typeAttributeDef = "AttributeDefinition" + typeEnumDef = "EnumerationDefinition" + typeItemDef = "ItemDefinition" + typeConnectionDef = "ConnectionDefinition" + typeActionDef = "ActionDefinition" + typeCalcDef = "CalculationDefinition" + typeStateDef = "StateDefinition" + typeUseCaseDef = "UseCaseDefinition" + typeVerificationDef = "VerificationCaseDefinition" + typeOccurrenceDef = "OccurrenceDefinition" + typePartUsage = "PartUsage" + typeAttributeUsage = "AttributeUsage" + typeItemUsage = "ItemUsage" + typeReferenceUsage = "ReferenceUsage" + typePortUsage = "PortUsage" + typeConstraintUsage = "ConstraintUsage" + typeRequirementUse = "RequirementUsage" + typeActionUsage = "ActionUsage" + typeStateUsage = "StateUsage" + typeCalcUsage = "CalculationUsage" + typeUseCaseUsage = "UseCaseUsage" + typeViewUsage = "ViewUsage" + typeViewpointUsage = "ViewpointUsage" + typeEnumUsage = "EnumerationUsage" + typeConnectionUsage = "ConnectionUsage" + typeBindingUsage = "BindingConnectorAsUsage" + typeInterfaceUsage = "InterfaceUsage" + typeFlowUsage = "FlowUsage" + typeSatisfyUsage = "SatisfyRequirementUsage" + typeAllocationUsage = "AllocationUsage" + typeDependency = "Dependency" + typeComment = "Comment" + typePackage = "Package" + typeDefinition = "Definition" +) + +// classTypes are the definitions a UML class of any stereotype migrates to. +var classTypes = []string{typePartDef, typeRequirementDef, typeConstraintDef, typePortDef, + typeVerificationDef, typeActionDef, typeStateDef, typeCalcDef, typeViewUsage, typeViewpointUsage} + +// propertyTypes are the usages a UML property of any type migrates to. +var propertyTypes = []string{typeAttributeUsage, typePartUsage, typeItemUsage, typeReferenceUsage, + typePortUsage, typeConstraintUsage, typeRequirementUse, typeActionUsage, typeStateUsage, + typeCalcUsage, typeUseCaseUsage} + +// behaviorTypes are the definitions a UML behavior migrates to. +var behaviorTypes = []string{typeActionDef, typeCalcDef, typeStateDef, typeVerificationDef} + +// classifierTypes are what a UML type, which is always a classifier, migrates +// to: a definition, or the view or viewpoint usage a «View» or «Viewpoint» +// class becomes. +var classifierTypes = []string{typeDefinition, typeViewUsage, typeViewpointUsage} + +// namespaceTypes add to the classifiers the packages and the states, which are +// namespaces in UML; a «View» package is a view usage too. +var namespaceTypes = []string{typePackage, typeDefinition, typeViewUsage, typeViewpointUsage, typeStateUsage} + +// packageableTypes are what the elements a package can own migrate to: the +// classifiers, packages, and the dependencies of every stereotype. +var packageableTypes = []string{typePackage, typeDefinition, typeViewUsage, typeViewpointUsage, + typeDependency, typeSatisfyUsage, typeAllocationUsage} + +// Notes on what the broad UML metaclasses list once migrated. +const ( + noteDiagramViews = "the views diagrams became are listed too" + noteClassifierExtra = "the views diagrams became and the action defs operations became are listed too" +) + +// metaclassTypes maps a UML metaclass to the v2 metaclasses its elements +// migrate to; a nil entry admits every element. +var metaclassTypes = map[string]v2Types{ + "Element": {}, + "NamedElement": {note: "every migrated element is named, so a NamedElement filter admits all of them"}, + "PackageableElement": {types: packageableTypes, + note: noteClassifierExtra + "; instances of value types, written as attributes, are not"}, + "Namespace": {types: namespaceTypes, note: noteDiagramViews + + "; transitions and structured activity nodes are not"}, + "Package": {types: []string{typePackage}}, + "Model": {types: []string{typePackage}, note: "a model is a package once migrated"}, + "Type": {types: classifierTypes, note: noteClassifierExtra}, + "Classifier": {types: classifierTypes, note: noteClassifierExtra}, + "Class": {types: classTypes}, + "Component": {types: []string{typePartDef}, note: "a component is a part def once migrated, as a block is"}, + "Actor": {types: []string{typePartDef}, note: "an actor is a part def once migrated, as a block is"}, + "Behavior": {types: behaviorTypes}, + "Activity": {types: []string{typeActionDef, typeCalcDef}}, + "OpaqueBehavior": {types: []string{typeActionDef, typeCalcDef}}, + "FunctionBehavior": {types: []string{typeCalcDef}}, + "Interaction": {types: []string{typeActionDef}}, + "StateMachine": {types: []string{typeStateDef}}, + "Operation": {types: []string{typeActionDef}, note: "an operation is an action def once migrated, as an activity is"}, + "UseCase": {types: []string{typeUseCaseDef}}, + "DataType": {types: []string{typeAttributeDef, typeEnumDef}}, + "PrimitiveType": {types: []string{typeAttributeDef}}, + "Enumeration": {types: []string{typeEnumDef}}, + "EnumerationLiteral": {types: []string{typeEnumUsage}}, + "Signal": {types: []string{typeItemDef}}, + "Interface": {types: []string{typePortDef}, note: "an interface is a port def once migrated, as an interface block is"}, + "Association": {types: []string{typeConnectionDef}}, + "AssociationClass": {types: []string{typeConnectionDef}}, + "InstanceSpecification": {types: []string{typeOccurrenceDef}, + note: "instances of value types are written as attributes, which an OccurrenceDefinition filter leaves out"}, + "Property": {types: propertyTypes, note: "properties typed by a view or viewpoint are not listed"}, + "Port": {types: []string{typePortUsage}}, + "Connector": {types: []string{typeConnectionUsage, typeBindingUsage, typeInterfaceUsage, typeFlowUsage}}, + "Constraint": {types: []string{typeConstraintUsage}, + note: "a constraint is a constraint usage once migrated, as a constraint property is"}, + "Comment": {types: []string{typeComment}}, + "Dependency": {types: []string{typeDependency}}, + "Abstraction": {types: []string{typeDependency}, note: "every dependency is listed, not only abstractions"}, + "Realization": {types: []string{typeDependency}, note: "every dependency is listed, not only realizations"}, + "Usage": {types: []string{typeDependency}, note: "every dependency is listed, not only usages"}, + "Diagram": {types: []string{typeViewUsage}, note: "views a «View» class became are listed with the diagrams' views"}, + "State": {types: []string{typeStateUsage}}, + "Action": {types: []string{typeActionUsage}}, + "Region": {types: []string{typeStateUsage}, note: "a region is written into its state, so states are listed for regions"}, +} + +// actionMetaclasses are the UML activity nodes the migrator writes as action usages. +var actionMetaclasses = map[string]bool{"ActivityNode": true, "ExecutableNode": true, "InvocationAction": true, + "CallAction": true, "CallBehaviorAction": true, "CallOperationAction": true, "OpaqueAction": true, + "SendSignalAction": true, "AcceptEventAction": true, "AcceptCallAction": true, "ValueSpecificationAction": true, + "ReadStructuralFeatureAction": true, "AddStructuralFeatureValueAction": true, "StructuredActivityNode": true, + "ConditionalNode": true, "LoopNode": true, "SequenceNode": true, "ExpansionRegion": true, "ControlNode": true, + "ForkNode": true, "JoinNode": true, "DecisionNode": true, "MergeNode": true} + +// stereotypeTypes maps a SysML profile stereotype to the v2 metaclasses of what +// it migrates to. +var stereotypeTypes = map[string]v2Types{ + "Block": {types: []string{typePartDef}}, + "Requirement": {types: []string{typeRequirementDef}}, + "AbstractRequirement": {types: []string{typeRequirementDef}}, + "ConstraintBlock": {types: []string{typeConstraintDef}}, + "InterfaceBlock": {types: []string{typePortDef}}, + "ValueType": {types: []string{typeAttributeDef, typeEnumDef}}, + "Stakeholder": {types: []string{typePartDef}, note: "a stakeholder is a part def once migrated, as a block is"}, + "View": {types: []string{typeViewUsage}}, + "Viewpoint": {types: []string{typeViewpointUsage}}, + "TestCase": {types: []string{typeVerificationDef}}, + "Satisfy": {types: []string{typeSatisfyUsage}}, + "Allocate": {types: []string{typeAllocationUsage}}, + "Refine": {types: []string{typeDependency}, note: "every dependency is listed, not only refinements"}, + "Trace": {types: []string{typeDependency}, note: "every dependency is listed, not only traces"}, + "Copy": {types: []string{typeDependency}, note: "every dependency is listed, not only copies"}, + "DeriveReqt": {types: []string{typeConnectionDef}, note: "every connection def is listed, not only derivations"}, + "ProxyPort": {types: []string{typePortUsage}}, + "FullPort": {types: []string{typePortUsage}}, + "FlowPort": {types: []string{typePortUsage}}, + "BindingConnector": {types: []string{typeBindingUsage}}, + "ItemFlow": {types: []string{typeFlowUsage}}, + "PartProperty": {types: []string{typePartUsage}}, + "SharedProperty": {types: []string{typePartUsage}, note: "every part usage is listed, composite ones included"}, + "ReferenceProperty": {types: []string{typeReferenceUsage}}, + "ValueProperty": {types: []string{typeAttributeUsage}}, + "ConstraintProperty": {types: []string{typeConstraintUsage}}, + "ConstraintParameter": {types: []string{typeAttributeUsage}, note: "every attribute usage is listed, not only constraint parameters"}, +} + +// standardHref names, when href points into the OMG UML or SysML documents, the +// document ("UML", "SysML") and the fragment's local name ("Class", "Block"). +func standardHref(href string) (doc, name string, ok bool) { + path, frag, found := strings.Cut(href, "#") + if !found || (!strings.Contains(path, "/spec/UML/") && !strings.Contains(path, "/spec/SysML/")) { + return "", "", false + } + doc = hrefDocument(path) + if i := strings.LastIndexByte(frag, '.'); i >= 0 { + frag = frag[i+1:] + } + return doc, frag, frag != "" +} + +// typeFilter decides how the element type ref names filters rows. +func (m *migration) typeFilter(ref sysmlv1.ElementRef) typeFilter { + e := ref.Element + if e == nil { + return typeFilter{label: ref.ID, refused: "the element type " + ref.ID + " " + m.unresolvedRef(ref)} + } + if doc, name, ok := standardHref(e.Href); ok { + switch { + case doc == "UML": + return metaclassFilter(name) + case stereotypeTypes[name].types != nil: + return fromTypes("«"+name+"»", stereotypeTypes[name]) + default: + return typeFilter{label: "«" + name + "»", refused: "no v2 metaclass stands for the elements of «" + name + "»"} + } + } + if e.IsProxy() { + if s := m.model.StereotypeRef(e.ID); s.Name != "" && isStandardNamespace(s.Namespace) { + if t, ok := stereotypeTypes[s.Name]; ok { + return fromTypes("«"+s.Name+"»", t) + } + return typeFilter{label: "«" + s.Name + "»", refused: "no v2 metaclass stands for the elements of «" + s.Name + "»"} + } + if e.Name == "" { + return typeFilter{label: e.Href, refused: "the element type " + e.Href + " is in a module the archive does not describe"} + } + if t, ok := stereotypeTypes[e.Name]; ok && isCustomizationHref(e.Href) { + return fromTypes("«"+e.Name+"»", t) + } + if subs := m.specializers(e); len(subs) > 0 { + return typeFilter{classifiers: subs, label: qualifiedName(e), + note: "the element type " + qualifiedName(e) + " is outside the document; rows are filtered by the document's classifiers specializing it"} + } + return typeFilter{label: qualifiedName(e), refused: "the element type " + qualifiedName(e) + " is outside the document, and not a UML metaclass or a SysML stereotype"} + } + if e.Type == "Stereotype" { + if t, ok := stereotypeTypes[e.Name]; ok && m.isLibrary(e) && libraryRoots[pathRoot(qualifiedName(e))] { + return fromTypes("«"+e.Name+"»", t) + } + if m.userStereotype(e) && m.written(e) { + return typeFilter{label: "«" + e.Name + "»", metadata: m.plainName(e)} + } + return typeFilter{label: "«" + e.Name + "»", refused: "«" + e.Name + "» is not written as a metadata def rows could be filtered by"} + } + if !m.written(e) { + return typeFilter{label: qualifiedName(e), refused: "the element type " + kindOf(e) + " " + qualifiedName(e) + " is not migrated"} + } + cat, _ := m.classify(e) + if cat.keyword() == "" || cat == catPackage { + return typeFilter{label: qualifiedName(e), refused: "the element type " + kindOf(e) + " " + qualifiedName(e) + " is not a classifier rows can be typed by"} + } + return typeFilter{classifiers: []*sysmlv1.Element{e}, label: qualifiedName(e)} +} + +// specializers lists the written classifiers of the document that specialize +// general, directly or through other classifiers outside the document, so a +// type outside the document still filters rows exactly. +func (m *migration) specializers(general *sysmlv1.Element) []*sysmlv1.Element { + var subs []*sysmlv1.Element + var walk func(e *sysmlv1.Element) + walk = func(e *sysmlv1.Element) { + for _, c := range e.Children { + walk(c) + } + if len(e.Owned("generalization")) == 0 || !m.inherits(e, general) || !m.written(e) { + return + } + if cat, _ := m.classify(e); cat.keyword() != "" && cat != catPackage { + subs = append(subs, e) + } + } + for _, r := range m.model.Roots { + if !m.isLibrary(r) { + walk(r) + } + } + return subs +} + +// isCustomizationHref reports whether an href points into MagicDraw's SysML +// customization module, whose stereotypes name property kinds. +func isCustomizationHref(href string) bool { + return fold(hrefDocument(href)) == fold(magicDrawCustomizationModule) +} + +// metaclassFilter is the filter of a UML metaclass. +func metaclassFilter(name string) typeFilter { + t, ok := metaclassTypes[name] + if actionMetaclasses[name] { + t, ok = v2Types{types: []string{typeActionUsage}, note: "every action usage is listed, whatever kind of activity node it was"}, true + } + if !ok { + return typeFilter{label: name, refused: "no v2 metaclass stands for the elements of a UML " + name} + } + if t.types == nil { + return typeFilter{label: name, all: true, note: t.note} + } + return fromTypes(name, t) +} + +func fromTypes(label string, t v2Types) typeFilter { + return typeFilter{label: label, types: t.types, note: t.note} +} diff --git a/internal/translate/migrate/typemodifier.go b/internal/translate/migrate/typemodifier.go new file mode 100644 index 0000000000..a2eb24229f --- /dev/null +++ b/internal/translate/migrate/typemodifier.go @@ -0,0 +1,135 @@ +package migrate + +import ( + "strings" + + "github.com/Open-MBEE/OpenSysML/internal/translate/xmi/sysmlv1" +) + +// typeModifier is what MagicDraw's «typeModifier» on a property or parameter +// says of its v2 declaration: a collection shape, or that the usage is a reference. +type typeModifier struct { + // text is the modifier as written: "[]", "[3]", "*", "&", "[][]"... + text string + // mult is the multiplicity the modifier gives, "[0..*]" or "[n]"; "" for a reference. + mult string + // ref is set when the usage is written as a reference. + ref bool + // refused is why the modifier has no v2 form; "" when it has one. + refused string +} + +// isTypeModifier matches the tool's exact «typeModifier» application. +func isTypeModifier(s *sysmlv1.Stereotype) bool { + return s.Name == "typeModifier" && s.Namespace == sysmlv1.MagicDrawProfileNS +} + +// readsTypeModifier reports whether the declaration of e reads application s +// as a «typeModifier»: it is written exactly, or kept as a comment with its own note. +func readsTypeModifier(e *sysmlv1.Element, s *sysmlv1.Stereotype) bool { + return isTypeModifier(s) && (e.Type == "Property" || e.Type == "Parameter" || e.Type == "Port") +} + +// writesTypeModifier reports whether application s on e is a «typeModifier» +// the declaration of e writes exactly, so that no comment repeats it. +func (m *migration) writesTypeModifier(e *sysmlv1.Element, s *sysmlv1.Stereotype) bool { + if !readsTypeModifier(e, s) { + return false + } + tm := m.typeModifier(e) + return tm != nil && tm.refused == "" +} + +// typeModifier reads the «typeModifier» applied to p, nil when none is. +func (m *migration) typeModifier(p *sysmlv1.Element) *typeModifier { + var app *sysmlv1.Stereotype + for _, s := range p.Stereotypes { + if isTypeModifier(s) { + app = s + break + } + } + if app == nil { + return nil + } + tm := &typeModifier{text: strings.TrimSpace(app.Tag("typeModifier"))} + switch shape, ok := strings.CutPrefix(tm.text, "["); { + case tm.text == "": + tm.refused = "it says nothing of the type" + case tm.text == "*", tm.text == "&": + tm.reference(m, p) + case !ok: + tm.refused = "the type modifier " + tm.text + " is not one the migrator reads" + case strings.Count(tm.text, "[") > 1, strings.ContainsAny(shape, "*,"): + tm.refused = "the type modifier " + tm.text + " has no v2 form: a multiplicity has one dimension" + default: + tm.collection(m, p, strings.TrimSuffix(shape, "]")) + } + return tm +} + +// collection maps [] and [n] to a multiplicity, on a declaration of one value. +func (tm *typeModifier) collection(m *migration, p *sysmlv1.Element, n string) { + if !strings.HasSuffix(tm.text, "]") || (n != "" && !isNatural(n)) { + tm.refused = "the type modifier " + tm.text + " is not one the migrator reads" + return + } + mult, note := m.declaredMultiplicity(p) + if note != "" { + tm.refused = "the type modifier " + tm.text + " has no v2 form: the declared " + note + return + } + if mult != "" { + tm.refused = "the type modifier " + tm.text + " has no v2 form: the declared multiplicity " + mult + " is already a collection, and a collection of collections has no multiplicity" + return + } + if n == "" { + tm.mult = "[0..*]" + } else { + tm.mult = "[" + n + "]" + } +} + +// reference maps * and & to a reference usage, on a property typed by a block. +func (tm *typeModifier) reference(m *migration, p *sysmlv1.Element) { + if p.Type != "Property" || p.Parent == nil { + tm.refused = "the type modifier " + tm.text + " has no v2 form: a parameter is not held by reference" + return + } + cat, _ := m.classify(p.Parent) + switch kw, _, _ := m.featureKeyword(p, cat); kw { + case "part", "item": + tm.ref = true + default: + tm.refused = "the type modifier " + tm.text + " has no v2 form: only a part or item is held by reference, not " + kwArticle(kw) + } +} + +// kwArticle names a usage keyword with its article. +func kwArticle(kw string) string { + if strings.HasPrefix(kw, "a") || strings.HasPrefix(kw, "i") { + return "an " + kw + } + return "a " + kw +} + +// shape is the multiplicity the type modifier writes in place of the declared +// one, "" when it writes none: a v1 array is ordered and admits repeated values. +func (tm *typeModifier) shape() string { + if tm == nil || tm.mult == "" { + return "" + } + return tm.mult + " ordered nonunique" +} + +// note is what the report says of the modifier: nothing when it is written +// exactly, else why it is kept as a comment. +func (tm *typeModifier) note() string { + if tm == nil || tm.refused == "" { + return "" + } + if tm.text == "" { + return "an empty «typeModifier» is kept as a comment: " + tm.refused + } + return "«typeModifier» " + tm.text + " is kept as a comment: " + tm.refused +} diff --git a/internal/translate/xmi/sysmlv1/criterion.go b/internal/translate/xmi/sysmlv1/criterion.go new file mode 100644 index 0000000000..881b987057 --- /dev/null +++ b/internal/translate/xmi/sysmlv1/criterion.go @@ -0,0 +1,96 @@ +package sysmlv1 + +import ( + "strings" + + "github.com/Open-MBEE/OpenSysML/internal/translate/xmi" +) + +// criterion decodes one structured expression as MagicDraw serializes a +// matrix's dependency criterion or a relation map's relation criterion: an +// XML document, escaped into the tag's text, whose root call applies one +// expression to the THIS argument. +func (m *Model) criterion(raw string) Criterion { + raw = strings.TrimSpace(raw) + if raw == "" { + return Criterion{Kind: CriterionOther, Malformed: "empty"} + } + doc, err := xmi.Parse(strings.NewReader(raw)) + if err != nil { + return Criterion{Kind: CriterionOther, Malformed: "not well-formed XML: " + err.Error()} + } + call := doc.Root + c := Criterion{Name: taggedValue(call, "name")} + expr := call.First("expression") + if expr == nil { + c.Kind, c.Malformed = CriterionOther, "no expression element" + return c + } + c.Expression = expr.Attr("type") + c.Direction = expr.Attr("direction") + c.IncludeSubtypes = expr.Attr("includeSubtypes") == "true" + switch c.Expression { + case "dslRelationExpressionSpecification": + c.Kind = CriterionRelation + if id := expr.Attr("stereotype"); id != "" { + c.Stereotype = m.StereotypeRef(id) + } else { + c.Malformed = "relation expression names no stereotype" + } + case "relationExpressionSpecification": + c.Kind = CriterionRelation + if c.Metaclass = expr.Attr("metaclass"); c.Metaclass == "" { + c.Malformed = "relation expression names no metaclass" + } + case "metaChainExpressionSpecification": + c.Kind = CriterionMetachain + var steps []string + for _, step := range expr.Tagged("chain") { + steps = append(steps, chainStep(step)) + } + c.Detail = strings.Join(steps, ".") + case "propertyExpressionSpecification": + c.Kind = CriterionProperty + if p := expr.First("property"); p != nil { + c.Detail = chainStep(p) + } + case "makeInlineExpressionSpecification": + c.Kind = CriterionScript + if body := expr.First("body"); body != nil { + c.Detail = strings.TrimSpace(body.Text) + } + if lang := expr.Attr("language"); lang != "" { + c.Detail = lang + ": " + c.Detail + } + case "": + c.Kind, c.Malformed = CriterionOther, "expression has no type" + default: + c.Kind = CriterionOther + } + return c +} + +// taggedValue reads one entry of a structured expression's taggedValues. +func taggedValue(e *xmi.Element, key string) string { + values := e.First("taggedValues") + if values == nil { + return "" + } + for _, entry := range values.Tagged("entry") { + if entry.Attr("key") == key { + if v := entry.First("value"); v != nil { + return strings.TrimSpace(v.Text) + } + } + } + return "" +} + +// chainStep spells one metachain step: Metaclass.property for a UML property, +// «Stereotype».tag for a stereotype property. +func chainStep(step *xmi.Element) string { + if step.Attr("type") == "stereotypeProperty" { + return "«" + step.Attr("stereotype") + "»." + step.Attr("tag") + } + return step.Attr("metaclass") + "." + step.Attr("property") +} diff --git a/internal/translate/xmi/sysmlv1/diagram.go b/internal/translate/xmi/sysmlv1/diagram.go index ccf6e4fde8..5eea22c447 100644 --- a/internal/translate/xmi/sysmlv1/diagram.go +++ b/internal/translate/xmi/sysmlv1/diagram.go @@ -159,7 +159,10 @@ func (m *Model) Diagram(id string) *Diagram { } // shown resolves the id of a shown element: an xmi:id of the read documents, -// an href into one of them, or an href another document's proxy stands for. +// an href into one of them, an href another document's proxy stands for, or +// the bare fragment of such an href, as a tool writes a module element's id +// once it has referenced the element by href. A bare fragment that hrefs of +// several documents share names no element (Ambiguous lists the candidates). func (m *Model) shown(id string) *Element { if e := m.byID[id]; e != nil { return e @@ -169,5 +172,26 @@ func (m *Model) shown(id string) *Element { return e } } - return m.proxies[id] + if p := m.proxies[id]; p != nil { + return p + } + if ps := m.fragments[id]; len(ps) == 1 { + return ps[0] + } + return nil +} + +// Ambiguous lists the hrefs a bare id could stand for when proxies of several +// documents share it as their fragment, in first-seen order; nil when the id +// resolves, or when no proxy carries it. +func (m *Model) Ambiguous(id string) []string { + ps := m.fragments[id] + if len(ps) < 2 || m.byID[id] != nil || m.proxies[id] != nil { + return nil + } + hrefs := make([]string, len(ps)) + for i, p := range ps { + hrefs[i] = p.Href + } + return hrefs } diff --git a/internal/translate/xmi/sysmlv1/diagram_test.go b/internal/translate/xmi/sysmlv1/diagram_test.go index 445887c762..b4acfefba8 100644 --- a/internal/translate/xmi/sysmlv1/diagram_test.go +++ b/internal/translate/xmi/sysmlv1/diagram_test.go @@ -314,3 +314,61 @@ func TestDiagramsInArchiveEntriesResolveAcrossDocuments(t *testing.T) { t.Fatalf("diagrams = %+v", m.Diagrams) } } + +func TestBareModuleFragmentResolvesOnlyWhenUnique(t *testing.T) { + // A tool that referenced a module element by href goes on to write its + // bare id, which resolves to that href's proxy while one document holds + // the fragment; once hrefs of two documents share it, the id is ambiguous + // and names no element rather than the first proxy seen. + m, err := Parse([]byte(`<?xml version="1.0"?> +<xmi:XMI xmlns:xmi="http://www.omg.org/spec/XMI/20131001" + xmlns:uml="http://www.omg.org/spec/UML/20161101" + xmlns:diagram="http://www.example.com/tool/diagram"> + <uml:Model xmi:type="uml:Model" xmi:id="_m" name="M"> + <packagedElement xmi:type="uml:Class" xmi:id="_a" name="A"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_a_x" name="x"> + <type xmi:type="uml:Class" href="module-a.xmi#_type1"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_a_y" name="y"> + <type xmi:type="uml:Class" href="module-b.xmi#_type1"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_a_z" name="z"> + <type xmi:type="uml:Class" href="module-a.xmi#_only"/> + </ownedAttribute> + </packagedElement> + <xmi:Extension extender="Example UML Tool 1.0"> + <modelExtension> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_d" name="Modules" ownerOfDiagram="_m"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="SysML Package Diagram" umlType="Class Diagram"> + <diagramContents> + <usedElements>_type1</usedElements> + <usedElements>_only</usedElements> + <usedObjects href="module-b.xmi#_type1"/> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + </modelExtension> + </xmi:Extension> + </uml:Model> +</xmi:XMI>`)) + if err != nil { + t.Fatal(err) + } + d := m.Diagrams[0] + if got := ids(d.Shown); got != "_type1? _only module-b.xmi#_type1" { + t.Errorf("shown = %q", got) + } + if d.Shown[1].Element != m.Lookup("module-a.xmi#_only") || d.Shown[2].Element != m.Lookup("module-b.xmi#_type1") { + t.Errorf("shown elements = %+v", d.Shown) + } + if got := strings.Join(m.Ambiguous("_type1"), " "); got != "module-a.xmi#_type1 module-b.xmi#_type1" { + t.Errorf("Ambiguous(_type1) = %q", got) + } + if m.Ambiguous("_only") != nil || m.Ambiguous("module-a.xmi#_type1") != nil || m.Ambiguous("_a") != nil || m.Ambiguous("_gone") != nil { + t.Error("a resolved or unknown id reads as ambiguous") + } +} diff --git a/internal/translate/xmi/sysmlv1/docgen.go b/internal/translate/xmi/sysmlv1/docgen.go new file mode 100644 index 0000000000..0fe0e39840 --- /dev/null +++ b/internal/translate/xmi/sysmlv1/docgen.go @@ -0,0 +1,622 @@ +package sysmlv1 + +import ( + "fmt" + "strconv" +) + +// DocGenDocument is one MDK DocGen document: a class carrying the Document +// stereotype of either DocGen profile, read into its view tree. +type DocGenDocument struct { + // Class is the document class; Root its view tree, of which the + // document class is the first view. + Class *Element + Root *DocGenView + // Application is the Document application that names the class. + Application *Stereotype +} + +// DocGenView is one view of a document: a class the SysML View stereotype +// applies to, placed by the composite properties of its parent view. +type DocGenView struct { + // Class is the view class. + Class *Element + // Viewpoint is the viewpoint the view conforms to; nil when none. + Viewpoint *Element + // Method is the viewpoint's method activity, the behavior of its + // operation named View or its method tag; nil when the viewpoint has none. + Method *Element + // MethodMalformed is why the viewpoint's method tag yields no activity, + // "" when it does or the viewpoint declares no method. + MethodMalformed string + // Exposed are the elements the view exposes or imports, in document order. + Exposed []ElementRef + // Paragraphs are the collaborator paragraphs placed in this view, in the + // order they are shown. + Paragraphs []*DocGenParagraph + // Children are the child views, in property order. + Children []*DocGenView + // Malformed lists what could not be read. + Malformed []string +} + +// DocGenParagraph is one collaborator paragraph: a comment the collaborator +// profile places in a view (its ownerId) of a document (its viewId), after +// the paragraph its siblingId names. +type DocGenParagraph struct { + // Application is the CollaboratorParagraph or CollaboratorImageParagraph application. + Application *Stereotype + // Image reports a CollaboratorImageParagraph: the comment carries an attached image file. + Image bool + // Comment is the comment shown; nil when the application's base is dangling. + Comment *Element + // Malformed is why the paragraph cannot be shown, "" when it can. + Malformed string + // Placed reports whether siblingId named a paragraph of the same view, + // which this one then follows; false also when siblingId is empty. + Placed bool +} + +// DocGenStep is one node of a DocGen activity chain: a collect, filter or +// sort step, a presentation node, a group, or a join of parallel branches. +type DocGenStep struct { + // Node is the activity node. + Node *Element + // Kind is the DocGen stereotype the node or its called behavior carries, + // "" when neither carries one. + Kind string + // Application is that stereotype's application. + Application *Stereotype + // Behavior is the behavior a CallBehaviorAction calls; nil otherwise. + Behavior *Element + // Targets are the node's explicit targets: its targets tag or its Expose + // suppliers; nil when the step works on what the chain feeds it. + Targets []ElementRef + // Branches are the parallel chains a fork opened, each ending at the + // node that rejoins them; Kind then names the join: "Union" for a merge, + // "Intersection" for a join, "XOR" for a decision. + Branches [][]*DocGenStep + // Malformed is why the step could not be read, "" when it could. + Malformed string +} + +// Int reads an integer tag of the step's application; def when absent. +func (s *DocGenStep) Int(name string, def int) (int, error) { + if s.Application == nil { + return def, nil + } + v := s.Application.Tag(name) + if v == "" { + return def, nil + } + n, err := strconv.Atoi(v) + if err != nil { + return 0, fmt.Errorf("%s %q is not an integer", name, v) + } + return n, nil +} + +// Flag reads a boolean tag of the step's application; def when absent. +func (s *DocGenStep) Flag(name string, def bool) bool { + if s.Application == nil { + return def + } + switch s.Application.Tag(name) { + case "true": + return true + case "false": + return false + } + return def +} + +// docGenNamespaces are the two profiles DocGen content is read from. +var docGenNamespaces = []string{DocGenNS, DocGenCollaboratorNS} + +// DocGen returns the DocGen application on e, from either DocGen profile. +func (e *Element) DocGen() *Stereotype { + if e == nil { + return nil + } + for _, s := range e.Stereotypes { + for _, ns := range docGenNamespaces { + if s.Namespace == ns { + return s + } + } + } + return nil +} + +// readDocuments reads every DocGen document once every document is read; a +// class several applications name is one document. +func (m *Model) readDocuments() { + r := &docGenReader{m: m, comments: map[string][]*Stereotype{}, placed: map[*Stereotype]bool{}} + for _, s := range m.Stereotypes { + if isCollaboratorParagraph(s) { + owner := s.Tag("ownerId") + r.comments[owner] = append(r.comments[owner], s) + } + } + seen := map[*Element]bool{} + for _, s := range m.Stereotypes { + if s.Name != "Document" || s.Base == nil || s.Base.Type != "Class" || seen[s.Base] { + continue + } + if s.Namespace != DocGenNS && s.Namespace != DocGenCollaboratorNS { + continue + } + seen[s.Base] = true + r.doc = s.Base + doc := &DocGenDocument{Class: s.Base, Application: s} + doc.Root = r.view(s.Base, nil, map[*Element]bool{}, true) + m.Documents = append(m.Documents, doc) + } + for _, s := range m.Stereotypes { + if !isCollaboratorParagraph(s) || r.placed[s] { + continue + } + p := &DocGenParagraph{Application: s, Comment: s.Base, Image: s.Name == "CollaboratorImageParagraph"} + if id := s.Tag("viewId"); id != "" && !seen[m.Lookup(id)] { + p.Malformed = fmt.Sprintf("viewId %q names no document", id) + } else { + p.Malformed = fmt.Sprintf("ownerId %q names no view of the document", s.Tag("ownerId")) + } + m.StrayParagraphs = append(m.StrayParagraphs, p) + } +} + +// isCollaboratorParagraph matches the collaborator profile's paragraph applications. +func isCollaboratorParagraph(s *Stereotype) bool { + return s.Namespace == DocGenCollaboratorNS && (s.Name == "CollaboratorParagraph" || s.Name == "CollaboratorImageParagraph") +} + +type docGenReader struct { + m *Model + comments map[string][]*Stereotype + // doc is the document class whose views are being read; placed are the + // paragraph applications some view of some document has shown. + doc *Element + placed map[*Stereotype]bool +} + +// isDocGenView reports whether e is a view class: the SysML View stereotype +// or the DocGen view stereotype applies to it. +func isDocGenView(e *Element) bool { + return e.Type == "Class" && (isSysMLStereotyped(e, "View") || e.DocGenView()) +} + +// DocGenView reports whether the DocGen profile's own view stereotype applies +// to e, which makes a class a view as the SysML «View» does. +func (e *Element) DocGenView() bool { + s := e.DocGen() + return s != nil && s.Namespace == DocGenNS && (s.Name == "view" || s.Name == "Dynamic_View") +} + +// IsDocGenProfile reports whether ns is one of the DocGen profiles the +// document mapping reads. +func IsDocGenProfile(ns string) bool { + return ns == DocGenNS || ns == DocGenCollaboratorNS +} + +// view reads one view placed by property p of its parent (nil at the root) +// and, when recurse, its children; a view already on the path is not +// entered twice. +func (r *docGenReader) view(class, p *Element, path map[*Element]bool, recurse bool) *DocGenView { + m := r.m + v := &DocGenView{Class: class, Paragraphs: r.paragraphs(class)} + path[class] = true + defer delete(path, class) + for _, g := range class.Owned("generalization") { + if !isSysMLStereotyped(g, "Conform") { + continue + } + if general := m.Ref(g, "general"); general != nil { + v.Viewpoint = general + } else { + v.Malformed = append(v.Malformed, fmt.Sprintf("Conform general %q names no element", g.Attrs["general"])) + } + } + if v.Viewpoint != nil { + v.Method, v.MethodMalformed = m.viewpointMethod(v.Viewpoint) + } + v.Exposed = m.exposed(class) + if p != nil && composite(p) { + v.Exposed = append(v.Exposed, m.exposed(p)...) + } + if !recurse { + return v + } + for _, p := range class.Owned("ownedAttribute") { + t := m.Ref(p, "type") + if p.Type != "Property" || t == nil || !isDocGenView(t) { + continue + } + if path[t] { + v.Malformed = append(v.Malformed, fmt.Sprintf("view %s contains itself", t.Name)) + continue + } + aggregation := p.Attrs["aggregation"] + v.Children = append(v.Children, r.view(t, p, path, aggregation != "" && aggregation != "none")) + } + return v +} + +// paragraphs reads the collaborator paragraphs placed in a view of the +// current document (viewId, when set, names the document class): in +// application order, each moved behind the paragraph its siblingId names. +func (r *docGenReader) paragraphs(class *Element) []*DocGenParagraph { + byComment := map[string]*DocGenParagraph{} + var out []*DocGenParagraph + for _, s := range r.comments[class.ID] { + if id := s.Tag("viewId"); id != "" && id != r.doc.ID { + continue + } + r.placed[s] = true + p := &DocGenParagraph{Application: s, Comment: s.Base, Image: s.Name == "CollaboratorImageParagraph"} + switch { + case s.Base == nil: + p.Malformed = fmt.Sprintf("base_Element %q names no element", s.BaseID) + case s.Base.Type != "Comment": + p.Malformed = fmt.Sprintf("base_Element names a %s, not a Comment", s.Base.Type) + case s.Tag("property") != "" && s.Tag("property") != collaboratorBody: + p.Malformed = fmt.Sprintf("property %q is not the comment body", s.Tag("property")) + } + if s.BaseID != "" { + byComment[s.BaseID] = p + } + out = append(out, p) + } + followers := map[*DocGenParagraph][]*DocGenParagraph{} + var heads []*DocGenParagraph + for _, p := range out { + after := byComment[p.Application.Tag("siblingId")] + if after == nil || after == p { + heads = append(heads, p) + continue + } + p.Placed = true + followers[after] = append(followers[after], p) + } + ordered := make([]*DocGenParagraph, 0, len(out)) + placed := map[*DocGenParagraph]bool{} + var place func(p *DocGenParagraph) + place = func(p *DocGenParagraph) { + if placed[p] { + return + } + placed[p] = true + ordered = append(ordered, p) + for _, f := range followers[p] { + place(f) + } + } + for _, p := range heads { + place(p) + } + // Paragraphs only reachable through a siblingId cycle keep document order. + for _, p := range out { + if !placed[p] { + p.Placed = false + place(p) + } + } + return ordered +} + +// collaboratorBody is the property tag naming the comment body. +const collaboratorBody = "META:QPROP:Element:body" + +// composite reports whether a property is a composite end. +func composite(p *Element) bool { return p.Attrs["aggregation"] == "composite" } + +// exposed lists the suppliers of e's Expose dependencies and the targets of +// its element and package imports, as DocGen feeds them to the method. +func (m *Model) exposed(e *Element) []ElementRef { + var out []ElementRef + for _, imp := range e.Owned("elementImport") { + out = append(out, m.elementRefs(imp, "importedElement")...) + } + for _, imp := range e.Owned("packageImport") { + out = append(out, m.elementRefs(imp, "importedPackage")...) + } + for _, dep := range m.dependenciesOf(e) { + if isSysMLStereotyped(dep, "Expose") { + out = append(out, m.elementRefs(dep, "supplier")...) + } + } + return out +} + +// dependenciesOf lists the dependencies whose client is e, wherever the +// document packages them, in document order. +func (m *Model) dependenciesOf(e *Element) []*Element { + if m.clients == nil { + m.clients = map[*Element][]*Element{} + for _, root := range m.Roots { + m.indexClients(root) + } + } + return m.clients[e] +} + +func (m *Model) indexClients(e *Element) { + if e.Type == "Dependency" || e.Type == "Abstraction" || e.Type == "Realization" { + for _, c := range m.Refs(e, "client") { + m.clients[c] = append(m.clients[c], e) + } + } + for _, c := range e.Children { + m.indexClients(c) + } +} + +// elementRefs lists a role's targets with their raw ids, dangling ones included. +func (m *Model) elementRefs(e *Element, role string) []ElementRef { + var out []ElementRef + for _, id := range e.RefIDs(role) { + out = append(out, ElementRef{ID: id, Element: m.Lookup(id)}) + } + return out +} + +// viewpointMethod finds the method activity of a viewpoint: the behaviors +// of its Viewpoint application's method tag, else its classifier behavior, +// else the owned behaviors that specify its operation named View; why says +// what a method tag that yields no activity named, when no route does. +func (m *Model) viewpointMethod(vp *Element) (method *Element, why string) { + if s := vp.Stereotype("Viewpoint"); s != nil { + for _, id := range s.IDs("method") { + b := m.Lookup(id) + switch { + case b == nil: + why = fmt.Sprintf("method %q names no element", id) + case b.Type != "Activity": + why = fmt.Sprintf("method %q names a %s, not an Activity", id, b.Type) + default: + return b, "" + } + } + } + if b := m.Ref(vp, "classifierBehavior"); b != nil && b.Type == "Activity" { + return b, "" + } + for _, b := range vp.Owned("ownedBehavior") { + spec := m.Ref(b, "specification") + if b.Type == "Activity" && spec != nil && spec.Parent == vp && spec.Name == "View" { + return b, "" + } + } + return nil, why +} + +// isSysMLStereotyped reports whether a stereotype of the SysML profile +// with the given name applies to e. +func isSysMLStereotyped(e *Element, name string) bool { + for _, s := range e.Stereotypes { + if s.Name == name && IsSysMLNamespace(s.Namespace) { + return true + } + } + return false +} + +// DocGenChain reads the activity chain of a DocGen behavior or structured +// node: the steps reached from its initial node along single control flows, +// with parallel branches folded into the step that rejoins them. +func (m *Model) DocGenChain(a *Element) ([]*DocGenStep, string) { + var initial *Element + for _, n := range a.Owned("node") { + if n.Type == "InitialNode" { + initial = n + break + } + } + if initial == nil { + return nil, "no initial node" + } + if why := m.danglingFlow(a); why != "" { + return nil, why + } + w := &chainWalker{m: m, out: m.flows(a, "source", "target"), in: m.flows(a, "target", "source")} + steps, end := w.walk(initial, nil) + return steps, end +} + +type chainWalker struct { + m *Model + out, in map[*Element][]*Element + seen map[*Element]bool +} + +// controlFlows are the edges of an activity or structured node that order +// its steps; object flows carry data between pins and are not followed. +func controlFlows(a *Element) []*Element { + var flows []*Element + for _, e := range a.Owned("edge") { + if e.Type == "ControlFlow" { + flows = append(flows, e) + } + } + return flows +} + +// danglingFlow reports the first control flow of a whose source or target +// names no node; such an edge could lead anywhere, so the chain is unreadable. +func (m *Model) danglingFlow(a *Element) string { + for _, e := range controlFlows(a) { + for _, role := range []string{"source", "target"} { + if m.Ref(e, role) != nil { + continue + } + if ids := e.RefIDs(role); len(ids) > 0 { + return fmt.Sprintf("%s %s's %s %q names no node", nodeName(e), e.ID, role, ids[0]) + } + return fmt.Sprintf("%s %s has no %s", nodeName(e), e.ID, role) + } + } + return "" +} + +// flows indexes the control flows of an activity or structured node by one +// end, listing the other end in edge order. +func (m *Model) flows(a *Element, from, to string) map[*Element][]*Element { + idx := map[*Element][]*Element{} + for _, e := range controlFlows(a) { + idx[m.Ref(e, from)] = append(idx[m.Ref(e, from)], m.Ref(e, to)) + } + return idx +} + +// walk follows single control flows from cur until the chain ends or stop +// is reached; it returns the steps and why the chain ended, "" when cleanly. +func (w *chainWalker) walk(cur *Element, stop *Element) ([]*DocGenStep, string) { + if w.seen == nil { + w.seen = map[*Element]bool{} + } + var steps []*DocGenStep + for { + next := w.out[cur] + if len(next) == 0 { + return steps, "" + } + if len(next) > 1 && cur.Type != "ForkNode" { + return steps, fmt.Sprintf("%s has %d outgoing flows", nodeName(cur), len(next)) + } + if cur.Type == "ForkNode" { + step, join, why := w.fork(cur, next) + if why != "" { + return steps, why + } + steps = append(steps, step) + cur = join + continue + } + n := next[0] + if n == stop { + return steps, "" + } + if w.seen[n] { + return steps, fmt.Sprintf("%s is reached twice", nodeName(n)) + } + w.seen[n] = true + if n.Type == "ActivityFinalNode" || n.Type == "FlowFinalNode" { + return steps, "" + } + if n.Type != "ForkNode" { + steps = append(steps, w.m.docGenStep(n)) + } + cur = n + } +} + +// fork reads the branches of a fork up to the node that rejoins them. +func (w *chainWalker) fork(fork *Element, heads []*Element) (*DocGenStep, *Element, string) { + step := &DocGenStep{Node: fork, Application: fork.DocGen()} + if step.Application != nil { + step.Kind = step.Application.Name + } + var join *Element + for _, head := range heads { + branch, end := w.branch(head, &join) + if end != "" { + return nil, nil, end + } + step.Branches = append(step.Branches, branch) + } + if join == nil { + return nil, nil, fmt.Sprintf("%s's branches never rejoin", nodeName(fork)) + } + if kind := joinKind(join); kind != "" { + step.Kind = kind + } else { + step.Malformed = fmt.Sprintf("%s joins branches without a Union", nodeName(join)) + } + return step, join, "" +} + +// branch walks one fork branch until a node with several incoming flows, +// which every branch must share. +func (w *chainWalker) branch(head *Element, join **Element) ([]*DocGenStep, string) { + var steps []*DocGenStep + cur := head + for { + if len(w.in[cur]) > 1 { + if *join == nil { + *join = cur + } else if *join != cur { + return steps, fmt.Sprintf("branches rejoin at both %s and %s", nodeName(*join), nodeName(cur)) + } + return steps, "" + } + if w.seen[cur] { + return steps, fmt.Sprintf("%s is reached twice", nodeName(cur)) + } + w.seen[cur] = true + steps = append(steps, w.m.docGenStep(cur)) + next := w.out[cur] + if len(next) != 1 { + return steps, fmt.Sprintf("%s has %d outgoing flows inside a fork", nodeName(cur), len(next)) + } + cur = next[0] + } +} + +// joinKind names the set operation a rejoining node performs. +func joinKind(n *Element) string { + switch n.Type { + case "MergeNode": + return "Union" + case "JoinNode": + return "Intersection" + case "DecisionNode": + return "XOR" + } + return "" +} + +// docGenStep types one activity node. +func (m *Model) docGenStep(n *Element) *DocGenStep { + s := &DocGenStep{Node: n} + if n.Type == "CallBehaviorAction" { + s.Behavior = m.Ref(n, "behavior") + if s.Behavior == nil && len(n.RefIDs("behavior")) > 0 { + s.Malformed = fmt.Sprintf("behavior %q names no element", n.RefIDs("behavior")[0]) + } + } + s.Application = n.DocGen() + if s.Application == nil && s.Behavior != nil { + s.Application = s.Behavior.DocGen() + } + if s.Application != nil { + s.Kind = s.Application.Name + } + s.Targets = m.stepTargets(n, s.Behavior) + return s +} + +// stepTargets reads a node's explicit targets as DocGen does: its targets +// tag, else its Expose suppliers, else the same of its called behavior. +func (m *Model) stepTargets(n, behavior *Element) []ElementRef { + for _, e := range []*Element{n, behavior} { + if e == nil { + continue + } + if s := e.DocGen(); s != nil { + if refs := m.TagRefs(s, "targets"); len(refs) > 0 { + return refs + } + } + if refs := m.exposed(e); len(refs) > 0 { + return refs + } + } + return nil +} + +func nodeName(n *Element) string { + if n.Name != "" { + return fmt.Sprintf("%s %q", n.Type, n.Name) + } + return n.Type +} diff --git a/internal/translate/xmi/sysmlv1/docgen_flows_test.go b/internal/translate/xmi/sysmlv1/docgen_flows_test.go new file mode 100644 index 0000000000..d0a2c41e88 --- /dev/null +++ b/internal/translate/xmi/sysmlv1/docgen_flows_test.go @@ -0,0 +1,109 @@ +package sysmlv1 + +import ( + "fmt" + "testing" +) + +// Object flows carry data between pins, not the document's order: an object +// flow beside the control chain, even a malformed one, leaves the chain as it is. +func TestDocGenChainIgnoresObjectFlows(t *testing.T) { + const method = `<?xml version="1.0"?> +<xmi:XMI xmi:version="2.5.1" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:uml="http://www.omg.org/spec/UML/20161101" + xmlns:Document_Profile_="http://www.magicdraw.com/schemas/manual/Document_Profile.xmi"> + <uml:Model xmi:id="_m" name="M"> + <packagedElement xmi:type="uml:Activity" xmi:id="_act" name="Method"> + <node xmi:type="uml:InitialNode" xmi:id="_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_collect" name="Collect"> + <result xmi:type="uml:OutputPin" xmi:id="_collect_out"/> + </node> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_filter" name="Filter"> + <argument xmi:type="uml:InputPin" xmi:id="_filter_in"/> + </node> + <node xmi:type="uml:StructuredActivityNode" xmi:id="_table" name="Table"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_e1" source="_init" target="_collect"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_e2" source="_collect" target="_filter"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_e3" source="_filter" target="_table"/> + %s + </packagedElement> + </uml:Model> + <Document_Profile_:CollectOwnedElements xmi:id="_st_c" base_Element="_collect"/> + <Document_Profile_:FilterByNames xmi:id="_st_f" base_Element="_filter"/> + <Document_Profile_:TableStructure xmi:id="_st_t" base_Element="_table"/> +</xmi:XMI>` + for _, tc := range []struct{ name, edges string }{ + {"no object flow", ``}, + {"pin to pin", `<edge xmi:type="uml:ObjectFlow" xmi:id="_o1" source="_collect_out" target="_filter_in"/>`}, + {"node to node", `<edge xmi:type="uml:ObjectFlow" xmi:id="_o1" source="_collect" target="_table"/>`}, + {"dangling", `<edge xmi:type="uml:ObjectFlow" xmi:id="_o1" source="_collect_out" target="_missing"/>`}, + {"no target", `<edge xmi:type="uml:ObjectFlow" xmi:id="_o1" source="_collect_out"/>`}, + } { + m, err := Parse([]byte(fmt.Sprintf(method, tc.edges))) + if err != nil { + t.Fatal(err) + } + steps, end := m.DocGenChain(m.Lookup("_act")) + if end != "" { + t.Errorf("%s: chain refused: %s", tc.name, end) + continue + } + var got []string + for _, s := range steps { + got = append(got, s.Kind) + } + if want := "[CollectOwnedElements FilterByNames TableStructure]"; fmt.Sprint(got) != want { + t.Errorf("%s: steps %v, want %s", tc.name, got, want) + } + } +} + +// A viewpoint whose method tag names no activity is malformed, and the view +// says so; a viewpoint that declares no method at all has none. +func TestDocGenViewKeepsWhyMethodIsMissing(t *testing.T) { + const model = `<?xml version="1.0"?> +<xmi:XMI xmi:version="2.5.1" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:uml="http://www.omg.org/spec/UML/20161101" + xmlns:sysml="http://www.omg.org/spec/SysML/20181001/SysML" + xmlns:Document_Profile_="http://www.magicdraw.com/schemas/manual/Document_Profile.xmi"> + <uml:Model xmi:id="_m" name="M"> + <packagedElement xmi:type="uml:Class" xmi:id="_vp" name="VP"> + <ownedBehavior xmi:type="uml:Activity" xmi:id="_act" name="Method"/> + <ownedBehavior xmi:type="uml:StateMachine" xmi:id="_sm" name="Machine"/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_doc" name="Doc"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen" general="_vp"/> + </packagedElement> + </uml:Model> + <Document_Profile_:Document xmi:id="_st_doc" base_Class="_doc"/> + <sysml:Viewpoint xmi:id="_st_vp" base_Class="_vp" %s/> + <sysml:Conform xmi:id="_st_conform" base_Generalization="_gen"/> +</xmi:XMI>` + for _, tc := range []struct{ tag, method, why string }{ + {``, "", ""}, + {`method="_act"`, "_act", ""}, + {`method="_gone"`, "", `method "_gone" names no element`}, + {`method="_sm"`, "", `method "_sm" names a StateMachine, not an Activity`}, + {`method="_gone _act"`, "_act", ""}, + } { + m, err := Parse([]byte(fmt.Sprintf(model, tc.tag))) + if err != nil { + t.Fatal(err) + } + if len(m.Documents) != 1 { + t.Fatalf("%s: %d documents, want 1", tc.tag, len(m.Documents)) + } + v := m.Documents[0].Root + if v.Viewpoint == nil || v.Viewpoint.ID != "_vp" { + t.Fatalf("%s: viewpoint %v, want VP", tc.tag, v.Viewpoint) + } + var method string + if v.Method != nil { + method = v.Method.ID + } + if method != tc.method { + t.Errorf("%s: method %q, want %q", tc.tag, method, tc.method) + } + if v.MethodMalformed != tc.why { + t.Errorf("%s: malformed %q, want %q", tc.tag, v.MethodMalformed, tc.why) + } + } +} diff --git a/internal/translate/xmi/sysmlv1/docgen_test.go b/internal/translate/xmi/sysmlv1/docgen_test.go new file mode 100644 index 0000000000..4b99551d9c --- /dev/null +++ b/internal/translate/xmi/sysmlv1/docgen_test.go @@ -0,0 +1,126 @@ +package sysmlv1 + +import ( + "fmt" + "testing" +) + +// A document's view tree is DocGen's: every view-typed property of a view is +// a child section in declaration order, and only a composite (or shared) +// property's view is entered for its own children; a plain reference is a +// leaf, and the exposures on the referencing property are not the view's. +func TestDocGenViewTreeFollowsAggregation(t *testing.T) { + m, err := Parse([]byte(`<?xml version="1.0"?> +<xmi:XMI xmi:version="2.5.1" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:uml="http://www.omg.org/spec/UML/20161101" + xmlns:sysml="http://www.omg.org/spec/SysML/20181001/SysML" + xmlns:Document_Profile_="http://www.magicdraw.com/schemas/manual/Document_Profile.xmi"> + <uml:Model xmi:id="_m" name="M"> + <packagedElement xmi:type="uml:Class" xmi:id="_doc" name="Doc"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_p_owned" name="owned" type="_owned" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_p_linked" name="linked" type="_linked"> + <ownedComment xmi:type="uml:Comment" xmi:id="_c" body="a note"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_p_shared" name="shared" type="_shared" aggregation="shared"/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_owned" name="Owned"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_p_leaf1" name="leaf" type="_leaf" aggregation="composite"/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_linked" name="Linked"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_p_leaf2" name="leaf" type="_leaf" aggregation="composite"/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_shared" name="Shared"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_p_leaf3" name="leaf" type="_leaf" aggregation="composite"/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_leaf" name="Leaf"/> + <packagedElement xmi:type="uml:Class" xmi:id="_target" name="Target"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_dep_owned" client="_p_owned" supplier="_target"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_dep_linked" client="_p_linked" supplier="_target"/> + </uml:Model> + <Document_Profile_:Document xmi:id="_st_doc" base_Class="_doc"/> + <Document_Profile_:view xmi:id="_st_owned" base_Class="_owned"/> + <Document_Profile_:view xmi:id="_st_linked" base_Class="_linked"/> + <Document_Profile_:view xmi:id="_st_shared" base_Class="_shared"/> + <Document_Profile_:view xmi:id="_st_leaf" base_Class="_leaf"/> + <sysml:Expose xmi:id="_st_expose_owned" base_Dependency="_dep_owned"/> + <sysml:Expose xmi:id="_st_expose_linked" base_Dependency="_dep_linked"/> +</xmi:XMI>`)) + if err != nil { + t.Fatal(err) + } + if len(m.Documents) != 1 { + t.Fatalf("%d documents, want 1", len(m.Documents)) + } + root := m.Documents[0].Root + names := func(vs []*DocGenView) []string { + var out []string + for _, v := range vs { + out = append(out, v.Class.Name) + } + return out + } + if got := names(root.Children); len(got) != 3 || got[0] != "Owned" || got[1] != "Linked" || got[2] != "Shared" { + t.Fatalf("sections = %v, want [Owned Linked Shared]", got) + } + owned, linked, shared := root.Children[0], root.Children[1], root.Children[2] + if got := names(owned.Children); len(got) != 1 || got[0] != "Leaf" { + t.Errorf("composite view's children = %v, want [Leaf]", got) + } + if got := names(shared.Children); len(got) != 1 || got[0] != "Leaf" { + t.Errorf("shared view's children = %v, want [Leaf]", got) + } + if len(linked.Children) != 0 { + t.Errorf("referenced view's children = %v, want none", names(linked.Children)) + } + if len(owned.Exposed) != 1 || owned.Exposed[0].Element == nil || owned.Exposed[0].Element.ID != "_target" { + t.Errorf("composite property's exposure = %+v, want Target", owned.Exposed) + } + if len(linked.Exposed) != 0 { + t.Errorf("referencing property's exposure = %+v, want none", linked.Exposed) + } + for _, v := range []*DocGenView{root, owned, linked, shared} { + if len(v.Malformed) != 0 { + t.Errorf("%s malformed: %v", v.Class.Name, v.Malformed) + } + } +} + +// A control flow whose source or target names no node makes the whole chain +// unreadable: the walk refuses it instead of ending cleanly where the edge is lost. +func TestDocGenChainRefusesDanglingFlows(t *testing.T) { + const method = `<?xml version="1.0"?> +<xmi:XMI xmi:version="2.5.1" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:uml="http://www.omg.org/spec/UML/20161101" + xmlns:Document_Profile_="http://www.magicdraw.com/schemas/manual/Document_Profile.xmi"> + <uml:Model xmi:id="_m" name="M"> + <packagedElement xmi:type="uml:Activity" xmi:id="_act" name="Method"> + <node xmi:type="uml:InitialNode" xmi:id="_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_collect" name="Collect"/> + <node xmi:type="uml:StructuredActivityNode" xmi:id="_table" name="Table"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_e1" source="_init" target="_collect"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_e2" %s/> + </packagedElement> + </uml:Model> + <Document_Profile_:CollectOwnedElements xmi:id="_st_c" base_Element="_collect"/> + <Document_Profile_:TableStructure xmi:id="_st_t" base_Element="_table"/> +</xmi:XMI>` + for _, tc := range []struct{ edge, want string }{ + {`source="_collect" target="_table"`, ""}, + {`source="_collect" target="_missing"`, `ControlFlow _e2's target "_missing" names no node`}, + {`source="_missing" target="_table"`, `ControlFlow _e2's source "_missing" names no node`}, + {`source="_collect"`, `ControlFlow _e2 has no target`}, + } { + m, err := Parse([]byte(fmt.Sprintf(method, tc.edge))) + if err != nil { + t.Fatal(err) + } + steps, end := m.DocGenChain(m.Lookup("_act")) + if end != tc.want { + t.Errorf("edge %s: chain ended with %q, want %q", tc.edge, end, tc.want) + } + if want := 2; tc.want == "" && len(steps) != want { + t.Errorf("edge %s: %d steps, want %d", tc.edge, len(steps), want) + } + if tc.want != "" && steps != nil { + t.Errorf("edge %s: a refused chain still has steps %v", tc.edge, steps) + } + } +} diff --git a/internal/translate/xmi/sysmlv1/modules.go b/internal/translate/xmi/sysmlv1/modules.go new file mode 100644 index 0000000000..5d8584edbc --- /dev/null +++ b/internal/translate/xmi/sysmlv1/modules.go @@ -0,0 +1,82 @@ +package sysmlv1 + +import ( + "bytes" + "strings" + + "github.com/Open-MBEE/OpenSysML/internal/translate/xmi" +) + +// moduleStereotype is a stereotype a used module's snapshot declares: its name +// and the profile element owning it. The snapshot is not part of the model. +type moduleStereotype struct { + name string + profile *xmi.Element +} + +// moduleEntry reports whether an archive entry is a MagicDraw snapshot of a +// used module's shared model, which declares the module's stereotypes by id. +func moduleEntry(name string) bool { + lower := strings.ToLower(name) + return strings.HasSuffix(lower, "shared_umodel$dsnapshot") || strings.HasSuffix(lower, "shared_umodel.snapshot") +} + +// indexModule reads one module snapshot: every stereotype it declares, keyed +// by id, with the profile owning it. A snapshot that is not XML is ignored. +func (m *Model) indexModule(data []byte) { + doc, err := xmi.Parse(bytes.NewReader(data)) + if err != nil || doc.Root == nil { + return + } + if m.moduleStereotypes == nil { + m.moduleStereotypes = map[string]moduleStereotype{} + } + var walk func(e, profile *xmi.Element) + walk = func(e, profile *xmi.Element) { + if isModuleKind(e, "Profile") { + profile = e + } + if isModuleKind(e, "Stereotype") && e.ID != "" && profile != nil { + m.moduleStereotypes[e.ID] = moduleStereotype{name: e.Name(), profile: profile} + } + for _, c := range e.Children { + walk(c, profile) + } + } + walk(doc.Root, nil) +} + +// isModuleKind reports whether a snapshot element is of the UML kind, by its +// xmi:type or xsi:type or, at the root, by its uml-namespaced tag. +func isModuleKind(e *xmi.Element, kind string) bool { + if e.Type != "" { + return local(e.Type) == kind + } + if t := e.Attr("type"); t != "" { + return local(t) == kind + } + return e.Tag == kind && xmi.IsUMLNamespace(e.Space) +} + +// bindModuleProfiles gives each module profile the namespace the document's +// stereotype table binds one of its stereotypes to, so ids of the profile's +// other stereotypes resolve to a name and namespace too. +func (m *Model) bindModuleProfiles() { + profiles := map[*xmi.Element]string{} + for id, known := range m.stereotypeNames { + if s, ok := m.moduleStereotypes[id]; ok { + profiles[s.profile] = known.namespace + } + } + for id, s := range m.moduleStereotypes { + if _, known := m.stereotypeNames[id]; known { + continue + } + if ns, ok := profiles[s.profile]; ok { + if m.stereotypeNames == nil { + m.stereotypeNames = map[string]stereotypeName{} + } + m.stereotypeNames[id] = stereotypeName{name: s.name, namespace: ns} + } + } +} diff --git a/internal/translate/xmi/sysmlv1/profiles.go b/internal/translate/xmi/sysmlv1/profiles.go new file mode 100644 index 0000000000..480de4b352 --- /dev/null +++ b/internal/translate/xmi/sysmlv1/profiles.go @@ -0,0 +1,184 @@ +package sysmlv1 + +import ( + "net/url" + "strings" + + "github.com/Open-MBEE/OpenSysML/internal/translate/xmi" +) + +// IsSysMLNamespace reports whether ns is an OMG SysML v1 profile namespace, +// of any version, or Papyrus' copy of it. +func IsSysMLNamespace(ns string) bool { + u, err := url.Parse(ns) + if err != nil { + return false + } + host := strings.TrimPrefix(strings.ToLower(u.Hostname()), "www.") + switch { + case host == "omg.org" || strings.HasSuffix(host, ".omg.org"): + return strings.HasPrefix(u.Path, "/spec/SysML/") + case host == "eclipse.org" || strings.HasSuffix(host, ".eclipse.org"): + return strings.HasPrefix(strings.ToLower(u.Path), "/papyrus/sysml/") + } + return false +} + +// Exact namespaces of the tool profiles the reader gives a typed form. Cameo +// serializes user profiles under the same host as its own, so only an exact +// namespace, never its host, identifies tool content. +const ( + // MagicDrawProfileNS is MagicDraw's own profile: tables and relation maps. + MagicDrawProfileNS = "http://www.omg.org/spec/UML/20131001/MagicDrawProfile" + // DependencyMatrixNS is MagicDraw's dependency matrix profile. + DependencyMatrixNS = "http://www.magicdraw.com/schemas/Dependency_Matrix_Profile.xmi" + // DocGenNS is the Open-MBEE MDK document generation profile. + DocGenNS = "http://www.magicdraw.com/schemas/manual/Document_Profile.xmi" + // DocGenCollaboratorNS is the MDK view collaborator profile. + DocGenCollaboratorNS = "http://www.magicdraw.com/schemas/manual/Document_View_Collaborator_Profile.xmi" +) + +// StereotypeRef names a stereotype an id refers to: by the namespace and name +// its profile declared it under when the document tells, by its element when +// the profile is bundled, and by the raw id always. +type StereotypeRef struct { + // ID is the id or href as written. + ID string + // Name is the stereotype's name; "" when the document does not tell. + Name string + // Namespace is the profile namespace the name belongs to; "" when unknown. + Namespace string + // Element is the bundled stereotype element; nil when the profile is not + // in the read documents. + Element *Element +} + +// Known reports whether the document told what stereotype the id names. +func (r StereotypeRef) Known() bool { + return r.Name != "" +} + +// stereotypeNames indexes MagicDraw's stereotypesHREFS table: what a tool +// stereotype id or href is called and under which namespace. +type stereotypeName struct { + name, namespace string +} + +// indexStereotypes reads one stereotypesHREFS table: each entry names a +// stereotype "prefix:Name" and its href, the prefix bound by an xmlns +// declaration in scope. Entries whose prefix is unbound are skipped. The +// first href recorded for a namespace and name is the one applications +// resolve their definition through. +func (m *Model) indexStereotypes(raw *xmi.Element) { + for _, entry := range raw.Children { + if entry.Tag != "stereotype" { + continue + } + prefix, name, ok := strings.Cut(entry.Attr("name"), ":") + href := entry.Attr("stereotypeHREF") + if !ok || name == "" || href == "" { + continue + } + namespace := entry.Namespace(prefix) + if namespace == "" { + continue + } + if m.stereotypeNames == nil { + m.stereotypeNames = map[string]stereotypeName{} + } + key := stereotypeKey{namespace, name} + if _, dup := m.stereotypeHrefs[key]; !dup { + m.stereotypeHrefs[key] = href + } + known := stereotypeName{name: name, namespace: namespace} + m.stereotypeNames[href] = known + if i := strings.LastIndexByte(href, '#'); i >= 0 { + m.stereotypeNames[href[i+1:]] = known + } + } +} + +// StereotypeRef resolves an id or href to the stereotype it names: through +// the tool's stereotype table, else through a bundled Stereotype element, +// whose profile namespace is the owning Profile's URI when it declares one. +func (m *Model) StereotypeRef(id string) StereotypeRef { + ref := StereotypeRef{ID: id, Element: m.byID[id]} + frag := id + if i := strings.LastIndexByte(id, '#'); i >= 0 { + frag = id[i+1:] + if ref.Element == nil { + ref.Element = m.byID[frag] + } + } + for _, key := range []string{id, frag} { + if known, ok := m.stereotypeNames[key]; ok { + ref.Name, ref.Namespace = known.name, known.namespace + return ref + } + } + if e := ref.Element; e != nil && e.Type == "Stereotype" { + ref.Name = e.Name + for p := e.Parent; p != nil; p = p.Parent { + if p.Type == "Profile" { + ref.Namespace = p.Attrs["URI"] + break + } + } + } + return ref +} + +// TagRefs resolves a reference-valued tag of a stereotype application to the +// elements it names, one ElementRef per id in serialized order, an href of +// another document yielding its proxy and a dangling id a nil Element. +func (m *Model) TagRefs(s *Stereotype, tag string) []ElementRef { + ids := s.IDs(tag) + if len(ids) == 0 { + return nil + } + refs := make([]ElementRef, 0, len(ids)) + for _, id := range ids { + refs = append(refs, m.elementRef(id)) + } + return refs +} + +// elementRef resolves one id or href as a table definition wrote it. +func (m *Model) elementRef(id string) ElementRef { + ref := ElementRef{ID: id, Element: m.shown(id)} + if ref.Element == nil && strings.Contains(id, "#") { + ref.Element = m.proxy(id) + } + return ref +} + +// Applications lists the stereotype applications of one exact profile +// namespace, in document order. +func (m *Model) Applications(namespace string) []*Stereotype { + var out []*Stereotype + for _, s := range m.Stereotypes { + if s.Namespace == namespace { + out = append(out, s) + } + } + return out +} + +// Applied returns e's application of the named stereotype from one exact +// profile namespace, or nil; a homonym from another profile does not count. +func (e *Element) Applied(namespace, name string) *Stereotype { + if e == nil { + return nil + } + for _, s := range e.Stereotypes { + if s.Name == name && s.Namespace == namespace { + return s + } + } + return nil +} + +// Bool reads a boolean tag; absent or unparsable is false. +func (s *Stereotype) Bool(name string) bool { + return s.Tag(name) == "true" +} diff --git a/internal/translate/xmi/sysmlv1/tables.go b/internal/translate/xmi/sysmlv1/tables.go new file mode 100644 index 0000000000..e4b4ef483c --- /dev/null +++ b/internal/translate/xmi/sysmlv1/tables.go @@ -0,0 +1,312 @@ +package sysmlv1 + +import ( + "fmt" + "strconv" + "strings" +) + +// TableKind is what kind of tabular diagram a Table defines. +type TableKind string + +// The MagicDraw diagram kinds that carry a semantic definition. +const ( + InstanceTable TableKind = "InstanceTable" + DiagramTable TableKind = "DiagramTable" + DependencyMatrix TableKind = "DependencyMatrix" + RelationMap TableKind = "RelationMap" +) + +// Table is the semantic definition of a MagicDraw table, dependency matrix or +// relation map: what it lists, how it filters, sorts and lays out columns, +// read from the tool's exact profile applications on the diagram. The +// presentation tags of those applications are not read. +type Table struct { + // Kind is the table's kind. + Kind TableKind + // Diagram is the diagram the definition applies to; nil when the + // base_Diagram id names no diagram of the read documents. + Diagram *Diagram + // DiagramID is the base_Diagram id as written. + DiagramID string + // Application is the profile application that defines the table; Filter + // is the matrix's MatrixFilter application, nil otherwise. + Application, Filter *Stereotype + // Scope lists the elements whose subtrees are searched for rows: a + // table's scope, a matrix's rowScope, a relation map's contextElement. + Scope []ElementRef + // WholeModel is the takeWholeModelAsScope flag. + WholeModel bool + // RowTypes are the classifiers, stereotypes or metaclasses rows must be + // of: a table's classifiers or rowElementType, a matrix's + // rowElementType, a relation map's elementTypes. + RowTypes []ElementRef + // IncludeSubtypes is whether rows of subtypes of RowTypes are listed too; + // MagicDraw defaults it to true. + IncludeSubtypes bool + // Rows are the elements listed as rows regardless of scope: a table's + // rowElements and additionalElements. + Rows []ElementRef + // Columns are the table's columns in serialized order, hidden ones included. + Columns []Column + // Sorts are the table's sort keys in priority order. + Sorts []Sort + // ColumnScope and ColumnTypes are a matrix's columnScope and + // columnElementType; IncludeColumnSubtypes its includeSubtypesOfColumnTypes. + ColumnScope, ColumnTypes []ElementRef + IncludeColumnSubtypes bool + // Direction is a matrix's direction tag: "Row to column", "Column to row" + // or "Both". + Direction string + // ShowElements is a matrix's showElements tag: "All" or "With relations". + ShowElements string + // Criteria are a matrix's dependencyCriteria or a relation map's + // relationCriterion, in serialized order. + Criteria []Criterion + // Depth is a relation map's depth; 0 when absent. + Depth int + // Malformed lists what in the serialization could not be read, each a + // short phrase naming the tag and the value. + Malformed []string +} + +// ColumnKind classifies a column id. +type ColumnKind string + +// The column ids MagicDraw writes. +const ( + // ColumnTool is a tool column with no model content: row numbers, the + // margin, an empty spacer. + ColumnTool ColumnKind = "tool" + // ColumnProperty is a QPROP:Element:<property> column: a UML property of + // the row element, named by Property. + ColumnProperty ColumnKind = "property" + // ColumnFeature is an IColumn:<id> column: a feature of the row + // classifier, resolved in Feature. + ColumnFeature ColumnKind = "feature" + // ColumnPropertyPair is the PROPERTY_COLUMN / VALUE_COLUMN pair of a + // generic table's property view. + ColumnPropertyPair ColumnKind = "propertyPair" + // ColumnUnknown is an id in a form the reader does not know. + ColumnUnknown ColumnKind = "unknown" +) + +// Column is one column of a table. +type Column struct { + // ID is the column id as written. + ID string + // Kind classifies the id. + Kind ColumnKind + // Property is the QPROP property name: "name", "documentation", "owner"... + Property string + // Feature is the IColumn feature; its Element is nil when dangling. + Feature ElementRef + // Hidden is whether hideColumns lists the column. + Hidden bool +} + +// Sort is one sort key: a column id and a direction. +type Sort struct { + // Column is the column id sorted by, in the form Column.ID uses. + Column string + // Descending is the direction. + Descending bool +} + +// CriterionKind is the form a matrix or relation map criterion takes. +type CriterionKind string + +// The criterion forms MagicDraw's structured expressions take. +const ( + // CriterionRelation walks relationships of one stereotype or metaclass. + CriterionRelation CriterionKind = "relation" + // CriterionMetachain navigates a chain of UML or stereotype properties. + CriterionMetachain CriterionKind = "metachain" + // CriterionProperty reads one UML property. + CriterionProperty CriterionKind = "property" + // CriterionScript evaluates an inline script, such as OCL. + CriterionScript CriterionKind = "script" + // CriterionOther is any other expression form. + CriterionOther CriterionKind = "other" +) + +// Criterion is one dependency or relation criterion of a matrix or relation +// map, decoded from the structured expression the tool serialized. +type Criterion struct { + // Name is the display name the tool recorded; "" when none. + Name string + // Kind is the expression form. + Kind CriterionKind + // Stereotype names the relationship stereotype a relation criterion + // walks; zero when it walks a metaclass instead. + Stereotype StereotypeRef + // Metaclass is the relationship metaclass a relation criterion walks; + // "" when it walks a stereotype. + Metaclass string + // Direction is the walk direction as written: "DIRECT", "REVERSED", + // "BOTH", or "" when the tool wrote none. + Direction string + // IncludeSubtypes is the criterion's includeSubtypes flag. + IncludeSubtypes bool + // Expression is the expression's xsi:type for forms other than a + // relation, and Detail its body: the chain steps, the property, the script. + Expression, Detail string + // Malformed is why the criterion could not be decoded; "" when it could. + Malformed string +} + +// readTables gives every table definition its typed form once every document +// is read, since a definition may precede its diagram or the elements it names. +func (m *Model) readTables() { + filters := map[string]*Stereotype{} + for _, s := range m.Applications(DependencyMatrixNS) { + if s.Name == "MatrixFilter" { + filters[s.BaseID] = s + } + } + for _, s := range m.Stereotypes { + switch { + case s.Namespace == MagicDrawProfileNS && s.Name == string(InstanceTable): + m.Tables = append(m.Tables, m.instanceTable(s)) + case s.Namespace == MagicDrawProfileNS && s.Name == string(DiagramTable): + m.Tables = append(m.Tables, m.diagramTable(s)) + case s.Namespace == MagicDrawProfileNS && s.Name == string(RelationMap): + m.Tables = append(m.Tables, m.relationMap(s)) + case s.Namespace == DependencyMatrixNS && s.Name == string(DependencyMatrix): + m.Tables = append(m.Tables, m.matrix(s, filters[s.BaseID])) + } + } +} + +// newTable reads what every kind shares: the diagram and the scope. +func (m *Model) newTable(kind TableKind, s *Stereotype) *Table { + t := &Table{Kind: kind, Application: s, DiagramID: s.BaseID} + t.Diagram = m.Diagram(t.DiagramID) + if t.Diagram == nil { + t.malformed("base_Diagram", t.DiagramID, "names no diagram") + } + t.WholeModel = s.Bool("takeWholeModelAsScope") + return t +} + +func (t *Table) malformed(tag, value, why string) { + if value == "" { + t.Malformed = append(t.Malformed, fmt.Sprintf("%s: %s", tag, why)) + return + } + t.Malformed = append(t.Malformed, fmt.Sprintf("%s %q: %s", tag, value, why)) +} + +// flag reads a boolean tag MagicDraw defaults to true when absent. +func flag(s *Stereotype, name string) bool { + return s.Tag(name) != "false" +} + +func (m *Model) instanceTable(s *Stereotype) *Table { + t := m.newTable(InstanceTable, s) + t.Scope = m.TagRefs(s, "scope") + t.RowTypes = m.TagRefs(s, "classifiers") + t.IncludeSubtypes = flag(s, "includeSubtypesOfRowTypes") + t.Rows = append(m.TagRefs(s, "rowElements"), m.TagRefs(s, "additionalElements")...) + t.readColumns(m, s) + return t +} + +func (m *Model) diagramTable(s *Stereotype) *Table { + t := m.newTable(DiagramTable, s) + t.Scope = m.TagRefs(s, "scope") + t.RowTypes = m.TagRefs(s, "rowElementType") + t.IncludeSubtypes = flag(s, "includeSubtypesOfRowTypes") + t.Rows = append(m.TagRefs(s, "rowElements"), m.TagRefs(s, "additionalElements")...) + t.readColumns(m, s) + return t +} + +func (m *Model) matrix(s, filter *Stereotype) *Table { + t := m.newTable(DependencyMatrix, s) + t.Filter = filter + t.Direction = s.Tag("direction") + t.ShowElements = s.Tag("showElements") + for _, raw := range s.Tags["dependencyCriteria"] { + t.Criteria = append(t.Criteria, m.criterion(raw)) + } + if filter == nil { + t.malformed("MatrixFilter", "", "no filter application names the diagram") + return t + } + t.Scope = m.TagRefs(filter, "rowScope") + t.RowTypes = m.TagRefs(filter, "rowElementType") + t.IncludeSubtypes = flag(filter, "includeSubtypesOfRowTypes") + t.ColumnScope = m.TagRefs(filter, "columnScope") + t.ColumnTypes = m.TagRefs(filter, "columnElementType") + t.IncludeColumnSubtypes = flag(filter, "includeSubtypesOfColumnTypes") + for _, tag := range []string{"rowQuery", "columnQuery"} { + if v := filter.Tag(tag); v != "" { + t.malformed(tag, "", "a structured query selects the elements") + } + } + return t +} + +func (m *Model) relationMap(s *Stereotype) *Table { + t := m.newTable(RelationMap, s) + t.Scope = m.TagRefs(s, "contextElement") + t.RowTypes = m.TagRefs(s, "elementTypes") + t.IncludeSubtypes = flag(s, "includeSubtypes") + if v := s.Tag("depth"); v != "" { + d, err := strconv.Atoi(v) + if err != nil || d < 0 { + t.malformed("depth", v, "not a non-negative integer") + } else { + t.Depth = d + } + } + for _, raw := range s.Tags["relationCriterion"] { + t.Criteria = append(t.Criteria, m.criterion(raw)) + } + return t +} + +// readColumns reads columnIds, hideColumns and sort. +func (t *Table) readColumns(m *Model, s *Stereotype) { + hidden := map[string]bool{} + for _, id := range s.Tags["hideColumns"] { + hidden[id] = true + } + for _, id := range s.Tags["columnIds"] { + c := m.column(id) + c.Hidden = hidden[id] + t.Columns = append(t.Columns, c) + } + for _, v := range s.Tags["sort"] { + column, direction, ok := strings.Cut(v, "^") + switch { + case !ok: + t.malformed("sort", v, "not in the form <column>^Asc|Desc") + case column == "-1", column == "_EMPTY_", column == "": + // Unsorted, as the tool writes it. + case direction == "Asc", direction == "Desc": + t.Sorts = append(t.Sorts, Sort{Column: column, Descending: direction == "Desc"}) + default: + t.malformed("sort", v, "not in the form <column>^Asc|Desc") + } + } +} + +// column classifies one column id. +func (m *Model) column(id string) Column { + c := Column{ID: id} + switch { + case id == "_NUMBER_", id == "MARGIN_COLUMN", id == "_EMPTY_": + c.Kind = ColumnTool + case id == "PROPERTY_COLUMN", id == "VALUE_COLUMN": + c.Kind = ColumnPropertyPair + case strings.HasPrefix(id, "QPROP:Element:"): + c.Kind, c.Property = ColumnProperty, strings.TrimPrefix(id, "QPROP:Element:") + case strings.HasPrefix(id, "IColumn:"): + c.Kind, c.Feature = ColumnFeature, m.elementRef(strings.TrimPrefix(id, "IColumn:")) + default: + c.Kind = ColumnUnknown + } + return c +} diff --git a/internal/translate/xmi/sysmlv1/xmi.go b/internal/translate/xmi/sysmlv1/xmi.go index 917eb05d62..42738cdb35 100644 --- a/internal/translate/xmi/sysmlv1/xmi.go +++ b/internal/translate/xmi/sysmlv1/xmi.go @@ -84,6 +84,9 @@ type Stereotype struct { // child elements as their text or idref, keyed by tag name. A multi-valued // tag lists each value. Tags map[string][]string + // attrValues counts, per tag, the leading Tags values that came from an + // attribute rather than a child element. + attrValues map[string]int } // Tag returns the first value of a tag, or "". @@ -94,13 +97,17 @@ func (s *Stereotype) Tag(name string) string { return "" } -// IDs returns the ids a reference-valued tag lists, one per value when the -// tool wrote child elements and split on whitespace when it wrote an IDREFS -// attribute. +// IDs returns the ids a reference-valued tag lists: an IDREFS attribute split +// on whitespace, a child element's idref or href kept whole, since an href +// into another archive entry may contain spaces. func (s *Stereotype) IDs(name string) []string { var ids []string - for _, v := range s.Tags[name] { - ids = append(ids, strings.Fields(v)...) + for i, v := range s.Tags[name] { + if i < s.attrValues[name] { + ids = append(ids, strings.Fields(v)...) + } else { + ids = append(ids, v) + } } return ids } @@ -117,8 +124,23 @@ type Model struct { Extensions []Extension // Diagrams are the diagrams read out of those blocks, in document order. Diagrams []Diagram - byID map[string]*Element - proxies map[string]*Element + // Tables are the table, matrix and relation map definitions read out of + // the tool profile applications, in document order. + Tables []*Table + // Documents are the MDK DocGen documents, in document order. + Documents []*DocGenDocument + // StrayParagraphs are collaborator paragraphs no document's view shows. + StrayParagraphs []*DocGenParagraph + byID map[string]*Element + proxies map[string]*Element + // fragments lists the proxies whose hrefs share a fragment, in first-seen + // order; a bare fragment resolves only while one proxy carries it. + fragments map[string][]*Element + stereotypeNames map[string]stereotypeName + // moduleStereotypes are the stereotypes the archive's module snapshots + // declare, by id, for resolving ids the stereotype table does not name. + moduleStereotypes map[string]moduleStereotype + clients map[*Element][]*Element // stereotypeHrefs are the definitions a tool's stereotypesHREFS table // names for applied stereotypes, by namespace and name. stereotypeHrefs map[stereotypeKey]string @@ -345,13 +367,15 @@ func documentEntry(name string) bool { // in an archive that has none, every XMI document among its .xmi/.xml/.uml // files; other XML there is metadata and is left alone. func parseArchive(zr *zip.Reader) (*Model, error) { - var project, documents []*zip.File + var project, modules, documents []*zip.File names := make([]string, 0, len(zr.File)) for _, f := range zr.File { names = append(names, f.Name) switch { case projectEntry(f.Name): project = append(project, f) + case moduleEntry(f.Name): + modules = append(modules, f) case documentEntry(f.Name): documents = append(documents, f) } @@ -365,6 +389,13 @@ func parseArchive(zr *zip.Reader) (*Model, error) { } read++ } + for _, f := range modules { + content, err := readEntry(f) + if err != nil { + return nil, err + } + m.indexModule(content) + } } else { for _, f := range documents { err := m.parseEntry(f) @@ -388,24 +419,33 @@ func parseArchive(zr *zip.Reader) (*Model, error) { return model, nil } -// parseEntry reads one archive entry as an XMI document. -func (m *Model) parseEntry(f *zip.File) error { +// readEntry reads one archive entry within the size bound. +func readEntry(f *zip.File) ([]byte, error) { if f.UncompressedSize64 > maxEntrySize { - return fmt.Errorf("archive entry %s: %d bytes exceeds the %d byte limit", f.Name, f.UncompressedSize64, maxEntrySize) + return nil, fmt.Errorf("archive entry %s: %d bytes exceeds the %d byte limit", f.Name, f.UncompressedSize64, maxEntrySize) } rc, err := f.Open() if err != nil { - return fmt.Errorf("archive entry %s: %w", f.Name, err) + return nil, fmt.Errorf("archive entry %s: %w", f.Name, err) } content, err := io.ReadAll(io.LimitReader(rc, maxEntrySize+1)) if cerr := rc.Close(); err == nil { err = cerr } if err != nil { - return fmt.Errorf("archive entry %s: %w", f.Name, err) + return nil, fmt.Errorf("archive entry %s: %w", f.Name, err) } if len(content) > maxEntrySize { - return fmt.Errorf("archive entry %s: exceeds the %d byte limit", f.Name, maxEntrySize) + return nil, fmt.Errorf("archive entry %s: exceeds the %d byte limit", f.Name, maxEntrySize) + } + return content, nil +} + +// parseEntry reads one archive entry as an XMI document. +func (m *Model) parseEntry(f *zip.File) error { + content, err := readEntry(f) + if err != nil { + return err } if err := m.parseDocument(content); err != nil { return fmt.Errorf("archive entry %s: %w", f.Name, err) @@ -415,7 +455,7 @@ func (m *Model) parseEntry(f *zip.File) error { func newModel() *Model { return &Model{ - byID: map[string]*Element{}, proxies: map[string]*Element{}, + byID: map[string]*Element{}, proxies: map[string]*Element{}, fragments: map[string][]*Element{}, stereotypeHrefs: map[stereotypeKey]string{}, ancestors: map[*Element][]*Element{}, } } @@ -542,6 +582,10 @@ func (m *Model) extensionContent(raw *xmi.Element, ext *Extension, ref *Element, if adopted[child] { continue } + if child.Tag == "stereotypesHREFS" { + m.indexStereotypes(child) + continue + } if isDiagram(child) { m.diagram(child, ext) m.extensionContent(child, ext, ref, adopted) @@ -555,9 +599,6 @@ func (m *Model) extensionContent(raw *xmi.Element, ext *Extension, ref *Element, if ref != nil && child.Tag == "referenceExtension" { m.describeReference(ref, child) } - if child.Tag == "stereotype" && child.Parent != nil && child.Parent.Tag == "stereotypesHREFS" { - m.recordStereotypeHref(child) - } m.extensionContent(child, ext, ref, adopted) } } @@ -592,25 +633,6 @@ func (m *Model) adoptValues(raw *xmi.Element, owner, ref *Element) map[*xmi.Elem return adopted } -// recordStereotypeHref reads one row of MagicDraw's stereotypesHREFS table, -// `<stereotype name='prefix:Name' stereotypeHREF='doc#id'/>`, resolving the -// prefix through the namespace declarations in scope. -func (m *Model) recordStereotypeHref(raw *xmi.Element) { - name, href := raw.Attr("name"), raw.Attr("stereotypeHREF") - i := strings.IndexByte(name, ':') - if i < 0 || href == "" { - return - } - ns := raw.Namespace(name[:i]) - if ns == "" { - return - } - key := stereotypeKey{ns, name[i+1:]} - if _, dup := m.stereotypeHrefs[key]; !dup { - m.stereotypeHrefs[key] = href - } -} - func (m *Model) describeReference(ref *Element, raw *xmi.Element) { if path := raw.Attr("referentPath"); path != "" { ref.QualifiedName = path @@ -638,7 +660,7 @@ func stereotypeText(raw *xmi.Element) string { func (m *Model) newStereotype(raw *xmi.Element) *Stereotype { s := &Stereotype{ - ID: raw.ID, Name: raw.Tag, Namespace: raw.Space, Tags: map[string][]string{}, + ID: raw.ID, Name: raw.Tag, Namespace: raw.Space, Tags: map[string][]string{}, attrValues: map[string]int{}, } for name, value := range raw.Attrs { switch { @@ -646,6 +668,7 @@ func (m *Model) newStereotype(raw *xmi.Element) *Stereotype { s.BaseID = value default: s.Tags[name] = append(s.Tags[name], value) + s.attrValues[name]++ } } for _, child := range raw.Children { @@ -699,6 +722,7 @@ func (m *Model) proxy(href string) *Element { p := &Element{ID: href, Href: href, Attrs: map[string]string{}, refs: map[string][]string{}} if i := strings.LastIndexByte(href, '#'); i >= 0 { p.Name = fragmentName(href[i+1:]) + m.fragments[href[i+1:]] = append(m.fragments[href[i+1:]], p) } m.proxies[href] = p return p @@ -765,6 +789,7 @@ func (m *Model) link() { } } } + m.bindModuleProfiles() m.linkDiagrams() defs := m.stereotypeDefinitions() for _, s := range m.Stereotypes { @@ -775,6 +800,8 @@ func (m *Model) link() { } } } + m.readTables() + m.readDocuments() } // stereotypeDefinitions lists every uml:Stereotype the documents read define. diff --git a/internal/translate/xmi/xmi.go b/internal/translate/xmi/xmi.go index 50e7b0ae06..2b1f1ae835 100644 --- a/internal/translate/xmi/xmi.go +++ b/internal/translate/xmi/xmi.go @@ -8,6 +8,8 @@ import ( "fmt" "io" "strings" + + "golang.org/x/net/html/charset" ) // isXMI reports whether the attribute is in an XMI namespace of any version. @@ -51,8 +53,9 @@ func isVersionSegment(s string) bool { } // Element is one XML element of an XMI document: its local tag, its xmi:type -// and xmi:id, its non-XMI and XMI attributes by local name, and its children -// in document order. A parsed document is never modified after Parse returns. +// and xmi:id, its non-XMI and XMI attributes by local name, the namespaces it +// declares, and its children in document order. A parsed document is never +// modified after Parse returns. type Element struct { Tag string Space string @@ -214,6 +217,7 @@ func (d *Document) ByID(id string) *Element { // xmi:id declared twice, and never panics on unexpected content. func Parse(r io.Reader) (*Document, error) { dec := xml.NewDecoder(r) + dec.CharsetReader = charset.NewReaderLabel doc := &Document{byID: make(map[string]*Element)} var stack []*Element for { @@ -252,8 +256,8 @@ func Parse(r io.Reader) (*Document, error) { return doc, nil } -// newElement reads a start tag: its xmi:type and xmi:id, then the remaining -// attributes by local name, namespace declarations aside. +// newElement reads a start tag: its xmi:type and xmi:id, its namespace +// declarations, then the remaining attributes by local name. func newElement(t xml.StartElement) *Element { e := &Element{ Tag: t.Name.Local, diff --git a/internal/workspace/libs/query_properties_test.go b/internal/workspace/libs/query_properties_test.go new file mode 100644 index 0000000000..fc61e8c71b --- /dev/null +++ b/internal/workspace/libs/query_properties_test.go @@ -0,0 +1,79 @@ +package libs + +import ( + "os" + "path/filepath" + "testing" + + "github.com/Open-MBEE/OpenSysML/internal/semantic/query" + "github.com/Open-MBEE/OpenSysML/internal/semantic/resolve" + "github.com/Open-MBEE/OpenSysML/internal/semantic/semantics" + "github.com/Open-MBEE/OpenSysML/internal/semantic/symbols" +) + +const modifierLibrary = `standard library package Lib { + abstract part def Vehicle; + individual part def Fleet1 :> Vehicle; + part def Wheel; + individual part car1 : Fleet1; + part wheel : Wheel; + attribute def Mass; +} +` + +// The declaration-borne query properties of a library element read the same +// whether its symbol was parsed, restored from the on-disk cache or decoded +// from a snapshot: every path carries the declaration. +func TestLibraryQueryPropertiesSurviveEveryLoadPath(t *testing.T) { + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "lib.sysml"), []byte(modifierLibrary), 0o600); err != nil { + t.Fatalf("write source: %v", err) + } + cacheDir := t.TempDir() + + src := NewDirSource(dir) + data, err := BuildSnapshot(src) + if err != nil { + t.Fatalf("BuildSnapshot: %v", err) + } + snapshot, err := DecodeSnapshot(data, NewLoader(src, nil).setDigest()) + if err != nil { + t.Fatalf("DecodeSnapshot: %v", err) + } + paths := map[string]*symbols.Index{ + "parsed": loadWholeLibrary(t, dir, cacheDir), + "restored": loadWholeLibrary(t, dir, cacheDir), + "snapshot": snapshot, + } + + want := map[string]map[string]string{ + "Lib::Vehicle": {query.PropertyIsAbstract: "true", query.PropertyIsIndividual: "false"}, + "Lib::Fleet1": {query.PropertyIsAbstract: "false", query.PropertyIsIndividual: "true"}, + "Lib::Wheel": {query.PropertyIsAbstract: "false", query.PropertyIsIndividual: "false"}, + "Lib::car1": {query.PropertyIsAbstract: "false", query.PropertyIsIndividual: "true"}, + "Lib::wheel": {query.PropertyIsAbstract: "false", query.PropertyIsIndividual: "false"}, + "Lib::Mass": {query.PropertyIsAbstract: "false", query.PropertyIsIndividual: "false"}, + } + for path, idx := range paths { + r := resolve.New(idx) + reader := query.NewPropertyReader(idx, r, semantics.NewModel(r)) + for fqn, props := range want { + matches := symbols.PreferDeclared(idx.LookupQualified(fqn)) + if len(matches) != 1 { + t.Fatalf("%s: %s matched %d symbols, want 1", path, fqn, len(matches)) + } + if matches[0].Decl == nil { + t.Errorf("%s: %s carries no declaration", path, fqn) + } + for prop, value := range props { + got, ok := reader.Values(matches[0], prop) + if !ok || len(got) != 1 || got[0] != value { + t.Errorf("%s: %s.%s = %v, %v; want [%s], true", path, fqn, prop, got, ok, value) + } + } + } + if _, ok := reader.Values(symbols.PreferDeclared(idx.LookupQualified("Lib"))[0], query.PropertyIsIndividual); ok { + t.Errorf("%s: the package Lib has an isIndividual value, want none", path) + } + } +} diff --git a/internal/workspace/libs/stdlib.snapshot b/internal/workspace/libs/stdlib.snapshot index fd5159940d..3f52ed2812 100644 Binary files a/internal/workspace/libs/stdlib.snapshot and b/internal/workspace/libs/stdlib.snapshot differ diff --git a/internal/workspace/libs/stdlib/OpenSysML Libraries/DocumentQueries.sysml b/internal/workspace/libs/stdlib/OpenSysML Libraries/DocumentQueries.sysml index 468df44fd8..1589737187 100644 --- a/internal/workspace/libs/stdlib/OpenSysML Libraries/DocumentQueries.sysml +++ b/internal/workspace/libs/stdlib/OpenSysML Libraries/DocumentQueries.sysml @@ -14,15 +14,24 @@ library package DocumentQueries { return result : Element[0..*] ordered; } + /* Descendants and Ancestors walk ownership from source to maxDepth levels; + * maxDepth = null (the default) walks the whole tree. */ calc def Descendants { in source : Element[0..*] ordered; - in maxDepth : Integer[1]; + in maxDepth : Integer[0..1] = null; return result : Element[0..*] ordered; } calc def Ancestors { in source : Element[0..*] ordered; - in maxDepth : Integer[1]; + in maxDepth : Integer[0..1] = null; + return result : Element[0..*] ordered; + } + + /* Named: the model elements the qualified names denote, in the order given; + * a name that denotes nothing is an error. */ + calc def Named { + in qualifiedName : String[1..*] ordered; return result : Element[0..*] ordered; } @@ -131,19 +140,22 @@ library package DocumentQueries { * "derivation" or "refinement"; direction: "outgoing" or "incoming". */ in relationshipKind : String[1]; in direction : String[1]; - in maxDepth : Integer[1]; + /* maxDepth: how many relationships to follow; null (the default) is unbounded. */ + in maxDepth : Integer[0..1] = null; return result : Element[0..*] ordered; } + /* WhereType keeps the elements that are of any of the named types. */ calc def WhereType { in source : Element[0..*] ordered; - in type : String[1]; + in type : String[1..*] ordered; return result : Element[0..*] ordered; } + /* WhereMetadata keeps the elements annotated with any of the named metadata definitions. */ calc def WhereMetadata { in source : Element[0..*] ordered; - in 'metadata' : String[1]; + in 'metadata' : String[1..*] ordered; return result : Element[0..*] ordered; } @@ -298,24 +310,27 @@ library package DocumentQueries { } /* A relationship-derived Project column: the elements RelatedElements - * reaches from each row, as a "list" (default), a "count" or "any". */ + * reaches from each row, as a "list" (default), a "count" or "any"; + * maxDepth = null is unbounded, and targets, when given, keeps only the + * reached elements among them. */ calc def RelatedColumn { in name : String[1]; in relationshipKind : String[1]; in direction : String[1]; - in maxDepth : Integer[1]; + in maxDepth : Integer[0..1] = null; in aggregate : String[1] = "list"; + in targets : Element[0..*] ordered = null; return result : ColumnSpec[1]; } /* WhereRelated: the source rows with at least one element reachable by the * relationship kind and direction of RelatedElements within maxDepth - * (exists = true), or with none (exists = false). */ + * (exists = true), or with none (exists = false); maxDepth = null is unbounded. */ calc def WhereRelated { in source : Element[0..*] ordered; in relationshipKind : String[1]; in direction : String[1]; - in maxDepth : Integer[1]; + in maxDepth : Integer[0..1] = null; in exists : Boolean[1] = true; return result : Element[0..*] ordered; } diff --git a/tests/migrate/documents_test.go b/tests/migrate/documents_test.go new file mode 100644 index 0000000000..5949db899c --- /dev/null +++ b/tests/migrate/documents_test.go @@ -0,0 +1,452 @@ +package migrate_test + +import ( + "bytes" + "os" + "strings" + "testing" + + "github.com/Open-MBEE/OpenSysML/internal/doc/docrender" + "github.com/Open-MBEE/OpenSysML/internal/frontend/repl" + "github.com/Open-MBEE/OpenSysML/internal/translate/migrate" +) + +// rows runs a migrated document query in the session and returns its report, +// failing unless the query executed. +func rows(t *testing.T, s *repl.Session, name string) string { + t.Helper() + v := s.RunDocumentQuery(name) + if !v.Holds() { + t.Fatalf("%s did not run:\n%s", name, strings.Join(v.Lines, "\n")) + } + return strings.Join(v.Lines, "\n") +} + +// wantInOrder asserts the wanted strings appear in got in the order given. +func wantInOrder(t *testing.T, what, got string, want ...string) { + t.Helper() + rest := got + for _, w := range want { + i := strings.Index(rest, w) + if i < 0 { + t.Fatalf("%s lacks %q (or has it out of order):\n%s", what, w, got) + } + rest = rest[i+len(w):] + } +} + +// markdownSection is the Markdown from heading to the next heading of its +// level, or "" when the document has no such heading. +func markdownSection(md, heading string) string { + i := strings.Index(md, heading) + if i < 0 { + return "" + } + body := md[i:] + level := heading[:strings.IndexByte(heading, ' ')+1] + if j := strings.Index(body[len(heading):], "\n"+level); j >= 0 { + body = body[:len(heading)+j] + } + return body +} + +// markdown renders a migrated document through the Markdown backend. +func markdown(t *testing.T, s *repl.Session, name string) string { + t.Helper() + out, err := s.RenderDocumentMarkdown(name, docrender.MarkdownOptions{}) + if err != nil { + t.Fatalf("render %s as Markdown: %v", name, err) + } + return out +} + +// html renders a migrated document through the HTML backend as a fragment. +func html(t *testing.T, s *repl.Session, name string) string { + t.Helper() + out, err := s.RenderDocumentHTML(name, docrender.HTMLOptions{Fragment: true}) + if err != nil { + t.Fatalf("render %s as HTML: %v", name, err) + } + return out +} + +// The queries a Cameo instance table, generic table, dependency matrix and +// relation map lower to execute over the migrated model and return the rows +// the tool showed: the instance rows of the scope plus the explicit rows, +// sorted as the table was, with the matrix cells naming the related elements. +func TestMigratedTablesExecute(t *testing.T) { + s := session(t, migrateFixtureFile(t, "tables")) + + // Instance table: individuals of Pump (and its subtypes) under the scope + // plus two explicit rows, sorted by mass descending with the empty cell last. + pumps := rows(t, s, "Plant::Inventory::'Pump Table Rows'") + wantInOrder(t, "Pump Table rows", pumps, + "returned 5 rows", + "Plant::Inventory::r1", `mass = 14`, + "Plant::Inventory::p1", `mass = 12.5`, `flow = 3`, + "Plant::Inventory::p2", `mass = 9`, + "Plant::Spares::s1", `mass = 7`, + "Plant::Spares::s2", `mass = ""`) + if strings.Contains(pumps, "Plant::Inventory::v1") { + t.Fatalf("Pump Table lists the valve v1:\n%s", pumps) + } + + // Built-in columns are projected before the feature columns, and a + // feature column captioned like a built-in property takes the suffixed name. + ledger := rows(t, s, "Plant::Inventory::'Pump Ledger Rows'") + wantInOrder(t, "Pump Ledger rows", ledger, + "Columns: name, qualifiedName, name 2, mass", + "Plant::Inventory::p1", `name = "p1"`, `qualifiedName = "Plant::Inventory::p1"`, `name 2 = "primary"`, `mass = 12.5`, + "Plant::Inventory::p2", `name = "p2"`, `qualifiedName = "Plant::Inventory::p2"`, `name 2 = ""`) + + // Generic table: every requirement definition of the scope by name. + reqs := rows(t, s, "Plant::Requirements::'Requirement Table Rows'") + wantInOrder(t, "Requirement Table rows", reqs, + "returned 3 rows", + "FlowRequirement", "The pump keeps the flow above the minimum.", + "MassRequirement", "SealRequirement") + + // Dependency matrix: the cell of each requirement row lists the blocks + // that satisfy it, and the duplicate criterion column stays distinct. + matrix := rows(t, s, "Plant::Requirements::'Satisfaction Matrix Rows'") + wantInOrder(t, "Satisfaction Matrix rows", matrix, + "Columns: name, Trace, Trace 2", + "FlowRequirement", "Trace = Plant::Structure::Pump", "Trace 2 = (none)", + "SealRequirement", "Trace = Plant::Structure::Valve", + "MassRequirement", "Trace = (none)") + + // Relation map: the requirements one satisfaction hop from the context. + related := rows(t, s, "Plant::Structure::'Pump Requirement Map Rows'") + wantInOrder(t, "Pump Requirement Map rows", related, + "returned 1 row", + "Plant::Requirements::FlowRequirement", `@type = "RequirementDefinition"`) + + // Whole-model scope filtered by a migrated user stereotype. + critical := rows(t, s, "Plant::'Critical Elements Rows'") + wantInOrder(t, "Critical Elements rows", critical, + "returned 2 rows", "Plant::Structure::Pump", "Plant::Requirements::FlowRequirement") +} + +// A generic table over a broad UML metaclass lists what that metaclass holds +// in the source model: the packageable elements of a package but not the +// features they own, and the «View» and «Viewpoint» classes among the types. +func TestMetaclassTablesExecute(t *testing.T) { + s := session(t, migrateFixtureFile(t, "metaclass_tables")) + + packageable := rows(t, s, "Tables::'Packageable Elements Rows'") + wantInOrder(t, "Packageable Elements rows", packageable, + "returned 11 rows", + "Plant::Structure\n", "Plant::Structure::Mode\n", "Plant::Structure::Pump\n", + "Plant::Structure::Pump::Cycle\n", "Plant::Structure::Pump::prime\n", "Plant::Structure::Valve\n", + "Plant::Structure::needs\n", "Plant::Structure::p1\n", + "Plant::Views\n", "Plant::Views::Operations\n", "Plant::Views::Overview\n") + for _, feature := range []string{"Pump::mass", "Pump::valve", "Pump::'prime 2'", "Mode::on", "Cycle::Idle", "p1::mass"} { + if strings.Contains(packageable, feature) { + t.Errorf("Packageable Elements lists the owned feature %s:\n%s", feature, packageable) + } + } + + namespaces := rows(t, s, "Tables::'Namespaces Rows'") + wantInOrder(t, "Namespaces rows", namespaces, + "returned 12 rows", + "Plant::Structure\n", "Plant::Structure::Mode\n", "Plant::Structure::Pump\n", + "Plant::Structure::Pump::Cycle\n", "Plant::Structure::Pump::Cycle::Idle\n", + "Plant::Structure::Pump::Cycle::Running\n", "Plant::Structure::Pump::prime\n", + "Plant::Structure::Valve\n", "Plant::Structure::p1\n", + "Plant::Views\n", "Plant::Views::Operations\n", "Plant::Views::Overview\n") + if strings.Contains(namespaces, "Plant::Structure::needs") { + t.Errorf("Namespaces lists the dependency needs:\n%s", namespaces) + } + + for _, name := range []string{"Types", "Classifiers"} { + got := rows(t, s, "Tables::'"+name+" Rows'") + wantInOrder(t, name+" rows", got, + "returned 8 rows", + "Plant::Structure::Mode\n", "Plant::Structure::Pump\n", "Plant::Structure::Pump::Cycle\n", + "Plant::Structure::Pump::prime\n", "Plant::Structure::Valve\n", "Plant::Structure::p1\n", + "Plant::Views::Operations\n", "Plant::Views::Overview\n") + for _, other := range []string{"Plant::Structure\n", "Plant::Views\n", "Cycle::Idle", "needs"} { + if strings.Contains(got, other) { + t.Errorf("%s lists %q, which is no type:\n%s", name, strings.TrimSpace(other), got) + } + } + } + + // A whole-model table over Diagram lists every view, the one the model + // itself owns — written at the top level — included. + diagrams := rows(t, s, "Tables::'Diagrams Rows'") + wantInOrder(t, "Diagrams rows", diagrams, + "returned 7 rows", + "Row 1: 'Model Overview'\n", "Plant::Views::Overview\n", "Tables::Classifiers\n", "Tables::Diagrams\n", + "Tables::Namespaces\n", "Tables::'Packageable Elements'\n", "Tables::Types\n") +} + +// A criterion that excludes subtypes of its stereotype cannot be told apart +// from one that includes them once every «Satisfy» — a user «Fulfil» +// specializing it included — is written as the same satisfy: the query lists +// the «Fulfil» relationships too, and the report says so. A criterion no +// applied stereotype specializes is exact either way. +func TestRelationCriterionSubtypes(t *testing.T) { + r := migrateFixtureFile(t, "relation_subtypes") + walked := "excludes subtypes of «Satisfy», but the «Fulfil» relationships are walked too" + wantNote(t, r, "_mx_exact", migrate.Approximated, "the criterion Satisfied by "+walked) + wantNote(t, r, "_map_valve", migrate.Approximated, "the criterion Satisfy "+walked) + wantNote(t, r, "_mx_wide", migrate.Mapped, "") + if es := entriesFor(r, "_mx_wide"); len(es) == 1 && strings.Contains(es[0].Note, "subtypes") { + t.Errorf("Wide Satisfaction Matrix notes subtypes: %s", es[0].Note) + } + + s := session(t, r) + exact := rows(t, s, "Plant::Requirements::'Exact Satisfaction Matrix Rows'") + wantInOrder(t, "Exact Satisfaction Matrix rows", exact, + "FlowRequirement", "Satisfied by = Plant::Structure::Pump", + "SealRequirement", "Satisfied by = Plant::Structure::Valve") + wide := rows(t, s, "Plant::Requirements::'Wide Satisfaction Matrix Rows'") + wantInOrder(t, "Wide Satisfaction Matrix rows", wide, + "FlowRequirement", "Satisfied by = Plant::Structure::Pump", "Derived by = Plant::Requirements::SealRequirement", + "SealRequirement", "Satisfied by = Plant::Structure::Valve", "Derived by = (none)") + reached := rows(t, s, "Plant::Structure::'Valve Requirement Map Rows'") + wantInOrder(t, "Valve Requirement Map rows", reached, + "returned 1 row", "Plant::Requirements::SealRequirement") + + // A v1 DeriveReqt runs from the derived requirement to its original, a v2 + // derivation the other way: following it from the derived one reaches the original. + original := rows(t, s, "Plant::Requirements::'Seal Derivation Map Rows'") + wantInOrder(t, "Seal Derivation Map rows", original, + "returned 1 row", "Plant::Requirements::FlowRequirement") +} + +// A table whose diagram the model itself owns is written at the top level, +// beside its view, as a table in a package is beside its own. +func TestTopLevelTableIsWritten(t *testing.T) { + data, err := os.ReadFile("testdata/xmi/tables.xmi") + if err != nil { + t.Fatal(err) + } + data = bytes.ReplaceAll(data, []byte(`ownerOfDiagram="_pkg_inventory"`), []byte(`ownerOfDiagram="_m"`)) + r, err := migrate.Migrate("tables.xmi", data) + if err != nil { + t.Fatalf("Migrate: %v", err) + } + wantClean(t, "tables.sysml", r) + wantInOrder(t, "top-level table", string(r.Notation), + "\nview 'Pump Table' {", "expose 'Pump Table Document';", + "\ncalc def 'Pump Table Rows' :> DocumentQueries::Query {", + "\npart def 'Pump Table Document' :> DocumentQueries::Document {", "calc rows : 'Pump Table Rows';") + wantNote(t, r, "_tbl_pumps", migrate.Approximated, "written as a Document holding a Table over the query 'Pump Table Rows'") + if es := entriesFor(r, "_tbl_pumps"); len(es) == 1 && es[0].Target != "part def 'Pump Table Document'" { + t.Errorf("target = %q", es[0].Target) + } + wantInOrder(t, "top-level Pump Table rows", rows(t, session(t, r), "'Pump Table Rows'"), + "returned 5 rows", "Plant::Inventory::r1", "Plant::Spares::s2") +} + +// The Document each table becomes renders through the real Markdown and HTML +// backends with the executed rows in it. +func TestMigratedTablesRender(t *testing.T) { + s := session(t, migrateFixtureFile(t, "tables")) + + md := markdown(t, s, "Plant::Inventory::'Pump Table Document'") + wantInOrder(t, "Pump Table Markdown", md, + "# Pump Table", + "| name | mass | flow |", + "| r1 | 14 | |", + "| p1 | 12.5 | 3 |", + "| p2 | 9 | |", + "| s1 | 7 | |", + "| s2 | | |") + + page := html(t, s, "Plant::Inventory::'Pump Table Document'") + wantInOrder(t, "Pump Table HTML", page, + `<h1 class="sysml-title">Pump Table</h1>`, + `data-query="Plant::Inventory::Pump Table Rows"`, + `<th scope="col" data-column="mass">mass</th>`, + `data-element="Plant::Inventory::r1"`, `>14</span>`, + `data-element="Plant::Inventory::p1"`, `>12.5</span>`, + `data-element="Plant::Spares::s2"`) + + matrix := markdown(t, s, "Plant::Requirements::'Satisfaction Matrix Document'") + wantInOrder(t, "Satisfaction Matrix Markdown", matrix, + "| name | Trace | Trace 2 |", + "| FlowRequirement | Plant::Structure::Pump | |", + "| SealRequirement | Plant::Structure::Valve | |", + "| MassRequirement | | |") +} + +// A DocGen document renders as the Section tree its views formed, each +// presentation node executing its query: lists, query-backed paragraphs, +// tables with property columns and view-backed diagrams all carry content, +// and a refused node or method leaves its enclosing section in place. +func TestMigratedDocumentsRender(t *testing.T) { + r := migrateFixtureFile(t, "documents") + wantNote(t, r, "_st_vp_headless", migrate.Unmapped, + `the viewpoint Fleet Viewpoints::Headless Viewpoint's method is not migrated: method "_act_vanished" names no element`) + wantInOrder(t, "headless section", string(r.Notation), + "part Headless : DocumentQueries::Section {", + `/* not migrated: the viewpoint Fleet Viewpoints::Headless Viewpoint's method is not migrated: method "_act_vanished" names no element */`) + // Several name patterns are one WhereName, so the rows keep their order. + wantInOrder(t, "name filter", string(r.Notation), + "calc def 'Fleet Handbook Requirement List Rows'", + `value = "^(?:Axle.*)$|^(?:Brake.*)$|^(?:Load.*)$"),`, + "calc def 'Fleet Handbook Requirement Texts Rows'") + // A table's or figure's caption is the title DocGen prints over it; the + // captions text follows as a paragraph while showCaptions holds. + wantInOrder(t, "captions", string(r.Notation), + `attribute redefines caption = "Fleet Parts";`, + `attribute redefines text = "The parts of the fleet, by name.";`, + `attribute redefines caption = "Safety Requirements";`, + "calc rows : 'Fleet Handbook Safety Requirements Rows';\n }\n }", + `attribute redefines caption = "Truck Structure";`, + `attribute redefines text = "The truck and what it hauls";`, + `attribute redefines caption = "Figure: Inside the truck";`, + "ref redefines source = truck.'Truck Internals';\n }\n }") + if strings.Contains(string(r.Notation), "showCaptions is false") { + t.Fatalf("a caption DocGen hides is written:\n%s", r.Notation) + } + s := session(t, r) + + md := markdown(t, s, "'Fleet Documents'::'Fleet Handbook Document'") + wantInOrder(t, "Fleet Handbook Markdown", md, + "# Fleet Handbook", + "## Introduction", + "*Fleet Parts*", + "| name | qualifiedName | documentation | Payload | name 2 |", + "| Axle | Fleet::Structure::Axle | | | |", + "| Trailer | Fleet::Structure::Trailer | Carries the load. | | |", + "| Truck | Fleet::Structure::Truck | Hauls one trailer. | | |", + "The parts of the fleet, by name.", + "## Requirements", + "Every truck of the fleet satisfies these requirements.", + "1. Load Limit\n2. Brake Distance\n3. Axle Count", + "The payload stays under the axle rating. A loaded truck stops within the legal distance. A truck has two axles.", + "### Safety", + "| Brake Distance | A loaded truck stops within the legal distance. |", + "## Figures", + "*Truck Structure*", + "```mermaid", + "The truck and what it hauls", + "## Traceability", + "- Truck", + "## Oddities", + "### Per Truck", + "One truck.", + "## Broken", + "## Severed", + "## Headless", + "## Notes") + if strings.Contains(md, "Axle Count | ") { + t.Fatalf("the Safety table lists a requirement outside the Safety filter:\n%s", md) + } + if strings.Contains(md, "Cut off here.") { + t.Fatalf("a method with a dangling control flow is written up to the break instead of refused:\n%s", md) + } + + page := html(t, s, "'Fleet Documents'::'Fleet Handbook Document'") + wantInOrder(t, "Fleet Handbook HTML", page, + `<h1 class="sysml-title">Fleet Handbook</h1>`, + "Introduction", + `data-element="Fleet::Structure::Truck"`, "Hauls one trailer.", + "Requirements", + "Every truck of the fleet satisfies these requirements.", + "<ol", "Load Limit", "Brake Distance", "Axle Count", "</ol>", + "Safety", + "Figures", + "Traceability", + "Oddities") + + // The Fleet section is named like the top-level package the view lives + // in, so both Diagram blocks name the view from the global namespace. A + // view inside a part def is reached through a usage of it the Document + // declares; one inside another view through that view. + // A filter, sort or collect before an Image transforms the diagrams as it + // does the elements: Truck Figures keeps the two Truck.* diagrams sorted + // by name, Other Figures the one diagram neither pattern excludes, Figure + // Owners lists the diagrams' owners, and No Figures draws nothing once a + // metaclass filter keeps no diagram. + wantNote(t, r, "_st_nofig_image", migrate.Mapped, + "it draws nothing: «FilterByMetaclasses» Fleet Viewpoints::No Figures Viewpoint::No Figures Method::Packages Only keeps none of the diagrams the view exposes or the node targets") + brief := markdown(t, s, "'Fleet Documents'::'Fleet Brief Document'") + wantInOrder(t, "Fleet Brief Markdown", brief, + "# Fleet Brief", "## Figures", "*Truck Structure*", "```mermaid", "The truck and what it hauls", + "## Fleet", "*Truck Structure*", "```mermaid", "The truck and what it hauls", + "*Truck Internals*", "```mermaid", "axles", + "*Fleet Overview*", "```mermaid", "Requirements", + "## Gallery", "*Figure: Inside the truck*", "```mermaid", "axles", + "## Truck Figures", "*Truck Internals*", "```mermaid", "axles", "*Truck Structure*", "```mermaid", "Trailer", + "## Other Figures", "*Fleet Overview*", "```mermaid", "Requirements", + "## Figure Owners", "- Structure\n- Truck", + "## No Figures") + if strings.Count(brief, "```mermaid") != 8 { + t.Errorf("Fleet Brief Markdown draws %d diagrams, want 8:\n%s", strings.Count(brief, "```mermaid"), brief) + } + if body := markdownSection(brief, "## Truck Figures"); strings.Contains(body, "*Fleet Overview*") { + t.Errorf("Truck Figures draws a diagram the name filter drops:\n%s", body) + } + if body := markdownSection(brief, "## Other Figures"); strings.Contains(body, "*Truck") { + t.Errorf("Other Figures draws a diagram the name filter excludes:\n%s", body) + } + if body := markdownSection(brief, "## No Figures"); strings.Contains(body, "```mermaid") { + t.Errorf("No Figures draws a diagram the metaclass filter drops:\n%s", body) + } + if strings.Contains(brief, "showCaptions is false") { + t.Fatalf("a caption DocGen hides is rendered:\n%s", brief) + } +} + +// Only the tool's own profile namespaces define tables: a user stereotype named +// InstanceTable, TableStructure or Document is ordinary metadata, applications +// of look-alike stereotypes under other URIs stay comments, and an exact-profile +// table whose serialization is malformed is refused with the fault named, its +// view and its independent siblings still written. +func TestTableHomonymsAndMalformedTables(t *testing.T) { + r := migrateFixtureFile(t, "table_homonyms") + wantClean(t, "table_homonyms.sysml", r) + notation := string(r.Notation) + + wantInOrder(t, "user profile", notation, + "metadata def TableStructure {", "metadata def InstanceTable {", "metadata def Document;", + "part def Catalog {", "@'Shop Profile'::InstanceTable {", `scope = "Shop";`, + "part def Ledger {", "@'Shop Profile'::TableStructure {", "rows = 12;", + "part def Report {", "@'Shop Profile'::Document;", "/* applied stereotype «Document» */") + wantNote(t, r, "_blk_report", migrate.Mapped, + "«Document» from http://www.magicdraw.com/schemas/manual/Document_Profile_Custom.xmi is applied from a profile the document does not define") + for _, name := range []string{"Catalog Table", "Custom Table"} { + if strings.Contains(notation, "'"+name+" Rows'") || strings.Contains(notation, "'"+name+" Document'") { + t.Errorf("the look-alike %s on a non-profile URI lowered to a query:\n%s", name, notation) + } + } + if n := strings.Count(notation, ":> DocumentQueries::Document {"); n != 1 { + t.Errorf("%d Documents written, want only the valid Catalog Map:\n%s", n, notation) + } + + refusals := map[string]string{ + "_tbl_dangling": "the scope _nowhere resolves to no element", + "_tbl_ambiguous": "the scope _shared names 2 module elements (http://example.com/modules/Warehouse.xmi#_shared, http://example.com/modules/Storefront.xmi#_shared)", + "_tbl_bad_sort": `sort "IColumn:_prop_price^Sideways": not in the form <column>^Asc|Desc; sort "price": not in the form <column>^Asc|Desc`, + "_tbl_no_classifier": "the instance table names no classifier", + "_tbl_ghost_column": "the column IColumn:_no_such_property names no property of the document", + "_tbl_no_diagram": "base_Diagram _no_such_diagram names no diagram of the document", + "_mx_broken": "the unnamed criterion is malformed: not well-formed XML: xmi: XML syntax error on line 4: unexpected EOF", + "_mx_orphan": "MatrixFilter: no filter application names the diagram", + "_map_deep": `depth "deep": not a non-negative integer`, + } + for id, why := range refusals { + wantNote(t, r, id, migrate.Unmapped, why) + } + wantInOrder(t, "refused tables", notation, + "view 'Dangling Scope' {", "/* not migrated: «InstanceTable» 'Dangling Scope' — the scope _nowhere resolves to no element */", + "view 'Ambiguous Scope' {", "/* not migrated: «InstanceTable» 'Ambiguous Scope' — the scope _shared names 2 module elements (http://example.com/modules/Warehouse.xmi#_shared, http://example.com/modules/Storefront.xmi#_shared) */", + "view 'Broken Matrix' {", "expose Catalog;", "/* not migrated: «DependencyMatrix» 'Broken Matrix' — the unnamed criterion is malformed", + "view 'Deep Map' {", "/* not migrated: «RelationMap» 'Deep Map' — depth \"deep\"", + "view 'Catalog Map' {", "expose 'Catalog Map Document';", + "calc def 'Catalog Map Rows' :> DocumentQueries::Query {", + `relationshipKind = "specialization"`, `direction = "incoming"`, "maxDepth = 1", + "part def 'Catalog Map Document' :> DocumentQueries::Document {") + wantNote(t, r, "_map_catalog", migrate.Mapped, "written as a Document holding a Table over the query 'Catalog Map Rows'") + + s := session(t, r) + wantInOrder(t, "Catalog Map rows", rows(t, s, "Shop::'Catalog Map Rows'"), + "returned 2 rows", "Shop::SeasonalCatalog", `@type = "PartDefinition"`, "Shop::c1") + wantInOrder(t, "Catalog Map Markdown", markdown(t, s, "Shop::'Catalog Map Document'"), + "# Catalog Map", "| qualifiedName | @type |", "| Shop::SeasonalCatalog | PartDefinition |", "| Shop::c1 | PartDefinition |") +} diff --git a/tests/migrate/layout_test.go b/tests/migrate/layout_test.go index e8f57e40c1..f1f8a67cf6 100644 --- a/tests/migrate/layout_test.go +++ b/tests/migrate/layout_test.go @@ -349,3 +349,69 @@ func TestLayoutDiagramWithoutWrittenView(t *testing.T) { t.Errorf("notation lays out a view never written:\n%s", r.Notation) } } + +// A table diagram with a layout record keeps its Canvas and Layout annotations +// on the view usage, next to the Document its table definition becomes: the +// view exposes the Document, the query still reads the laid-out rows, and the +// join counts the diagram as laid out. +func TestLayoutOfTableDiagram(t *testing.T) { + data, err := os.ReadFile("testdata/xmi/tables.xmi") + if err != nil { + t.Fatal(err) + } + layout := &mtip.Export{Diagrams: []mtip.Diagram{{ + ID: "_diag_pumps", + Name: "Pump Table", + Type: "sysml.InstanceTable", + Placements: []mtip.Placement{ + {ID: "_inst_p1", X: 0, Y: 0, Width: 400, Height: 20}, + {ID: "_inst_p2", X: 0, Y: 20, Width: 400, Height: 20}, + {ID: "_inst_r1", X: 0, Y: 40, Width: 400, Height: 20}, + }, + Unsupported: map[string]int{}, + }}} + r, err := migrate.MigrateOptions("tables.xmi", data, migrate.Options{Layout: layout, LayoutSource: "tables.xml"}) + if err != nil { + t.Fatalf("MigrateOptions: %v", err) + } + wantClean(t, "tables.sysml", r) + l := r.Report.Layout + if l == nil { + t.Fatal("no layout summary") + } + if l.Diagrams != 1 || l.DiagramsJoined != 1 || l.DiagramsUnmatched != 0 || l.PlacementsWritten != 3 { + t.Errorf("layout summary: %+v", l) + } + notation := string(r.Notation) + wantInOrder(t, "laid-out table view", notation, + "view 'Pump Table' {", + "expose p1;", "expose p2;", "expose r1;", + "expose 'Pump Table Document';", + `@DiagramLayout::Canvas { unit = "px"; width = 400; height = 60; }`, + "metadata DiagramLayout::Layout about p1 { x = 0; y = 0; width = 400; height = 20; }", + "metadata DiagramLayout::Layout about p2 { x = 0; y = 20; width = 400; height = 20; }", + "metadata DiagramLayout::Layout about r1 { x = 0; y = 40; width = 400; height = 20; }", + "render Views::asElementTable;", + "calc def 'Pump Table Rows' :> DocumentQueries::Query {", + "part def 'Pump Table Document' :> DocumentQueries::Document {") + + plain, err := migrate.Migrate("tables.xmi", data) + if err != nil { + t.Fatalf("Migrate: %v", err) + } + strip := func(s string) string { + var kept []string + for _, line := range strings.Split(s, "\n") { + if !strings.Contains(line, "DiagramLayout::") { + kept = append(kept, line) + } + } + return strings.Join(kept, "\n") + } + if strip(notation) != string(plain.Notation) { + t.Errorf("the layout changed more than the annotations:\n%s", notation) + } + s := session(t, r) + wantInOrder(t, "laid-out Pump Table rows", rows(t, s, "Plant::Inventory::'Pump Table Rows'"), + "returned 5 rows", "Plant::Inventory::r1", "Plant::Inventory::p1", "Plant::Inventory::p2", "Plant::Spares::s1", "Plant::Spares::s2") +} diff --git a/tests/migrate/migrate_test.go b/tests/migrate/migrate_test.go index b5281c6ce1..1874089309 100644 --- a/tests/migrate/migrate_test.go +++ b/tests/migrate/migrate_test.go @@ -325,6 +325,12 @@ var constructFixtures = []string{ "layout", "malformed_diagrams", "stub_actions", + "tables", + "metaclass_tables", + "documents", + "type_modifiers", + "table_homonyms", + "relation_subtypes", } // migrateFixtureFile migrates testdata/xmi/<name>.xmi. diff --git a/tests/migrate/testdata/xmi/documents.golden.report.txt b/tests/migrate/testdata/xmi/documents.golden.report.txt new file mode 100644 index 0000000000..fde46dad0f --- /dev/null +++ b/tests/migrate/testdata/xmi/documents.golden.report.txt @@ -0,0 +1,332 @@ +# SysML v1 to v2 migration report: documents.xmi +# exported by Example UML Tool +# migrated 317 element(s): 234 mapped, 65 approximated, 18 unmapped (4 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) + +## unmapped (18) +«CollaboratorImageParagraph» Comment Fleet Documents::Notes::<Comment> _st_note_blank_image (the attached image "depot.png" has no caption, and a Diagram shows a view, not an image file) +«CollaboratorParagraph» Comment Fleet Documents::Notes::<Comment> _st_note_named (property "META:QPROP:Element:name" is not the comment body) +«CollaboratorParagraph» Comment Fleet Documents::Notes::<Comment> _st_note_empty (the paragraph's comment has no body) +«CollaboratorParagraph» Comment Fleet Documents::Notes::<Comment> _st_note_stray (ownerId "_view_gone" names no view of the document) +«CollaboratorParagraph» Comment Fleet Documents::Notes::<Comment> _st_note_other_doc (viewId "_doc_gone" names no document) +«Conform» Generalization Fleet Documents::Notes::<Generalization> _gen_notes (the viewpoint is not in the document; applied stereotypes «Conform») +Activity Fleet Viewpoints::Broken Viewpoint::Broken Method _act_broken (the method Fleet Viewpoints::Broken Viewpoint::Broken Method is not migrated: no initial node) +«Image» CallBehaviorAction Fleet Viewpoints::Figures Viewpoint::Figures Method::Image _st_figures_image (the SysML Activity Diagram 'Parts Method Flow' is a view rendered as textual notation, which a document does not draw) +«Viewpoint» Class Fleet Viewpoints::Headless Viewpoint _st_vp_headless (the viewpoint Fleet Viewpoints::Headless Viewpoint's method is not migrated: method "_act_vanished" names no element) +«CollectOwnedElements» CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Collect Owned Elements _st_odd_depth (the depth "many" is not a whole number) +«CollectTypes» CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Collect Types _st_odd_types (no query operation or content block stands for «CollectTypes») +«Paragraph» CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Evaluated Paragraph _st_odd_ocl (its body is evaluated as OCL, which no query evaluates) +«Image» CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Image _st_odd_image (it shows no diagram: only a diagram the view exposes or the node targets directly has a view to show) +CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Missing Behavior _odd_missing (behavior "_act_gone" names no element) +CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Missing Behavior _odd_missing -> 'Missing Behavior' (1 behavior reference(s) resolve to nothing in the document (_act_gone); the action calls no behavior) +«TableStructure» StructuredActivityNode Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Type Table _st_odd_table (the elements it shows pass through «CollectOwnedElements» Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Collect Owned Elements is not migrated: the depth "many" is not a whole number) +«Dynamic_View» CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Per Truck::Per Truck Again _st_per_truck (the «Dynamic_View» Activity Fleet Viewpoints::Oddities Viewpoint::Per Truck calls itself, and a recursive section has no static spelling) +Activity Fleet Viewpoints::Severed Viewpoint::Severed Method _act_severed (the method Fleet Viewpoints::Severed Viewpoint::Severed Method is not migrated: ControlFlow _severed_e2's target "_severed_gone" names no node) + +## approximated (65) +«Document» Class Fleet Documents::Fleet Brief _doc_brief -> 'Fleet Documents'::'Fleet Brief' (a plain UML class without «Block» is written as a part def) +«Document» Class Fleet Documents::Fleet Handbook _doc_handbook -> 'Fleet Documents'::'Fleet Handbook' (a plain UML class without «Block» is written as a part def) +«Document» Class Fleet Documents::Fleet Handbook _st_doc_handbook -> part def 'Fleet Documents'::'Fleet Handbook Document' (the «Document» is written as a Document definition of 9 section(s); the column «TableExpressionColumn» Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts::Owner Name is not written: the expression "owner.name" is not a bare query property (name, documentation, qualifiedName, owner, id); the column name is written as name 2: column names are unique; Project lists its properties first: name, qualifiedName, documentation precede the other columns; elements of the stereotypes specializing «Safety» are kept too; the column «TableExpressionColumn» Fleet Viewpoints::Safety Viewpoint::Safety Method::Safety Requirements::Owner is not written: the expression "owner.oclAsType(NamedElement).name" is not a bare query property (name, documentation, qualifiedName, owner, id); each item's documentation follows its name; the attached image "fleet.png" is not written, since a Diagram shows a view, not an image file; its caption stands as the paragraph; Conform general "_vp_gone" names no element) +«View» Class Fleet Documents::Notes _view_notes -> 'Fleet Documents'::Notes (a generalization refers to nothing in the document) +«CollaboratorImageParagraph» Comment Fleet Documents::Notes::<Comment> _st_note_image -> part 'Fleet Documents'::'Fleet Handbook Document'::Notes::'paragraph 3' (the attached image "fleet.png" is not written, since a Diagram shows a view, not an image file; its caption stands as the paragraph) +Activity Fleet Viewpoints::Broken Viewpoint::Broken Method _act_broken -> 'Fleet Viewpoints'::'Broken Viewpoint'::'Broken Method' (the classifier behavior is run by every object of Fleet Viewpoints::Broken Viewpoint as its usage broken Method) +«Paragraph» CallBehaviorAction Fleet Viewpoints::Broken Viewpoint::Broken Method::Paragraph _broken_para -> Paragraph (no edge leads to the node, so it starts with the activity; a step with no behavior and no duration; it passes the token on) +Activity Fleet Viewpoints::Figure Owners Viewpoint::Figure Owners Method _act_owners -> 'Fleet Viewpoints'::'Figure Owners Viewpoint'::'Figure Owners Method' (the classifier behavior is run by every object of Fleet Viewpoints::Figure Owners Viewpoint as its usage figure Owners Method) +«CollectOwners» CallBehaviorAction Fleet Viewpoints::Figure Owners Viewpoint::Figure Owners Method::Collect Owners _owners_collect -> 'Collect Owners' (a step with no behavior and no duration; it passes the token on) +«BulletedList» CallBehaviorAction Fleet Viewpoints::Figure Owners Viewpoint::Figure Owners Method::List _owners_list -> List (a step with no behavior and no duration; it passes the token on) +«SortByName» CallBehaviorAction Fleet Viewpoints::Figure Owners Viewpoint::Figure Owners Method::Sort _owners_sort -> Sort (a step with no behavior and no duration; it passes the token on) +Activity Fleet Viewpoints::Figures Viewpoint::Figures Method _act_figures -> 'Fleet Viewpoints'::'Figures Viewpoint'::'Figures Method' (the classifier behavior is run by every object of Fleet Viewpoints::Figures Viewpoint as its usage figures Method) +«Image» CallBehaviorAction Fleet Viewpoints::Figures Viewpoint::Figures Method::Image _figures_image -> Image (a step with no behavior and no duration; it passes the token on) +Activity Fleet Viewpoints::Gallery Viewpoint::Gallery Method _act_gallery -> 'Fleet Viewpoints'::'Gallery Viewpoint'::'Gallery Method' (the classifier behavior is run by every object of Fleet Viewpoints::Gallery Viewpoint as its usage gallery Method) +«Image» CallBehaviorAction Fleet Viewpoints::Gallery Viewpoint::Gallery Method::Image _gallery_image -> Image (a step with no behavior and no duration; it passes the token on) +Activity Fleet Viewpoints::No Figures Viewpoint::No Figures Method _act_nofig -> 'Fleet Viewpoints'::'No Figures Viewpoint'::'No Figures Method' (the classifier behavior is run by every object of Fleet Viewpoints::No Figures Viewpoint as its usage no Figures Method) +«Image» CallBehaviorAction Fleet Viewpoints::No Figures Viewpoint::No Figures Method::Image _nofig_image -> Image (a step with no behavior and no duration; it passes the token on) +«FilterByMetaclasses» CallBehaviorAction Fleet Viewpoints::No Figures Viewpoint::No Figures Method::Packages Only _nofig_packages -> 'Packages Only' (a step with no behavior and no duration; it passes the token on) +Activity Fleet Viewpoints::Oddities Viewpoint::Oddities Method _act_odd -> 'Fleet Viewpoints'::'Oddities Viewpoint'::'Oddities Method' (the classifier behavior is run by every object of Fleet Viewpoints::Oddities Viewpoint as its usage oddities Method) +«CollectOwnedElements» CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Collect Owned Elements _odd_depth -> 'Collect Owned Elements' (a step with no behavior and no duration; it passes the token on) +«CollectTypes» CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Collect Types _odd_types -> 'Collect Types' (a step with no behavior and no duration; it passes the token on) +«Paragraph» CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Evaluated Paragraph _odd_ocl -> 'Evaluated Paragraph' (a step with no behavior and no duration; it passes the token on) +«Image» CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Image _odd_image -> Image (a step with no behavior and no duration; it passes the token on) +«TableAttributeColumn» CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Type Table::Name _odd_col_name -> Name (a step with no behavior and no duration; it passes the token on) +«Paragraph» CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Per Truck::Paragraph _per_para -> Paragraph (a step with no behavior and no duration; it passes the token on) +Activity Fleet Viewpoints::Other Figures Viewpoint::Other Figures Method _act_others -> 'Fleet Viewpoints'::'Other Figures Viewpoint'::'Other Figures Method' (the classifier behavior is run by every object of Fleet Viewpoints::Other Figures Viewpoint as its usage other Figures Method) +«Image» CallBehaviorAction Fleet Viewpoints::Other Figures Viewpoint::Other Figures Method::Image _others_image -> Image (a step with no behavior and no duration; it passes the token on) +«FilterByNames» CallBehaviorAction Fleet Viewpoints::Other Figures Viewpoint::Other Figures Method::Not Truck Diagrams _others_names -> 'Not Truck Diagrams' (a step with no behavior and no duration; it passes the token on) +Activity Fleet Viewpoints::Parts Viewpoint::Parts Method _act_parts -> 'Fleet Viewpoints'::'Parts Viewpoint'::'Parts Method' (the classifier behavior is run by every object of Fleet Viewpoints::Parts Viewpoint as its usage parts Method) +ObjectFlow Fleet Viewpoints::Parts Viewpoint::Parts Method::<ObjectFlow> _parts_o1 (the flow is written, but 'Collect Owned Elements' calls no behavior and computes nothing, so no value travels it, and 'Filter By Metaclasses' does not wait for one) +«CollectOwnedElements» CallBehaviorAction Fleet Viewpoints::Parts Viewpoint::Parts Method::Collect Owned Elements _parts_collect -> 'Collect Owned Elements' (a step with no behavior and no duration, which passes the token on; its pins are declared as its parameters, but the action computes nothing, so its output 'result' holds no value) +OutputPin Fleet Viewpoints::Parts Viewpoint::Parts Method::Collect Owned Elements::result _parts_collect_out -> 'Fleet Viewpoints'::'Parts Viewpoint'::'Parts Method'::'Collect Owned Elements'.result (it is declared admitting no value: the action calls no behavior, so nothing computes it) +«FilterByMetaclasses» CallBehaviorAction Fleet Viewpoints::Parts Viewpoint::Parts Method::Filter By Metaclasses _parts_filter -> 'Filter By Metaclasses' (a step with no behavior and no duration, which passes the token on; its pins are declared as its parameters, but the action computes nothing) +InputPin Fleet Viewpoints::Parts Viewpoint::Parts Method::Filter By Metaclasses::input _parts_filter_in -> 'Fleet Viewpoints'::'Parts Viewpoint'::'Parts Method'::'Filter By Metaclasses'.input (it is declared admitting no value: 'Collect Owned Elements', which feeds it, produces no value) +«TableStructure» StructuredActivityNode Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts _st_parts_table -> part 'Fleet Documents'::'Fleet Handbook Document'::Introduction::table (its rows are the query 'Fleet Handbook Fleet Parts Rows'; the column «TableExpressionColumn» Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts::Owner Name is not written: the expression "owner.name" is not a bare query property (name, documentation, qualifiedName, owner, id); the column name is written as name 2: column names are unique; Project lists its properties first: name, qualifiedName, documentation precede the other columns) +«TablePropertyColumn» CallBehaviorAction Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts::Alias _parts_col_alias -> Alias (a step with no behavior and no duration; it passes the token on) +«TableAttributeColumn» CallBehaviorAction Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts::Name _parts_col_name -> Name (a step with no behavior and no duration; it passes the token on) +«TableExpressionColumn» CallBehaviorAction Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts::Owner Name _parts_col_owner -> 'Owner Name' (a step with no behavior and no duration; it passes the token on) +«TablePropertyColumn» CallBehaviorAction Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts::Payload _parts_col_payload -> Payload (a step with no behavior and no duration; it passes the token on) +«TableExpressionColumn» CallBehaviorAction Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts::Qualified Name _parts_col_qname -> 'Qualified Name' (a step with no behavior and no duration; it passes the token on) +«SortByName» CallBehaviorAction Fleet Viewpoints::Parts Viewpoint::Parts Method::Sort By Name _parts_sort -> 'Sort By Name' (a step with no behavior and no duration; it passes the token on) +«CollectOwnedElements» CallBehaviorAction Fleet Viewpoints::Requirements Viewpoint::Requirements Method::Collect Owned Elements _reqs_collect -> 'Collect Owned Elements' (a step with no behavior and no duration; it passes the token on) +«FilterByNames» CallBehaviorAction Fleet Viewpoints::Requirements Viewpoint::Requirements Method::Filter By Names _reqs_names -> 'Filter By Names' (a step with no behavior and no duration; it passes the token on) +«FilterByStereotypes» CallBehaviorAction Fleet Viewpoints::Requirements Viewpoint::Requirements Method::Filter By Stereotypes _reqs_filter -> 'Filter By Stereotypes' (a step with no behavior and no duration; it passes the token on) +«Paragraph» CallBehaviorAction Fleet Viewpoints::Requirements Viewpoint::Requirements Method::Introduction _reqs_intro -> Introduction (a step with no behavior and no duration; it passes the token on) +«BulletedList» CallBehaviorAction Fleet Viewpoints::Requirements Viewpoint::Requirements Method::Requirement List _reqs_list -> 'Requirement List' (a step with no behavior and no duration; it passes the token on) +«Paragraph» CallBehaviorAction Fleet Viewpoints::Requirements Viewpoint::Requirements Method::Requirement Texts _reqs_texts -> 'Requirement Texts' (a step with no behavior and no duration; it passes the token on) +«CollectOwnedElements» CallBehaviorAction Fleet Viewpoints::Safety Viewpoint::Safety Method::Collect Owned Elements _safety_collect -> 'Collect Owned Elements' (a step with no behavior and no duration; it passes the token on) +«FilterByNames» CallBehaviorAction Fleet Viewpoints::Safety Viewpoint::Safety Method::Filter By Names _safety_names -> 'Filter By Names' (a step with no behavior and no duration; it passes the token on) +«FilterByStereotypes» CallBehaviorAction Fleet Viewpoints::Safety Viewpoint::Safety Method::Filter By Stereotypes _safety_filter -> 'Filter By Stereotypes' (a step with no behavior and no duration; it passes the token on) +«TableStructure» StructuredActivityNode Fleet Viewpoints::Safety Viewpoint::Safety Method::Safety Requirements _st_safety_table -> part 'Fleet Documents'::'Fleet Handbook Document'::Requirements::Safety::table (its rows are the query 'Fleet Handbook Safety Requirements Rows'; elements of the stereotypes specializing «Safety» are kept too; the column «TableExpressionColumn» Fleet Viewpoints::Safety Viewpoint::Safety Method::Safety Requirements::Owner is not written: the expression "owner.oclAsType(NamedElement).name" is not a bare query property (name, documentation, qualifiedName, owner, id)) +«TableAttributeColumn» CallBehaviorAction Fleet Viewpoints::Safety Viewpoint::Safety Method::Safety Requirements::Name _safety_col_name -> Name (a step with no behavior and no duration; it passes the token on) +«TableExpressionColumn» CallBehaviorAction Fleet Viewpoints::Safety Viewpoint::Safety Method::Safety Requirements::Owner _safety_col_ocl -> Owner (a step with no behavior and no duration; it passes the token on) +«TableAttributeColumn» CallBehaviorAction Fleet Viewpoints::Safety Viewpoint::Safety Method::Safety Requirements::Text _safety_col_doc -> Text (a step with no behavior and no duration; it passes the token on) +Activity Fleet Viewpoints::Selected Figures Viewpoint::Selected Figures Method _act_selected -> 'Fleet Viewpoints'::'Selected Figures Viewpoint'::'Selected Figures Method' (the classifier behavior is run by every object of Fleet Viewpoints::Selected Figures Viewpoint as its usage selected Figures Method) +«Image» CallBehaviorAction Fleet Viewpoints::Selected Figures Viewpoint::Selected Figures Method::Image _selected_image -> Image (a step with no behavior and no duration; it passes the token on) +«SortByName» CallBehaviorAction Fleet Viewpoints::Selected Figures Viewpoint::Selected Figures Method::Sort _selected_sort -> Sort (a step with no behavior and no duration; it passes the token on) +«FilterByNames» CallBehaviorAction Fleet Viewpoints::Selected Figures Viewpoint::Selected Figures Method::Truck Diagrams _selected_names -> 'Truck Diagrams' (a step with no behavior and no duration; it passes the token on) +Activity Fleet Viewpoints::Severed Viewpoint::Severed Method _act_severed -> 'Fleet Viewpoints'::'Severed Viewpoint'::'Severed Method' (the classifier behavior is run by every object of Fleet Viewpoints::Severed Viewpoint as its usage severed Method) +«Paragraph» CallBehaviorAction Fleet Viewpoints::Severed Viewpoint::Severed Method::Paragraph _severed_para -> Paragraph (a step with no behavior and no duration; it passes the token on) +Activity Fleet Viewpoints::Traceability Viewpoint::Traceability Method _act_trace -> 'Fleet Viewpoints'::'Traceability Viewpoint'::'Traceability Method' (the classifier behavior is run by every object of Fleet Viewpoints::Traceability Viewpoint as its usage traceability Method) +«CollectOwnedElements» CallBehaviorAction Fleet Viewpoints::Traceability Viewpoint::Traceability Method::Collect Owned Elements _trace_owned -> 'Collect Owned Elements' (a step with no behavior and no duration; it passes the token on) +«CollectByDirectedRelationshipStereotypes» CallBehaviorAction Fleet Viewpoints::Traceability Viewpoint::Traceability Method::Collect Satisfiers _trace_satisfiers -> 'Collect Satisfiers' (a step with no behavior and no duration; it passes the token on) +«BulletedList» CallBehaviorAction Fleet Viewpoints::Traceability Viewpoint::Traceability Method::Related Elements _trace_list -> 'Related Elements' (a step with no behavior and no duration; it passes the token on) +«BulletedList» CallBehaviorAction Fleet Viewpoints::Traceability Viewpoint::Traceability Method::Related Elements _st_trace_list -> part 'Fleet Documents'::'Fleet Handbook Document'::Traceability::list (its rows are the query 'Fleet Handbook Related Elements Rows'; each item's documentation follows its name) + +## mapped (234) +Package Fleet _pkg_fleet -> Fleet +Package Fleet Documents _pkg_docs -> 'Fleet Documents' +«Expose» Dependency Fleet Documents::<Dependency> _expose_intro -> 'Fleet Documents'::Introduction +«Expose» Dependency Fleet Documents::<Dependency> _expose_reqs -> 'Fleet Documents'::Requirements +«Expose» Dependency Fleet Documents::<Dependency> _expose_reqs_again -> 'Fleet Documents'::Requirements +«Expose» Dependency Fleet Documents::<Dependency> _expose_figures -> 'Fleet Documents'::Figures +«Expose» Dependency Fleet Documents::<Dependency> _expose_trace -> 'Fleet Documents'::Traceability +«Expose» Dependency Fleet Documents::<Dependency> _expose_odd -> 'Fleet Documents'::Oddities +«Expose» Dependency Fleet Documents::<Dependency> _expose_broken -> 'Fleet Documents'::Broken +«Expose» Dependency Fleet Documents::<Dependency> _expose_severed -> 'Fleet Documents'::Severed +«View» Class Fleet Documents::Broken _view_broken -> 'Fleet Documents'::Broken +«Conform» Generalization Fleet Documents::Broken::<Generalization> _gen_broken -> 'Fleet Documents'::Broken +«View» Class Fleet Documents::Figures _view_figures -> 'Fleet Documents'::Figures +«Conform» Generalization Fleet Documents::Figures::<Generalization> _gen_figures -> 'Fleet Documents'::Figures +«Document» Class Fleet Documents::Fleet Brief _st_doc_brief -> part def 'Fleet Documents'::'Fleet Brief Document' (the «Document» is written as a Document definition of 7 section(s)) +Property Fleet Documents::Fleet Brief::figures _brief_figures -> 'Fleet Documents'::'Fleet Brief'::figures +Property Fleet Documents::Fleet Brief::fleet _brief_fleet -> 'Fleet Documents'::'Fleet Brief'::fleet +Property Fleet Documents::Fleet Brief::gallery _brief_gallery -> 'Fleet Documents'::'Fleet Brief'::gallery +Property Fleet Documents::Fleet Brief::noFigures _brief_nofig -> 'Fleet Documents'::'Fleet Brief'::noFigures +Property Fleet Documents::Fleet Brief::others _brief_others -> 'Fleet Documents'::'Fleet Brief'::others +Property Fleet Documents::Fleet Brief::owners _brief_owners -> 'Fleet Documents'::'Fleet Brief'::owners +Property Fleet Documents::Fleet Brief::selected _brief_selected -> 'Fleet Documents'::'Fleet Brief'::selected +Property Fleet Documents::Fleet Handbook::author _doc_author -> 'Fleet Documents'::'Fleet Handbook'::author +Property Fleet Documents::Fleet Handbook::broken _doc_broken -> 'Fleet Documents'::'Fleet Handbook'::broken +Property Fleet Documents::Fleet Handbook::figures _doc_figures -> 'Fleet Documents'::'Fleet Handbook'::figures +Property Fleet Documents::Fleet Handbook::headless _doc_headless -> 'Fleet Documents'::'Fleet Handbook'::headless +Property Fleet Documents::Fleet Handbook::introduction _doc_intro -> 'Fleet Documents'::'Fleet Handbook'::introduction +Property Fleet Documents::Fleet Handbook::notes _doc_notes -> 'Fleet Documents'::'Fleet Handbook'::notes +Property Fleet Documents::Fleet Handbook::oddities _doc_odd -> 'Fleet Documents'::'Fleet Handbook'::oddities +Property Fleet Documents::Fleet Handbook::requirements _doc_reqs -> 'Fleet Documents'::'Fleet Handbook'::requirements +Property Fleet Documents::Fleet Handbook::severed _doc_severed -> 'Fleet Documents'::'Fleet Handbook'::severed +Property Fleet Documents::Fleet Handbook::traceability _doc_trace -> 'Fleet Documents'::'Fleet Handbook'::traceability +«View» Class Fleet Documents::Headless _view_headless -> 'Fleet Documents'::Headless +«Conform» Generalization Fleet Documents::Headless::<Generalization> _gen_headless -> 'Fleet Documents'::Headless +«View» Class Fleet Documents::Introduction _view_intro -> 'Fleet Documents'::Introduction +«Conform» Generalization Fleet Documents::Introduction::<Generalization> _gen_intro -> 'Fleet Documents'::Introduction +«CollaboratorParagraph» Comment Fleet Documents::Notes::<Comment> _note_second +«CollaboratorParagraph» Comment Fleet Documents::Notes::<Comment> _note_first +«AttachedFile» Comment Fleet Documents::Notes::<Comment> _note_image +«CollaboratorParagraph» Comment Fleet Documents::Notes::<Comment> _note_named +«CollaboratorParagraph» Comment Fleet Documents::Notes::<Comment> _st_note_first -> part 'Fleet Documents'::'Fleet Handbook Document'::Notes::paragraph +«CollaboratorParagraph» Comment Fleet Documents::Notes::<Comment> _st_note_second -> part 'Fleet Documents'::'Fleet Handbook Document'::Notes::'paragraph 2' +«View» Class Fleet Documents::Oddities _view_odd -> 'Fleet Documents'::Oddities +«Conform» Generalization Fleet Documents::Oddities::<Generalization> _gen_odd -> 'Fleet Documents'::Oddities +«View» Class Fleet Documents::Requirements _view_reqs -> 'Fleet Documents'::Requirements +«Conform» Generalization Fleet Documents::Requirements::<Generalization> _gen_reqs -> 'Fleet Documents'::Requirements +Property Fleet Documents::Requirements::safety _reqs_safety -> 'Fleet Documents'::Requirements::safety +«view» Class Fleet Documents::Safety _view_safety -> 'Fleet Documents'::Safety +«Conform» Generalization Fleet Documents::Safety::<Generalization> _gen_safety -> 'Fleet Documents'::Safety +PackageImport Fleet Documents::Safety::<PackageImport> _imp_safety -> Fleet +«View» Class Fleet Documents::Severed _view_severed -> 'Fleet Documents'::Severed +«Conform» Generalization Fleet Documents::Severed::<Generalization> _gen_severed -> 'Fleet Documents'::Severed +«View» Class Fleet Documents::Traceability _view_trace -> 'Fleet Documents'::Traceability +«Conform» Generalization Fleet Documents::Traceability::<Generalization> _gen_trace -> 'Fleet Documents'::Traceability +Profile Fleet Profile _fleet_profile -> 'Fleet Profile' +Stereotype Fleet Profile::Safety _st_safety -> 'Fleet Profile'::Safety +Package Fleet Viewpoints _pkg_viewpoints -> 'Fleet Viewpoints' +«Viewpoint» Class Fleet Viewpoints::Broken Viewpoint _vp_broken -> 'Fleet Viewpoints'::'Broken Viewpoint' +ActivityFinalNode Fleet Viewpoints::Broken Viewpoint::Broken Method::<ActivityFinalNode> _broken_final -> final +ControlFlow Fleet Viewpoints::Broken Viewpoint::Broken Method::<ControlFlow> _broken_e1 +«Viewpoint» Class Fleet Viewpoints::Figure Owners Viewpoint _vp_owners -> 'Fleet Viewpoints'::'Figure Owners Viewpoint' +ActivityFinalNode Fleet Viewpoints::Figure Owners Viewpoint::Figure Owners Method::<ActivityFinalNode> _owners_final -> final +ControlFlow Fleet Viewpoints::Figure Owners Viewpoint::Figure Owners Method::<ControlFlow> _owners_e2 +ControlFlow Fleet Viewpoints::Figure Owners Viewpoint::Figure Owners Method::<ControlFlow> _owners_e3 +ControlFlow Fleet Viewpoints::Figure Owners Viewpoint::Figure Owners Method::<ControlFlow> _owners_e4 +InitialNode Fleet Viewpoints::Figure Owners Viewpoint::Figure Owners Method::<InitialNode> _owners_init -> start +«BulletedList» CallBehaviorAction Fleet Viewpoints::Figure Owners Viewpoint::Figure Owners Method::List _st_owners_list -> part 'Fleet Documents'::'Fleet Brief Document'::'Figure Owners'::list (its rows are the query 'Fleet Brief List Rows') +«Viewpoint» Class Fleet Viewpoints::Figures Viewpoint _vp_figures -> 'Fleet Viewpoints'::'Figures Viewpoint' +ActivityFinalNode Fleet Viewpoints::Figures Viewpoint::Figures Method::<ActivityFinalNode> _figures_final -> final +ControlFlow Fleet Viewpoints::Figures Viewpoint::Figures Method::<ControlFlow> _figures_e2 +InitialNode Fleet Viewpoints::Figures Viewpoint::Figures Method::<InitialNode> _figures_init -> start +«Image» CallBehaviorAction Fleet Viewpoints::Figures Viewpoint::Figures Method::Image _st_figures_image -> part 'Fleet Documents'::'Fleet Handbook Document'::Figures::diagram +«Image» CallBehaviorAction Fleet Viewpoints::Figures Viewpoint::Figures Method::Image _st_figures_image -> part 'Fleet Documents'::'Fleet Handbook Document'::Figures::paragraph (the paragraph is the Diagram's caption) +«Image» CallBehaviorAction Fleet Viewpoints::Figures Viewpoint::Figures Method::Image _st_figures_image -> part 'Fleet Documents'::'Fleet Brief Document'::Figures::diagram +«Image» CallBehaviorAction Fleet Viewpoints::Figures Viewpoint::Figures Method::Image _st_figures_image -> part 'Fleet Documents'::'Fleet Brief Document'::Figures::paragraph (the paragraph is the Diagram's caption) +«Image» CallBehaviorAction Fleet Viewpoints::Figures Viewpoint::Figures Method::Image _st_figures_image -> part 'Fleet Documents'::'Fleet Brief Document'::Fleet::diagram +«Image» CallBehaviorAction Fleet Viewpoints::Figures Viewpoint::Figures Method::Image _st_figures_image -> part 'Fleet Documents'::'Fleet Brief Document'::Fleet::paragraph (the paragraph is the Diagram's caption) +«Image» CallBehaviorAction Fleet Viewpoints::Figures Viewpoint::Figures Method::Image _st_figures_image -> part 'Fleet Documents'::'Fleet Brief Document'::Fleet::'diagram 2' +«Image» CallBehaviorAction Fleet Viewpoints::Figures Viewpoint::Figures Method::Image _st_figures_image -> part 'Fleet Documents'::'Fleet Brief Document'::Fleet::'diagram 3' +«Viewpoint» Class Fleet Viewpoints::Gallery Viewpoint _vp_gallery -> 'Fleet Viewpoints'::'Gallery Viewpoint' +ActivityFinalNode Fleet Viewpoints::Gallery Viewpoint::Gallery Method::<ActivityFinalNode> _gallery_final -> final +ControlFlow Fleet Viewpoints::Gallery Viewpoint::Gallery Method::<ControlFlow> _gallery_e2 +InitialNode Fleet Viewpoints::Gallery Viewpoint::Gallery Method::<InitialNode> _gallery_init -> start +«Image» CallBehaviorAction Fleet Viewpoints::Gallery Viewpoint::Gallery Method::Image _st_gallery_image -> part 'Fleet Documents'::'Fleet Brief Document'::Gallery::diagram +«Viewpoint» Class Fleet Viewpoints::Headless Viewpoint _vp_headless -> 'Fleet Viewpoints'::'Headless Viewpoint' +«Viewpoint» Class Fleet Viewpoints::No Figures Viewpoint _vp_nofig -> 'Fleet Viewpoints'::'No Figures Viewpoint' +ActivityFinalNode Fleet Viewpoints::No Figures Viewpoint::No Figures Method::<ActivityFinalNode> _nofig_final -> final +ControlFlow Fleet Viewpoints::No Figures Viewpoint::No Figures Method::<ControlFlow> _nofig_e2 +ControlFlow Fleet Viewpoints::No Figures Viewpoint::No Figures Method::<ControlFlow> _nofig_e3 +InitialNode Fleet Viewpoints::No Figures Viewpoint::No Figures Method::<InitialNode> _nofig_init -> start +«Image» CallBehaviorAction Fleet Viewpoints::No Figures Viewpoint::No Figures Method::Image _st_nofig_image (it draws nothing: «FilterByMetaclasses» Fleet Viewpoints::No Figures Viewpoint::No Figures Method::Packages Only keeps none of the diagrams the view exposes or the node targets) +«Viewpoint» Class Fleet Viewpoints::Oddities Viewpoint _vp_odd -> 'Fleet Viewpoints'::'Oddities Viewpoint' +ActivityFinalNode Fleet Viewpoints::Oddities Viewpoint::Oddities Method::<ActivityFinalNode> _odd_final -> final +ControlFlow Fleet Viewpoints::Oddities Viewpoint::Oddities Method::<ControlFlow> _odd_e2 +ControlFlow Fleet Viewpoints::Oddities Viewpoint::Oddities Method::<ControlFlow> _odd_e3 +ControlFlow Fleet Viewpoints::Oddities Viewpoint::Oddities Method::<ControlFlow> _odd_e4 +ControlFlow Fleet Viewpoints::Oddities Viewpoint::Oddities Method::<ControlFlow> _odd_e5 +ControlFlow Fleet Viewpoints::Oddities Viewpoint::Oddities Method::<ControlFlow> _odd_e6 +ControlFlow Fleet Viewpoints::Oddities Viewpoint::Oddities Method::<ControlFlow> _odd_e7 +ControlFlow Fleet Viewpoints::Oddities Viewpoint::Oddities Method::<ControlFlow> _odd_e8 +InitialNode Fleet Viewpoints::Oddities Viewpoint::Oddities Method::<InitialNode> _odd_init -> start +CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Per Truck _odd_dynamic -> 'Per Truck' +CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Per Truck _odd_dynamic -> part 'Fleet Documents'::'Fleet Handbook Document'::Oddities::'Per Truck' +«TableStructure» StructuredActivityNode Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Type Table _odd_table -> 'Type Table' +InitialNode Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Type Table::<InitialNode> _odd_table_init -> start +«Dynamic_View» Activity Fleet Viewpoints::Oddities Viewpoint::Per Truck _act_per_truck -> 'Fleet Viewpoints'::'Oddities Viewpoint'::'Per Truck' +ActivityFinalNode Fleet Viewpoints::Oddities Viewpoint::Per Truck::<ActivityFinalNode> _per_final -> final +ControlFlow Fleet Viewpoints::Oddities Viewpoint::Per Truck::<ControlFlow> _per_e2 +ControlFlow Fleet Viewpoints::Oddities Viewpoint::Per Truck::<ControlFlow> _per_e3 +InitialNode Fleet Viewpoints::Oddities Viewpoint::Per Truck::<InitialNode> _per_init -> start +«Paragraph» CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Per Truck::Paragraph _st_per_para -> part 'Fleet Documents'::'Fleet Handbook Document'::Oddities::'Per Truck'::paragraph +CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Per Truck::Per Truck Again _per_again -> 'Per Truck Again' +CallBehaviorAction Fleet Viewpoints::Oddities Viewpoint::Per Truck::Per Truck Again _per_again -> part 'Fleet Documents'::'Fleet Handbook Document'::Oddities::'Per Truck'::'Per Truck' +«Viewpoint» Class Fleet Viewpoints::Other Figures Viewpoint _vp_others -> 'Fleet Viewpoints'::'Other Figures Viewpoint' +ActivityFinalNode Fleet Viewpoints::Other Figures Viewpoint::Other Figures Method::<ActivityFinalNode> _others_final -> final +ControlFlow Fleet Viewpoints::Other Figures Viewpoint::Other Figures Method::<ControlFlow> _others_e2 +ControlFlow Fleet Viewpoints::Other Figures Viewpoint::Other Figures Method::<ControlFlow> _others_e3 +InitialNode Fleet Viewpoints::Other Figures Viewpoint::Other Figures Method::<InitialNode> _others_init -> start +«Image» CallBehaviorAction Fleet Viewpoints::Other Figures Viewpoint::Other Figures Method::Image _st_others_image -> part 'Fleet Documents'::'Fleet Brief Document'::'Other Figures'::diagram +«Viewpoint» Class Fleet Viewpoints::Parts Viewpoint _vp_parts -> 'Fleet Viewpoints'::'Parts Viewpoint' +ActivityFinalNode Fleet Viewpoints::Parts Viewpoint::Parts Method::<ActivityFinalNode> _parts_final -> final +ControlFlow Fleet Viewpoints::Parts Viewpoint::Parts Method::<ControlFlow> _parts_e2 +ControlFlow Fleet Viewpoints::Parts Viewpoint::Parts Method::<ControlFlow> _parts_e3 +ControlFlow Fleet Viewpoints::Parts Viewpoint::Parts Method::<ControlFlow> _parts_e4 +ControlFlow Fleet Viewpoints::Parts Viewpoint::Parts Method::<ControlFlow> _parts_e5 +InitialNode Fleet Viewpoints::Parts Viewpoint::Parts Method::<InitialNode> _parts_init -> start +«TableStructure» StructuredActivityNode Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts _parts_table -> 'Fleet Parts' +«TableStructure» StructuredActivityNode Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts _st_parts_table -> part 'Fleet Documents'::'Fleet Handbook Document'::Introduction::paragraph (the paragraph is the Table's caption) +ControlFlow Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts::<ControlFlow> _parts_table_e2 +ControlFlow Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts::<ControlFlow> _parts_table_e3 +ControlFlow Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts::<ControlFlow> _parts_table_e4 +ControlFlow Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts::<ControlFlow> _parts_table_e5 +ControlFlow Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts::<ControlFlow> _parts_table_e6 +FlowFinalNode Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts::<FlowFinalNode> _parts_table_final -> done (a flow final ends the token, as done does) +InitialNode Fleet Viewpoints::Parts Viewpoint::Parts Method::Fleet Parts::<InitialNode> _parts_table_init -> start +Diagram Fleet Viewpoints::Parts Viewpoint::Parts Method::Parts Method Flow _diag_parts_flow -> 'Fleet Viewpoints'::'Parts Viewpoint'::'Parts Method'::'Parts Method Flow' (a SysML Activity Diagram written as a view rendered asInterconnectionDiagram of ActionFlowView; the view exposes action def 'Fleet Viewpoints'::'Parts Viewpoint'::'Parts Method', whose graph the rendering draws with the 3 shown nodes and edges of it) +«Viewpoint» Class Fleet Viewpoints::Requirements Viewpoint _vp_reqs -> 'Fleet Viewpoints'::'Requirements Viewpoint' +Activity Fleet Viewpoints::Requirements Viewpoint::Requirements Method _act_reqs -> 'Fleet Viewpoints'::'Requirements Viewpoint'::'Requirements Method' +ActivityFinalNode Fleet Viewpoints::Requirements Viewpoint::Requirements Method::<ActivityFinalNode> _reqs_final -> final +ControlFlow Fleet Viewpoints::Requirements Viewpoint::Requirements Method::<ControlFlow> _reqs_e2 +ControlFlow Fleet Viewpoints::Requirements Viewpoint::Requirements Method::<ControlFlow> _reqs_e3 +ControlFlow Fleet Viewpoints::Requirements Viewpoint::Requirements Method::<ControlFlow> _reqs_e3b +ControlFlow Fleet Viewpoints::Requirements Viewpoint::Requirements Method::<ControlFlow> _reqs_e4 +ControlFlow Fleet Viewpoints::Requirements Viewpoint::Requirements Method::<ControlFlow> _reqs_e5 +ControlFlow Fleet Viewpoints::Requirements Viewpoint::Requirements Method::<ControlFlow> _reqs_e6 +InitialNode Fleet Viewpoints::Requirements Viewpoint::Requirements Method::<InitialNode> _reqs_init -> start +«Paragraph» CallBehaviorAction Fleet Viewpoints::Requirements Viewpoint::Requirements Method::Introduction _st_reqs_intro -> part 'Fleet Documents'::'Fleet Handbook Document'::Requirements::paragraph +«BulletedList» CallBehaviorAction Fleet Viewpoints::Requirements Viewpoint::Requirements Method::Requirement List _st_reqs_list -> part 'Fleet Documents'::'Fleet Handbook Document'::Requirements::list (its rows are the query 'Fleet Handbook Requirement List Rows') +«Paragraph» CallBehaviorAction Fleet Viewpoints::Requirements Viewpoint::Requirements Method::Requirement Texts _st_reqs_texts -> part 'Fleet Documents'::'Fleet Handbook Document'::Requirements::'paragraph 2' (its rows are the query 'Fleet Handbook Requirement Texts Rows') +«Viewpoint» Class Fleet Viewpoints::Safety Viewpoint _vp_safety -> 'Fleet Viewpoints'::'Safety Viewpoint' +Activity Fleet Viewpoints::Safety Viewpoint::Safety Method _act_safety -> 'Fleet Viewpoints'::'Safety Viewpoint'::'Safety Method' +ActivityFinalNode Fleet Viewpoints::Safety Viewpoint::Safety Method::<ActivityFinalNode> _safety_final -> final +ControlFlow Fleet Viewpoints::Safety Viewpoint::Safety Method::<ControlFlow> _safety_e2 +ControlFlow Fleet Viewpoints::Safety Viewpoint::Safety Method::<ControlFlow> _safety_e3 +ControlFlow Fleet Viewpoints::Safety Viewpoint::Safety Method::<ControlFlow> _safety_e4 +ControlFlow Fleet Viewpoints::Safety Viewpoint::Safety Method::<ControlFlow> _safety_e5 +InitialNode Fleet Viewpoints::Safety Viewpoint::Safety Method::<InitialNode> _safety_init -> start +«TableStructure» StructuredActivityNode Fleet Viewpoints::Safety Viewpoint::Safety Method::Safety Requirements _safety_table -> 'Safety Requirements' +ControlFlow Fleet Viewpoints::Safety Viewpoint::Safety Method::Safety Requirements::<ControlFlow> _safety_table_e2 +ControlFlow Fleet Viewpoints::Safety Viewpoint::Safety Method::Safety Requirements::<ControlFlow> _safety_table_e3 +InitialNode Fleet Viewpoints::Safety Viewpoint::Safety Method::Safety Requirements::<InitialNode> _safety_table_init -> start +Operation Fleet Viewpoints::Safety Viewpoint::View _op_safety_view -> 'Fleet Viewpoints'::'Safety Viewpoint'::View (its owner's usage view performs it, as a call on an object does) +«Viewpoint» Class Fleet Viewpoints::Selected Figures Viewpoint _vp_selected -> 'Fleet Viewpoints'::'Selected Figures Viewpoint' +ActivityFinalNode Fleet Viewpoints::Selected Figures Viewpoint::Selected Figures Method::<ActivityFinalNode> _selected_final -> final +ControlFlow Fleet Viewpoints::Selected Figures Viewpoint::Selected Figures Method::<ControlFlow> _selected_e2 +ControlFlow Fleet Viewpoints::Selected Figures Viewpoint::Selected Figures Method::<ControlFlow> _selected_e3 +ControlFlow Fleet Viewpoints::Selected Figures Viewpoint::Selected Figures Method::<ControlFlow> _selected_e4 +InitialNode Fleet Viewpoints::Selected Figures Viewpoint::Selected Figures Method::<InitialNode> _selected_init -> start +«Image» CallBehaviorAction Fleet Viewpoints::Selected Figures Viewpoint::Selected Figures Method::Image _st_selected_image -> part 'Fleet Documents'::'Fleet Brief Document'::'Truck Figures'::diagram +«Image» CallBehaviorAction Fleet Viewpoints::Selected Figures Viewpoint::Selected Figures Method::Image _st_selected_image -> part 'Fleet Documents'::'Fleet Brief Document'::'Truck Figures'::'diagram 2' +«Viewpoint» Class Fleet Viewpoints::Severed Viewpoint _vp_severed -> 'Fleet Viewpoints'::'Severed Viewpoint' +InitialNode Fleet Viewpoints::Severed Viewpoint::Severed Method::<InitialNode> _severed_init -> start +«Viewpoint» Class Fleet Viewpoints::Traceability Viewpoint _vp_trace -> 'Fleet Viewpoints'::'Traceability Viewpoint' +ActivityFinalNode Fleet Viewpoints::Traceability Viewpoint::Traceability Method::<ActivityFinalNode> _trace_final -> final +ControlFlow Fleet Viewpoints::Traceability Viewpoint::Traceability Method::<ControlFlow> _trace_e2 +ControlFlow Fleet Viewpoints::Traceability Viewpoint::Traceability Method::<ControlFlow> _trace_e3 +ControlFlow Fleet Viewpoints::Traceability Viewpoint::Traceability Method::<ControlFlow> _trace_e4 +ControlFlow Fleet Viewpoints::Traceability Viewpoint::Traceability Method::<ControlFlow> _trace_e5 +ControlFlow Fleet Viewpoints::Traceability Viewpoint::Traceability Method::<ControlFlow> _trace_e6 +ControlFlow Fleet Viewpoints::Traceability Viewpoint::Traceability Method::<ControlFlow> _trace_e7 +ForkNode Fleet Viewpoints::Traceability Viewpoint::Traceability Method::<ForkNode> _trace_fork -> 'fork' +InitialNode Fleet Viewpoints::Traceability Viewpoint::Traceability Method::<InitialNode> _trace_init -> start +MergeNode Fleet Viewpoints::Traceability Viewpoint::Traceability Method::<MergeNode> _trace_merge -> 'merge' +Package Fleet Views _pkg_views -> 'Fleet Views' +«Expose» Dependency Fleet Views::<Dependency> _expose_fleet -> 'Fleet Views'::Fleet +«Expose» Dependency Fleet Views::<Dependency> _expose_fleet_flow -> 'Fleet Views'::Fleet +«Expose» Dependency Fleet Views::<Dependency> _expose_fleet_internals -> 'Fleet Views'::Fleet +«Expose» Dependency Fleet Views::<Dependency> _expose_fleet_overview -> 'Fleet Views'::Fleet +«Expose» Dependency Fleet Views::<Dependency> _expose_gallery -> 'Fleet Views'::Gallery +«Expose» Dependency Fleet Views::<Dependency> _expose_selected -> 'Fleet Views'::'Truck Figures' +«Expose» Dependency Fleet Views::<Dependency> _expose_selected_flow -> 'Fleet Views'::'Truck Figures' +«Expose» Dependency Fleet Views::<Dependency> _expose_selected_internals -> 'Fleet Views'::'Truck Figures' +«Expose» Dependency Fleet Views::<Dependency> _expose_selected_overview -> 'Fleet Views'::'Truck Figures' +«Expose» Dependency Fleet Views::<Dependency> _expose_selected_again -> 'Fleet Views'::'Truck Figures' +«Expose» Dependency Fleet Views::<Dependency> _expose_others -> 'Fleet Views'::'Other Figures' +«Expose» Dependency Fleet Views::<Dependency> _expose_others_flow -> 'Fleet Views'::'Other Figures' +«Expose» Dependency Fleet Views::<Dependency> _expose_others_internals -> 'Fleet Views'::'Other Figures' +«Expose» Dependency Fleet Views::<Dependency> _expose_others_overview -> 'Fleet Views'::'Other Figures' +«Expose» Dependency Fleet Views::<Dependency> _expose_owners_internals -> 'Fleet Views'::'Figure Owners' +«Expose» Dependency Fleet Views::<Dependency> _expose_owners -> 'Fleet Views'::'Figure Owners' +«Expose» Dependency Fleet Views::<Dependency> _expose_nofig -> 'Fleet Views'::'No Figures' +«View» Class Fleet Views::Figure Owners _view_owners -> 'Fleet Views'::'Figure Owners' +«Conform» Generalization Fleet Views::Figure Owners::<Generalization> _gen_owners -> 'Fleet Views'::'Figure Owners' +«View» Class Fleet Views::Fleet _view_fleet -> 'Fleet Views'::Fleet +«Conform» Generalization Fleet Views::Fleet::<Generalization> _gen_fleet -> 'Fleet Views'::Fleet +Diagram Fleet Views::Fleet::Fleet Overview _diag_fleet_overview -> 'Fleet Views'::Fleet::'Fleet Overview' (a SysML Package Diagram written as a view rendered asTreeDiagram) +«View» Class Fleet Views::Gallery _view_gallery -> 'Fleet Views'::Gallery +«Conform» Generalization Fleet Views::Gallery::<Generalization> _gen_gallery -> 'Fleet Views'::Gallery +«View» Class Fleet Views::No Figures _view_nofig -> 'Fleet Views'::'No Figures' +«Conform» Generalization Fleet Views::No Figures::<Generalization> _gen_nofig -> 'Fleet Views'::'No Figures' +«View» Class Fleet Views::Other Figures _view_others -> 'Fleet Views'::'Other Figures' +«Conform» Generalization Fleet Views::Other Figures::<Generalization> _gen_others -> 'Fleet Views'::'Other Figures' +«View» Class Fleet Views::Truck Figures _view_selected -> 'Fleet Views'::'Truck Figures' +«Conform» Generalization Fleet Views::Truck Figures::<Generalization> _gen_selected -> 'Fleet Views'::'Truck Figures' +Package Fleet::Requirements _pkg_reqs -> Fleet::Requirements +«Requirement» Class Fleet::Requirements::Axle Count _req_axles -> Fleet::Requirements::'Axle Count' +«Requirement» Class Fleet::Requirements::Brake Distance _req_brake -> Fleet::Requirements::'Brake Distance' +Comment Fleet::Requirements::Brake Distance::<Comment> _cmt_req_brake +«Requirement» Class Fleet::Requirements::Load Limit _req_load -> Fleet::Requirements::'Load Limit' +Comment Fleet::Requirements::Load Limit::<Comment> _cmt_req_load +Package Fleet::Structure _pkg_structure -> Fleet::Structure +«Satisfy» Abstraction Fleet::Structure::<Abstraction> _dep_satisfy_truck -> Fleet::Structure::Truck +«Satisfy» Abstraction Fleet::Structure::<Abstraction> _dep_satisfy_trailer -> Fleet::Structure::Trailer +«Block» Class Fleet::Structure::Axle _blk_axle -> Fleet::Structure::Axle +«Block» Class Fleet::Structure::Trailer _blk_trailer -> Fleet::Structure::Trailer +Comment Fleet::Structure::Trailer::<Comment> _cmt_trailer +Property Fleet::Structure::Trailer::payload _prop_trailer_payload -> Fleet::Structure::Trailer::payload +«Block» Class Fleet::Structure::Truck _blk_truck -> Fleet::Structure::Truck +Diagram Fleet::Structure::Truck Structure _diag_truck -> Fleet::Structure::'Truck Structure' (a SysML Block Definition Diagram written as a view rendered asTreeDiagram) +Comment Fleet::Structure::Truck::<Comment> _cmt_truck +Diagram Fleet::Structure::Truck::Truck Internals _diag_truck_internals -> Fleet::Structure::Truck::'Truck Internals' (a SysML Internal Block Diagram written as a view rendered asInterconnectionDiagram) +Property Fleet::Structure::Truck::axles _prop_axles -> Fleet::Structure::Truck::axles +Property Fleet::Structure::Truck::payload _prop_payload -> Fleet::Structure::Truck::payload +Model Model _m (the root model's members are written at the top level) + +## skipped (4) +«AttachedFile» Comment Fleet Documents::Notes::<Comment> _note_blank_image (empty comment) +«CollaboratorParagraph» Comment Fleet Documents::Notes::<Comment> _note_empty (empty comment) +Extension Fleet Profile::<Extension> _ext_safety (an extension binds a stereotype to the metaclass it extends; v2 metadata applies to any element) +Property Fleet Profile::Safety::base_Class _st_safety_base (an extension end names the metaclass the stereotype extends; v2 metadata applies to any element) diff --git a/tests/migrate/testdata/xmi/documents.golden.sysml b/tests/migrate/testdata/xmi/documents.golden.sysml new file mode 100644 index 0000000000..fa706ad3c8 --- /dev/null +++ b/tests/migrate/testdata/xmi/documents.golden.sysml @@ -0,0 +1,622 @@ +package 'Fleet Profile' { + metadata def Safety; +} +package Fleet { + package Structure { + part def Truck { + doc /* Hauls one trailer. */ + attribute payload : ScalarValues::Real; + part axles : Axle[2]; + satisfy requirement : Fleet::Requirements::'Load Limit'; + view 'Truck Internals' { + expose axles; + render Views::asInterconnectionDiagram; + } + } + part def Trailer { + doc /* Carries the load. */ + attribute payload : ScalarValues::Real; + satisfy requirement : Fleet::Requirements::'Brake Distance'; + } + part def Axle; + view 'Truck Structure' { + expose Truck; + expose Trailer; + expose Axle; + render Views::asTreeDiagram; + } + } + package Requirements { + requirement def <'R-1'> 'Load Limit' { + doc /* The payload stays under the axle rating. */ + comment /* The payload stays under the axle rating. */ + } + requirement def <'R-2'> 'Brake Distance' { + doc /* A loaded truck stops within the legal distance. */ + comment /* A loaded truck stops within the legal distance. */ + @'Fleet Profile'::Safety; + } + requirement def <'R-3'> 'Axle Count' { + doc /* A truck has two axles. */ + @'Fleet Profile'::Safety; + } + } +} +package 'Fleet Viewpoints' { + viewpoint 'Parts Viewpoint' { + doc /* List the parts of the fleet. */ + action def 'Parts Method' { + view 'Parts Method Flow' : StandardViewDefinitions::ActionFlowView { + expose 'Parts Method'; + render Views::asInterconnectionDiagram; + } + first start then 'Collect Owned Elements'; + action 'Collect Owned Elements' { + out result[0..1]; + } + first 'Collect Owned Elements' then 'Filter By Metaclasses'; + action 'Filter By Metaclasses' { + in input[0..1]; + } + first 'Filter By Metaclasses' then 'Sort By Name'; + action 'Sort By Name'; + first 'Sort By Name' then 'Fleet Parts'; + action 'Fleet Parts' { + first start then Name; + action Name; + first Name then Payload; + action Payload; + first Payload then 'Qualified Name'; + action 'Qualified Name'; + first 'Qualified Name' then 'Owner Name'; + action 'Owner Name'; + first 'Owner Name' then Alias; + action Alias; + first Alias then done; + } + first 'Fleet Parts' then final; + action final terminate; + flow 'Collect Owned Elements'.result to 'Filter By Metaclasses'.input; + } + perform action 'parts Method' : 'Parts Method'; + } + viewpoint 'Requirements Viewpoint' { + doc /* List the requirements. + * method: Fleet Viewpoints::Requirements Viewpoint::Requirements Method + */ + action def 'Requirements Method' { + first start then 'Collect Owned Elements'; + action 'Collect Owned Elements'; + first 'Collect Owned Elements' then 'Filter By Stereotypes'; + action 'Filter By Stereotypes'; + first 'Filter By Stereotypes' then 'Filter By Names'; + action 'Filter By Names'; + first 'Filter By Names' then Introduction; + action Introduction; + first Introduction then 'Requirement List'; + action 'Requirement List'; + first 'Requirement List' then 'Requirement Texts'; + action 'Requirement Texts'; + first 'Requirement Texts' then final; + action final terminate; + } + } + viewpoint 'Safety Viewpoint' { + doc /* List the safety requirements. */ + abstract action def View; + action 'view' : View; + action def 'Safety Method' { + first start then 'Collect Owned Elements'; + action 'Collect Owned Elements'; + first 'Collect Owned Elements' then 'Filter By Stereotypes'; + action 'Filter By Stereotypes'; + first 'Filter By Stereotypes' then 'Filter By Names'; + action 'Filter By Names'; + first 'Filter By Names' then 'Safety Requirements'; + action 'Safety Requirements' { + first start then Name; + action Name; + first Name then Text; + action Text; + first Text then Owner; + action Owner; + } + first 'Safety Requirements' then final; + action final terminate; + } + } + viewpoint 'Figures Viewpoint' { + action def 'Figures Method' { + first start then Image; + action Image; + first Image then final; + action final terminate; + } + perform action 'figures Method' : 'Figures Method'; + } + viewpoint 'Gallery Viewpoint' { + action def 'Gallery Method' { + first start then Image; + action Image; + first Image then final; + action final terminate; + } + perform action 'gallery Method' : 'Gallery Method'; + } + viewpoint 'Selected Figures Viewpoint' { + action def 'Selected Figures Method' { + first start then 'Truck Diagrams'; + action 'Truck Diagrams'; + first 'Truck Diagrams' then Sort; + action Sort; + first Sort then Image; + action Image; + first Image then final; + action final terminate; + } + perform action 'selected Figures Method' : 'Selected Figures Method'; + } + viewpoint 'Other Figures Viewpoint' { + action def 'Other Figures Method' { + first start then 'Not Truck Diagrams'; + action 'Not Truck Diagrams'; + first 'Not Truck Diagrams' then Image; + action Image; + first Image then final; + action final terminate; + } + perform action 'other Figures Method' : 'Other Figures Method'; + } + viewpoint 'Figure Owners Viewpoint' { + action def 'Figure Owners Method' { + first start then 'Collect Owners'; + action 'Collect Owners'; + first 'Collect Owners' then Sort; + action Sort; + first Sort then List; + action List; + first List then final; + action final terminate; + } + perform action 'figure Owners Method' : 'Figure Owners Method'; + } + viewpoint 'No Figures Viewpoint' { + action def 'No Figures Method' { + first start then 'Packages Only'; + action 'Packages Only'; + first 'Packages Only' then Image; + action Image; + first Image then final; + action final terminate; + } + perform action 'no Figures Method' : 'No Figures Method'; + } + viewpoint 'Traceability Viewpoint' { + action def 'Traceability Method' { + first start then 'fork'; + fork 'fork'; + first 'fork' then 'Collect Satisfiers'; + first 'fork' then 'Collect Owned Elements'; + action 'Collect Satisfiers'; + first 'Collect Satisfiers' then 'merge'; + action 'Collect Owned Elements'; + first 'Collect Owned Elements' then 'merge'; + merge 'merge'; + first 'merge' then 'Related Elements'; + action 'Related Elements'; + first 'Related Elements' then final; + action final terminate; + } + perform action 'traceability Method' : 'Traceability Method'; + } + viewpoint 'Oddities Viewpoint' { + action def 'Oddities Method' { + first start then 'Evaluated Paragraph'; + action 'Evaluated Paragraph'; + first 'Evaluated Paragraph' then Image; + action Image; + first Image then 'Collect Owned Elements'; + action 'Collect Owned Elements'; + first 'Collect Owned Elements' then 'Collect Types'; + action 'Collect Types'; + first 'Collect Types' then 'Type Table'; + action 'Type Table' { + first start then Name; + action Name; + } + first 'Type Table' then 'Per Truck'; + action 'Per Truck' : 'Fleet Viewpoints'::'Oddities Viewpoint'::'Per Truck'; + first 'Per Truck' then 'Missing Behavior'; + action 'Missing Behavior' { + /* not migrated: CallBehaviorAction 'Missing Behavior' — 1 behavior reference(s) resolve to nothing in the document (_act_gone); the action calls no behavior */ + } + first 'Missing Behavior' then final; + action final terminate; + } + action def 'Per Truck' { + first start then Paragraph; + action Paragraph; + first Paragraph then 'Per Truck Again'; + action 'Per Truck Again' : 'Per Truck'; + first 'Per Truck Again' then final; + action final terminate; + /* applied stereotype «Dynamic_View»: loop = true; title = Per Truck */ + } + perform action 'oddities Method' : 'Oddities Method'; + } + viewpoint 'Broken Viewpoint' { + action def 'Broken Method' { + first start then Paragraph; + action Paragraph; + first Paragraph then final; + action final terminate; + } + perform action 'broken Method' : 'Broken Method'; + } + viewpoint 'Severed Viewpoint' { + action def 'Severed Method' { + first start then Paragraph; + action Paragraph; + } + perform action 'severed Method' : 'Severed Method'; + } + viewpoint 'Headless Viewpoint' { + doc /* method: _act_vanished */ + } +} +package 'Fleet Documents' { + part def 'Fleet Handbook' { + view introduction :> Introduction; + view requirements :> Requirements; + view figures :> Figures; + view traceability :> Traceability; + view oddities :> Oddities; + view broken :> Broken; + view severed :> Severed; + view headless :> Headless; + view notes :> Notes; + attribute author : ScalarValues::String; + /* applied stereotype «Document» */ + } + part def 'Fleet Brief' { + view figures :> Figures; + view fleet :> 'Fleet Views'::Fleet; + view gallery :> 'Fleet Views'::Gallery; + view selected :> 'Fleet Views'::'Truck Figures'; + view others :> 'Fleet Views'::'Other Figures'; + view owners :> 'Fleet Views'::'Figure Owners'; + view noFigures :> 'Fleet Views'::'No Figures'; + /* applied stereotype «Document» */ + } + view Introduction { + satisfy 'Fleet Viewpoints'::'Parts Viewpoint'; + expose Fleet::Structure::**; + } + view Requirements { + satisfy 'Fleet Viewpoints'::'Requirements Viewpoint'; + view safety :> Safety; + expose Fleet::Requirements::**; + expose Fleet::Requirements::**; + } + view Safety { + satisfy 'Fleet Viewpoints'::'Safety Viewpoint'; + public import Fleet::*; + /* applied stereotype «view» */ + } + view Figures { + satisfy 'Fleet Viewpoints'::'Figures Viewpoint'; + expose Fleet::Structure::'Truck Structure'; + } + view Traceability { + satisfy 'Fleet Viewpoints'::'Traceability Viewpoint'; + expose Fleet::Requirements::'Load Limit'; + } + view Oddities { + satisfy 'Fleet Viewpoints'::'Oddities Viewpoint'; + expose Fleet::Structure::Truck; + } + view Broken { + satisfy 'Fleet Viewpoints'::'Broken Viewpoint'; + expose Fleet::**; + } + view Severed { + satisfy 'Fleet Viewpoints'::'Severed Viewpoint'; + expose Fleet::**; + } + view Headless { + satisfy 'Fleet Viewpoints'::'Headless Viewpoint'; + } + view Notes { + doc /* Second note. */ + comment /* First note. */ + comment /* Figure: the fleet at the depot */ + comment /* Named, not bodied. */ + /* not migrated: «Conform» Generalization (_gen_notes) — the viewpoint is not in the document; applied stereotypes «Conform» */ + } + calc def 'Fleet Handbook Fleet Parts Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::OrderBy( + source = DocumentQueries::WhereType( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Fleet::Structure")), + maxDepth = 1), + type = ("PartDefinition", "RequirementDefinition", "ConstraintDefinition", "PortDefinition", "VerificationCaseDefinition", "ActionDefinition", "StateDefinition", "CalculationDefinition", "ViewUsage", "ViewpointUsage")), + property = "name", + direction = "ascending", + missing = "last", + multiple = "first"), + properties = ("name", "qualifiedName", "documentation"), + columns = ( + DocumentQueries::Column(name = "Payload", expression = Fleet::Structure::Truck::payload ?? ""), + DocumentQueries::Column(name = "name 2", expression = Fleet::Structure::Truck::payload ?? ""))) + } + calc def 'Fleet Handbook Requirement List Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::WhereName( + source = DocumentQueries::WhereType( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Fleet::Requirements"))), + type = ("RequirementDefinition")), + operator = "matches", + value = "^(?:Axle.*)$|^(?:Brake.*)$|^(?:Load.*)$"), + properties = ("name")) + } + calc def 'Fleet Handbook Requirement Texts Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::WhereName( + source = DocumentQueries::WhereType( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Fleet::Requirements"))), + type = ("RequirementDefinition")), + operator = "matches", + value = "^(?:Axle.*)$|^(?:Brake.*)$|^(?:Load.*)$"), + properties = ("documentation")) + } + calc def 'Fleet Handbook Safety Requirements Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::Except( + source = DocumentQueries::WhereMetadata( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Fleet"))), + 'metadata' = ("Fleet Profile::Safety")), + exclude = DocumentQueries::WhereName( + source = DocumentQueries::WhereMetadata( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Fleet"))), + 'metadata' = ("Fleet Profile::Safety")), + operator = "matches", + value = "^(?:.*Count)$")), + properties = ("name", "documentation")) + } + calc def 'Fleet Handbook Related Elements Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::OrderBy( + source = DocumentQueries::Union( + source = DocumentQueries::RelatedElements( + source = DocumentQueries::Named(qualifiedName = ("Fleet::Requirements::Load Limit")), + relationshipKind = "satisfaction", + direction = "incoming", + maxDepth = 1), + other = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Fleet::Requirements::Load Limit")), + maxDepth = 1)), + property = "name", + direction = "ascending", + missing = "last", + multiple = "first"), + properties = ("name", "documentation")) + } + part def 'Fleet Handbook Document' :> DocumentQueries::Document { + attribute redefines title = "Fleet Handbook"; + part Introduction : DocumentQueries::Section { + attribute redefines title = "Introduction"; + part table : DocumentQueries::Table { + attribute redefines caption = "Fleet Parts"; + calc rows : 'Fleet Handbook Fleet Parts Rows'; + } + part paragraph : DocumentQueries::Paragraph { + attribute redefines text = "The parts of the fleet, by name."; + } + } + part Requirements : DocumentQueries::Section { + attribute redefines title = "Requirements"; + part paragraph : DocumentQueries::Paragraph { + attribute redefines text = "Every truck of the fleet satisfies these requirements."; + } + part list : DocumentQueries::List { + attribute redefines style = "number"; + calc items : 'Fleet Handbook Requirement List Rows'; + } + part 'paragraph 2' : DocumentQueries::Paragraph { + calc values : 'Fleet Handbook Requirement Texts Rows'; + } + part Safety : DocumentQueries::Section { + attribute redefines title = "Safety"; + part table : DocumentQueries::Table { + attribute redefines caption = "Safety Requirements"; + calc rows : 'Fleet Handbook Safety Requirements Rows'; + } + } + } + part Figures : DocumentQueries::Section { + attribute redefines title = "Figures"; + part diagram : DocumentQueries::Diagram { + attribute redefines caption = "Truck Structure"; + ref redefines source = Fleet::Structure::'Truck Structure'; + } + part paragraph : DocumentQueries::Paragraph { + attribute redefines text = "The truck and what it hauls"; + } + } + part Traceability : DocumentQueries::Section { + attribute redefines title = "Traceability"; + part list : DocumentQueries::List { + attribute redefines style = "bullet"; + calc items : 'Fleet Handbook Related Elements Rows'; + } + } + part Oddities : DocumentQueries::Section { + attribute redefines title = "Oddities"; + /* not migrated: «Paragraph» CallBehaviorAction 'Evaluated Paragraph' — its body is evaluated as OCL, which no query evaluates */ + /* not migrated: «Image» CallBehaviorAction 'Image' — it shows no diagram: only a diagram the view exposes or the node targets directly has a view to show */ + /* not migrated: «TableStructure» StructuredActivityNode 'Type Table' — the elements it shows pass through «CollectOwnedElements» Fleet Viewpoints::Oddities Viewpoint::Oddities Method::Collect Owned Elements is not migrated: the depth "many" is not a whole number */ + part 'Per Truck' : DocumentQueries::Section { + attribute redefines title = "Per Truck"; + part paragraph : DocumentQueries::Paragraph { + attribute redefines text = "One truck."; + } + part 'Per Truck' : DocumentQueries::Section { + attribute redefines title = "Per Truck"; + /* not migrated: the «Dynamic_View» Activity Fleet Viewpoints::Oddities Viewpoint::Per Truck calls itself, and a recursive section has no static spelling */ + } + } + } + part Broken : DocumentQueries::Section { + attribute redefines title = "Broken"; + /* not migrated: the method Fleet Viewpoints::Broken Viewpoint::Broken Method is not migrated: no initial node */ + } + part Severed : DocumentQueries::Section { + attribute redefines title = "Severed"; + /* not migrated: the method Fleet Viewpoints::Severed Viewpoint::Severed Method is not migrated: ControlFlow _severed_e2's target "_severed_gone" names no node */ + } + part Headless : DocumentQueries::Section { + attribute redefines title = "Headless"; + /* not migrated: the viewpoint Fleet Viewpoints::Headless Viewpoint's method is not migrated: method "_act_vanished" names no element */ + } + part Notes : DocumentQueries::Section { + attribute redefines title = "Notes"; + part paragraph : DocumentQueries::Paragraph { + attribute redefines text = "First note."; + } + part 'paragraph 2' : DocumentQueries::Paragraph { + attribute redefines text = "Second note."; + } + part 'paragraph 3' : DocumentQueries::Paragraph { + attribute redefines text = "Figure: the fleet at the depot"; + } + /* not migrated: «Image Paragraph» Comment '<Comment>' — the attached image "depot.png" has no caption, and a Diagram shows a view, not an image file */ + /* not migrated: «Paragraph» Comment '<Comment>' — property "META:QPROP:Element:name" is not the comment body */ + /* not migrated: «Paragraph» Comment '<Comment>' — the paragraph's comment has no body */ + } + } + calc def 'Fleet Brief List Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::OrderBy( + source = DocumentQueries::Named(qualifiedName = ("Fleet::Structure::Truck", "Fleet::Structure")), + property = "name", + direction = "ascending", + missing = "last", + multiple = "first"), + properties = ("name")) + } + part def 'Fleet Brief Document' :> DocumentQueries::Document { + attribute redefines title = "Fleet Brief"; + ref truck : $::Fleet::Structure::Truck; + part Figures : DocumentQueries::Section { + attribute redefines title = "Figures"; + part diagram : DocumentQueries::Diagram { + attribute redefines caption = "Truck Structure"; + ref redefines source = $::Fleet::Structure::'Truck Structure'; + } + part paragraph : DocumentQueries::Paragraph { + attribute redefines text = "The truck and what it hauls"; + } + } + part Fleet : DocumentQueries::Section { + attribute redefines title = "Fleet"; + part diagram : DocumentQueries::Diagram { + attribute redefines caption = "Truck Structure"; + ref redefines source = $::Fleet::Structure::'Truck Structure'; + } + part paragraph : DocumentQueries::Paragraph { + attribute redefines text = "The truck and what it hauls"; + } + /* not migrated: «Image» CallBehaviorAction 'Image' — the SysML Activity Diagram 'Parts Method Flow' is a view rendered as textual notation, which a document does not draw */ + part 'diagram 2' : DocumentQueries::Diagram { + attribute redefines caption = "Truck Internals"; + ref redefines source = truck.'Truck Internals'; + } + part 'diagram 3' : DocumentQueries::Diagram { + attribute redefines caption = "Fleet Overview"; + ref redefines source = 'Fleet Views'::Fleet.'Fleet Overview'; + } + } + part Gallery : DocumentQueries::Section { + attribute redefines title = "Gallery"; + part diagram : DocumentQueries::Diagram { + attribute redefines caption = "Figure: Inside the truck"; + ref redefines source = truck.'Truck Internals'; + } + } + part 'Truck Figures' : DocumentQueries::Section { + attribute redefines title = "Truck Figures"; + part diagram : DocumentQueries::Diagram { + attribute redefines caption = "Truck Internals"; + ref redefines source = truck.'Truck Internals'; + } + part 'diagram 2' : DocumentQueries::Diagram { + attribute redefines caption = "Truck Structure"; + ref redefines source = $::Fleet::Structure::'Truck Structure'; + } + } + part 'Other Figures' : DocumentQueries::Section { + attribute redefines title = "Other Figures"; + part diagram : DocumentQueries::Diagram { + attribute redefines caption = "Fleet Overview"; + ref redefines source = 'Fleet Views'::Fleet.'Fleet Overview'; + } + } + part 'Figure Owners' : DocumentQueries::Section { + attribute redefines title = "Figure Owners"; + part list : DocumentQueries::List { + attribute redefines style = "bullet"; + calc items : 'Fleet Brief List Rows'; + } + } + part 'No Figures' : DocumentQueries::Section { + attribute redefines title = "No Figures"; + } + } +} +package 'Fleet Views' { + view Fleet { + satisfy 'Fleet Viewpoints'::'Figures Viewpoint'; + expose $::Fleet::Structure::'Truck Structure'; + expose 'Fleet Viewpoints'::'Parts Viewpoint'::'Parts Method'::'Parts Method Flow'; + expose $::Fleet::Structure::Truck::'Truck Internals'; + expose 'Fleet Overview'; + view 'Fleet Overview' { + expose $::Fleet::Structure; + expose $::Fleet::Requirements; + render Views::asTreeDiagram; + } + } + view Gallery { + satisfy 'Fleet Viewpoints'::'Gallery Viewpoint'; + expose $::Fleet::Structure::Truck::'Truck Internals'; + } + view 'Truck Figures' { + satisfy 'Fleet Viewpoints'::'Selected Figures Viewpoint'; + expose $::Fleet::Structure::'Truck Structure'; + expose 'Fleet Viewpoints'::'Parts Viewpoint'::'Parts Method'::'Parts Method Flow'; + expose $::Fleet::Structure::Truck::'Truck Internals'; + expose Fleet::'Fleet Overview'; + expose $::Fleet::Structure::'Truck Structure'; + } + view 'Other Figures' { + satisfy 'Fleet Viewpoints'::'Other Figures Viewpoint'; + expose $::Fleet::Structure::'Truck Structure'; + expose 'Fleet Viewpoints'::'Parts Viewpoint'::'Parts Method'::'Parts Method Flow'; + expose $::Fleet::Structure::Truck::'Truck Internals'; + expose Fleet::'Fleet Overview'; + } + view 'Figure Owners' { + satisfy 'Fleet Viewpoints'::'Figure Owners Viewpoint'; + expose $::Fleet::Structure::Truck::'Truck Internals'; + expose $::Fleet::Structure::'Truck Structure'; + } + view 'No Figures' { + satisfy 'Fleet Viewpoints'::'No Figures Viewpoint'; + expose $::Fleet::Structure::'Truck Structure'; + } +} diff --git a/tests/migrate/testdata/xmi/documents.xmi b/tests/migrate/testdata/xmi/documents.xmi new file mode 100644 index 0000000000..b094ddc954 --- /dev/null +++ b/tests/migrate/testdata/xmi/documents.xmi @@ -0,0 +1,640 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xmi:XMI xmi:version="2.5.1" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" + xmlns:uml="http://www.omg.org/spec/UML/20161101" + xmlns:sysml="http://www.omg.org/spec/SysML/20181001/SysML" + xmlns:MagicDraw_Profile="http://www.omg.org/spec/UML/20131001/MagicDrawProfile" + xmlns:Document_Profile_="http://www.magicdraw.com/schemas/manual/Document_Profile.xmi" + xmlns:Document_View_Collaborator_Profile="http://www.magicdraw.com/schemas/manual/Document_View_Collaborator_Profile.xmi" + xmlns:Fleet_Profile="http://example.com/schemas/Fleet_Profile.xmi" + xmlns:diagram="http://www.example.com/tool/diagram"> + <xmi:Documentation exporter="Example UML Tool" exporterVersion="1.0"/> + <uml:Model xmi:type="uml:Model" xmi:id="_m" name="Model"> + + <packagedElement xmi:type="uml:Profile" xmi:id="_fleet_profile" name="Fleet Profile" URI="http://example.com/schemas/Fleet_Profile.xmi"> + <packagedElement xmi:type="uml:Stereotype" xmi:id="_st_safety" name="Safety"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_st_safety_base" name="base_Class" association="_ext_safety"> + <type xmi:type="uml:Class" href="http://www.omg.org/spec/UML/20131001/UML.xmi#Class"/> + </ownedAttribute> + </packagedElement> + <packagedElement xmi:type="uml:Extension" xmi:id="_ext_safety" memberEnd="_st_safety_base _ext_safety_end"> + <ownedEnd xmi:type="uml:ExtensionEnd" xmi:id="_ext_safety_end" name="extension_Safety" type="_st_safety" aggregation="composite"/> + </packagedElement> + </packagedElement> + + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_fleet" name="Fleet"> + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_structure" name="Structure"> + <packagedElement xmi:type="uml:Class" xmi:id="_blk_truck" name="Truck"> + <ownedComment xmi:type="uml:Comment" xmi:id="_cmt_truck" body="Hauls one trailer."/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_prop_payload" name="payload"> + <type xmi:type="uml:PrimitiveType" href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#Real"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_prop_axles" name="axles" type="_blk_axle" aggregation="composite"> + <lowerValue xmi:type="uml:LiteralInteger" xmi:id="_prop_axles_l" value="2"/> + <upperValue xmi:type="uml:LiteralUnlimitedNatural" xmi:id="_prop_axles_u" value="2"/> + </ownedAttribute> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_blk_trailer" name="Trailer"> + <ownedComment xmi:type="uml:Comment" xmi:id="_cmt_trailer" body="Carries the load."/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_prop_trailer_payload" name="payload"> + <type xmi:type="uml:PrimitiveType" href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#Real"/> + </ownedAttribute> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_blk_axle" name="Axle"/> + <packagedElement xmi:type="uml:Abstraction" xmi:id="_dep_satisfy_truck" client="_blk_truck" supplier="_req_load"/> + <packagedElement xmi:type="uml:Abstraction" xmi:id="_dep_satisfy_trailer" client="_blk_trailer" supplier="_req_brake"/> + </packagedElement> + + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_reqs" name="Requirements"> + <packagedElement xmi:type="uml:Class" xmi:id="_req_load" name="Load Limit"> + <ownedComment xmi:type="uml:Comment" xmi:id="_cmt_req_load" body="The payload stays under the axle rating."/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_req_brake" name="Brake Distance"> + <ownedComment xmi:type="uml:Comment" xmi:id="_cmt_req_brake" body="A loaded truck stops within the legal distance."/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_req_axles" name="Axle Count"/> + </packagedElement> + </packagedElement> + + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_viewpoints" name="Fleet Viewpoints"> + + <packagedElement xmi:type="uml:Class" xmi:id="_vp_parts" name="Parts Viewpoint" classifierBehavior="_act_parts"> + <ownedBehavior xmi:type="uml:Activity" xmi:id="_act_parts" name="Parts Method"> + <node xmi:type="uml:InitialNode" xmi:id="_parts_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_parts_collect" name="Collect Owned Elements"> + <result xmi:type="uml:OutputPin" xmi:id="_parts_collect_out" name="result"/> + </node> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_parts_filter" name="Filter By Metaclasses"> + <argument xmi:type="uml:InputPin" xmi:id="_parts_filter_in" name="input"/> + </node> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_parts_sort" name="Sort By Name"/> + <node xmi:type="uml:StructuredActivityNode" xmi:id="_parts_table" name="Fleet Parts"> + <node xmi:type="uml:InitialNode" xmi:id="_parts_table_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_parts_col_name" name="Name"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_parts_col_payload" name="Payload"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_parts_col_qname" name="Qualified Name"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_parts_col_owner" name="Owner Name"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_parts_col_alias" name="Alias"/> + <node xmi:type="uml:FlowFinalNode" xmi:id="_parts_table_final"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_parts_table_e1" source="_parts_table_init" target="_parts_col_name"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_parts_table_e2" source="_parts_col_name" target="_parts_col_payload"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_parts_table_e3" source="_parts_col_payload" target="_parts_col_qname"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_parts_table_e4" source="_parts_col_qname" target="_parts_col_owner"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_parts_table_e5" source="_parts_col_owner" target="_parts_col_alias"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_parts_table_e6" source="_parts_col_alias" target="_parts_table_final"/> + </node> + <node xmi:type="uml:ActivityFinalNode" xmi:id="_parts_final"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_parts_e1" source="_parts_init" target="_parts_collect"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_parts_e2" source="_parts_collect" target="_parts_filter"/> + <edge xmi:type="uml:ObjectFlow" xmi:id="_parts_o1" source="_parts_collect_out" target="_parts_filter_in"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_parts_e3" source="_parts_filter" target="_parts_sort"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_parts_e4" source="_parts_sort" target="_parts_table"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_parts_e5" source="_parts_table" target="_parts_final"/> + </ownedBehavior> + </packagedElement> + + <packagedElement xmi:type="uml:Class" xmi:id="_vp_reqs" name="Requirements Viewpoint"> + <ownedBehavior xmi:type="uml:Activity" xmi:id="_act_reqs" name="Requirements Method"> + <node xmi:type="uml:InitialNode" xmi:id="_reqs_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_reqs_collect" name="Collect Owned Elements"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_reqs_filter" name="Filter By Stereotypes"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_reqs_names" name="Filter By Names"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_reqs_intro" name="Introduction"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_reqs_list" name="Requirement List"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_reqs_texts" name="Requirement Texts"/> + <node xmi:type="uml:ActivityFinalNode" xmi:id="_reqs_final"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_reqs_e1" source="_reqs_init" target="_reqs_collect"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_reqs_e2" source="_reqs_collect" target="_reqs_filter"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_reqs_e3" source="_reqs_filter" target="_reqs_names"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_reqs_e3b" source="_reqs_names" target="_reqs_intro"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_reqs_e4" source="_reqs_intro" target="_reqs_list"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_reqs_e5" source="_reqs_list" target="_reqs_texts"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_reqs_e6" source="_reqs_texts" target="_reqs_final"/> + </ownedBehavior> + </packagedElement> + + <packagedElement xmi:type="uml:Class" xmi:id="_vp_safety" name="Safety Viewpoint"> + <ownedOperation xmi:type="uml:Operation" xmi:id="_op_safety_view" name="View"/> + <ownedBehavior xmi:type="uml:Activity" xmi:id="_act_safety" name="Safety Method" specification="_op_safety_view"> + <node xmi:type="uml:InitialNode" xmi:id="_safety_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_safety_collect" name="Collect Owned Elements"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_safety_filter" name="Filter By Stereotypes"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_safety_names" name="Filter By Names"/> + <node xmi:type="uml:StructuredActivityNode" xmi:id="_safety_table" name="Safety Requirements"> + <node xmi:type="uml:InitialNode" xmi:id="_safety_table_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_safety_col_name" name="Name"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_safety_col_doc" name="Text"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_safety_col_ocl" name="Owner"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_safety_table_e1" source="_safety_table_init" target="_safety_col_name"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_safety_table_e2" source="_safety_col_name" target="_safety_col_doc"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_safety_table_e3" source="_safety_col_doc" target="_safety_col_ocl"/> + </node> + <node xmi:type="uml:ActivityFinalNode" xmi:id="_safety_final"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_safety_e1" source="_safety_init" target="_safety_collect"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_safety_e2" source="_safety_collect" target="_safety_filter"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_safety_e3" source="_safety_filter" target="_safety_names"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_safety_e4" source="_safety_names" target="_safety_table"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_safety_e5" source="_safety_table" target="_safety_final"/> + </ownedBehavior> + </packagedElement> + + <packagedElement xmi:type="uml:Class" xmi:id="_vp_figures" name="Figures Viewpoint" classifierBehavior="_act_figures"> + <ownedBehavior xmi:type="uml:Activity" xmi:id="_act_figures" name="Figures Method"> + <node xmi:type="uml:InitialNode" xmi:id="_figures_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_figures_image" name="Image"/> + <node xmi:type="uml:ActivityFinalNode" xmi:id="_figures_final"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_figures_e1" source="_figures_init" target="_figures_image"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_figures_e2" source="_figures_image" target="_figures_final"/> + </ownedBehavior> + </packagedElement> + + <packagedElement xmi:type="uml:Class" xmi:id="_vp_gallery" name="Gallery Viewpoint" classifierBehavior="_act_gallery"> + <ownedBehavior xmi:type="uml:Activity" xmi:id="_act_gallery" name="Gallery Method"> + <node xmi:type="uml:InitialNode" xmi:id="_gallery_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_gallery_image" name="Image"/> + <node xmi:type="uml:ActivityFinalNode" xmi:id="_gallery_final"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_gallery_e1" source="_gallery_init" target="_gallery_image"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_gallery_e2" source="_gallery_image" target="_gallery_final"/> + </ownedBehavior> + </packagedElement> + + <packagedElement xmi:type="uml:Class" xmi:id="_vp_selected" name="Selected Figures Viewpoint" classifierBehavior="_act_selected"> + <ownedBehavior xmi:type="uml:Activity" xmi:id="_act_selected" name="Selected Figures Method"> + <node xmi:type="uml:InitialNode" xmi:id="_selected_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_selected_names" name="Truck Diagrams"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_selected_sort" name="Sort"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_selected_image" name="Image"/> + <node xmi:type="uml:ActivityFinalNode" xmi:id="_selected_final"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_selected_e1" source="_selected_init" target="_selected_names"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_selected_e2" source="_selected_names" target="_selected_sort"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_selected_e3" source="_selected_sort" target="_selected_image"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_selected_e4" source="_selected_image" target="_selected_final"/> + </ownedBehavior> + </packagedElement> + + <packagedElement xmi:type="uml:Class" xmi:id="_vp_others" name="Other Figures Viewpoint" classifierBehavior="_act_others"> + <ownedBehavior xmi:type="uml:Activity" xmi:id="_act_others" name="Other Figures Method"> + <node xmi:type="uml:InitialNode" xmi:id="_others_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_others_names" name="Not Truck Diagrams"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_others_image" name="Image"/> + <node xmi:type="uml:ActivityFinalNode" xmi:id="_others_final"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_others_e1" source="_others_init" target="_others_names"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_others_e2" source="_others_names" target="_others_image"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_others_e3" source="_others_image" target="_others_final"/> + </ownedBehavior> + </packagedElement> + + <packagedElement xmi:type="uml:Class" xmi:id="_vp_owners" name="Figure Owners Viewpoint" classifierBehavior="_act_owners"> + <ownedBehavior xmi:type="uml:Activity" xmi:id="_act_owners" name="Figure Owners Method"> + <node xmi:type="uml:InitialNode" xmi:id="_owners_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_owners_collect" name="Collect Owners"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_owners_sort" name="Sort"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_owners_list" name="List"/> + <node xmi:type="uml:ActivityFinalNode" xmi:id="_owners_final"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_owners_e1" source="_owners_init" target="_owners_collect"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_owners_e2" source="_owners_collect" target="_owners_sort"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_owners_e3" source="_owners_sort" target="_owners_list"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_owners_e4" source="_owners_list" target="_owners_final"/> + </ownedBehavior> + </packagedElement> + + <packagedElement xmi:type="uml:Class" xmi:id="_vp_nofig" name="No Figures Viewpoint" classifierBehavior="_act_nofig"> + <ownedBehavior xmi:type="uml:Activity" xmi:id="_act_nofig" name="No Figures Method"> + <node xmi:type="uml:InitialNode" xmi:id="_nofig_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_nofig_packages" name="Packages Only"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_nofig_image" name="Image"/> + <node xmi:type="uml:ActivityFinalNode" xmi:id="_nofig_final"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_nofig_e1" source="_nofig_init" target="_nofig_packages"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_nofig_e2" source="_nofig_packages" target="_nofig_image"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_nofig_e3" source="_nofig_image" target="_nofig_final"/> + </ownedBehavior> + </packagedElement> + + <packagedElement xmi:type="uml:Class" xmi:id="_vp_trace" name="Traceability Viewpoint" classifierBehavior="_act_trace"> + <ownedBehavior xmi:type="uml:Activity" xmi:id="_act_trace" name="Traceability Method"> + <node xmi:type="uml:InitialNode" xmi:id="_trace_init"/> + <node xmi:type="uml:ForkNode" xmi:id="_trace_fork"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_trace_satisfiers" name="Collect Satisfiers"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_trace_owned" name="Collect Owned Elements"/> + <node xmi:type="uml:MergeNode" xmi:id="_trace_merge"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_trace_list" name="Related Elements"/> + <node xmi:type="uml:ActivityFinalNode" xmi:id="_trace_final"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_trace_e1" source="_trace_init" target="_trace_fork"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_trace_e2" source="_trace_fork" target="_trace_satisfiers"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_trace_e3" source="_trace_fork" target="_trace_owned"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_trace_e4" source="_trace_satisfiers" target="_trace_merge"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_trace_e5" source="_trace_owned" target="_trace_merge"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_trace_e6" source="_trace_merge" target="_trace_list"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_trace_e7" source="_trace_list" target="_trace_final"/> + </ownedBehavior> + </packagedElement> + + <packagedElement xmi:type="uml:Class" xmi:id="_vp_odd" name="Oddities Viewpoint" classifierBehavior="_act_odd"> + <ownedBehavior xmi:type="uml:Activity" xmi:id="_act_odd" name="Oddities Method"> + <node xmi:type="uml:InitialNode" xmi:id="_odd_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_odd_ocl" name="Evaluated Paragraph"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_odd_image" name="Image"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_odd_depth" name="Collect Owned Elements"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_odd_types" name="Collect Types"/> + <node xmi:type="uml:StructuredActivityNode" xmi:id="_odd_table" name="Type Table"> + <node xmi:type="uml:InitialNode" xmi:id="_odd_table_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_odd_col_name" name="Name"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_odd_table_e1" source="_odd_table_init" target="_odd_col_name"/> + </node> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_odd_dynamic" name="Per Truck" behavior="_act_per_truck"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_odd_missing" name="Missing Behavior" behavior="_act_gone"/> + <node xmi:type="uml:ActivityFinalNode" xmi:id="_odd_final"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_odd_e1" source="_odd_init" target="_odd_ocl"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_odd_e2" source="_odd_ocl" target="_odd_image"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_odd_e3" source="_odd_image" target="_odd_depth"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_odd_e4" source="_odd_depth" target="_odd_types"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_odd_e5" source="_odd_types" target="_odd_table"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_odd_e6" source="_odd_table" target="_odd_dynamic"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_odd_e7" source="_odd_dynamic" target="_odd_missing"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_odd_e8" source="_odd_missing" target="_odd_final"/> + </ownedBehavior> + <ownedBehavior xmi:type="uml:Activity" xmi:id="_act_per_truck" name="Per Truck"> + <node xmi:type="uml:InitialNode" xmi:id="_per_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_per_para" name="Paragraph"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_per_again" name="Per Truck Again" behavior="_act_per_truck"/> + <node xmi:type="uml:ActivityFinalNode" xmi:id="_per_final"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_per_e1" source="_per_init" target="_per_para"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_per_e2" source="_per_para" target="_per_again"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_per_e3" source="_per_again" target="_per_final"/> + </ownedBehavior> + </packagedElement> + + <packagedElement xmi:type="uml:Class" xmi:id="_vp_broken" name="Broken Viewpoint" classifierBehavior="_act_broken"> + <ownedBehavior xmi:type="uml:Activity" xmi:id="_act_broken" name="Broken Method"> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_broken_para" name="Paragraph"/> + <node xmi:type="uml:ActivityFinalNode" xmi:id="_broken_final"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_broken_e1" source="_broken_para" target="_broken_final"/> + </ownedBehavior> + </packagedElement> + + <packagedElement xmi:type="uml:Class" xmi:id="_vp_severed" name="Severed Viewpoint" classifierBehavior="_act_severed"> + <ownedBehavior xmi:type="uml:Activity" xmi:id="_act_severed" name="Severed Method"> + <node xmi:type="uml:InitialNode" xmi:id="_severed_init"/> + <node xmi:type="uml:CallBehaviorAction" xmi:id="_severed_para" name="Paragraph"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_severed_e1" source="_severed_init" target="_severed_para"/> + <edge xmi:type="uml:ControlFlow" xmi:id="_severed_e2" source="_severed_para" target="_severed_gone"/> + </ownedBehavior> + </packagedElement> + + <packagedElement xmi:type="uml:Class" xmi:id="_vp_headless" name="Headless Viewpoint"/> + </packagedElement> + + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_docs" name="Fleet Documents"> + <packagedElement xmi:type="uml:Class" xmi:id="_doc_handbook" name="Fleet Handbook"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_doc_intro" name="introduction" type="_view_intro" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_doc_reqs" name="requirements" type="_view_reqs" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_doc_figures" name="figures" type="_view_figures" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_doc_trace" name="traceability" type="_view_trace" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_doc_odd" name="oddities" type="_view_odd" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_doc_broken" name="broken" type="_view_broken" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_doc_severed" name="severed" type="_view_severed" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_doc_headless" name="headless" type="_view_headless" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_doc_notes" name="notes" type="_view_notes" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_doc_author" name="author"> + <type xmi:type="uml:PrimitiveType" href="http://www.omg.org/spec/UML/20131001/PrimitiveTypes.xmi#String"/> + </ownedAttribute> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_doc_brief" name="Fleet Brief"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_brief_figures" name="figures" type="_view_figures" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_brief_fleet" name="fleet" type="_view_fleet" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_brief_gallery" name="gallery" type="_view_gallery" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_brief_selected" name="selected" type="_view_selected" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_brief_others" name="others" type="_view_others" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_brief_owners" name="owners" type="_view_owners" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_brief_nofig" name="noFigures" type="_view_nofig" aggregation="composite"/> + </packagedElement> + + <packagedElement xmi:type="uml:Class" xmi:id="_view_intro" name="Introduction"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_intro" general="_vp_parts"/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_view_reqs" name="Requirements"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_reqs" general="_vp_reqs"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_reqs_safety" name="safety" type="_view_safety" aggregation="composite"/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_view_safety" name="Safety"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_safety" general="_vp_safety"/> + <packageImport xmi:type="uml:PackageImport" xmi:id="_imp_safety" importedPackage="_pkg_fleet"/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_view_figures" name="Figures"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_figures" general="_vp_figures"/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_view_trace" name="Traceability"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_trace" general="_vp_trace"/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_view_odd" name="Oddities"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_odd" general="_vp_odd"/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_view_broken" name="Broken"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_broken" general="_vp_broken"/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_view_severed" name="Severed"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_severed" general="_vp_severed"/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_view_headless" name="Headless"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_headless" general="_vp_headless"/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_view_notes" name="Notes"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_notes" general="_vp_gone"/> + <ownedComment xmi:type="uml:Comment" xmi:id="_note_second" body="Second note."/> + <ownedComment xmi:type="uml:Comment" xmi:id="_note_first" body="First note."/> + <ownedComment xmi:type="uml:Comment" xmi:id="_note_image" body="Figure: the fleet at the depot"/> + <ownedComment xmi:type="uml:Comment" xmi:id="_note_blank_image"/> + <ownedComment xmi:type="uml:Comment" xmi:id="_note_named" body="Named, not bodied."/> + <ownedComment xmi:type="uml:Comment" xmi:id="_note_empty"/> + </packagedElement> + + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_intro" client="_view_intro" supplier="_pkg_structure"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_reqs" client="_view_reqs" supplier="_pkg_reqs"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_reqs_again" client="_view_reqs" supplier="_pkg_reqs"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_figures" client="_view_figures" supplier="_diag_truck"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_trace" client="_view_trace" supplier="_req_load"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_odd" client="_view_odd" supplier="_blk_truck"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_broken" client="_view_broken" supplier="_pkg_fleet"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_severed" client="_view_severed" supplier="_pkg_fleet"/> + </packagedElement> + + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_views" name="Fleet Views"> + <packagedElement xmi:type="uml:Class" xmi:id="_view_fleet" name="Fleet"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_fleet" general="_vp_figures"/> + </packagedElement> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_fleet" client="_view_fleet" supplier="_diag_truck"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_fleet_flow" client="_view_fleet" supplier="_diag_parts_flow"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_fleet_internals" client="_view_fleet" supplier="_diag_truck_internals"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_fleet_overview" client="_view_fleet" supplier="_diag_fleet_overview"/> + <packagedElement xmi:type="uml:Class" xmi:id="_view_gallery" name="Gallery"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_gallery" general="_vp_gallery"/> + </packagedElement> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_gallery" client="_view_gallery" supplier="_diag_truck_internals"/> + <packagedElement xmi:type="uml:Class" xmi:id="_view_selected" name="Truck Figures"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_selected" general="_vp_selected"/> + </packagedElement> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_selected" client="_view_selected" supplier="_diag_truck"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_selected_flow" client="_view_selected" supplier="_diag_parts_flow"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_selected_internals" client="_view_selected" supplier="_diag_truck_internals"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_selected_overview" client="_view_selected" supplier="_diag_fleet_overview"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_selected_again" client="_view_selected" supplier="_diag_truck"/> + <packagedElement xmi:type="uml:Class" xmi:id="_view_others" name="Other Figures"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_others" general="_vp_others"/> + </packagedElement> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_others" client="_view_others" supplier="_diag_truck"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_others_flow" client="_view_others" supplier="_diag_parts_flow"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_others_internals" client="_view_others" supplier="_diag_truck_internals"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_others_overview" client="_view_others" supplier="_diag_fleet_overview"/> + <packagedElement xmi:type="uml:Class" xmi:id="_view_owners" name="Figure Owners"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_owners" general="_vp_owners"/> + </packagedElement> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_owners_internals" client="_view_owners" supplier="_diag_truck_internals"/> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_owners" client="_view_owners" supplier="_diag_truck"/> + <packagedElement xmi:type="uml:Class" xmi:id="_view_nofig" name="No Figures"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_nofig" general="_vp_nofig"/> + </packagedElement> + <packagedElement xmi:type="uml:Dependency" xmi:id="_expose_nofig" client="_view_nofig" supplier="_diag_truck"/> + </packagedElement> + + <xmi:Extension extender="Example UML Tool 1.0"> + <modelExtension> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_truck" name="Truck Structure" ownerOfDiagram="_pkg_structure"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="SysML Block Definition Diagram" umlType="Class Diagram"> + <diagramContents> + <usedElements>_blk_truck</usedElements> + <usedElements>_blk_trailer</usedElements> + <usedElements>_blk_axle</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_parts_flow" name="Parts Method Flow" ownerOfDiagram="_act_parts"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="SysML Activity Diagram" umlType="Activity Diagram"> + <diagramContents> + <usedElements>_parts_collect</usedElements> + <usedElements>_parts_filter</usedElements> + <usedElements>_parts_sort</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_truck_internals" name="Truck Internals" ownerOfDiagram="_blk_truck"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="SysML Internal Block Diagram" umlType="Composite Structure Diagram"> + <diagramContents> + <usedElements>_prop_axles</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_fleet_overview" name="Fleet Overview" ownerOfDiagram="_view_fleet"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="SysML Package Diagram" umlType="Package Diagram"> + <diagramContents> + <usedElements>_pkg_structure</usedElements> + <usedElements>_pkg_reqs</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + </modelExtension> + </xmi:Extension> + </uml:Model> + + <sysml:Block xmi:id="_st_blk_truck" base_Class="_blk_truck"/> + <sysml:Block xmi:id="_st_blk_trailer" base_Class="_blk_trailer"/> + <sysml:Block xmi:id="_st_blk_axle" base_Class="_blk_axle"/> + <sysml:Requirement xmi:id="_st_req_load" base_Class="_req_load" Id="R-1" Text="The payload stays under the axle rating."/> + <sysml:Requirement xmi:id="_st_req_brake" base_Class="_req_brake" Id="R-2" Text="A loaded truck stops within the legal distance."/> + <sysml:Requirement xmi:id="_st_req_axles" base_Class="_req_axles" Id="R-3" Text="A truck has two axles."/> + <sysml:Satisfy xmi:id="_st_satisfy_truck" base_Abstraction="_dep_satisfy_truck"/> + <sysml:Satisfy xmi:id="_st_satisfy_trailer" base_Abstraction="_dep_satisfy_trailer"/> + <Fleet_Profile:Safety xmi:id="_app_safety_brake" base_Class="_req_brake"/> + <Fleet_Profile:Safety xmi:id="_app_safety_axles" base_Class="_req_axles"/> + + <sysml:Viewpoint xmi:id="_st_vp_parts" base_Class="_vp_parts" purpose="List the parts of the fleet."/> + <sysml:Viewpoint xmi:id="_st_vp_reqs" base_Class="_vp_reqs" method="_act_reqs" purpose="List the requirements."/> + <sysml:Viewpoint xmi:id="_st_vp_safety" base_Class="_vp_safety" purpose="List the safety requirements."/> + <sysml:Viewpoint xmi:id="_st_vp_figures" base_Class="_vp_figures"/> + <sysml:Viewpoint xmi:id="_st_vp_gallery" base_Class="_vp_gallery"/> + <sysml:Viewpoint xmi:id="_st_vp_selected" base_Class="_vp_selected"/> + <sysml:Viewpoint xmi:id="_st_vp_others" base_Class="_vp_others"/> + <sysml:Viewpoint xmi:id="_st_vp_owners" base_Class="_vp_owners"/> + <sysml:Viewpoint xmi:id="_st_vp_nofig" base_Class="_vp_nofig"/> + <sysml:Viewpoint xmi:id="_st_vp_trace" base_Class="_vp_trace"/> + <sysml:Viewpoint xmi:id="_st_vp_odd" base_Class="_vp_odd"/> + <sysml:Viewpoint xmi:id="_st_vp_broken" base_Class="_vp_broken"/> + <sysml:Viewpoint xmi:id="_st_vp_severed" base_Class="_vp_severed"/> + <sysml:Viewpoint xmi:id="_st_vp_headless" base_Class="_vp_headless" method="_act_vanished"/> + + <sysml:View xmi:id="_st_view_intro" base_Class="_view_intro"/> + <sysml:View xmi:id="_st_view_reqs" base_Class="_view_reqs"/> + <Document_Profile_:view xmi:id="_st_view_safety" base_Class="_view_safety"/> + <sysml:View xmi:id="_st_view_figures" base_Class="_view_figures"/> + <sysml:View xmi:id="_st_view_trace" base_Class="_view_trace"/> + <sysml:View xmi:id="_st_view_odd" base_Class="_view_odd"/> + <sysml:View xmi:id="_st_view_broken" base_Class="_view_broken"/> + <sysml:View xmi:id="_st_view_severed" base_Class="_view_severed"/> + <sysml:View xmi:id="_st_view_headless" base_Class="_view_headless"/> + <sysml:View xmi:id="_st_view_notes" base_Class="_view_notes"/> + <sysml:View xmi:id="_st_view_fleet" base_Class="_view_fleet"/> + <sysml:View xmi:id="_st_view_gallery" base_Class="_view_gallery"/> + <sysml:View xmi:id="_st_view_selected" base_Class="_view_selected"/> + <sysml:View xmi:id="_st_view_others" base_Class="_view_others"/> + <sysml:View xmi:id="_st_view_owners" base_Class="_view_owners"/> + <sysml:View xmi:id="_st_view_nofig" base_Class="_view_nofig"/> + + <sysml:Conform xmi:id="_st_conform_intro" base_Generalization="_gen_intro"/> + <sysml:Conform xmi:id="_st_conform_reqs" base_Generalization="_gen_reqs"/> + <sysml:Conform xmi:id="_st_conform_safety" base_Generalization="_gen_safety"/> + <sysml:Conform xmi:id="_st_conform_figures" base_Generalization="_gen_figures"/> + <sysml:Conform xmi:id="_st_conform_trace" base_Generalization="_gen_trace"/> + <sysml:Conform xmi:id="_st_conform_odd" base_Generalization="_gen_odd"/> + <sysml:Conform xmi:id="_st_conform_broken" base_Generalization="_gen_broken"/> + <sysml:Conform xmi:id="_st_conform_severed" base_Generalization="_gen_severed"/> + <sysml:Conform xmi:id="_st_conform_headless" base_Generalization="_gen_headless"/> + <sysml:Conform xmi:id="_st_conform_notes" base_Generalization="_gen_notes"/> + <sysml:Conform xmi:id="_st_conform_fleet" base_Generalization="_gen_fleet"/> + <sysml:Conform xmi:id="_st_conform_gallery" base_Generalization="_gen_gallery"/> + <sysml:Conform xmi:id="_st_conform_selected" base_Generalization="_gen_selected"/> + <sysml:Conform xmi:id="_st_conform_others" base_Generalization="_gen_others"/> + <sysml:Conform xmi:id="_st_conform_owners" base_Generalization="_gen_owners"/> + <sysml:Conform xmi:id="_st_conform_nofig" base_Generalization="_gen_nofig"/> + + <sysml:Expose xmi:id="_st_expose_intro" base_Dependency="_expose_intro"/> + <sysml:Expose xmi:id="_st_expose_reqs" base_Dependency="_expose_reqs"/> + <sysml:Expose xmi:id="_st_expose_reqs_again" base_Dependency="_expose_reqs_again"/> + <sysml:Expose xmi:id="_st_expose_figures" base_Dependency="_expose_figures"/> + <sysml:Expose xmi:id="_st_expose_trace" base_Dependency="_expose_trace"/> + <sysml:Expose xmi:id="_st_expose_odd" base_Dependency="_expose_odd"/> + <sysml:Expose xmi:id="_st_expose_broken" base_Dependency="_expose_broken"/> + <sysml:Expose xmi:id="_st_expose_severed" base_Dependency="_expose_severed"/> + <sysml:Expose xmi:id="_st_expose_fleet" base_Dependency="_expose_fleet"/> + <sysml:Expose xmi:id="_st_expose_fleet_flow" base_Dependency="_expose_fleet_flow"/> + <sysml:Expose xmi:id="_st_expose_fleet_internals" base_Dependency="_expose_fleet_internals"/> + <sysml:Expose xmi:id="_st_expose_fleet_overview" base_Dependency="_expose_fleet_overview"/> + <sysml:Expose xmi:id="_st_expose_gallery" base_Dependency="_expose_gallery"/> + <sysml:Expose xmi:id="_st_expose_selected" base_Dependency="_expose_selected"/> + <sysml:Expose xmi:id="_st_expose_selected_flow" base_Dependency="_expose_selected_flow"/> + <sysml:Expose xmi:id="_st_expose_selected_internals" base_Dependency="_expose_selected_internals"/> + <sysml:Expose xmi:id="_st_expose_selected_overview" base_Dependency="_expose_selected_overview"/> + <sysml:Expose xmi:id="_st_expose_selected_again" base_Dependency="_expose_selected_again"/> + <sysml:Expose xmi:id="_st_expose_others" base_Dependency="_expose_others"/> + <sysml:Expose xmi:id="_st_expose_others_flow" base_Dependency="_expose_others_flow"/> + <sysml:Expose xmi:id="_st_expose_others_internals" base_Dependency="_expose_others_internals"/> + <sysml:Expose xmi:id="_st_expose_others_overview" base_Dependency="_expose_others_overview"/> + <sysml:Expose xmi:id="_st_expose_owners_internals" base_Dependency="_expose_owners_internals"/> + <sysml:Expose xmi:id="_st_expose_owners" base_Dependency="_expose_owners"/> + <sysml:Expose xmi:id="_st_expose_nofig" base_Dependency="_expose_nofig"/> + + <Document_View_Collaborator_Profile:Document xmi:id="_st_doc_handbook" base_Class="_doc_handbook"/> + <Document_Profile_:Document xmi:id="_st_doc_brief" base_Class="_doc_brief"/> + + <Document_Profile_:CollectOwnedElements xmi:id="_st_parts_collect" base_CallBehaviorAction="_parts_collect" depth="1"/> + <Document_Profile_:FilterByMetaclasses xmi:id="_st_parts_filter" base_CallBehaviorAction="_parts_filter" include="true"> + <metaclasses href="http://www.omg.org/spec/UML/20131001/UML.xmi#Class"/> + </Document_Profile_:FilterByMetaclasses> + <Document_Profile_:SortByName xmi:id="_st_parts_sort" base_CallBehaviorAction="_parts_sort"/> + <Document_Profile_:TableStructure xmi:id="_st_parts_table" base_StructuredActivityNode="_parts_table" includeDoc="true" loop="false" numberedRows="true"> + <titles>Fleet Parts</titles> + <captions>The parts of the fleet, by name.</captions> + </Document_Profile_:TableStructure> + <Document_Profile_:TableAttributeColumn xmi:id="_st_parts_col_name" base_CallBehaviorAction="_parts_col_name" desiredAttribute="Name"/> + <Document_Profile_:TablePropertyColumn xmi:id="_st_parts_col_payload" base_CallBehaviorAction="_parts_col_payload" desiredProperty="_prop_payload"> + <titles>Payload</titles> + </Document_Profile_:TablePropertyColumn> + <Document_Profile_:TableExpressionColumn xmi:id="_st_parts_col_qname" base_CallBehaviorAction="_parts_col_qname" expression="qualifiedName"/> + <Document_Profile_:TableExpressionColumn xmi:id="_st_parts_col_owner" base_CallBehaviorAction="_parts_col_owner" expression="owner.name"/> + <Document_Profile_:TablePropertyColumn xmi:id="_st_parts_col_alias" base_CallBehaviorAction="_parts_col_alias" desiredProperty="_prop_payload"> + <titles>name</titles> + </Document_Profile_:TablePropertyColumn> + + <Document_Profile_:CollectOwnedElements xmi:id="_st_reqs_collect" base_CallBehaviorAction="_reqs_collect" depth="0"/> + <Document_Profile_:FilterByStereotypes xmi:id="_st_reqs_filter" base_CallBehaviorAction="_reqs_filter" considerDerived="true" include="true"> + <stereotypes href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#SysML.Requirement"/> + </Document_Profile_:FilterByStereotypes> + <Document_Profile_:FilterByNames xmi:id="_st_reqs_names" base_CallBehaviorAction="_reqs_names" include="true"> + <names>Axle.*</names> + <names>Brake.*</names> + <names>Load.*</names> + </Document_Profile_:FilterByNames> + <Document_Profile_:Paragraph xmi:id="_st_reqs_intro" base_CallBehaviorAction="_reqs_intro" body="<p>Every <b>truck</b> of the fleet satisfies these requirements.</p>"/> + <Document_Profile_:BulletedList xmi:id="_st_reqs_list" base_CallBehaviorAction="_reqs_list" orderedList="true" showTargets="true" includeDoc="false"/> + <Document_Profile_:Paragraph xmi:id="_st_reqs_texts" base_CallBehaviorAction="_reqs_texts"/> + + <Document_Profile_:CollectOwnedElements xmi:id="_st_safety_collect" base_CallBehaviorAction="_safety_collect" depth="0"/> + <Document_Profile_:FilterByStereotypes xmi:id="_st_safety_filter" base_CallBehaviorAction="_safety_filter" stereotypes="_st_safety" considerDerived="false" include="true"/> + <Document_Profile_:FilterByNames xmi:id="_st_safety_names" base_CallBehaviorAction="_safety_names" include="false"> + <names>.*Count</names> + </Document_Profile_:FilterByNames> + <Document_Profile_:TableStructure xmi:id="_st_safety_table" base_StructuredActivityNode="_safety_table" includeDoc="false" showCaptions="false"> + <captions>Hidden: showCaptions is false.</captions> + </Document_Profile_:TableStructure> + <Document_Profile_:TableAttributeColumn xmi:id="_st_safety_col_name" base_CallBehaviorAction="_safety_col_name" desiredAttribute="Name"/> + <Document_Profile_:TableAttributeColumn xmi:id="_st_safety_col_doc" base_CallBehaviorAction="_safety_col_doc" desiredAttribute="Documentation"/> + <Document_Profile_:TableExpressionColumn xmi:id="_st_safety_col_ocl" base_CallBehaviorAction="_safety_col_ocl" expression="owner.oclAsType(NamedElement).name"/> + + <Document_Profile_:Image xmi:id="_st_figures_image" base_CallBehaviorAction="_figures_image" showCaptions="true"> + <captions>The truck and what it hauls</captions> + </Document_Profile_:Image> + <Document_Profile_:Image xmi:id="_st_gallery_image" base_CallBehaviorAction="_gallery_image" showCaptions="false" titlePrefix="Figure: "> + <titles>Inside the truck</titles> + <captions>Hidden: showCaptions is false.</captions> + </Document_Profile_:Image> + + <Document_Profile_:FilterByNames xmi:id="_st_selected_names" base_CallBehaviorAction="_selected_names" include="true"> + <names>Truck.*</names> + </Document_Profile_:FilterByNames> + <Document_Profile_:SortByName xmi:id="_st_selected_sort" base_CallBehaviorAction="_selected_sort"/> + <Document_Profile_:Image xmi:id="_st_selected_image" base_CallBehaviorAction="_selected_image"/> + <Document_Profile_:FilterByNames xmi:id="_st_others_names" base_CallBehaviorAction="_others_names" include="false"> + <names>Truck.*</names> + <names>Parts.*</names> + </Document_Profile_:FilterByNames> + <Document_Profile_:Image xmi:id="_st_others_image" base_CallBehaviorAction="_others_image"/> + <Document_Profile_:CollectOwners xmi:id="_st_owners_collect" base_CallBehaviorAction="_owners_collect" depth="1"/> + <Document_Profile_:SortByName xmi:id="_st_owners_sort" base_CallBehaviorAction="_owners_sort"/> + <Document_Profile_:BulletedList xmi:id="_st_owners_list" base_CallBehaviorAction="_owners_list" orderedList="false" showTargets="true" includeDoc="false"/> + <Document_Profile_:FilterByMetaclasses xmi:id="_st_nofig_packages" base_CallBehaviorAction="_nofig_packages" include="true"> + <metaclasses href="http://www.omg.org/spec/UML/20131001/UML.xmi#Package"/> + </Document_Profile_:FilterByMetaclasses> + <Document_Profile_:Image xmi:id="_st_nofig_image" base_CallBehaviorAction="_nofig_image"/> + + <Document_Profile_:CollectByDirectedRelationshipStereotypes xmi:id="_st_trace_satisfiers" base_CallBehaviorAction="_trace_satisfiers" depth="1" directionOut="false"> + <stereotypes href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#SysML.Satisfy"/> + </Document_Profile_:CollectByDirectedRelationshipStereotypes> + <Document_Profile_:CollectOwnedElements xmi:id="_st_trace_owned" base_CallBehaviorAction="_trace_owned" depth="1"/> + <Document_Profile_:BulletedList xmi:id="_st_trace_list" base_CallBehaviorAction="_trace_list" orderedList="false" showTargets="true" includeDoc="true" sortElementsByName="true"/> + + <Document_Profile_:Paragraph xmi:id="_st_odd_ocl" base_CallBehaviorAction="_odd_ocl" body="self.name" evaluateOcl="true"/> + <Document_Profile_:Image xmi:id="_st_odd_image" base_CallBehaviorAction="_odd_image"/> + <Document_Profile_:CollectOwnedElements xmi:id="_st_odd_depth" base_CallBehaviorAction="_odd_depth" depth="many"/> + <Document_Profile_:CollectTypes xmi:id="_st_odd_types" base_CallBehaviorAction="_odd_types"/> + <Document_Profile_:TableStructure xmi:id="_st_odd_table" base_StructuredActivityNode="_odd_table"/> + <Document_Profile_:TableAttributeColumn xmi:id="_st_odd_col_name" base_CallBehaviorAction="_odd_col_name" desiredAttribute="Name"/> + <Document_Profile_:Dynamic_View xmi:id="_st_per_truck" base_Activity="_act_per_truck" loop="true" title="Per Truck"/> + <Document_Profile_:Paragraph xmi:id="_st_per_para" base_CallBehaviorAction="_per_para" body="One truck."/> + + <Document_Profile_:Paragraph xmi:id="_st_broken_para" base_CallBehaviorAction="_broken_para" body="Never reached."/> + <Document_Profile_:Paragraph xmi:id="_st_severed_para" base_CallBehaviorAction="_severed_para" body="Cut off here."/> + + <MagicDraw_Profile:AttachedFile xmi:id="_st_attached_image" base_Comment="_note_image" file="fleet.png"/> + <MagicDraw_Profile:AttachedFile xmi:id="_st_attached_blank" base_Comment="_note_blank_image" file="depot.png"/> + <Document_View_Collaborator_Profile:CollaboratorParagraph xmi:id="_st_note_second" base_Element="_note_second" property="META:QPROP:Element:body" documentId="_doc_handbook" viewId="_doc_handbook" ownerId="_view_notes" siblingId="_note_first"/> + <Document_View_Collaborator_Profile:CollaboratorParagraph xmi:id="_st_note_first" base_Element="_note_first" property="META:QPROP:Element:body" documentId="_doc_handbook" viewId="_doc_handbook" ownerId="_view_notes"/> + <Document_View_Collaborator_Profile:CollaboratorImageParagraph xmi:id="_st_note_image" base_Element="_note_image" property="META:QPROP:Element:body" documentId="_doc_handbook" viewId="_doc_handbook" ownerId="_view_notes"/> + <Document_View_Collaborator_Profile:CollaboratorImageParagraph xmi:id="_st_note_blank_image" base_Element="_note_blank_image" property="META:QPROP:Element:body" documentId="_doc_handbook" viewId="_doc_handbook" ownerId="_view_notes"/> + <Document_View_Collaborator_Profile:CollaboratorParagraph xmi:id="_st_note_named" base_Element="_note_named" property="META:QPROP:Element:name" documentId="_doc_handbook" viewId="_doc_handbook" ownerId="_view_notes"/> + <Document_View_Collaborator_Profile:CollaboratorParagraph xmi:id="_st_note_empty" base_Element="_note_empty" property="META:QPROP:Element:body" documentId="_doc_handbook" viewId="_doc_handbook" ownerId="_view_notes"/> + <Document_View_Collaborator_Profile:CollaboratorParagraph xmi:id="_st_note_stray" base_Element="_note_first" property="META:QPROP:Element:body" documentId="_doc_handbook" viewId="_doc_handbook" ownerId="_view_gone"/> + <Document_View_Collaborator_Profile:CollaboratorParagraph xmi:id="_st_note_other_doc" base_Element="_note_first" property="META:QPROP:Element:body" documentId="_doc_gone" viewId="_doc_gone" ownerId="_view_notes"/> +</xmi:XMI> diff --git a/tests/migrate/testdata/xmi/metaclass_tables.golden.report.txt b/tests/migrate/testdata/xmi/metaclass_tables.golden.report.txt new file mode 100644 index 0000000000..e1c602ffff --- /dev/null +++ b/tests/migrate/testdata/xmi/metaclass_tables.golden.report.txt @@ -0,0 +1,44 @@ +# SysML v1 to v2 migration report: metaclass_tables.xmi +# exported by Example UML Tool +# migrated 37 element(s): 32 mapped, 5 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) + +## approximated (5) +«DiagramTable» Diagram Tables::Classifiers _tbl_classifiers -> part def Tables::'Classifiers Document' (the «DiagramTable» is written as a Document holding a Table over the query 'Classifiers Rows'; the views diagrams became and the action defs operations became are listed too) +«DiagramTable» Diagram Tables::Diagrams _tbl_diagrams -> part def Tables::'Diagrams Document' (the «DiagramTable» is written as a Document holding a Table over the query 'Diagrams Rows'; views a «View» class became are listed with the diagrams' views) +«DiagramTable» Diagram Tables::Namespaces _tbl_namespaces -> part def Tables::'Namespaces Document' (the «DiagramTable» is written as a Document holding a Table over the query 'Namespaces Rows'; the views diagrams became are listed too; transitions and structured activity nodes are not) +«DiagramTable» Diagram Tables::Packageable Elements _tbl_packageable -> part def Tables::'Packageable Elements Document' (the «DiagramTable» is written as a Document holding a Table over the query 'Packageable Elements Rows'; the views diagrams became and the action defs operations became are listed too; instances of value types, written as attributes, are not) +«DiagramTable» Diagram Tables::Types _tbl_types -> part def Tables::'Types Document' (the «DiagramTable» is written as a Document holding a Table over the query 'Types Rows'; the views diagrams became and the action defs operations became are listed too) + +## mapped (32) +Model Model _m (the root model's members are written at the top level) +Diagram Model Overview _diag_model -> 'Model Overview' (a SysML Package Diagram written as a view rendered asTreeDiagram) +Package Plant _pkg_plant -> Plant +Package Plant::Structure _pkg_structure -> Plant::Structure +Enumeration Plant::Structure::Mode _enum_mode -> Plant::Structure::Mode +EnumerationLiteral Plant::Structure::Mode::off _lit_off -> Plant::Structure::Mode::off +EnumerationLiteral Plant::Structure::Mode::on _lit_on -> Plant::Structure::Mode::on +«Block» Class Plant::Structure::Pump _blk_pump -> Plant::Structure::Pump +Comment Plant::Structure::Pump::<Comment> _cmt_pump +StateMachine Plant::Structure::Pump::Cycle _sm_cycle -> Plant::Structure::Pump::Cycle +Region Plant::Structure::Pump::Cycle::main _rg_main (the one region is written as the body of its owner) +Pseudostate Plant::Structure::Pump::Cycle::main::<Pseudostate> _ps_init (written as the entry of the region) +Transition Plant::Structure::Pump::Cycle::main::<Transition> _tr_init +Transition Plant::Structure::Pump::Cycle::main::<Transition> _tr_start +State Plant::Structure::Pump::Cycle::main::Idle _st_idle -> Idle +State Plant::Structure::Pump::Cycle::main::Running _st_running -> Running +Property Plant::Structure::Pump::mass _prop_mass -> Plant::Structure::Pump::mass +Operation Plant::Structure::Pump::prime _op_prime -> Plant::Structure::Pump::prime (its owner's usage prime 2 performs it, as a call on an object does) +Property Plant::Structure::Pump::valve _prop_valve -> Plant::Structure::Pump::valve +«Block» Class Plant::Structure::Valve _blk_valve -> Plant::Structure::Valve +Dependency Plant::Structure::needs _dep_pump_valve -> Plant::Structure::needs +InstanceSpecification Plant::Structure::p1 _inst_p1 -> Plant::Structure::p1 +Slot Plant::Structure::p1::<Slot> _slot_p1_mass -> Plant::Structure::p1::mass +Package Plant::Views _pkg_views -> Plant::Views +«Viewpoint» Class Plant::Views::Operations _vp_operations -> Plant::Views::Operations +«View» Class Plant::Views::Overview _view_overview -> Plant::Views::Overview +Package Tables _pkg_tables -> Tables +Diagram Tables::Classifiers _diag_classifiers -> Tables::Classifiers (a Generic Table written as a view rendered asElementTable) +Diagram Tables::Diagrams _diag_diagrams -> Tables::Diagrams (a Generic Table written as a view rendered asElementTable) +Diagram Tables::Namespaces _diag_namespaces -> Tables::Namespaces (a Generic Table written as a view rendered asElementTable) +Diagram Tables::Packageable Elements _diag_packageable -> Tables::'Packageable Elements' (a Generic Table written as a view rendered asElementTable) +Diagram Tables::Types _diag_types -> Tables::Types (a Generic Table written as a view rendered asElementTable) diff --git a/tests/migrate/testdata/xmi/metaclass_tables.golden.sysml b/tests/migrate/testdata/xmi/metaclass_tables.golden.sysml new file mode 100644 index 0000000000..b329705d0a --- /dev/null +++ b/tests/migrate/testdata/xmi/metaclass_tables.golden.sysml @@ -0,0 +1,164 @@ +package Plant { + package Structure { + part def Pump { + doc /* Moves fluid through the plant. */ + attribute mass : ScalarValues::Real; + part valve : Valve; + abstract action def prime; + action 'prime 2' : prime; + state def Cycle { + entry; then Idle; + state Idle; + state Running; + transition first Idle then Running; + } + } + part def Valve; + enum def Mode { + on; + off; + } + individual part def p1 :> Pump { + attribute :>> mass = 12.5; + } + dependency needs from Pump to Valve; + } + package Views { + view Overview; + viewpoint Operations; + } +} +package Tables { + view 'Packageable Elements' { + expose Plant::Structure::Pump; + expose 'Packageable Elements Document'; + render Views::asElementTable; + } + calc def 'Packageable Elements Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::OrderBy( + source = DocumentQueries::WhereType( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant"))), + type = ("Package", "Definition", "ViewUsage", "ViewpointUsage", "Dependency", "SatisfyRequirementUsage", "AllocationUsage")), + property = "qualifiedName", + direction = "ascending", + missing = "last", + multiple = "first"), + properties = ("qualifiedName")) + } + part def 'Packageable Elements Document' :> DocumentQueries::Document { + attribute redefines title = "Packageable Elements"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Packageable Elements"; + calc rows : 'Packageable Elements Rows'; + } + } + view Namespaces { + expose Plant::Structure::Pump; + expose 'Namespaces Document'; + render Views::asElementTable; + } + calc def 'Namespaces Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::OrderBy( + source = DocumentQueries::WhereType( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant"))), + type = ("Package", "Definition", "ViewUsage", "ViewpointUsage", "StateUsage")), + property = "qualifiedName", + direction = "ascending", + missing = "last", + multiple = "first"), + properties = ("qualifiedName")) + } + part def 'Namespaces Document' :> DocumentQueries::Document { + attribute redefines title = "Namespaces"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Namespaces"; + calc rows : 'Namespaces Rows'; + } + } + view Types { + expose Plant::Structure::Pump; + expose 'Types Document'; + render Views::asElementTable; + } + calc def 'Types Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::OrderBy( + source = DocumentQueries::WhereType( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant"))), + type = ("Definition", "ViewUsage", "ViewpointUsage")), + property = "qualifiedName", + direction = "ascending", + missing = "last", + multiple = "first"), + properties = ("qualifiedName")) + } + part def 'Types Document' :> DocumentQueries::Document { + attribute redefines title = "Types"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Types"; + calc rows : 'Types Rows'; + } + } + view Classifiers { + expose Plant::Structure::Pump; + expose 'Classifiers Document'; + render Views::asElementTable; + } + calc def 'Classifiers Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::OrderBy( + source = DocumentQueries::WhereType( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant"))), + type = ("Definition", "ViewUsage", "ViewpointUsage")), + property = "qualifiedName", + direction = "ascending", + missing = "last", + multiple = "first"), + properties = ("qualifiedName")) + } + part def 'Classifiers Document' :> DocumentQueries::Document { + attribute redefines title = "Classifiers"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Classifiers"; + calc rows : 'Classifiers Rows'; + } + } + view Diagrams { + expose Plant::Structure::Pump; + expose 'Diagrams Document'; + render Views::asElementTable; + } + calc def 'Diagrams Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::OrderBy( + source = DocumentQueries::WhereType( + source = DocumentQueries::Union( + source = DocumentQueries::Named(qualifiedName = ("Plant", "Tables", "Model Overview")), + other = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant", "Tables", "Model Overview")))), + type = ("ViewUsage")), + property = "qualifiedName", + direction = "ascending", + missing = "last", + multiple = "first"), + properties = ("qualifiedName")) + } + part def 'Diagrams Document' :> DocumentQueries::Document { + attribute redefines title = "Diagrams"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Diagrams"; + calc rows : 'Diagrams Rows'; + } + } +} +view 'Model Overview' { + expose Plant; + expose Tables; + render Views::asTreeDiagram; +} diff --git a/tests/migrate/testdata/xmi/metaclass_tables.xmi b/tests/migrate/testdata/xmi/metaclass_tables.xmi new file mode 100644 index 0000000000..f28f0b779e --- /dev/null +++ b/tests/migrate/testdata/xmi/metaclass_tables.xmi @@ -0,0 +1,161 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xmi:XMI xmi:version="2.5.1" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" + xmlns:uml="http://www.omg.org/spec/UML/20161101" + xmlns:sysml="http://www.omg.org/spec/SysML/20181001/SysML" + xmlns:MagicDraw_Profile="http://www.omg.org/spec/UML/20131001/MagicDrawProfile" + xmlns:diagram="http://www.example.com/tool/diagram"> + <xmi:Documentation exporter="Example UML Tool" exporterVersion="1.0"/> + <uml:Model xmi:type="uml:Model" xmi:id="_m" name="Model"> + + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_plant" name="Plant"> + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_structure" name="Structure"> + <packagedElement xmi:type="uml:Class" xmi:id="_blk_pump" name="Pump"> + <ownedComment xmi:type="uml:Comment" xmi:id="_cmt_pump" body="Moves fluid through the plant."/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_prop_mass" name="mass"> + <type xmi:type="uml:PrimitiveType" href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#Real"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_prop_valve" name="valve" type="_blk_valve" aggregation="composite"/> + <ownedOperation xmi:type="uml:Operation" xmi:id="_op_prime" name="prime"/> + <ownedBehavior xmi:type="uml:StateMachine" xmi:id="_sm_cycle" name="Cycle"> + <region xmi:type="uml:Region" xmi:id="_rg_main" name="main"> + <subvertex xmi:type="uml:Pseudostate" xmi:id="_ps_init"/> + <subvertex xmi:type="uml:State" xmi:id="_st_idle" name="Idle"/> + <subvertex xmi:type="uml:State" xmi:id="_st_running" name="Running"/> + <transition xmi:type="uml:Transition" xmi:id="_tr_init" source="_ps_init" target="_st_idle"/> + <transition xmi:type="uml:Transition" xmi:id="_tr_start" source="_st_idle" target="_st_running"/> + </region> + </ownedBehavior> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_blk_valve" name="Valve"/> + <packagedElement xmi:type="uml:Enumeration" xmi:id="_enum_mode" name="Mode"> + <ownedLiteral xmi:type="uml:EnumerationLiteral" xmi:id="_lit_on" name="on"/> + <ownedLiteral xmi:type="uml:EnumerationLiteral" xmi:id="_lit_off" name="off"/> + </packagedElement> + <packagedElement xmi:type="uml:InstanceSpecification" xmi:id="_inst_p1" name="p1" classifier="_blk_pump"> + <slot xmi:type="uml:Slot" xmi:id="_slot_p1_mass" definingFeature="_prop_mass"> + <value xmi:type="uml:LiteralReal" xmi:id="_slot_p1_mass_v" value="12.5"/> + </slot> + </packagedElement> + <packagedElement xmi:type="uml:Dependency" xmi:id="_dep_pump_valve" name="needs" client="_blk_pump" supplier="_blk_valve"/> + </packagedElement> + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_views" name="Views"> + <packagedElement xmi:type="uml:Class" xmi:id="_view_overview" name="Overview"/> + <packagedElement xmi:type="uml:Class" xmi:id="_vp_operations" name="Operations"/> + </packagedElement> + </packagedElement> + + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_tables" name="Tables"/> + + <xmi:Extension extender="Example UML Tool 1.0"> + <modelExtension> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_packageable" name="Packageable Elements" ownerOfDiagram="_pkg_tables"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Generic Table" umlType="Class Diagram"> + <diagramContents> + <usedElements>_blk_pump</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_namespaces" name="Namespaces" ownerOfDiagram="_pkg_tables"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Generic Table" umlType="Class Diagram"> + <diagramContents> + <usedElements>_blk_pump</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_types" name="Types" ownerOfDiagram="_pkg_tables"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Generic Table" umlType="Class Diagram"> + <diagramContents> + <usedElements>_blk_pump</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_classifiers" name="Classifiers" ownerOfDiagram="_pkg_tables"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Generic Table" umlType="Class Diagram"> + <diagramContents> + <usedElements>_blk_pump</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_diagrams" name="Diagrams" ownerOfDiagram="_pkg_tables"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Generic Table" umlType="Class Diagram"> + <diagramContents> + <usedElements>_blk_pump</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_model" name="Model Overview" ownerOfDiagram="_m"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="SysML Package Diagram" umlType="Package Diagram"> + <diagramContents> + <usedElements>_pkg_plant</usedElements> + <usedElements>_pkg_tables</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + </modelExtension> + </xmi:Extension> + </uml:Model> + + <sysml:Block xmi:id="_st_blk_pump" base_Class="_blk_pump"/> + <sysml:Block xmi:id="_st_blk_valve" base_Class="_blk_valve"/> + <sysml:View xmi:id="_st_view_overview" base_Class="_view_overview"/> + <sysml:Viewpoint xmi:id="_st_vp_operations" base_Class="_vp_operations"/> + + <MagicDraw_Profile:DiagramTable xmi:id="_tbl_packageable" base_Diagram="_diag_packageable" scope="_pkg_plant" includeSubtypesOfRowTypes="true"> + <rowElementType href="http://www.omg.org/spec/UML/20131001/UML.xmi#PackageableElement"/> + <columnIds>_NUMBER_</columnIds> + <columnIds>QPROP:Element:qualifiedName</columnIds> + <sort>QPROP:Element:qualifiedName^Asc</sort> + </MagicDraw_Profile:DiagramTable> + + <MagicDraw_Profile:DiagramTable xmi:id="_tbl_namespaces" base_Diagram="_diag_namespaces" scope="_pkg_plant" includeSubtypesOfRowTypes="true"> + <rowElementType href="http://www.omg.org/spec/UML/20131001/UML.xmi#Namespace"/> + <columnIds>_NUMBER_</columnIds> + <columnIds>QPROP:Element:qualifiedName</columnIds> + <sort>QPROP:Element:qualifiedName^Asc</sort> + </MagicDraw_Profile:DiagramTable> + + <MagicDraw_Profile:DiagramTable xmi:id="_tbl_types" base_Diagram="_diag_types" scope="_pkg_plant" includeSubtypesOfRowTypes="true"> + <rowElementType href="http://www.omg.org/spec/UML/20131001/UML.xmi#Type"/> + <columnIds>_NUMBER_</columnIds> + <columnIds>QPROP:Element:qualifiedName</columnIds> + <sort>QPROP:Element:qualifiedName^Asc</sort> + </MagicDraw_Profile:DiagramTable> + + <MagicDraw_Profile:DiagramTable xmi:id="_tbl_classifiers" base_Diagram="_diag_classifiers" scope="_pkg_plant" includeSubtypesOfRowTypes="true"> + <rowElementType href="http://www.omg.org/spec/UML/20131001/UML.xmi#Classifier"/> + <columnIds>_NUMBER_</columnIds> + <columnIds>QPROP:Element:qualifiedName</columnIds> + <sort>QPROP:Element:qualifiedName^Asc</sort> + </MagicDraw_Profile:DiagramTable> + + <MagicDraw_Profile:DiagramTable xmi:id="_tbl_diagrams" base_Diagram="_diag_diagrams" takeWholeModelAsScope="true" includeSubtypesOfRowTypes="true"> + <rowElementType href="http://www.omg.org/spec/UML/20131001/UML.xmi#Diagram"/> + <columnIds>_NUMBER_</columnIds> + <columnIds>QPROP:Element:qualifiedName</columnIds> + <sort>QPROP:Element:qualifiedName^Asc</sort> + </MagicDraw_Profile:DiagramTable> +</xmi:XMI> diff --git a/tests/migrate/testdata/xmi/relation_subtypes.golden.report.txt b/tests/migrate/testdata/xmi/relation_subtypes.golden.report.txt new file mode 100644 index 0000000000..d689b488e6 --- /dev/null +++ b/tests/migrate/testdata/xmi/relation_subtypes.golden.report.txt @@ -0,0 +1,28 @@ +# SysML v1 to v2 migration report: relation_subtypes.xmi +# exported by Example UML Tool +# migrated 21 element(s): 19 mapped, 2 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) + +## approximated (2) +«DependencyMatrix» Diagram Plant::Requirements::Exact Satisfaction Matrix _mx_exact -> part def Plant::Requirements::'Exact Satisfaction Matrix Document' (the «DependencyMatrix» is written as a Document holding a Table over the query 'Exact Satisfaction Matrix Rows'; the criterion Satisfied by excludes subtypes of «Satisfy», but the «Fulfil» relationships are walked too: they are written as the same relationship) +«RelationMap» Diagram Plant::Structure::Valve Requirement Map _map_valve -> part def Plant::Structure::'Valve Requirement Map Document' (the «RelationMap» is written as a Document holding a Table over the query 'Valve Requirement Map Rows'; the criterion Satisfy excludes subtypes of «Satisfy», but the «Fulfil» relationships are walked too: they are written as the same relationship) + +## mapped (19) +Model Model _m (the root model's members are written at the top level) +Package Plant _pkg_plant -> Plant +Package Plant::Requirements _pkg_reqs -> Plant::Requirements +«Satisfy» Abstraction Plant::Requirements::<Abstraction> _dep_satisfy -> Plant::Structure::Pump +«Fulfil» Abstraction Plant::Requirements::<Abstraction> _dep_fulfil -> Plant::Structure::Valve +«DeriveReqt» Abstraction Plant::Requirements::<Abstraction> _dep_derive -> Plant::Requirements::'Derive SealRequirement' +Diagram Plant::Requirements::Exact Satisfaction Matrix _diag_exact -> Plant::Requirements::'Exact Satisfaction Matrix' (a Dependency Matrix written as a view rendered asElementTable) +«Requirement» Class Plant::Requirements::FlowRequirement _req_flow -> Plant::Requirements::FlowRequirement +«RelationMap» Diagram Plant::Requirements::Seal Derivation Map _map_derived -> part def Plant::Requirements::'Seal Derivation Map Document' (the «RelationMap» is written as a Document holding a Table over the query 'Seal Derivation Map Rows') +Diagram Plant::Requirements::Seal Derivation Map _diag_derived -> Plant::Requirements::'Seal Derivation Map' (a Relation Map written as a view rendered asTreeDiagram) +«Requirement» Class Plant::Requirements::SealRequirement _req_seal -> Plant::Requirements::SealRequirement +«DependencyMatrix» Diagram Plant::Requirements::Wide Satisfaction Matrix _mx_wide -> part def Plant::Requirements::'Wide Satisfaction Matrix Document' (the «DependencyMatrix» is written as a Document holding a Table over the query 'Wide Satisfaction Matrix Rows') +Diagram Plant::Requirements::Wide Satisfaction Matrix _diag_wide -> Plant::Requirements::'Wide Satisfaction Matrix' (a Dependency Matrix written as a view rendered asElementTable) +Package Plant::Structure _pkg_structure -> Plant::Structure +«Block» Class Plant::Structure::Pump _blk_pump -> Plant::Structure::Pump +«Block» Class Plant::Structure::Valve _blk_valve -> Plant::Structure::Valve +Diagram Plant::Structure::Valve Requirement Map _diag_map -> Plant::Structure::'Valve Requirement Map' (a Relation Map written as a view rendered asTreeDiagram) +Profile Traceability _traceability -> Traceability +Stereotype Traceability::Fulfil _st_fulfil -> Traceability::Fulfil diff --git a/tests/migrate/testdata/xmi/relation_subtypes.golden.sysml b/tests/migrate/testdata/xmi/relation_subtypes.golden.sysml new file mode 100644 index 0000000000..dae154c18c --- /dev/null +++ b/tests/migrate/testdata/xmi/relation_subtypes.golden.sysml @@ -0,0 +1,149 @@ +package Traceability { + metadata def Fulfil; +} +package Plant { + package Structure { + part def Pump { + satisfy requirement : Plant::Requirements::FlowRequirement; + } + part def Valve { + satisfy requirement : Plant::Requirements::SealRequirement { + @Traceability::Fulfil; + } + } + view 'Valve Requirement Map' { + expose Valve; + expose 'Valve Requirement Map Document'; + render Views::asTreeDiagram; + } + calc def 'Valve Requirement Map Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::WhereType( + source = DocumentQueries::RelatedElements( + source = DocumentQueries::Named(qualifiedName = ("Plant::Structure::Valve")), + relationshipKind = "satisfaction", + direction = "outgoing", + maxDepth = 1), + type = ("PartDefinition", "RequirementDefinition", "ConstraintDefinition", "PortDefinition", "VerificationCaseDefinition", "ActionDefinition", "StateDefinition", "CalculationDefinition", "ViewUsage", "ViewpointUsage")), + properties = ("qualifiedName", "@type")) + } + part def 'Valve Requirement Map Document' :> DocumentQueries::Document { + attribute redefines title = "Valve Requirement Map"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Valve Requirement Map"; + calc rows : 'Valve Requirement Map Rows'; + } + } + } + package Requirements { + requirement def <'R-1'> FlowRequirement { + doc /* The pump keeps the flow above the minimum. */ + } + requirement def <'R-2'> SealRequirement { + doc /* Every valve seals when closed. */ + } + connection def 'Derive SealRequirement' :> RequirementDerivation::Derivation { + end #RequirementDerivation::original originalRequirement : FlowRequirement; + end #RequirementDerivation::derive derivedRequirement : SealRequirement; + } + view 'Exact Satisfaction Matrix' { + expose FlowRequirement; + expose Plant::Structure::Pump; + expose 'Exact Satisfaction Matrix Document'; + render Views::asElementTable; + } + calc def 'Exact Satisfaction Matrix Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::WhereType( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant::Requirements"))), + type = ("RequirementDefinition")), + properties = ("name"), + columns = ( + DocumentQueries::RelatedColumn( + name = "Satisfied by", + relationshipKind = "satisfaction", + direction = "incoming", + maxDepth = 1, + aggregate = "list", + targets = DocumentQueries::WhereType( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant::Structure"))), + type = ("PartDefinition"))))) + } + part def 'Exact Satisfaction Matrix Document' :> DocumentQueries::Document { + attribute redefines title = "Exact Satisfaction Matrix"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Exact Satisfaction Matrix"; + calc rows : 'Exact Satisfaction Matrix Rows'; + } + } + view 'Wide Satisfaction Matrix' { + expose FlowRequirement; + expose SealRequirement; + expose Plant::Structure::Pump; + expose Plant::Structure::Valve; + expose 'Wide Satisfaction Matrix Document'; + render Views::asElementTable; + } + calc def 'Wide Satisfaction Matrix Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::WhereType( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant::Requirements"))), + type = ("RequirementDefinition")), + properties = ("name"), + columns = ( + DocumentQueries::RelatedColumn( + name = "Satisfied by", + relationshipKind = "satisfaction", + direction = "incoming", + maxDepth = 1, + aggregate = "list", + targets = DocumentQueries::WhereType( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant"))), + type = ("PartDefinition", "RequirementDefinition"))), + DocumentQueries::RelatedColumn( + name = "Derived by", + relationshipKind = "derivation", + direction = "outgoing", + maxDepth = 1, + aggregate = "list", + targets = DocumentQueries::WhereType( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant"))), + type = ("PartDefinition", "RequirementDefinition"))))) + } + part def 'Wide Satisfaction Matrix Document' :> DocumentQueries::Document { + attribute redefines title = "Wide Satisfaction Matrix"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Wide Satisfaction Matrix"; + calc rows : 'Wide Satisfaction Matrix Rows'; + } + } + view 'Seal Derivation Map' { + expose SealRequirement; + expose 'Seal Derivation Map Document'; + render Views::asTreeDiagram; + } + calc def 'Seal Derivation Map Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::WhereType( + source = DocumentQueries::RelatedElements( + source = DocumentQueries::Named(qualifiedName = ("Plant::Requirements::SealRequirement")), + relationshipKind = "derivation", + direction = "incoming", + maxDepth = 1), + type = ("PartDefinition", "RequirementDefinition", "ConstraintDefinition", "PortDefinition", "VerificationCaseDefinition", "ActionDefinition", "StateDefinition", "CalculationDefinition", "ViewUsage", "ViewpointUsage")), + properties = ("qualifiedName", "@type")) + } + part def 'Seal Derivation Map Document' :> DocumentQueries::Document { + attribute redefines title = "Seal Derivation Map"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Seal Derivation Map"; + calc rows : 'Seal Derivation Map Rows'; + } + } + } +} diff --git a/tests/migrate/testdata/xmi/relation_subtypes.xmi b/tests/migrate/testdata/xmi/relation_subtypes.xmi new file mode 100644 index 0000000000..316b76032a --- /dev/null +++ b/tests/migrate/testdata/xmi/relation_subtypes.xmi @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xmi:XMI xmi:version="2.5.1" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" + xmlns:uml="http://www.omg.org/spec/UML/20161101" + xmlns:sysml="http://www.omg.org/spec/SysML/20181001/SysML" + xmlns:MagicDraw_Profile="http://www.omg.org/spec/UML/20131001/MagicDrawProfile" + xmlns:Dependency_Matrix_Profile="http://www.magicdraw.com/schemas/Dependency_Matrix_Profile.xmi" + xmlns:Traceability="http://example.com/schemas/Traceability.xmi" + xmlns:diagram="http://www.example.com/tool/diagram"> + <xmi:Documentation exporter="Example UML Tool" exporterVersion="1.0"/> + <uml:Model xmi:type="uml:Model" xmi:id="_m" name="Model"> + + <!-- «Fulfil» specializes SysML's «Satisfy», so a Fulfil abstraction is a satisfy. --> + <packagedElement xmi:type="uml:Profile" xmi:id="_traceability" name="Traceability" URI="http://example.com/schemas/Traceability.xmi"> + <packagedElement xmi:type="uml:Stereotype" xmi:id="_st_fulfil" name="Fulfil"> + <generalization xmi:type="uml:Generalization" xmi:id="_st_fulfil_gen"> + <general xmi:type="uml:Stereotype" href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#Satisfy"/> + </generalization> + </packagedElement> + </packagedElement> + + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_plant" name="Plant"> + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_structure" name="Structure"> + <packagedElement xmi:type="uml:Class" xmi:id="_blk_pump" name="Pump"/> + <packagedElement xmi:type="uml:Class" xmi:id="_blk_valve" name="Valve"/> + </packagedElement> + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_reqs" name="Requirements"> + <packagedElement xmi:type="uml:Class" xmi:id="_req_flow" name="FlowRequirement"/> + <packagedElement xmi:type="uml:Class" xmi:id="_req_seal" name="SealRequirement"/> + <packagedElement xmi:type="uml:Abstraction" xmi:id="_dep_satisfy" client="_blk_pump" supplier="_req_flow"/> + <packagedElement xmi:type="uml:Abstraction" xmi:id="_dep_fulfil" client="_blk_valve" supplier="_req_seal"/> + <packagedElement xmi:type="uml:Abstraction" xmi:id="_dep_derive" client="_req_seal" supplier="_req_flow"/> + </packagedElement> + </packagedElement> + + <xmi:Extension extender="Example UML Tool 1.0"> + <modelExtension> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_exact" name="Exact Satisfaction Matrix" ownerOfDiagram="_pkg_reqs"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Dependency Matrix" umlType="Class Diagram"> + <diagramContents> + <usedElements>_req_flow</usedElements> + <usedElements>_blk_pump</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_wide" name="Wide Satisfaction Matrix" ownerOfDiagram="_pkg_reqs"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Dependency Matrix" umlType="Class Diagram"> + <diagramContents> + <usedElements>_req_flow</usedElements> + <usedElements>_req_seal</usedElements> + <usedElements>_blk_pump</usedElements> + <usedElements>_blk_valve</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_derived" name="Seal Derivation Map" ownerOfDiagram="_pkg_reqs"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Relation Map" umlType="Class Diagram"> + <diagramContents> + <usedElements>_req_seal</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_map" name="Valve Requirement Map" ownerOfDiagram="_pkg_structure"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Relation Map" umlType="Class Diagram"> + <diagramContents> + <usedElements>_blk_valve</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + </modelExtension> + <stereotypesHREFS> + <stereotype name="sysml:Satisfy" stereotypeHREF="SysML Profile.mdzip#_st_satisfy"/> + <stereotype name="sysml:DeriveReqt" stereotypeHREF="SysML Profile.mdzip#_st_derive"/> + </stereotypesHREFS> + </xmi:Extension> + </uml:Model> + + <sysml:Block xmi:id="_st_blk_pump" base_Class="_blk_pump"/> + <sysml:Block xmi:id="_st_blk_valve" base_Class="_blk_valve"/> + <sysml:Requirement xmi:id="_st_req_flow" base_Class="_req_flow" Id="R-1" Text="The pump keeps the flow above the minimum."/> + <sysml:Requirement xmi:id="_st_req_seal" base_Class="_req_seal" Id="R-2" Text="Every valve seals when closed."/> + <sysml:Satisfy xmi:id="_st_satisfy_pump" base_Abstraction="_dep_satisfy"/> + <Traceability:Fulfil xmi:id="_st_fulfil_valve" base_Abstraction="_dep_fulfil"/> + <sysml:DeriveReqt xmi:id="_st_derive_seal" base_Abstraction="_dep_derive"/> + + <!-- Satisfy without subtypes: the «Fulfil» satisfy is walked too, which the report says. --> + <Dependency_Matrix_Profile:DependencyMatrix xmi:id="_mx_exact" base_Diagram="_diag_exact" direction="Column to row" showElements="All" takeWholeModelAsScope="false"> + <dependencyCriteria><?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<callExpressionSpecification includeCustomTypes="false" includeSubtypes="false" xmlns="http://www.nomagic.com/schemas/MagicDraw/StructuredExpression/2013"> + <taggedValues> + <entry key="name"> + <value>Satisfied by</value> + </entry> + </taggedValues> + <argument xsi:type="lookupExpressionSpecification" symbol="THIS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> + <expression xsi:type="dslRelationExpressionSpecification" stereotype="_st_satisfy" direction="REVERSED" includeCustomTypes="false" includeSubtypes="false" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> +</callExpressionSpecification> +</dependencyCriteria> + </Dependency_Matrix_Profile:DependencyMatrix> + <Dependency_Matrix_Profile:MatrixFilter xmi:id="_mf_exact" base_Diagram="_diag_exact" rowScope="_pkg_reqs" columnScope="_pkg_structure" rowScopeDefined="true" columnScopeDefined="true" includeSubtypesOfRowTypes="true" includeSubtypesOfColumnTypes="true"> + <rowElementType href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#SysML.Requirement"/> + <columnElementType href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#SysML.Block"/> + </Dependency_Matrix_Profile:MatrixFilter> + + <!-- Satisfy with subtypes is exact; so is DeriveReqt without them, since nothing specializing it is applied. --> + <Dependency_Matrix_Profile:DependencyMatrix xmi:id="_mx_wide" base_Diagram="_diag_wide" direction="Column to row" showElements="All" takeWholeModelAsScope="false"> + <dependencyCriteria><?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<callExpressionSpecification includeCustomTypes="false" includeSubtypes="true" xmlns="http://www.nomagic.com/schemas/MagicDraw/StructuredExpression/2013"> + <taggedValues> + <entry key="name"> + <value>Satisfied by</value> + </entry> + </taggedValues> + <argument xsi:type="lookupExpressionSpecification" symbol="THIS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> + <expression xsi:type="dslRelationExpressionSpecification" stereotype="_st_satisfy" direction="REVERSED" includeCustomTypes="false" includeSubtypes="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> +</callExpressionSpecification> +</dependencyCriteria> + <dependencyCriteria><?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<callExpressionSpecification includeCustomTypes="false" includeSubtypes="false" xmlns="http://www.nomagic.com/schemas/MagicDraw/StructuredExpression/2013"> + <taggedValues> + <entry key="name"> + <value>Derived by</value> + </entry> + </taggedValues> + <argument xsi:type="lookupExpressionSpecification" symbol="THIS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> + <expression xsi:type="dslRelationExpressionSpecification" stereotype="_st_derive" direction="REVERSED" includeCustomTypes="false" includeSubtypes="false" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> +</callExpressionSpecification> +</dependencyCriteria> + </Dependency_Matrix_Profile:DependencyMatrix> + <Dependency_Matrix_Profile:MatrixFilter xmi:id="_mf_wide" base_Diagram="_diag_wide" rowScope="_pkg_reqs" columnScope="_pkg_plant" rowScopeDefined="true" columnScopeDefined="true" includeSubtypesOfRowTypes="true" includeSubtypesOfColumnTypes="true"> + <rowElementType href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#SysML.Requirement"/> + <columnElementType href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#SysML.Block"/> + <columnElementType href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#SysML.Requirement"/> + </Dependency_Matrix_Profile:MatrixFilter> + + <!-- A relation map following DeriveReqt from the derived requirement to its original. --> + <MagicDraw_Profile:RelationMap xmi:id="_map_derived" base_Diagram="_diag_derived" depth="1" layout="Tree" contextElement="_req_seal" includeSubtypes="true" showLegend="false"> + <elementTypes href="http://www.omg.org/spec/UML/20131001/UML.xmi#Class"/> + <relationCriterion><?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<callExpressionSpecification includeCustomTypes="false" includeSubtypes="true" xmlns="http://www.nomagic.com/schemas/MagicDraw/StructuredExpression/2013"> + <taggedValues> + <entry key="name"> + <value>Derived from</value> + </entry> + </taggedValues> + <argument xsi:type="lookupExpressionSpecification" symbol="THIS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> + <expression xsi:type="dslRelationExpressionSpecification" stereotype="_st_derive" direction="DIRECT" includeCustomTypes="false" includeSubtypes="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> +</callExpressionSpecification> +</relationCriterion> + </MagicDraw_Profile:RelationMap> + + <!-- A relation map walking Satisfy without subtypes from the valve, whose only satisfy is a «Fulfil». --> + <MagicDraw_Profile:RelationMap xmi:id="_map_valve" base_Diagram="_diag_map" depth="1" layout="Tree" contextElement="_blk_valve" includeSubtypes="true" showLegend="false"> + <elementTypes href="http://www.omg.org/spec/UML/20131001/UML.xmi#Class"/> + <relationCriterion><?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<callExpressionSpecification includeCustomTypes="false" includeSubtypes="false" xmlns="http://www.nomagic.com/schemas/MagicDraw/StructuredExpression/2013"> + <taggedValues> + <entry key="name"> + <value>Satisfy</value> + </entry> + </taggedValues> + <argument xsi:type="lookupExpressionSpecification" symbol="THIS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> + <expression xsi:type="dslRelationExpressionSpecification" stereotype="_st_satisfy" direction="DIRECT" includeCustomTypes="false" includeSubtypes="false" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> +</callExpressionSpecification> +</relationCriterion> + </MagicDraw_Profile:RelationMap> +</xmi:XMI> diff --git a/tests/migrate/testdata/xmi/table_homonyms.golden.report.txt b/tests/migrate/testdata/xmi/table_homonyms.golden.report.txt new file mode 100644 index 0000000000..dc631a6cbd --- /dev/null +++ b/tests/migrate/testdata/xmi/table_homonyms.golden.report.txt @@ -0,0 +1,55 @@ +# SysML v1 to v2 migration report: table_homonyms.xmi +# exported by Example UML Tool +# migrated 38 element(s): 23 mapped, 6 approximated, 9 unmapped (6 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) + +## unmapped (9) +«InstanceTable» <Diagram> _tbl_no_diagram (base_Diagram _no_such_diagram names no diagram of the document) +«InstanceTable» Diagram Shop::Ambiguous Scope _tbl_ambiguous (the scope _shared names 2 module elements (http://example.com/modules/Warehouse.xmi#_shared, http://example.com/modules/Storefront.xmi#_shared)) +«InstanceTable» Diagram Shop::Bad Sort _tbl_bad_sort (sort "IColumn:_prop_price^Sideways": not in the form <column>^Asc|Desc; sort "price": not in the form <column>^Asc|Desc) +«DependencyMatrix» Diagram Shop::Broken Matrix _mx_broken (the unnamed criterion is malformed: not well-formed XML: xmi: XML syntax error on line 4: unexpected EOF) +«InstanceTable» Diagram Shop::Dangling Scope _tbl_dangling (the scope _nowhere resolves to no element) +«RelationMap» Diagram Shop::Deep Map _map_deep (depth "deep": not a non-negative integer) +«DiagramTable» Diagram Shop::Ghost Column _tbl_ghost_column (none of the table's columns reads what a query can; the column IColumn:_no_such_property is omitted: the column IColumn:_no_such_property names no property of the document) +«InstanceTable» Diagram Shop::No Classifier _tbl_no_classifier (the instance table names no classifier) +«DependencyMatrix» Diagram Shop::Orphan Matrix _mx_orphan (MatrixFilter: no filter application names the diagram) + +## approximated (6) +Diagram Shop::Ambiguous Scope _diag_ambiguous -> Shop::'Ambiguous Scope' (a SysML Instance Table written as a view rendered asElementTable; the diagram shows nothing; the view exposes nothing) +Diagram Shop::Dangling Scope _diag_dangling -> Shop::'Dangling Scope' (a SysML Instance Table written as a view rendered asElementTable; the diagram shows nothing; the view exposes nothing) +Diagram Shop::No Classifier _diag_no_classifier -> Shop::'No Classifier' (a SysML Instance Table written as a view rendered asElementTable; the diagram shows nothing; the view exposes nothing) +Diagram Shop::Orphan Matrix _diag_orphan_matrix -> Shop::'Orphan Matrix' (a Dependency Matrix written as a view rendered asElementTable; the diagram shows nothing; the view exposes nothing) +Property Shop::Report::a _prop_report_a -> Shop::Report::a (typed by library element with no known v2 counterpart; type http://example.com/modules/Warehouse.xmi#_shared lives outside the document and is not written) +Property Shop::Report::b _prop_report_b -> Shop::Report::b (typed by library element with no known v2 counterpart; type http://example.com/modules/Storefront.xmi#_shared lives outside the document and is not written) + +## mapped (23) +Model Model _m (the root model's members are written at the top level) +Package Shop _pkg_shop -> Shop +Profile Shop Profile _shop_profile -> 'Shop Profile' +Stereotype Shop Profile::Document _st_shop_document -> 'Shop Profile'::Document +Stereotype Shop Profile::InstanceTable _st_shop_instances -> 'Shop Profile'::InstanceTable +Property Shop Profile::InstanceTable::scope _st_shop_instances_scope -> 'Shop Profile'::InstanceTable::scope +Stereotype Shop Profile::TableStructure _st_shop_table -> 'Shop Profile'::TableStructure +Property Shop Profile::TableStructure::rows _st_shop_table_rows -> 'Shop Profile'::TableStructure::rows +Diagram Shop::Bad Sort _diag_bad_sort -> Shop::'Bad Sort' (a SysML Instance Table written as a view rendered asElementTable) +Diagram Shop::Broken Matrix _diag_broken_matrix -> Shop::'Broken Matrix' (a Dependency Matrix written as a view rendered asElementTable) +«Block» Class Shop::Catalog _blk_catalog -> Shop::Catalog +«RelationMap» Diagram Shop::Catalog Map _map_catalog -> part def Shop::'Catalog Map Document' (the «RelationMap» is written as a Document holding a Table over the query 'Catalog Map Rows') +Diagram Shop::Catalog Map _diag_catalog_map -> Shop::'Catalog Map' (a Relation Map written as a view rendered asTreeDiagram) +Diagram Shop::Catalog Table _diag_shop_table -> Shop::'Catalog Table' (a SysML Instance Table written as a view rendered asElementTable) +Property Shop::Catalog::price _prop_price -> Shop::Catalog::price +Diagram Shop::Custom Table _diag_custom_table -> Shop::'Custom Table' (a Generic Table written as a view rendered asElementTable) +Diagram Shop::Deep Map _diag_deep_map -> Shop::'Deep Map' (a Relation Map written as a view rendered asTreeDiagram) +Diagram Shop::Ghost Column _diag_ghost_column -> Shop::'Ghost Column' (a Generic Table written as a view rendered asElementTable) +«Block» Class Shop::Ledger _blk_ledger -> Shop::Ledger +«Block» Class Shop::Report _blk_report -> Shop::Report («Document» from http://www.magicdraw.com/schemas/manual/Document_Profile_Custom.xmi is applied from a profile the document does not define; the application is kept as a comment) +«Block» Class Shop::SeasonalCatalog _blk_seasonal -> Shop::SeasonalCatalog +InstanceSpecification Shop::c1 _inst_c1 -> Shop::c1 +Slot Shop::c1::<Slot> _slot_c1_price -> Shop::c1::price + +## skipped (6) +Extension Shop Profile::<Extension> _ext_shop_table (an extension binds a stereotype to the metaclass it extends; v2 metadata applies to any element) +Extension Shop Profile::<Extension> _ext_shop_instances (an extension binds a stereotype to the metaclass it extends; v2 metadata applies to any element) +Extension Shop Profile::<Extension> _ext_shop_document (an extension binds a stereotype to the metaclass it extends; v2 metadata applies to any element) +Property Shop Profile::Document::base_Class _st_shop_document_base (an extension end names the metaclass the stereotype extends; v2 metadata applies to any element) +Property Shop Profile::InstanceTable::base_Class _st_shop_instances_base (an extension end names the metaclass the stereotype extends; v2 metadata applies to any element) +Property Shop Profile::TableStructure::base_Class _st_shop_table_base (an extension end names the metaclass the stereotype extends; v2 metadata applies to any element) diff --git a/tests/migrate/testdata/xmi/table_homonyms.golden.sysml b/tests/migrate/testdata/xmi/table_homonyms.golden.sysml new file mode 100644 index 0000000000..67c2ef2ccb --- /dev/null +++ b/tests/migrate/testdata/xmi/table_homonyms.golden.sysml @@ -0,0 +1,102 @@ +package 'Shop Profile' { + metadata def TableStructure { + attribute rows : ScalarValues::Integer; + } + metadata def InstanceTable { + attribute scope : ScalarValues::String; + } + metadata def Document; +} +package Shop { + part def Catalog { + attribute price : ScalarValues::Real; + @'Shop Profile'::InstanceTable { + scope = "Shop"; + } + } + part def Ledger { + @'Shop Profile'::TableStructure { + rows = 12; + } + } + part def Report { + attribute a; + attribute b; + @'Shop Profile'::Document; + /* applied stereotype «Document» */ + } + part def SeasonalCatalog :> Catalog; + individual part def c1 :> Catalog { + attribute :>> price = 4.5; + } + view 'Catalog Table' { + expose c1; + render Views::asElementTable; + } + view 'Custom Table' { + expose Catalog; + render Views::asElementTable; + } + view 'Dangling Scope' { + render Views::asElementTable; + } + /* not migrated: «InstanceTable» 'Dangling Scope' — the scope _nowhere resolves to no element */ + view 'Ambiguous Scope' { + render Views::asElementTable; + } + /* not migrated: «InstanceTable» 'Ambiguous Scope' — the scope _shared names 2 module elements (http://example.com/modules/Warehouse.xmi#_shared, http://example.com/modules/Storefront.xmi#_shared) */ + view 'Bad Sort' { + expose c1; + render Views::asElementTable; + } + /* not migrated: «InstanceTable» 'Bad Sort' — sort "IColumn:_prop_price^Sideways": not in the form <column>^Asc|Desc; sort "price": not in the form <column>^Asc|Desc */ + view 'No Classifier' { + render Views::asElementTable; + } + /* not migrated: «InstanceTable» 'No Classifier' — the instance table names no classifier */ + view 'Ghost Column' { + expose Catalog; + render Views::asElementTable; + } + /* not migrated: «DiagramTable» 'Ghost Column' — none of the table's columns reads what a query can; the column IColumn:_no_such_property is omitted: the column IColumn:_no_such_property names no property of the document */ + view 'Broken Matrix' { + expose Catalog; + render Views::asElementTable; + } + /* not migrated: «DependencyMatrix» 'Broken Matrix' — the unnamed criterion is malformed: not well-formed XML: xmi: XML syntax error on line 4: unexpected EOF */ + view 'Orphan Matrix' { + render Views::asElementTable; + } + /* not migrated: «DependencyMatrix» 'Orphan Matrix' — MatrixFilter: no filter application names the diagram */ + view 'Deep Map' { + expose Catalog; + expose SeasonalCatalog; + render Views::asTreeDiagram; + } + /* not migrated: «RelationMap» 'Deep Map' — depth "deep": not a non-negative integer */ + view 'Catalog Map' { + expose Catalog; + expose SeasonalCatalog; + expose 'Catalog Map Document'; + render Views::asTreeDiagram; + } + calc def 'Catalog Map Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::WhereType( + source = DocumentQueries::RelatedElements( + source = DocumentQueries::Named(qualifiedName = ("Shop::Catalog")), + relationshipKind = "specialization", + direction = "incoming", + maxDepth = 1), + type = ("PartDefinition", "RequirementDefinition", "ConstraintDefinition", "PortDefinition", "VerificationCaseDefinition", "ActionDefinition", "StateDefinition", "CalculationDefinition", "ViewUsage", "ViewpointUsage")), + properties = ("qualifiedName", "@type")) + } + part def 'Catalog Map Document' :> DocumentQueries::Document { + attribute redefines title = "Catalog Map"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Catalog Map"; + calc rows : 'Catalog Map Rows'; + } + } +} +/* not migrated: «InstanceTable» _no_such_diagram — base_Diagram _no_such_diagram names no diagram of the document */ diff --git a/tests/migrate/testdata/xmi/table_homonyms.xmi b/tests/migrate/testdata/xmi/table_homonyms.xmi new file mode 100644 index 0000000000..9fc3baad86 --- /dev/null +++ b/tests/migrate/testdata/xmi/table_homonyms.xmi @@ -0,0 +1,305 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xmi:XMI xmi:version="2.5.1" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" + xmlns:uml="http://www.omg.org/spec/UML/20161101" + xmlns:sysml="http://www.omg.org/spec/SysML/20181001/SysML" + xmlns:MagicDraw_Profile="http://www.omg.org/spec/UML/20131001/MagicDrawProfile" + xmlns:Dependency_Matrix_Profile="http://www.magicdraw.com/schemas/Dependency_Matrix_Profile.xmi" + xmlns:Shop_Profile="http://example.com/schemas/Shop_Profile.xmi" + xmlns:MagicDraw_Custom="http://www.omg.org/spec/UML/20131001/MagicDrawProfile/Custom" + xmlns:Document_Custom="http://www.magicdraw.com/schemas/manual/Document_Profile_Custom.xmi" + xmlns:diagram="http://www.example.com/tool/diagram"> + <xmi:Documentation exporter="Example UML Tool" exporterVersion="1.0"/> + <uml:Model xmi:type="uml:Model" xmi:id="_m" name="Model"> + + <!-- A user profile whose stereotypes are spelled as the tool's table and + document stereotypes are, on its own URI. --> + <packagedElement xmi:type="uml:Profile" xmi:id="_shop_profile" name="Shop Profile" URI="http://example.com/schemas/Shop_Profile.xmi"> + <packagedElement xmi:type="uml:Stereotype" xmi:id="_st_shop_table" name="TableStructure"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_st_shop_table_base" name="base_Class" association="_ext_shop_table"> + <type xmi:type="uml:Class" href="http://www.omg.org/spec/UML/20131001/UML.xmi#Class"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_st_shop_table_rows" name="rows"> + <type xmi:type="uml:PrimitiveType" href="http://www.omg.org/spec/UML/20131001/PrimitiveTypes.xmi#Integer"/> + </ownedAttribute> + </packagedElement> + <packagedElement xmi:type="uml:Extension" xmi:id="_ext_shop_table" memberEnd="_st_shop_table_base _ext_shop_table_end"> + <ownedEnd xmi:type="uml:ExtensionEnd" xmi:id="_ext_shop_table_end" name="extension_TableStructure" type="_st_shop_table" aggregation="composite"/> + </packagedElement> + <packagedElement xmi:type="uml:Stereotype" xmi:id="_st_shop_instances" name="InstanceTable"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_st_shop_instances_base" name="base_Class" association="_ext_shop_instances"> + <type xmi:type="uml:Class" href="http://www.omg.org/spec/UML/20131001/UML.xmi#Class"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_st_shop_instances_scope" name="scope"> + <type xmi:type="uml:PrimitiveType" href="http://www.omg.org/spec/UML/20131001/PrimitiveTypes.xmi#String"/> + </ownedAttribute> + </packagedElement> + <packagedElement xmi:type="uml:Extension" xmi:id="_ext_shop_instances" memberEnd="_st_shop_instances_base _ext_shop_instances_end"> + <ownedEnd xmi:type="uml:ExtensionEnd" xmi:id="_ext_shop_instances_end" name="extension_InstanceTable" type="_st_shop_instances" aggregation="composite"/> + </packagedElement> + <packagedElement xmi:type="uml:Stereotype" xmi:id="_st_shop_document" name="Document"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_st_shop_document_base" name="base_Class" association="_ext_shop_document"> + <type xmi:type="uml:Class" href="http://www.omg.org/spec/UML/20131001/UML.xmi#Class"/> + </ownedAttribute> + </packagedElement> + <packagedElement xmi:type="uml:Extension" xmi:id="_ext_shop_document" memberEnd="_st_shop_document_base _ext_shop_document_end"> + <ownedEnd xmi:type="uml:ExtensionEnd" xmi:id="_ext_shop_document_end" name="extension_Document" type="_st_shop_document" aggregation="composite"/> + </packagedElement> + </packagedElement> + + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_shop" name="Shop"> + <packagedElement xmi:type="uml:Class" xmi:id="_blk_catalog" name="Catalog"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_prop_price" name="price"> + <type xmi:type="uml:PrimitiveType" href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#Real"/> + </ownedAttribute> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_blk_ledger" name="Ledger"/> + <packagedElement xmi:type="uml:Class" xmi:id="_blk_report" name="Report"> + <!-- Two used modules whose elements share the fragment _shared. --> + <ownedAttribute xmi:type="uml:Property" xmi:id="_prop_report_a" name="a"> + <type xmi:type="uml:Class" href="http://example.com/modules/Warehouse.xmi#_shared"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_prop_report_b" name="b"> + <type xmi:type="uml:Class" href="http://example.com/modules/Storefront.xmi#_shared"/> + </ownedAttribute> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_blk_seasonal" name="SeasonalCatalog"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_seasonal" general="_blk_catalog"/> + </packagedElement> + <packagedElement xmi:type="uml:InstanceSpecification" xmi:id="_inst_c1" name="c1" classifier="_blk_catalog"> + <slot xmi:type="uml:Slot" xmi:id="_slot_c1_price" definingFeature="_prop_price"> + <value xmi:type="uml:LiteralReal" xmi:id="_slot_c1_price_v" value="4.5"/> + </slot> + </packagedElement> + </packagedElement> + + <xmi:Extension extender="Example UML Tool 1.0"> + <modelExtension> + <!-- Table diagrams whose only table definitions are homonyms. --> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_shop_table" name="Catalog Table" ownerOfDiagram="_pkg_shop"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="SysML Instance Table" umlType="Class Diagram"> + <diagramContents> + <usedElements>_inst_c1</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_custom_table" name="Custom Table" ownerOfDiagram="_pkg_shop"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Generic Table" umlType="Class Diagram"> + <diagramContents> + <usedElements>_blk_catalog</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <!-- Table diagrams whose exact-profile definitions are malformed. --> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_dangling" name="Dangling Scope" ownerOfDiagram="_pkg_shop"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="SysML Instance Table" umlType="Class Diagram"> + <diagramContents/> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_ambiguous" name="Ambiguous Scope" ownerOfDiagram="_pkg_shop"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="SysML Instance Table" umlType="Class Diagram"> + <diagramContents/> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_bad_sort" name="Bad Sort" ownerOfDiagram="_pkg_shop"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="SysML Instance Table" umlType="Class Diagram"> + <diagramContents> + <usedElements>_inst_c1</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_no_classifier" name="No Classifier" ownerOfDiagram="_pkg_shop"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="SysML Instance Table" umlType="Class Diagram"> + <diagramContents/> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_ghost_column" name="Ghost Column" ownerOfDiagram="_pkg_shop"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Generic Table" umlType="Class Diagram"> + <diagramContents> + <usedElements>_blk_catalog</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_broken_matrix" name="Broken Matrix" ownerOfDiagram="_pkg_shop"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Dependency Matrix" umlType="Class Diagram"> + <diagramContents> + <usedElements>_blk_catalog</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_orphan_matrix" name="Orphan Matrix" ownerOfDiagram="_pkg_shop"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Dependency Matrix" umlType="Class Diagram"> + <diagramContents/> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_deep_map" name="Deep Map" ownerOfDiagram="_pkg_shop"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Relation Map" umlType="Class Diagram"> + <diagramContents> + <usedElements>_blk_catalog</usedElements> + <usedElements>_blk_seasonal</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <!-- A sound relation map beside the malformed ones. --> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_catalog_map" name="Catalog Map" ownerOfDiagram="_pkg_shop"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Relation Map" umlType="Class Diagram"> + <diagramContents> + <usedElements>_blk_catalog</usedElements> + <usedElements>_blk_seasonal</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + </modelExtension> + </xmi:Extension> + </uml:Model> + + <sysml:Block xmi:id="_st_blk_catalog" base_Class="_blk_catalog"/> + <sysml:Block xmi:id="_st_blk_ledger" base_Class="_blk_ledger"/> + <sysml:Block xmi:id="_st_blk_report" base_Class="_blk_report"/> + <sysml:Block xmi:id="_st_blk_seasonal" base_Class="_blk_seasonal"/> + + <!-- Homonyms on the user profile: ordinary metadata, never a table. --> + <Shop_Profile:TableStructure xmi:id="_app_shop_table" base_Class="_blk_ledger" rows="12"/> + <Shop_Profile:InstanceTable xmi:id="_app_shop_instances" base_Class="_blk_catalog" scope="Shop"/> + <Shop_Profile:Document xmi:id="_app_shop_document" base_Class="_blk_report"/> + + <!-- Homonyms on look-alike URIs the archive does not define: tool-profile + comments, never a table or a document. --> + <MagicDraw_Custom:InstanceTable xmi:id="_app_custom_instances" base_Diagram="_diag_shop_table" scope="_pkg_shop"> + <classifiers xmi:idref="_blk_catalog"/> + <columnIds>QPROP:Element:name</columnIds> + </MagicDraw_Custom:InstanceTable> + <MagicDraw_Custom:DiagramTable xmi:id="_app_custom_table" base_Diagram="_diag_custom_table" scope="_pkg_shop"> + <rowElementType href="http://www.omg.org/spec/UML/20131001/UML.xmi#Class"/> + <columnIds>QPROP:Element:name</columnIds> + </MagicDraw_Custom:DiagramTable> + <Document_Custom:Document xmi:id="_app_custom_document" base_Class="_blk_report"/> + + <!-- Malformed definitions on the exact tool profiles. --> + <MagicDraw_Profile:InstanceTable xmi:id="_tbl_dangling" base_Diagram="_diag_dangling" scope="_nowhere"> + <classifiers xmi:idref="_blk_catalog"/> + <columnIds>QPROP:Element:name</columnIds> + </MagicDraw_Profile:InstanceTable> + <!-- A bare module id two used modules' hrefs share: it names neither. --> + <MagicDraw_Profile:InstanceTable xmi:id="_tbl_ambiguous" base_Diagram="_diag_ambiguous" scope="_shared"> + <classifiers xmi:idref="_blk_catalog"/> + <columnIds>QPROP:Element:name</columnIds> + </MagicDraw_Profile:InstanceTable> + <MagicDraw_Profile:InstanceTable xmi:id="_tbl_bad_sort" base_Diagram="_diag_bad_sort" scope="_pkg_shop"> + <classifiers xmi:idref="_blk_catalog"/> + <columnIds>QPROP:Element:name</columnIds> + <columnIds>IColumn:_prop_price</columnIds> + <sort>IColumn:_prop_price^Sideways</sort> + <sort>price</sort> + </MagicDraw_Profile:InstanceTable> + <MagicDraw_Profile:InstanceTable xmi:id="_tbl_no_classifier" base_Diagram="_diag_no_classifier" scope="_pkg_shop"> + <columnIds>QPROP:Element:name</columnIds> + </MagicDraw_Profile:InstanceTable> + <MagicDraw_Profile:DiagramTable xmi:id="_tbl_ghost_column" base_Diagram="_diag_ghost_column" scope="_pkg_shop"> + <rowElementType href="http://www.omg.org/spec/UML/20131001/UML.xmi#Class"/> + <columnIds>IColumn:_no_such_property</columnIds> + </MagicDraw_Profile:DiagramTable> + <MagicDraw_Profile:InstanceTable xmi:id="_tbl_no_diagram" base_Diagram="_no_such_diagram" scope="_pkg_shop"> + <classifiers xmi:idref="_blk_catalog"/> + <columnIds>QPROP:Element:name</columnIds> + </MagicDraw_Profile:InstanceTable> + + <Dependency_Matrix_Profile:DependencyMatrix xmi:id="_mx_broken" base_Diagram="_diag_broken_matrix" direction="Row to column" showElements="All"> + <dependencyCriteria><?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<callExpressionSpecification xmlns="http://www.nomagic.com/schemas/MagicDraw/StructuredExpression/2013"> + <argument xsi:type="lookupExpressionSpecification" symbol="THIS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> + <expression xsi:type="relationExpressionSpecification" metaclass="Generalization" direction="DIRECT" +</dependencyCriteria> + </Dependency_Matrix_Profile:DependencyMatrix> + <Dependency_Matrix_Profile:MatrixFilter xmi:id="_mf_broken" base_Diagram="_diag_broken_matrix" rowScope="_pkg_shop" columnScope="_pkg_shop" rowScopeDefined="true" columnScopeDefined="true"> + <rowElementType href="http://www.omg.org/spec/UML/20131001/UML.xmi#Class"/> + <columnElementType href="http://www.omg.org/spec/UML/20131001/UML.xmi#Class"/> + </Dependency_Matrix_Profile:MatrixFilter> + + <Dependency_Matrix_Profile:DependencyMatrix xmi:id="_mx_orphan" base_Diagram="_diag_orphan_matrix" direction="Row to column" showElements="All"> + <dependencyCriteria><?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<callExpressionSpecification xmlns="http://www.nomagic.com/schemas/MagicDraw/StructuredExpression/2013"> + <taggedValues> + <entry key="name"> + <value>Generalization</value> + </entry> + </taggedValues> + <argument xsi:type="lookupExpressionSpecification" symbol="THIS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> + <expression xsi:type="relationExpressionSpecification" metaclass="Generalization" direction="DIRECT" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> +</callExpressionSpecification> +</dependencyCriteria> + </Dependency_Matrix_Profile:DependencyMatrix> + + <MagicDraw_Profile:RelationMap xmi:id="_map_deep" base_Diagram="_diag_deep_map" depth="deep" contextElement="_blk_catalog" includeSubtypes="true"> + <elementTypes href="http://www.omg.org/spec/UML/20131001/UML.xmi#Class"/> + <relationCriterion><?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<callExpressionSpecification xmlns="http://www.nomagic.com/schemas/MagicDraw/StructuredExpression/2013"> + <taggedValues> + <entry key="name"> + <value>Generalization</value> + </entry> + </taggedValues> + <argument xsi:type="lookupExpressionSpecification" symbol="THIS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> + <expression xsi:type="relationExpressionSpecification" metaclass="Generalization" direction="REVERSED" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> +</callExpressionSpecification> +</relationCriterion> + </MagicDraw_Profile:RelationMap> + + <MagicDraw_Profile:RelationMap xmi:id="_map_catalog" base_Diagram="_diag_catalog_map" depth="1" contextElement="_blk_catalog" includeSubtypes="true"> + <elementTypes href="http://www.omg.org/spec/UML/20131001/UML.xmi#Class"/> + <relationCriterion><?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<callExpressionSpecification xmlns="http://www.nomagic.com/schemas/MagicDraw/StructuredExpression/2013"> + <taggedValues> + <entry key="name"> + <value>Generalization</value> + </entry> + </taggedValues> + <argument xsi:type="lookupExpressionSpecification" symbol="THIS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> + <expression xsi:type="relationExpressionSpecification" metaclass="Generalization" direction="REVERSED" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> +</callExpressionSpecification> +</relationCriterion> + </MagicDraw_Profile:RelationMap> +</xmi:XMI> diff --git a/tests/migrate/testdata/xmi/tables.golden.report.txt b/tests/migrate/testdata/xmi/tables.golden.report.txt new file mode 100644 index 0000000000..edb231f62e --- /dev/null +++ b/tests/migrate/testdata/xmi/tables.golden.report.txt @@ -0,0 +1,68 @@ +# SysML v1 to v2 migration report: tables.xmi +# exported by Example UML Tool +# migrated 55 element(s): 51 mapped, 3 approximated, 1 unmapped (2 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) + +## unmapped (1) +«DependencyMatrix» Diagram Plant::Requirements::Trace Matrix _mx_trace (the criterion Owner chain is a metaChainExpressionSpecification, which no relationship walk expresses) + +## approximated (3) +«InstanceTable» Diagram Plant::Inventory::Pump Ledger _tbl_ledger -> part def Plant::Inventory::'Pump Ledger Document' (the «InstanceTable» is written as a Document holding a Table over the query 'Pump Ledger Rows'; rows of subtypes of the row types are listed too: a type filter admits conforming elements; the column name is written as name 2: column names are unique; Project lists its properties first: name, qualifiedName precede the other columns) +«InstanceTable» Diagram Plant::Inventory::Pump Table _tbl_pumps -> part def Plant::Inventory::'Pump Table Document' (the «InstanceTable» is written as a Document holding a Table over the query 'Pump Table Rows'; the column QPROP:Element:classifier is omitted: no query property stands for the UML property classifier) +«DependencyMatrix» Diagram Plant::Requirements::Satisfaction Matrix _mx_satisfy -> part def Plant::Requirements::'Satisfaction Matrix Document' (the «DependencyMatrix» is written as a Document holding a Table over the query 'Satisfaction Matrix Rows'; the column Trace is written as Trace 2: column names are unique) + +## mapped (51) +Model Model _m (the root model's members are written at the top level) +Package Plant _pkg_plant -> Plant +Profile Plant Profile _plant_profile -> 'Plant Profile' +Stereotype Plant Profile::Critical _st_critical -> 'Plant Profile'::Critical +Property Plant Profile::Critical::level _st_critical_level -> 'Plant Profile'::Critical::level +«DiagramTable» Diagram Plant::Critical Elements _tbl_critical -> part def Plant::'Critical Elements Document' (the «DiagramTable» is written as a Document holding a Table over the query 'Critical Elements Rows') +Diagram Plant::Critical Elements _diag_critical -> Plant::'Critical Elements' (a Generic Table written as a view rendered asElementTable) +Package Plant::Inventory _pkg_inventory -> Plant::Inventory +Diagram Plant::Inventory::Pump Ledger _diag_ledger -> Plant::Inventory::'Pump Ledger' (a SysML Instance Table written as a view rendered asElementTable) +Diagram Plant::Inventory::Pump Table _diag_pumps -> Plant::Inventory::'Pump Table' (a SysML Instance Table written as a view rendered asElementTable) +InstanceSpecification Plant::Inventory::p1 _inst_p1 -> Plant::Inventory::p1 +Slot Plant::Inventory::p1::<Slot> _slot_p1_mass -> Plant::Inventory::p1::mass +Slot Plant::Inventory::p1::<Slot> _slot_p1_flow -> Plant::Inventory::p1::'flow' +Slot Plant::Inventory::p1::<Slot> _slot_p1_label -> Plant::Inventory::p1::name +InstanceSpecification Plant::Inventory::p2 _inst_p2 -> Plant::Inventory::p2 +Slot Plant::Inventory::p2::<Slot> _slot_p2_mass -> Plant::Inventory::p2::mass +InstanceSpecification Plant::Inventory::r1 _inst_r1 -> Plant::Inventory::r1 +Slot Plant::Inventory::r1::<Slot> _slot_r1_mass -> Plant::Inventory::r1::mass +InstanceSpecification Plant::Inventory::v1 _inst_v1 -> Plant::Inventory::v1 +Slot Plant::Inventory::v1::<Slot> _slot_v1_mass -> Plant::Inventory::v1::mass +Package Plant::Requirements _pkg_reqs -> Plant::Requirements +«Satisfy» Abstraction Plant::Requirements::<Abstraction> _dep_satisfy_pump -> Plant::Structure::Pump +«Satisfy» Abstraction Plant::Requirements::<Abstraction> _dep_satisfy_valve -> Plant::Structure::Valve +«DeriveReqt» Abstraction Plant::Requirements::<Abstraction> _dep_derive -> Plant::Requirements::'Derive MassRequirement' +«Requirement» Class Plant::Requirements::FlowRequirement _req_flow -> Plant::Requirements::FlowRequirement +Comment Plant::Requirements::FlowRequirement::<Comment> _cmt_req_flow +«Requirement» Class Plant::Requirements::MassRequirement _req_mass -> Plant::Requirements::MassRequirement +«DiagramTable» Diagram Plant::Requirements::Requirement Table _tbl_reqs -> part def Plant::Requirements::'Requirement Table Document' (the «DiagramTable» is written as a Document holding a Table over the query 'Requirement Table Rows') +Diagram Plant::Requirements::Requirement Table _diag_reqs -> Plant::Requirements::'Requirement Table' (a Requirement Table written as a view rendered asElementTable) +Diagram Plant::Requirements::Satisfaction Matrix _diag_matrix -> Plant::Requirements::'Satisfaction Matrix' (a Dependency Matrix written as a view rendered asElementTable) +«Requirement» Class Plant::Requirements::SealRequirement _req_seal -> Plant::Requirements::SealRequirement +Comment Plant::Requirements::SealRequirement::<Comment> _cmt_req_seal +Diagram Plant::Requirements::Trace Matrix _diag_trace_matrix -> Plant::Requirements::'Trace Matrix' (a Dependency Matrix written as a view rendered asElementTable) +Package Plant::Spares _pkg_spares -> Plant::Spares +InstanceSpecification Plant::Spares::s1 _inst_s1 -> Plant::Spares::s1 +Slot Plant::Spares::s1::<Slot> _slot_s1_mass -> Plant::Spares::s1::mass +InstanceSpecification Plant::Spares::s2 _inst_s2 -> Plant::Spares::s2 +Package Plant::Structure _pkg_structure -> Plant::Structure +«Block» Class Plant::Structure::Pump _blk_pump -> Plant::Structure::Pump +«RelationMap» Diagram Plant::Structure::Pump Requirement Map _map_pump -> part def Plant::Structure::'Pump Requirement Map Document' (the «RelationMap» is written as a Document holding a Table over the query 'Pump Requirement Map Rows') +Diagram Plant::Structure::Pump Requirement Map _diag_map -> Plant::Structure::'Pump Requirement Map' (a Relation Map written as a view rendered asTreeDiagram) +Comment Plant::Structure::Pump::<Comment> _cmt_pump +Property Plant::Structure::Pump::flow _prop_flow -> Plant::Structure::Pump::'flow' +Property Plant::Structure::Pump::mass _prop_mass -> Plant::Structure::Pump::mass +Property Plant::Structure::Pump::name _prop_label -> Plant::Structure::Pump::name +Operation Plant::Structure::Pump::prime _op_prime -> Plant::Structure::Pump::prime (its owner's usage prime 2 performs it, as a call on an object does) +«Block» Class Plant::Structure::ReservePump _blk_reserve -> Plant::Structure::ReservePump +«Block» Class Plant::Structure::Station _blk_station -> Plant::Structure::Station +Property Plant::Structure::Station::pumps _prop_pumps -> Plant::Structure::Station::pumps +«Block» Class Plant::Structure::Valve _blk_valve -> Plant::Structure::Valve +Property Plant::Structure::Valve::mass _prop_valve_mass -> Plant::Structure::Valve::mass + +## skipped (2) +Extension Plant Profile::<Extension> _ext_critical (an extension binds a stereotype to the metaclass it extends; v2 metadata applies to any element) +Property Plant Profile::Critical::base_Class _st_critical_base (an extension end names the metaclass the stereotype extends; v2 metadata applies to any element) diff --git a/tests/migrate/testdata/xmi/tables.golden.sysml b/tests/migrate/testdata/xmi/tables.golden.sysml new file mode 100644 index 0000000000..79fdbf2fb9 --- /dev/null +++ b/tests/migrate/testdata/xmi/tables.golden.sysml @@ -0,0 +1,263 @@ +package 'Plant Profile' { + metadata def Critical { + attribute level : ScalarValues::Integer; + } +} +package Plant { + package Structure { + part def Pump { + doc /* Moves fluid through the plant. */ + attribute mass : ScalarValues::Real; + attribute 'flow' : ScalarValues::Real; + attribute name : ScalarValues::String; + abstract action def prime; + action 'prime 2' : prime; + satisfy requirement : Plant::Requirements::FlowRequirement; + @'Plant Profile'::Critical { + level = 2; + } + } + part def ReservePump :> Pump; + part def Valve { + attribute mass : ScalarValues::Real; + satisfy requirement : Plant::Requirements::SealRequirement; + } + part def Station { + part pumps : Pump[1..*]; + } + view 'Pump Requirement Map' { + expose Pump; + expose Plant::Requirements::FlowRequirement; + expose 'Pump Requirement Map Document'; + render Views::asTreeDiagram; + } + calc def 'Pump Requirement Map Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::WhereType( + source = DocumentQueries::RelatedElements( + source = DocumentQueries::Named(qualifiedName = ("Plant::Structure::Pump")), + relationshipKind = "satisfaction", + direction = "outgoing", + maxDepth = 2), + type = ("PartDefinition", "RequirementDefinition", "ConstraintDefinition", "PortDefinition", "VerificationCaseDefinition", "ActionDefinition", "StateDefinition", "CalculationDefinition", "ViewUsage", "ViewpointUsage")), + properties = ("qualifiedName", "@type")) + } + part def 'Pump Requirement Map Document' :> DocumentQueries::Document { + attribute redefines title = "Pump Requirement Map"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Pump Requirement Map"; + calc rows : 'Pump Requirement Map Rows'; + } + } + } + package Inventory { + individual part def p1 :> Plant::Structure::Pump { + attribute :>> mass = 12.5; + attribute :>> 'flow' = 3.0; + attribute :>> name = "primary"; + } + individual part def p2 :> Plant::Structure::Pump { + attribute :>> mass = 9.0; + } + individual part def r1 :> Plant::Structure::ReservePump { + attribute :>> mass = 14.0; + } + individual part def v1 :> Plant::Structure::Valve { + attribute :>> mass = 2.0; + } + view 'Pump Table' { + expose p1; + expose p2; + expose r1; + expose 'Pump Table Document'; + render Views::asElementTable; + } + calc def 'Pump Table Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::OrderBy( + source = DocumentQueries::WhereFeature( + source = DocumentQueries::WhereType( + source = DocumentQueries::Union( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant::Inventory"))), + other = DocumentQueries::Named(qualifiedName = ("Plant::Spares::s1", "Plant::Spares::s2"))), + type = ("Plant::Structure::Pump")), + 'feature' = "isIndividual", + operator = "=", + value = "true"), + property = "mass", + direction = "descending", + missing = "last", + multiple = "first"), + properties = ("name"), + columns = ( + DocumentQueries::Column(name = "mass", expression = Plant::Structure::Pump::mass ?? ""), + DocumentQueries::Column(name = "flow", expression = Plant::Structure::Pump::'flow' ?? ""))) + } + part def 'Pump Table Document' :> DocumentQueries::Document { + attribute redefines title = "Pump Table"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Pump Table"; + calc rows : 'Pump Table Rows'; + } + } + view 'Pump Ledger' { + expose p1; + expose p2; + expose 'Pump Ledger Document'; + render Views::asElementTable; + } + calc def 'Pump Ledger Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::OrderBy( + source = DocumentQueries::WhereFeature( + source = DocumentQueries::WhereType( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant::Inventory"))), + type = ("Plant::Structure::Pump")), + 'feature' = "isIndividual", + operator = "=", + value = "true"), + property = "name", + direction = "ascending", + missing = "last", + multiple = "first"), + properties = ("name", "qualifiedName"), + columns = ( + DocumentQueries::Column(name = "name 2", expression = Plant::Structure::Pump::name ?? ""), + DocumentQueries::Column(name = "mass", expression = Plant::Structure::Pump::mass ?? ""))) + } + part def 'Pump Ledger Document' :> DocumentQueries::Document { + attribute redefines title = "Pump Ledger"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Pump Ledger"; + calc rows : 'Pump Ledger Rows'; + } + } + } + package Spares { + individual part def s1 :> Plant::Structure::Pump { + attribute :>> mass = 7.0; + } + individual part def s2 :> Plant::Structure::Pump; + } + package Requirements { + requirement def <'R-1'> FlowRequirement { + doc /* The pump keeps the flow above the minimum. */ + comment /* The pump keeps the flow up. */ + @'Plant Profile'::Critical { + level = 1; + } + } + requirement def <'R-2'> SealRequirement { + doc /* Every valve seals when closed. */ + comment /* Every valve seals when closed. */ + } + requirement def <'R-3'> MassRequirement { + doc /* The pump mass stays under the limit. */ + } + connection def 'Derive MassRequirement' :> RequirementDerivation::Derivation { + end #RequirementDerivation::original originalRequirement : FlowRequirement; + end #RequirementDerivation::derive derivedRequirement : MassRequirement; + } + view 'Requirement Table' { + expose FlowRequirement; + expose SealRequirement; + expose MassRequirement; + expose 'Requirement Table Document'; + render Views::asElementTable; + } + calc def 'Requirement Table Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::OrderBy( + source = DocumentQueries::WhereType( + source = DocumentQueries::Union( + source = DocumentQueries::Named(qualifiedName = ("Plant Profile", "Plant")), + other = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant Profile", "Plant")))), + type = ("RequirementDefinition")), + property = "name", + direction = "ascending", + missing = "last", + multiple = "first"), + properties = ("name", "documentation")) + } + part def 'Requirement Table Document' :> DocumentQueries::Document { + attribute redefines title = "Requirement Table"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Requirement Table"; + calc rows : 'Requirement Table Rows'; + } + } + view 'Satisfaction Matrix' { + expose FlowRequirement; + expose SealRequirement; + expose Plant::Structure::Pump; + expose Plant::Structure::Valve; + expose 'Satisfaction Matrix Document'; + render Views::asElementTable; + } + calc def 'Satisfaction Matrix Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::WhereType( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant::Requirements"))), + type = ("RequirementDefinition")), + properties = ("name"), + columns = ( + DocumentQueries::RelatedColumn( + name = "Trace", + relationshipKind = "satisfaction", + direction = "incoming", + maxDepth = 1, + aggregate = "list", + targets = DocumentQueries::WhereType( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant::Structure"))), + type = ("PartDefinition"))), + DocumentQueries::RelatedColumn( + name = "Trace 2", + relationshipKind = "derivation", + direction = "outgoing", + maxDepth = 1, + aggregate = "list", + targets = DocumentQueries::WhereType( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant::Structure"))), + type = ("PartDefinition"))))) + } + part def 'Satisfaction Matrix Document' :> DocumentQueries::Document { + attribute redefines title = "Satisfaction Matrix"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Satisfaction Matrix"; + calc rows : 'Satisfaction Matrix Rows'; + } + } + view 'Trace Matrix' { + expose FlowRequirement; + render Views::asElementTable; + } + /* not migrated: «DependencyMatrix» 'Trace Matrix' — the criterion Owner chain is a metaChainExpressionSpecification, which no relationship walk expresses */ + } + view 'Critical Elements' { + expose Plant::Structure::Pump; + expose Plant::Requirements::FlowRequirement; + expose 'Critical Elements Document'; + render Views::asElementTable; + } + calc def 'Critical Elements Rows' :> DocumentQueries::Query { + DocumentQueries::Project( + source = DocumentQueries::WhereMetadata( + source = DocumentQueries::Descendants( + source = DocumentQueries::Named(qualifiedName = ("Plant"))), + 'metadata' = ("Plant Profile::Critical")), + properties = ("qualifiedName")) + } + part def 'Critical Elements Document' :> DocumentQueries::Document { + attribute redefines title = "Critical Elements"; + part rows : DocumentQueries::Table { + attribute redefines caption = "Critical Elements"; + calc rows : 'Critical Elements Rows'; + } + } +} diff --git a/tests/migrate/testdata/xmi/tables.xmi b/tests/migrate/testdata/xmi/tables.xmi new file mode 100644 index 0000000000..a0a944f3a2 --- /dev/null +++ b/tests/migrate/testdata/xmi/tables.xmi @@ -0,0 +1,322 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xmi:XMI xmi:version="2.5.1" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" + xmlns:uml="http://www.omg.org/spec/UML/20161101" + xmlns:sysml="http://www.omg.org/spec/SysML/20181001/SysML" + xmlns:MagicDraw_Profile="http://www.omg.org/spec/UML/20131001/MagicDrawProfile" + xmlns:Dependency_Matrix_Profile="http://www.magicdraw.com/schemas/Dependency_Matrix_Profile.xmi" + xmlns:Plant_Profile="http://example.com/schemas/Plant_Profile.xmi" + xmlns:diagram="http://www.example.com/tool/diagram"> + <xmi:Documentation exporter="Example UML Tool" exporterVersion="1.0"/> + <uml:Model xmi:type="uml:Model" xmi:id="_m" name="Model"> + + <packagedElement xmi:type="uml:Profile" xmi:id="_plant_profile" name="Plant Profile" URI="http://example.com/schemas/Plant_Profile.xmi"> + <packagedElement xmi:type="uml:Stereotype" xmi:id="_st_critical" name="Critical"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_st_critical_base" name="base_Class" association="_ext_critical"> + <type xmi:type="uml:Class" href="http://www.omg.org/spec/UML/20131001/UML.xmi#Class"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_st_critical_level" name="level"> + <type xmi:type="uml:PrimitiveType" href="http://www.omg.org/spec/UML/20131001/PrimitiveTypes.xmi#Integer"/> + </ownedAttribute> + </packagedElement> + <packagedElement xmi:type="uml:Extension" xmi:id="_ext_critical" memberEnd="_st_critical_base _ext_critical_end"> + <ownedEnd xmi:type="uml:ExtensionEnd" xmi:id="_ext_critical_end" name="extension_Critical" type="_st_critical" aggregation="composite"/> + </packagedElement> + </packagedElement> + + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_plant" name="Plant"> + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_structure" name="Structure"> + <packagedElement xmi:type="uml:Class" xmi:id="_blk_pump" name="Pump"> + <ownedComment xmi:type="uml:Comment" xmi:id="_cmt_pump" body="Moves fluid through the plant."/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_prop_mass" name="mass"> + <type xmi:type="uml:PrimitiveType" href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#Real"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_prop_flow" name="flow"> + <type xmi:type="uml:PrimitiveType" href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#Real"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_prop_label" name="name"> + <type xmi:type="uml:PrimitiveType" href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#String"/> + </ownedAttribute> + <ownedOperation xmi:type="uml:Operation" xmi:id="_op_prime" name="prime"/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_blk_reserve" name="ReservePump"> + <generalization xmi:type="uml:Generalization" xmi:id="_gen_reserve" general="_blk_pump"/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_blk_valve" name="Valve"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_prop_valve_mass" name="mass"> + <type xmi:type="uml:PrimitiveType" href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#Real"/> + </ownedAttribute> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_blk_station" name="Station"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_prop_pumps" name="pumps" type="_blk_pump" aggregation="composite"> + <lowerValue xmi:type="uml:LiteralInteger" xmi:id="_prop_pumps_l" value="1"/> + <upperValue xmi:type="uml:LiteralUnlimitedNatural" xmi:id="_prop_pumps_u" value="*"/> + </ownedAttribute> + </packagedElement> + </packagedElement> + + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_inventory" name="Inventory"> + <packagedElement xmi:type="uml:InstanceSpecification" xmi:id="_inst_p1" name="p1" classifier="_blk_pump"> + <slot xmi:type="uml:Slot" xmi:id="_slot_p1_mass" definingFeature="_prop_mass"> + <value xmi:type="uml:LiteralReal" xmi:id="_slot_p1_mass_v" value="12.5"/> + </slot> + <slot xmi:type="uml:Slot" xmi:id="_slot_p1_flow" definingFeature="_prop_flow"> + <value xmi:type="uml:LiteralReal" xmi:id="_slot_p1_flow_v" value="3.0"/> + </slot> + <slot xmi:type="uml:Slot" xmi:id="_slot_p1_label" definingFeature="_prop_label"> + <value xmi:type="uml:LiteralString" xmi:id="_slot_p1_label_v" value="primary"/> + </slot> + </packagedElement> + <packagedElement xmi:type="uml:InstanceSpecification" xmi:id="_inst_p2" name="p2" classifier="_blk_pump"> + <slot xmi:type="uml:Slot" xmi:id="_slot_p2_mass" definingFeature="_prop_mass"> + <value xmi:type="uml:LiteralReal" xmi:id="_slot_p2_mass_v" value="9.0"/> + </slot> + </packagedElement> + <packagedElement xmi:type="uml:InstanceSpecification" xmi:id="_inst_r1" name="r1" classifier="_blk_reserve"> + <slot xmi:type="uml:Slot" xmi:id="_slot_r1_mass" definingFeature="_prop_mass"> + <value xmi:type="uml:LiteralReal" xmi:id="_slot_r1_mass_v" value="14.0"/> + </slot> + </packagedElement> + <packagedElement xmi:type="uml:InstanceSpecification" xmi:id="_inst_v1" name="v1" classifier="_blk_valve"> + <slot xmi:type="uml:Slot" xmi:id="_slot_v1_mass" definingFeature="_prop_valve_mass"> + <value xmi:type="uml:LiteralReal" xmi:id="_slot_v1_mass_v" value="2.0"/> + </slot> + </packagedElement> + </packagedElement> + + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_spares" name="Spares"> + <packagedElement xmi:type="uml:InstanceSpecification" xmi:id="_inst_s1" name="s1" classifier="_blk_pump"> + <slot xmi:type="uml:Slot" xmi:id="_slot_s1_mass" definingFeature="_prop_mass"> + <value xmi:type="uml:LiteralReal" xmi:id="_slot_s1_mass_v" value="7.0"/> + </slot> + </packagedElement> + <packagedElement xmi:type="uml:InstanceSpecification" xmi:id="_inst_s2" name="s2" classifier="_blk_pump"/> + </packagedElement> + + <packagedElement xmi:type="uml:Package" xmi:id="_pkg_reqs" name="Requirements"> + <packagedElement xmi:type="uml:Class" xmi:id="_req_flow" name="FlowRequirement"> + <ownedComment xmi:type="uml:Comment" xmi:id="_cmt_req_flow" body="The pump keeps the flow up."/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_req_seal" name="SealRequirement"> + <ownedComment xmi:type="uml:Comment" xmi:id="_cmt_req_seal" body="Every valve seals when closed."/> + </packagedElement> + <packagedElement xmi:type="uml:Class" xmi:id="_req_mass" name="MassRequirement"/> + <packagedElement xmi:type="uml:Abstraction" xmi:id="_dep_satisfy_pump" client="_blk_pump" supplier="_req_flow"/> + <packagedElement xmi:type="uml:Abstraction" xmi:id="_dep_satisfy_valve" client="_blk_valve" supplier="_req_seal"/> + <packagedElement xmi:type="uml:Abstraction" xmi:id="_dep_derive" client="_req_mass" supplier="_req_flow"/> + </packagedElement> + </packagedElement> + + <xmi:Extension extender="Example UML Tool 1.0"> + <modelExtension> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_pumps" name="Pump Table" ownerOfDiagram="_pkg_inventory"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="SysML Instance Table" umlType="Class Diagram"> + <diagramContents> + <usedElements>_inst_p1</usedElements> + <usedElements>_inst_p2</usedElements> + <usedElements>_inst_r1</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_ledger" name="Pump Ledger" ownerOfDiagram="_pkg_inventory"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="SysML Instance Table" umlType="Class Diagram"> + <diagramContents> + <usedElements>_inst_p1</usedElements> + <usedElements>_inst_p2</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_reqs" name="Requirement Table" ownerOfDiagram="_pkg_reqs"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Requirement Table" umlType="Class Diagram"> + <diagramContents> + <usedElements>_req_flow</usedElements> + <usedElements>_req_seal</usedElements> + <usedElements>_req_mass</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_critical" name="Critical Elements" ownerOfDiagram="_pkg_plant"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Generic Table" umlType="Class Diagram"> + <diagramContents> + <usedElements>_blk_pump</usedElements> + <usedElements>_req_flow</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_matrix" name="Satisfaction Matrix" ownerOfDiagram="_pkg_reqs"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Dependency Matrix" umlType="Class Diagram"> + <diagramContents> + <usedElements>_req_flow</usedElements> + <usedElements>_req_seal</usedElements> + <usedElements>_blk_pump</usedElements> + <usedElements>_blk_valve</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_trace_matrix" name="Trace Matrix" ownerOfDiagram="_pkg_reqs"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Dependency Matrix" umlType="Class Diagram"> + <diagramContents> + <usedElements>_req_flow</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + <ownedDiagram xmi:type="uml:Diagram" xmi:id="_diag_map" name="Pump Requirement Map" ownerOfDiagram="_pkg_structure"> + <xmi:Extension extender="Example UML Tool 1.0"> + <diagramRepresentation> + <diagram:DiagramRepresentationObject type="Relation Map" umlType="Class Diagram"> + <diagramContents> + <usedElements>_blk_pump</usedElements> + <usedElements>_req_flow</usedElements> + </diagramContents> + </diagram:DiagramRepresentationObject> + </diagramRepresentation> + </xmi:Extension> + </ownedDiagram> + </modelExtension> + <stereotypesHREFS> + <stereotype name="sysml:Satisfy" stereotypeHREF="SysML Profile.mdzip#_st_satisfy"/> + <stereotype name="sysml:DeriveReqt" stereotypeHREF="SysML Profile.mdzip#_st_derive"/> + </stereotypesHREFS> + </xmi:Extension> + </uml:Model> + + <sysml:Block xmi:id="_st_blk_pump" base_Class="_blk_pump"/> + <sysml:Block xmi:id="_st_blk_reserve" base_Class="_blk_reserve"/> + <sysml:Block xmi:id="_st_blk_valve" base_Class="_blk_valve"/> + <sysml:Block xmi:id="_st_blk_station" base_Class="_blk_station"/> + <sysml:Requirement xmi:id="_st_req_flow" base_Class="_req_flow" Id="R-1" Text="The pump keeps the flow above the minimum."/> + <sysml:Requirement xmi:id="_st_req_seal" base_Class="_req_seal" Id="R-2" Text="Every valve seals when closed."/> + <sysml:Requirement xmi:id="_st_req_mass" base_Class="_req_mass" Id="R-3" Text="The pump mass stays under the limit."/> + <sysml:Satisfy xmi:id="_st_satisfy_pump" base_Abstraction="_dep_satisfy_pump"/> + <sysml:Satisfy xmi:id="_st_satisfy_valve" base_Abstraction="_dep_satisfy_valve"/> + <sysml:DeriveReqt xmi:id="_st_derive" base_Abstraction="_dep_derive"/> + <Plant_Profile:Critical xmi:id="_app_critical_pump" base_Class="_blk_pump" level="2"/> + <Plant_Profile:Critical xmi:id="_app_critical_req" base_Class="_req_flow" level="1"/> + + <MagicDraw_Profile:InstanceTable xmi:id="_tbl_pumps" base_Diagram="_diag_pumps" scope="_pkg_inventory" includeSubtypesOfRowTypes="true" showScopeAsRoot="false" displayMode="Compact tree" additionalElements="_inst_s1 _inst_s2"> + <classifiers xmi:idref="_blk_pump"/> + <columnIds>_NUMBER_</columnIds> + <columnIds>QPROP:Element:name</columnIds> + <columnIds>IColumn:_prop_mass</columnIds> + <columnIds>IColumn:_prop_flow</columnIds> + <columnIds>QPROP:Element:owner</columnIds> + <columnIds>QPROP:Element:classifier</columnIds> + <hideColumns>QPROP:Element:owner</hideColumns> + <sort>IColumn:_prop_mass^Desc</sort> + <columnWidth>35</columnWidth> + <columnWidth>200</columnWidth> + </MagicDraw_Profile:InstanceTable> + + <MagicDraw_Profile:InstanceTable xmi:id="_tbl_ledger" base_Diagram="_diag_ledger" scope="_pkg_inventory" includeSubtypesOfRowTypes="false" showScopeAsRoot="false"> + <classifiers xmi:idref="_blk_pump"/> + <columnIds>_NUMBER_</columnIds> + <columnIds>IColumn:_prop_label</columnIds> + <columnIds>QPROP:Element:name</columnIds> + <columnIds>IColumn:_prop_mass</columnIds> + <columnIds>QPROP:Element:qualifiedName</columnIds> + <sort>QPROP:Element:name^Asc</sort> + </MagicDraw_Profile:InstanceTable> + + <MagicDraw_Profile:DiagramTable xmi:id="_tbl_reqs" base_Diagram="_diag_reqs" takeWholeModelAsScope="true" includeSubtypesOfRowTypes="true"> + <rowElementType href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#SysML.Requirement"/> + <columnIds>_NUMBER_</columnIds> + <columnIds>QPROP:Element:name</columnIds> + <columnIds>QPROP:Element:documentation</columnIds> + <sort>QPROP:Element:name^Asc</sort> + </MagicDraw_Profile:DiagramTable> + + <MagicDraw_Profile:DiagramTable xmi:id="_tbl_critical" base_Diagram="_diag_critical" scope="_pkg_plant" includeSubtypesOfRowTypes="true"> + <rowElementType xmi:idref="_st_critical"/> + <columnIds>_NUMBER_</columnIds> + <columnIds>QPROP:Element:qualifiedName</columnIds> + <sort>-1^Asc</sort> + </MagicDraw_Profile:DiagramTable> + + <Dependency_Matrix_Profile:DependencyMatrix xmi:id="_mx_satisfy" base_Diagram="_diag_matrix" direction="Column to row" showElements="All" takeWholeModelAsScope="false"> + <dependencyCriteria><?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<callExpressionSpecification includeCustomTypes="false" includeSubtypes="true" xmlns="http://www.nomagic.com/schemas/MagicDraw/StructuredExpression/2013"> + <taggedValues> + <entry key="name"> + <value>Trace</value> + </entry> + </taggedValues> + <argument xsi:type="lookupExpressionSpecification" symbol="THIS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> + <expression xsi:type="dslRelationExpressionSpecification" stereotype="_st_satisfy" direction="REVERSED" includeCustomTypes="false" includeSubtypes="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> +</callExpressionSpecification> +</dependencyCriteria> + <dependencyCriteria><?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<callExpressionSpecification includeCustomTypes="false" includeSubtypes="true" xmlns="http://www.nomagic.com/schemas/MagicDraw/StructuredExpression/2013"> + <taggedValues> + <entry key="name"> + <value>Trace</value> + </entry> + </taggedValues> + <argument xsi:type="lookupExpressionSpecification" symbol="THIS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> + <expression xsi:type="dslRelationExpressionSpecification" stereotype="_st_derive" direction="REVERSED" includeCustomTypes="false" includeSubtypes="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> +</callExpressionSpecification> +</dependencyCriteria> + </Dependency_Matrix_Profile:DependencyMatrix> + <Dependency_Matrix_Profile:MatrixFilter xmi:id="_mf_satisfy" base_Diagram="_diag_matrix" rowScope="_pkg_reqs" columnScope="_pkg_structure" rowScopeDefined="true" columnScopeDefined="true" includeSubtypesOfRowTypes="true" includeSubtypesOfColumnTypes="true"> + <rowElementType href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#SysML.Requirement"/> + <columnElementType href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#SysML.Block"/> + </Dependency_Matrix_Profile:MatrixFilter> + + <Dependency_Matrix_Profile:DependencyMatrix xmi:id="_mx_trace" base_Diagram="_diag_trace_matrix" direction="Row to column" showElements="All" takeWholeModelAsScope="false"> + <dependencyCriteria><?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<callExpressionSpecification xmlns="http://www.nomagic.com/schemas/MagicDraw/StructuredExpression/2013"> + <taggedValues> + <entry key="name"> + <value>Owner chain</value> + </entry> + </taggedValues> + <argument xsi:type="lookupExpressionSpecification" symbol="THIS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> + <expression xsi:type="metaChainExpressionSpecification" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <chain type="metaclassProperty" metaclass="Element" property="owner"/> + <chain type="metaclassProperty" metaclass="Namespace" property="ownedMember"/> + </expression> +</callExpressionSpecification> +</dependencyCriteria> + </Dependency_Matrix_Profile:DependencyMatrix> + <Dependency_Matrix_Profile:MatrixFilter xmi:id="_mf_trace" base_Diagram="_diag_trace_matrix" rowScope="_pkg_reqs" columnScope="_pkg_reqs" rowScopeDefined="true" columnScopeDefined="true"> + <rowElementType href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#SysML.Requirement"/> + <columnElementType href="http://www.omg.org/spec/SysML/20181001/SysML.xmi#SysML.Requirement"/> + </Dependency_Matrix_Profile:MatrixFilter> + + <MagicDraw_Profile:RelationMap xmi:id="_map_pump" base_Diagram="_diag_map" depth="2" layout="Tree" contextElement="_blk_pump" includeSubtypes="true" showLegend="false"> + <elementTypes href="http://www.omg.org/spec/UML/20131001/UML.xmi#Class"/> + <relationCriterion><?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<callExpressionSpecification includeCustomTypes="false" includeSubtypes="true" xmlns="http://www.nomagic.com/schemas/MagicDraw/StructuredExpression/2013"> + <taggedValues> + <entry key="name"> + <value>Satisfy</value> + </entry> + </taggedValues> + <argument xsi:type="lookupExpressionSpecification" symbol="THIS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> + <expression xsi:type="dslRelationExpressionSpecification" stereotype="_st_satisfy" direction="DIRECT" includeCustomTypes="false" includeSubtypes="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> +</callExpressionSpecification> +</relationCriterion> + </MagicDraw_Profile:RelationMap> +</xmi:XMI> diff --git a/tests/migrate/testdata/xmi/type_modifiers.golden.report.txt b/tests/migrate/testdata/xmi/type_modifiers.golden.report.txt new file mode 100644 index 0000000000..30a29844f8 --- /dev/null +++ b/tests/migrate/testdata/xmi/type_modifiers.golden.report.txt @@ -0,0 +1,37 @@ +# SysML v1 to v2 migration report: type_modifiers.xmi +# exported by Example UML Tool +# migrated 26 element(s): 17 mapped, 9 approximated, 0 unmapped (2 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) + +## approximated (9) +«typeModifier» Property Signals::Filter::blank _blank -> Signals::Filter::blank (an empty «typeModifier» is kept as a comment: it says nothing of the type) +«typeModifier» Property Signals::Filter::grid _grid -> Signals::Filter::grid («typeModifier» [][] is kept as a comment: the type modifier [][] has no v2 form: a multiplicity has one dimension) +«typeModifier» Property Signals::Filter::history _history -> Signals::Filter::history («typeModifier» [] is kept as a comment: the type modifier [] has no v2 form: the declared multiplicity [0..*] is already a collection, and a collection of collections has no multiplicity) +«typeModifier» Property Signals::Filter::level _level -> Signals::Filter::level («typeModifier» * is kept as a comment: the type modifier * has no v2 form: only a part or item is held by reference, not an attribute) +«typeModifier» Property Signals::Filter::odd _odd -> Signals::Filter::odd («typeModifier» [2x] is kept as a comment: the type modifier [2x] is not one the migrator reads) +«typeModifier» Property Signals::Filter::rect _rect -> Signals::Filter::rect («typeModifier» [2*3] is kept as a comment: the type modifier [2*3] has no v2 form: a multiplicity has one dimension) +«typeModifier» Parameter Signals::Filter::smooth::handle _par_handle -> Signals::Filter::smooth::handle («typeModifier» * is kept as a comment: the type modifier * has no v2 form: a parameter is not held by reference) +«typeModifier» Parameter Signals::Filter::smooth::result _par_result -> Signals::Filter::smooth::result (the return parameter is written as an out parameter) +«typeModifier» Property Signals::Filter::stray _stray -> Signals::Filter::stray (multiplicity n..n is not a range of natural numbers and is not written; «typeModifier» [] is kept as a comment: the type modifier [] has no v2 form: the declared multiplicity n..n is not a range of natural numbers and is not written) + +## mapped (17) +Model Model _m (the root model's members are written at the top level) +Profile Shapes Profile _shapes -> 'Shapes Profile' +Stereotype Shapes Profile::typeModifier _st_shape_mod -> 'Shapes Profile'::typeModifier +Property Shapes Profile::typeModifier::typeModifier _st_shape_mod_tag -> 'Shapes Profile'::typeModifier::typeModifier +Package Signals _pkg -> Signals +«Block» Class Signals::Actuator _actuator -> Signals::Actuator +«Block» Class Signals::Filter _filter -> Signals::Filter +«typeModifier» Property Signals::Filter::count _count -> Signals::Filter::count +«typeModifier» Property Signals::Filter::drive _drive -> Signals::Filter::drive +«typeModifier» Property Signals::Filter::probe _probe -> Signals::Filter::probe +«typeModifier» Property Signals::Filter::row _row -> Signals::Filter::row +«typeModifier» Property Signals::Filter::samples _samples -> Signals::Filter::samples +Operation Signals::Filter::smooth _op_smooth -> Signals::Filter::smooth (its owner's usage smooth 2 performs it, as a call on an object does) +«typeModifier» Parameter Signals::Filter::smooth::values _par_values -> Signals::Filter::smooth::values +«typeModifier» Parameter Signals::Filter::smooth::window _par_window -> Signals::Filter::smooth::window +«typeModifier» Property Signals::Filter::taps _taps -> Signals::Filter::taps +«Block» Class Signals::Sensor _sensor -> Signals::Sensor + +## skipped (2) +Extension Shapes Profile::<Extension> _ext_shape_mod (an extension binds a stereotype to the metaclass it extends; v2 metadata applies to any element) +Property Shapes Profile::typeModifier::base_Element _st_shape_mod_base (an extension end names the metaclass the stereotype extends; v2 metadata applies to any element) diff --git a/tests/migrate/testdata/xmi/type_modifiers.golden.sysml b/tests/migrate/testdata/xmi/type_modifiers.golden.sysml new file mode 100644 index 0000000000..6f5c39c750 --- /dev/null +++ b/tests/migrate/testdata/xmi/type_modifiers.golden.sysml @@ -0,0 +1,51 @@ +package 'Shapes Profile' { + metadata def typeModifier { + attribute typeModifier : ScalarValues::String; + } +} +package Signals { + part def Actuator; + part def Sensor; + part def Filter { + attribute samples : ScalarValues::Real[0..*] ordered nonunique; + attribute row : ScalarValues::Real[3] ordered nonunique; + attribute grid : ScalarValues::Real { + /* applied stereotype «typeModifier»: typeModifier = [][] */ + } + attribute rect : ScalarValues::Real { + /* applied stereotype «typeModifier»: typeModifier = [2*3] */ + } + attribute history : ScalarValues::Real[0..*] { + /* applied stereotype «typeModifier»: typeModifier = [] */ + } + attribute stray : ScalarValues::Real { + /* applied stereotype «typeModifier»: typeModifier = [] */ + } + attribute taps : ScalarValues::Real[4] ordered nonunique; + attribute level : ScalarValues::Real { + /* applied stereotype «typeModifier»: typeModifier = * */ + } + attribute count : ScalarValues::Integer { + @'Shapes Profile'::typeModifier { + typeModifier = "[]"; + } + } + attribute blank : ScalarValues::Real { + /* applied stereotype «typeModifier»: typeModifier = */ + } + attribute odd : ScalarValues::Real { + /* applied stereotype «typeModifier»: typeModifier = [2x] */ + } + ref part drive : Actuator; + ref part probe : Sensor; + abstract action def smooth { + in values : ScalarValues::Real[0..*] ordered nonunique; + in window : ScalarValues::Real[4] ordered nonunique; + in handle : ScalarValues::Real { + /* applied stereotype «typeModifier»: typeModifier = * */ + } + out result : ScalarValues::Real[0..*] ordered nonunique; + } + action 'smooth 2' : smooth; + } +} diff --git a/tests/migrate/testdata/xmi/type_modifiers.xmi b/tests/migrate/testdata/xmi/type_modifiers.xmi new file mode 100644 index 0000000000..36a316a429 --- /dev/null +++ b/tests/migrate/testdata/xmi/type_modifiers.xmi @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xmi:XMI xmi:version="2.5.1" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" + xmlns:uml="http://www.omg.org/spec/UML/20161101" + xmlns:sysml="http://www.omg.org/spec/SysML/20181001/SysML" + xmlns:MagicDraw_Profile="http://www.omg.org/spec/UML/20131001/MagicDrawProfile" + xmlns:Shapes_Profile="http://www.example.org/schemas/Shapes_Profile.xmi"> + <xmi:Documentation exporter="Example UML Tool" exporterVersion="1.0"/> + <uml:Model xmi:type="uml:Model" xmi:id="_m" name="Model"> + <packagedElement xmi:type="uml:Profile" xmi:id="_shapes" name="Shapes Profile" URI="http://www.example.org/schemas/Shapes_Profile.xmi"> + <packagedElement xmi:type="uml:Stereotype" xmi:id="_st_shape_mod" name="typeModifier"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_st_shape_mod_base" name="base_Element" association="_ext_shape_mod"> + <type xmi:type="uml:Class" href="http://www.omg.org/spec/UML/20131001/UML.xmi#Element"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_st_shape_mod_tag" name="typeModifier"> + <type xmi:type="uml:PrimitiveType" href="http://www.omg.org/spec/UML/20131001/PrimitiveTypes.xmi#String"/> + </ownedAttribute> + </packagedElement> + <packagedElement xmi:type="uml:Extension" xmi:id="_ext_shape_mod" memberEnd="_st_shape_mod_base _ext_shape_mod_end"> + <ownedEnd xmi:type="uml:ExtensionEnd" xmi:id="_ext_shape_mod_end" name="extension_typeModifier" type="_st_shape_mod" aggregation="composite"/> + </packagedElement> + </packagedElement> + <packagedElement xmi:type="uml:Package" xmi:id="_pkg" name="Signals"> + <packagedElement xmi:type="uml:Class" xmi:id="_actuator" name="Actuator"/> + <packagedElement xmi:type="uml:Class" xmi:id="_sensor" name="Sensor"/> + <packagedElement xmi:type="uml:Class" xmi:id="_filter" name="Filter"> + <ownedAttribute xmi:type="uml:Property" xmi:id="_samples" name="samples"> + <type href="http://www.omg.org/spec/UML/20161101/PrimitiveTypes.xmi#Real"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_row" name="row"> + <type href="http://www.omg.org/spec/UML/20161101/PrimitiveTypes.xmi#Real"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_grid" name="grid"> + <type href="http://www.omg.org/spec/UML/20161101/PrimitiveTypes.xmi#Real"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_rect" name="rect"> + <type href="http://www.omg.org/spec/UML/20161101/PrimitiveTypes.xmi#Real"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_history" name="history"> + <type href="http://www.omg.org/spec/UML/20161101/PrimitiveTypes.xmi#Real"/> + <lowerValue xmi:type="uml:LiteralInteger" xmi:id="_history_lo" value="0"/> + <upperValue xmi:type="uml:LiteralUnlimitedNatural" xmi:id="_history_hi" value="*"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_stray" name="stray"> + <type href="http://www.omg.org/spec/UML/20161101/PrimitiveTypes.xmi#Real"/> + <lowerValue xmi:type="uml:LiteralString" xmi:id="_stray_lo" value="n"/> + <upperValue xmi:type="uml:LiteralString" xmi:id="_stray_hi" value="n"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_taps" name="taps"> + <type href="http://www.omg.org/spec/UML/20161101/PrimitiveTypes.xmi#Real"/> + <lowerValue xmi:type="uml:LiteralInteger" xmi:id="_taps_lo" value="1"/> + <upperValue xmi:type="uml:LiteralInteger" xmi:id="_taps_hi" value="1"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_level" name="level"> + <type href="http://www.omg.org/spec/UML/20161101/PrimitiveTypes.xmi#Real"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_count" name="count"> + <type href="http://www.omg.org/spec/UML/20161101/PrimitiveTypes.xmi#Integer"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_blank" name="blank"> + <type href="http://www.omg.org/spec/UML/20161101/PrimitiveTypes.xmi#Real"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_odd" name="odd"> + <type href="http://www.omg.org/spec/UML/20161101/PrimitiveTypes.xmi#Real"/> + </ownedAttribute> + <ownedAttribute xmi:type="uml:Property" xmi:id="_drive" name="drive" type="_actuator" aggregation="composite"/> + <ownedAttribute xmi:type="uml:Property" xmi:id="_probe" name="probe" type="_sensor" aggregation="composite"/> + <ownedOperation xmi:type="uml:Operation" xmi:id="_op_smooth" name="smooth"> + <ownedParameter xmi:type="uml:Parameter" xmi:id="_par_values" name="values" direction="in"> + <type href="http://www.omg.org/spec/UML/20161101/PrimitiveTypes.xmi#Real"/> + </ownedParameter> + <ownedParameter xmi:type="uml:Parameter" xmi:id="_par_window" name="window" direction="in"> + <type href="http://www.omg.org/spec/UML/20161101/PrimitiveTypes.xmi#Real"/> + </ownedParameter> + <ownedParameter xmi:type="uml:Parameter" xmi:id="_par_handle" name="handle" direction="in"> + <type href="http://www.omg.org/spec/UML/20161101/PrimitiveTypes.xmi#Real"/> + </ownedParameter> + <ownedParameter xmi:type="uml:Parameter" xmi:id="_par_result" name="result" direction="return"> + <type href="http://www.omg.org/spec/UML/20161101/PrimitiveTypes.xmi#Real"/> + </ownedParameter> + </ownedOperation> + </packagedElement> + </packagedElement> + </uml:Model> + <sysml:Block xmi:id="_a_actuator" base_Class="_actuator"/> + <sysml:Block xmi:id="_a_sensor" base_Class="_sensor"/> + <sysml:Block xmi:id="_a_filter" base_Class="_filter"/> + <MagicDraw_Profile:typeModifier xmi:id="_tm_samples" base_Element="_samples" typeModifier="[]"/> + <MagicDraw_Profile:typeModifier xmi:id="_tm_row" base_Element="_row" typeModifier="[3]"/> + <MagicDraw_Profile:typeModifier xmi:id="_tm_grid" base_Element="_grid" typeModifier="[][]"/> + <MagicDraw_Profile:typeModifier xmi:id="_tm_rect" base_Element="_rect" typeModifier="[2*3]"/> + <MagicDraw_Profile:typeModifier xmi:id="_tm_history" base_Element="_history" typeModifier="[]"/> + <MagicDraw_Profile:typeModifier xmi:id="_tm_stray" base_Element="_stray" typeModifier="[]"/> + <MagicDraw_Profile:typeModifier xmi:id="_tm_taps" base_Element="_taps" typeModifier="[4]"/> + <MagicDraw_Profile:typeModifier xmi:id="_tm_level" base_Element="_level" typeModifier="*"/> + <MagicDraw_Profile:typeModifier xmi:id="_tm_blank" base_Element="_blank" typeModifier=""/> + <MagicDraw_Profile:typeModifier xmi:id="_tm_odd" base_Element="_odd" typeModifier="[2x]"/> + <MagicDraw_Profile:typeModifier xmi:id="_tm_drive" base_Element="_drive" typeModifier="*"/> + <MagicDraw_Profile:typeModifier xmi:id="_tm_probe" base_Element="_probe" typeModifier="&"/> + <MagicDraw_Profile:typeModifier xmi:id="_tm_values" base_Element="_par_values" typeModifier="[]"/> + <MagicDraw_Profile:typeModifier xmi:id="_tm_window" base_Element="_par_window" typeModifier="[4]"/> + <MagicDraw_Profile:typeModifier xmi:id="_tm_handle" base_Element="_par_handle" typeModifier="*"/> + <MagicDraw_Profile:typeModifier xmi:id="_tm_result" base_Element="_par_result" typeModifier="[]"/> + <Shapes_Profile:typeModifier xmi:id="_tm_count" base_Element="_count" typeModifier="[]"/> +</xmi:XMI> diff --git a/tests/migrate/typemodifier_test.go b/tests/migrate/typemodifier_test.go new file mode 100644 index 0000000000..890e56816f --- /dev/null +++ b/tests/migrate/typemodifier_test.go @@ -0,0 +1,90 @@ +package migrate_test + +import ( + "strings" + "testing" + + "github.com/Open-MBEE/OpenSysML/internal/translate/migrate" +) + +// MagicDraw's «typeModifier» on a property or parameter writes its collection +// shape as a multiplicity: [] is [0..*], [n] is [n], both ordered nonunique. +func TestTypeModifierCollectionsBecomeMultiplicities(t *testing.T) { + r := migrateFixtureFile(t, "type_modifiers") + wantLine(t, r.Notation, "attribute samples : ScalarValues::Real[0..*] ordered nonunique;") + wantLine(t, r.Notation, "attribute row : ScalarValues::Real[3] ordered nonunique;") + wantLine(t, r.Notation, "attribute taps : ScalarValues::Real[4] ordered nonunique;") + wantLine(t, r.Notation, "in values : ScalarValues::Real[0..*] ordered nonunique;") + wantLine(t, r.Notation, "in window : ScalarValues::Real[4] ordered nonunique;") + wantLine(t, r.Notation, "out result : ScalarValues::Real[0..*] ordered nonunique;") + for _, id := range []string{"_samples", "_row", "_taps", "_par_values", "_par_window"} { + if es := entriesFor(r, id); len(es) != 1 || es[0].Verdict != migrate.Mapped || es[0].Note != "" { + t.Errorf("%s entries = %+v", id, es) + } + } + // A modifier the declaration writes is not repeated as a comment: the [] + // comments left are history's and stray's, whose [] is refused. + if n := strings.Count(string(r.Notation), "typeModifier = [] */"); n != 2 { + t.Errorf("%d comments of typeModifier = [], want 2", n) + } + wantNoLine(t, r.Notation, "typeModifier = [3]") + wantNoLine(t, r.Notation, "typeModifier = [4]") +} + +// * and & make a part or item a reference usage; on a value they have no form. +func TestTypeModifierPointersBecomeReferences(t *testing.T) { + r := migrateFixtureFile(t, "type_modifiers") + wantLine(t, r.Notation, "ref part drive : Actuator;") + wantLine(t, r.Notation, "ref part probe : Sensor;") + for _, id := range []string{"_drive", "_probe"} { + if es := entriesFor(r, id); len(es) != 1 || es[0].Verdict != migrate.Mapped { + t.Errorf("%s entries = %+v", id, es) + } + } + wantLine(t, r.Notation, "attribute level : ScalarValues::Real {") + wantLine(t, r.Notation, "/* applied stereotype «typeModifier»: typeModifier = * */") + wantNote(t, r, "_level", migrate.Approximated, "«typeModifier» * is kept as a comment: the type modifier * has no v2 form: only a part or item is held by reference, not an attribute") + wantLine(t, r.Notation, "in handle : ScalarValues::Real {") + wantNote(t, r, "_par_handle", migrate.Approximated, "«typeModifier» * is kept as a comment: the type modifier * has no v2 form: a parameter is not held by reference") +} + +// A shape with two dimensions, one over a declared collection or a malformed +// multiplicity, or one the migrator does not read stays a comment, and the +// report says why. +func TestTypeModifierRefusalsStayComments(t *testing.T) { + r := migrateFixtureFile(t, "type_modifiers") + wantLine(t, r.Notation, "attribute grid : ScalarValues::Real {") + wantLine(t, r.Notation, "/* applied stereotype «typeModifier»: typeModifier = [][] */") + wantNote(t, r, "_grid", migrate.Approximated, "«typeModifier» [][] is kept as a comment: the type modifier [][] has no v2 form: a multiplicity has one dimension") + wantLine(t, r.Notation, "/* applied stereotype «typeModifier»: typeModifier = [2*3] */") + wantNote(t, r, "_rect", migrate.Approximated, "«typeModifier» [2*3] is kept as a comment: the type modifier [2*3] has no v2 form: a multiplicity has one dimension") + wantLine(t, r.Notation, "attribute history : ScalarValues::Real[0..*] {") + wantNote(t, r, "_history", migrate.Approximated, "«typeModifier» [] is kept as a comment: the type modifier [] has no v2 form: the declared multiplicity [0..*] is already a collection, and a collection of collections has no multiplicity") + wantLine(t, r.Notation, "attribute stray : ScalarValues::Real {") + wantNote(t, r, "_stray", migrate.Approximated, "multiplicity n..n is not a range of natural numbers and is not written; «typeModifier» [] is kept as a comment: the type modifier [] has no v2 form: the declared multiplicity n..n is not a range of natural numbers and is not written") + wantNote(t, r, "_odd", migrate.Approximated, "«typeModifier» [2x] is kept as a comment: the type modifier [2x] is not one the migrator reads") + wantNote(t, r, "_blank", migrate.Approximated, "an empty «typeModifier» is kept as a comment: it says nothing of the type") + for _, id := range []string{"_grid", "_rect", "_history", "_stray", "_odd", "_blank", "_level", "_par_handle"} { + es := entriesFor(r, id) + if len(es) != 1 || es[0].Verdict != migrate.Approximated { + t.Errorf("%s entries = %+v", id, es) + } + if strings.Contains(es[0].Note, "profile the document does not define") { + t.Errorf("%s note repeats the comment verdict: %s", id, es[0].Note) + } + } +} + +// A user stereotype named typeModifier outside the tool's profile is model +// content: a metadata usage, read for no shape. +func TestTypeModifierHomonymIsNotTheToolMarker(t *testing.T) { + r := migrateFixtureFile(t, "type_modifiers") + wantLine(t, r.Notation, "metadata def typeModifier {") + wantLine(t, r.Notation, "attribute count : ScalarValues::Integer {") + wantLine(t, r.Notation, "@'Shapes Profile'::typeModifier {") + wantLine(t, r.Notation, "typeModifier = \"[]\";") + wantNoLine(t, r.Notation, "attribute count : ScalarValues::Integer[0..*]") + if es := entriesFor(r, "_count"); len(es) != 1 || es[0].Verdict != migrate.Mapped || es[0].Note != "" { + t.Errorf("_count entries = %+v", es) + } +} diff --git a/tests/parser/testdata/parse/expression_global_name.golden b/tests/parser/testdata/parse/expression_global_name.golden new file mode 100644 index 0000000000..700973571c --- /dev/null +++ b/tests/parser/testdata/parse/expression_global_name.golden @@ -0,0 +1,25 @@ +(RootNamespace + (Membership visibility="default" + (Package name="P" library=false standard=false + (Membership visibility="default" + (Package name="DocumentQueries" library=false standard=false)) + (Membership visibility="default" + (Definition kind="calc" abstract=false variation=false name="Q" + (Relationship kind="specializes" target=$::DocumentQueries::Query + (*ast.QualifiedName)) + (InvocationExpr type="$::DocumentQueries::Named" + (NamedArg name="qualifiedName" + (LiteralString value="\"P\""))))) + (Membership visibility="default" + (Definition kind="calc" abstract=false variation=false name="R" + (Relationship kind="specializes" target=$::DocumentQueries::Query + (*ast.QualifiedName)) + (Usage kind="attribute" name="result" ref=false direction="out" composite=false derived=false ordered=false nonunique=false + (InvocationExpr type="$::DocumentQueries::Named" + (NamedArg name="qualifiedName" + (LiteralString value="\"P\"")))))) + (Membership visibility="default" + (Usage kind="attribute" name="x" ref=false direction="none" composite=false derived=false ordered=false nonunique=false + (OperatorExpr operator="+" + (FeatureReference name="$::P::y") + (LiteralInteger value="1"))))))) \ No newline at end of file diff --git a/tests/parser/testdata/parse/expression_global_name.sysml b/tests/parser/testdata/parse/expression_global_name.sysml new file mode 100644 index 0000000000..47a03d5575 --- /dev/null +++ b/tests/parser/testdata/parse/expression_global_name.sysml @@ -0,0 +1,10 @@ +package P { + package DocumentQueries {} + calc def Q :> $::DocumentQueries::Query { + $::DocumentQueries::Named(qualifiedName = "P") + } + calc def R :> $::DocumentQueries::Query { + return result = $::DocumentQueries::Named(qualifiedName = "P"); + } + attribute x = $::P::y + 1; +}