metanorma-plugin-lutaml 0.7.55.pre.alpha.2 → 0.7.56

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: 4df1a140d2fef0dd3a780ac070ab6f6a3906191da12c6d72b30883ad0217bf25
4
- data.tar.gz: ad4569a0ce6ae86577978e7358de9e98c1955227755efa699a7182aca38ab428
3
+ metadata.gz: '082818d3c08e139aaf8f7e51fbc3187bdff832c7ff79020ca3a8057e497c0f0e'
4
+ data.tar.gz: 8524c16a1c751a3a9b03b9e9e0e77ce9040ccd455d6c7cb4c9bd2ae862b5b1c5
5
5
  SHA512:
6
- metadata.gz: 31f6b4b4a670f661412cedc5cdb5538b074054f6618a35e06bfa15080af2dedba5075b237f163e4ba63e8ab99d95b92dc1ed8b99c35111a8abf588616fa62c37
7
- data.tar.gz: 3edd282734af0e5b2d3f6e74daa6e5b9a310e51b022baca413ab00aac448a186a76d22ffb8e5924bacdf0c9c4aaf08b50ce877e2f63a22f83c3d536b68219258
6
+ metadata.gz: 96178a219154034173db6621b51a70821c7c97ca0826db593d8bd9aa38a417156460601db83319f9ee4994451eb16575ab2a1f58befc72fbbf4f6b1bf70809c3
7
+ data.tar.gz: c61b1123f75491be102bc38c90f1f361c169a8cdf3227cc7ee0f87e236119efc506d1145e30ac891be37641e15ca1b7a1be401b97d6dd47606d262373abfcfd9
data/Gemfile CHANGED
@@ -25,13 +25,10 @@ gem "timecop"
25
25
  gem "vcr"
26
26
  gem "webmock"
27
27
 
28
- # leptris 1.9.367.0 is the highest match for standoc's '~> 1.9.361' but
29
- # publishes only linux prebuilds plus a ruby gem with no vendored
30
- # libleptris; windows-latest falls back to that ruby gem and dies on
31
- # require "metanorma-standoc". 1.9.361.1 is the latest that still ships
32
- # x64-mingw-ucrt (and darwin) binaries. Lift once upstream packs Windows
33
- # again.
34
- gem "leptris", "~> 1.9.361.1"
28
+ # moxml requires leptris >= 1.9.386 (older bindings are rejected at
29
+ # load: Moxml::AdapterError). 1.9.39x ships every platform again,
30
+ # including x64-mingw-ucrt, so the old Windows-gap pin is gone.
31
+ gem "leptris", ">= 1.9.386"
35
32
 
36
33
  # pre-release line pins (see metanorma release wave)
37
34
  gem "isodoc", github: "metanorma/isodoc", branch: "main"
@@ -61,7 +61,12 @@ module Metanorma
61
61
 
62
62
  def prefix_relative_paths(line, path_prefix)
63
63
  line.gsub(RELATIVE_PREFIX_MACRO_REGEXP) do |_match|
64
- full_path = File.expand_path(File.join(path_prefix, $3.strip))
64
+ # The prefix is docfile-relative (metanorma-plugin-lutaml#325):
65
+ # expanding it against the process CWD produced absolute image
66
+ # paths under whatever directory the build was launched from
67
+ # (e.g. <repo>/../../schemas/...), which then failed to resolve
68
+ # at render time. An absolute prefix passes through unchanged.
69
+ full_path = File.join(path_prefix, $3.strip)
65
70
  "#{$1}#{$2}#{full_path}#{$4}"
66
71
  end
67
72
  end
@@ -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.2".freeze
4
+ VERSION = "0.7.56".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.2
4
+ version: 0.7.56
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-27 00:00:00.000000000 Z
11
+ date: 2026-10-30 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