metanorma-document 0.2.12 → 0.4.0

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.
Files changed (194) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +3 -2
  3. data/README.adoc +1 -1
  4. data/Rakefile +30 -5
  5. data/frontend/dist/app.css +1 -0
  6. data/frontend/dist/app.iife.js +24 -0
  7. data/frontend/package-lock.json +7 -505
  8. data/frontend/tailwind.config.js +5 -0
  9. data/lib/metanorma/csa_document/root.rb +27 -0
  10. data/lib/metanorma/csa_document.rb +9 -0
  11. data/lib/metanorma/document/cli.rb +52 -0
  12. data/lib/metanorma/document/components/ancillary_blocks/sourcecode_body.rb +14 -0
  13. data/lib/metanorma/document/components/inline/comma_element.rb +3 -0
  14. data/lib/metanorma/document/components/inline/enum_comma_element.rb +3 -0
  15. data/lib/metanorma/document/components/inline/eref_element.rb +4 -1
  16. data/lib/metanorma/document/components/inline/fmt_termsource_element.rb +4 -2
  17. data/lib/metanorma/document/components/inline/math_element.rb +13 -7
  18. data/lib/metanorma/document/components/inline/semx_element.rb +7 -8
  19. data/lib/metanorma/document/components/inline/vocabulary.rb +4 -0
  20. data/lib/metanorma/document/components/multi_paragraph/admonition_block.rb +6 -1
  21. data/lib/metanorma/document/components/paragraphs/paragraph_block.rb +3 -0
  22. data/lib/metanorma/document/components/reference_elements/callout.rb +3 -2
  23. data/lib/metanorma/document/components/tables/table_cell.rb +3 -0
  24. data/lib/metanorma/document/elements/input.rb +44 -0
  25. data/lib/metanorma/document/elements.rb +1 -0
  26. data/lib/metanorma/document/relaton/address.rb +5 -0
  27. data/lib/metanorma/document/relaton/affiliation.rb +3 -0
  28. data/lib/metanorma/document/relaton/bib_item_size.rb +8 -9
  29. data/lib/metanorma/document/relaton/bib_item_type.rb +3 -0
  30. data/lib/metanorma/document/relaton/bibliographic_date.rb +5 -0
  31. data/lib/metanorma/document/relaton/contact_method.rb +5 -0
  32. data/lib/metanorma/document/relaton/contribution_info.rb +6 -2
  33. data/lib/metanorma/document/relaton/contributor.rb +4 -0
  34. data/lib/metanorma/document/relaton/contributor_role.rb +3 -0
  35. data/lib/metanorma/document/relaton/copyright_association.rb +3 -0
  36. data/lib/metanorma/document/relaton/copyright_owner.rb +3 -0
  37. data/lib/metanorma/document/relaton/date_time.rb +3 -0
  38. data/lib/metanorma/document/relaton/document_identifier.rb +4 -0
  39. data/lib/metanorma/document/relaton/document_relation.rb +8 -0
  40. data/lib/metanorma/document/relaton/document_status.rb +4 -0
  41. data/lib/metanorma/document/relaton/formatted_address.rb +4 -0
  42. data/lib/metanorma/document/relaton/full_name.rb +14 -3
  43. data/lib/metanorma/document/relaton/keyword_type.rb +3 -0
  44. data/lib/metanorma/document/relaton/locality_stack.rb +6 -9
  45. data/lib/metanorma/document/relaton/localized_name.rb +8 -5
  46. data/lib/metanorma/document/relaton/logo_element.rb +5 -0
  47. data/lib/metanorma/document/relaton/medium_type.rb +5 -16
  48. data/lib/metanorma/document/relaton/org_identifier.rb +2 -0
  49. data/lib/metanorma/document/relaton/org_subdivision.rb +2 -0
  50. data/lib/metanorma/document/relaton/org_subdivision_identifier.rb +2 -0
  51. data/lib/metanorma/document/relaton/organization.rb +3 -0
  52. data/lib/metanorma/document/relaton/person.rb +7 -1
  53. data/lib/metanorma/document/relaton/person_identifier.rb +3 -0
  54. data/lib/metanorma/document/relaton/place_type.rb +5 -0
  55. data/lib/metanorma/document/relaton/region_element.rb +3 -9
  56. data/lib/metanorma/document/relaton/relaton_collection.rb +2 -0
  57. data/lib/metanorma/document/relaton/series_type.rb +7 -0
  58. data/lib/metanorma/document/relaton/typed_note.rb +4 -0
  59. data/lib/metanorma/document/relaton/typed_title_string.rb +4 -0
  60. data/lib/metanorma/document/relaton/typed_uri.rb +3 -8
  61. data/lib/metanorma/document/relaton/validity_type.rb +8 -10
  62. data/lib/metanorma/document/relaton/variant_full_name.rb +3 -0
  63. data/lib/metanorma/document/relaton/variant_org_name.rb +3 -0
  64. data/lib/metanorma/document/relaton/version_info.rb +5 -0
  65. data/lib/metanorma/document/relaton/vocab_id_type.rb +6 -12
  66. data/lib/metanorma/document/relaton.rb +0 -14
  67. data/lib/metanorma/document/version.rb +1 -1
  68. data/lib/metanorma/document.rb +1 -0
  69. data/lib/metanorma/html/base_renderer.rb +133 -307
  70. data/lib/metanorma/html/component/base.rb +2 -9
  71. data/lib/metanorma/html/concerns/metadata_extraction.rb +61 -0
  72. data/lib/metanorma/html/concerns/presentation_validation.rb +61 -0
  73. data/lib/metanorma/html/concerns/svg_processing.rb +32 -0
  74. data/lib/metanorma/html/concerns/text_extraction.rb +93 -0
  75. data/lib/metanorma/html/concerns/toc_registry.rb +47 -0
  76. data/lib/metanorma/html/csa_renderer.rb +9 -0
  77. data/lib/metanorma/html/drops/figure_drop.rb +33 -10
  78. data/lib/metanorma/html/drops/sourcecode_drop.rb +3 -6
  79. data/lib/metanorma/html/flavor.rb +50 -0
  80. data/lib/metanorma/html/flavor_registry.rb +49 -0
  81. data/lib/metanorma/html/generator.rb +113 -29
  82. data/lib/metanorma/html/iso_renderer.rb +0 -142
  83. data/lib/metanorma/html/renderer_delegation.rb +21 -0
  84. data/lib/metanorma/html/renderers/block_renderer.rb +10 -0
  85. data/lib/metanorma/html/renderers/element_order_traversal.rb +48 -0
  86. data/lib/metanorma/html/renderers/inline_renderer.rb +49 -124
  87. data/lib/metanorma/html/renderers/pubid_renderer.rb +4 -26
  88. data/lib/metanorma/html/renderers.rb +2 -0
  89. data/lib/metanorma/html/standard_renderer.rb +198 -36
  90. data/lib/metanorma/html.rb +4 -51
  91. data/lib/metanorma/iso_document/sections/iso_preface.rb +27 -13
  92. data/lib/metanorma/mirror/default_registry.rb +21 -0
  93. data/lib/metanorma/mirror/handlers/inline/catalog.rb +52 -0
  94. data/lib/metanorma/mirror/handlers/inline/rich_html_renderer.rb +34 -35
  95. data/lib/metanorma/mirror/handlers/inline.rb +1 -0
  96. data/lib/metanorma/mirror/handlers/sourcecode.rb +1 -1
  97. data/lib/metanorma/mirror/model/guide.rb +3 -2
  98. data/lib/metanorma/mirror/output/formats/inline_format.rb +64 -3
  99. data/lib/metanorma/mirror/output/pipeline.rb +1 -0
  100. data/lib/metanorma/mirror/output.rb +0 -2
  101. data/lib/metanorma/mirror/rewriter.rb +3 -1
  102. data/lib/metanorma/mirror/transformer.rb +91 -3
  103. data/lib/metanorma/mirror.rb +0 -1
  104. data/lib/metanorma/registers/setup.rb +10 -0
  105. data/lib/metanorma/standard_document/annotation_container.rb +2 -0
  106. data/lib/metanorma/standard_document/elements/input.rb +4 -25
  107. data/lib/metanorma/standard_document/metadata/metanorma_extension.rb +12 -2
  108. data/lib/metanorma/standard_document/metadata/presentation_metadata.rb +107 -0
  109. data/lib/metanorma/standard_document/metadata/semantic_metadata.rb +18 -0
  110. data/lib/metanorma/standard_document/metadata/unitsml/amount_of_substance.rb +19 -0
  111. data/lib/metanorma/standard_document/metadata/unitsml/base_quantity.rb +34 -0
  112. data/lib/metanorma/standard_document/metadata/unitsml/dimension.rb +39 -0
  113. data/lib/metanorma/standard_document/metadata/unitsml/dimension_set.rb +19 -0
  114. data/lib/metanorma/standard_document/metadata/unitsml/electric_current.rb +19 -0
  115. data/lib/metanorma/standard_document/metadata/unitsml/enumerated_root_unit.rb +23 -0
  116. data/lib/metanorma/standard_document/metadata/unitsml/length.rb +19 -0
  117. data/lib/metanorma/standard_document/metadata/unitsml/luminous_intensity.rb +19 -0
  118. data/lib/metanorma/standard_document/metadata/unitsml/mass.rb +19 -0
  119. data/lib/metanorma/standard_document/metadata/unitsml/namespace.rb +17 -0
  120. data/lib/metanorma/standard_document/metadata/unitsml/plane_angle.rb +19 -0
  121. data/lib/metanorma/standard_document/metadata/unitsml/prefix.rb +30 -0
  122. data/lib/metanorma/standard_document/metadata/unitsml/prefix_name.rb +21 -0
  123. data/lib/metanorma/standard_document/metadata/unitsml/prefix_set.rb +19 -0
  124. data/lib/metanorma/standard_document/metadata/unitsml/prefix_symbol.rb +21 -0
  125. data/lib/metanorma/standard_document/metadata/unitsml/quantity.rb +28 -0
  126. data/lib/metanorma/standard_document/metadata/unitsml/quantity_name.rb +21 -0
  127. data/lib/metanorma/standard_document/metadata/unitsml/quantity_set.rb +19 -0
  128. data/lib/metanorma/standard_document/metadata/unitsml/root_units.rb +19 -0
  129. data/lib/metanorma/standard_document/metadata/unitsml/thermodynamic_temperature.rb +19 -0
  130. data/lib/metanorma/standard_document/metadata/unitsml/time.rb +19 -0
  131. data/lib/metanorma/standard_document/metadata/unitsml/unit.rb +32 -0
  132. data/lib/metanorma/standard_document/metadata/unitsml/unit_name.rb +21 -0
  133. data/lib/metanorma/standard_document/metadata/unitsml/unit_set.rb +19 -0
  134. data/lib/metanorma/standard_document/metadata/unitsml/unit_symbol.rb +36 -0
  135. data/lib/metanorma/standard_document/metadata/unitsml/unit_system.rb +23 -0
  136. data/lib/metanorma/standard_document/metadata/unitsml/unitsml_root.rb +28 -0
  137. data/lib/metanorma/standard_document/metadata/unitsml.rb +39 -0
  138. data/lib/metanorma/standard_document/metadata.rb +4 -0
  139. data/lib/metanorma/standard_document/sections/misc_container.rb +8 -6
  140. data/lib/metanorma/standard_document/sections/sections.rb +7 -0
  141. data/lib/metanorma/un_document/blocks/un_admonition_block.rb +86 -0
  142. data/lib/metanorma/un_document/blocks.rb +9 -0
  143. data/lib/metanorma/un_document/root.rb +8 -0
  144. data/lib/metanorma/un_document/sections/un_abstract_section.rb +48 -0
  145. data/lib/metanorma/un_document/sections/un_preface.rb +19 -2
  146. data/lib/metanorma/un_document/sections.rb +1 -0
  147. data/lib/metanorma/un_document/un_text_element.rb +116 -0
  148. data/lib/metanorma/un_document.rb +2 -0
  149. data/script/benchmark_render.rb +35 -0
  150. data/tasks/roundtrip_samples.rake +1 -1
  151. metadata +78 -50
  152. data/.rubocop_todo.yml +0 -327
  153. data/TODO.finalize/01-split-multi-class-files.md +0 -8
  154. data/TODO.finalize/02-remove-data-dist-iso-document-frontend.md +0 -6
  155. data/TODO.finalize/03-align-frontend-iife-and-gha-build.md +0 -11
  156. data/TODO.finalize/04-theme-per-flavor-config-driven.md +0 -9
  157. data/TODO.finalize/05-migrate-all-html-to-liquid.md +0 -16
  158. data/TODO.finalize/06-integrate-pubid-library.md +0 -16
  159. data/TODO.finalize/07-replace-relaton-models-with-relaton-bib.md +0 -39
  160. data/TODO.finalize/08-refactor-renderer-architecture.md +0 -25
  161. data/TODO.finalize/09-eliminate-code-smells.md +0 -14
  162. data/TODO.finalize/10-comprehensive-spec-coverage.md +0 -24
  163. data/TODO.finalize/11-config-driven-flavor-metadata.md +0 -20
  164. data/TODO.finalize/12-add-frozen-string-literal.md +0 -6
  165. data/TODO.finalize/13-extract-composition-from-god-class.md +0 -96
  166. data/TODO.finalize/14-config-driven-section-ordering.md +0 -31
  167. data/TODO.finalize/15-theme-directories-per-flavor.md +0 -46
  168. data/TODO.finalize/16-theme-lutaml-model-schema.md +0 -44
  169. data/docs/html-renderer.adoc +0 -261
  170. data/lib/data/dist/iso_document/frontend/assets/index-mwzbzmnK.js +0 -22
  171. data/lib/data/dist/iso_document/frontend/assets/index-pGvKTNid.css +0 -1
  172. data/lib/data/dist/iso_document/frontend/assets/iso-red.svg +0 -53
  173. data/lib/data/dist/iso_document/frontend/index.html +0 -13
  174. data/lib/metanorma/document/relaton/bib_item_size_type.rb +0 -21
  175. data/lib/metanorma/document/relaton/bibliographic_date_type.rb +0 -22
  176. data/lib/metanorma/document/relaton/document_relation_type.rb +0 -31
  177. data/lib/metanorma/document/relaton/iso4217_code.rb +0 -17
  178. data/lib/metanorma/document/relaton/iso8601_date.rb +0 -17
  179. data/lib/metanorma/document/relaton/personal_identifier_type.rb +0 -21
  180. data/lib/metanorma/document/relaton/region_type.rb +0 -20
  181. data/lib/metanorma/document/relaton/series_type_type.rb +0 -21
  182. data/lib/metanorma/document/relaton/specific_locality_type.rb +0 -25
  183. data/lib/metanorma/document/relaton/title_type.rb +0 -21
  184. data/lib/metanorma/mirror/metanorma_to_mirror.rb +0 -101
  185. data/lib/metanorma/mirror/output/html_renderer.rb +0 -95
  186. data/lib/metanorma/mirror/output/html_renderers/block_renderers.rb +0 -178
  187. data/lib/metanorma/mirror/output/html_renderers/inline_renderer.rb +0 -61
  188. data/lib/metanorma/mirror/output/html_renderers/list_renderers.rb +0 -96
  189. data/lib/metanorma/mirror/output/html_renderers/mark_renderers.rb +0 -64
  190. data/lib/metanorma/mirror/output/html_renderers/section_renderers.rb +0 -129
  191. data/lib/metanorma/mirror/output/html_renderers/structural_renderers.rb +0 -80
  192. data/lib/metanorma/mirror/output/html_renderers/table_renderers.rb +0 -87
  193. data/lib/metanorma/mirror/output/html_renderers.rb +0 -76
  194. data/lib/metanorma/standard_document/annotation.rb +0 -37
@@ -160,57 +160,217 @@ module Metanorma
160
160
 
161
161
  # --- Term rendering ---
162
162
 
163
+ # Unified term entry renderer. Handles both semantic-only term models
164
+ # (StandardDocument::Terms::Term) and presentation-aware models with
165
+ # fmt-* attributes (IsoDocument::Terms::IsoTerm): safe_attr returns nil
166
+ # for attributes a model does not declare, so the fmt-* branches are
167
+ # skipped automatically on semantic-only models.
163
168
  def render_term(term, **_opts)
164
- attrs = element_attrs(id: safe_attr(term, :id))
169
+ attrs = element_attrs(id: safe_attr(term, :id), **term_data_attrs(term))
170
+ fmt_definition = safe_attr(term, :fmt_definition)
171
+
165
172
  parts = []
166
- term.preferred&.each do |d|
167
- parts << (render_term_designation(d, "preferred") || "")
173
+ parts << (render_term_number(term) || "")
174
+ parts << render_term_designations(term, :fmt_preferred, :preferred,
175
+ "preferred")
176
+ parts << render_term_designations(term, :fmt_admitted, :admitted,
177
+ "admitted")
178
+ parts << render_term_designations(term, :fmt_deprecates, :deprecates,
179
+ "deprecated")
180
+ parts << (render_term_domain(term, fmt_definition) || "")
181
+ parts << render_term_definitions(term, fmt_definition)
182
+ parts << render_term_note_parts(term)
183
+ parts << render_term_example_parts(term)
184
+ parts << render_term_source_parts(term)
185
+ safe_attr(term, :admonition)&.each do |admonition|
186
+ parts << (render_admonition(admonition) || "")
168
187
  end
169
- term.admitted&.each do |d|
170
- parts << (render_term_designation(d, "admitted") || "")
188
+ safe_attr(term, :term)&.each { |sub| parts << (render_term(sub) || "") }
189
+ render_liquid("_element.html.liquid", {
190
+ "tag" => "div",
191
+ "extra_attrs" => attrs,
192
+ "content" => parts.join,
193
+ })
194
+ end
195
+
196
+ def term_data_attrs(term)
197
+ term_name = extract_term_name(term)
198
+ term_definition = extract_term_definition(term)
199
+ data_attrs = {}
200
+ if term_name && !term_name.empty?
201
+ data_attrs["data-term-name"] = term_name
171
202
  end
172
- term.deprecates&.each do |d|
173
- parts << (render_term_designation(d, "deprecated") || "")
203
+ if term_definition && !term_definition.empty?
204
+ data_attrs["data-term-definition"] = term_definition
174
205
  end
175
-
176
- if term.domain
177
- domain_text = if term.domain.is_a?(String)
178
- term.domain
206
+ data_attrs
207
+ end
208
+
209
+ def render_term_number(term)
210
+ fmt_name = safe_attr(term, :fmt_name)
211
+ term_number = safe_attr(term, :term_number)
212
+ if fmt_name
213
+ render_liquid("_term_number.html.liquid", {
214
+ "content" => render_inline_element(fmt_name),
215
+ })
216
+ elsif term_number
217
+ number_text = if term_number.is_a?(String)
218
+ term_number
179
219
  else
180
- safe_attr(term.domain, :text).to_s
220
+ extract_text_value(term_number)
181
221
  end
182
- unless domain_text.to_s.empty?
183
- parts << render_liquid("_term_domain.html.liquid", {
184
- "text" => escape_html(domain_text),
185
- }).to_s
222
+ render_liquid("_term_number.html.liquid", {
223
+ "content" => escape_html(number_text),
224
+ })
225
+ end
226
+ end
227
+
228
+ # Designations (preferred/admitted/deprecated): fmt-* presentation
229
+ # elements win when present; otherwise render semantic designations.
230
+ def render_term_designations(term, fmt_attr, semantic_attr, type)
231
+ fmt = safe_attr(term, fmt_attr)
232
+ fmt_list = fmt.is_a?(Array) ? fmt : [fmt].compact
233
+ parts = []
234
+ if fmt_list.empty?
235
+ term.public_send(semantic_attr)&.each do |designation|
236
+ parts << (render_term_designation(designation, type) || "")
237
+ end
238
+ else
239
+ fmt_list.each do |element|
240
+ element.p&.each { |para| parts << (render_paragraph(para) || "") }
186
241
  end
187
242
  end
243
+ parts.join
244
+ end
245
+
246
+ def render_term_domain(term, fmt_definition)
247
+ return nil if fmt_definition
248
+
249
+ domain = term.domain
250
+ return nil unless domain
251
+
252
+ domain_text = domain.is_a?(String) ? domain : safe_attr(domain, :text).to_s
253
+ return nil if domain_text.empty?
254
+
255
+ render_liquid("_term_domain.html.liquid", {
256
+ "text" => escape_html(domain_text),
257
+ }).to_s
258
+ end
259
+
260
+ def render_term_definitions(term, fmt_definition)
261
+ return render_ordered_content(fmt_definition) || "" if fmt_definition
262
+
263
+ parts = []
264
+ safe_attr(term, :p)&.each do |para|
265
+ parts << (render_paragraph(para) || "")
266
+ end
267
+ term.definition&.each do |definition|
268
+ parts << (render_term_definition(definition) || "")
269
+ end
270
+ parts.join
271
+ end
188
272
 
189
- if term.definition
190
- Array(term.definition).each do |defn|
191
- parts << (render_term_definition(defn) || "")
273
+ def render_term_note_parts(term)
274
+ notes = safe_attr(term, :termnote) || Array(safe_attr(term, :note))
275
+ parts = []
276
+ notes.each_with_index do |note, i|
277
+ parts << (render_term_note_item(note, i) || "")
278
+ end
279
+ parts.join
280
+ end
281
+
282
+ # A term note can be a raw String (rendered with a "Note N to entry"
283
+ # label), a termnote element (labelled wrapper), or a plain block
284
+ # (rendered as a generic note).
285
+ def render_term_note_item(note, index)
286
+ if note.is_a?(String)
287
+ render_liquid("_term_text_note.html.liquid", {
288
+ "label" => "Note #{index + 1} to entry: ",
289
+ "content" => escape_html(note),
290
+ }).to_s
291
+ elsif term_scoped_block?(note)
292
+ render_term_note(note)
293
+ else
294
+ render_note(note)
295
+ end
296
+ end
297
+
298
+ def render_term_example_parts(term)
299
+ examples = safe_attr(term, :termexample) ||
300
+ Array(safe_attr(term, :example))
301
+ examples.map do |example|
302
+ render_term_example_item(example) || ""
303
+ end.join
304
+ end
305
+
306
+ # A term example can be a termexample element (labelled wrapper), a
307
+ # raw String, or a plain paragraph block (both rendered as paragraphs).
308
+ # render_paragraph cannot take a bare String (render_mixed_inline
309
+ # expects a model), so Strings go through the paragraph template
310
+ # directly — same markup render_paragraph would emit.
311
+ def render_term_example_item(example)
312
+ if example.is_a?(String)
313
+ return render_liquid("_paragraph.html.liquid", {
314
+ "attrs" => "",
315
+ "content" => escape_html(example),
316
+ })
317
+ end
318
+ return render_term_example(example) if term_scoped_block?(example)
319
+
320
+ render_paragraph(example)
321
+ end
322
+
323
+ # Termnote/termexample elements wrap block children (`p`, lists);
324
+ # plain paragraph blocks and Strings do not declare a `p` attribute.
325
+ def term_scoped_block?(node)
326
+ node.is_a?(Lutaml::Model::Serializable) &&
327
+ node.class.attributes.key?(:p)
328
+ end
329
+
330
+ def render_term_source_parts(term)
331
+ fmt_termsource = safe_attr(term, :fmt_termsource)
332
+ parts = []
333
+ if fmt_termsource && !fmt_termsource.empty?
334
+ fmt_termsource.each do |source|
335
+ parts << render_liquid("_paragraph.html.liquid", {
336
+ "attrs" => " class=\"term-source\"",
337
+ "content" => render_mixed_inline(source),
338
+ })
339
+ end
340
+ else
341
+ term.source&.each { |src| parts << (render_term_source(src) || "") }
342
+ safe_attr(term, :termsource)&.each do |source|
343
+ parts << (render_term_source_element(source) || "")
192
344
  end
193
345
  end
346
+ parts.join
347
+ end
194
348
 
195
- term.note&.each_with_index do |note, i|
196
- if note.is_a?(String)
197
- label = "Note #{i + 1} to entry: "
198
- parts << render_liquid("_term_text_note.html.liquid", {
199
- "label" => label,
200
- "content" => escape_html(note),
201
- }).to_s
202
- else
203
- parts << (render_note(note) || "")
349
+ def extract_term_name(term)
350
+ fmt_pref = safe_attr(term, :fmt_preferred)
351
+ if fmt_pref && !fmt_pref.empty?
352
+ fp = fmt_pref.first
353
+ if fp.p && !fp.p.empty?
354
+ return extract_plain_text(fp.p.first)
204
355
  end
205
356
  end
357
+ if term.preferred && !term.preferred.empty?
358
+ return extract_designation_name(term.preferred.first).to_s
359
+ end
206
360
 
207
- term.example&.each { |ex| parts << (render_paragraph(ex) || "") }
208
- term.source&.each { |src| parts << (render_term_source(src) || "") }
209
- render_liquid("_element.html.liquid", {
210
- "tag" => "div",
211
- "extra_attrs" => attrs,
212
- "content" => parts.join,
213
- })
361
+ safe_attr(term, :id).to_s.delete_prefix("term-")
362
+ end
363
+
364
+ def extract_term_definition(term)
365
+ fmt_def = safe_attr(term, :fmt_definition)
366
+ return extract_plain_text(fmt_def) if fmt_def
367
+
368
+ term_p = safe_attr(term, :p)
369
+ if term_p && !term_p.empty?
370
+ text = term_p.map { |para| extract_plain_text(para) }.join(" ")
371
+ return text.strip unless text.strip.empty?
372
+ end
373
+ nil
214
374
  end
215
375
 
216
376
  def render_term_designation(designation, _type)
@@ -454,11 +614,13 @@ module Metanorma
454
614
  [prefix_text, rest.empty? ? nil : rest]
455
615
  end
456
616
 
617
+ PREFERRED_LINK_TYPES = %w[src citation].freeze
618
+
457
619
  def bibitem_url(item)
458
620
  links = Array(item.link)
459
621
  return nil if links.empty?
460
622
 
461
- preferred = links.find { |l| ["src", "citation"].include?(l.type) }
623
+ preferred = links.find { |l| PREFERRED_LINK_TYPES.include?(l.type) }
462
624
  return preferred.content.to_s if preferred && !preferred.content.to_s.empty?
463
625
 
464
626
  non_rss = links.find do |l|
@@ -11,61 +11,13 @@ module Metanorma
11
11
  Liquid::Environment.default.file_system = Liquid::LocalFileSystem.new(
12
12
  TEMPLATES_ROOT, "_%s.html.liquid"
13
13
  )
14
-
15
- Moxml::Adapter::Nokogiri.class_eval do
16
- class << self
17
- alias_method :_original_children_patched, :children
18
- remove_method :children
19
- end
20
-
21
- def self.children(node)
22
- node.children.to_a
23
- end
24
- end
25
- end
26
-
27
- # Patch missing Moxml module method used by lutaml-model XML serializer
28
- ::Moxml.class_eval do
29
- def self.preprocess_entities(text)
30
- if block_given?
31
- yield text
32
- else
33
- text
34
- end
35
- end
36
- end
37
-
38
- module Html
39
- Lutaml::Xml::XmlElement.class_eval do
40
- remove_method :order
41
-
42
- def order
43
- return @order_cache if @order_cache
44
-
45
- @order_cache = children.filter_map do |child|
46
- if child.text?
47
- Lutaml::Xml::Element.new("Text", "text",
48
- text_content: child.text,
49
- node_type: :text)
50
- elsif child.cdata?
51
- Lutaml::Xml::Element.new("Text", "#cdata-section",
52
- text_content: child.text,
53
- node_type: :cdata)
54
- elsif child.comment?
55
- nil
56
- else
57
- Lutaml::Xml::Element.new("Element", child.unprefixed_name,
58
- node_type: :element,
59
- namespace_uri: child.namespace_uri,
60
- namespace_prefix: child.namespace_prefix)
61
- end
62
- end
63
- end
64
- end
65
14
  end
66
15
 
67
16
  autoload :BaseRenderer, "metanorma/html/base_renderer"
17
+ autoload :Flavor, "metanorma/html/flavor"
18
+ autoload :FlavorRegistry, "metanorma/html/flavor_registry"
68
19
  autoload :Generator, "metanorma/html/generator"
20
+ autoload :RendererDelegation, "metanorma/html/renderer_delegation"
69
21
  autoload :Theme, "metanorma/html/theme"
70
22
  autoload :AssetPipeline, "metanorma/html/asset_pipeline"
71
23
  autoload :Component, "metanorma/html/component"
@@ -74,6 +26,7 @@ module Metanorma
74
26
  autoload :IsoRenderer, "metanorma/html/iso_renderer"
75
27
  autoload :BipmRenderer, "metanorma/html/bipm_renderer"
76
28
  autoload :CcRenderer, "metanorma/html/cc_renderer"
29
+ autoload :CsaRenderer, "metanorma/html/csa_renderer"
77
30
  autoload :IccRenderer, "metanorma/html/icc_renderer"
78
31
  autoload :PdfaRenderer, "metanorma/html/pdfa_renderer"
79
32
  autoload :IecRenderer, "metanorma/html/iec_renderer"
@@ -4,30 +4,44 @@ module Metanorma
4
4
  module IsoDocument
5
5
  module Sections
6
6
  # Prefatory clauses in an ISO/IEC document.
7
- # Extends StandardDocument::Preface with ISO-specific section types:
8
- # abstract IsoAbstractSection
9
- # foreword → IsoForewordSection
10
- # introduction IsoClauseSection
11
- # ISO preface mandates foreword; isodoc preface is permissive.
12
- class IsoPreface < Metanorma::StandardDocument::Sections::Preface
13
- # Override: ISO uses specific section types
7
+ # Corresponds to isostandard.rng:
8
+ # preface = element preface { abstract?, foreword, introduction? }
9
+ #
10
+ # This class deliberately does NOT inherit StandardDocument::Preface:
11
+ # lutaml-model deep-duplicates a parent's XML mappings into subclasses,
12
+ # so inheriting would keep the isodoc-level acknowledgements and
13
+ # executivesummary mappings that the ISO grammar forbids. Attributes
14
+ # are composed directly instead.
15
+ #
16
+ # The `clause` mapping is a documented extension required by real ISO
17
+ # presentation output, which emits front-matter clauses such as
18
+ # `<clause type="toc">` ahead of the foreword. `content` mirrors
19
+ # `clause` for the generic preface-content consumers (e.g. the mirror
20
+ # renderer), matching the runtime behavior of the previous inherited
21
+ # dual mapping.
22
+ class IsoPreface < Lutaml::Model::Serializable
14
23
  attribute :abstract, IsoAbstractSection
15
24
  attribute :foreword, IsoForewordSection
16
25
  attribute :introduction, IsoClauseSection
17
26
 
18
- # Generic clauses in preface use IsoClauseSection
27
+ # Presentation front-matter clauses (e.g. table of contents placeholder)
19
28
  attribute :clause, IsoClauseSection, collection: true
29
+ attribute :content, Metanorma::StandardDocument::Sections::ContentSection,
30
+ collection: true
20
31
 
21
- # Acknowledgements and executivesummary use IsoClauseSection
22
- attribute :acknowledgements, IsoClauseSection
23
- attribute :executivesummary, IsoClauseSection
32
+ # Presentation-specific attributes
33
+ attribute :semx_id, :string
34
+ attribute :displayorder, :integer
24
35
 
25
36
  xml do
26
37
  element "preface"
27
38
  ordered
28
39
 
29
- Metanorma::StandardDocument::SectionXmlMapping.apply_preface_elements(self)
30
- map_element "clause", to: :clause
40
+ map_element "abstract", to: :abstract
41
+ map_element "foreword", to: :foreword
42
+ map_element "introduction", to: :introduction
43
+ map_element "clause", to: :clause
44
+ map_element "clause", to: :content
31
45
 
32
46
  Metanorma::StandardDocument::SectionXmlMapping.apply_preface_attributes(self)
33
47
  end
@@ -153,6 +153,27 @@ module Metanorma
153
153
  Handlers::Structural,
154
154
  method_name: :preface,
155
155
  )
156
+ # IsoPreface and UnPreface deliberately do not inherit
157
+ # StandardDocument::Preface (grammar-strict classes compose from
158
+ # mixins), so they need explicit registrations — the registry
159
+ # resolves handlers through class ancestry.
160
+ registry.register(
161
+ Metanorma::IsoDocument::Sections::IsoPreface,
162
+ Handlers::Structural,
163
+ method_name: :preface,
164
+ )
165
+ registry.register(
166
+ Metanorma::UnDocument::Sections::UnPreface,
167
+ Handlers::Structural,
168
+ method_name: :preface,
169
+ )
170
+ # UnAbstractSection likewise composes instead of inheriting
171
+ # ContentSection.
172
+ registry.register(
173
+ Metanorma::UnDocument::Sections::UnAbstractSection,
174
+ Handlers::Section,
175
+ method_name: :content_section,
176
+ )
156
177
  registry.register(
157
178
  Metanorma::StandardDocument::Sections::Sections,
158
179
  Handlers::Structural,
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Mirror
5
+ module Handlers
6
+ module Inline
7
+ # Shared declarative table of inline marks whose HTML rendering is
8
+ # a fixed tag with optional static attrs. It is consumed by
9
+ # RichHtmlRenderer, which renders Metanorma XML elements to HTML
10
+ # directly (the fallback / attribute-value path) during the
11
+ # forward transform into the mirror IR.
12
+ #
13
+ # Marks whose attrs vary per instance (link, xref, eref, span)
14
+ # carry data in mark.attrs and are not in this table; each
15
+ # consumer keeps custom handlers for those.
16
+ module Catalog
17
+ SIMPLE_WRAPS = {
18
+ "emphasis" => { tag: :em },
19
+ "strong" => { tag: :strong },
20
+ "subscript" => { tag: :sub },
21
+ "superscript" => { tag: :sup },
22
+ "code" => { tag: :code },
23
+ "underline" => { tag: :u },
24
+ "strike" => { tag: :s },
25
+ "smallcap" => { tag: :span, style: "font-variant: small-caps" },
26
+ "concept" => { tag: :span, class: "concept" },
27
+ "bcp14" => { tag: :span, class: "bcp14" },
28
+ "footnote" => { tag: :sup, class: "footnote-inline" },
29
+ "stem" => { tag: :span, class: "stem" },
30
+ }.freeze
31
+
32
+ # XML element classes that render via SIMPLE_WRAPS without
33
+ # per-instance attrs. Maps element class → mark type (catalog
34
+ # key). RichHtmlRenderer uses this to dispatch simple elements
35
+ # through the shared table.
36
+ SIMPLE_ELEMENTS = {
37
+ Metanorma::Document::Components::Inline::EmRawElement => "emphasis",
38
+ Metanorma::Document::Components::Inline::StrongRawElement => "strong",
39
+ Metanorma::Document::Components::Inline::SubElement => "subscript",
40
+ Metanorma::Document::Components::Inline::SupElement => "superscript",
41
+ Metanorma::Document::Components::Inline::TtElement => "code",
42
+ Metanorma::Document::Components::TextElements::UnderlineElement => "underline",
43
+ Metanorma::Document::Components::TextElements::StrikeElement => "strike",
44
+ Metanorma::Document::Components::Inline::SmallCapElement => "smallcap",
45
+ Metanorma::Document::Components::Inline::Bcp14Element => "bcp14",
46
+ Metanorma::Document::Components::Inline::ConceptElement => "concept",
47
+ }.freeze
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -10,35 +10,14 @@ module Metanorma
10
10
  # representation of inline content is required (e.g., attribute values,
11
11
  # fallback rendering). All HTML construction goes through
12
12
  # Nokogiri::HTML5::Builder to guarantee well-formed, escaped output.
13
+ #
14
+ # Simple-wrap elements (em, strong, sub, sup, code, u, s, smallcap,
15
+ # bcp14, concept) take their tag-and-attrs mapping from the shared
16
+ # Inline::Catalog table.
13
17
  module RichHtmlRenderer
14
- RENDERERS = {
15
- Metanorma::Document::Components::Inline::EmRawElement => ->(el) {
16
- wrap { |d| d.em { d << raw(extract(el)) } }
17
- },
18
- Metanorma::Document::Components::Inline::StrongRawElement => ->(el) {
19
- wrap { |d| d.strong { d << raw(extract(el)) } }
20
- },
21
- Metanorma::Document::Components::Inline::SubElement => ->(el) {
22
- wrap { |d| d.sub { d << raw(extract(el)) } }
23
- },
24
- Metanorma::Document::Components::Inline::SupElement => ->(el) {
25
- wrap { |d| d.sup { d << raw(extract(el)) } }
26
- },
27
- Metanorma::Document::Components::Inline::TtElement => ->(el) {
28
- wrap { |d| d.code { d << raw(extract(el)) } }
29
- },
30
- Metanorma::Document::Components::TextElements::UnderlineElement => ->(el) {
31
- wrap { |d| d.u { d << raw(extract(el)) } }
32
- },
33
- Metanorma::Document::Components::TextElements::StrikeElement => ->(el) {
34
- wrap { |d| d.s { d << raw(extract(el)) } }
35
- },
36
- Metanorma::Document::Components::Inline::SmallCapElement => ->(el) {
37
- wrap { |d| d.span { d << raw(extract(el)) } }
38
- },
39
- Metanorma::Document::Components::Inline::Bcp14Element => ->(el) {
40
- wrap { |d| d.span { d << raw(extract(el)) } }
41
- },
18
+ # Complex element handlers — these have per-instance attrs or
19
+ # multi-step rendering and bypass the shared Catalog.
20
+ COMPLEX_RENDERERS = {
42
21
  Metanorma::Document::Components::Inline::StemInlineElement => ->(el) {
43
22
  for_stem(el)
44
23
  },
@@ -57,9 +36,6 @@ module Metanorma
57
36
  Metanorma::Document::Components::Inline::FnElement => ->(el) {
58
37
  for_fn(el)
59
38
  },
60
- Metanorma::Document::Components::Inline::ConceptElement => ->(el) {
61
- wrap { |d| d.span(class: "concept") { d << raw(extract(el)) } }
62
- },
63
39
  Metanorma::Document::Components::Inline::SpanElement => ->(el) {
64
40
  for_span(el)
65
41
  },
@@ -93,8 +69,25 @@ module Metanorma
93
69
  end
94
70
 
95
71
  def self.render_element(element)
96
- renderer = RENDERERS[element.class]
97
- renderer ? renderer.call(element) : extract(element)
72
+ complex = COMPLEX_RENDERERS[element.class]
73
+ return complex.call(element) if complex
74
+
75
+ mark_type = Catalog::SIMPLE_ELEMENTS[element.class]
76
+ return render_simple(mark_type, element) if mark_type
77
+
78
+ extract(element)
79
+ end
80
+
81
+ def self.render_simple(mark_type, element)
82
+ spec = Catalog::SIMPLE_WRAPS[mark_type]
83
+ attrs = spec.except(:tag)
84
+ wrap do |d|
85
+ if attrs.empty?
86
+ d.public_send(spec[:tag]) { d << raw(extract(element)) }
87
+ else
88
+ d.public_send(spec[:tag], attrs) { d << raw(extract(element)) }
89
+ end
90
+ end
98
91
  end
99
92
 
100
93
  def self.for_stem(element)
@@ -105,7 +98,10 @@ module Metanorma
105
98
  .sub(/\s*xmlns(:\w+)?="[^"]*"\s*/, " ")
106
99
  .gsub(/\s+/, " ")
107
100
  .strip
108
- wrap { |d| d.span(class: "inline-math") { d << raw(mathml) } }
101
+ spec = Catalog::SIMPLE_WRAPS["stem"]
102
+ wrap do |d|
103
+ d.public_send(spec[:tag], spec.except(:tag)) { d << raw(mathml) }
104
+ end
109
105
  end
110
106
 
111
107
  def self.for_xref(element)
@@ -130,7 +126,10 @@ module Metanorma
130
126
 
131
127
  def self.for_fn(element)
132
128
  reference = SafeAttr.read(element, :reference) || ""
133
- wrap { |d| d.sup(class: "footnote-inline") { d.text reference } }
129
+ spec = Catalog::SIMPLE_WRAPS["footnote"]
130
+ wrap do |d|
131
+ d.public_send(spec[:tag], spec.except(:tag)) { d.text reference }
132
+ end
134
133
  end
135
134
 
136
135
  def self.for_span(element)
@@ -6,6 +6,7 @@ module Metanorma
6
6
  module Inline
7
7
  autoload :TextExtractor, "#{__dir__}/inline/text_extractor"
8
8
  autoload :RichHtmlRenderer, "#{__dir__}/inline/rich_html_renderer"
9
+ autoload :Catalog, "#{__dir__}/inline/catalog"
9
10
 
10
11
  MARK_BUILDERS = {
11
12
  Metanorma::Document::Components::Inline::EmRawElement => ->(_el) {
@@ -11,7 +11,7 @@ module Metanorma
11
11
 
12
12
  body = SafeAttr.read(element, :body)
13
13
  text = if body
14
- Array(body.content).join
14
+ body.decoded_content
15
15
  elsif SafeAttr.read(element, :content).is_a?(String)
16
16
  SafeAttr.read(element, :content)
17
17
  else
@@ -4,12 +4,13 @@ module Metanorma
4
4
  module Mirror
5
5
  module Model
6
6
  class Guide
7
- attr_reader :content, :meta, :title
7
+ attr_reader :content, :meta, :title, :document
8
8
 
9
- def initialize(content:, meta: {}, title: nil)
9
+ def initialize(content:, meta: {}, title: nil, document: nil)
10
10
  @content = content
11
11
  @meta = meta
12
12
  @title = title
13
+ @document = document
13
14
  end
14
15
 
15
16
  def to_h