= Metanorma LutaML plugin (metanorma-plugin-lutaml) image:https://github.com/metanorma/metanorma-plugin-lutaml/workflows/rake/badge.svg["Build Status", link="https://github.com/metanorma/metanorma-plugin-lutaml/actions?workflow=rake"] == Purpose LutaML is a data model accessor that supports various data model formats, including: * EXPRESS (`*.exp` files) * OMG UML in XMI format (`*.xmi` files) This plugin allows you to access LutaML models from within a Metanorma document. == Installation [source,console] ---- $ gem install metanorma-plugin-lutaml ---- == Usage with EXPRESS === General LutaML supports accessing EXPRESS models via the https://github.com/lutaml/expressir[Expressir] parser. === Usage of the `lutaml` command Given an `example.exp` EXPRESS file with content: [source,exp] ---- SCHEMA test_schema 'test'; (* Need select elements for measure_value *) REFERENCE FROM measure_schema (measure_value); TYPE my_type1 = EXTENSIBLE SELECT; END_TYPE; TYPE my_type2 = EXTENSIBLE ENUMERATION; END_TYPE; TYPE my_type3 = EXTENSIBLE ENUMERATION; END_TYPE; TYPE my_type4 = EXTENSIBLE ENUMERATION; END_TYPE; TYPE my_type5 = EXTENSIBLE ENUMERATION; END_TYPE; END_SCHEMA; ---- And the `lutaml` block: [source,adoc] ----- [lutaml,example.exp,my_context] ---- {% for schema in my_context.schemas %} == {{schema.id}} {% for entity in schema.entities %} === {{entity.id}} {% endfor %} {% endfor %} ---- ----- Where: * content within the block is called the "`template`"; * `{example.exp}` is the location of the EXPRESS schema file (`*.exp`) that contains data to be loaded. Location of the file is computed relative to the source directory that `[lutaml]` is used (e.g., if `[lutaml,example.exp,my_context]` is invoked in an `.adoc` file located at `/foo/bar/doc.adoc`, the data file is expected to be found at `/foo/bar/example.exp`); * `{my_context}` is the name where the EXPRESS Repository read from the `.exp` file can be accessed with. ** The `context` object is a serialized `Expressir::Model::Repository` object with all variable names available. See https://github.com/lutaml/expressir[Expressir] docs for reference. `{my_context}` has `schemas` method to access Expressir https://github.com/lutaml/expressir/blob/master/lib/expressir/model/schema.rb[schemas] Will produce this output: ____ == test_schema === my_type1 === my_type2 === my_type3 === my_type4 === my_type5 ____ This command also supports `.lutaml` files. Instead of using the direct path to the file one can use `lutaml-express-index` document attribute to supply directory with express files or YAML index file to parse as well as the cache file location. The syntax is as follows: [source,adoc] ----- :lutaml-express-index: my_custom_name; dir_or_index_path[; cache=cache_path] ----- Where: * `my_custom_name` - is name of the parsed EXPRESS files context for the later use with lutaml macro * `dir_or_index_path` - location of directory with EXPRESS files or path to the YAML index file to parse * `cache_path` - optional, location of the Expressir cache file to use Example of usage: [source,adoc] ----- = Document title Author :lutaml-express-index: index_name; /path/to/express_files; cache=/path/to/cache_file.yaml [lutaml,index_name,context] ---- {% for schema in context.schemas %} == {{schema.id}} {% endfor %} ---- ----- === Using `config.yaml` This functionality allows `[lutaml_express]` blocks to load a full set of EXPRESS schemas in one index, and then provide a select ("filter") option per-block via a separate YAML file. [source,adoc] ---- :lutaml-express-index: all_schemas; ../schemas_all.yaml; [lutaml_express,all_schemas,context,leveloffset=+1,config_yaml=schemas.yaml] --- {% assign selected = context.schemas | where: "selected" %} {% render "templates/resources/schema" for selected as schema %} --- ---- Where `schemas_all.yml` provides all schemas: [source,yaml] ---- --- schemas: action_schema: path: "../../schemas/resources/action_schema/action_schema.exp" application_context_schema: path: "../../schemas/resources/application_context_schema/application_context_schema.exp" approval_schema: path: "../../schemas/resources/approval_schema/approval_schema.exp" ... ---- And `schemas.yaml` only selects 2 schemas: [source,yaml] ---- --- schemas: action_schema: anything: ... application_context_schema: anything: ... ---- The resulting block adds the `select` attribute to every schema of the the "context" object, which allows you to filter those out for complex operations via Liquid: [source,liquid] ---- [lutaml_express,schemas_1,repo,leveloffset=+1,config_yaml=select.yaml] --- {% assign selected = repo.schemas | where: "selected" %} ... do things with `selected` ... ---- NOTE: This functionality is used in the ISO 10303 SRL to load the full schema set at once but only render the selected schemas in individual documents. == Usage with UML === Rendering a LutaML view: `lutaml_diagram` This command allows to quickly render a LutaML view as an image file. Given a file `example.lutaml` file with content: [source,java] ---- diagram MyView { title "my diagram" enum AddressClassProfile { imlicistAttributeProfile: CharacterString [0..1] { definition this is multiline with `asciidoc` end definition } } class AttributeProfile { +addressClassProfile: CharacterString [0..1] imlicistAttributeProfile: CharacterString [0..1] { definition this is attribute definition } } } ---- The `lutaml_diagram` command will add the image to the document. [source,adoc] ----- lutaml_diagram::example.lutaml[] ----- The `lutaml_diagram` command can also be used to denote a block with an embedded LutaML view. For example: [source,java] ---- [lutaml_diagram] .... diagram MyView { title "my diagram" enum AddressClassProfile { imlicistAttributeProfile: CharacterString [0..1] { definition { This is multiline AsciiDoc content. } } } class AttributeProfile { +addressClassProfile: CharacterString [0..1] imlicistAttributeProfile: CharacterString [0..1] { definition this is attribute definition } } } .... ---- === Rendering a LutaML Enterprise Architect diagram from XMI: `lutaml_ea_diagram` This command allows to quickly render a LutaML diagram as an image file by specifying the name of diagram. [source,adoc] ----- lutaml_ea_diagram::[name="name_of_diagram",base_path="/path/to/xmi-images",format="png"] ----- The code will search the diagram with name `name_of_diagram` and then render it as: [source,adoc] ----- [[figure-{{ diagram.xmi_id }}]] .{{ diagram.name }} image::{{ image_base_path }}/{{ diagram.xmi_id }}.{{ format | default: 'png' }}[] ----- === Rendering a LutaML GML Dictionary: `lutaml_gml_dictionary` This command allows to render a LutaML GML Dictionary by using Liquid Drop. GmlDictionaryDrop has the following attributes: * name * file_name * dictionary_entry Each `dictionary_entry` has the following attributes: * name * description [source,adoc] ----- lutaml_gml_dictionary::/path/to/dictionary.xml[template="/path/to/template.liquid",context=dict] ----- The command accepts the options listed below: * `/path/to/dictionary.xml` specifies the path of xml file of the GML Dictionary. * `template="/path/to/template.liquid"` specifies the liquid template. For example, you can create a liquid template and link it by `template`. * `context=dict` define the context in the template. [source,adoc] ----- [cols="3a,22a"] |=== | Name | {{ dict.file_name }} h| Code h| Description {% for entry in dict.dictionary_entry %} | {{ entry.name }} | {{ entry.description }} {% endfor %} |=== [.source] <> ----- In spite of specifying the path of the template, you can also define an inline template within a block by `[lutaml_gml_dictionary,"/path/to/dictionary.xml",context=dict]`. [source,adoc] ----- [lutaml_gml_dictionary,"/path/to/dictionary.xml",context=dict] -- {% capture link %}https://www.test.com/{{ dict.file_name }}{% endcapture %} [cols="3a,22a"] |=== | File Name | {{ dict.file_name }} h| URL | {{ link }} h| Help | Description {% for entry in dict.dictionary_entry %} | {{ entry.name }} | {{ entry.description }} {% endfor %} |=== [.source] <> -- ----- === Rendering a LutaML table of a class: `lutaml_klass_table` This command allows to render a LutaML table of a class by using Liquid Drop. The table will show: * Class Name * Class Definition * Inherited Properties * Self-defined Properties * Properties Inherited from Association * Properties Defined in Association [source,adoc] ----- lutaml_klass_table::/path/to/example.xmi[name="NameOfClass",template="/path/to/templates/_my_klass_table.liquid"] ----- The command accepts the options listed below: * `/path/to/example.xmi` specifies the path of xmi file. * `name="NameOfClass"` specifies the name of the Class. * `template="/path/to/templates/_my_klass_table.liquid"` specifies the path of the liquid template. (Optional) By default, it will look for the template `_klass_table.liquid` defined in `lib/metanorma/plugin/lutaml/templates`. This template can be customized by changing the template path in the `template` option. === Generating UML class and attributes: `lutaml_uml_class` This command allows rendering a definition clause for a UML class. Given `example.lutaml` with this content: [source,java] ---- class Register { definition { A register of information. } identifier: String[1] { definition { Unique identifier of the register. } } concepts: Concept[0..*] { definition { Concepts. } } } ---- The command: [source,adoc] ---- [lutaml_uml_class,views/Register_Register.lutaml,Register] ---- Will produce this output: ____ == Register A register of information. === Attributes ==== identifier Unique identifier of the register. .Specification of `Register.identifier` |=== h|Value type and multiplicity | `String [1]` |=== ==== concepts Concepts. .Specification of `Register.concepts` |=== h|Value type and multiplicity | `Concepts [0..*]` |=== ____ The command accepts two options: * `skip_headers=true` (or just `skip_headers`). The initial heading (the UML class name) will not be generated. This is useful if additional content is to be supplied to the clause, such as diagrams that are defined outside the UML model. * `depth={n}`. (default: `2`) This determines the depth of the generated headings. A depth of `2` means the initial heading will have 2 equal signs, and so forth. The heading depth of the attributes are in relation to the initial depth, so a depth of `2` will have the "Attributes" section at depth `3`. === `lutaml_uml_attributes_table` This command allows rendering definition tables for a UML model. Given `example.lutaml` file with the content: [source,java] ---- diagram MyView { title "my diagram" enum AddressClassProfile { imlicistAttributeProfile: CharacterString [0..1] { definition this is multiline with `ascidoc` end definition } } class AttributeProfile { +addressClassProfile: CharacterString [0..1] imlicistAttributeProfile: CharacterString [0..1] { definition this is attribute definition } } } ---- And the `lutaml_uml_attributes_table` macro: [source,adoc] ----- [lutaml_uml_attributes_table, example.lutaml, AttributeProfile] ----- Will produce this output: ____ === AttributeProfile .AttributeProfile attributes |=== |Name |Definition |Mandatory/ Optional/ Conditional |Max Occur |Data Type |addressClassProfile |TODO: enum's definition |M |1 | `CharacterString` |imlicistAttributeProfile |this is attribute definition with multiply lines |M |1 | `CharacterString` |=== ____ In case of "enumeration" (AddressClassProfile) entity: [source,adoc] ----- [lutaml_uml_attributes_table, example.lutaml, AddressClassProfile] ----- Will produce this output: ____ === AddressClassProfile .AddressClassProfile values |=== |Name |Definition |imlicistAttributeProfile |this is multiline with `asciidoc` |=== ____ === Usage of `lutaml_uml_datamodel_description` macro This command allows to quickly render data model packages and its dependent objects for supplied XMI file. Given an Enterprise Architect `example.xmi` file with 2 packages: * 'Another' * 'CityGML' The `lutaml_uml_datamodel_description` macro can be used: [source,adoc] ----- [lutaml_uml_datamodel_description, path/to/example.xmi] -- [.before] .... my text .... [.diagram_include_block, base_path="requirements/", format="emf"] .... Diagram text .... [.include_block, package="Another", base_path="spec/fixtures"] .... my text .... [.include_block, base_path="spec/fixtures"] .... my text .... [.before, package="Another"] .... text before Another package .... [.after, package="Another"] .... text after Another package .... [.after, package="CityGML"] .... text after CityGML package .... [.after] .... footer text .... -- -- ----- Where: * `path/to/example.xmi` - required, path to the XMI file to render * `[.before]` - block text that adds additional text before the rendered output, can be used only once, additional occurrences of macro will overwrite text, not that `literal` block style must be used in there(eg `....`) * `[.after]` - block text that adds additional text after the rendered output, can be used only once, additional occurrences of macro will overwrite text * `[.after, package="Another"]` - block text to be inserted before(after in case of `.before` name) the package * `[.package_text, position="after", package="Another"]` - include custom adoc code into package rendered body, `position` is a a required attribute which tells where to insert the code. * `[.package_text, package="Another"]` - same as above, but include block will be included only for supplied package name * `[.diagram_include_block]` - block text to automatically include diagram images. Attribute `base_path` is a required attribute to supply path prefix where to look for a diagram image. `format` is an optional attribute that tells what file extension to use when including diagram file. + The logic is as follows: [source,adoc] ----- {% for diagram in package.diagrams %} [[figure-{{ diagram.xmi_id }}]] .{{ diagram.name }} image::{{ image_base_path }}/{{ diagram.xmi_id }}.{{ format | default: 'png' }}[] {% if diagram.definition %} {{ diagram.definition | html2adoc }} {% endif %} {% endfor %} ----- For instance, the script will take package diagrams supplied in the XMI file and will try to include `image` with the name equal to diagram' xmi_id attribute plus `.png`. Also one can add any text to the macro text, it will be added as paragraph before each image include. * `[.diagram_include_block, package="Another"]` - same as above, but diagram will be included only for supplied package name * `[.include_block, base_path="spec/fixtures"]` - macro to include files (`*.adoc` or `*.liquid`) for each package name. Attribute `base_path` is a required attribute to supply path prefix where to look for file to include. Macro will look for a file called `base_path` + `/` `_package_name`(downcase, replace : -> '', ' ' -> '_') + `.adoc`[`.liquid`], eg for package 'My Package name' and `base_path` eq to `my/path`, macro will look for the following file path: `my/path/_my_package_name.adoc`. * `[.include_block, package="Another", base_path="spec/fixtures"]` - same as above, but include block will be included only for supplied package name NOTE: .after, .before, package_text and include_block macroses all can be used with additional option - `liquid`, if this option is supplied then the code inside block will be interpolated in liquid context There are two other commands that are used to refer to LutaML generated document elements: * `lutaml_figure`. Provides a reference anchor to a figure defined in the XMI file, using its XMI ID for reference. * `lutaml_table`. Provides a reference anchor to the definition tables of a particular package, class, enumeration or data type object in the XMI. The syntax is as follows: [source,adoc] ----- // For lutaml_figure This is lutaml_figure::[package="Wrapper root package", name="Fig B1 Full model"] figure // For lutaml_table This is lutaml_table::[package="Wrapper root package"] package This is lutaml_table::[package="Wrapper root package", class="my name"] class This is lutaml_table::[package="Wrapper root package", enum="my name"] enumeration This is lutaml_table::[package="Wrapper root package", data_type="my name"] data type ----- This code will be transformed into `<>` and will point to diagram figure. One can only use this macro when document rendered `lutaml_uml_datamodel_description` macro as it needs diagram lookup table in order to reference package diagram. Will produce this output: [source,adoc] ----- my text == CityGML package === CityGML overview Diagram text [[figure-EAID_ACBB5EE3_3428_40f5_9C7C_E41923419F29]] .CityGML Package Diagram image::requirements/EAID_ACBB5EE3_3428_40f5_9C7C_E41923419F29.png[] BuildingFurnitureFunctionValue is a code list that enumerates the different purposes of a BuildingFurniture. [[figure-EAID_938AE961_1C57_4052_B964_997D1894A58D]] .Use of ISO and OASIS standards in CityGML image::requirements/EAID_938AE961_1C57_4052_B964_997D1894A58D.png[] The CityGML package is organized into 2 packages with 1 modules: . Another package . CityTML package my text Content for CityGML package ==== Defining tables .<> -- Elements of “Another::AbstractAtomicTimeseries” (class) [[section-EAPK_9C96A88B_E98B_490b_8A9C_24AEDAC64293]] .Elements of “Another::AbstractAtomicTimeseries” (class) [width="100%",cols="a,a,a,a,a,a,a,a"] |=== h|Name: 7+| AbstractAtomicTimeseries h|Definition: 7+| h|Stereotype: 7+| interface h|Abstract: 7+| h|Associations: 7+| (none) .4+h|Public attributes: | _Name_ 2+| _Definition_ | _Derived_ | _Obligation_ | _Maximum occurrence_ | _Data type_ | adeOfAbstractAtomicTimeseries 2+| | | C | * | ADEOfAbstractAtomicTimeseries | observationProperty 2+| | | M | 1 | CharacterString | uom 2+| | | C | 1 | CharacterString h|Constraints: 7+| (none) |=== === Additional Information text after CityGML package ----- In addition to the XMI file, this macro also supports a YAML configuration file that specifies: * What packages to include in the render; * What render style is desired; * Location of the root package (which package should the iterative process start at). The format for using the YAML configuration file: [source,yaml] ---- --- packages: # includes these packages - "Package *" - two* - three # skips these packages - skip: four render_style: entity_list | data_dictionary | default section_depth: 2 ---- Where: * `packages` - required, root element with the list of strings or objects * `Package *` - pattern matching, specifies lookup condition for packages to render. + NOTE: In this example, it is equal to the following regular expression: `/^Package.*$/` * `skip: four` - object with package name to skip * `render_style` - what template to use to render packages, can be one of: ** `entity_list` ** `data_dictionary`; or ** `default` * `section_depth` - what package to use as root package for render. e.g., a `section_depth` equal to `2` tells the processor to use the first nested package of the first root packages in XMI file. + EXAMPLE: If the XMI file has this package structure, and we have `section_depth` equal to 2, root package will be `one-1`. + [source,json] ---- [ { name: 'One', packages: [{ name: 'one-1' }, { name: 'one-2' }] }, { name: 'Two', packages: [{ name: 'two-1' }, { name: 'two-2' }] } ] ---- Usage with macro: [source,adoc] -- [lutaml_uml_datamodel_description, path/to/example.xmi, path/to/config.yml] ---- [.diagram_include_block, base_path="models/Images", format="png"] ... ... ---- -- The processor will read the supplied YAML config file (`path/to/config.yml`), and iterate through packages according to the order supplied in the file. All packages that matches `skip` in the YAML config file will be skipped during render. === Usage of `lutaml_ea_xmi` macro This command is a replacement for `lutaml_uml_datamodel_description` to perform the same functionalities of `lutaml_uml_datamodel_description`, which is to render data model packages and its dependent objects for supplied XMI file, by using Liquid Drop. The performance of `lutaml_ea_xmi` can be improved by 10~20 times. (Tested with a 10.6MB XMI file with 120000+ lines) To use this macro, you only need to replace `lutaml_uml_datamodel_description` by `lutaml_ea_xmi`. Replace: [source,adoc] ----- [lutaml_uml_datamodel_description, path/to/example.xmi] ... ----- By: [source,adoc] ----- [lutaml_ea_xmi, path/to/example.xmi] ... ----- == Documentation Please refer to https://www.metanorma.org.