metanorma-plugin-lutaml 0.7.55.pre.alpha.1 → 0.7.55

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: d08e2206e64a9c020c1059501939d9a44cfa5e0412368b84e9702442ed38e5ca
4
- data.tar.gz: 3d1bc9fbb60c1a750ab70d2ffcd98e61257e4a7d21f44886cb485456b56af81c
3
+ metadata.gz: 608d678b19ef1c180dc4e244f98d329a8452d9c875469978b6d84247894c09d5
4
+ data.tar.gz: 64269c82a46666d5b50df626671d985917c6acb845708a8d527b6070a2da9e1e
5
5
  SHA512:
6
- metadata.gz: 8777cde7a2a1379e9cabb417192d3bc026786cb04739e10578c2eede1764bdffe2b113be22837f376957c7ba6525ba831fd78192c1c381fc5c70a0ec0151d661
7
- data.tar.gz: 6ac5da62726d19874eceeb0c39710ad034e890e78402f41f516d1f00b5954aed441b9176ec403c97cd228d7537e6e8e3cd710495e2bee3c2927ae598d39d84a1
6
+ metadata.gz: 9078948b7bd2f32a925871da5b6f51f54d6613a660c20dff4e0c19d1f72c77814f5877f02700926eb031b33dae6de3aae7bc09668a65581f8d9287db0d29a9ad
7
+ data.tar.gz: 10fa46ed3a240a00777df9f10621b1cdb5e5e35646958bf9b94b830ebacd8ca8b520dd367dcab4d9019a7042a19ec57270e8cea5d9cb63212ec957fdf0f8d6bf
data/Gemfile CHANGED
@@ -37,9 +37,6 @@ gem "leptris", "~> 1.9.361.1"
37
37
  gem "isodoc", github: "metanorma/isodoc", branch: "main"
38
38
  gem "metanorma-standoc", github: "metanorma/metanorma-standoc", branch: "main"
39
39
  gem "suma", github: "metanorma/suma", branch: "main"
40
- # standoc main's block_attributes reference
41
- # Metanorma::Document::Components::AncillaryBlocks::SvgmapElement, which
42
- # the released metanorma-document 0.5.1 predates; ref-pinned to the
43
- # document main tree the suite is green against
44
- gem "metanorma-document", github: "metanorma/metanorma-document",
45
- ref: "ca4eb4b26f7ab0d2994a4710d0cf158607a832b4"
40
+ # standoc main (>= 3.5.1.pre.alpha.2) floors metanorma-document at
41
+ # 0.5.2.pre.alpha.1 (SvgmapElement, metanorma-standoc#1303); main carries it
42
+ gem "metanorma-document", github: "metanorma/metanorma-document", branch: "main"
data/Rakefile CHANGED
@@ -10,6 +10,7 @@ XMI_HEAVY_SPEC_FILES = %w[
10
10
  ].freeze
11
11
 
12
12
  XMI_SPEC_FILES = %w[
13
+ spec/metanorma/plugin/lutaml/lutaml_ea_uml_preprocessor_spec.rb
13
14
  spec/metanorma/plugin/lutaml/lutaml_klass_table_block_macro_spec.rb
14
15
  spec/metanorma/plugin/lutaml/lutaml_enum_table_block_macro_spec.rb
15
16
  spec/metanorma/plugin/lutaml/lutaml_xmi_index_spec.rb
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "expressir"
4
+ require "pathname"
4
5
 
5
6
  module Metanorma
6
7
  module Plugin
@@ -16,7 +17,17 @@ module Metanorma
16
17
  )
17
18
  resolved = document.path_resolver
18
19
  .system_path(file_path, docfile_directory)
19
- File.expand_path(resolved, docfile_directory)
20
+ # The prefix lands inside emitted image macros, so it must not
21
+ # carry the absolute build path: two builds of the same tree in
22
+ # two directories produced different image/@src values
23
+ # (metanorma/metanorma-plugin-lutaml#325). A path relative to the
24
+ # docfile resolves to the same schema file at render time and is
25
+ # byte-identical across build directories.
26
+ # The docfile directory may be relative ("."): relative_path_from
27
+ # refuses mixed prefixes, so both sides expand first
28
+ Pathname.new(File.expand_path(resolved))
29
+ .relative_path_from(Pathname.new(File.expand_path(docfile_directory)))
30
+ .to_s
20
31
  end
21
32
 
22
33
  def self.for(model)
@@ -47,6 +58,7 @@ module Metanorma
47
58
  end
48
59
 
49
60
  def decorate_remarks(options)
61
+ promote_schema_prose
50
62
  decorate_own_remarks(options)
51
63
  decorate_child_remark_items(options)
52
64
  traverse_children(options)
@@ -54,7 +66,29 @@ module Metanorma
54
66
 
55
67
  private
56
68
 
69
+ # A bare (*"Schema_name" ...*) remark is the schema-level prose:
70
+ # expressir models it in Schema#remarks, not as a RemarkItem, so
71
+ # renders that walk remark_items (every clause-4 template) drop
72
+ # it (iso-10303#720). Promote it to a leading RemarkItem keyed
73
+ # by the schema path, the same way an entity-tagged remark names
74
+ # its target. The prose stays in Schema#remarks too; templates
75
+ # that already render it there keep working unchanged.
76
+ def promote_schema_prose
77
+ return unless @model.is_a?(Expressir::Model::Declarations::Schema)
78
+ return if @model.remarks.to_a.empty?
79
+
80
+ items = @model.remark_items.to_a
81
+ return if items.any? { |ri| ri.id == @model.id }
82
+
83
+ prose = Expressir::Model::Declarations::RemarkItem.new(
84
+ id: @model.id,
85
+ remarks: @model.remarks.dup,
86
+ )
87
+ @model.remark_items = [prose] + items
88
+ end
89
+
57
90
  def decorate_own_remarks(options)
91
+
58
92
  @model.remarks = ExpressRemarksDecorator.decorate_array(
59
93
  @model.remarks, options
60
94
  )
@@ -18,6 +18,10 @@ module Metanorma
18
18
  #
19
19
  # [lutaml_data,<definitions.lml>,<context>]
20
20
  #
21
+ # Options can be supplied as a JSON object:
22
+ #
23
+ # [lutaml_data,models.lml,store,options={"include_path":"templates"}]
24
+ #
21
25
  class LutamlDataPreprocessor < BasePreprocessor
22
26
  DATA_FILE_EXTENSIONS = %w[.lml .yaml .yml .json].freeze
23
27
  DEFAULT_CONTEXT_NAME = "lutaml_data"
@@ -53,7 +57,7 @@ module Metanorma
53
57
  return [line] unless block_header_match
54
58
 
55
59
  definition_file, instance_file, context_name, options =
56
- parse_header(block_header_match)
60
+ parse_header_logged(block_header_match)
57
61
 
58
62
  end_mark = input_lines.next
59
63
  lines = extract_block_lines(input_lines, end_mark)
@@ -68,10 +72,16 @@ module Metanorma
68
72
  )
69
73
  end
70
74
 
75
+ def parse_header_logged(match)
76
+ parse_header(match)
77
+ rescue StandardError => e
78
+ log_block_failure(e)
79
+ raise e
80
+ end
81
+
71
82
  def parse_header(match)
72
83
  definition_file = match[:definition_file].strip
73
- segments = match[:rest].split(",").map(&:strip)
74
- options, segments = split_options(segments)
84
+ options, segments = split_options(match[:rest])
75
85
 
76
86
  data_segments, name_segments = segments
77
87
  .reject(&:empty?).partition { |segment| data_file?(segment) }
@@ -80,7 +90,18 @@ module Metanorma
80
90
  name_segments.last || DEFAULT_CONTEXT_NAME, options]
81
91
  end
82
92
 
83
- def split_options(segments)
93
+ def split_options(rest)
94
+ object_options = rest.match(/,\s*options=(\{.*\})\s*,?\s*\z/)
95
+ if object_options
96
+ require "json"
97
+ return [JSON.parse(object_options[1]),
98
+ rest[0...object_options.begin(0)].split(",").map(&:strip)]
99
+ end
100
+
101
+ split_inline_options(rest.split(",").map(&:strip))
102
+ end
103
+
104
+ def split_inline_options(segments)
84
105
  options_start = segments.index { |segment| segment.include?("=") }
85
106
  return [{}, segments] unless options_start
86
107
 
@@ -105,12 +126,16 @@ module Metanorma
105
126
 
106
127
  [render_liquid(lines, document, options, context_name, instances)]
107
128
  rescue StandardError => e
129
+ log_block_failure(e)
130
+ raise e
131
+ end
132
+
133
+ def log_block_failure(error)
108
134
  ::Metanorma::Util.log(
109
135
  "[#{self.class.name}] Failed to parse LutaML data block: " \
110
- "#{e.message}",
136
+ "#{error.message}",
111
137
  :error,
112
138
  )
113
- raise e
114
139
  end
115
140
 
116
141
  def render_liquid(lines, document, options, context_name, instances)
@@ -126,7 +151,7 @@ module Metanorma
126
151
  case value
127
152
  when Hash
128
153
  value.transform_keys(&:to_s)
129
- .transform_values { |v| deep_stringify(v) }
154
+ .transform_values { |v| deep_stringify(v) }
130
155
  when Array
131
156
  value.map { |v| deep_stringify(v) }
132
157
  else
@@ -1,7 +1,7 @@
1
1
  module Metanorma
2
2
  module Plugin
3
3
  module Lutaml
4
- VERSION = "0.7.55.pre.alpha.1".freeze
4
+ VERSION = "0.7.55".freeze
5
5
  end
6
6
  end
7
7
  end
@@ -4,12 +4,16 @@ module Metanorma
4
4
  module Plugin
5
5
  module Lutaml
6
6
  module XmiContextBuilder
7
+ MODEL_COLLECTIONS = %i[classes data_types].freeze
8
+
7
9
  def create_context_object( # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
8
10
  lutaml_document, additional_context, options,
9
11
  context_name = "context"
10
12
  )
11
- root_package = package_level(lutaml_document.to_liquid,
12
- options.package_root_level || 1)
13
+ root_context = lutaml_document.to_liquid
14
+ root_package = package_level(
15
+ root_context, options.package_root_level || 1
16
+ )
13
17
  contexts = create_default_context_object(
14
18
  context_name, root_package, additional_context, options
15
19
  )
@@ -17,6 +21,7 @@ module Metanorma
17
21
  if options.packages.nil?
18
22
  contexts[context_name]["render_nested_packages"] = true
19
23
  contexts[context_name]["packages"] = root_package.packages
24
+ add_model_anchor_context(contexts[context_name], root_context)
20
25
 
21
26
  return contexts
22
27
  end
@@ -30,6 +35,7 @@ module Metanorma
30
35
  "render_nested_packages" => !!options.render_nested_packages,
31
36
  },
32
37
  )
38
+ add_model_anchor_context(contexts[context_name], root_context)
33
39
 
34
40
  contexts
35
41
  end
@@ -42,13 +48,27 @@ module Metanorma
42
48
  "name" => root_package.name,
43
49
  "root_packages" => [root_package],
44
50
  "additional_context" => additional_context
45
- .merge("external_classes" => options.external_classes),
51
+ .merge(
52
+ "external_classes" => external_model_targets(
53
+ options.external_classes,
54
+ ),
55
+ ),
46
56
  "skip_unrecognized_connector" => !!options
47
57
  .skip_unrecognized_connector,
48
58
  }
49
59
  contexts
50
60
  end
51
61
 
62
+ def external_model_targets(external_classes)
63
+ external_classes&.transform_values do |target|
64
+ if target.is_a?(String) && target.match?(/\Asection-[^\s#]+\z/)
65
+ "##{target}"
66
+ else
67
+ target
68
+ end
69
+ end
70
+ end
71
+
52
72
  def fill_in_entities_refs_attributes(document, # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
53
73
  lutaml_document, _options)
54
74
  all_children_packages = lutaml_document.packages
@@ -94,6 +114,96 @@ module Metanorma
94
114
  .call(lutaml_document.packages)
95
115
  .merge(children_pks_diags)
96
116
  end
117
+
118
+ def add_model_anchor_context(context, root_package)
119
+ packages = model_packages(root_package)
120
+ context["additional_context"]["model_anchors"] =
121
+ model_reference_targets(packages)
122
+ .merge(model_anchors(packages))
123
+ .merge(model_stub_anchors(root_package))
124
+ end
125
+
126
+ # Ids the XMI templates link to (attribute types, association ends),
127
+ # including ones the XMI never defines. Unnamed; anchors override.
128
+ def model_reference_targets(packages)
129
+ model_entities(packages).each_with_object({}) do |entity, targets|
130
+ Array(entity_list(entity, :attributes)).each do |attribute|
131
+ add_model_anchor(targets, object_xmi_id(attribute), nil)
132
+ end
133
+ Array(entity_list(entity, :associations)).each do |association|
134
+ add_model_anchor(targets, association_target(association), nil)
135
+ end
136
+ end
137
+ end
138
+
139
+ def model_entities(packages)
140
+ packages.flat_map do |package|
141
+ MODEL_COLLECTIONS.flat_map do |collection|
142
+ Array(entity_list(package, collection))
143
+ end
144
+ end
145
+ end
146
+
147
+ def entity_list(object, collection)
148
+ object.public_send(collection) if object.respond_to?(collection)
149
+ end
150
+
151
+ def association_target(association)
152
+ return unless association.respond_to?(:member_end_xmi_id)
153
+
154
+ association.member_end_xmi_id
155
+ end
156
+
157
+ def model_packages(root_package)
158
+ packages_tree(root_package)
159
+ .compact
160
+ .uniq { |package| package_key(package) }
161
+ end
162
+
163
+ def packages_tree(package)
164
+ [package, *child_packages(package).flat_map do |child|
165
+ packages_tree(child)
166
+ end]
167
+ end
168
+
169
+ def child_packages(package)
170
+ Array(package.packages).compact.uniq { |child| package_key(child) }
171
+ end
172
+
173
+ def package_key(package)
174
+ xmi_id = package.xmi_id if package.respond_to?(:xmi_id)
175
+ xmi_id || package.name
176
+ end
177
+
178
+ def model_anchors(packages)
179
+ collections = %w[classes enums data_types]
180
+ packages.each_with_object({}) do |package, anchors|
181
+ add_model_anchor(anchors, object_xmi_id(package), package.name)
182
+ collections.each do |collection|
183
+ next unless package.respond_to?(collection)
184
+
185
+ Array(package.public_send(collection)).each do |entity|
186
+ add_model_anchor(anchors, object_xmi_id(entity), entity.name)
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ def object_xmi_id(object)
193
+ object.xmi_id if object.respond_to?(:xmi_id)
194
+ end
195
+
196
+ def add_model_anchor(anchors, xmi_id, name)
197
+ anchors[xmi_id] = name if xmi_id.is_a?(String) && !xmi_id.empty?
198
+ end
199
+
200
+ def model_stub_anchors(root_package)
201
+ xmi_root_model = root_package.instance_variable_get(:@xmi_root_model)
202
+ Array(xmi_root_model&.extension&.ea_stub)
203
+ .each_with_object({}) do |stub, anchors|
204
+ add_model_anchor(anchors, stub.id, stub.name)
205
+ end
206
+ end
97
207
  end
98
208
  end
99
209
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "asciidoctor"
4
+ require "asciidoctor/extensions"
5
+
6
+ module Metanorma
7
+ module Plugin
8
+ module Lutaml
9
+ # Resolve model links after every macro has generated its actual anchors.
10
+ class XmiModelReference < ::Asciidoctor::Extensions::InlineMacroProcessor
11
+ use_dsl
12
+ named :lutaml_model_reference
13
+ parse_content_as :text
14
+ match(/(?<!\\)&lt;&lt;section-
15
+ (?<target>(?:(?!&gt;&gt;)[^,\s])+?)
16
+ (?:,(?<content>.*?))?&gt;&gt;/x)
17
+
18
+ INVENTORY_ATTRIBUTE = "lutaml_model_anchors"
19
+
20
+ def self.root_document(document)
21
+ document = document.parent_document while document.parent_document
22
+ document
23
+ end
24
+
25
+ def self.register(document, anchors)
26
+ document = root_document(document)
27
+ unless document.attributes.key?(INVENTORY_ATTRIBUTE)
28
+ document.attributes[INVENTORY_ATTRIBUTE] = {}
29
+ document.extensions.inline_macro self
30
+ end
31
+ document.attributes[INVENTORY_ATTRIBUTE]
32
+ .merge!(anchors) { |_, old, new| new || old }
33
+ end
34
+
35
+ def process(parent, target, attrs)
36
+ document = self.class.root_document(parent.document)
37
+ inventory = document.attributes.fetch(INVENTORY_ATTRIBUTE, {})
38
+ name = inventory[target]
39
+ text = reference_text(parent, attrs["text"], target, name)
40
+ value = if inventory.key?(target) && !anchored?(document, target)
41
+ plain_text(parent, text, target)
42
+ else
43
+ label = text ? ",#{text}" : ""
44
+ "&lt;&lt;section-#{target}#{label}&gt;&gt;"
45
+ end
46
+ create_inline(parent, :quoted, value, type: :unquoted)
47
+ end
48
+
49
+ private
50
+
51
+ def anchored?(document, target)
52
+ document.catalog[:refs].key?("section-#{target}")
53
+ end
54
+
55
+ def plain_text(parent, text, target)
56
+ text.to_s.strip.empty? ? parent.sub_specialchars(target) : text
57
+ end
58
+
59
+ def reference_text(parent, text, target, name)
60
+ return text unless name && (text.to_s.strip.empty? || text == target)
61
+
62
+ parent.sub_specialchars(name)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -21,20 +21,26 @@ module Metanorma
21
21
  }.freeze
22
22
  RENDER_STYLE_ATTRIBUTE = "render_style"
23
23
 
24
- def model_representation(lutaml_doc, document, add_context, options) # rubocop:disable Metrics/MethodLength
24
+ def model_representation( # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
25
+ lutaml_doc, document, add_context, options
26
+ )
25
27
  fill_in_entities_refs_attributes(document, lutaml_doc, options)
28
+ contexts = create_context_object(lutaml_doc, add_context, options,
29
+ "context")
26
30
  render_result, errors = Utils.render_liquid_string(
27
31
  template_string: template(options.section_depth || 2,
28
32
  options.render_style,
29
33
  options.include_root),
30
- contexts: create_context_object(lutaml_doc,
31
- add_context,
32
- options,
33
- "context"),
34
+ contexts: contexts,
34
35
  document: document,
35
36
  include_path: template_path(document, options.template_path),
36
37
  )
37
38
  Utils.notify_render_errors(document, errors)
39
+ additional_context = contexts.fetch("context")
40
+ .fetch("additional_context", {})
41
+ XmiModelReference.register(
42
+ document, additional_context["model_anchors"] || {}
43
+ )
38
44
  render_result.split("\n")
39
45
  end
40
46
 
@@ -7,6 +7,7 @@ module Metanorma
7
7
  autoload :XmiCache, "metanorma/plugin/lutaml/xmi_cache"
8
8
  autoload :XmiConfig, "metanorma/plugin/lutaml/xmi_config"
9
9
  autoload :XmiContextBuilder, "metanorma/plugin/lutaml/xmi_context_builder"
10
+ autoload :XmiModelReference, "metanorma/plugin/lutaml/xmi_model_reference"
10
11
  autoload :XmiPackageFilter, "metanorma/plugin/lutaml/xmi_package_filter"
11
12
  autoload :XmiRenderer, "metanorma/plugin/lutaml/xmi_renderer"
12
13
  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.55.pre.alpha.1
4
+ version: 0.7.55
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-10-22 00:00:00.000000000 Z
11
+ date: 2026-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor
@@ -263,6 +263,7 @@ files:
263
263
  - lib/metanorma/plugin/lutaml/xmi_cache.rb
264
264
  - lib/metanorma/plugin/lutaml/xmi_config.rb
265
265
  - lib/metanorma/plugin/lutaml/xmi_context_builder.rb
266
+ - lib/metanorma/plugin/lutaml/xmi_model_reference.rb
266
267
  - lib/metanorma/plugin/lutaml/xmi_package_filter.rb
267
268
  - lib/metanorma/plugin/lutaml/xmi_parse_cache.rb
268
269
  - lib/metanorma/plugin/lutaml/xmi_partial_load_registry.rb