metanorma-plugin-lutaml 0.7.39 → 0.7.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad20f1059d6fb886f3b8f8e209a5eb2053bd84cf197b4df0a6f28c32ceb05b76
4
- data.tar.gz: ea0e8cdfdc56ada195921fbefff8256f67e702dba4685a001ba22051031bfc74
3
+ metadata.gz: 4ce6ee99ec2b2d8f9d9457bc9dfec664df395b369cb506bfd2654ef197ce1454
4
+ data.tar.gz: 8f0d7030ae6087cc263bce330516a5125c6598bef022bdaee98d5169c8c95880
5
5
  SHA512:
6
- metadata.gz: a72753313c88e6a7bc1388c764fe2f315faf4ff7447690dd5d6a8851f5fe15285d1b53629ff6c79db3ba53ec20e7cdb9d05242e1ef2b5e9fe7b6e69076cedad0
7
- data.tar.gz: 98fbf0c36b867aae35acf710853ea74506bfc7835e8ed4368e9acedc84fe9f622f2cc44aae66fabc75bd49c9242487027d94239cec948972cf0e6117941e1ac2
6
+ metadata.gz: 93a97b590a75c6e74e2bb36822b50813cbc9329a5f183e4117fd79a4e063a9d54cc3c4334886844fb87172fb653c137ffa24df793fa384851e652ff8f1d414fa
7
+ data.tar.gz: d35c301506861ec62b1bbb18c59a99cf2c5d011dec4cb4f878387a96543227b7bb0ee5ee77b1a7d3751a1c3f6c68afe2272cb5b2219ec957f42c429ed3bcad6e
data/.gitignore CHANGED
@@ -2,6 +2,9 @@ Gemfile.lock
2
2
  .rspec_status
3
3
  spec/assets/lutaml
4
4
  test.err
5
+ test.err.html
6
+ TODO.cleanup/
7
+ pkg/
5
8
 
6
9
  .rubocop-https--*
7
10
  .DS_Store
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-05-12 14:53:16 UTC using RuboCop version 1.86.1.
3
+ # on 2026-05-13 23:15:25 UTC using RuboCop version 1.86.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -12,75 +12,67 @@ Gemspec/RequireMFA:
12
12
  Exclude:
13
13
  - 'metanorma-plugin-lutaml.gemspec'
14
14
 
15
- # Offense count: 1
15
+ # Offense count: 4
16
16
  # This cop supports safe autocorrection (--autocorrect).
17
17
  # Configuration parameters: EnforcedStyle, IndentationWidth.
18
18
  # SupportedStyles: with_first_argument, with_fixed_indentation
19
19
  Layout/ArgumentAlignment:
20
20
  Exclude:
21
- - 'lib/metanorma/plugin/lutaml/lutaml_klass_table_block_macro.rb'
21
+ - 'spec/regen_expected.rb'
22
+ - 'spec/regen_expected_spec.rb'
22
23
 
23
24
  # Offense count: 1
24
25
  # This cop supports safe autocorrection (--autocorrect).
25
- Layout/ClosingParenthesisIndentation:
26
+ # Configuration parameters: IndentationWidth.
27
+ Layout/AssignmentIndentation:
26
28
  Exclude:
27
- - 'lib/metanorma/plugin/lutaml/lutaml_ea_xmi_base.rb'
29
+ - 'lib/metanorma/plugin/lutaml/base_preprocessor.rb'
28
30
 
29
- # Offense count: 1
31
+ # Offense count: 2
30
32
  # This cop supports safe autocorrection (--autocorrect).
31
33
  Layout/EmptyLines:
32
34
  Exclude:
33
- - 'lib/metanorma/plugin/lutaml/lutaml_ea_xmi_base.rb'
34
-
35
- # Offense count: 1
36
- # This cop supports safe autocorrection (--autocorrect).
37
- # Configuration parameters: EnforcedStyle, IndentationWidth.
38
- # SupportedStyles: consistent, consistent_relative_to_receiver, special_for_inner_method_call, special_for_inner_method_call_in_parentheses
39
- Layout/FirstArgumentIndentation:
40
- Exclude:
41
- - 'lib/metanorma/plugin/lutaml/lutaml_ea_xmi_base.rb'
35
+ - 'spec/regen_expected.rb'
42
36
 
43
- # Offense count: 9
37
+ # Offense count: 12
44
38
  # This cop supports safe autocorrection (--autocorrect).
45
39
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
46
40
  # URISchemes: http, https
47
41
  Layout/LineLength:
48
42
  Exclude:
49
43
  - 'Rakefile'
44
+ - 'lib/metanorma/plugin/lutaml/base_preprocessor.rb'
50
45
  - 'lib/metanorma/plugin/lutaml/lutaml_ea_xmi_base.rb'
51
- - 'lib/metanorma/plugin/lutaml/lutaml_klass_table_block_macro.rb'
52
46
  - 'lib/metanorma/plugin/lutaml/utils.rb'
53
47
  - 'spec/metanorma/plugin/lutaml/lutaml_uml_datamodel_description_preprocessor_spec.rb'
48
+ - 'spec/metanorma/plugin/lutaml/lutaml_xsd_preprocessor_spec.rb'
49
+ - 'spec/regen_expected.rb'
50
+ - 'spec/regen_expected_spec.rb'
54
51
  - 'spec/support/shared_examples/structured_data_2_text_preprocessor.rb'
55
52
 
56
- # Offense count: 1
53
+ # Offense count: 2
57
54
  # This cop supports safe autocorrection (--autocorrect).
58
55
  # Configuration parameters: EnforcedStyle.
59
- # SupportedStyles: symmetrical, new_line, same_line
60
- Layout/MultilineMethodCallBraceLayout:
56
+ # SupportedStyles: final_newline, final_blank_line
57
+ Layout/TrailingEmptyLines:
61
58
  Exclude:
62
- - 'lib/metanorma/plugin/lutaml/lutaml_ea_xmi_base.rb'
59
+ - 'Gemfile'
60
+ - 'spec/regen_expected.rb'
63
61
 
64
- # Offense count: 3
62
+ # Offense count: 5
65
63
  # This cop supports safe autocorrection (--autocorrect).
66
64
  # Configuration parameters: AllowInHeredoc.
67
65
  Layout/TrailingWhitespace:
68
66
  Exclude:
69
- - 'lib/metanorma/plugin/lutaml/lutaml_ea_xmi_base.rb'
70
- - 'lib/metanorma/plugin/lutaml/lutaml_klass_table_block_macro.rb'
67
+ - 'lib/metanorma/plugin/lutaml/base_preprocessor.rb'
68
+ - 'spec/regen_expected.rb'
69
+ - 'spec/regen_expected_spec.rb'
71
70
 
72
- # Offense count: 1
73
- # This cop supports safe autocorrection (--autocorrect).
74
- # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions.
75
- # NotImplementedExceptions: NotImplementedError
76
- Lint/UnusedMethodArgument:
77
- Exclude:
78
- - 'lib/metanorma/plugin/lutaml/lutaml_ea_xmi_base.rb'
79
-
80
- # Offense count: 1
71
+ # Offense count: 2
81
72
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
82
73
  Metrics/AbcSize:
83
74
  Exclude:
75
+ - 'lib/metanorma/plugin/lutaml/lutaml_xsd_preprocessor.rb'
84
76
  - 'lib/metanorma/plugin/lutaml/utils.rb'
85
77
 
86
78
  # Offense count: 2
@@ -90,7 +82,7 @@ Metrics/CyclomaticComplexity:
90
82
  - 'lib/metanorma/plugin/lutaml/lutaml_ea_xmi_base.rb'
91
83
  - 'lib/metanorma/plugin/lutaml/utils.rb'
92
84
 
93
- # Offense count: 3
85
+ # Offense count: 7
94
86
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
95
87
  Metrics/MethodLength:
96
88
  Max: 19
@@ -118,12 +110,6 @@ Naming/PredicateMethod:
118
110
  - 'lib/metanorma/plugin/lutaml/asciidoctor/preprocessor.rb'
119
111
  - 'lib/metanorma/plugin/lutaml/liquid/custom_filters/file_exist.rb'
120
112
 
121
- # Offense count: 1
122
- # This cop supports unsafe autocorrection (--autocorrect-all).
123
- Performance/AncestorsInclude:
124
- Exclude:
125
- - 'lib/metanorma/plugin/lutaml/utils.rb'
126
-
127
113
  # Offense count: 86
128
114
  # Configuration parameters: Prefixes, AllowedPatterns.
129
115
  # Prefixes: when, with, without
@@ -141,10 +127,16 @@ RSpec/ContextWording:
141
127
  - 'spec/metanorma/plugin/lutaml/macros_data2text_spec.rb'
142
128
  - 'spec/support/shared_examples/structured_data_2_text_preprocessor.rb'
143
129
 
144
- # Offense count: 37
130
+ # Offense count: 1
131
+ # Configuration parameters: IgnoredMetadata.
132
+ RSpec/DescribeClass:
133
+ Exclude:
134
+ - 'spec/regen_expected_spec.rb'
135
+
136
+ # Offense count: 40
145
137
  # Configuration parameters: CountAsOne.
146
138
  RSpec/ExampleLength:
147
- Max: 26
139
+ Max: 38
148
140
 
149
141
  # Offense count: 13
150
142
  # Configuration parameters: Max, AllowedIdentifiers, AllowedPatterns.
@@ -169,11 +161,11 @@ RSpec/LeakyLocalVariable:
169
161
  - 'spec/metanorma/plugin/lutaml/lutaml_uml_datamodel_description_preprocessor_spec.rb'
170
162
  - 'spec/metanorma/plugin/lutaml/lutaml_xmi_index_spec.rb'
171
163
 
172
- # Offense count: 14
164
+ # Offense count: 16
173
165
  RSpec/MultipleExpectations:
174
166
  Max: 6
175
167
 
176
- # Offense count: 9
168
+ # Offense count: 15
177
169
  # Configuration parameters: AllowSubject.
178
170
  RSpec/MultipleMemoizedHelpers:
179
171
  Max: 8
@@ -191,11 +183,17 @@ RSpec/NamedSubject:
191
183
  - 'spec/metanorma/plugin/lutaml/lutaml_xmi_index_spec.rb'
192
184
  - 'spec/metanorma/plugin/lutaml/source_extractor_spec.rb'
193
185
 
194
- # Offense count: 117
186
+ # Offense count: 124
195
187
  # Configuration parameters: AllowedGroups.
196
188
  RSpec/NestedGroups:
197
189
  Max: 7
198
190
 
191
+ # Offense count: 3
192
+ # This cop supports unsafe autocorrection (--autocorrect-all).
193
+ RSpec/Output:
194
+ Exclude:
195
+ - 'spec/regen_expected.rb'
196
+
199
197
  # Offense count: 1
200
198
  RSpec/PendingWithoutReason:
201
199
  Exclude:
@@ -225,14 +223,16 @@ RSpec/SubjectDeclaration:
225
223
 
226
224
  # Offense count: 1
227
225
  # This cop supports safe autocorrection (--autocorrect).
228
- Style/RedundantBegin:
226
+ # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
227
+ # SupportedStyles: single_quotes, double_quotes
228
+ Style/StringLiterals:
229
229
  Exclude:
230
- - 'lib/metanorma/plugin/lutaml/lutaml_ea_xmi_base.rb'
230
+ - 'metanorma-plugin-lutaml.gemspec'
231
231
 
232
232
  # Offense count: 1
233
233
  # This cop supports safe autocorrection (--autocorrect).
234
- # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
234
+ # Configuration parameters: EnforcedStyle.
235
235
  # SupportedStyles: single_quotes, double_quotes
236
- Style/StringLiterals:
236
+ Style/StringLiteralsInInterpolation:
237
237
  Exclude:
238
- - 'metanorma-plugin-lutaml.gemspec'
238
+ - 'spec/metanorma/plugin/lutaml/lutaml_xsd_preprocessor_spec.rb'
data/CLAUDE.md CHANGED
@@ -39,10 +39,12 @@ All extensions follow the Asciidoctor extension API. The two main extension type
39
39
 
40
40
  ### Preprocessor Inheritance Hierarchy
41
41
 
42
- - `LutamlPreprocessor` — handles `[lutaml]`, `[lutaml_express]`, `[lutaml_express_liquid]` blocks. Parses EXPRESS files via the `lutaml`/`expressir` gems, builds Liquid contexts, and renders templates.
42
+ - `BasePreprocessor` — abstract base for EXPRESS and XSD preprocessors. Uses Template Method pattern: subclasses implement `lutaml_liquid?`, `load_lutaml_file`, `index_type_name` and may override `update_repo`, `template`, `reorder_schemas`.
43
+ - `LutamlPreprocessor` < `BasePreprocessor` — handles `[lutaml]`, `[lutaml_express]`, `[lutaml_express_liquid]` blocks. Adds EXPRESS-specific `update_repo` (cache unwrap, remark decoration), Liquid environment with custom tags/filters, schema reordering.
44
+ - `LutamlXsdPreprocessor` < `BasePreprocessor` — handles `[lutaml_xsd]` blocks. Parses XSD files via `lutaml-model`, double-newline template joins for Asciidoctor paragraph breaks.
43
45
  - `LutamlUmlDatamodelDescriptionPreprocessor` and `LutamlEaXmiPreprocessor` — both include `LutamlEaXmiBase`, which handles XMI parsing via `lutaml` gem and renders using bundled Liquid templates.
44
46
  - `LutamlXmiUmlPreprocessor` — another XMI-based preprocessor with its own macro regex.
45
- - `BaseStructuredTextPreprocessor` — base for `[yaml2text]`, `[json2text]`, `[data2text]` blocks. Its subclasses (`Yaml2TextPreprocessor`, `Json2TextPreprocessor`, `Data2TextPreprocessor`) differ only in how they load content (YAML vs JSON vs auto-detect). The `Content` module provides the actual parsing logic.
47
+ - `BaseStructuredTextPreprocessor` — base for `[yaml2text]`, `[json2text]`, `[data2text]` blocks. Its subclasses (`Yaml2TextPreprocessor`, `Json2TextPreprocessor`, `Data2TextPreprocessor`) differ only in how they load content (YAML vs JSON vs auto-detect).
46
48
 
47
49
  ### Key Shared Modules
48
50
 
@@ -71,8 +73,10 @@ Tests use `metanorma-standoc` as the backend. The spec helper registers all exte
71
73
 
72
74
  ## Key Dependencies
73
75
 
74
- - `lutaml` — core LutaML parser/model library (EXPRESS, UML, XMI formats)
76
+ - `lutaml` — core LutaML parser/model library (EXPRESS, UML, XMI, XSD formats)
77
+ - `lutaml-model` — LutaML serialization framework (provides XSD parsing, Liquid drops)
75
78
  - `expressir` — EXPRESS schema parser
76
79
  - `ogc-gml` — OGC GML dictionary parser
77
80
  - `liquid` — template rendering engine
78
81
  - `asciidoctor` — document processing framework
82
+ - `canon` — semantic XML comparison for test assertions
data/Gemfile CHANGED
@@ -12,7 +12,12 @@ rescue StandardError
12
12
  nil
13
13
  end
14
14
 
15
- gem "rake", "~> 13"
15
+ gem "canon"
16
+ gem "html2doc", github: "metanorma/html2doc", branch: "main"
17
+ gem "lutaml"
18
+ gem "metanorma", github: "metanorma/metanorma", branch: "main"
19
+ gem "metanorma-standoc", github: "metanorma/metanorma-standoc", branch: "main"
20
+ gem "rake"
16
21
  gem "rspec"
17
22
  gem "rspec-html-matchers"
18
23
  gem "rubocop"
data/README.adoc CHANGED
@@ -14,6 +14,7 @@ within a Metanorma document:
14
14
  * Enterprise Architect exported UML files in XMI format (`*.xmi`)
15
15
  * LutaML GML Dictionary files (`*.xml`)
16
16
  * JSON or YAML files (`*.json|*.yml|*.yaml`)
17
+ * XML Schema files (`*.xsd`)
17
18
 
18
19
  == Installation
19
20
 
@@ -34,6 +35,8 @@ link:docs/usages/lutaml-gml.adoc[Usage with LutaML GML Dictionary by lutaml_gml_
34
35
 
35
36
  link:docs/usages/json_yaml.adoc[Usage with JSON or YAML files by data2text, yaml2text or json2text]
36
37
 
38
+ link:docs/usages/lutaml-xsd.adoc[Usage with XML Schema files by lutaml_xsd]
39
+
37
40
  == Documentation
38
41
 
39
42
  Please refer to https://www.metanorma.org.
@@ -0,0 +1,94 @@
1
+ == Usage with LutaML XSD
2
+
3
+ === Overview
4
+
5
+ The `lutaml_xsd` macro parses *XML Schema (XSD)* files through `lutaml-model`
6
+ and exposes the parsed schema object to *Liquid* templates.
7
+
8
+ === Syntax
9
+
10
+ [source,adoc]
11
+ -----
12
+ [lutaml_xsd,<path_to_xsd>,<context_name>[, option1=value1, option2=value2, ...]]
13
+ ----
14
+ <your Liquid template here>
15
+ ----
16
+ -----
17
+
18
+ * `<path_to_xsd>`: Path to the XSD file to be processed.
19
+ * `<context_name>`: The name of the context variable to use in the template.
20
+ * `option1=value1, ...`: Optional parameters (<<options,supported options>>).
21
+
22
+ [[options]]
23
+ === Options
24
+
25
+ * `location`: Base URL or path for resolving `<xs:import>` and `<xs:include>`
26
+ statements in the XSD. When omitted, the directory of `<path_to_xsd>` is used.
27
+
28
+ === Liquid Template Context
29
+
30
+ The context variable (e.g., `unitsml`) exposes the parsed
31
+ `Lutaml::Xml::Schema::Xsd::Schema` object through its Liquid drop.
32
+
33
+ Commonly used schema collections:
34
+
35
+ * `element`: List of elements defined in the XSD.
36
+ * `complex_type`: List of complex types defined in the XSD.
37
+ * `simple_type`, `attribute`, `attribute_group`, `group`, `import`, and
38
+ `include`: Other schema components exposed by `lutaml-model`.
39
+
40
+ Commonly used helpers:
41
+
42
+ * `elements_sorted_by_name`, `complex_types_sorted_by_name`,
43
+ `attribute_groups_sorted_by_name`: Sorted schema collections.
44
+ * `used_by`, `child_elements`, `attribute_elements`, and `referenced_type`:
45
+ Component helpers exposed by parsed XSD objects.
46
+
47
+ === Example: Listing Elements and Complex Types
48
+
49
+ [source,adoc]
50
+ -----
51
+ = Elements
52
+ [lutaml_xsd,path/to/unitsml.xsd,unitsml]
53
+ ----
54
+ {% for element in unitsml.elements_sorted_by_name %}
55
+ Name: *{{ element.name }}*
56
+ Type: *{{ element.type }}*
57
+ Used by: {{ element.used_by | map: "name" | join: ", " }}
58
+ {% endfor %}
59
+ ----
60
+
61
+ = ComplexTypes
62
+ [lutaml_xsd,path/to/unitsml.xsd,unitsml]
63
+ ----
64
+ {% for complex_type in unitsml.complex_types_sorted_by_name %}
65
+ Name: *{{ complex_type.name }}*
66
+ Children: {{ complex_type.child_elements | map: "name" | join: ", " }}
67
+ Attributes: {{ complex_type.attribute_elements | map: "name" | join: ", " }}
68
+ {% endfor %}
69
+ ----
70
+ -----
71
+
72
+ === Example: Using with Remote XSD and Options
73
+
74
+ [source,adoc]
75
+ -----
76
+ [lutaml_xsd,path/to/omml.xsd,omml, location=https://raw.githubusercontent.com/t-yuki/ooxml-xsd/refs/heads/master]
77
+ ----
78
+ {% for element in omml.element %}
79
+ Name: *{{ element.name }}*
80
+ Type: *{{ element.type }}*
81
+ {% endfor %}
82
+ ----
83
+ -----
84
+
85
+ === Use Cases
86
+
87
+ * Generate documentation for XML schemas.
88
+ * Extract and list schema elements and types or other details.
89
+ * Customize output using Liquid templates.
90
+
91
+ === Notes
92
+
93
+ * The macro supports local files at `<path_to_xsd>`.
94
+ * You can use all standard *Liquid* template features for formatting and logic.
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "liquid"
4
+ require "asciidoctor"
5
+ require "asciidoctor/reader"
6
+ require "metanorma/plugin/lutaml/utils"
7
+ require "metanorma/plugin/lutaml/asciidoctor/preprocessor"
8
+
9
+ module Metanorma
10
+ module Plugin
11
+ module Lutaml
12
+ # Base preprocessor for LutaML format-specific preprocessors.
13
+ #
14
+ # Subclasses must implement:
15
+ # - #lutaml_liquid?(line) — match the macro header line
16
+ # - #load_lutaml_file(document, file_path, options)
17
+ # parse format-specific input
18
+ #
19
+ # Subclasses may override:
20
+ # - #index_type_name — human-readable format name for error messages
21
+ # - #update_repo(options, repo) — transform parsed repo before rendering
22
+ # - #template(lines) — parse Liquid template lines
23
+ # - #reorder_schemas(repo_liquid, options) — reorder/filter schemas
24
+ class BasePreprocessor < ::Asciidoctor::Extensions::Preprocessor
25
+ include Utils
26
+
27
+ FILE_SYSTEM_PATTERNS = ["%s.liquid", "_%s.liquid", "_%s.adoc"].freeze
28
+
29
+ def process(document, reader)
30
+ input_lines = Asciidoctor::PreprocessorNoIfdefsReader
31
+ .new(document, reader.lines).readlines.to_enum
32
+
33
+ express_indexes = Utils.parse_document_express_indexes(
34
+ document, input_lines
35
+ )
36
+
37
+ result_content = process_input_lines(
38
+ document: document,
39
+ input_lines: input_lines,
40
+ express_indexes: express_indexes,
41
+ )
42
+
43
+ Asciidoctor::PreprocessorNoIfdefsReader.new(document, result_content)
44
+ end
45
+
46
+ protected
47
+
48
+ def load_lutaml_file(_document, _file_path, _options)
49
+ raise NotImplementedError,
50
+ "#{self.class}#load_lutaml_file must be implemented"
51
+ end
52
+
53
+ def lutaml_liquid?(_line)
54
+ raise NotImplementedError,
55
+ "#{self.class}#lutaml_liquid? must be implemented"
56
+ end
57
+
58
+ def index_type_name
59
+ raise NotImplementedError,
60
+ "#{self.class}#index_type_name must be implemented"
61
+ end
62
+
63
+ def update_repo(_options, repo)
64
+ repo
65
+ end
66
+
67
+ def template(lines)
68
+ ::Liquid::Template.parse(lines.join("\n"))
69
+ end
70
+
71
+ def reorder_schemas(repo_liquid, _options)
72
+ repo_liquid
73
+ end
74
+
75
+ def index_missing_message(path)
76
+ "Unable to load #{index_type_name} file for `#{path}`, " \
77
+ "please specify the full path."
78
+ end
79
+
80
+ def build_file_system(document, options)
81
+ include_paths = [Utils.relative_file_path(document, "")]
82
+ options["include_path"]&.split(",")&.each do |path|
83
+ include_paths.push(Utils.relative_file_path(document, path))
84
+ end
85
+ ::Metanorma::Plugin::Lutaml::Liquid::LocalFileSystem
86
+ .new(include_paths, FILE_SYSTEM_PATTERNS)
87
+ end
88
+
89
+ private
90
+
91
+ def process_input_lines(document:, input_lines:, express_indexes:)
92
+ result = []
93
+ loop do
94
+ result.push(
95
+ *process_text_blocks(document, input_lines, express_indexes),
96
+ )
97
+ end
98
+ result
99
+ end
100
+
101
+ def process_text_blocks(document, input_lines, express_indexes) # rubocop:disable Metrics/AbcSize
102
+ line = input_lines.next
103
+ block_header_match = lutaml_liquid?(line)
104
+
105
+ return [line] unless block_header_match
106
+
107
+ index_names = block_header_match[:index_names].split(";").map(&:strip)
108
+ context_name = block_header_match[:context_name].strip
109
+ options = (block_header_match[:options] &&
110
+ parse_options(block_header_match[:options].to_s.strip)) || {}
111
+
112
+ end_mark = input_lines.next
113
+
114
+ render_liquid_template(
115
+ document: document,
116
+ lines: extract_block_lines(input_lines, end_mark),
117
+ index_names: index_names,
118
+ context_name: context_name,
119
+ options: options,
120
+ indexes: express_indexes,
121
+ )
122
+ end
123
+
124
+ def extract_block_lines(input_lines, end_mark)
125
+ block = []
126
+ while (block_line = input_lines.next) != end_mark
127
+ block.push(block_line)
128
+ end
129
+ block
130
+ end
131
+
132
+ # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/ParameterLists
133
+ def gather_context_liquid_items(index_names:, document:,
134
+ indexes:, options: {})
135
+ index_names.map do |path|
136
+ if indexes[path] && indexes[path][:model]
137
+ repo = indexes[path][:model]
138
+ repo = update_repo(options, repo)
139
+ indexes[path][:liquid_drop] ||= repo.to_liquid
140
+ else
141
+ full_path = Utils.relative_file_path(document, path)
142
+ unless File.file?(full_path)
143
+ raise StandardError, index_missing_message(path)
144
+ end
145
+
146
+ repo = load_lutaml_file(document, path, options)
147
+ repo = update_repo(options, repo)
148
+ indexes[path] = { liquid_drop: repo.to_liquid }
149
+ end
150
+
151
+ indexes[path]
152
+ end
153
+ end
154
+ # rubocop:enable Metrics/AbcSize,Metrics/MethodLength,Metrics/ParameterLists
155
+
156
+ def render_liquid_template(document:, lines:, context_name:, # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/ParameterLists
157
+ index_names:, options:, indexes:)
158
+ options = process_options(document, options)
159
+
160
+ all_items = gather_context_liquid_items(
161
+ index_names: index_names, document: document, indexes: indexes,
162
+ options: options.merge("document" => document)
163
+ )
164
+
165
+ parsed_template = template(lines)
166
+ parsed_template.registers[:file_system] =
167
+ build_file_system(document, options)
168
+
169
+ all_items.map do |item|
170
+ parsed_template.assigns[context_name] = item[:liquid_drop]
171
+ parsed_template.assigns["ordered_schemas"] = reorder_schemas(
172
+ item[:liquid_drop], options
173
+ )
174
+ parsed_template.assigns["schemas_order"] =
175
+ options["selected_schemas"]
176
+ parsed_template.render
177
+ end.flatten
178
+ rescue StandardError => e
179
+ ::Metanorma::Util.log(
180
+ "[#{self.class.name}] Failed to parse LutaML block: #{e.message}",
181
+ :error,
182
+ )
183
+ raise e
184
+ end
185
+
186
+ def process_options(document, options)
187
+ if (config_yaml_path = options.delete("config_yaml"))
188
+ config = read_config_yaml_file(document, config_yaml_path)
189
+ if config["selected_schemas"]
190
+ options["selected_schemas"] =
191
+ config["selected_schemas"]
192
+ end
193
+ end
194
+ options
195
+ end
196
+
197
+ def read_config_yaml_file(document, file_path) # rubocop:disable Metrics/MethodLength
198
+ return {} unless file_path
199
+
200
+ relative_file_path = Utils.relative_file_path(document, file_path)
201
+ config_yaml = YAML.safe_load(
202
+ File.read(relative_file_path, encoding: "UTF-8"),
203
+ )
204
+
205
+ return {} unless config_yaml["schemas"]
206
+
207
+ unless config_yaml["schemas"].is_a?(Hash)
208
+ raise StandardError,
209
+ "[lutaml_express_liquid] attribute `config_yaml` must " \
210
+ "point to a YAML file with the `schemas` key as a hash."
211
+ end
212
+
213
+ { "selected_schemas" => config_yaml["schemas"].keys }
214
+ end
215
+
216
+ def parse_options(options_string)
217
+ options_string
218
+ .to_s
219
+ .scan(/,\s*([^=]+?)=(\s*[^,]+)/)
220
+ .to_h { |elem| elem.map(&:strip) }
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
@@ -490,7 +490,9 @@ module Metanorma
490
490
  def serialize_klass_drop_by_name(xmi_path, name, document = nil,
491
491
  guidance = nil)
492
492
  parser, uml_doc = build_uml_document(xmi_path, document)
493
- raw_klass = find_packaged_klass(parser.xmi_index, name)
493
+ root_model_name = parser.xmi_root_model.model.name
494
+ raw_klass = find_packaged_klass(parser.xmi_index, name,
495
+ root_model_name: root_model_name)
494
496
  warn "Class not found for name: #{name}" if raw_klass.nil?
495
497
  klass = raw_klass && find_uml_node_by_xmi_id(
496
498
  uml_doc, raw_klass.id, :classes
@@ -550,11 +552,14 @@ guidance = nil)
550
552
  nil
551
553
  end
552
554
 
553
- def find_packaged_klass(index, path)
554
- segments = path.split("::")
555
+ def find_packaged_klass(index, path, root_model_name: nil)
556
+ segments = path.split("::").reject(&:empty?)
557
+ if root_model_name && segments.first == root_model_name
558
+ segments.shift
559
+ end
555
560
  if segments.one?
556
561
  index.find_packaged_by_name_and_types(
557
- path, ["uml:Class", "uml:AssociationClass"]
562
+ segments.first, ["uml:Class", "uml:AssociationClass"]
558
563
  )
559
564
  else
560
565
  find_packaged_klass_by_path(index, segments)
@@ -563,20 +568,25 @@ guidance = nil)
563
568
 
564
569
  def find_packaged_klass_by_path(index, segments)
565
570
  klass_name = segments.pop
566
- klass = index.find_packaged_by_name_and_types(
567
- klass_name, ["uml:Class", "uml:AssociationClass"]
568
- )
569
- return unless klass
570
571
 
571
- # Verify the path by walking up the parent chain
572
- current = klass
573
- segments.reverse_each do |pkg_name|
572
+ candidates = ["uml:Class", "uml:AssociationClass"]
573
+ .flat_map { |t| index.packaged_elements_of_type(t) }
574
+ .select { |e| e.name == klass_name }
575
+
576
+ candidates.find do |klass|
577
+ match_parent_chain?(index, klass, segments)
578
+ end
579
+ end
580
+
581
+ def match_parent_chain?(index, element, parent_segments)
582
+ current = element
583
+ parent_segments.reverse_each do |pkg_name|
574
584
  parent = index.find_parent(current.id)
575
- return unless parent && parent.name == pkg_name
585
+ return false unless parent && parent.name == pkg_name
576
586
 
577
587
  current = parent
578
588
  end
579
- klass
589
+ true
580
590
  end
581
591
 
582
592
  def find_packaged_enum(index, name)
@@ -1,21 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "liquid"
4
- require "asciidoctor"
5
- require "asciidoctor/reader"
6
- require "lutaml"
7
- require "metanorma/plugin/lutaml/utils"
8
- require "metanorma/plugin/lutaml/asciidoctor/preprocessor"
9
3
  require "metanorma/plugin/lutaml/express_remarks_decorator"
10
4
 
11
5
  module Metanorma
12
6
  module Plugin
13
7
  module Lutaml
14
- # Class for processing Lutaml files
15
- class LutamlPreprocessor < ::Asciidoctor::Extensions::Preprocessor
16
- include Utils
17
-
18
- REMARKS_ATTRIBUTE = "remarks"
8
+ # Preprocessor for EXPRESS schema formats (lutaml, lutaml_express,
9
+ # lutaml_express_liquid). Parses EXPRESS files via the lutaml/expressir
10
+ # gems, decorates remarks with relative path resolution, and renders
11
+ # Liquid templates with the EXPRESS-specific Liquid environment.
12
+ class LutamlPreprocessor < BasePreprocessor
19
13
  EXPRESS_PREPROCESSOR_REGEX = %r{
20
14
  ^ # Start of line
21
15
  \[ # Opening bracket
@@ -30,117 +24,38 @@ module Metanorma
30
24
  \] # Closing bracket
31
25
  }x
32
26
 
33
- def process(document, reader) # rubocop:disable Metrics/MethodLength
34
- r = Asciidoctor::PreprocessorNoIfdefsReader.new(document,
35
- reader.lines)
36
- input_lines = r.readlines.to_enum
37
-
38
- express_indexes = Utils.parse_document_express_indexes(
39
- document,
40
- input_lines,
41
- )
42
-
43
- result_content = process_input_lines(
44
- document: document,
45
- input_lines: input_lines,
46
- express_indexes: express_indexes,
47
- )
48
-
49
- Asciidoctor::PreprocessorNoIfdefsReader.new(document, result_content)
50
- end
51
-
52
27
  protected
53
28
 
54
29
  def lutaml_liquid?(line)
55
30
  line.match(EXPRESS_PREPROCESSOR_REGEX)
56
31
  end
57
32
 
58
- def load_express_lutaml_file(document, file_path)
59
- ::Lutaml::Parser.parse(
60
- File.new(
61
- Utils.relative_file_path(document, file_path),
62
- encoding: "UTF-8",
63
- ),
64
- )
65
- end
66
-
67
- private
33
+ def load_lutaml_file(document, file_path, _options)
34
+ full_path = Utils.relative_file_path(document, file_path)
68
35
 
69
- def process_input_lines(document:, input_lines:, express_indexes:)
70
- result = []
71
- loop do
72
- result.push(
73
- *process_text_blocks(document, input_lines, express_indexes),
74
- )
36
+ file = File.new(full_path, encoding: "UTF-8")
37
+ if full_path.end_with?(".exp")
38
+ ::Lutaml::Express::Parsers::Exp.parse(file)
39
+ else
40
+ ::Lutaml::Uml::Parsers::Dsl.parse(file)
75
41
  end
76
- result
77
- end
78
-
79
- def process_text_blocks(document, input_lines, express_indexes) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
80
- line = input_lines.next
81
- block_header_match = lutaml_liquid?(line)
82
-
83
- return [line] if block_header_match.nil?
84
-
85
- index_names = block_header_match[:index_names].split(";").map(&:strip)
86
- context_name = block_header_match[:context_name].strip
87
-
88
- options = (block_header_match[:options] &&
89
- parse_options(block_header_match[:options].to_s.strip)) || {}
90
-
91
- end_mark = input_lines.next
92
-
93
- render_liquid_template(
94
- document: document,
95
- lines: extract_block_lines(input_lines, end_mark),
96
- index_names: index_names,
97
- context_name: context_name,
98
- options: options,
99
- indexes: express_indexes,
100
- )
101
42
  end
102
43
 
103
- def extract_block_lines(input_lines, end_mark)
104
- block = []
105
- while (block_line = input_lines.next) != end_mark
106
- block.push(block_line)
107
- end
108
- block
44
+ def index_type_name
45
+ "EXPRESS"
109
46
  end
110
47
 
111
- def gather_context_liquid_items( # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/ParameterLists
112
- index_names:, document:, indexes:, options: {}
113
- )
114
- index_names.map do |path| # rubocop:disable Metrics/BlockLength
115
- if indexes[path] && indexes[path][:model]
116
- repo = indexes[path][:model]
117
- repo = update_repo(options, repo)
118
- indexes[path][:liquid_drop] ||= repo.to_liquid
119
- else
120
- full_path = Utils.relative_file_path(document, path)
121
- unless File.file?(full_path)
122
- raise StandardError.new(
123
- "Unable to load EXPRESS index for `#{path}`, " \
124
- "please define it at `:lutaml-express-index:` or specify " \
125
- "the full path.",
126
- )
127
- end
128
- repo = load_express_lutaml_file(document, path)
129
- repo = update_repo(options, repo)
130
- indexes[path] = {
131
- liquid_drop: repo.to_liquid,
132
- }
133
- end
134
-
135
- indexes[path]
136
- end
48
+ def index_missing_message(path)
49
+ "Unable to load EXPRESS index for `#{path}`, " \
50
+ "please define it at `:lutaml-express-index:` or specify " \
51
+ "the full path."
137
52
  end
138
53
 
139
54
  def update_repo(options, repo)
140
- # Unwrap repo if it's a cache
141
- repo = repo.content if repo.is_a? Expressir::Model::Cache
55
+ repo = repo.content if repo.is_a?(Expressir::Model::Cache)
56
+ return repo unless repo.is_a?(Expressir::Model::Repository) ||
57
+ repo.is_a?(Expressir::Model::ExpFile)
142
58
 
143
- # Process each schema
144
59
  repo.schemas.each do |schema|
145
60
  options["relative_path_prefix"] =
146
61
  relative_path_prefix(options, schema)
@@ -150,6 +65,25 @@ module Metanorma
150
65
  repo
151
66
  end
152
67
 
68
+ def template(lines)
69
+ ::Liquid::Template.parse(
70
+ lines.join("\n"),
71
+ environment: create_liquid_environment,
72
+ )
73
+ end
74
+
75
+ def reorder_schemas(repo_liquid, options)
76
+ return repo_liquid.schemas unless options["selected_schemas"]
77
+
78
+ options["selected_schemas"].filter_map do |schema_name|
79
+ repo_liquid.schemas.find do |schema|
80
+ schema.id == schema_name || schema.file_basename == schema_name
81
+ end
82
+ end
83
+ end
84
+
85
+ private
86
+
153
87
  def update_remarks(model, options)
154
88
  model.remarks = decorate_remarks(options, model.remarks)
155
89
  model.remark_items&.each do |ri|
@@ -157,127 +91,29 @@ module Metanorma
157
91
  end
158
92
 
159
93
  model.children.each do |child|
160
- if child.respond_to?(:remarks) && child.respond_to?(:remark_items)
161
- update_remarks(child, options)
162
- end
94
+ next unless child.is_a?(Expressir::Model::ModelElement)
95
+
96
+ update_remarks(child, options)
163
97
  end
164
98
  end
165
99
 
166
100
  def relative_path_prefix(options, model)
167
- return nil if options.nil? || options["document"].nil?
101
+ return if options.nil? || options["document"].nil?
168
102
 
169
103
  document = options["document"]
170
104
  file_path = File.dirname(model.file)
171
105
  docfile_directory = File.dirname(
172
106
  document.attributes["docfile"] || ".",
173
107
  )
174
- document
175
- .path_resolver
176
- .system_path(file_path, docfile_directory)
108
+ document.path_resolver.system_path(file_path, docfile_directory)
177
109
  end
178
110
 
179
111
  def decorate_remarks(options, remarks)
180
112
  return [] unless remarks
181
113
 
182
114
  remarks.map do |remark|
183
- ::Metanorma::Plugin::Lutaml::ExpressRemarksDecorator
184
- .call(remark, options)
185
- end
186
- end
187
-
188
- def read_config_yaml_file(document, file_path) # rubocop:disable Metrics/MethodLength
189
- return {} if file_path.nil?
190
-
191
- relative_file_path = Utils.relative_file_path(document, file_path)
192
- config_yaml = YAML.safe_load(
193
- File.read(relative_file_path, encoding: "UTF-8"),
194
- )
195
-
196
- return {} unless config_yaml["schemas"]
197
-
198
- unless config_yaml["schemas"].is_a?(Hash)
199
- raise StandardError.new(
200
- "[lutaml_express_liquid] attribute `config_yaml` must point " \
201
- "to a YAML file that has the `schemas` key containing a hash.",
202
- )
115
+ ExpressRemarksDecorator.call(remark, options)
203
116
  end
204
-
205
- { "selected_schemas" => config_yaml["schemas"].keys }
206
- end
207
-
208
- def render_liquid_template(document:, lines:, context_name:, # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/ParameterLists
209
- index_names:, options:, indexes:)
210
- # Process options and configuration
211
- options = process_options(document, options)
212
-
213
- # Get all context items in one go
214
- all_items = gather_context_liquid_items(
215
- index_names: index_names, document: document, indexes: indexes,
216
- options: options.merge("document" => document)
217
- )
218
-
219
- # Setup include paths for liquid templates
220
- include_paths = [Utils.relative_file_path(document, "")]
221
- options["include_path"]&.split(",")&.each do |path|
222
- # resolve include_path relative to the document
223
- include_paths.push(Utils.relative_file_path(document, path))
224
- end
225
-
226
- file_system = ::Metanorma::Plugin::Lutaml::Liquid::LocalFileSystem
227
- .new(include_paths, ["%s.liquid", "_%s.liquid", "_%s.adoc"])
228
-
229
- # Parse template once outside the loop
230
- template = ::Liquid::Template
231
- .parse(lines.join("\n"), environment: create_liquid_environment)
232
- template.registers[:file_system] = file_system
233
-
234
- # Render for each item
235
- all_items.map do |item|
236
- template.assigns[context_name] = item[:liquid_drop]
237
- template.assigns["ordered_schemas"] = reorder_schemas(
238
- item[:liquid_drop], options
239
- )
240
- template.assigns["schemas_order"] = options["selected_schemas"]
241
- template.render
242
- end.flatten
243
- rescue StandardError => e
244
- ::Metanorma::Util
245
- .log("[LutamlPreprocessor] Failed to parse LutaML block: " \
246
- "#{e.message}", :error)
247
- raise e
248
- end
249
-
250
- def reorder_schemas(repo_liquid, options)
251
- return repo_liquid.schemas unless options["selected_schemas"]
252
-
253
- ordered_schemas = []
254
- options["selected_schemas"].each do |schema_name|
255
- ordered_schema = repo_liquid.schemas.find do |schema|
256
- schema.id == schema_name || schema.file_basename == schema_name
257
- end
258
- ordered_schemas.push(ordered_schema)
259
- end
260
-
261
- ordered_schemas
262
- end
263
-
264
- def process_options(document, options)
265
- # Process config file if specified
266
- if (config_yaml_path = options.delete("config_yaml"))
267
- config = read_config_yaml_file(document, config_yaml_path)
268
- if config["selected_schemas"]
269
- options["selected_schemas"] =
270
- config["selected_schemas"]
271
- end
272
- end
273
- options
274
- end
275
-
276
- def parse_options(options_string)
277
- options_string
278
- .to_s
279
- .scan(/,\s*([^=]+?)=(\s*[^,]+)/)
280
- .to_h { |elem| elem.map(&:strip) }
281
117
  end
282
118
  end
283
119
  end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/xml/parsers/xsd"
4
+
5
+ module Metanorma
6
+ module Plugin
7
+ module Lutaml
8
+ # Preprocessor for XSD (XML Schema Definition) files. Parses XSD via
9
+ # lutaml-model's XSD parser and exposes the schema object to Liquid
10
+ # templates.
11
+ #
12
+ # Two syntaxes are supported:
13
+ # Block: [lutaml_xsd, path, context, options] .... ----
14
+ # Direct: lutaml_xsd::path[context, template, options]
15
+ #
16
+ # Caching: parsed XSD results are cached at two levels:
17
+ # - Class-level (@@xsd_cache) persists across document invocations
18
+ # - Document-level (document.attributes["lutaml_xsd_cache"]) within a
19
+ # single document's processing
20
+ class LutamlXsdPreprocessor < BasePreprocessor
21
+ XSD_PREPROCESSOR_REGEX = %r{
22
+ ^ # Start of line
23
+ \[ # Opening bracket
24
+ (?:\blutaml_xsd\b) # lutaml_xsd
25
+ , # Comma separator
26
+ (?<index_names>[^,]+)? # Optional index names
27
+ ,? # Optional comma
28
+ (?<context_name>[^,]+)? # Optional context name
29
+ (?<options>,.*)? # Optional options
30
+ \] # Closing bracket
31
+ }x
32
+
33
+ XSD_DIRECT_REGEX = %r{
34
+ ^\s* # Start of line
35
+ lutaml_xsd:: # Macro prefix
36
+ (?<file_path>[^\[]+?) # XSD file path
37
+ \[ # Opening bracket
38
+ (?<context_name>[^,]+) # Context name
39
+ ,\s* # Comma separator
40
+ (?<template>[^,\]]+) # Template file path
41
+ (?<options>,[^\]]+)? # Optional options
42
+ \] # Closing bracket
43
+ \s*$ # End of line
44
+ }x
45
+
46
+ def initialize(_config = {})
47
+ super
48
+ @@xsd_cache ||= {}
49
+ end
50
+
51
+ protected
52
+
53
+ def lutaml_liquid?(line)
54
+ line.match(XSD_PREPROCESSOR_REGEX)
55
+ end
56
+
57
+ def load_lutaml_file(document, file_path, options)
58
+ full_path = Utils.relative_file_path(document, file_path)
59
+ location = xsd_location(full_path, options)
60
+ cache_key = [full_path, location]
61
+
62
+ cached = document_cache_entry(document, cache_key)
63
+ return cached if cached
64
+
65
+ result = @@xsd_cache[cache_key] ||=
66
+ parse_xsd_file(full_path, location)
67
+
68
+ set_document_cache_entry(document, cache_key, result)
69
+ result
70
+ end
71
+
72
+ def index_type_name
73
+ "XSD"
74
+ end
75
+
76
+ def index_missing_message(path)
77
+ "Unable to load XSD file for `#{path}`, please specify the full path."
78
+ end
79
+
80
+ def template(lines)
81
+ # XSD templates use double-newline joins to produce Asciidoctor
82
+ # paragraph breaks (single newlines are treated as continuation).
83
+ ::Liquid::Template.parse(
84
+ lines.join("\n\n"),
85
+ environment: create_liquid_environment,
86
+ )
87
+ end
88
+
89
+ private
90
+
91
+ def process_text_blocks(document, input_lines, express_indexes)
92
+ match = xsd_direct?(input_lines.peek)
93
+ return handle_direct_block(document, input_lines, match) if match
94
+
95
+ super
96
+ end
97
+
98
+ def xsd_direct?(line)
99
+ line.match(XSD_DIRECT_REGEX)
100
+ end
101
+
102
+ def handle_direct_block(document, input_lines, match)
103
+ input_lines.next # consume the matched line
104
+
105
+ file_path = match[:file_path].strip
106
+ context_name = match[:context_name].strip
107
+ template_file = Utils.relative_file_path(document,
108
+ match[:template].strip)
109
+ options = match[:options] ? parse_options(match[:options].strip) : {}
110
+
111
+ schema = load_lutaml_file(document, file_path, options)
112
+ schema_drop = update_repo(options, schema).to_liquid
113
+
114
+ template_source = File.read(template_file, encoding: "UTF-8")
115
+ tmpl = template([template_source])
116
+ tmpl.registers[:file_system] = build_file_system(document, options)
117
+ tmpl.assigns[context_name] = schema_drop
118
+
119
+ tmpl.render.split("\n", -1)
120
+ rescue StandardError => e
121
+ ::Metanorma::Util.log(
122
+ "[#{self.class.name}] Failed to parse LutaML block: #{e.message}",
123
+ :error,
124
+ )
125
+ raise e
126
+ end
127
+
128
+ def parse_xsd_file(full_path, location)
129
+ File.open(full_path, "r:UTF-8") do |file|
130
+ ::Lutaml::Xml::Parsers::Xsd.parse(file, location: location)
131
+ end
132
+ end
133
+
134
+ def xsd_location(full_path, options)
135
+ options["location"] || File.dirname(full_path)
136
+ end
137
+
138
+ def document_cache_entry(document, cache_key)
139
+ document.attributes["lutaml_xsd_cache"]&.[](cache_key)
140
+ end
141
+
142
+ def set_document_cache_entry(document, cache_key, result)
143
+ document.attributes["lutaml_xsd_cache"] ||= {}
144
+ document.attributes["lutaml_xsd_cache"][cache_key] = result
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -183,8 +183,7 @@ module Metanorma
183
183
  end
184
184
 
185
185
  def load_express_repo_from_cache(path)
186
- ::Lutaml::Parser
187
- .parse(File.new(path), ::Lutaml::Parser::EXPRESS_CACHE_PARSE_TYPE)
186
+ ::Lutaml::Express::Parsers::Exp.parse_cache(path)
188
187
  end
189
188
 
190
189
  def save_express_repo_to_cache(path, repository, document)
@@ -202,10 +201,8 @@ module Metanorma
202
201
  end
203
202
 
204
203
  def load_express_from_folder(folder)
205
- files = Dir["#{folder}/*.exp"].map do |nested_path|
206
- File.new(nested_path, encoding: "UTF-8")
207
- end
208
- ::Lutaml::Parser.parse(files)
204
+ file_paths = Dir["#{folder}/*.exp"]
205
+ ::Expressir::Express::Parser.from_files(file_paths)
209
206
  end
210
207
 
211
208
  # TODO: Refactor this using Suma::SchemaConfig
@@ -220,16 +217,12 @@ module Metanorma
220
217
  schema_yaml_base_path = schema_yaml_base_path + root_schema_path
221
218
  end
222
219
 
223
- files_to_load = yaml_content["schemas"].map do |key, value|
224
- # If there is no path: set for a schema, we assume it uses the
225
- # schema name as the #{filename}.exp.
220
+ file_paths = yaml_content["schemas"].map do |key, value|
226
221
  schema_path = Pathname.new(value["path"] || "#{key}.exp")
227
-
228
- real_schema_path = schema_yaml_base_path + schema_path
229
- File.new(real_schema_path.cleanpath.to_s, encoding: "UTF-8")
222
+ (schema_yaml_base_path + schema_path).cleanpath.to_s
230
223
  end
231
224
 
232
- ::Lutaml::Parser.parse(files_to_load)
225
+ ::Expressir::Express::Parser.from_files(file_paths)
233
226
  end
234
227
 
235
228
  def parse_document_express_indexes(document, input_lines) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
@@ -1,7 +1,7 @@
1
1
  module Metanorma
2
2
  module Plugin
3
3
  module Lutaml
4
- VERSION = "0.7.39".freeze
4
+ VERSION = "0.7.41".freeze
5
5
  end
6
6
  end
7
7
  end
@@ -3,7 +3,9 @@ require "metanorma/plugin/lutaml/config"
3
3
  require "metanorma/plugin/lutaml/json2_text_preprocessor"
4
4
  require "metanorma/plugin/lutaml/yaml2_text_preprocessor"
5
5
  require "metanorma/plugin/lutaml/data2_text_preprocessor"
6
+ require "metanorma/plugin/lutaml/base_preprocessor"
6
7
  require "metanorma/plugin/lutaml/lutaml_preprocessor"
8
+ require "metanorma/plugin/lutaml/lutaml_xsd_preprocessor"
7
9
  require "metanorma/plugin/lutaml/lutaml_uml_datamodel_description_preprocessor"
8
10
  require "metanorma/plugin/lutaml/lutaml_ea_xmi_preprocessor"
9
11
  require "metanorma/plugin/lutaml/lutaml_xmi_uml_preprocessor"
@@ -26,16 +26,17 @@ Gem::Specification.new do |spec|
26
26
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
27
  spec.require_paths = ["lib"]
28
28
 
29
- spec.required_ruby_version = ">= 2.7.0" # rubocop:disable Gemspec/RequiredRubyVersion
29
+ spec.required_ruby_version = ">= 3.0.0" # rubocop:disable Gemspec/RequiredRubyVersion
30
30
 
31
31
  spec.add_dependency "asciidoctor"
32
32
  spec.add_dependency "coradoc", "~> 1.1.8"
33
- spec.add_dependency "expressir", "~> 2.3", ">= 2.3.4"
33
+ spec.add_dependency "expressir", "~> 2.3", ">= 2.3.5"
34
34
  spec.add_dependency "isodoc"
35
35
  spec.add_dependency "liquid"
36
36
  spec.add_dependency "lutaml", "~> 0.10", ">= 0.10.12"
37
+ spec.add_dependency "lutaml-model", "~> 0.8.4"
37
38
  spec.add_dependency "ogc-gml", "~> 1.1"
38
39
  spec.add_dependency "relaton-cli"
39
40
 
40
- spec.metadata["rubygems_mfa_required"] = "false"
41
+ spec.metadata["rubygems_mfa_required"] = "true"
41
42
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metanorma-plugin-lutaml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.39
4
+ version: 0.7.41
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-12 00:00:00.000000000 Z
11
+ date: 2026-05-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor
@@ -47,7 +47,7 @@ dependencies:
47
47
  version: '2.3'
48
48
  - - ">="
49
49
  - !ruby/object:Gem::Version
50
- version: 2.3.4
50
+ version: 2.3.5
51
51
  type: :runtime
52
52
  prerelease: false
53
53
  version_requirements: !ruby/object:Gem::Requirement
@@ -57,7 +57,7 @@ dependencies:
57
57
  version: '2.3'
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: 2.3.4
60
+ version: 2.3.5
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: isodoc
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -106,6 +106,20 @@ dependencies:
106
106
  - - ">="
107
107
  - !ruby/object:Gem::Version
108
108
  version: 0.10.12
109
+ - !ruby/object:Gem::Dependency
110
+ name: lutaml-model
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: 0.8.4
116
+ type: :runtime
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: 0.8.4
109
123
  - !ruby/object:Gem::Dependency
110
124
  name: ogc-gml
111
125
  requirement: !ruby/object:Gem::Requirement
@@ -160,9 +174,11 @@ files:
160
174
  - docs/usages/json_yaml.adoc
161
175
  - docs/usages/lutaml-gml.adoc
162
176
  - docs/usages/lutaml-uml.adoc
177
+ - docs/usages/lutaml-xsd.adoc
163
178
  - docs/usages/xmi_to_uml.adoc
164
179
  - lib/metanorma-plugin-lutaml.rb
165
180
  - lib/metanorma/plugin/lutaml/asciidoctor/preprocessor.rb
181
+ - lib/metanorma/plugin/lutaml/base_preprocessor.rb
166
182
  - lib/metanorma/plugin/lutaml/base_structured_text_preprocessor.rb
167
183
  - lib/metanorma/plugin/lutaml/config.rb
168
184
  - lib/metanorma/plugin/lutaml/config/guidance.rb
@@ -213,6 +229,7 @@ files:
213
229
  - lib/metanorma/plugin/lutaml/lutaml_table_inline_macro.rb
214
230
  - lib/metanorma/plugin/lutaml/lutaml_uml_datamodel_description_preprocessor.rb
215
231
  - lib/metanorma/plugin/lutaml/lutaml_xmi_uml_preprocessor.rb
232
+ - lib/metanorma/plugin/lutaml/lutaml_xsd_preprocessor.rb
216
233
  - lib/metanorma/plugin/lutaml/parse_error.rb
217
234
  - lib/metanorma/plugin/lutaml/source_extractor.rb
218
235
  - lib/metanorma/plugin/lutaml/utils.rb
@@ -223,7 +240,7 @@ homepage: https://github.com/metanorma/metanorma-plugin-lutaml
223
240
  licenses:
224
241
  - BSD-2-Clause
225
242
  metadata:
226
- rubygems_mfa_required: 'false'
243
+ rubygems_mfa_required: 'true'
227
244
  post_install_message:
228
245
  rdoc_options: []
229
246
  require_paths:
@@ -232,7 +249,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
232
249
  requirements:
233
250
  - - ">="
234
251
  - !ruby/object:Gem::Version
235
- version: 2.7.0
252
+ version: 3.0.0
236
253
  required_rubygems_version: !ruby/object:Gem::Requirement
237
254
  requirements:
238
255
  - - ">="