metanorma-document 0.5.0 → 0.5.2.pre.alpha.1

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 (543) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +81 -0
  3. data/Rakefile +3 -7
  4. data/data/stylesheets/base/_dark.css +3 -3
  5. data/data/stylesheets/base/_print.css +1 -1
  6. data/data/stylesheets/components/term.css +4 -2
  7. data/data/themes/ogc.yaml +5 -0
  8. data/lib/metanorma/basic_document/ancillary_blocks/figure_block.rb +2 -2
  9. data/lib/metanorma/basic_document/multi_paragraph/admonition_block.rb +2 -2
  10. data/lib/metanorma/document/cli.rb +134 -0
  11. data/lib/metanorma/document/components/ancillary_blocks/figure_block.rb +5 -0
  12. data/lib/metanorma/document/components/ancillary_blocks/fmt_figure_element.rb +47 -0
  13. data/lib/metanorma/document/components/ancillary_blocks/formula_block.rb +2 -0
  14. data/lib/metanorma/document/components/ancillary_blocks/imagemap_element.rb +73 -0
  15. data/lib/metanorma/document/components/ancillary_blocks/literal_block.rb +2 -0
  16. data/lib/metanorma/document/components/ancillary_blocks/sourcecode_block.rb +2 -0
  17. data/lib/metanorma/document/components/ancillary_blocks/svg_target_element.rb +27 -0
  18. data/lib/metanorma/document/components/ancillary_blocks/svgmap_element.rb +28 -0
  19. data/lib/metanorma/document/components/ancillary_blocks.rb +4 -0
  20. data/lib/metanorma/document/components/bib_data/bibliographic_item.rb +4 -0
  21. data/lib/metanorma/document/components/blocks/fmt_provision.rb +25 -0
  22. data/lib/metanorma/document/components/blocks/note_block.rb +8 -0
  23. data/lib/metanorma/document/components/blocks/permission_model.rb +9 -0
  24. data/lib/metanorma/document/components/blocks/recommendation_model.rb +9 -0
  25. data/lib/metanorma/document/components/blocks/requirement_base.rb +12 -0
  26. data/lib/metanorma/document/components/blocks/requirement_import.rb +23 -0
  27. data/lib/metanorma/document/components/blocks/requirement_measurement_target.rb +23 -0
  28. data/lib/metanorma/document/components/blocks/requirement_model.rb +9 -0
  29. data/lib/metanorma/document/components/blocks/requirement_specification.rb +23 -0
  30. data/lib/metanorma/document/components/blocks/requirement_verification.rb +23 -0
  31. data/lib/metanorma/document/components/blocks.rb +8 -0
  32. data/lib/metanorma/document/components/data_types/formatted_string.rb +9 -0
  33. data/lib/metanorma/document/components/id_elements/image.rb +4 -0
  34. data/lib/metanorma/document/components/inline/columnbreak_element.rb +17 -0
  35. data/lib/metanorma/document/components/inline/concept_element.rb +18 -0
  36. data/lib/metanorma/document/components/inline/date_element.rb +29 -0
  37. data/lib/metanorma/document/components/inline/display_text_element.rb +2 -0
  38. data/lib/metanorma/document/components/inline/em_raw_element.rb +6 -7
  39. data/lib/metanorma/document/components/inline/eref_element.rb +48 -22
  40. data/lib/metanorma/document/components/inline/eref_stack.rb +23 -0
  41. data/lib/metanorma/document/components/inline/error_message.rb +28 -0
  42. data/lib/metanorma/document/components/inline/fmt_date_element.rb +28 -0
  43. data/lib/metanorma/document/components/inline/fmt_date_inline_element.rb +26 -0
  44. data/lib/metanorma/document/components/inline/fmt_eref_element.rb +25 -0
  45. data/lib/metanorma/document/components/inline/fmt_origin_element.rb +24 -0
  46. data/lib/metanorma/document/components/inline/fmt_preferred_element.rb +3 -0
  47. data/lib/metanorma/document/components/inline/fmt_source_element.rb +31 -0
  48. data/lib/metanorma/document/components/inline/fn_element.rb +11 -0
  49. data/lib/metanorma/document/components/inline/link_element.rb +8 -0
  50. data/lib/metanorma/document/components/inline/name_with_id_element.rb +2 -0
  51. data/lib/metanorma/document/components/inline/refterm_element.rb +21 -0
  52. data/lib/metanorma/document/components/inline/semx_element.rb +22 -0
  53. data/lib/metanorma/document/components/inline/sourcelocality_stack_element.rb +27 -0
  54. data/lib/metanorma/document/components/inline/span_element.rb +2 -0
  55. data/lib/metanorma/document/components/inline/stem_inline_element.rb +2 -0
  56. data/lib/metanorma/document/components/inline/strong_raw_element.rb +6 -9
  57. data/lib/metanorma/document/components/inline/sub_element.rb +2 -0
  58. data/lib/metanorma/document/components/inline/termref_element.rb +24 -0
  59. data/lib/metanorma/document/components/inline/title_with_annotation_element.rb +10 -31
  60. data/lib/metanorma/document/components/inline/variant_title_element.rb +2 -0
  61. data/lib/metanorma/document/components/inline/vocabulary.rb +36 -0
  62. data/lib/metanorma/document/components/inline.rb +23 -0
  63. data/lib/metanorma/document/components/lists/dd_element.rb +6 -1
  64. data/lib/metanorma/document/components/lists/definition.rb +2 -0
  65. data/lib/metanorma/document/components/lists/definition_list.rb +3 -0
  66. data/lib/metanorma/document/components/lists/dt_element.rb +3 -0
  67. data/lib/metanorma/document/components/lists/fmt_list_element.rb +73 -0
  68. data/lib/metanorma/document/components/lists/list_item.rb +3 -0
  69. data/lib/metanorma/document/components/lists/ordered_list.rb +4 -2
  70. data/lib/metanorma/document/components/lists/unordered_list.rb +2 -0
  71. data/lib/metanorma/document/components/lists.rb +2 -0
  72. data/lib/metanorma/document/components/multi_paragraph/quote_block.rb +16 -0
  73. data/lib/metanorma/document/components/multi_paragraph/review_block.rb +2 -2
  74. data/lib/metanorma/document/components/paragraphs/paragraph_block.rb +15 -0
  75. data/lib/metanorma/document/components/reference_elements/citation.rb +7 -0
  76. data/lib/metanorma/document/components/reference_elements/source_element.rb +4 -0
  77. data/lib/metanorma/document/components/semantic/node.rb +46 -0
  78. data/lib/metanorma/document/components/semantic.rb +11 -0
  79. data/lib/metanorma/document/components/tables/header_table_cell.rb +1 -0
  80. data/lib/metanorma/document/components/tables/table_cell.rb +70 -44
  81. data/lib/metanorma/document/components/tables/text_table_cell.rb +1 -0
  82. data/lib/metanorma/document/components/tables/text_table_row.rb +2 -0
  83. data/lib/metanorma/document/components/text_elements/ruby_annotation_element.rb +26 -0
  84. data/lib/metanorma/document/components/text_elements/ruby_element.rb +41 -6
  85. data/lib/metanorma/document/components/text_elements/ruby_pronunciation_element.rb +25 -0
  86. data/lib/metanorma/document/components/text_elements/strike_element.rb +3 -0
  87. data/lib/metanorma/document/components/text_elements/underline_element.rb +3 -0
  88. data/lib/metanorma/document/components/text_elements.rb +2 -0
  89. data/lib/metanorma/document/components.rb +1 -0
  90. data/lib/metanorma/document/grammar_vocabulary.rb +109 -0
  91. data/lib/metanorma/document/mirror_seed.rb +253 -0
  92. data/lib/metanorma/document/mko/collection.rb +168 -0
  93. data/lib/metanorma/document/mko/exporter.rb +17 -0
  94. data/lib/metanorma/document/mko/project.rb +1110 -0
  95. data/lib/metanorma/document/mko.rb +122 -0
  96. data/lib/metanorma/document/model_access.rb +44 -0
  97. data/lib/metanorma/document/native_models.rb +319 -0
  98. data/lib/metanorma/document/plain_text.rb +39 -0
  99. data/lib/metanorma/document/relaton/version_info.rb +4 -0
  100. data/lib/metanorma/document/version.rb +1 -1
  101. data/lib/metanorma/document.rb +19 -26
  102. data/lib/metanorma/{collection → document_collection}/directive.rb +1 -1
  103. data/lib/metanorma/{collection → document_collection}/doc_container.rb +6 -2
  104. data/lib/metanorma/{collection → document_collection}/entry.rb +2 -2
  105. data/lib/metanorma/{collection → document_collection}/root.rb +2 -2
  106. data/lib/metanorma/document_collection.rb +10 -0
  107. data/lib/metanorma/html/base_renderer.rb +105 -28
  108. data/lib/metanorma/html/component/footnote_collector.rb +21 -20
  109. data/lib/metanorma/html/concerns/metadata_extraction.rb +8 -3
  110. data/lib/metanorma/html/concerns/presentation_validation.rb +1 -2
  111. data/lib/metanorma/html/concerns/text_extraction.rb +154 -35
  112. data/lib/metanorma/html/concerns.rb +19 -0
  113. data/lib/metanorma/html/drops/admonition_drop.rb +1 -1
  114. data/lib/metanorma/html/drops/example_drop.rb +8 -1
  115. data/lib/metanorma/html/drops/footnote_drop.rb +1 -1
  116. data/lib/metanorma/html/drops/note_drop.rb +31 -4
  117. data/lib/metanorma/html/drops/sourcecode_drop.rb +22 -4
  118. data/lib/metanorma/html/generator.rb +11 -187
  119. data/lib/metanorma/html/renderer_delegation.rb +2 -2
  120. data/lib/metanorma/html/renderers/block_renderer.rb +246 -51
  121. data/lib/metanorma/html/renderers/inline_renderer.rb +144 -17
  122. data/lib/metanorma/html/renderers/section_renderer.rb +12 -2
  123. data/lib/metanorma/html/renderers/semantic_renderer.rb +487 -0
  124. data/lib/metanorma/html/renderers.rb +2 -0
  125. data/lib/metanorma/html/standard_renderer.rb +743 -81
  126. data/lib/metanorma/html/templates/_agency_cover.html.liquid +30 -0
  127. data/lib/metanorma/html/templates/_columnbreak.html.liquid +1 -0
  128. data/lib/metanorma/html/templates/_cover.html.liquid +3 -0
  129. data/lib/metanorma/html/templates/_example.html.liquid +1 -1
  130. data/lib/metanorma/html/templates/_main_content.html.liquid +1 -1
  131. data/lib/metanorma/html/templates/_note.html.liquid +1 -1
  132. data/lib/metanorma/html/templates/_paragraph.html.liquid +1 -1
  133. data/lib/metanorma/html/templates/_ruby.html.liquid +1 -0
  134. data/lib/metanorma/html/templates/_sourcecode.html.liquid +4 -3
  135. data/lib/metanorma/html/templates/_standard_cover.html.liquid +54 -3
  136. data/lib/metanorma/html/templates/_term_number.html.liquid +1 -1
  137. data/lib/metanorma/html.rb +30 -15
  138. data/tasks/flavors.rake +116 -0
  139. data/tasks/grammar.rake +28 -0
  140. data/tasks/roundtrip_samples.rake +48 -34
  141. metadata +121 -416
  142. data/frontend/dist/app.css +0 -1
  143. data/frontend/dist/app.iife.js +0 -24
  144. data/lib/metanorma/bipm_document/metadata/bipm_bib_data_extension_type.rb +0 -20
  145. data/lib/metanorma/bipm_document/metadata/bipm_bibliographic_item.rb +0 -17
  146. data/lib/metanorma/bipm_document/metadata/bipm_structured_identifier.rb +0 -21
  147. data/lib/metanorma/bipm_document/metadata/depiction_element.rb +0 -18
  148. data/lib/metanorma/bipm_document/metadata.rb +0 -15
  149. data/lib/metanorma/bipm_document/root.rb +0 -25
  150. data/lib/metanorma/bipm_document.rb +0 -8
  151. data/lib/metanorma/bsi_document/metadata/bsi_bib_data_extension_type.rb +0 -10
  152. data/lib/metanorma/bsi_document/metadata/bsi_bibliographic_item.rb +0 -15
  153. data/lib/metanorma/bsi_document/metadata.rb +0 -12
  154. data/lib/metanorma/bsi_document/root.rb +0 -29
  155. data/lib/metanorma/bsi_document/sections/bsi_annex_section.rb +0 -25
  156. data/lib/metanorma/bsi_document/sections/bsi_clause_section.rb +0 -25
  157. data/lib/metanorma/bsi_document/sections/bsi_sections.rb +0 -27
  158. data/lib/metanorma/bsi_document/sections.rb +0 -11
  159. data/lib/metanorma/bsi_document.rb +0 -11
  160. data/lib/metanorma/cc_document/metadata/cc_bib_data_extension_type.rb +0 -12
  161. data/lib/metanorma/cc_document/metadata/cc_bibliographic_item.rb +0 -17
  162. data/lib/metanorma/cc_document/metadata.rb +0 -11
  163. data/lib/metanorma/cc_document/root.rb +0 -25
  164. data/lib/metanorma/cc_document.rb +0 -8
  165. data/lib/metanorma/collection.rb +0 -10
  166. data/lib/metanorma/csa_document/root.rb +0 -27
  167. data/lib/metanorma/csa_document.rb +0 -9
  168. data/lib/metanorma/gb_document/metadata/gb_bib_data_extension_type.rb +0 -10
  169. data/lib/metanorma/gb_document/metadata/gb_bibliographic_item.rb +0 -15
  170. data/lib/metanorma/gb_document/metadata.rb +0 -12
  171. data/lib/metanorma/gb_document/root.rb +0 -29
  172. data/lib/metanorma/gb_document.rb +0 -10
  173. data/lib/metanorma/generic_document/root.rb +0 -31
  174. data/lib/metanorma/generic_document.rb +0 -7
  175. data/lib/metanorma/html/bipm_renderer.rb +0 -23
  176. data/lib/metanorma/html/cc_renderer.rb +0 -9
  177. data/lib/metanorma/html/csa_renderer.rb +0 -9
  178. data/lib/metanorma/html/flavor.rb +0 -50
  179. data/lib/metanorma/html/flavor_registry.rb +0 -49
  180. data/lib/metanorma/html/icc_renderer.rb +0 -10
  181. data/lib/metanorma/html/iec_renderer.rb +0 -9
  182. data/lib/metanorma/html/ieee_renderer.rb +0 -9
  183. data/lib/metanorma/html/ietf_renderer.rb +0 -9
  184. data/lib/metanorma/html/iho_renderer.rb +0 -9
  185. data/lib/metanorma/html/iso_renderer.rb +0 -535
  186. data/lib/metanorma/html/itu_renderer.rb +0 -9
  187. data/lib/metanorma/html/ogc_renderer.rb +0 -23
  188. data/lib/metanorma/html/pdfa_renderer.rb +0 -10
  189. data/lib/metanorma/html/ribose_renderer.rb +0 -9
  190. data/lib/metanorma/html/templates/_bipm_cover.html.liquid +0 -27
  191. data/lib/metanorma/html/templates/_iso_cover.html.liquid +0 -31
  192. data/lib/metanorma/html/templates/_iso_doc_title.html.liquid +0 -3
  193. data/lib/metanorma/iec_document/root.rb +0 -27
  194. data/lib/metanorma/iec_document.rb +0 -9
  195. data/lib/metanorma/ieee_document/metadata/ieee_bib_data_extension_type.rb +0 -18
  196. data/lib/metanorma/ieee_document/metadata/ieee_bibliographic_item.rb +0 -20
  197. data/lib/metanorma/ieee_document/metadata/ieee_structured_identifier.rb +0 -25
  198. data/lib/metanorma/ieee_document/metadata.rb +0 -14
  199. data/lib/metanorma/ieee_document/root.rb +0 -29
  200. data/lib/metanorma/ieee_document/sections/ieee_sections.rb +0 -31
  201. data/lib/metanorma/ieee_document/sections.rb +0 -9
  202. data/lib/metanorma/ieee_document.rb +0 -11
  203. data/lib/metanorma/ietf_document/metadata/ietf_bib_data_extension_type.rb +0 -31
  204. data/lib/metanorma/ietf_document/metadata/ietf_bibliographic_item.rb +0 -51
  205. data/lib/metanorma/ietf_document/metadata/ietf_editorial_group.rb +0 -17
  206. data/lib/metanorma/ietf_document/metadata/pi_settings.rb +0 -32
  207. data/lib/metanorma/ietf_document/metadata.rb +0 -16
  208. data/lib/metanorma/ietf_document/root.rb +0 -30
  209. data/lib/metanorma/ietf_document/sections/ietf_annex_section.rb +0 -26
  210. data/lib/metanorma/ietf_document/sections/ietf_clause_section.rb +0 -26
  211. data/lib/metanorma/ietf_document/sections/ietf_content_section.rb +0 -25
  212. data/lib/metanorma/ietf_document/sections/ietf_sections.rb +0 -35
  213. data/lib/metanorma/ietf_document/sections.rb +0 -12
  214. data/lib/metanorma/ietf_document.rb +0 -11
  215. data/lib/metanorma/iho_document/metadata/iho_bib_data_extension_type.rb +0 -23
  216. data/lib/metanorma/iho_document/metadata/iho_bibliographic_item.rb +0 -20
  217. data/lib/metanorma/iho_document/metadata/iho_editorial_group.rb +0 -19
  218. data/lib/metanorma/iho_document/metadata/iho_series.rb +0 -22
  219. data/lib/metanorma/iho_document/metadata/iho_sub_group.rb +0 -18
  220. data/lib/metanorma/iho_document/metadata.rb +0 -15
  221. data/lib/metanorma/iho_document/root.rb +0 -25
  222. data/lib/metanorma/iho_document.rb +0 -8
  223. data/lib/metanorma/iso_document/annotation_container.rb +0 -7
  224. data/lib/metanorma/iso_document/blocks/iso_admonition_block.rb +0 -27
  225. data/lib/metanorma/iso_document/blocks/iso_admonition_type.rb +0 -25
  226. data/lib/metanorma/iso_document/blocks.rb +0 -12
  227. data/lib/metanorma/iso_document/boilerplate.rb +0 -7
  228. data/lib/metanorma/iso_document/metadata/abstract_title.rb +0 -36
  229. data/lib/metanorma/iso_document/metadata/doc_identifier.rb +0 -26
  230. data/lib/metanorma/iso_document/metadata/doctype_element.rb +0 -20
  231. data/lib/metanorma/iso_document/metadata/ics.rb +0 -19
  232. data/lib/metanorma/iso_document/metadata/iec_document_category.rb +0 -23
  233. data/lib/metanorma/iso_document/metadata/iso_bib_data_extension_type.rb +0 -38
  234. data/lib/metanorma/iso_document/metadata/iso_bibliographic_item.rb +0 -103
  235. data/lib/metanorma/iso_document/metadata/iso_document_id.rb +0 -46
  236. data/lib/metanorma/iso_document/metadata/iso_document_stage_codes.rb +0 -26
  237. data/lib/metanorma/iso_document/metadata/iso_document_status.rb +0 -26
  238. data/lib/metanorma/iso_document/metadata/iso_document_substage_codes.rb +0 -26
  239. data/lib/metanorma/iso_document/metadata/iso_document_type.rb +0 -25
  240. data/lib/metanorma/iso_document/metadata/iso_localized_title.rb +0 -61
  241. data/lib/metanorma/iso_document/metadata/iso_project_group.rb +0 -32
  242. data/lib/metanorma/iso_document/metadata/iso_sub_group.rb +0 -38
  243. data/lib/metanorma/iso_document/metadata/language_element.rb +0 -18
  244. data/lib/metanorma/iso_document/metadata/metanorma_extension.rb +0 -9
  245. data/lib/metanorma/iso_document/metadata/project_number.rb +0 -22
  246. data/lib/metanorma/iso_document/metadata/script_element.rb +0 -18
  247. data/lib/metanorma/iso_document/metadata/stage_element.rb +0 -26
  248. data/lib/metanorma/iso_document/metadata/stagename_element.rb +0 -18
  249. data/lib/metanorma/iso_document/metadata/structured_identifier.rb +0 -16
  250. data/lib/metanorma/iso_document/metadata/title_amd.rb +0 -12
  251. data/lib/metanorma/iso_document/metadata/title_amendment_prefix.rb +0 -12
  252. data/lib/metanorma/iso_document/metadata/title_collection.rb +0 -47
  253. data/lib/metanorma/iso_document/metadata/title_full.rb +0 -12
  254. data/lib/metanorma/iso_document/metadata/title_intro.rb +0 -12
  255. data/lib/metanorma/iso_document/metadata/title_main.rb +0 -12
  256. data/lib/metanorma/iso_document/metadata/title_part.rb +0 -12
  257. data/lib/metanorma/iso_document/metadata/title_part_prefix.rb +0 -12
  258. data/lib/metanorma/iso_document/metadata.rb +0 -44
  259. data/lib/metanorma/iso_document/root.rb +0 -26
  260. data/lib/metanorma/iso_document/sections/colophon.rb +0 -3
  261. data/lib/metanorma/iso_document/sections/iso_abstract_section.rb +0 -15
  262. data/lib/metanorma/iso_document/sections/iso_amendment_clause.rb +0 -24
  263. data/lib/metanorma/iso_document/sections/iso_annex_section.rb +0 -50
  264. data/lib/metanorma/iso_document/sections/iso_clause_section.rb +0 -49
  265. data/lib/metanorma/iso_document/sections/iso_foreword_section.rb +0 -33
  266. data/lib/metanorma/iso_document/sections/iso_preface.rb +0 -51
  267. data/lib/metanorma/iso_document/sections/iso_sections.rb +0 -51
  268. data/lib/metanorma/iso_document/sections/iso_terms_section.rb +0 -76
  269. data/lib/metanorma/iso_document/sections.rb +0 -17
  270. data/lib/metanorma/iso_document/terms/iso_term.rb +0 -71
  271. data/lib/metanorma/iso_document/terms/iso_term_collection.rb +0 -20
  272. data/lib/metanorma/iso_document/terms/term_definition.rb +0 -30
  273. data/lib/metanorma/iso_document/terms/term_designation.rb +0 -44
  274. data/lib/metanorma/iso_document/terms/term_domain_element.rb +0 -18
  275. data/lib/metanorma/iso_document/terms/term_example.rb +0 -40
  276. data/lib/metanorma/iso_document/terms/term_expression.rb +0 -10
  277. data/lib/metanorma/iso_document/terms/term_note.rb +0 -42
  278. data/lib/metanorma/iso_document/terms/term_origin.rb +0 -4
  279. data/lib/metanorma/iso_document/terms/term_source.rb +0 -21
  280. data/lib/metanorma/iso_document/terms/verbal_definition.rb +0 -42
  281. data/lib/metanorma/iso_document/terms.rb +0 -21
  282. data/lib/metanorma/iso_document.rb +0 -15
  283. data/lib/metanorma/itu_document/metadata/itu_bib_data_extension_type.rb +0 -26
  284. data/lib/metanorma/itu_document/metadata/itu_bibliographic_item.rb +0 -21
  285. data/lib/metanorma/itu_document/metadata/itu_series.rb +0 -22
  286. data/lib/metanorma/itu_document/metadata/itu_structured_identifier.rb +0 -23
  287. data/lib/metanorma/itu_document/metadata/meeting_date.rb +0 -18
  288. data/lib/metanorma/itu_document/metadata/meeting_element.rb +0 -18
  289. data/lib/metanorma/itu_document/metadata/study_period.rb +0 -18
  290. data/lib/metanorma/itu_document/metadata.rb +0 -18
  291. data/lib/metanorma/itu_document/root.rb +0 -25
  292. data/lib/metanorma/itu_document.rb +0 -8
  293. data/lib/metanorma/jis_document/metadata/jis_bib_data_extension_type.rb +0 -10
  294. data/lib/metanorma/jis_document/metadata/jis_bibliographic_item.rb +0 -15
  295. data/lib/metanorma/jis_document/metadata.rb +0 -12
  296. data/lib/metanorma/jis_document/root.rb +0 -29
  297. data/lib/metanorma/jis_document/sections/jis_annex_section.rb +0 -20
  298. data/lib/metanorma/jis_document/sections.rb +0 -9
  299. data/lib/metanorma/jis_document.rb +0 -11
  300. data/lib/metanorma/m3d_document/metadata/m3d_bib_data_extension_type.rb +0 -10
  301. data/lib/metanorma/m3d_document/metadata/m3d_bibliographic_item.rb +0 -15
  302. data/lib/metanorma/m3d_document/metadata.rb +0 -12
  303. data/lib/metanorma/m3d_document/root.rb +0 -29
  304. data/lib/metanorma/m3d_document.rb +0 -10
  305. data/lib/metanorma/mirror/default_registry.rb +0 -191
  306. data/lib/metanorma/mirror/handler_registry.rb +0 -60
  307. data/lib/metanorma/mirror/handler_result.rb +0 -37
  308. data/lib/metanorma/mirror/handlers/admonition.rb +0 -18
  309. data/lib/metanorma/mirror/handlers/example.rb +0 -21
  310. data/lib/metanorma/mirror/handlers/figure.rb +0 -53
  311. data/lib/metanorma/mirror/handlers/formula.rb +0 -35
  312. data/lib/metanorma/mirror/handlers/inline/catalog.rb +0 -52
  313. data/lib/metanorma/mirror/handlers/inline/rich_html_renderer.rb +0 -158
  314. data/lib/metanorma/mirror/handlers/inline/text_extractor.rb +0 -116
  315. data/lib/metanorma/mirror/handlers/inline.rb +0 -226
  316. data/lib/metanorma/mirror/handlers/list.rb +0 -110
  317. data/lib/metanorma/mirror/handlers/note.rb +0 -26
  318. data/lib/metanorma/mirror/handlers/paragraph.rb +0 -19
  319. data/lib/metanorma/mirror/handlers/quote.rb +0 -16
  320. data/lib/metanorma/mirror/handlers/review.rb +0 -19
  321. data/lib/metanorma/mirror/handlers/section.rb +0 -149
  322. data/lib/metanorma/mirror/handlers/sourcecode.rb +0 -27
  323. data/lib/metanorma/mirror/handlers/structural.rb +0 -68
  324. data/lib/metanorma/mirror/handlers/table.rb +0 -81
  325. data/lib/metanorma/mirror/handlers/term.rb +0 -100
  326. data/lib/metanorma/mirror/handlers.rb +0 -60
  327. data/lib/metanorma/mirror/id_strategy/positional.rb +0 -166
  328. data/lib/metanorma/mirror/id_strategy/preserve.rb +0 -12
  329. data/lib/metanorma/mirror/id_strategy.rb +0 -34
  330. data/lib/metanorma/mirror/math_util.rb +0 -53
  331. data/lib/metanorma/mirror/metadata.rb +0 -37
  332. data/lib/metanorma/mirror/model/container.rb +0 -47
  333. data/lib/metanorma/mirror/model/factory.rb +0 -52
  334. data/lib/metanorma/mirror/model/guide.rb +0 -26
  335. data/lib/metanorma/mirror/model/leaf.rb +0 -17
  336. data/lib/metanorma/mirror/model/mark.rb +0 -53
  337. data/lib/metanorma/mirror/model/node.rb +0 -47
  338. data/lib/metanorma/mirror/model/soft_break.rb +0 -33
  339. data/lib/metanorma/mirror/model/text.rb +0 -34
  340. data/lib/metanorma/mirror/model.rb +0 -16
  341. data/lib/metanorma/mirror/output/builder.rb +0 -39
  342. data/lib/metanorma/mirror/output/formats/base_format.rb +0 -90
  343. data/lib/metanorma/mirror/output/formats/inline_format.rb +0 -142
  344. data/lib/metanorma/mirror/output/formats.rb +0 -53
  345. data/lib/metanorma/mirror/output/pipeline.rb +0 -93
  346. data/lib/metanorma/mirror/output/pipeline_context.rb +0 -22
  347. data/lib/metanorma/mirror/output.rb +0 -12
  348. data/lib/metanorma/mirror/rewriter.rb +0 -123
  349. data/lib/metanorma/mirror/safe_attr.rb +0 -16
  350. data/lib/metanorma/mirror/serialization/json_serializer.rb +0 -25
  351. data/lib/metanorma/mirror/serialization/yaml_serializer.rb +0 -20
  352. data/lib/metanorma/mirror/serialization.rb +0 -10
  353. data/lib/metanorma/mirror/transformer.rb +0 -110
  354. data/lib/metanorma/mirror.rb +0 -31
  355. data/lib/metanorma/nist_document/metadata/nist_bib_data_extension_type.rb +0 -10
  356. data/lib/metanorma/nist_document/metadata/nist_bibliographic_item.rb +0 -15
  357. data/lib/metanorma/nist_document/metadata.rb +0 -12
  358. data/lib/metanorma/nist_document/root.rb +0 -29
  359. data/lib/metanorma/nist_document/sections/errata.rb +0 -16
  360. data/lib/metanorma/nist_document/sections/errata_clause.rb +0 -20
  361. data/lib/metanorma/nist_document/sections/errata_row.rb +0 -22
  362. data/lib/metanorma/nist_document/sections/nist_preface.rb +0 -30
  363. data/lib/metanorma/nist_document/sections.rb +0 -12
  364. data/lib/metanorma/nist_document.rb +0 -11
  365. data/lib/metanorma/ogc_document/metadata/ogc_bib_data_extension_type.rb +0 -12
  366. data/lib/metanorma/ogc_document/metadata/ogc_bibliographic_item.rb +0 -19
  367. data/lib/metanorma/ogc_document/metadata.rb +0 -12
  368. data/lib/metanorma/ogc_document/root.rb +0 -25
  369. data/lib/metanorma/ogc_document.rb +0 -8
  370. data/lib/metanorma/plateau_document/metadata/plateau_bibliographic_item.rb +0 -13
  371. data/lib/metanorma/plateau_document/metadata.rb +0 -10
  372. data/lib/metanorma/plateau_document/root.rb +0 -29
  373. data/lib/metanorma/plateau_document.rb +0 -10
  374. data/lib/metanorma/registers/setup.rb +0 -166
  375. data/lib/metanorma/registers.rb +0 -7
  376. data/lib/metanorma/ribose_document/metadata/ribose_bib_data_extension_type.rb +0 -20
  377. data/lib/metanorma/ribose_document/metadata/ribose_bibliographic_item.rb +0 -18
  378. data/lib/metanorma/ribose_document/metadata.rb +0 -12
  379. data/lib/metanorma/ribose_document/root.rb +0 -26
  380. data/lib/metanorma/ribose_document.rb +0 -8
  381. data/lib/metanorma/standard_document/annotation_container.rb +0 -39
  382. data/lib/metanorma/standard_document/block_attributes.rb +0 -281
  383. data/lib/metanorma/standard_document/blocks/amend_block.rb +0 -50
  384. data/lib/metanorma/standard_document/blocks/amend_content_block.rb +0 -35
  385. data/lib/metanorma/standard_document/blocks/auto_number.rb +0 -20
  386. data/lib/metanorma/standard_document/blocks/block_source.rb +0 -22
  387. data/lib/metanorma/standard_document/blocks/change_type.rb +0 -21
  388. data/lib/metanorma/standard_document/blocks/element_name.rb +0 -22
  389. data/lib/metanorma/standard_document/blocks/form.rb +0 -38
  390. data/lib/metanorma/standard_document/blocks/image_map_area_type.rb +0 -24
  391. data/lib/metanorma/standard_document/blocks/image_map_area_type_type.rb +0 -21
  392. data/lib/metanorma/standard_document/blocks/image_map_block.rb +0 -32
  393. data/lib/metanorma/standard_document/blocks/image_map_coords.rb +0 -19
  394. data/lib/metanorma/standard_document/blocks/image_map_radius.rb +0 -19
  395. data/lib/metanorma/standard_document/blocks/multilingual_rendering_type.rb +0 -21
  396. data/lib/metanorma/standard_document/blocks/passthrough.rb +0 -3
  397. data/lib/metanorma/standard_document/blocks/requirement_model.rb +0 -20
  398. data/lib/metanorma/standard_document/blocks/source_origin_element.rb +0 -23
  399. data/lib/metanorma/standard_document/blocks/standard_admonition_block.rb +0 -32
  400. data/lib/metanorma/standard_document/blocks/standard_block.rb +0 -28
  401. data/lib/metanorma/standard_document/blocks/standard_block_no_notes.rb +0 -34
  402. data/lib/metanorma/standard_document/blocks/standard_example_block.rb +0 -30
  403. data/lib/metanorma/standard_document/blocks/standard_figure_block.rb +0 -32
  404. data/lib/metanorma/standard_document/blocks/standard_formula_block.rb +0 -30
  405. data/lib/metanorma/standard_document/blocks/standard_note_block.rb +0 -64
  406. data/lib/metanorma/standard_document/blocks/standard_paragraph_block.rb +0 -25
  407. data/lib/metanorma/standard_document/blocks/standard_quote_block.rb +0 -29
  408. data/lib/metanorma/standard_document/blocks/standard_sourcecode_block.rb +0 -32
  409. data/lib/metanorma/standard_document/blocks/standard_table_block.rb +0 -39
  410. data/lib/metanorma/standard_document/blocks/svg_map_block.rb +0 -23
  411. data/lib/metanorma/standard_document/blocks/svg_target_type.rb +0 -20
  412. data/lib/metanorma/standard_document/blocks/table_col.rb +0 -16
  413. data/lib/metanorma/standard_document/blocks/to_c.rb +0 -17
  414. data/lib/metanorma/standard_document/blocks.rb +0 -60
  415. data/lib/metanorma/standard_document/boilerplate.rb +0 -32
  416. data/lib/metanorma/standard_document/elements/add.rb +0 -3
  417. data/lib/metanorma/standard_document/elements/del.rb +0 -3
  418. data/lib/metanorma/standard_document/elements/form_input.rb +0 -27
  419. data/lib/metanorma/standard_document/elements/input.rb +0 -12
  420. data/lib/metanorma/standard_document/elements/input_type.rb +0 -27
  421. data/lib/metanorma/standard_document/elements/label.rb +0 -19
  422. data/lib/metanorma/standard_document/elements/option.rb +0 -27
  423. data/lib/metanorma/standard_document/elements/orientation_type.rb +0 -27
  424. data/lib/metanorma/standard_document/elements/select.rb +0 -32
  425. data/lib/metanorma/standard_document/elements/standard_page_break_element.rb +0 -22
  426. data/lib/metanorma/standard_document/elements/textarea.rb +0 -25
  427. data/lib/metanorma/standard_document/elements.rb +0 -20
  428. data/lib/metanorma/standard_document/lists/standard_definition.rb +0 -24
  429. data/lib/metanorma/standard_document/lists/standard_definition_list.rb +0 -30
  430. data/lib/metanorma/standard_document/lists/standard_definition_term.rb +0 -27
  431. data/lib/metanorma/standard_document/lists/standard_unordered_list.rb +0 -28
  432. data/lib/metanorma/standard_document/lists/unordered_checkable_list_item.rb +0 -26
  433. data/lib/metanorma/standard_document/lists.rb +0 -17
  434. data/lib/metanorma/standard_document/metadata/editorial_group_type.rb +0 -25
  435. data/lib/metanorma/standard_document/metadata/ics_type.rb +0 -26
  436. data/lib/metanorma/standard_document/metadata/metanorma_extension.rb +0 -26
  437. data/lib/metanorma/standard_document/metadata/presentation_metadata.rb +0 -107
  438. data/lib/metanorma/standard_document/metadata/semantic_metadata.rb +0 -18
  439. data/lib/metanorma/standard_document/metadata/standard_bib_data.rb +0 -25
  440. data/lib/metanorma/standard_document/metadata/standard_bib_data_extension_type.rb +0 -37
  441. data/lib/metanorma/standard_document/metadata/structured_identifier_type.rb +0 -48
  442. data/lib/metanorma/standard_document/metadata/technical_committee_type.rb +0 -27
  443. data/lib/metanorma/standard_document/metadata/unitsml/amount_of_substance.rb +0 -19
  444. data/lib/metanorma/standard_document/metadata/unitsml/base_quantity.rb +0 -34
  445. data/lib/metanorma/standard_document/metadata/unitsml/dimension.rb +0 -39
  446. data/lib/metanorma/standard_document/metadata/unitsml/dimension_set.rb +0 -19
  447. data/lib/metanorma/standard_document/metadata/unitsml/electric_current.rb +0 -19
  448. data/lib/metanorma/standard_document/metadata/unitsml/enumerated_root_unit.rb +0 -23
  449. data/lib/metanorma/standard_document/metadata/unitsml/length.rb +0 -19
  450. data/lib/metanorma/standard_document/metadata/unitsml/luminous_intensity.rb +0 -19
  451. data/lib/metanorma/standard_document/metadata/unitsml/mass.rb +0 -19
  452. data/lib/metanorma/standard_document/metadata/unitsml/namespace.rb +0 -17
  453. data/lib/metanorma/standard_document/metadata/unitsml/plane_angle.rb +0 -19
  454. data/lib/metanorma/standard_document/metadata/unitsml/prefix.rb +0 -30
  455. data/lib/metanorma/standard_document/metadata/unitsml/prefix_name.rb +0 -21
  456. data/lib/metanorma/standard_document/metadata/unitsml/prefix_set.rb +0 -19
  457. data/lib/metanorma/standard_document/metadata/unitsml/prefix_symbol.rb +0 -21
  458. data/lib/metanorma/standard_document/metadata/unitsml/quantity.rb +0 -28
  459. data/lib/metanorma/standard_document/metadata/unitsml/quantity_name.rb +0 -21
  460. data/lib/metanorma/standard_document/metadata/unitsml/quantity_set.rb +0 -19
  461. data/lib/metanorma/standard_document/metadata/unitsml/root_units.rb +0 -19
  462. data/lib/metanorma/standard_document/metadata/unitsml/thermodynamic_temperature.rb +0 -19
  463. data/lib/metanorma/standard_document/metadata/unitsml/time.rb +0 -19
  464. data/lib/metanorma/standard_document/metadata/unitsml/unit.rb +0 -32
  465. data/lib/metanorma/standard_document/metadata/unitsml/unit_name.rb +0 -21
  466. data/lib/metanorma/standard_document/metadata/unitsml/unit_set.rb +0 -19
  467. data/lib/metanorma/standard_document/metadata/unitsml/unit_symbol.rb +0 -36
  468. data/lib/metanorma/standard_document/metadata/unitsml/unit_system.rb +0 -23
  469. data/lib/metanorma/standard_document/metadata/unitsml/unitsml_root.rb +0 -28
  470. data/lib/metanorma/standard_document/metadata/unitsml.rb +0 -39
  471. data/lib/metanorma/standard_document/metadata.rb +0 -23
  472. data/lib/metanorma/standard_document/namespace.rb +0 -11
  473. data/lib/metanorma/standard_document/refs/reference_to_term.rb +0 -26
  474. data/lib/metanorma/standard_document/refs/reference_to_termbase.rb +0 -27
  475. data/lib/metanorma/standard_document/refs/standoc_locality_stack.rb +0 -24
  476. data/lib/metanorma/standard_document/refs/standoc_reference_to_citation_element.rb +0 -27
  477. data/lib/metanorma/standard_document/refs/standoc_reference_to_id_element.rb +0 -36
  478. data/lib/metanorma/standard_document/refs/xref_case_type.rb +0 -27
  479. data/lib/metanorma/standard_document/refs/xref_connective_type.rb +0 -27
  480. data/lib/metanorma/standard_document/refs/xref_target_type.rb +0 -25
  481. data/lib/metanorma/standard_document/refs.rb +0 -18
  482. data/lib/metanorma/standard_document/root.rb +0 -10
  483. data/lib/metanorma/standard_document/root_attributes.rb +0 -78
  484. data/lib/metanorma/standard_document/sections/abstract.rb +0 -19
  485. data/lib/metanorma/standard_document/sections/acknowledgements.rb +0 -19
  486. data/lib/metanorma/standard_document/sections/annex_section.rb +0 -69
  487. data/lib/metanorma/standard_document/sections/bibliography_section.rb +0 -20
  488. data/lib/metanorma/standard_document/sections/boilerplate_type.rb +0 -32
  489. data/lib/metanorma/standard_document/sections/clause_hierarchical_section.rb +0 -32
  490. data/lib/metanorma/standard_document/sections/clause_section.rb +0 -91
  491. data/lib/metanorma/standard_document/sections/colophon.rb +0 -16
  492. data/lib/metanorma/standard_document/sections/definition_section.rb +0 -72
  493. data/lib/metanorma/standard_document/sections/floating_section_title.rb +0 -23
  494. data/lib/metanorma/standard_document/sections/floating_title.rb +0 -27
  495. data/lib/metanorma/standard_document/sections/foreword.rb +0 -19
  496. data/lib/metanorma/standard_document/sections/introduction.rb +0 -20
  497. data/lib/metanorma/standard_document/sections/misc_container.rb +0 -31
  498. data/lib/metanorma/standard_document/sections/normative_type.rb +0 -29
  499. data/lib/metanorma/standard_document/sections/preface.rb +0 -36
  500. data/lib/metanorma/standard_document/sections/sections.rb +0 -47
  501. data/lib/metanorma/standard_document/sections/standard_content_section.rb +0 -41
  502. data/lib/metanorma/standard_document/sections/standard_hierarchical_section.rb +0 -30
  503. data/lib/metanorma/standard_document/sections/standard_references_section.rb +0 -74
  504. data/lib/metanorma/standard_document/sections/standard_section.rb +0 -39
  505. data/lib/metanorma/standard_document/sections/terms_section.rb +0 -61
  506. data/lib/metanorma/standard_document/sections.rb +0 -34
  507. data/lib/metanorma/standard_document/standard_document_type.rb +0 -15
  508. data/lib/metanorma/standard_document/terms/abbreviation_type.rb +0 -27
  509. data/lib/metanorma/standard_document/terms/concept.rb +0 -42
  510. data/lib/metanorma/standard_document/terms/definition_collection.rb +0 -24
  511. data/lib/metanorma/standard_document/terms/designation.rb +0 -31
  512. data/lib/metanorma/standard_document/terms/expression_designation.rb +0 -38
  513. data/lib/metanorma/standard_document/terms/expression_type.rb +0 -27
  514. data/lib/metanorma/standard_document/terms/grammar_gender.rb +0 -27
  515. data/lib/metanorma/standard_document/terms/grammar_info.rb +0 -36
  516. data/lib/metanorma/standard_document/terms/graphical_symbol_designation.rb +0 -25
  517. data/lib/metanorma/standard_document/terms/letter_symbol_designation.rb +0 -27
  518. data/lib/metanorma/standard_document/terms/non_verbal_representation.rb +0 -33
  519. data/lib/metanorma/standard_document/terms/related_term.rb +0 -31
  520. data/lib/metanorma/standard_document/terms/related_term_type.rb +0 -28
  521. data/lib/metanorma/standard_document/terms/term.rb +0 -66
  522. data/lib/metanorma/standard_document/terms/term_collection.rb +0 -24
  523. data/lib/metanorma/standard_document/terms/term_definition.rb +0 -29
  524. data/lib/metanorma/standard_document/terms/term_expression.rb +0 -20
  525. data/lib/metanorma/standard_document/terms/term_name_element.rb +0 -29
  526. data/lib/metanorma/standard_document/terms/term_source.rb +0 -29
  527. data/lib/metanorma/standard_document/terms/term_source_status.rb +0 -29
  528. data/lib/metanorma/standard_document/terms/term_source_type.rb +0 -21
  529. data/lib/metanorma/standard_document/terms/verbal_expression.rb +0 -35
  530. data/lib/metanorma/standard_document/terms.rb +0 -33
  531. data/lib/metanorma/standard_document.rb +0 -27
  532. data/lib/metanorma/un_document/blocks/un_admonition_block.rb +0 -86
  533. data/lib/metanorma/un_document/blocks.rb +0 -9
  534. data/lib/metanorma/un_document/metadata/un_bib_data_extension_type.rb +0 -10
  535. data/lib/metanorma/un_document/metadata/un_bibliographic_item.rb +0 -15
  536. data/lib/metanorma/un_document/metadata.rb +0 -12
  537. data/lib/metanorma/un_document/root.rb +0 -37
  538. data/lib/metanorma/un_document/sections/un_abstract_section.rb +0 -48
  539. data/lib/metanorma/un_document/sections/un_preface.rb +0 -40
  540. data/lib/metanorma/un_document/sections/un_sections.rb +0 -24
  541. data/lib/metanorma/un_document/sections.rb +0 -11
  542. data/lib/metanorma/un_document/un_text_element.rb +0 -116
  543. data/lib/metanorma/un_document.rb +0 -13
@@ -0,0 +1,1110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Mko
5
+ # The projection: typed document model -> knowledge objects.
6
+ # Dispatches on model classes only (BasicDocument/StandardDocument
7
+ # vocabulary); carries zero flavor knowledge. All attribute access is
8
+ # keyed on lutaml attribute declarations — flavor classes may omit
9
+ # attributes the base trees carry.
10
+ class Project
11
+ include Metanorma::Document::ModelAccess
12
+
13
+ class << self
14
+ def call(model, presentation: nil, assets: nil)
15
+ new(model, presentation: presentation, assets: assets).call
16
+ end
17
+ end
18
+
19
+ def initialize(model, presentation: nil, assets: nil)
20
+ @model = model
21
+ @presentation = presentation
22
+ @assets = assets
23
+ @units = []
24
+ @units_by_id = {}
25
+ # model element -> unit, by object identity: what re-orders
26
+ # children into document order from element_order
27
+ @element_units = {}
28
+ @edges = []
29
+ @numbers = {}
30
+ @structure = []
31
+ # document order (#56): assigned at emission, never re-derived
32
+ @ordinal = 0
33
+ # units register (#55): entries + symbol lookup for formula refs
34
+ @unitsml = []
35
+ @units_by_symbol = {}
36
+ # The document language resolution (#53 item 3): every unit's
37
+ # lang carries its provenance — markup, heuristic, default, or
38
+ # the explicit fallback (Mko::Language).
39
+ @document_lang = resolve_document_lang(model)
40
+ @lang = @document_lang
41
+ @flavor = flavor_of(model)
42
+ @doc_date = published_on(model)
43
+ end
44
+
45
+ # One export = one instance: the walk's state (@units, @edges,
46
+ # @numbers, @structure) is owned by this export, never shared.
47
+ def call
48
+ collect_numbers(@presentation)
49
+ build_units_register(@model)
50
+ walk_root(@model)
51
+ identity = build_identity(@model)
52
+ @edges.concat(document_relation_edges(identity))
53
+ # Native object models (Glossarist concepts, Relaton bibdata)
54
+ # come from the model layer; the projection only serializes.
55
+ Mko::Result.new(document: identity, units: @units, edges: @edges,
56
+ unitsml: @unitsml,
57
+ glossary: Document::NativeModels.glossarist_concepts(
58
+ @model, lang: @lang.lang, date: @doc_date
59
+ ),
60
+ bibdata: Document::NativeModels.relaton_bibdata(@model),
61
+ bibliography:
62
+ Document::NativeModels.relaton_bibliography(@model),
63
+ identifiers: build_identifiers(@model),
64
+ assets: @assets&.entries || [], flavor: @flavor)
65
+ end
66
+
67
+ # Cross-document relations from the bibliographic record, as
68
+ # edges: doc:<short> -> ext:<identifier>, kind = the Relaton
69
+ # relation type verbatim (obsoletes, hasSuccessor, hasPart,
70
+ # updates, ...). Relaton's own vocabulary, no lossy mapping —
71
+ # consumers already carry the relaton type tables.
72
+ def document_relation_edges(identity)
73
+ Array(identity.relations).filter_map do |rel|
74
+ to = Document::PlainText.call(rel.to)
75
+ next if to.empty?
76
+
77
+ Schema::Edge.new(from: "doc:#{identity.ids.short}",
78
+ to: "ext:#{to}", kind: rel.type)
79
+ end
80
+ end
81
+
82
+ def published_on(model)
83
+ bib = val(model, :bibdata)
84
+ vals(bib, :date).filter_map { |d| scalar(val(d, :on)) }.first
85
+ end
86
+
87
+ # Base-quantity attr -> SI vector symbol; the model-side spelling
88
+ # of Mko::Units::BASE_QUANTITY_SYMBOLS (which is keyed by XML
89
+ # element name). Composition itself is shared: vectorize.
90
+ DIMENSION_SYMBOLS = {
91
+ length: "L", mass: "M", time: "T", electric_current: "I",
92
+ thermodynamic_temperature: "Θ", amount_of_substance: "N",
93
+ luminous_intensity: "J", plane_angle: "φ"
94
+ }.freeze
95
+ # Hand-entered status fields contradict their own succession
96
+ # links (#53 item 4: 58 of 224 OIML families); edges are
97
+ # structure. Derived from the Relaton relations verbatim:
98
+ # superseded iff this record names a successor.
99
+ SUCCESSOR_TYPES = %w[hasSuccessor obsoletedBy succeededBy
100
+ supersededBy updatedBy].freeze
101
+ # Prose containers hold paragraphs and lists (lists are not
102
+ # separate units — their content belongs in the clause text).
103
+ # Composed in document order via element_order, like
104
+ # extract_plain_text, but scoped to prose: tables/figures/
105
+ # formulas are their own units and stay out.
106
+ SECTION_PROSE_SOURCES = {
107
+ "p" => :paragraphs, "ul" => :unordered_lists,
108
+ "ol" => :ordered_lists, "dl" => :definition_lists
109
+ }.freeze
110
+ BLOCK_SOURCES = {
111
+ "table" => :tables, "figure" => :figures, "formula" => :formulas,
112
+ "note" => :notes, "example" => :examples,
113
+ "sourcecode" => :sourcecode_blocks,
114
+ # image maps: their wrapped figures are the content — each
115
+ # emits as a figure unit, the map's link geometry rides the
116
+ # native mirror object
117
+ "svgmap" => :svgmaps, "imagemap" => :imagemaps
118
+ }.freeze
119
+ # Element-name -> mapped attributes, for re-ordering a section's
120
+ # child units into document order from element_order (the same
121
+ # interleave trick section_text uses for prose). "clause" and
122
+ # "term" cover both tree spellings (subsections/clause, term/terms).
123
+ SECTION_CHILD_SOURCES = {
124
+ "clause" => %i[subsections clause], "terms" => %i[terms],
125
+ "term" => %i[term terms],
126
+ "table" => %i[tables], "figure" => %i[figures],
127
+ "svgmap" => %i[svgmaps], "imagemap" => %i[imagemaps],
128
+ "formula" => %i[formulas], "note" => %i[notes],
129
+ "example" => %i[examples], "sourcecode" => %i[sourcecode_blocks],
130
+ "requirement" => %i[requirement],
131
+ "recommendation" => %i[recommendation],
132
+ "permission" => %i[permission],
133
+ "p" => %i[paragraphs]
134
+ }.freeze
135
+
136
+ private
137
+
138
+ def flavor_of(model)
139
+ val(model, :flavor) ||
140
+ model.class.name.to_s.split("::")[1]&.downcase
141
+ end
142
+
143
+ # Element-level language resolution (#53 item 3): the element's
144
+ # own :lang wins the day the models map xml:lang
145
+ # (metanorma-standoc#1243) — markup stays authoritative; with no
146
+ # xml:lang in scope the element's own prose decides (the stopword
147
+ # langid the RAG consumer shares — a translated annex tags
148
+ # itself); else the enclosing scope, then the document default,
149
+ # stands. Returns the resolution to restore when the scope ends.
150
+ def push_element_lang(element, text: nil)
151
+ previous = @lang
152
+ @lang = Mko::Language.resolve(own: val(element, :lang),
153
+ text: text, inherited: previous,
154
+ default: @document_lang)
155
+ previous
156
+ end
157
+
158
+ # The document-level chain: the root's :lang the day the models
159
+ # map the root xml:lang, else the declared bibdata language, else
160
+ # detection over the document's own prose, else the explicit
161
+ # fallback.
162
+ def resolve_document_lang(model)
163
+ Mko::Language.document(markup: val(model, :lang),
164
+ declared: first_lang(model),
165
+ text: document_sample(model))
166
+ end
167
+
168
+ def first_lang(model)
169
+ bib = val(model, :bibdata)
170
+ scalar(vals(bib, :language).first)
171
+ end
172
+
173
+ # The document-level detection sample: the direct prose of every
174
+ # top-level section (SAMPLE_WORDS caps what detect reads).
175
+ def document_sample(model)
176
+ parts = []
177
+ each_top_section(model) do |s|
178
+ text = section_text(s)
179
+ parts << text unless text.empty?
180
+ end
181
+ parts.join("\n")
182
+ end
183
+
184
+ # -- numbering (presentation model) ----------------------------
185
+
186
+ def collect_numbers(presentation)
187
+ return unless presentation
188
+
189
+ # Two passes, deliberately: the harvest gathers EVERY semx
190
+ # autonum fragment (the preface's TOC processed last carries
191
+ # bare fragments that would otherwise shadow the composed
192
+ # numbers of sections already walked — observed on R 60-1,
193
+ # metanorma-document#93); the number_tree pass then recomposes
194
+ # and its writes win. The passes are sequential PHASES, not
195
+ # combinable loops — combining them re-opens the shadowing.
196
+ # rubocop:disable Style/CombinableLoops
197
+ each_top_section(presentation) { |s| harvest_autonums(s) }
198
+ each_top_section(presentation) { |s| number_tree(s, nil) }
199
+ # rubocop:enable Style/CombinableLoops
200
+ end
201
+
202
+ # The 0.4.x emitter no longer carries clause numbers in section
203
+ # fmt-titles — they ride the TOC's semx autonum fragments, each
204
+ # keyed by the source clause's id. Register them globally; the
205
+ # walk resolves each section's own anchor against the registry.
206
+ def harvest_autonums(model)
207
+ return unless serializable?(model)
208
+
209
+ if val(model, :element_attr) == "autonum"
210
+ src = val(model, :source)
211
+ text = Document::PlainText.call(model)
212
+ @numbers[src] = text if src && !text.empty?
213
+ end
214
+ model.class.attributes.each_value do |attr|
215
+ next if attr.name.nil?
216
+
217
+ raw = model.public_send(attr.name)
218
+ next if raw.nil?
219
+
220
+ Array(raw).each { |v| harvest_autonums(v) }
221
+ end
222
+ end
223
+
224
+ def each_top_section(model, &)
225
+ sections = val(model, :sections)
226
+ walk_container(sections, &)
227
+ preface = val(model, :preface)
228
+ walk_container(preface, &)
229
+ vals(model, :annex).each(&)
230
+ end
231
+
232
+ def walk_container(container, &)
233
+ vals(container, :clause).each(&)
234
+ vals(container, :terms).each(&)
235
+ # preface-shaped containers declare :foreword/:introduction
236
+ # sections — OIML-CS admin documents keep their whole body
237
+ # there (semantic XML serializes an empty <sections>)
238
+ vals(container, :foreword).each(&)
239
+ vals(container, :introduction).each(&)
240
+ end
241
+
242
+ # Term entries: the iso tree declares them as :term with nested
243
+ # terms-sections in :terms; the standoc tree maps them to :terms.
244
+ def term_entries(tsec)
245
+ entries = vals(tsec, :term)
246
+ entries = vals(tsec, :terms) if entries.empty?
247
+ entries
248
+ end
249
+
250
+ def nested_terms_sections(tsec)
251
+ vals(tsec, :term).empty? ? [] : vals(tsec, :terms)
252
+ end
253
+
254
+ def number_tree(section, parent)
255
+ register_number(section, parent)
256
+ vals(section, :subsections).each { |s| number_tree(s, section) }
257
+ vals(section, :clause).each { |s| number_tree(s, section) }
258
+ vals(section, :terms).each do |tsec|
259
+ register_number(tsec, section)
260
+ term_entries(tsec).each { |t| register_number(t, tsec) }
261
+ nested_terms_sections(tsec).each { |nested| number_tree(nested, section) }
262
+ end
263
+ end
264
+
265
+ def register_number(element, parent)
266
+ anchor = element_anchor(element, parent)
267
+ return unless anchor
268
+
269
+ autonum = section_autonum(element)
270
+ return unless autonum && !autonum.empty?
271
+
272
+ @numbers[anchor] = compose_autonum(parent, autonum)
273
+ end
274
+
275
+ # The 0.4.x TOC renders BARE fragments for iso-tree terms sections
276
+ # ("7" where the clause tree says "3.7" — metanorma-document#93):
277
+ # the harvest keyed them by source id and nothing composed parent
278
+ # chains, so terms-section units cited the bare fragment (or fell
279
+ # back to their parent's anchor) and every term under them cited
280
+ # it too. A bare fragment composes under its registered parent; a
281
+ # composed autonum ("3.7", "A.1") already carries its chain.
282
+ def compose_autonum(parent, autonum)
283
+ return autonum if autonum.include?(".")
284
+
285
+ pnum = parent && @numbers[element_anchor(parent)]
286
+ pnum && !pnum.empty? ? "#{pnum}.#{autonum}" : autonum
287
+ end
288
+
289
+ def section_autonum(section)
290
+ autonum = val(section, :autonum)
291
+ return autonum if autonum && !autonum.empty?
292
+
293
+ fmt = val(section, :fmt_title)
294
+ fmt ? fmt_autonum(fmt) : nil
295
+ end
296
+
297
+ # Clause numbers live in presentation fmt-title spans as semx
298
+ # autonum fragments ("1" + "." + "2" -> "1.2").
299
+ def fmt_autonum(fmt_title)
300
+ parts = []
301
+ collect_semx_autonums(fmt_title, parts)
302
+ parts.empty? ? nil : parts.join(".")
303
+ end
304
+
305
+ def collect_semx_autonums(element, out)
306
+ return unless serializable?(element)
307
+
308
+ if val(element, :element_attr) == "autonum"
309
+ text = Document::PlainText.call(element)
310
+ out << text unless text.empty?
311
+ end
312
+ element.class.attributes.each_value do |attr|
313
+ next if attr.name.nil?
314
+
315
+ raw = element.public_send(attr.name)
316
+ next if raw.nil?
317
+
318
+ Array(raw).each do |v|
319
+ collect_semx_autonums(v, out)
320
+ end
321
+ end
322
+ end
323
+
324
+ # -- units register (#55) ---------------------------------------
325
+
326
+ # The register from the model's typed UnitsML container. The
327
+ # container rides the root-level metanorma-extension (semantic
328
+ # XML puts UnitsML beside presentation-metadata); some trees also
329
+ # nest an extension under bibdata/ext — both are checked. A
330
+ # projection of what the source carries: no re-parsing, no
331
+ # invention.
332
+ def build_units_register(model)
333
+ root = [val(model, :metanorma_extension),
334
+ val(val_any(val(model, :bibdata), :ext, :extension),
335
+ :metanorma_extension)]
336
+ .filter_map { |mnx| val(mnx, :unitsml) }.first
337
+ return [] unless root
338
+
339
+ dimensions = dimension_vectors(root)
340
+ quantity_kinds = dimension_quantity_kinds(root)
341
+ pairs = vals(val(root, :unit_set), :unit).map do |unit|
342
+ [unit, unit_entry(unit, dimensions, quantity_kinds)]
343
+ end
344
+ entries = pairs.map(&:last)
345
+ by_id = entries.to_h { |e| [e.id, e] }
346
+ prefixes = prefix_symbols(root)
347
+ pairs.each do |unit, entry|
348
+ expr = si_expression(unit, by_id, prefixes)
349
+ entry.si_expression = expr if expr
350
+ end
351
+ @units_by_symbol = entries.each_with_object({}) do |e, h|
352
+ h[e.symbol] = e.id if e.symbol
353
+ end
354
+ @unitsml = entries
355
+ end
356
+
357
+ def unit_entry(unit, dimensions, quantity_kinds)
358
+ url = val(unit, :dimension_url).to_s.sub("#", "")
359
+ Mko::Units::Entry.new(
360
+ id: val(unit, :id),
361
+ symbol: html_symbol(unit),
362
+ name: scalar(val(unit, :unit_name)),
363
+ quantity_kind: quantity_kinds[url],
364
+ dimension_url: url.empty? ? nil : url,
365
+ dimension: dimensions[url],
366
+ )
367
+ end
368
+
369
+ # The display symbol: the HTML-typed UnitSymbol, else the first.
370
+ # Composite symbols carry XML whitespace from pretty-printing —
371
+ # squashed, never significant.
372
+ def html_symbol(unit)
373
+ symbols = vals(unit, :unit_symbol)
374
+ pick = symbols.find { |s| val(s, :type) == "HTML" } || symbols.first
375
+ return nil unless pick
376
+
377
+ text = vals(pick, :text).map { |t| scalar(t).to_s.strip }.join(" ")
378
+ text = text.gsub(/\s+/, " ").strip
379
+ text.empty? ? Document::PlainText.call(pick) : text
380
+ end
381
+
382
+ def dimension_vectors(root)
383
+ vals(val(root, :dimension_set), :dimension)
384
+ .to_h do |dim|
385
+ [val(dim, :id), Mko::Units.vectorize(
386
+ DIMENSION_SYMBOLS.filter_map do |attr, sym|
387
+ part = val(dim, attr) or next
388
+ [sym, val(part, :power_numerator),
389
+ val(part, :power_denominator)]
390
+ end,
391
+ )]
392
+ end
393
+ end
394
+
395
+ # Quantity kinds keyed by dimension URL: a unit's kind is the
396
+ # name of the quantity with the same dimension.
397
+ def dimension_quantity_kinds(root)
398
+ vals(val(root, :quantity_set), :quantity)
399
+ .each_with_object({}) do |q, h|
400
+ url = val(q, :dimension_url).to_s.sub("#", "")
401
+ name = vals(q, :quantity_name).map { |n| scalar(n) }
402
+ .reject(&:empty?).first
403
+ h[url] ||= name if name && !url.empty?
404
+ end
405
+ end
406
+
407
+ def prefix_symbols(root)
408
+ vals(val(root, :prefix_set), :prefix)
409
+ .to_h do |p|
410
+ [val(p, :id), vals(p, :prefix_symbol)
411
+ .map { |s| scalar(s) }.join]
412
+ end
413
+ end
414
+
415
+ # The unit's composition as the source enumerates it: root-unit
416
+ # symbols with prefixes and powers, resolved within the register.
417
+ def si_expression(unit, by_id, prefixes)
418
+ parts = vals(val(unit, :root_units), :enumerated_root_unit)
419
+ .filter_map do |eru|
420
+ sym = by_id[val(eru, :unit)]&.symbol or next
421
+ sym = "#{prefixes[val(eru, :prefix)]}#{sym}"
422
+ power = val(eru, :power_numerator)
423
+ power && power != 1 ? "#{sym}^#{power}" : sym
424
+ end
425
+ parts.empty? ? nil : parts.join("·")
426
+ end
427
+
428
+ # -- identity ----------------------------------------------------
429
+
430
+ # Identity comes from the native pubid parser first (one parser,
431
+ # every publisher it covers); the series-letter regex remains as
432
+ # the fallback for publishers pubid does not model. Nil-safe: an unparseable canonical yields nils (fields absent in
433
+ # JSON), never a crash — unknown shapes stay visible, not fatal.
434
+ def parse_identity(canonical)
435
+ native = Document::NativeModels.pubid_identity(canonical)
436
+ return native if native
437
+
438
+ m = canonical.to_s.match(/\A(?:[A-Z][A-Za-z0-9&-]*\s+)?([A-Z])\s*(\d+)(?:-(\d+|[A-Za-z][A-Za-z0-9]*))?\b/)
439
+ m ? [m[1], m[2], m[3]] : []
440
+ end
441
+
442
+ def build_identity(model)
443
+ bib = val(model, :bibdata)
444
+ ids = docids(bib)
445
+ canonical = ids.find { |d| val(d, :primary) && docid_text(d) }
446
+ &.then { |d| docid_text(d) } ||
447
+ ids.reject { |d| docid_is_urn?(d) }
448
+ .filter_map { |d| docid_text(d) }.first
449
+ series, number, part = parse_identity(canonical)
450
+ Schema::Document.new(
451
+ ids: Schema::Ids.new(
452
+ canonical: canonical,
453
+ short: Mko.slug(canonical),
454
+ series: series,
455
+ number: number,
456
+ part: part,
457
+ docid: ids.filter_map { |d| docid_text(d) },
458
+ urn: ids.select { |d| docid_is_urn?(d) }
459
+ .filter_map { |d| docid_text(d) },
460
+ ),
461
+ flavor: @flavor,
462
+ doctype: doctype_text(bib),
463
+ titles: build_titles(bib),
464
+ edition: scalar(val(bib, :edition)),
465
+ languages: vals(bib, :language).filter_map { |l| scalar(l) },
466
+ status: build_status(bib),
467
+ derived_status: derived_status(build_relations(bib)),
468
+ dates: build_dates(bib),
469
+ relations: build_relations(bib),
470
+ structure: @structure,
471
+ )
472
+ end
473
+
474
+ def scalar(value)
475
+ return nil if value.nil?
476
+ return value.map { |v| scalar(v) }.join(" ") if value.is_a?(Array)
477
+ return value.to_s unless serializable?(value)
478
+
479
+ Document::PlainText.call(value)
480
+ end
481
+
482
+ # Flavors rename some metadata attributes (e.g. the iso tree uses
483
+ # titles/doc_identifier). Prefer the base name; fall back by name.
484
+ def val_any(obj, *names)
485
+ names.each do |n|
486
+ v = val(obj, n)
487
+ return v unless v.nil?
488
+ end
489
+ nil
490
+ end
491
+
492
+ # Doctype: base tree is a scalar; iso-style trees nest typed doctype
493
+ # elements under bibdata extensions.
494
+ def doctype_text(bib)
495
+ dt = val_any(bib, :doctype)
496
+ dt = val(val(bib, :ext), :doctype_element) if dt.nil?
497
+ return nil if dt.nil?
498
+
499
+ dt = Array(dt).first if dt.is_a?(Array)
500
+ abbr = val(dt, :abbreviation)
501
+ return abbr.to_s unless abbr.to_s.empty?
502
+
503
+ value = val(dt, :value)
504
+ return value.to_s unless value.to_s.empty?
505
+
506
+ scalar(dt)
507
+ end
508
+
509
+ def derived_status(relations)
510
+ has_successor = relations.any? do |r|
511
+ SUCCESSOR_TYPES.include?(r.type) && r.to && !r.to.empty?
512
+ end
513
+ has_successor ? "superseded" : nil
514
+ end
515
+
516
+ def build_titles(bib)
517
+ vals_any(bib, :title, :titles).map do |t|
518
+ Schema::TitleEntry.new(lang: val(t, :language)&.to_s,
519
+ text: Document::PlainText.call(t))
520
+ end
521
+ end
522
+
523
+ def build_status(bib)
524
+ st = val(bib, :status)
525
+ return nil unless st
526
+
527
+ stage = Array(val(st, :stage)).first
528
+ Schema::StatusInfo.new(
529
+ stage: stage ? scalar(stage) : nil,
530
+ substage: scalar(val(st, :substage)),
531
+ abbreviation: stage ? val(stage, :abbreviation) : nil,
532
+ )
533
+ end
534
+
535
+ def build_dates(bib)
536
+ vals(bib, :date).map do |d|
537
+ Schema::DateEntry.new(type: val(d, :type),
538
+ on: scalar(val(d, :on)))
539
+ end
540
+ end
541
+
542
+ def build_relations(bib)
543
+ vals(bib, :relation).map do |r|
544
+ item = val(r, :bibitem)
545
+ to = docids(item).filter_map { |d| docid_text(d) }.first
546
+ Schema::RelationEntry.new(type: val(r, :type), to: to)
547
+ end
548
+ end
549
+
550
+ def build_identifiers(model)
551
+ infos = Document::NativeModels.pubid_identifiers(model).map do |e|
552
+ pubid = e[:pubid] && JSON.parse(e[:pubid].to_json)
553
+ Schema::IdentifierInfo.new(
554
+ original: e[:original], type: e[:type],
555
+ primary: e[:primary], parsed: pubid
556
+ )
557
+ end
558
+ Schema::Identifiers.new(identifiers: infos)
559
+ end
560
+
561
+ def vals_any(obj, *names)
562
+ names.filter_map { |n| val(obj, n) }.flat_map { |v| Array(v) }
563
+ end
564
+
565
+ def docid_is_urn?(docid)
566
+ val(docid, :type) == "URN" || docid_text(docid).to_s.start_with?("urn:")
567
+ end
568
+
569
+ # -- the walk ----------------------------------------------------
570
+
571
+ # The iso tree declares Term entries as :term (so a terms
572
+ # section is recognizable by that declaration); the standoc tree
573
+ # maps them to :terms. The 0.4.x clause models declare :term
574
+ # too (clauses may embed term containers), so the declaration
575
+ # alone misclassifies every ISO clause as a terms section —
576
+ # require actual term entries.
577
+ def terms_section?(obj)
578
+ serializable?(obj) && obj.class.attributes.key?(:term) &&
579
+ !vals(obj, :term).empty?
580
+ end
581
+
582
+ def walk_child_section(section, type: "clause")
583
+ if terms_section?(section)
584
+ walk_terms_section(section)
585
+ else
586
+ walk_section(section, type: type)
587
+ end
588
+ end
589
+
590
+ def walk_root(model)
591
+ sections = val(model, :sections)
592
+ walk_container(sections) { |s| walk_child_section(s) }
593
+ preface = val(model, :preface)
594
+ walk_container(preface) { |s| walk_child_section(s, type: "clause") }
595
+ vals(model, :annex).each { |a| walk_section(a, type: "annex") }
596
+ walk_bibliography(model)
597
+ end
598
+
599
+ # Sections scope the element-level language (#53 item 3): the
600
+ # section's resolution — its own xml:lang the day the models map
601
+ # it, else its own prose, else the enclosing scope — applies to
602
+ # every unit emitted inside.
603
+ def walk_section(section, type: "clause", parent: nil, breadcrumb: [])
604
+ text = section_text(section)
605
+ restore_lang = push_element_lang(section, text: text)
606
+ begin
607
+ anchor = element_anchor(section)
608
+ number = @numbers[anchor] || section_autonum(section)
609
+
610
+ title = Document::PlainText.call(val(section, :title))
611
+ unit = emit_unit(
612
+ type: type, anchor: anchor, number: number, title: title,
613
+ parent: parent, breadcrumb: breadcrumb,
614
+ obligation: val(section, :obligation),
615
+ text: text, model: section
616
+ )
617
+ crumb = breadcrumb + [number ? "#{number} #{title}" : title]
618
+ node = Schema::StructureNode.new(id: unit.id, number: number,
619
+ title: title)
620
+ @structure << node
621
+ children = walk_blocks(section, unit.id, crumb)
622
+ vals(section, :subsections).each do |sub|
623
+ child = walk_section(sub, parent: unit.id, breadcrumb: crumb)
624
+ node.children << child if child
625
+ children << child if child
626
+ end
627
+ vals(section, :clause).each do |sub|
628
+ child = walk_section(sub, parent: unit.id, breadcrumb: crumb)
629
+ node.children << child if child
630
+ children << child if child
631
+ end
632
+ vals(section, :terms).each do |tsec|
633
+ ts_unit = walk_terms_section(tsec, parent: unit.id,
634
+ breadcrumb: crumb)
635
+ children << ts_unit if ts_unit
636
+ end
637
+ finalize_section(unit,
638
+ ordered_child_ids(section, children.map(&:id)))
639
+ node
640
+ ensure
641
+ @lang = restore_lang
642
+ end
643
+ end
644
+
645
+ def walk_terms_section(tsec, parent: nil, breadcrumb: [])
646
+ text = section_text(tsec)
647
+ restore_lang = push_element_lang(tsec, text: text)
648
+ begin
649
+ anchor = element_anchor(tsec)
650
+ number = @numbers[anchor] || section_autonum(tsec)
651
+ title = Document::PlainText.call(val(tsec, :title))
652
+ unit = emit_unit(
653
+ type: "clause", anchor: anchor, number: number, title: title,
654
+ parent: parent, breadcrumb: breadcrumb,
655
+ obligation: val(tsec, :obligation), text: text,
656
+ model: tsec
657
+ )
658
+ crumb = breadcrumb + [title]
659
+ children = term_entries(tsec).map do |t|
660
+ walk_term(t, parent: unit.id, breadcrumb: crumb,
661
+ section: tsec)
662
+ end
663
+ nested_terms_sections(tsec).each do |nested|
664
+ children << walk_terms_section(nested, parent: unit.id,
665
+ breadcrumb: crumb)
666
+ end
667
+ vals_any(tsec, :paragraphs, :p).each do |p|
668
+ children << emit_unit(type: "note", anchor: element_anchor(p),
669
+ parent: unit.id, breadcrumb: crumb,
670
+ text: Document::PlainText.call(p), model: p)
671
+ end
672
+ finalize_section(unit, ordered_child_ids(tsec, children.map(&:id)))
673
+ unit
674
+ ensure
675
+ @lang = restore_lang
676
+ end
677
+ end
678
+
679
+ def walk_term(term, parent:, breadcrumb:, section: nil)
680
+ anchor = element_anchor(term, section)
681
+ number = @numbers[anchor]
682
+ designations = vals(term, :preferred)
683
+ .map { |d| Document::PlainText.call(d) }.reject(&:empty?)
684
+ # both trees declare the synonym attrs as :admitted/:deprecates
685
+ admitted = vals(term, :admitted)
686
+ .map { |d| Document::PlainText.call(d) }.reject(&:empty?)
687
+ deprecated = vals(term, :deprecates)
688
+ .map { |d| Document::PlainText.call(d) }.reject(&:empty?)
689
+ definition = Document::PlainText.call(val(term, :definition))
690
+ sources = vals(term, :source).map do |src|
691
+ Schema::TermSourceEntry.new(
692
+ citeas: vals(src, :origin).first&.citeas,
693
+ )
694
+ end
695
+ payload = Schema::TermPayload.new(
696
+ concept: anchor, designations: designations,
697
+ admitted: admitted, deprecated: deprecated,
698
+ definition: definition, sources: sources
699
+ )
700
+ unit = emit_unit(
701
+ type: "term", anchor: anchor, number: number,
702
+ title: designations.first, parent: parent,
703
+ breadcrumb: breadcrumb, text: definition, model: term,
704
+ payload: payload
705
+ )
706
+ @edges << Schema::Edge.new(
707
+ from: unit.id, to: "concept:#{anchor}", kind: "defines",
708
+ )
709
+ sources.filter_map(&:citeas).each do |citeas|
710
+ cited = Document::PlainText.call(citeas)
711
+ next if cited.empty?
712
+
713
+ @edges << Schema::Edge.new(
714
+ from: unit.id, to: "ext:#{cited}", kind: "cites",
715
+ )
716
+ end
717
+ unit
718
+ end
719
+
720
+ def section_text(section)
721
+ collections = SECTION_PROSE_SOURCES.each_with_object({}) do |(_name, attr), h|
722
+ h[attr] = vals(section, attr)
723
+ end
724
+ indices = Hash.new(0)
725
+ parts = []
726
+ # element_order is nil on programmatically built (unparsed)
727
+ # models — the declaration-order fallback below covers them
728
+ Array(section.element_order).each do |el|
729
+ next unless el.is_a?(Lutaml::Xml::Element)
730
+
731
+ attr = SECTION_PROSE_SOURCES[el.name.to_s] or next
732
+ item = collections[attr][indices[attr]]
733
+ indices[attr] += 1
734
+ text = Document::PlainText.call(item) if item
735
+ parts << text if text && !text.empty?
736
+ end
737
+ # element_order unavailable: fall back to declaration order
738
+ if parts.empty?
739
+ parts = collections.values.flatten.filter_map do |item|
740
+ text = Document::PlainText.call(item)
741
+ text unless text.empty?
742
+ end
743
+ end
744
+ parts.join("\n")
745
+ end
746
+
747
+ def walk_blocks(section, parent_id, breadcrumb)
748
+ BLOCK_SOURCES.flat_map do |type, attr|
749
+ vals(section, attr)
750
+ .flat_map { |block| Array(send("walk_#{type}", block, parent_id, breadcrumb)) }
751
+ end + walk_requirements(section, parent_id, breadcrumb)
752
+ end
753
+
754
+ def walk_table(table, parent_id, breadcrumb)
755
+ head_row = vals(val(table, :thead), :tr).first
756
+ columns = vals(head_row, :th).map do |cell|
757
+ Schema::TableColumn.new(label: Document::PlainText.call(cell))
758
+ end
759
+ rows = vals(val(table, :tbody), :tr).map do |tr|
760
+ vals(tr, :td).map { |cell| Document::PlainText.call(cell) }.join(" | ")
761
+ end
762
+ payload = Schema::TablePayload.new(
763
+ caption: Document::PlainText.call(val(table, :name)),
764
+ columns: columns, rows: rows,
765
+ mirror: Document::NativeModels.mirror_object(table)
766
+ )
767
+ emit_unit(
768
+ type: "table", anchor: element_anchor(table),
769
+ number: val(table, :autonum),
770
+ title: payload.caption, parent: parent_id,
771
+ breadcrumb: breadcrumb, text: payload.embed_text,
772
+ model: table, payload: payload
773
+ )
774
+ end
775
+
776
+ # Image maps: the wrapped figures are the content — each emits
777
+ # as a figure unit (caption, image, native mirror); the map's
778
+ # link geometry travels in the mirror object.
779
+ def walk_svgmap(svgmap, parent_id, breadcrumb)
780
+ vals(svgmap, :figure).map do |figure|
781
+ walk_figure(figure, parent_id, breadcrumb)
782
+ end
783
+ end
784
+
785
+ def walk_imagemap(imagemap, parent_id, breadcrumb)
786
+ vals(imagemap, :figure).map do |figure|
787
+ walk_figure(figure, parent_id, breadcrumb)
788
+ end
789
+ end
790
+
791
+ def walk_figure(figure, parent_id, breadcrumb)
792
+ caption = Document::PlainText.call(val(figure, :name))
793
+ img = val(figure, :image)
794
+ uri = val(figure, :source) || val(img, :source) ||
795
+ val(img, :filename)
796
+ alt = val(figure, :alt) || val(img, :alt)
797
+ # retrievability invariant (#53 item 1): an uncaptioned figure
798
+ # still carries retrievable text — its alt text
799
+ retrievable = [caption, alt].compact.reject { |s| s.to_s.empty? }
800
+ payload = Schema::FigurePayload.new(
801
+ alt: alt, uri: uri,
802
+ asset: @assets&.attach(uri),
803
+ caption: caption,
804
+ mirror: Document::NativeModels.mirror_object(figure)
805
+ )
806
+ emit_unit(
807
+ type: "figure", anchor: element_anchor(figure),
808
+ number: val(figure, :autonum), title: caption,
809
+ parent: parent_id, breadcrumb: breadcrumb,
810
+ text: retrievable.join(". "), model: figure, payload: payload
811
+ )
812
+ end
813
+
814
+ def walk_formula(formula, parent_id, breadcrumb)
815
+ stem = val(formula, :stem)
816
+ asciimath = val(stem, :asciimath)&.value
817
+ mathml = mathml_of(stem)
818
+ description = Document::PlainText.call(val(formula, :name))
819
+ # Native math object: Plurimath's own serializations (it has no
820
+ # data to_json; to_* are the native forms). Derived encodings
821
+ # are marked with their converter — never canonical (#55).
822
+ plurimath = Document::NativeModels.plurimath_formula(formula)
823
+ payload = Schema::FormulaPayload.new(
824
+ asciimath: asciimath, mathml: mathml,
825
+ units: register_refs(asciimath),
826
+ latex: derived_form(plurimath, :to_latex),
827
+ omml: derived_form(plurimath, :to_omml),
828
+ description: description
829
+ )
830
+ emit_unit(
831
+ type: "formula", anchor: element_anchor(formula),
832
+ number: val(formula, :autonum), title: description,
833
+ parent: parent_id, breadcrumb: breadcrumb,
834
+ text: [asciimath, description].compact.join(" — "),
835
+ model: formula, payload: payload
836
+ )
837
+ end
838
+
839
+ def derived_form(plurimath, serializer)
840
+ form = plurimath&.public_send(serializer)
841
+ form && Schema::FormulaPayload::DerivedForm.new(
842
+ form: form, converter: "plurimath",
843
+ )
844
+ end
845
+
846
+ # Units a formula uses: the unitsml() tokens of its AsciiMath,
847
+ # resolved against the bundle register by symbol — so consumers
848
+ # compare quantity kinds, never unit strings (#55).
849
+ def register_refs(asciimath)
850
+ asciimath.to_s.scan(/unitsml\(([^)]+)\)/)
851
+ .map(&:first).filter_map { |sym| @units_by_symbol[sym] }
852
+ .uniq
853
+ end
854
+
855
+ def mathml_of(stem)
856
+ math = val(stem, :math)
857
+ return nil unless math
858
+
859
+ math.to_xml
860
+ rescue StandardError
861
+ nil
862
+ end
863
+
864
+ def walk_note(note, parent_id, breadcrumb)
865
+ emit_unit(
866
+ type: "note", anchor: element_anchor(note), parent: parent_id,
867
+ breadcrumb: breadcrumb, text: Document::PlainText.call(note), model: note
868
+ )
869
+ end
870
+
871
+ def walk_example(example, parent_id, breadcrumb)
872
+ emit_unit(
873
+ type: "example", anchor: element_anchor(example),
874
+ parent: parent_id, breadcrumb: breadcrumb,
875
+ text: Document::PlainText.call(example), model: example
876
+ )
877
+ end
878
+
879
+ def walk_sourcecode(block, parent_id, breadcrumb)
880
+ emit_unit(
881
+ type: "sourcecode", anchor: element_anchor(block),
882
+ number: val(block, :autonum),
883
+ title: Document::PlainText.call(val(block, :name)),
884
+ parent: parent_id, breadcrumb: breadcrumb,
885
+ text: Document::PlainText.call(block), model: block
886
+ )
887
+ end
888
+
889
+ def walk_requirements(section, parent_id, breadcrumb)
890
+ %i[requirement recommendation permission].flat_map do |attr|
891
+ vals(section, attr)
892
+ .map { |req| walk_requirement(req, parent_id, breadcrumb) }
893
+ end
894
+ end
895
+
896
+ def walk_requirement(req, parent_id, breadcrumb)
897
+ payload = requirement_payload(req)
898
+ unit = emit_unit(
899
+ type: "requirement", anchor: element_anchor(req),
900
+ number: val(req, :autonum), title: payload.identifier,
901
+ parent: parent_id, breadcrumb: breadcrumb,
902
+ obligation: val(req, :obligation), text: payload.statement,
903
+ model: req, payload: payload
904
+ )
905
+ klass = requirement_class(req)
906
+ if klass
907
+ @edges << Schema::Edge.new(
908
+ from: unit.id, to: "class:#{klass}", kind: "class_of",
909
+ )
910
+ end
911
+ vals(req, :requirement).each do |sub|
912
+ walk_requirement(sub, unit.id, breadcrumb)
913
+ end
914
+ unit
915
+ end
916
+
917
+ def requirement_payload(req)
918
+ statement = vals(req, :description).map { |d| Document::PlainText.call(d) }
919
+ .reject(&:empty?).join("\n")
920
+ Schema::RequirementPayload.new(
921
+ identifier: val(req, :anchor) || val(req, :id),
922
+ klass: requirement_class(req),
923
+ obligation: val(req, :obligation),
924
+ subject: val(req, :subject), statement: statement,
925
+ inherits: vals(req, :inherit).filter_map do |i|
926
+ text = Array(val(i, :text)).join
927
+ next text unless text.empty?
928
+
929
+ val(Array(val(i, :eref)).first, :citeas)
930
+ end
931
+ )
932
+ end
933
+
934
+ def requirement_class(req)
935
+ vals(req, :classification).find { |c| val(c, :tag) == "class" }
936
+ .then { |c| c && Document::PlainText.call(val(c, :value)) }
937
+ end
938
+
939
+ def walk_bibliography(model)
940
+ bibs = val(model, :bibliography)
941
+ Array(bibs).each { |bib_section| walk_references_section(bib_section) }
942
+ end
943
+
944
+ def walk_references_section(bib_section)
945
+ vals(bib_section, :references).each do |refs|
946
+ title = Document::PlainText.call(val(refs, :title))
947
+ section_unit = emit_unit(
948
+ type: "clause", anchor: element_anchor(refs), title: title,
949
+ parent: nil, breadcrumb: [], text: nil, model: refs
950
+ )
951
+ # The base tree maps entries as :bibitem; the standoc/iso
952
+ # tree nests BibliographicItems in :references.
953
+ items = vals(refs, :bibitem)
954
+ items = vals(refs, :references) if items.empty?
955
+ children = items.map do |item|
956
+ key = val(item, :anchor) || val(item, :id)
957
+ cited = docid_text(vals(item, :docidentifier).first) ||
958
+ Document::PlainText.call(val(item, :formatted_ref))
959
+ payload = Schema::ReferencePayload.new(key: key, cited: cited)
960
+ unit = emit_unit(
961
+ type: "reference", anchor: key, title: cited,
962
+ parent: section_unit.id, breadcrumb: [title].compact,
963
+ text: cited, model: item, payload: payload
964
+ )
965
+ if cited && !cited.empty?
966
+ @edges << Schema::Edge.new(
967
+ from: unit.id, to: "ext:#{cited}", kind: "cites",
968
+ )
969
+ end
970
+ unit
971
+ end
972
+ finalize_section(section_unit, children.map(&:id))
973
+ end
974
+ end
975
+
976
+ # -- unit assembly -------------------------------------------------
977
+
978
+ # Document order, not walk order (#56): children are the ids of
979
+ # this section's direct units, interleaved as the author wrote
980
+ # them. Falls back to emission order when element_order is not
981
+ # available; unmapped stragglers append so no child is lost.
982
+ def ordered_child_ids(section, emitted)
983
+ return emitted if emitted.empty?
984
+
985
+ pools = SECTION_CHILD_SOURCES.each_with_object({}) do |(name, attrs), h|
986
+ h[name] = { list: attrs.flat_map { |a| vals(section, a) }, idx: 0 }
987
+ end
988
+ ordered = []
989
+ Array(section.element_order).each do |el|
990
+ next unless el.is_a?(Lutaml::Xml::Element)
991
+
992
+ pool = pools[el.name.to_s] or next
993
+ item = pool[:list][pool[:idx]]
994
+ pool[:idx] += 1
995
+ unit = item && @element_units[item]
996
+ ordered << unit.id if unit
997
+ end
998
+ return emitted if ordered.empty?
999
+
1000
+ ordered + (emitted - ordered)
1001
+ end
1002
+
1003
+ # The section contract (#56): coverage payload, retrievable text,
1004
+ # and a hash over the finalized content. Runs after the section's
1005
+ # children are walked — summaries and membership are only known
1006
+ # then.
1007
+ def finalize_section(unit, child_ids)
1008
+ children = child_ids.filter_map { |id| @units_by_id[id] }
1009
+ payload = Schema::SectionPayload.new(
1010
+ summary: section_summary(unit, children), children: child_ids,
1011
+ )
1012
+ # Retrievability invariant: a container section with no direct
1013
+ # prose still carries retrievable text — its summary
1014
+ unit.text = payload.summary if unit.text.to_s.empty?
1015
+ unit.payload = payload_hash(payload)
1016
+ unit.hash = content_hash(unit.text, payload)
1017
+ unit
1018
+ end
1019
+
1020
+ # Deterministic composition — title plus the covered sub-clauses.
1021
+ # Never model-inferred: consumers may build their own LLM
1022
+ # summaries, but the shipped default requires none.
1023
+ def section_summary(unit, children)
1024
+ head = [unit.number, unit.title].compact
1025
+ .reject { |s| s.to_s.empty? }.join(" ")
1026
+ covered = children.filter_map do |c|
1027
+ parts = [c.number, c.title].compact.reject { |s| s.to_s.empty? }
1028
+ parts.empty? ? nil : parts.join(" ")
1029
+ end
1030
+ return head if covered.empty?
1031
+
1032
+ [head.empty? ? nil : "#{head}.", "Covers: #{covered.join('; ')}."]
1033
+ .compact.join(" ")
1034
+ end
1035
+
1036
+ # rubocop:disable-next Metrics/ParameterLists
1037
+ def emit_unit(type:, anchor:, parent:, breadcrumb:, text:, model:,
1038
+ payload: nil, number: nil, title: nil, obligation: nil)
1039
+ anchor ||= content_anchor(model, type)
1040
+ id = "u:#{anchor}"
1041
+ cite_as = citation_anchor(anchor: anchor, number: number,
1042
+ parent: parent)
1043
+ unit = Schema::Unit.new(
1044
+ id: id, type: type, anchor: anchor, number: number,
1045
+ ordinal: @ordinal, cite_as: cite_as, title: title,
1046
+ parent: parent, breadcrumb: breadcrumb.compact,
1047
+ obligation: obligation, lang: @lang.lang,
1048
+ lang_source: @lang.source, text: text,
1049
+ payload: payload_hash(payload), hash: content_hash(text, payload)
1050
+ )
1051
+ @ordinal += 1
1052
+ @units << unit
1053
+ @units_by_id[id] = unit
1054
+ @element_units[model] = unit if model
1055
+ if parent
1056
+ @edges << Schema::Edge.new(from: id, to: parent, kind: "part_of")
1057
+ end
1058
+ unit
1059
+ end
1060
+
1061
+ # Embedded objects cite their containing clause: a table in §4.1.2
1062
+ # is cited as 4.1.2, not as its own anchor. Top-level sections and
1063
+ # parentless units cite their own number/anchor.
1064
+ def citation_anchor(anchor:, number:, parent:)
1065
+ own = number || anchor
1066
+ if parent && @units_by_id
1067
+ pu = @units_by_id[parent]
1068
+ if pu && %w[clause annex].include?(pu.type)
1069
+ return (pu.number || pu.anchor || own).to_s
1070
+ end
1071
+ end
1072
+ own&.to_s
1073
+ end
1074
+
1075
+ def payload_hash(payload)
1076
+ return nil unless payload
1077
+
1078
+ JSON.parse(payload.to_json)
1079
+ end
1080
+
1081
+ def content_hash(text, payload)
1082
+ basis = text.to_s + payload.to_json
1083
+ "sha256:#{Digest::SHA256.hexdigest(basis)[0, 16]}"
1084
+ end
1085
+
1086
+ def content_anchor(model, type)
1087
+ basis = "#{model.class.name}:#{type}"
1088
+ "h-#{Digest::SHA256.hexdigest(basis)[0, 10]}"
1089
+ end
1090
+
1091
+ # Semantic anchors (anchor="term-paddy") are the human-meaningful,
1092
+ # cross-render-stable identifiers; GUIDs are the last resort. The
1093
+ # presentation model repeats the same anchor, so numbering joins
1094
+ # survive the preference.
1095
+ def element_anchor(element, parent_section = nil)
1096
+ anchor = val(element, :anchor)
1097
+ return anchor if anchor && !anchor.empty?
1098
+
1099
+ id = val(element, :id)
1100
+ return id if id && !id.empty?
1101
+
1102
+ semx = val(element, :semx_id)
1103
+ return semx if semx && !semx.empty?
1104
+
1105
+ pid = val(parent_section, :id) || val(parent_section, :anchor)
1106
+ pid && "#{pid}-x#{@units.size + 1}"
1107
+ end
1108
+ end
1109
+ end
1110
+ end