metanorma-document 0.3.0 → 0.5.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 (197) hide show
  1. checksums.yaml +4 -4
  2. data/BUGS.sts/08-list-items-duplicate-bullets.md +45 -0
  3. data/CLAUDE.md +3 -2
  4. data/Rakefile +30 -5
  5. data/data/stylesheets/components/header.css +3 -5
  6. data/data/stylesheets/components/lists.css +14 -0
  7. data/frontend/dist/app.css +1 -0
  8. data/frontend/dist/app.iife.js +24 -0
  9. data/frontend/package-lock.json +7 -505
  10. data/frontend/tailwind.config.js +5 -0
  11. data/lib/metanorma/{oiml_document → csa_document}/root.rb +2 -2
  12. data/lib/metanorma/csa_document.rb +9 -0
  13. data/lib/metanorma/document/cli.rb +52 -0
  14. data/lib/metanorma/document/components/ancillary_blocks/sourcecode_body.rb +14 -0
  15. data/lib/metanorma/document/components/inline/comma_element.rb +3 -0
  16. data/lib/metanorma/document/components/inline/enum_comma_element.rb +3 -0
  17. data/lib/metanorma/document/components/inline/eref_element.rb +4 -1
  18. data/lib/metanorma/document/components/inline/fmt_termsource_element.rb +4 -2
  19. data/lib/metanorma/document/components/inline/math_element.rb +13 -7
  20. data/lib/metanorma/document/components/inline/vocabulary.rb +4 -0
  21. data/lib/metanorma/document/components/multi_paragraph/admonition_block.rb +6 -1
  22. data/lib/metanorma/document/components/paragraphs/paragraph_block.rb +3 -0
  23. data/lib/metanorma/document/components/reference_elements/callout.rb +3 -2
  24. data/lib/metanorma/document/components/tables/table_cell.rb +3 -0
  25. data/lib/metanorma/document/elements/input.rb +44 -0
  26. data/lib/metanorma/document/elements.rb +1 -0
  27. data/lib/metanorma/document/relaton/address.rb +5 -0
  28. data/lib/metanorma/document/relaton/affiliation.rb +3 -0
  29. data/lib/metanorma/document/relaton/bib_item_size.rb +8 -9
  30. data/lib/metanorma/document/relaton/bib_item_type.rb +3 -0
  31. data/lib/metanorma/document/relaton/bibliographic_date.rb +5 -0
  32. data/lib/metanorma/document/relaton/contact_method.rb +5 -0
  33. data/lib/metanorma/document/relaton/contribution_info.rb +6 -2
  34. data/lib/metanorma/document/relaton/contributor.rb +4 -0
  35. data/lib/metanorma/document/relaton/contributor_role.rb +3 -0
  36. data/lib/metanorma/document/relaton/copyright_association.rb +3 -0
  37. data/lib/metanorma/document/relaton/copyright_owner.rb +3 -0
  38. data/lib/metanorma/document/relaton/date_time.rb +3 -0
  39. data/lib/metanorma/document/relaton/document_identifier.rb +4 -0
  40. data/lib/metanorma/document/relaton/document_relation.rb +8 -0
  41. data/lib/metanorma/document/relaton/document_status.rb +4 -0
  42. data/lib/metanorma/document/relaton/formatted_address.rb +4 -0
  43. data/lib/metanorma/document/relaton/full_name.rb +14 -3
  44. data/lib/metanorma/document/relaton/keyword_type.rb +3 -0
  45. data/lib/metanorma/document/relaton/locality_stack.rb +6 -9
  46. data/lib/metanorma/document/relaton/localized_name.rb +8 -5
  47. data/lib/metanorma/document/relaton/logo_element.rb +5 -0
  48. data/lib/metanorma/document/relaton/medium_type.rb +5 -16
  49. data/lib/metanorma/document/relaton/org_identifier.rb +2 -0
  50. data/lib/metanorma/document/relaton/org_subdivision.rb +2 -0
  51. data/lib/metanorma/document/relaton/org_subdivision_identifier.rb +2 -0
  52. data/lib/metanorma/document/relaton/organization.rb +3 -0
  53. data/lib/metanorma/document/relaton/person.rb +7 -1
  54. data/lib/metanorma/document/relaton/person_identifier.rb +3 -0
  55. data/lib/metanorma/document/relaton/place_type.rb +5 -0
  56. data/lib/metanorma/document/relaton/region_element.rb +3 -9
  57. data/lib/metanorma/document/relaton/relaton_collection.rb +2 -0
  58. data/lib/metanorma/document/relaton/series_type.rb +7 -0
  59. data/lib/metanorma/document/relaton/typed_note.rb +4 -0
  60. data/lib/metanorma/document/relaton/typed_title_string.rb +4 -0
  61. data/lib/metanorma/document/relaton/typed_uri.rb +3 -8
  62. data/lib/metanorma/document/relaton/validity_type.rb +8 -10
  63. data/lib/metanorma/document/relaton/variant_full_name.rb +3 -0
  64. data/lib/metanorma/document/relaton/variant_org_name.rb +3 -0
  65. data/lib/metanorma/document/relaton/version_info.rb +5 -0
  66. data/lib/metanorma/document/relaton/vocab_id_type.rb +6 -12
  67. data/lib/metanorma/document/relaton.rb +0 -14
  68. data/lib/metanorma/document/version.rb +1 -1
  69. data/lib/metanorma/document.rb +2 -1
  70. data/lib/metanorma/html/asset_pipeline.rb +1 -0
  71. data/lib/metanorma/html/base_renderer.rb +106 -236
  72. data/lib/metanorma/html/concerns/metadata_extraction.rb +61 -0
  73. data/lib/metanorma/html/concerns/presentation_validation.rb +61 -0
  74. data/lib/metanorma/html/concerns/svg_processing.rb +32 -0
  75. data/lib/metanorma/html/concerns/text_extraction.rb +93 -0
  76. data/lib/metanorma/html/concerns/toc_registry.rb +47 -0
  77. data/lib/metanorma/html/csa_renderer.rb +9 -0
  78. data/lib/metanorma/html/drops/figure_drop.rb +33 -10
  79. data/lib/metanorma/html/drops/sourcecode_drop.rb +3 -6
  80. data/lib/metanorma/html/flavor.rb +6 -1
  81. data/lib/metanorma/html/generator.rb +8 -8
  82. data/lib/metanorma/html/iso_renderer.rb +0 -142
  83. data/lib/metanorma/html/renderers/block_renderer.rb +41 -5
  84. data/lib/metanorma/html/renderers/element_order_traversal.rb +15 -1
  85. data/lib/metanorma/html/renderers/inline_renderer.rb +16 -76
  86. data/lib/metanorma/html/standard_renderer.rb +195 -35
  87. data/lib/metanorma/html/templates/_list_item.html.liquid +1 -1
  88. data/lib/metanorma/html/theme.rb +38 -11
  89. data/lib/metanorma/html.rb +1 -52
  90. data/lib/metanorma/iso_document/sections/iso_preface.rb +27 -13
  91. data/lib/metanorma/mirror/default_registry.rb +21 -0
  92. data/lib/metanorma/mirror/handlers/inline/catalog.rb +7 -12
  93. data/lib/metanorma/mirror/handlers/inline/rich_html_renderer.rb +2 -3
  94. data/lib/metanorma/mirror/handlers/sourcecode.rb +1 -1
  95. data/lib/metanorma/mirror/model/guide.rb +3 -2
  96. data/lib/metanorma/mirror/output/formats/inline_format.rb +64 -3
  97. data/lib/metanorma/mirror/output/pipeline.rb +1 -0
  98. data/lib/metanorma/mirror/output.rb +0 -2
  99. data/lib/metanorma/mirror/rewriter.rb +3 -1
  100. data/lib/metanorma/registers/setup.rb +6 -2
  101. data/lib/metanorma/standard_document/annotation_container.rb +2 -0
  102. data/lib/metanorma/standard_document/elements/input.rb +4 -25
  103. data/lib/metanorma/standard_document/metadata/metanorma_extension.rb +12 -2
  104. data/lib/metanorma/standard_document/metadata/presentation_metadata.rb +107 -0
  105. data/lib/metanorma/standard_document/metadata/semantic_metadata.rb +18 -0
  106. data/lib/metanorma/standard_document/metadata/unitsml/amount_of_substance.rb +19 -0
  107. data/lib/metanorma/standard_document/metadata/unitsml/base_quantity.rb +34 -0
  108. data/lib/metanorma/standard_document/metadata/unitsml/dimension.rb +39 -0
  109. data/lib/metanorma/standard_document/metadata/unitsml/dimension_set.rb +19 -0
  110. data/lib/metanorma/standard_document/metadata/unitsml/electric_current.rb +19 -0
  111. data/lib/metanorma/standard_document/metadata/unitsml/enumerated_root_unit.rb +23 -0
  112. data/lib/metanorma/standard_document/metadata/unitsml/length.rb +19 -0
  113. data/lib/metanorma/standard_document/metadata/unitsml/luminous_intensity.rb +19 -0
  114. data/lib/metanorma/standard_document/metadata/unitsml/mass.rb +19 -0
  115. data/lib/metanorma/standard_document/metadata/unitsml/namespace.rb +17 -0
  116. data/lib/metanorma/standard_document/metadata/unitsml/plane_angle.rb +19 -0
  117. data/lib/metanorma/standard_document/metadata/unitsml/prefix.rb +30 -0
  118. data/lib/metanorma/standard_document/metadata/unitsml/prefix_name.rb +21 -0
  119. data/lib/metanorma/standard_document/metadata/unitsml/prefix_set.rb +19 -0
  120. data/lib/metanorma/standard_document/metadata/unitsml/prefix_symbol.rb +21 -0
  121. data/lib/metanorma/standard_document/metadata/unitsml/quantity.rb +28 -0
  122. data/lib/metanorma/standard_document/metadata/unitsml/quantity_name.rb +21 -0
  123. data/lib/metanorma/standard_document/metadata/unitsml/quantity_set.rb +19 -0
  124. data/lib/metanorma/standard_document/metadata/unitsml/root_units.rb +19 -0
  125. data/lib/metanorma/standard_document/metadata/unitsml/thermodynamic_temperature.rb +19 -0
  126. data/lib/metanorma/standard_document/metadata/unitsml/time.rb +19 -0
  127. data/lib/metanorma/standard_document/metadata/unitsml/unit.rb +32 -0
  128. data/lib/metanorma/standard_document/metadata/unitsml/unit_name.rb +21 -0
  129. data/lib/metanorma/standard_document/metadata/unitsml/unit_set.rb +19 -0
  130. data/lib/metanorma/standard_document/metadata/unitsml/unit_symbol.rb +36 -0
  131. data/lib/metanorma/standard_document/metadata/unitsml/unit_system.rb +23 -0
  132. data/lib/metanorma/standard_document/metadata/unitsml/unitsml_root.rb +28 -0
  133. data/lib/metanorma/standard_document/metadata/unitsml.rb +39 -0
  134. data/lib/metanorma/standard_document/metadata.rb +4 -0
  135. data/lib/metanorma/standard_document/sections/misc_container.rb +8 -6
  136. data/lib/metanorma/standard_document/sections/sections.rb +7 -0
  137. data/lib/metanorma/un_document/blocks/un_admonition_block.rb +86 -0
  138. data/lib/metanorma/un_document/blocks.rb +9 -0
  139. data/lib/metanorma/un_document/root.rb +8 -0
  140. data/lib/metanorma/un_document/sections/un_abstract_section.rb +48 -0
  141. data/lib/metanorma/un_document/sections/un_preface.rb +19 -2
  142. data/lib/metanorma/un_document/sections.rb +1 -0
  143. data/lib/metanorma/un_document/un_text_element.rb +116 -0
  144. data/lib/metanorma/un_document.rb +2 -0
  145. data/script/benchmark_render.rb +35 -0
  146. data/tasks/roundtrip_samples.rake +2 -2
  147. metadata +65 -54
  148. data/.rubocop_todo.yml +0 -220
  149. data/TODO.finalize/01-split-multi-class-files.md +0 -8
  150. data/TODO.finalize/02-remove-data-dist-iso-document-frontend.md +0 -6
  151. data/TODO.finalize/03-align-frontend-iife-and-gha-build.md +0 -11
  152. data/TODO.finalize/04-theme-per-flavor-config-driven.md +0 -9
  153. data/TODO.finalize/05-migrate-all-html-to-liquid.md +0 -16
  154. data/TODO.finalize/06-integrate-pubid-library.md +0 -16
  155. data/TODO.finalize/07-replace-relaton-models-with-relaton-bib.md +0 -39
  156. data/TODO.finalize/08-refactor-renderer-architecture.md +0 -25
  157. data/TODO.finalize/09-eliminate-code-smells.md +0 -14
  158. data/TODO.finalize/10-comprehensive-spec-coverage.md +0 -24
  159. data/TODO.finalize/11-config-driven-flavor-metadata.md +0 -20
  160. data/TODO.finalize/12-add-frozen-string-literal.md +0 -6
  161. data/TODO.finalize/13-extract-composition-from-god-class.md +0 -96
  162. data/TODO.finalize/14-config-driven-section-ordering.md +0 -31
  163. data/TODO.finalize/15-theme-directories-per-flavor.md +0 -46
  164. data/TODO.finalize/16-theme-lutaml-model-schema.md +0 -44
  165. data/data/logos/oiml-logo-full-dark.svg +0 -1
  166. data/data/logos/oiml-logo-full-light.svg +0 -1
  167. data/data/logos/oiml-logo-icon-dark.svg +0 -1
  168. data/data/logos/oiml-logo-icon-light.svg +0 -1
  169. data/data/logos/oiml-logo.svg +0 -1
  170. data/data/themes/oiml.yaml +0 -56
  171. data/docs/html-renderer.adoc +0 -261
  172. data/lib/data/dist/iso_document/frontend/assets/index-mwzbzmnK.js +0 -22
  173. data/lib/data/dist/iso_document/frontend/assets/index-pGvKTNid.css +0 -1
  174. data/lib/data/dist/iso_document/frontend/assets/iso-red.svg +0 -53
  175. data/lib/data/dist/iso_document/frontend/index.html +0 -13
  176. data/lib/metanorma/document/relaton/bib_item_size_type.rb +0 -21
  177. data/lib/metanorma/document/relaton/bibliographic_date_type.rb +0 -22
  178. data/lib/metanorma/document/relaton/document_relation_type.rb +0 -31
  179. data/lib/metanorma/document/relaton/iso4217_code.rb +0 -17
  180. data/lib/metanorma/document/relaton/iso8601_date.rb +0 -17
  181. data/lib/metanorma/document/relaton/personal_identifier_type.rb +0 -21
  182. data/lib/metanorma/document/relaton/region_type.rb +0 -20
  183. data/lib/metanorma/document/relaton/series_type_type.rb +0 -21
  184. data/lib/metanorma/document/relaton/specific_locality_type.rb +0 -25
  185. data/lib/metanorma/document/relaton/title_type.rb +0 -21
  186. data/lib/metanorma/html/oiml_renderer.rb +0 -33
  187. data/lib/metanorma/mirror/output/html_renderer.rb +0 -95
  188. data/lib/metanorma/mirror/output/html_renderers/block_renderers.rb +0 -178
  189. data/lib/metanorma/mirror/output/html_renderers/inline_renderer.rb +0 -61
  190. data/lib/metanorma/mirror/output/html_renderers/list_renderers.rb +0 -96
  191. data/lib/metanorma/mirror/output/html_renderers/mark_renderers.rb +0 -41
  192. data/lib/metanorma/mirror/output/html_renderers/section_renderers.rb +0 -129
  193. data/lib/metanorma/mirror/output/html_renderers/structural_renderers.rb +0 -80
  194. data/lib/metanorma/mirror/output/html_renderers/table_renderers.rb +0 -87
  195. data/lib/metanorma/mirror/output/html_renderers.rb +0 -76
  196. data/lib/metanorma/oiml_document.rb +0 -9
  197. data/lib/metanorma/standard_document/annotation.rb +0 -37
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Html
5
+ module Concerns
6
+ # Plain-text extraction from model nodes, mixed into BaseRenderer.
7
+ # Used wherever rendered output needs a text-only form (titles,
8
+ # labels, captions, ToC entries). Walks `element_order` and typed
9
+ # attributes directly on the model — never strips tags from
10
+ # rendered HTML.
11
+ module TextExtraction
12
+ def extract_plain_text(node)
13
+ return node.to_s if node.is_a?(String)
14
+ return extract_text_value(node).to_s unless node.is_a?(Lutaml::Model::Serializable)
15
+
16
+ parts = []
17
+ xml_mapping = node.class.mappings_for(:xml, node.lutaml_register)
18
+
19
+ if node.element_order.is_a?(Array) && xml_mapping
20
+ element_to_attr =
21
+ Renderers::ElementOrderTraversal.element_to_attr_map(xml_mapping)
22
+
23
+ indices = Hash.new(0)
24
+ node.element_order.each do |el|
25
+ next unless el.is_a?(Lutaml::Xml::Element)
26
+
27
+ if el.text?
28
+ parts << el.text_content.to_s
29
+ elsif el.name == "tab"
30
+ parts << " "
31
+ # rubocop:disable Lint/DuplicateBranch
32
+ elsif el.name == "br"
33
+ parts << " "
34
+ # rubocop:enable Lint/DuplicateBranch
35
+ elsif el.element?
36
+ attr_name = element_to_attr[el.name]
37
+ if attr_name
38
+ coll = node.public_send(attr_name)
39
+ obj = if coll.is_a?(Array)
40
+ idx = indices[attr_name]
41
+ indices[attr_name] += 1
42
+ coll[idx]
43
+ else
44
+ coll
45
+ end
46
+ text = extract_plain_text(obj)
47
+ parts << (text.empty? ? " " : text)
48
+ elsif el.name == "span"
49
+ parts << " "
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ if parts.join.strip.empty?
56
+ t = safe_attr(node, :text)
57
+ parts << (t.is_a?(Array) ? t.join : t.to_s) if t
58
+ end
59
+
60
+ parts.join.strip.gsub(" ", " ")
61
+ end
62
+
63
+ def extract_text_value(val)
64
+ return nil if val.nil?
65
+ return val if val.is_a?(String)
66
+
67
+ if val.is_a?(Array)
68
+ val.map { |v| extract_text_value(v) }.join
69
+ elsif val.is_a?(Lutaml::Model::Serializable)
70
+ c = safe_attr(val, :content)
71
+ if c && !c.equal?(val)
72
+ extract_text_value(c)
73
+ else
74
+ t = safe_attr(val, :text)
75
+ if t
76
+ extract_text_value(t)
77
+ else
78
+ v = safe_attr(val, :value)
79
+ if v
80
+ extract_text_value(v)
81
+ else
82
+ val.to_s
83
+ end
84
+ end
85
+ end
86
+ else
87
+ val.to_s
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Html
5
+ module Concerns
6
+ # Table-of-contents entry collection and ToC rendering, mixed into
7
+ # BaseRenderer. Sub-renderers register section/figure/table entries
8
+ # as they walk the document; assemble_document renders the collected
9
+ # entries through the _toc.html.liquid template.
10
+ module TocRegistry
11
+ def toc_entries
12
+ @toc_entries
13
+ end
14
+
15
+ def build_toc_html(entries)
16
+ entry_drops = entries.map { |e| Drops::TocEntryDrop.new(e) }
17
+ figure_drops = @figure_entries.map { |f| Drops::FigureListEntryDrop.new(f) }
18
+ table_drops = @table_entries.map { |t| Drops::FigureListEntryDrop.new(t) }
19
+ has_special_lists = !@figure_entries.empty? || !@table_entries.empty?
20
+
21
+ render_liquid("_toc.html.liquid", {
22
+ "entries" => entry_drops,
23
+ "figures" => figure_drops,
24
+ "tables" => table_drops,
25
+ "has_special_lists" => has_special_lists,
26
+ })
27
+ end
28
+
29
+ def register_toc_entry(id:, level:, text:)
30
+ @toc_entries << { id: id, level: level, text: text }
31
+ end
32
+
33
+ def register_figure_entry(id:, text:)
34
+ @figure_entries << { id: id, text: text }
35
+ end
36
+
37
+ def figure_entries
38
+ @figure_entries
39
+ end
40
+
41
+ def register_table_entry(id:, text:)
42
+ @table_entries << { id: id, text: text }
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Html
5
+ class CsaRenderer < IsoRenderer
6
+ register_render Metanorma::CsaDocument::Root, :render_document
7
+ end
8
+ end
9
+ end
@@ -47,24 +47,47 @@ module Metanorma
47
47
  end
48
48
  sub_figures_html = sub_figures_parts.join
49
49
 
50
- key_parts = []
51
- renderer.safe_attr(figure, :note)&.each do |n|
52
- key_parts << (renderer.render_note(n) || "")
53
- end
54
- renderer.safe_attr(figure, :dl)&.then do |dl|
55
- key_parts << (renderer.render_definition_list(dl) || "")
56
- end
57
- key_html = key_parts.join
58
-
59
50
  new(
60
51
  id: id,
61
52
  image_html: image_html,
62
53
  caption_html: caption_html,
63
- key_html: key_html,
54
+ key_html: build_key_html(figure, renderer),
64
55
  sub_figures_html: sub_figures_html,
65
56
  css_class: "figure",
66
57
  )
67
58
  end
59
+
60
+ class << self
61
+ private
62
+
63
+ # Notes, plain definition lists, and figure.key (e.g.
64
+ # <key class="formula_dl">) — symbol/footnote definitions
65
+ # belonging to the figure.
66
+ def build_key_html(figure, renderer)
67
+ parts = []
68
+ renderer.safe_attr(figure, :note)&.each do |n|
69
+ parts << (renderer.render_note(n) || "")
70
+ end
71
+ renderer.safe_attr(figure, :dl)&.then do |dl|
72
+ parts << (renderer.render_definition_list(dl) || "")
73
+ end
74
+ append_figure_key(parts, renderer.safe_attr(figure, :key), renderer)
75
+ parts.join
76
+ end
77
+
78
+ def append_figure_key(parts, key, renderer)
79
+ return unless key
80
+
81
+ name = renderer.safe_attr(key, :name)
82
+ parts << (renderer.render_inline_element(name) || "") if name
83
+ if renderer.safe_attr(key, :dl)
84
+ parts << (renderer.render_definition_list(key.dl) || "")
85
+ end
86
+ renderer.safe_attr(key, :p)&.each do |para|
87
+ parts << (renderer.render_paragraph(para) || "")
88
+ end
89
+ end
90
+ end
68
91
  end
69
92
  end
70
93
  end
@@ -23,22 +23,19 @@ css_class: nil)
23
23
  renderer.render_inline_element(sc.name)
24
24
  end
25
25
 
26
- code_text = if sc.body&.content
27
- Array(sc.body.content).join
26
+ code_text = if sc.body
27
+ sc.body.decoded_content
28
28
  elsif sc.content
29
29
  Array(sc.content).join
30
30
  else
31
31
  ""
32
32
  end
33
- raw_text = code_text.gsub("&lt;", "<").gsub("&gt;", ">").gsub("&amp;", "&").gsub(
34
- "&quot;", "\""
35
- )
36
33
 
37
34
  new(
38
35
  id: id,
39
36
  lang: lang,
40
37
  name_html: name_html,
41
- code_html: renderer.escape_html(raw_text),
38
+ code_html: renderer.escape_html(code_text),
42
39
  css_class: "sourcecode",
43
40
  )
44
41
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
4
+
3
5
  module Metanorma
4
6
  module Html
5
7
  # A flavor ties together the four concepts that identify a Metanorma
@@ -38,7 +40,10 @@ module Metanorma
38
40
 
39
41
  Object.const_get(pubid_module.to_s)
40
42
  rescue NameError
41
- nil
43
+ raise ArgumentError,
44
+ "Flavor #{name.inspect}: pubid module #{pubid_module} could " \
45
+ "not be resolved — fix the registration in " \
46
+ "Html::Generator.build_flavor_registry"
42
47
  end
43
48
  end
44
49
  end
@@ -87,6 +87,7 @@ module Metanorma
87
87
  IsoRenderer
88
88
  BipmRenderer
89
89
  CcRenderer
90
+ CsaRenderer
90
91
  IccRenderer
91
92
  IecRenderer
92
93
  IeeeRenderer
@@ -94,7 +95,6 @@ module Metanorma
94
95
  IhoRenderer
95
96
  ItuRenderer
96
97
  OgcRenderer
97
- OimlRenderer
98
98
  PdfaRenderer
99
99
  RiboseRenderer
100
100
 
@@ -136,6 +136,12 @@ module Metanorma
136
136
  model_class: Metanorma::CcDocument::Root,
137
137
  renderer_class: CcRenderer,
138
138
  ))
139
+ registry.register(Flavor.new(
140
+ name: :csa,
141
+ model_class: Metanorma::CsaDocument::Root,
142
+ renderer_class: CsaRenderer,
143
+ pubid_module: :"Pubid::Csa",
144
+ ))
139
145
  registry.register(Flavor.new(
140
146
  name: :iec,
141
147
  model_class: Metanorma::IecDocument::Root,
@@ -163,19 +169,13 @@ module Metanorma
163
169
  name: :itu,
164
170
  model_class: Metanorma::ItuDocument::Root,
165
171
  renderer_class: ItuRenderer,
166
- pubid_module: :"Pubid::Ithu",
172
+ pubid_module: :"Pubid::Itu",
167
173
  ))
168
174
  registry.register(Flavor.new(
169
175
  name: :ogc,
170
176
  model_class: Metanorma::OgcDocument::Root,
171
177
  renderer_class: OgcRenderer,
172
178
  ))
173
- registry.register(Flavor.new(
174
- name: :oiml,
175
- model_class: Metanorma::OimlDocument::Root,
176
- renderer_class: OimlRenderer,
177
- pubid_module: :"Pubid::Oiml",
178
- ))
179
179
  registry.register(Flavor.new(
180
180
  name: :pdfa,
181
181
  model_class: Metanorma::RiboseDocument::Root,
@@ -330,148 +330,6 @@ module Metanorma
330
330
 
331
331
  # --- ISO Term rendering ---
332
332
 
333
- def render_term(term, **_opts)
334
- term_name = extract_term_name(term)
335
- term_def = extract_term_definition(term)
336
- data_attrs = {}
337
- if term_name && !term_name.empty?
338
- data_attrs["data-term-name"] = term_name
339
- end
340
- if term_def && !term_def.empty?
341
- data_attrs["data-term-definition"] = term_def
342
- end
343
- attrs = element_attrs(id: safe_attr(term, :id), **data_attrs)
344
- fmt_name = safe_attr(term, :fmt_name)
345
- fmt_preferred = safe_attr(term, :fmt_preferred)
346
- fmt_admitted = safe_attr(term, :fmt_admitted)
347
- fmt_deprecates = safe_attr(term, :fmt_deprecates)
348
- fmt_definition = safe_attr(term, :fmt_definition)
349
- fmt_termsource = safe_attr(term, :fmt_termsource)
350
- term_number = safe_attr(term, :term_number)
351
- term_p = safe_attr(term, :p)
352
- term_note = safe_attr(term, :termnote) || Array(safe_attr(term, :note))
353
- term_example = safe_attr(term,
354
- :termexample) || Array(safe_attr(term,
355
- :example))
356
- term_termsource = safe_attr(term, :termsource)
357
- term_admonition = safe_attr(term, :admonition)
358
- term_nested = safe_attr(term, :term)
359
-
360
- parts = []
361
- if fmt_name
362
- tn_content = render_inline_element(fmt_name)
363
- parts << render_liquid("_term_number.html.liquid",
364
- { "content" => tn_content })
365
- elsif term_number
366
- tn_text = if term_number.is_a?(String)
367
- term_number
368
- else
369
- extract_text_value(term_number)
370
- end
371
- parts << render_liquid("_term_number.html.liquid",
372
- { "content" => escape_html(tn_text) })
373
- end
374
-
375
- if fmt_preferred && !fmt_preferred.empty?
376
- fmt_preferred.each do |fp|
377
- fp.p&.each { |para| parts << (render_paragraph(para) || "") }
378
- end
379
- elsif term.preferred && !term.preferred.empty?
380
- term.preferred&.each do |designation|
381
- parts << (render_term_designation(designation, "preferred") || "")
382
- end
383
- end
384
-
385
- if fmt_admitted && !fmt_admitted.empty?
386
- fmt_admitted.each do |fa|
387
- fa.p&.each { |para| parts << (render_paragraph(para) || "") }
388
- end
389
- elsif term.admitted && !term.admitted.empty?
390
- term.admitted&.each do |designation|
391
- parts << (render_term_designation(designation, "admitted") || "")
392
- end
393
- end
394
-
395
- if fmt_deprecates
396
- fmt_deprecates.p&.each do |para|
397
- parts << (render_paragraph(para) || "")
398
- end
399
- elsif term.deprecates && !term.deprecates.empty?
400
- term.deprecates&.each do |designation|
401
- parts << (render_term_designation(designation, "deprecated") || "")
402
- end
403
- end
404
-
405
- if term.domain && !fmt_definition
406
- domain_text = safe_attr(term.domain, :text)
407
- if domain_text
408
- parts << render_liquid("_term_domain.html.liquid", {
409
- "text" => escape_html(domain_text),
410
- })
411
- end
412
- end
413
-
414
- if fmt_definition
415
- parts << (render_ordered_content(fmt_definition) || "")
416
- else
417
- term_p&.each { |para| parts << (render_paragraph(para) || "") }
418
- term.definition&.each do |defn|
419
- parts << (render_term_definition(defn) || "")
420
- end
421
- end
422
-
423
- term_note&.each { |note| parts << (render_term_note(note) || "") }
424
- term_example&.each { |ex| parts << (render_term_example(ex) || "") }
425
-
426
- if fmt_termsource && !fmt_termsource.empty?
427
- fmt_termsource.each do |fts|
428
- src_content = render_mixed_inline(fts)
429
- parts << render_liquid("_paragraph.html.liquid", {
430
- "attrs" => " class=\"term-source\"",
431
- "content" => src_content,
432
- })
433
- end
434
- else
435
- term.source&.each { |src| parts << (render_term_source(src) || "") }
436
- term_termsource&.each do |src|
437
- parts << (render_term_source_element(src) || "")
438
- end
439
- end
440
-
441
- term_admonition&.each { |adm| parts << (render_admonition(adm) || "") }
442
- term_nested&.each { |sub| parts << (render_term(sub) || "") }
443
-
444
- render_liquid("_element.html.liquid", "tag" => "div",
445
- "extra_attrs" => attrs, "content" => parts.join)
446
- end
447
-
448
- def extract_term_name(term)
449
- fmt_pref = safe_attr(term, :fmt_preferred)
450
- if fmt_pref && !fmt_pref.empty?
451
- fp = fmt_pref.first
452
- if fp.p && !fp.p.empty?
453
- return extract_plain_text(fp.p.first)
454
- end
455
- end
456
- if term.preferred && !term.preferred.empty?
457
- return extract_designation_name(term.preferred.first).to_s
458
- end
459
-
460
- safe_attr(term, :id).to_s.delete_prefix("term-")
461
- end
462
-
463
- def extract_term_definition(term)
464
- fmt_def = safe_attr(term, :fmt_definition)
465
- return extract_plain_text(fmt_def) if fmt_def
466
-
467
- term_p = safe_attr(term, :p)
468
- if term_p && !term_p.empty?
469
- text = term_p.map { |para| extract_plain_text(para) }.join(" ")
470
- return text.strip unless text.strip.empty?
471
- end
472
- nil
473
- end
474
-
475
333
  def render_term_designation(designation, type)
476
334
  css_class = type == "deprecated" ? "term-deprecated" : "term-name"
477
335
  name = extract_designation_name(designation)
@@ -154,9 +154,11 @@ module Metanorma
154
154
  end
155
155
 
156
156
  def render_unordered_list(ul, **_opts)
157
- attrs = element_attrs(id: safe_attr(ul, :id))
157
+ labeled = ul.listitem&.any? { |li| list_label(li) } || false
158
+ attrs = element_attrs(id: safe_attr(ul, :id),
159
+ class: labeled ? "mn-labeled-list" : nil)
158
160
  items = ul.listitem&.filter_map do |li|
159
- render_list_item_content(li)
161
+ render_list_item_content(li, labeled: labeled)
160
162
  end || []
161
163
  render_liquid("_list.html.liquid", {
162
164
  "list_tag" => "ul",
@@ -182,10 +184,12 @@ module Metanorma
182
184
  end
183
185
 
184
186
  def render_ordered_list(ol, **_opts)
187
+ labeled = ol.listitem&.any? { |li| list_label(li) } || false
185
188
  attrs = element_attrs(id: safe_attr(ol, :id),
189
+ class: labeled ? "mn-labeled-list" : nil,
186
190
  start: safe_attr(ol, :start), type: safe_attr(ol, :type_attr))
187
191
  items = ol.listitem&.filter_map do |li|
188
- render_list_item_content(li)
192
+ render_list_item_content(li, labeled: labeled)
189
193
  end || []
190
194
  render_liquid("_list.html.liquid", {
191
195
  "list_tag" => "ol",
@@ -194,14 +198,36 @@ module Metanorma
194
198
  })
195
199
  end
196
200
 
197
- def render_list_item_content(li)
201
+ def render_list_item_content(li, labeled: false)
198
202
  li_id = safe_attr(li, :id)
199
203
  attrs = li_id ? %( id="#{escape_html(li_id)}") : ""
200
- inner = coordinator.render_mixed_content_in_order(li)
204
+ label = labeled ? list_label(li) : nil
205
+ inner = if label
206
+ coordinator.render_mixed_content_in_order(
207
+ li,
208
+ skip_classes: [Metanorma::Document::Components::Inline::FmtNameElement],
209
+ )
210
+ else
211
+ coordinator.render_mixed_content_in_order(li)
212
+ end
201
213
  render_liquid("_list_item.html.liquid", "attrs" => attrs,
214
+ "label" => label && escape_html(label),
202
215
  "content" => inner)
203
216
  end
204
217
 
218
+ # Label glyph for a list item, taken from the presentation XML
219
+ # (the item's fmt-name/autonum). The presentation XML is
220
+ # authoritative for the bullet or number; when present it is
221
+ # rendered as the item marker and the browser's own list-style
222
+ # marker is suppressed (see components/lists.css).
223
+ def list_label(li)
224
+ fmt_name = coordinator.safe_attr(li, :fmt_name)
225
+ return nil unless fmt_name
226
+
227
+ label = coordinator.extract_plain_text(fmt_name).strip
228
+ label.empty? ? nil : label
229
+ end
230
+
205
231
  def render_definition_list(dl, **_opts)
206
232
  attrs = element_attrs(id: safe_attr(dl, :id))
207
233
  terms = dl.dt&.each_with_index&.map do |dt, i|
@@ -271,6 +297,16 @@ module Metanorma
271
297
  render_liquid("_example.html.liquid", { "block" => drop })
272
298
  end
273
299
 
300
+ def render_form(form, **_opts)
301
+ attrs = element_attrs(id: safe_attr(form, :id), class: "form")
302
+ content = coordinator.render_ordered_content(form)
303
+ render_liquid("_element.html.liquid", {
304
+ "tag" => "div",
305
+ "extra_attrs" => attrs,
306
+ "content" => content,
307
+ })
308
+ end
309
+
274
310
  def render_sourcecode(sc, **_opts)
275
311
  drop = Drops::SourcecodeDrop.from_model(sc,
276
312
  renderer: coordinator.renderer_context)
@@ -15,10 +15,24 @@ module Metanorma
15
15
  module ElementOrderTraversal
16
16
  module_function
17
17
 
18
+ # The map depends only on the model class's xml mapping, which is
19
+ # immutable after class definition — cache one map per mapping
20
+ # object instead of rebuilding it for every rendered node.
21
+ @cache = {}.compare_by_identity
22
+
23
+ def element_to_attr_map(xml_mapping)
24
+ @cache[xml_mapping] ||= build_map(xml_mapping)
25
+ end
26
+
27
+ # Test/benchmark hook: drop all memoized maps.
28
+ def reset_cache!
29
+ @cache.clear
30
+ end
31
+
18
32
  # Returns `{ xml_element_name => attr_name }` including both
19
33
  # symbol and string keys for each mapped element, so callers
20
34
  # can look up by either el.name form.
21
- def element_to_attr_map(xml_mapping)
35
+ def build_map(xml_mapping)
22
36
  {}.tap do |map|
23
37
  xml_mapping.mapping_elements_hash.each_value do |rule_or_array|
24
38
  Array(rule_or_array).each do |rule|