metanorma-document 0.3.0 → 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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +3 -2
  3. data/Rakefile +30 -5
  4. data/frontend/dist/app.css +1 -0
  5. data/frontend/dist/app.iife.js +24 -0
  6. data/frontend/package-lock.json +7 -505
  7. data/frontend/tailwind.config.js +5 -0
  8. data/lib/metanorma/csa_document/root.rb +27 -0
  9. data/lib/metanorma/csa_document.rb +9 -0
  10. data/lib/metanorma/document/cli.rb +52 -0
  11. data/lib/metanorma/document/components/ancillary_blocks/sourcecode_body.rb +14 -0
  12. data/lib/metanorma/document/components/inline/comma_element.rb +3 -0
  13. data/lib/metanorma/document/components/inline/enum_comma_element.rb +3 -0
  14. data/lib/metanorma/document/components/inline/eref_element.rb +4 -1
  15. data/lib/metanorma/document/components/inline/fmt_termsource_element.rb +4 -2
  16. data/lib/metanorma/document/components/inline/math_element.rb +13 -7
  17. data/lib/metanorma/document/components/inline/vocabulary.rb +4 -0
  18. data/lib/metanorma/document/components/multi_paragraph/admonition_block.rb +6 -1
  19. data/lib/metanorma/document/components/paragraphs/paragraph_block.rb +3 -0
  20. data/lib/metanorma/document/components/reference_elements/callout.rb +3 -2
  21. data/lib/metanorma/document/components/tables/table_cell.rb +3 -0
  22. data/lib/metanorma/document/elements/input.rb +44 -0
  23. data/lib/metanorma/document/elements.rb +1 -0
  24. data/lib/metanorma/document/relaton/address.rb +5 -0
  25. data/lib/metanorma/document/relaton/affiliation.rb +3 -0
  26. data/lib/metanorma/document/relaton/bib_item_size.rb +8 -9
  27. data/lib/metanorma/document/relaton/bib_item_type.rb +3 -0
  28. data/lib/metanorma/document/relaton/bibliographic_date.rb +5 -0
  29. data/lib/metanorma/document/relaton/contact_method.rb +5 -0
  30. data/lib/metanorma/document/relaton/contribution_info.rb +6 -2
  31. data/lib/metanorma/document/relaton/contributor.rb +4 -0
  32. data/lib/metanorma/document/relaton/contributor_role.rb +3 -0
  33. data/lib/metanorma/document/relaton/copyright_association.rb +3 -0
  34. data/lib/metanorma/document/relaton/copyright_owner.rb +3 -0
  35. data/lib/metanorma/document/relaton/date_time.rb +3 -0
  36. data/lib/metanorma/document/relaton/document_identifier.rb +4 -0
  37. data/lib/metanorma/document/relaton/document_relation.rb +8 -0
  38. data/lib/metanorma/document/relaton/document_status.rb +4 -0
  39. data/lib/metanorma/document/relaton/formatted_address.rb +4 -0
  40. data/lib/metanorma/document/relaton/full_name.rb +14 -3
  41. data/lib/metanorma/document/relaton/keyword_type.rb +3 -0
  42. data/lib/metanorma/document/relaton/locality_stack.rb +6 -9
  43. data/lib/metanorma/document/relaton/localized_name.rb +8 -5
  44. data/lib/metanorma/document/relaton/logo_element.rb +5 -0
  45. data/lib/metanorma/document/relaton/medium_type.rb +5 -16
  46. data/lib/metanorma/document/relaton/org_identifier.rb +2 -0
  47. data/lib/metanorma/document/relaton/org_subdivision.rb +2 -0
  48. data/lib/metanorma/document/relaton/org_subdivision_identifier.rb +2 -0
  49. data/lib/metanorma/document/relaton/organization.rb +3 -0
  50. data/lib/metanorma/document/relaton/person.rb +7 -1
  51. data/lib/metanorma/document/relaton/person_identifier.rb +3 -0
  52. data/lib/metanorma/document/relaton/place_type.rb +5 -0
  53. data/lib/metanorma/document/relaton/region_element.rb +3 -9
  54. data/lib/metanorma/document/relaton/relaton_collection.rb +2 -0
  55. data/lib/metanorma/document/relaton/series_type.rb +7 -0
  56. data/lib/metanorma/document/relaton/typed_note.rb +4 -0
  57. data/lib/metanorma/document/relaton/typed_title_string.rb +4 -0
  58. data/lib/metanorma/document/relaton/typed_uri.rb +3 -8
  59. data/lib/metanorma/document/relaton/validity_type.rb +8 -10
  60. data/lib/metanorma/document/relaton/variant_full_name.rb +3 -0
  61. data/lib/metanorma/document/relaton/variant_org_name.rb +3 -0
  62. data/lib/metanorma/document/relaton/version_info.rb +5 -0
  63. data/lib/metanorma/document/relaton/vocab_id_type.rb +6 -12
  64. data/lib/metanorma/document/relaton.rb +0 -14
  65. data/lib/metanorma/document/version.rb +1 -1
  66. data/lib/metanorma/document.rb +1 -0
  67. data/lib/metanorma/html/base_renderer.rb +86 -234
  68. data/lib/metanorma/html/concerns/metadata_extraction.rb +61 -0
  69. data/lib/metanorma/html/concerns/presentation_validation.rb +61 -0
  70. data/lib/metanorma/html/concerns/svg_processing.rb +32 -0
  71. data/lib/metanorma/html/concerns/text_extraction.rb +93 -0
  72. data/lib/metanorma/html/concerns/toc_registry.rb +47 -0
  73. data/lib/metanorma/html/csa_renderer.rb +9 -0
  74. data/lib/metanorma/html/drops/figure_drop.rb +33 -10
  75. data/lib/metanorma/html/drops/sourcecode_drop.rb +3 -6
  76. data/lib/metanorma/html/flavor.rb +6 -1
  77. data/lib/metanorma/html/generator.rb +8 -1
  78. data/lib/metanorma/html/iso_renderer.rb +0 -142
  79. data/lib/metanorma/html/renderers/block_renderer.rb +10 -0
  80. data/lib/metanorma/html/renderers/element_order_traversal.rb +15 -1
  81. data/lib/metanorma/html/renderers/inline_renderer.rb +13 -75
  82. data/lib/metanorma/html/standard_renderer.rb +195 -35
  83. data/lib/metanorma/html.rb +1 -51
  84. data/lib/metanorma/iso_document/sections/iso_preface.rb +27 -13
  85. data/lib/metanorma/mirror/default_registry.rb +21 -0
  86. data/lib/metanorma/mirror/handlers/inline/catalog.rb +7 -12
  87. data/lib/metanorma/mirror/handlers/inline/rich_html_renderer.rb +2 -3
  88. data/lib/metanorma/mirror/handlers/sourcecode.rb +1 -1
  89. data/lib/metanorma/mirror/model/guide.rb +3 -2
  90. data/lib/metanorma/mirror/output/formats/inline_format.rb +64 -3
  91. data/lib/metanorma/mirror/output/pipeline.rb +1 -0
  92. data/lib/metanorma/mirror/output.rb +0 -2
  93. data/lib/metanorma/mirror/rewriter.rb +3 -1
  94. data/lib/metanorma/registers/setup.rb +10 -0
  95. data/lib/metanorma/standard_document/annotation_container.rb +2 -0
  96. data/lib/metanorma/standard_document/elements/input.rb +4 -25
  97. data/lib/metanorma/standard_document/metadata/metanorma_extension.rb +12 -2
  98. data/lib/metanorma/standard_document/metadata/presentation_metadata.rb +107 -0
  99. data/lib/metanorma/standard_document/metadata/semantic_metadata.rb +18 -0
  100. data/lib/metanorma/standard_document/metadata/unitsml/amount_of_substance.rb +19 -0
  101. data/lib/metanorma/standard_document/metadata/unitsml/base_quantity.rb +34 -0
  102. data/lib/metanorma/standard_document/metadata/unitsml/dimension.rb +39 -0
  103. data/lib/metanorma/standard_document/metadata/unitsml/dimension_set.rb +19 -0
  104. data/lib/metanorma/standard_document/metadata/unitsml/electric_current.rb +19 -0
  105. data/lib/metanorma/standard_document/metadata/unitsml/enumerated_root_unit.rb +23 -0
  106. data/lib/metanorma/standard_document/metadata/unitsml/length.rb +19 -0
  107. data/lib/metanorma/standard_document/metadata/unitsml/luminous_intensity.rb +19 -0
  108. data/lib/metanorma/standard_document/metadata/unitsml/mass.rb +19 -0
  109. data/lib/metanorma/standard_document/metadata/unitsml/namespace.rb +17 -0
  110. data/lib/metanorma/standard_document/metadata/unitsml/plane_angle.rb +19 -0
  111. data/lib/metanorma/standard_document/metadata/unitsml/prefix.rb +30 -0
  112. data/lib/metanorma/standard_document/metadata/unitsml/prefix_name.rb +21 -0
  113. data/lib/metanorma/standard_document/metadata/unitsml/prefix_set.rb +19 -0
  114. data/lib/metanorma/standard_document/metadata/unitsml/prefix_symbol.rb +21 -0
  115. data/lib/metanorma/standard_document/metadata/unitsml/quantity.rb +28 -0
  116. data/lib/metanorma/standard_document/metadata/unitsml/quantity_name.rb +21 -0
  117. data/lib/metanorma/standard_document/metadata/unitsml/quantity_set.rb +19 -0
  118. data/lib/metanorma/standard_document/metadata/unitsml/root_units.rb +19 -0
  119. data/lib/metanorma/standard_document/metadata/unitsml/thermodynamic_temperature.rb +19 -0
  120. data/lib/metanorma/standard_document/metadata/unitsml/time.rb +19 -0
  121. data/lib/metanorma/standard_document/metadata/unitsml/unit.rb +32 -0
  122. data/lib/metanorma/standard_document/metadata/unitsml/unit_name.rb +21 -0
  123. data/lib/metanorma/standard_document/metadata/unitsml/unit_set.rb +19 -0
  124. data/lib/metanorma/standard_document/metadata/unitsml/unit_symbol.rb +36 -0
  125. data/lib/metanorma/standard_document/metadata/unitsml/unit_system.rb +23 -0
  126. data/lib/metanorma/standard_document/metadata/unitsml/unitsml_root.rb +28 -0
  127. data/lib/metanorma/standard_document/metadata/unitsml.rb +39 -0
  128. data/lib/metanorma/standard_document/metadata.rb +4 -0
  129. data/lib/metanorma/standard_document/sections/misc_container.rb +8 -6
  130. data/lib/metanorma/standard_document/sections/sections.rb +7 -0
  131. data/lib/metanorma/un_document/blocks/un_admonition_block.rb +86 -0
  132. data/lib/metanorma/un_document/blocks.rb +9 -0
  133. data/lib/metanorma/un_document/root.rb +8 -0
  134. data/lib/metanorma/un_document/sections/un_abstract_section.rb +48 -0
  135. data/lib/metanorma/un_document/sections/un_preface.rb +19 -2
  136. data/lib/metanorma/un_document/sections.rb +1 -0
  137. data/lib/metanorma/un_document/un_text_element.rb +116 -0
  138. data/lib/metanorma/un_document.rb +2 -0
  139. data/script/benchmark_render.rb +35 -0
  140. data/tasks/roundtrip_samples.rake +1 -1
  141. metadata +63 -45
  142. data/.rubocop_todo.yml +0 -220
  143. data/TODO.finalize/01-split-multi-class-files.md +0 -8
  144. data/TODO.finalize/02-remove-data-dist-iso-document-frontend.md +0 -6
  145. data/TODO.finalize/03-align-frontend-iife-and-gha-build.md +0 -11
  146. data/TODO.finalize/04-theme-per-flavor-config-driven.md +0 -9
  147. data/TODO.finalize/05-migrate-all-html-to-liquid.md +0 -16
  148. data/TODO.finalize/06-integrate-pubid-library.md +0 -16
  149. data/TODO.finalize/07-replace-relaton-models-with-relaton-bib.md +0 -39
  150. data/TODO.finalize/08-refactor-renderer-architecture.md +0 -25
  151. data/TODO.finalize/09-eliminate-code-smells.md +0 -14
  152. data/TODO.finalize/10-comprehensive-spec-coverage.md +0 -24
  153. data/TODO.finalize/11-config-driven-flavor-metadata.md +0 -20
  154. data/TODO.finalize/12-add-frozen-string-literal.md +0 -6
  155. data/TODO.finalize/13-extract-composition-from-god-class.md +0 -96
  156. data/TODO.finalize/14-config-driven-section-ordering.md +0 -31
  157. data/TODO.finalize/15-theme-directories-per-flavor.md +0 -46
  158. data/TODO.finalize/16-theme-lutaml-model-schema.md +0 -44
  159. data/docs/html-renderer.adoc +0 -261
  160. data/lib/data/dist/iso_document/frontend/assets/index-mwzbzmnK.js +0 -22
  161. data/lib/data/dist/iso_document/frontend/assets/index-pGvKTNid.css +0 -1
  162. data/lib/data/dist/iso_document/frontend/assets/iso-red.svg +0 -53
  163. data/lib/data/dist/iso_document/frontend/index.html +0 -13
  164. data/lib/metanorma/document/relaton/bib_item_size_type.rb +0 -21
  165. data/lib/metanorma/document/relaton/bibliographic_date_type.rb +0 -22
  166. data/lib/metanorma/document/relaton/document_relation_type.rb +0 -31
  167. data/lib/metanorma/document/relaton/iso4217_code.rb +0 -17
  168. data/lib/metanorma/document/relaton/iso8601_date.rb +0 -17
  169. data/lib/metanorma/document/relaton/personal_identifier_type.rb +0 -21
  170. data/lib/metanorma/document/relaton/region_type.rb +0 -20
  171. data/lib/metanorma/document/relaton/series_type_type.rb +0 -21
  172. data/lib/metanorma/document/relaton/specific_locality_type.rb +0 -25
  173. data/lib/metanorma/document/relaton/title_type.rb +0 -21
  174. data/lib/metanorma/mirror/output/html_renderer.rb +0 -95
  175. data/lib/metanorma/mirror/output/html_renderers/block_renderers.rb +0 -178
  176. data/lib/metanorma/mirror/output/html_renderers/inline_renderer.rb +0 -61
  177. data/lib/metanorma/mirror/output/html_renderers/list_renderers.rb +0 -96
  178. data/lib/metanorma/mirror/output/html_renderers/mark_renderers.rb +0 -41
  179. data/lib/metanorma/mirror/output/html_renderers/section_renderers.rb +0 -129
  180. data/lib/metanorma/mirror/output/html_renderers/structural_renderers.rb +0 -80
  181. data/lib/metanorma/mirror/output/html_renderers/table_renderers.rb +0 -87
  182. data/lib/metanorma/mirror/output/html_renderers.rb +0 -76
  183. 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
272
+
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
188
322
 
189
- if term.definition
190
- Array(term.definition).each do |defn|
191
- parts << (render_term_definition(defn) || "")
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)
@@ -11,57 +11,6 @@ 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"
@@ -77,6 +26,7 @@ module Metanorma
77
26
  autoload :IsoRenderer, "metanorma/html/iso_renderer"
78
27
  autoload :BipmRenderer, "metanorma/html/bipm_renderer"
79
28
  autoload :CcRenderer, "metanorma/html/cc_renderer"
29
+ autoload :CsaRenderer, "metanorma/html/csa_renderer"
80
30
  autoload :IccRenderer, "metanorma/html/icc_renderer"
81
31
  autoload :PdfaRenderer, "metanorma/html/pdfa_renderer"
82
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,
@@ -5,19 +5,14 @@ module Metanorma
5
5
  module Handlers
6
6
  module Inline
7
7
  # Shared declarative table of inline marks whose HTML rendering is
8
- # a fixed tag with optional static attrs. Two adapters consume it:
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.
9
12
  #
10
- # - RichHtmlRenderer: renders Metanorma XML elements to HTML
11
- # directly (the fallback / attribute-value path).
12
- # - Output::HtmlRenderers::MarkRenderers: renders mirror Model marks
13
- # to HTML (the primary SSR path).
14
- #
15
- # Both adapters must produce visually equivalent HTML for the same
16
- # mark type — the table is the single source of truth for the
17
- # mark-name to tag-and-attrs mapping. Marks whose attrs vary per
18
- # instance (link, xref, eref, span) carry data in mark.attrs and
19
- # are not in this table; each adapter keeps custom handlers for
20
- # those.
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.
21
16
  module Catalog
22
17
  SIMPLE_WRAPS = {
23
18
  "emphasis" => { tag: :em },
@@ -12,9 +12,8 @@ module Metanorma
12
12
  # Nokogiri::HTML5::Builder to guarantee well-formed, escaped output.
13
13
  #
14
14
  # Simple-wrap elements (em, strong, sub, sup, code, u, s, smallcap,
15
- # bcp14, concept) share their tag-and-attrs mapping with the Model-side
16
- # renderer via Inline::Catalog, so the XML→HTML and Model→HTML paths
17
- # produce visually equivalent output for the same mark type.
15
+ # bcp14, concept) take their tag-and-attrs mapping from the shared
16
+ # Inline::Catalog table.
18
17
  module RichHtmlRenderer
19
18
  # Complex element handlers — these have per-instance attrs or
20
19
  # multi-step rendering and bypass the shared Catalog.
@@ -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
@@ -8,12 +8,42 @@ module Metanorma
8
8
  module Output
9
9
  module Formats
10
10
  class InlineFormat < BaseFormat
11
+ # Classic content styles inlined for the no-JS SSR body: reset +
12
+ # typography + block/inline component styles from the classic
13
+ # asset pipeline. Page chrome (layout, print, transitions, dark,
14
+ # cover, header, footer, toc, search, progress, shortcuts,
15
+ # glossary panel, flavor modules) is owned by the SPA and
16
+ # excluded.
17
+ CONTENT_CSS_MODULES = %w[
18
+ base/_reset
19
+ base/_typography
20
+ components/section
21
+ components/note
22
+ components/example
23
+ components/sourcecode
24
+ components/formula
25
+ components/admonition
26
+ components/table
27
+ components/footnote
28
+ components/figure
29
+ components/term
30
+ components/bibliography
31
+ components/inline
32
+ components/index
33
+ ].freeze
34
+
35
+ class << self
36
+ attr_accessor :missing_bundle_warned
37
+ end
38
+
11
39
  def write(output_path, guide, title: "Metanorma")
12
40
  FileUtils.mkdir_p(File.dirname(output_path))
13
41
 
14
- data_script = "window.METANORMA_DATA = #{safe_json(guide)};"
15
- ssr_body = HtmlRenderer.new(guide).render
16
- head_parts = []
42
+ warn_missing_bundle unless iife_bundle_exists?
43
+
44
+ data_script = "window.METANORMA_DATA = #{safe_json(guide.to_h)};"
45
+ ssr_body = render_ssr_body(guide)
46
+ head_parts = [build_style(classic_content_css)]
17
47
  css_inline = read_css_inline
18
48
  if css_inline && !css_inline.empty?
19
49
  head_parts << build_style(css_inline)
@@ -42,6 +72,37 @@ module Metanorma
42
72
 
43
73
  private
44
74
 
75
+ # SSR body is the classic renderer's document body: identical to
76
+ # the static HTML output, embedded as the no-JS / first-paint
77
+ # placeholder the SPA replaces on mount.
78
+ def render_ssr_body(guide)
79
+ document = guide.document
80
+ unless document
81
+ raise ArgumentError,
82
+ "InlineFormat requires a Guide carrying its source document"
83
+ end
84
+
85
+ renderer = Metanorma::Html::Generator.renderer_for(document).new
86
+ renderer.generate_body(document)
87
+ end
88
+
89
+ def classic_content_css
90
+ css_dir = Metanorma::Html::AssetPipeline::CSS_DIR
91
+ CONTENT_CSS_MODULES.filter_map do |mod|
92
+ path = File.join(css_dir, "#{mod}.css")
93
+ File.read(path) if File.exist?(path)
94
+ end.join("\n")
95
+ end
96
+
97
+ def warn_missing_bundle
98
+ return if self.class.missing_bundle_warned
99
+
100
+ warn "metanorma-document: #{iife_bundle_path} not found — " \
101
+ "writing static HTML without the interactive SPA. " \
102
+ "Run `rake build_frontend` to build it."
103
+ self.class.missing_bundle_warned = true
104
+ end
105
+
45
106
  def build_style(css)
46
107
  Nokogiri::HTML5::Builder.new do |doc|
47
108
  doc.style { doc.text css }
@@ -23,6 +23,7 @@ id_strategy: nil)
23
23
  content: @context.content,
24
24
  meta: @context.meta,
25
25
  title: @context.title,
26
+ document: @context.parsed,
26
27
  )
27
28
  end
28
29
 
@@ -6,8 +6,6 @@ module Metanorma
6
6
  autoload :Pipeline, "#{__dir__}/output/pipeline"
7
7
  autoload :PipelineContext, "#{__dir__}/output/pipeline_context"
8
8
  autoload :Builder, "#{__dir__}/output/builder"
9
- autoload :HtmlRenderers, "#{__dir__}/output/html_renderers"
10
- autoload :HtmlRenderer, "#{__dir__}/output/html_renderer"
11
9
  autoload :Formats, "#{__dir__}/output/formats"
12
10
  end
13
11
  end
@@ -6,7 +6,9 @@ module Metanorma
6
6
  # per-type customization. Despite living in the reverse-conversion
7
7
  # namespace, this class produces a Model graph (not Metanorma XML) —
8
8
  # the output is a fully model-driven representation that can be
9
- # serialized via Model#to_h or rendered via Output::HtmlRenderer.
9
+ # serialized via Model#to_h or embedded into HTML output by
10
+ # Output::Formats::InlineFormat (which renders the source document
11
+ # through the classic Metanorma::Html renderer).
10
12
  #
11
13
  # Instance API:
12
14
  # Rewriter.new(skip: Set.new(%w[review footnotes]))
@@ -44,6 +44,12 @@ module Metanorma
44
44
  Lutaml::Model::GlobalRegister.register(reg)
45
45
  end
46
46
 
47
+ def setup_csa_register
48
+ reg = Lutaml::Model::Register.new(:csa_document,
49
+ fallback: [:iso_document])
50
+ Lutaml::Model::GlobalRegister.register(reg)
51
+ end
52
+
47
53
  def setup_bsi_register
48
54
  iso = Metanorma::IsoDocument
49
55
  reg = Lutaml::Model::Register.new(:bsi_document,
@@ -155,6 +161,10 @@ module Metanorma
155
161
  from_type: sd::Sections::Preface,
156
162
  to_type: Metanorma::UnDocument::Sections::UnPreface,
157
163
  )
164
+ reg.register_global_type_substitution(
165
+ from_type: Metanorma::Document::Components::MultiParagraph::AdmonitionBlock,
166
+ to_type: Metanorma::UnDocument::Blocks::UnAdmonitionBlock,
167
+ )
158
168
  end
159
169
  end
160
170
  end
@@ -10,6 +10,7 @@ module Metanorma
10
10
  attribute :to, :string
11
11
  attribute :type, :string
12
12
  attribute :date, :string
13
+ attribute :semx_id, :string
13
14
  attribute :paragraphs,
14
15
  Metanorma::Document::Components::Paragraphs::ParagraphBlock,
15
16
  collection: true
@@ -22,6 +23,7 @@ module Metanorma
22
23
  map_attribute "to", to: :to
23
24
  map_attribute "type", to: :type
24
25
  map_attribute "date", to: :date
26
+ map_attribute "semx-id", to: :semx_id
25
27
  map_element "p", to: :paragraphs
26
28
  end
27
29
  end