metanorma-document 0.2.9 → 0.3.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 (394) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +72 -81
  3. data/CLAUDE.md +26 -0
  4. data/README.adoc +144 -0
  5. data/Rakefile +14 -2
  6. data/TODO.finalize/01-split-multi-class-files.md +8 -0
  7. data/TODO.finalize/02-remove-data-dist-iso-document-frontend.md +6 -0
  8. data/TODO.finalize/03-align-frontend-iife-and-gha-build.md +11 -0
  9. data/TODO.finalize/04-theme-per-flavor-config-driven.md +9 -0
  10. data/TODO.finalize/05-migrate-all-html-to-liquid.md +16 -0
  11. data/TODO.finalize/06-integrate-pubid-library.md +16 -0
  12. data/TODO.finalize/07-replace-relaton-models-with-relaton-bib.md +39 -0
  13. data/TODO.finalize/08-refactor-renderer-architecture.md +25 -0
  14. data/TODO.finalize/09-eliminate-code-smells.md +14 -0
  15. data/TODO.finalize/10-comprehensive-spec-coverage.md +24 -0
  16. data/TODO.finalize/11-config-driven-flavor-metadata.md +20 -0
  17. data/TODO.finalize/12-add-frozen-string-literal.md +6 -0
  18. data/TODO.finalize/13-extract-composition-from-god-class.md +96 -0
  19. data/TODO.finalize/14-config-driven-section-ordering.md +31 -0
  20. data/TODO.finalize/15-theme-directories-per-flavor.md +46 -0
  21. data/TODO.finalize/16-theme-lutaml-model-schema.md +44 -0
  22. data/data/javascripts/components/mn-footnotes.js +68 -0
  23. data/data/javascripts/components/mn-lightbox.js +18 -2
  24. data/data/javascripts/components/mn-shortcuts.js +2 -0
  25. data/data/javascripts/core/mn-navigation.js +8 -0
  26. data/data/javascripts/core/mn-theme.js +9 -9
  27. data/data/logos/oiml-logo-full-dark.svg +1 -0
  28. data/data/logos/oiml-logo-full-light.svg +1 -0
  29. data/data/logos/oiml-logo-icon-dark.svg +1 -0
  30. data/data/logos/oiml-logo-icon-light.svg +1 -0
  31. data/data/logos/oiml-logo.svg +1 -0
  32. data/data/logos/pdfa-logo.svg +31 -94
  33. data/data/stylesheets/base/_layout.css +3 -0
  34. data/data/stylesheets/base/_reset.css +3 -3
  35. data/data/stylesheets/base/_typography.css +0 -5
  36. data/data/stylesheets/components/bibliography.css +3 -3
  37. data/data/stylesheets/components/cover.css +3 -1
  38. data/data/stylesheets/components/figure.css +13 -1
  39. data/data/stylesheets/components/footnote.css +65 -5
  40. data/data/stylesheets/components/formula.css +44 -0
  41. data/data/stylesheets/components/header.css +17 -2
  42. data/data/stylesheets/components/inline.css +1 -1
  43. data/data/stylesheets/components/table.css +15 -2
  44. data/data/stylesheets/components/term.css +0 -1
  45. data/data/themes/bipm.yaml +60 -0
  46. data/data/themes/cc.yaml +43 -0
  47. data/data/themes/icc.yaml +39 -0
  48. data/data/themes/iec.yaml +44 -0
  49. data/data/themes/ieee.yaml +43 -0
  50. data/data/themes/ietf.yaml +45 -0
  51. data/data/themes/iho.yaml +46 -0
  52. data/data/themes/iso.yaml +40 -0
  53. data/data/themes/itu.yaml +45 -0
  54. data/data/themes/ogc.yaml +51 -0
  55. data/data/themes/oiml.yaml +56 -0
  56. data/data/themes/pdfa.yaml +44 -0
  57. data/data/themes/ribose.yaml +43 -0
  58. data/exe/metanorma-document +11 -0
  59. data/frontend/e2e/sample-html.test.ts +256 -0
  60. data/frontend/index.html +12 -0
  61. data/frontend/package-lock.json +4725 -0
  62. data/frontend/package.json +32 -0
  63. data/frontend/postcss.config.js +6 -0
  64. data/frontend/src/App.vue +79 -0
  65. data/frontend/src/app.ts +21 -0
  66. data/frontend/src/components/AppSidebar.vue +77 -0
  67. data/frontend/src/components/DocumentReader.vue +180 -0
  68. data/frontend/src/components/MirrorRenderer.vue +23 -0
  69. data/frontend/src/components/ReadingProgressBar.vue +29 -0
  70. data/frontend/src/components/TextRenderer.vue +131 -0
  71. data/frontend/src/components/TocItem.vue +57 -0
  72. data/frontend/src/components/TopBar.vue +49 -0
  73. data/frontend/src/components/__tests__/MirrorRenderer.test.ts +178 -0
  74. data/frontend/src/components/blocks/AdmonitionBlock.vue +74 -0
  75. data/frontend/src/components/blocks/ClauseBlock.vue +53 -0
  76. data/frontend/src/components/blocks/ExampleBlock.vue +21 -0
  77. data/frontend/src/components/blocks/FallbackBlock.vue +18 -0
  78. data/frontend/src/components/blocks/FigureBlock.vue +26 -0
  79. data/frontend/src/components/blocks/FootnotesBlock.vue +25 -0
  80. data/frontend/src/components/blocks/ListBlocks.vue +62 -0
  81. data/frontend/src/components/blocks/NoteBlock.vue +21 -0
  82. data/frontend/src/components/blocks/ParagraphBlock.vue +25 -0
  83. data/frontend/src/components/blocks/QuoteBlock.vue +25 -0
  84. data/frontend/src/components/blocks/ReviewBlock.vue +39 -0
  85. data/frontend/src/components/blocks/SourcecodeBlock.vue +52 -0
  86. data/frontend/src/components/blocks/TableBlock.vue +48 -0
  87. data/frontend/src/components/blocks/index.ts +36 -0
  88. data/frontend/src/stores/__tests__/documentStore.fixture.test.ts +272 -0
  89. data/frontend/src/stores/__tests__/documentStore.test.ts +143 -0
  90. data/frontend/src/stores/__tests__/ebookStore.test.ts +145 -0
  91. data/frontend/src/stores/__tests__/uiStore.test.ts +42 -0
  92. data/frontend/src/stores/documentStore.ts +153 -0
  93. data/frontend/src/stores/ebookStore.ts +179 -0
  94. data/frontend/src/stores/uiStore.ts +32 -0
  95. data/frontend/src/styles.css +156 -0
  96. data/frontend/src/types/__tests__/rendering-contract.test.ts +172 -0
  97. data/frontend/src/types/rendering-contract.ts +209 -0
  98. data/frontend/src/utils/__tests__/typeMetadata.test.ts +102 -0
  99. data/frontend/src/utils/typeMetadata.ts +121 -0
  100. data/frontend/tailwind.config.js +18 -0
  101. data/frontend/test/fixtures/iso-document.json +271 -0
  102. data/frontend/tmp/debug_e2e.cjs +46 -0
  103. data/frontend/tsconfig.json +25 -0
  104. data/frontend/tsconfig.node.json +11 -0
  105. data/frontend/vite.config.ts +37 -0
  106. data/frontend/vitest.e2e.config.ts +9 -0
  107. data/lib/metanorma/bipm_document/metadata/bipm_bibliographic_item.rb +0 -14
  108. data/lib/metanorma/bipm_document/metadata/depiction_element.rb +18 -0
  109. data/lib/metanorma/bipm_document/metadata.rb +1 -1
  110. data/lib/metanorma/document/cli.rb +116 -0
  111. data/lib/metanorma/document/components/ancillary_blocks/callout_annotation.rb +27 -0
  112. data/lib/metanorma/document/components/ancillary_blocks/sourcecode_block.rb +0 -65
  113. data/lib/metanorma/document/components/ancillary_blocks/sourcecode_body.rb +18 -0
  114. data/lib/metanorma/document/components/ancillary_blocks.rb +4 -0
  115. data/lib/metanorma/document/components/bib_data/bib_data.rb +7 -0
  116. data/lib/metanorma/document/components/blocks/classification_value.rb +21 -0
  117. data/lib/metanorma/document/components/blocks/permission_model.rb +28 -0
  118. data/lib/metanorma/document/components/blocks/recommendation_model.rb +28 -0
  119. data/lib/metanorma/document/components/blocks/requirement_base.rb +30 -0
  120. data/lib/metanorma/document/components/blocks/requirement_classification.rb +20 -0
  121. data/lib/metanorma/document/components/blocks/requirement_description.rb +43 -0
  122. data/lib/metanorma/document/components/blocks/requirement_inherit.rb +24 -0
  123. data/lib/metanorma/document/components/blocks/requirement_model.rb +0 -131
  124. data/lib/metanorma/document/components/blocks.rb +8 -7
  125. data/lib/metanorma/document/components/id_elements/image.rb +2 -0
  126. data/lib/metanorma/document/components/inline/asciimath_element.rb +2 -0
  127. data/lib/metanorma/document/components/inline/attribution_element.rb +2 -0
  128. data/lib/metanorma/document/components/inline/bcp14_element.rb +2 -0
  129. data/lib/metanorma/document/components/inline/biblio_tag_element.rb +2 -0
  130. data/lib/metanorma/document/components/inline/br_element.rb +2 -0
  131. data/lib/metanorma/document/components/inline/comma_element.rb +2 -0
  132. data/lib/metanorma/document/components/inline/concept_element.rb +2 -0
  133. data/lib/metanorma/document/components/inline/display_text_element.rb +2 -0
  134. data/lib/metanorma/document/components/inline/em_raw_element.rb +2 -0
  135. data/lib/metanorma/document/components/inline/enum_comma_element.rb +2 -0
  136. data/lib/metanorma/document/components/inline/eref_element.rb +2 -0
  137. data/lib/metanorma/document/components/inline/fmt_admitted_element.rb +4 -0
  138. data/lib/metanorma/document/components/inline/fmt_annotation_body_element.rb +4 -0
  139. data/lib/metanorma/document/components/inline/fmt_annotation_end_element.rb +4 -0
  140. data/lib/metanorma/document/components/inline/fmt_annotation_start_element.rb +4 -0
  141. data/lib/metanorma/document/components/inline/fmt_concept_element.rb +4 -0
  142. data/lib/metanorma/document/components/inline/fmt_definition_element.rb +4 -0
  143. data/lib/metanorma/document/components/inline/fmt_fn_body_element.rb +4 -0
  144. data/lib/metanorma/document/components/inline/fmt_fn_label_element.rb +4 -0
  145. data/lib/metanorma/document/components/inline/fmt_footnote_container_element.rb +4 -0
  146. data/lib/metanorma/document/components/inline/fmt_identifier_element.rb +4 -0
  147. data/lib/metanorma/document/components/inline/fmt_name_element.rb +4 -0
  148. data/lib/metanorma/document/components/inline/fmt_preferred_element.rb +4 -0
  149. data/lib/metanorma/document/components/inline/fmt_sourcecode_element.rb +4 -0
  150. data/lib/metanorma/document/components/inline/fmt_stem_element.rb +10 -0
  151. data/lib/metanorma/document/components/inline/fmt_termsource_element.rb +4 -0
  152. data/lib/metanorma/document/components/inline/fmt_title_element.rb +4 -0
  153. data/lib/metanorma/document/components/inline/fmt_xref_element.rb +4 -0
  154. data/lib/metanorma/document/components/inline/fmt_xref_label_element.rb +4 -0
  155. data/lib/metanorma/document/components/inline/fn_element.rb +2 -0
  156. data/lib/metanorma/document/components/inline/link_element.rb +2 -0
  157. data/lib/metanorma/document/components/inline/localized_string_element.rb +2 -0
  158. data/lib/metanorma/document/components/inline/localized_strings_element.rb +2 -0
  159. data/lib/metanorma/document/components/inline/location_element.rb +24 -0
  160. data/lib/metanorma/document/components/inline/math_element.rb +2 -0
  161. data/lib/metanorma/document/components/inline/name_with_id_element.rb +4 -0
  162. data/lib/metanorma/document/components/inline/rendered_display.rb +23 -0
  163. data/lib/metanorma/document/components/inline/rendered_math_element.rb +21 -0
  164. data/lib/metanorma/document/components/inline/semantic_content.rb +51 -0
  165. data/lib/metanorma/document/components/inline/semantic_math_element.rb +20 -0
  166. data/lib/metanorma/document/components/inline/semx_child_element.rb +2 -0
  167. data/lib/metanorma/document/components/inline/semx_element.rb +12 -1
  168. data/lib/metanorma/document/components/inline/small_cap_element.rb +2 -0
  169. data/lib/metanorma/document/components/inline/span_element.rb +10 -15
  170. data/lib/metanorma/document/components/inline/stem_inline_element.rb +7 -1
  171. data/lib/metanorma/document/components/inline/strong_raw_element.rb +2 -0
  172. data/lib/metanorma/document/components/inline/sub_element.rb +2 -0
  173. data/lib/metanorma/document/components/inline/sup_element.rb +2 -0
  174. data/lib/metanorma/document/components/inline/tab_element.rb +2 -0
  175. data/lib/metanorma/document/components/inline/title_with_annotation_element.rb +4 -0
  176. data/lib/metanorma/document/components/inline/tt_element.rb +2 -0
  177. data/lib/metanorma/document/components/inline/variant_title_element.rb +2 -0
  178. data/lib/metanorma/document/components/inline/vocabulary.rb +184 -0
  179. data/lib/metanorma/document/components/inline/xref_element.rb +10 -0
  180. data/lib/metanorma/document/components/inline.rb +12 -0
  181. data/lib/metanorma/document/components/multi_paragraph/quote_author_element.rb +20 -0
  182. data/lib/metanorma/document/components/multi_paragraph/quote_block.rb +0 -29
  183. data/lib/metanorma/document/components/multi_paragraph.rb +2 -0
  184. data/lib/metanorma/document/components/reference_elements/source_element.rb +0 -31
  185. data/lib/metanorma/document/components/reference_elements/source_modification.rb +21 -0
  186. data/lib/metanorma/document/components/reference_elements/source_origin.rb +28 -0
  187. data/lib/metanorma/document/components/reference_elements.rb +2 -2
  188. data/lib/metanorma/document/components/tables/col_element.rb +18 -0
  189. data/lib/metanorma/document/components/tables/col_group_element.rb +18 -0
  190. data/lib/metanorma/document/components/tables/table_block.rb +0 -22
  191. data/lib/metanorma/document/components/tables/table_body_section.rb +16 -0
  192. data/lib/metanorma/document/components/tables/table_foot_section.rb +16 -0
  193. data/lib/metanorma/document/components/tables/table_head_section.rb +16 -0
  194. data/lib/metanorma/document/components/tables/table_section.rb +0 -25
  195. data/lib/metanorma/document/components/tables.rb +5 -3
  196. data/lib/metanorma/document/relaton/address.rb +0 -27
  197. data/lib/metanorma/document/relaton/bib_item_locality.rb +3 -12
  198. data/lib/metanorma/document/relaton/copyright_association.rb +0 -10
  199. data/lib/metanorma/document/relaton/copyright_owner.rb +16 -0
  200. data/lib/metanorma/document/relaton/edition.rb +4 -7
  201. data/lib/metanorma/document/relaton/formatted_address.rb +29 -0
  202. data/lib/metanorma/document/relaton/logo_element.rb +18 -0
  203. data/lib/metanorma/document/relaton/organization.rb +0 -14
  204. data/lib/metanorma/document/relaton/phone.rb +3 -9
  205. data/lib/metanorma/document/relaton/place_type.rb +0 -13
  206. data/lib/metanorma/document/relaton/price_type.rb +3 -9
  207. data/lib/metanorma/document/relaton/region_element.rb +18 -0
  208. data/lib/metanorma/document/relaton.rb +6 -1
  209. data/lib/metanorma/document/version.rb +1 -1
  210. data/lib/metanorma/document.rb +2 -0
  211. data/lib/metanorma/html/asset_pipeline.rb +1 -0
  212. data/lib/metanorma/html/base_renderer.rb +284 -1057
  213. data/lib/metanorma/html/bipm_renderer.rb +15 -144
  214. data/lib/metanorma/html/cc_renderer.rb +1 -45
  215. data/lib/metanorma/html/component/base.rb +2 -14
  216. data/lib/metanorma/html/component/index_section.rb +41 -43
  217. data/lib/metanorma/html/drops/admonition_drop.rb +1 -4
  218. data/lib/metanorma/html/drops/biblio_entry_drop.rb +50 -0
  219. data/lib/metanorma/html/drops/block_element_drop.rb +7 -2
  220. data/lib/metanorma/html/drops/example_drop.rb +1 -4
  221. data/lib/metanorma/html/drops/figure_drop.rb +31 -22
  222. data/lib/metanorma/html/drops/footnote_drop.rb +1 -1
  223. data/lib/metanorma/html/drops/formula_drop.rb +45 -16
  224. data/lib/metanorma/html/drops/note_drop.rb +9 -7
  225. data/lib/metanorma/html/drops/sourcecode_drop.rb +12 -5
  226. data/lib/metanorma/html/drops.rb +2 -0
  227. data/lib/metanorma/html/flavor.rb +45 -0
  228. data/lib/metanorma/html/flavor_registry.rb +49 -0
  229. data/lib/metanorma/html/generator.rb +108 -31
  230. data/lib/metanorma/html/icc_renderer.rb +0 -39
  231. data/lib/metanorma/html/iec_renderer.rb +1 -44
  232. data/lib/metanorma/html/ieee_renderer.rb +1 -45
  233. data/lib/metanorma/html/ietf_renderer.rb +1 -48
  234. data/lib/metanorma/html/iho_renderer.rb +1 -46
  235. data/lib/metanorma/html/iso_renderer.rb +290 -343
  236. data/lib/metanorma/html/itu_renderer.rb +1 -45
  237. data/lib/metanorma/html/ogc_renderer.rb +15 -76
  238. data/lib/metanorma/html/oiml_renderer.rb +18 -38
  239. data/lib/metanorma/html/pdfa_renderer.rb +0 -39
  240. data/lib/metanorma/html/renderer_delegation.rb +21 -0
  241. data/lib/metanorma/html/renderers/block_renderer.rb +372 -0
  242. data/lib/metanorma/html/renderers/element_order_traversal.rb +34 -0
  243. data/lib/metanorma/html/renderers/inline_renderer.rb +642 -0
  244. data/lib/metanorma/html/renderers/pubid_renderer.rb +47 -0
  245. data/lib/metanorma/html/renderers/section_renderer.rb +193 -0
  246. data/lib/metanorma/html/renderers.rb +10 -0
  247. data/lib/metanorma/html/ribose_renderer.rb +1 -45
  248. data/lib/metanorma/html/standard_renderer.rb +344 -230
  249. data/lib/metanorma/html/templates/_audio.html.liquid +1 -0
  250. data/lib/metanorma/html/templates/_biblio_entry.html.liquid +1 -0
  251. data/lib/metanorma/html/templates/_bipm_cover.html.liquid +27 -0
  252. data/lib/metanorma/html/templates/_boilerplate.html.liquid +1 -0
  253. data/lib/metanorma/html/templates/_bookmark.html.liquid +1 -0
  254. data/lib/metanorma/html/templates/_br.html.liquid +1 -0
  255. data/lib/metanorma/html/templates/_definition_list.html.liquid +6 -0
  256. data/lib/metanorma/html/templates/_element.html.liquid +1 -0
  257. data/lib/metanorma/html/templates/_fn_marker.html.liquid +1 -0
  258. data/lib/metanorma/html/templates/_footnotes.html.liquid +6 -6
  259. data/lib/metanorma/html/templates/_formula.html.liquid +1 -1
  260. data/lib/metanorma/html/templates/_header.html.liquid +1 -1
  261. data/lib/metanorma/html/templates/_heading.html.liquid +1 -0
  262. data/lib/metanorma/html/templates/_image.html.liquid +1 -0
  263. data/lib/metanorma/html/templates/_inline_span.html.liquid +1 -0
  264. data/lib/metanorma/html/templates/_iso_cover.html.liquid +1 -1
  265. data/lib/metanorma/html/templates/_link.html.liquid +1 -0
  266. data/lib/metanorma/html/templates/_list.html.liquid +3 -0
  267. data/lib/metanorma/html/templates/_list_item.html.liquid +1 -0
  268. data/lib/metanorma/html/templates/_main_content.html.liquid +1 -0
  269. data/lib/metanorma/html/templates/_math_container.html.liquid +1 -0
  270. data/lib/metanorma/html/templates/_paragraph.html.liquid +1 -0
  271. data/lib/metanorma/html/templates/_prefatory_section.html.liquid +1 -0
  272. data/lib/metanorma/html/templates/_pubid_identifier.html.liquid +1 -0
  273. data/lib/metanorma/html/templates/_quote.html.liquid +4 -0
  274. data/lib/metanorma/html/templates/_ref_date.html.liquid +1 -0
  275. data/lib/metanorma/html/templates/_ref_title.html.liquid +1 -0
  276. data/lib/metanorma/html/templates/_section.html.liquid +5 -0
  277. data/lib/metanorma/html/templates/_standard_cover.html.liquid +31 -0
  278. data/lib/metanorma/html/templates/_standard_doc_title.html.liquid +3 -0
  279. data/lib/metanorma/html/templates/_stem_span.html.liquid +1 -0
  280. data/lib/metanorma/html/templates/_table.html.liquid +9 -0
  281. data/lib/metanorma/html/templates/_term_domain.html.liquid +1 -0
  282. data/lib/metanorma/html/templates/_term_example.html.liquid +1 -0
  283. data/lib/metanorma/html/templates/_term_note.html.liquid +1 -0
  284. data/lib/metanorma/html/templates/_term_number.html.liquid +1 -0
  285. data/lib/metanorma/html/templates/_term_source.html.liquid +1 -0
  286. data/lib/metanorma/html/templates/_term_text_note.html.liquid +1 -0
  287. data/lib/metanorma/html/templates/_video.html.liquid +1 -0
  288. data/lib/metanorma/html/templates/_wrapped_preface.html.liquid +1 -0
  289. data/lib/metanorma/html/templates/document.html.liquid +4 -0
  290. data/lib/metanorma/html/theme.rb +180 -107
  291. data/lib/metanorma/html.rb +16 -0
  292. data/lib/metanorma/ietf_document/metadata/ietf_bib_data_extension_type.rb +0 -25
  293. data/lib/metanorma/ietf_document/metadata/pi_settings.rb +32 -0
  294. data/lib/metanorma/ietf_document/metadata.rb +2 -0
  295. data/lib/metanorma/iso_document/metadata/doctype_element.rb +20 -0
  296. data/lib/metanorma/iso_document/metadata/iso_bib_data_extension_type.rb +0 -48
  297. data/lib/metanorma/iso_document/metadata/iso_bibliographic_item.rb +0 -72
  298. data/lib/metanorma/iso_document/metadata/iso_document_status.rb +0 -30
  299. data/lib/metanorma/iso_document/metadata/iso_localized_title.rb +4 -1
  300. data/lib/metanorma/iso_document/metadata/language_element.rb +18 -0
  301. data/lib/metanorma/iso_document/metadata/project_number.rb +22 -0
  302. data/lib/metanorma/iso_document/metadata/script_element.rb +18 -0
  303. data/lib/metanorma/iso_document/metadata/stage_element.rb +26 -0
  304. data/lib/metanorma/iso_document/metadata/stagename_element.rb +18 -0
  305. data/lib/metanorma/iso_document/metadata/structured_identifier.rb +0 -17
  306. data/lib/metanorma/iso_document/metadata/title_collection.rb +1 -0
  307. data/lib/metanorma/iso_document/metadata.rb +9 -4
  308. data/lib/metanorma/iso_document/terms/iso_term.rb +0 -13
  309. data/lib/metanorma/iso_document/terms/term_domain_element.rb +18 -0
  310. data/lib/metanorma/iso_document/terms.rb +1 -0
  311. data/lib/metanorma/itu_document/metadata/itu_bib_data_extension_type.rb +0 -37
  312. data/lib/metanorma/itu_document/metadata/meeting_date.rb +18 -0
  313. data/lib/metanorma/itu_document/metadata/meeting_element.rb +18 -0
  314. data/lib/metanorma/itu_document/metadata/study_period.rb +18 -0
  315. data/lib/metanorma/itu_document/metadata.rb +3 -4
  316. data/lib/metanorma/mirror/default_registry.rb +170 -0
  317. data/lib/metanorma/mirror/handler_registry.rb +60 -0
  318. data/lib/metanorma/mirror/handler_result.rb +37 -0
  319. data/lib/metanorma/mirror/handlers/admonition.rb +18 -0
  320. data/lib/metanorma/mirror/handlers/example.rb +21 -0
  321. data/lib/metanorma/mirror/handlers/figure.rb +53 -0
  322. data/lib/metanorma/mirror/handlers/formula.rb +35 -0
  323. data/lib/metanorma/mirror/handlers/inline/catalog.rb +57 -0
  324. data/lib/metanorma/mirror/handlers/inline/rich_html_renderer.rb +159 -0
  325. data/lib/metanorma/mirror/handlers/inline/text_extractor.rb +116 -0
  326. data/lib/metanorma/mirror/handlers/inline.rb +226 -0
  327. data/lib/metanorma/mirror/handlers/list.rb +110 -0
  328. data/lib/metanorma/mirror/handlers/note.rb +26 -0
  329. data/lib/metanorma/mirror/handlers/paragraph.rb +19 -0
  330. data/lib/metanorma/mirror/handlers/quote.rb +16 -0
  331. data/lib/metanorma/mirror/handlers/review.rb +19 -0
  332. data/lib/metanorma/mirror/handlers/section.rb +149 -0
  333. data/lib/metanorma/mirror/handlers/sourcecode.rb +27 -0
  334. data/lib/metanorma/mirror/handlers/structural.rb +68 -0
  335. data/lib/metanorma/mirror/handlers/table.rb +81 -0
  336. data/lib/metanorma/mirror/handlers/term.rb +100 -0
  337. data/lib/metanorma/mirror/handlers.rb +60 -0
  338. data/lib/metanorma/mirror/id_strategy/positional.rb +166 -0
  339. data/lib/metanorma/mirror/id_strategy/preserve.rb +12 -0
  340. data/lib/metanorma/mirror/id_strategy.rb +34 -0
  341. data/lib/metanorma/mirror/math_util.rb +53 -0
  342. data/lib/metanorma/mirror/metadata.rb +37 -0
  343. data/lib/metanorma/mirror/model/container.rb +47 -0
  344. data/lib/metanorma/mirror/model/factory.rb +52 -0
  345. data/lib/metanorma/mirror/model/guide.rb +25 -0
  346. data/lib/metanorma/mirror/model/leaf.rb +17 -0
  347. data/lib/metanorma/mirror/model/mark.rb +53 -0
  348. data/lib/metanorma/mirror/model/node.rb +47 -0
  349. data/lib/metanorma/mirror/model/soft_break.rb +33 -0
  350. data/lib/metanorma/mirror/model/text.rb +34 -0
  351. data/lib/metanorma/mirror/model.rb +16 -0
  352. data/lib/metanorma/mirror/output/builder.rb +39 -0
  353. data/lib/metanorma/mirror/output/formats/base_format.rb +90 -0
  354. data/lib/metanorma/mirror/output/formats/inline_format.rb +81 -0
  355. data/lib/metanorma/mirror/output/formats.rb +53 -0
  356. data/lib/metanorma/mirror/output/html_renderer.rb +95 -0
  357. data/lib/metanorma/mirror/output/html_renderers/block_renderers.rb +178 -0
  358. data/lib/metanorma/mirror/output/html_renderers/inline_renderer.rb +61 -0
  359. data/lib/metanorma/mirror/output/html_renderers/list_renderers.rb +96 -0
  360. data/lib/metanorma/mirror/output/html_renderers/mark_renderers.rb +41 -0
  361. data/lib/metanorma/mirror/output/html_renderers/section_renderers.rb +129 -0
  362. data/lib/metanorma/mirror/output/html_renderers/structural_renderers.rb +80 -0
  363. data/lib/metanorma/mirror/output/html_renderers/table_renderers.rb +87 -0
  364. data/lib/metanorma/mirror/output/html_renderers.rb +76 -0
  365. data/lib/metanorma/mirror/output/pipeline.rb +92 -0
  366. data/lib/metanorma/mirror/output/pipeline_context.rb +22 -0
  367. data/lib/metanorma/mirror/output.rb +14 -0
  368. data/lib/metanorma/mirror/rewriter.rb +121 -0
  369. data/lib/metanorma/mirror/safe_attr.rb +16 -0
  370. data/lib/metanorma/mirror/serialization/json_serializer.rb +25 -0
  371. data/lib/metanorma/mirror/serialization/yaml_serializer.rb +20 -0
  372. data/lib/metanorma/mirror/serialization.rb +10 -0
  373. data/lib/metanorma/mirror/transformer.rb +110 -0
  374. data/lib/metanorma/mirror.rb +31 -0
  375. data/lib/metanorma/standard_document/annotation.rb +37 -0
  376. data/lib/metanorma/standard_document/block_attributes.rb +15 -2
  377. data/lib/metanorma/standard_document/blocks/block_source.rb +0 -23
  378. data/lib/metanorma/standard_document/blocks/source_origin_element.rb +23 -0
  379. data/lib/metanorma/standard_document/blocks.rb +1 -1
  380. data/lib/metanorma/standard_document/terms/term_expression.rb +0 -31
  381. data/lib/metanorma/standard_document/terms/term_name_element.rb +29 -0
  382. data/lib/metanorma/standard_document/terms.rb +1 -0
  383. data/script/render_both.rb +18 -0
  384. metadata +272 -13
  385. data/.github/workflows/rake.yml +0 -21
  386. data/.github/workflows/release.yml +0 -25
  387. data/.github/workflows/roundtrip-samples.yml +0 -132
  388. data/.gitignore +0 -19
  389. data/.rspec +0 -3
  390. data/.rubocop.yml +0 -22
  391. data/Gemfile +0 -28
  392. data/bin/console +0 -15
  393. data/bin/setup +0 -8
  394. data/metanorma-document.gemspec +0 -35
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da12347749776b99b68dcc3816de625289ce2e56f51a9716c05d38088a4913b7
4
- data.tar.gz: 2bf0989d205c0ab6a0c282fd5c4cab7d0c7deaccf012baafc64fa97c9160029f
3
+ metadata.gz: 05125b6d3a480509e630600c04a152707d7af1cf32423a3906b99032fbb18fcd
4
+ data.tar.gz: 1c45b49ac852306fca98f05fc59cb0f790a5b814a6a3b8b56cfd6960b9cc726c
5
5
  SHA512:
6
- metadata.gz: 1af9848d0640195e2176378b067214afe02f1da504893ed09686625e0a79f637bfad69fa5575d0959b59d31e78eff2b986dcdb42d63169db853ab808fb52bf22
7
- data.tar.gz: 8dd05a2a8ed9344e5d70a35ab3c4653ec9b4c69dc001ef3cd400c6822b4874b8582304df5eee0798f37f8da43388e90fc71dbd88402d41fc1e401cd95fcd396a
6
+ metadata.gz: 9ebfc36533344fd420cc2286357b77d4fe94213c8f31a0defdd7c4b06af6938e0258472bd279bd275f6a85f34a2c991a4fd12cd90d0b212e5ae9780506353600
7
+ data.tar.gz: 66b5e24551fd844cb8ad9368a10468509e0b3a7cc15836b5400d57024513da889c76f2571fc0d97cfdfacfacca7e72e6fe8d5d6f92c5bb54bc0a7d57d08d43ad
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-05-21 09:20:40 UTC using RuboCop version 1.86.1.
3
+ # on 2026-07-18 16:00:24 UTC using RuboCop version 1.86.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -11,124 +11,102 @@ Gemspec/RequiredRubyVersion:
11
11
  Exclude:
12
12
  - 'metanorma-document.gemspec'
13
13
 
14
- # Offense count: 9
15
- # This cop supports safe autocorrection (--autocorrect).
16
- # Configuration parameters: EnforcedStyle, IndentationWidth.
17
- # SupportedStyles: with_first_argument, with_fixed_indentation
18
- Layout/ArgumentAlignment:
19
- Exclude:
20
- - 'lib/metanorma/standard_document.rb'
21
- - 'spec/metanorma/flavor_roots/flavor_roots_spec.rb'
22
-
23
- # Offense count: 666
14
+ # Offense count: 703
24
15
  # This cop supports safe autocorrection (--autocorrect).
25
16
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
26
17
  # URISchemes: http, https
27
18
  Layout/LineLength:
28
19
  Enabled: false
29
20
 
30
- # Offense count: 1
31
- # This cop supports safe autocorrection (--autocorrect).
32
- # Configuration parameters: AllowInHeredoc.
33
- Layout/TrailingWhitespace:
34
- Exclude:
35
- - 'lib/metanorma/standard_document.rb'
36
-
37
- # Offense count: 1
21
+ # Offense count: 2
38
22
  # Configuration parameters: AllowedMethods.
39
23
  # AllowedMethods: enums
40
24
  Lint/ConstantDefinitionInBlock:
41
25
  Exclude:
42
26
  - 'spec/metanorma/html/renderer/class_ownership_spec.rb'
27
+ - 'spec/metanorma/html/renderer/liquid_templates_spec.rb'
43
28
 
44
- # Offense count: 1
29
+ # Offense count: 3
45
30
  # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
46
31
  Lint/DuplicateBranch:
47
32
  Exclude:
48
33
  - 'lib/metanorma/html/base_renderer.rb'
34
+ - 'lib/metanorma/html/renderers/inline_renderer.rb'
35
+ - 'lib/metanorma/html/standard_renderer.rb'
49
36
 
50
- # Offense count: 3
37
+ # Offense count: 2
51
38
  # Configuration parameters: AllowComments, AllowEmptyLambdas.
52
39
  Lint/EmptyBlock:
53
40
  Exclude:
54
- - 'lib/metanorma/html/drops/formula_drop.rb'
55
41
  - 'spec/metanorma/iso_document/metadata/title_roundtrip_spec.rb'
56
42
 
57
43
  # Offense count: 4
58
44
  # Configuration parameters: AllowedParentClasses.
59
45
  Lint/MissingSuper:
60
46
  Exclude:
61
- - 'lib/metanorma/html/drops/block_element_drop.rb'
62
- - 'lib/metanorma/html/drops/figure_list_entry_drop.rb'
63
- - 'lib/metanorma/html/drops/footnote_drop.rb'
64
- - 'lib/metanorma/html/drops/toc_entry_drop.rb'
47
+ - 'lib/metanorma/html/drops/figure_drop.rb'
48
+ - 'lib/metanorma/html/drops/formula_drop.rb'
49
+ - 'lib/metanorma/html/drops/sourcecode_drop.rb'
50
+ - 'lib/metanorma/mirror/id_strategy/positional.rb'
65
51
 
66
52
  # Offense count: 1
67
53
  Lint/StructNewOverride:
68
54
  Exclude:
69
55
  - 'lib/metanorma/html/component/index_term_collector.rb'
70
56
 
71
- # Offense count: 80
57
+ # Offense count: 23
58
+ # This cop supports safe autocorrection (--autocorrect).
59
+ # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions.
60
+ # NotImplementedExceptions: NotImplementedError
61
+ Lint/UnusedMethodArgument:
62
+ Exclude:
63
+ - 'lib/metanorma/mirror/handlers/formula.rb'
64
+ - 'lib/metanorma/mirror/handlers/sourcecode.rb'
65
+ - 'lib/metanorma/mirror/output/html_renderers/block_renderers.rb'
66
+ - 'lib/metanorma/mirror/output/html_renderers/list_renderers.rb'
67
+ - 'lib/metanorma/mirror/output/html_renderers/section_renderers.rb'
68
+ - 'lib/metanorma/mirror/output/html_renderers/structural_renderers.rb'
69
+ - 'lib/metanorma/mirror/output/html_renderers/table_renderers.rb'
70
+ - 'spec/metanorma/mirror/handler_registry_spec.rb'
71
+ - 'spec/metanorma/mirror/html_renderer_spec.rb'
72
+
73
+ # Offense count: 114
72
74
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
73
75
  Metrics/AbcSize:
74
76
  Enabled: false
75
77
 
76
- # Offense count: 23
78
+ # Offense count: 12
77
79
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
78
80
  # AllowedMethods: refine
79
81
  Metrics/BlockLength:
80
- Max: 80
82
+ Max: 76
81
83
 
82
- # Offense count: 1
84
+ # Offense count: 3
83
85
  # Configuration parameters: CountBlocks, CountModifierForms.
84
86
  Metrics/BlockNesting:
85
87
  Max: 4
86
88
 
87
- # Offense count: 56
89
+ # Offense count: 84
88
90
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
89
91
  Metrics/CyclomaticComplexity:
90
- Exclude:
91
- - 'lib/metanorma/document/components/lists/list_item.rb'
92
- - 'lib/metanorma/html/base_renderer.rb'
93
- - 'lib/metanorma/html/bipm_renderer.rb'
94
- - 'lib/metanorma/html/component/index_term_collector.rb'
95
- - 'lib/metanorma/html/drops/figure_drop.rb'
96
- - 'lib/metanorma/html/drops/formula_drop.rb'
97
- - 'lib/metanorma/html/generator.rb'
98
- - 'lib/metanorma/html/iso_renderer.rb'
99
- - 'lib/metanorma/html/ogc_renderer.rb'
100
- - 'lib/metanorma/html/standard_renderer.rb'
101
- - 'lib/metanorma/html/theme.rb'
102
- - 'lib/metanorma/iso_document/metadata/iso_localized_title.rb'
92
+ Enabled: false
103
93
 
104
- # Offense count: 101
94
+ # Offense count: 149
105
95
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
106
96
  Metrics/MethodLength:
107
- Max: 95
97
+ Max: 102
108
98
 
109
- # Offense count: 1
99
+ # Offense count: 3
110
100
  # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
111
101
  Metrics/ParameterLists:
112
102
  Max: 7
113
103
 
114
- # Offense count: 51
104
+ # Offense count: 68
115
105
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
116
106
  Metrics/PerceivedComplexity:
117
- Exclude:
118
- - 'lib/metanorma/document/components/lists/list_item.rb'
119
- - 'lib/metanorma/html/base_renderer.rb'
120
- - 'lib/metanorma/html/bipm_renderer.rb'
121
- - 'lib/metanorma/html/component/index_term_collector.rb'
122
- - 'lib/metanorma/html/drops/figure_drop.rb'
123
- - 'lib/metanorma/html/drops/formula_drop.rb'
124
- - 'lib/metanorma/html/generator.rb'
125
- - 'lib/metanorma/html/iso_renderer.rb'
126
- - 'lib/metanorma/html/ogc_renderer.rb'
127
- - 'lib/metanorma/html/standard_renderer.rb'
128
- - 'lib/metanorma/html/theme.rb'
129
- - 'lib/metanorma/iso_document/metadata/iso_localized_title.rb'
107
+ Enabled: false
130
108
 
131
- # Offense count: 28
109
+ # Offense count: 52
132
110
  # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
133
111
  # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
134
112
  Naming/MethodParameterName:
@@ -137,17 +115,21 @@ Naming/MethodParameterName:
137
115
  - 'lib/metanorma/html/component/footnote_collector.rb'
138
116
  - 'lib/metanorma/html/drops/sourcecode_drop.rb'
139
117
  - 'lib/metanorma/html/iso_renderer.rb'
118
+ - 'lib/metanorma/html/renderers/block_renderer.rb'
119
+ - 'lib/metanorma/html/renderers/inline_renderer.rb'
140
120
  - 'spec/metanorma/html/renderer/footnote_collector_spec.rb'
141
121
 
142
- # Offense count: 2
122
+ # Offense count: 4
143
123
  # Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates.
144
124
  # AllowedMethods: call
145
125
  # WaywardPredicates: infinite?, nonzero?
146
126
  Naming/PredicateMethod:
147
127
  Exclude:
148
128
  - 'lib/metanorma/html/base_renderer.rb'
129
+ - 'lib/metanorma/html/drops/biblio_entry_drop.rb'
130
+ - 'lib/metanorma/html/renderers/inline_renderer.rb'
149
131
 
150
- # Offense count: 1
132
+ # Offense count: 3
151
133
  # Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros, UseSorbetSigs.
152
134
  # NamePrefix: is_, has_, have_, does_
153
135
  # ForbiddenPrefixes: is_, has_, have_, does_
@@ -155,7 +137,9 @@ Naming/PredicateMethod:
155
137
  # MethodDefinitionMacros: define_method, define_singleton_method
156
138
  Naming/PredicatePrefix:
157
139
  Exclude:
158
- - 'lib/metanorma/html/standard_renderer.rb'
140
+ - 'spec/**/*'
141
+ - 'lib/metanorma/html/base_renderer.rb'
142
+ - 'lib/metanorma/html/drops/biblio_entry_drop.rb'
159
143
 
160
144
  # Offense count: 1
161
145
  Performance/MapMethodChain:
@@ -165,27 +149,42 @@ Performance/MapMethodChain:
165
149
  # Offense count: 7
166
150
  RSpec/BeforeAfterAll:
167
151
  Exclude:
152
+ - '**/spec/spec_helper.rb'
153
+ - '**/spec/rails_helper.rb'
154
+ - '**/spec/support/**/*.rb'
168
155
  - 'spec/metanorma/flavor_roots/flavor_roots_spec.rb'
169
156
 
170
- # Offense count: 18
157
+ # Offense count: 14
158
+ # Configuration parameters: Prefixes, AllowedPatterns.
159
+ # Prefixes: when, with, without
160
+ RSpec/ContextWording:
161
+ Exclude:
162
+ - 'spec/metanorma/html/renderer/theme_spec.rb'
163
+ - 'spec/metanorma/mirror/html_renderer_spec.rb'
164
+
165
+ # Offense count: 32
171
166
  # Configuration parameters: IgnoredMetadata.
172
167
  RSpec/DescribeClass:
173
168
  Enabled: false
174
169
 
175
- # Offense count: 95
170
+ # Offense count: 234
176
171
  # Configuration parameters: CountAsOne.
177
172
  RSpec/ExampleLength:
178
- Max: 29
173
+ Max: 37
179
174
 
180
- # Offense count: 1
175
+ # Offense count: 2
181
176
  RSpec/LeakyConstantDeclaration:
182
177
  Exclude:
183
178
  - 'spec/metanorma/html/renderer/class_ownership_spec.rb'
179
+ - 'spec/metanorma/html/renderer/liquid_templates_spec.rb'
184
180
 
185
- # Offense count: 2
181
+ # Offense count: 5
186
182
  RSpec/MultipleDescribes:
187
183
  Exclude:
188
184
  - 'spec/metanorma/document_spec.rb'
185
+ - 'spec/metanorma/html/flavor_registry_spec.rb'
186
+ - 'spec/metanorma/mirror/handlers/remaining_handlers_spec.rb'
187
+ - 'spec/metanorma/mirror/serialization_spec.rb'
189
188
  - 'spec/metanorma/standard_document/sections_spec.rb'
190
189
 
191
190
  # Offense count: 1
@@ -200,13 +199,13 @@ RSpec/NamedSubject:
200
199
  Exclude:
201
200
  - 'spec/metanorma/flavor_roots/flavor_roots_spec.rb'
202
201
 
203
- # Offense count: 599
202
+ # Offense count: 1076
204
203
  # Configuration parameters: AllowedPatterns.
205
204
  # AllowedPatterns: ^expect_, ^assert_
206
205
  RSpec/NoExpectationExample:
207
206
  Enabled: false
208
207
 
209
- # Offense count: 25
208
+ # Offense count: 29
210
209
  # Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata, InflectorPath, EnforcedInflector.
211
210
  # SupportedInflectors: default, active_support
212
211
  RSpec/SpecFilePathFormat:
@@ -216,14 +215,6 @@ RSpec/SpecFilePathFormat:
216
215
  # This cop supports safe autocorrection (--autocorrect).
217
216
  Style/ComparableClamp:
218
217
  Exclude:
219
- - 'lib/metanorma/html/base_renderer.rb'
220
218
  - 'lib/metanorma/html/iso_renderer.rb'
219
+ - 'lib/metanorma/html/renderers/section_renderer.rb'
221
220
  - 'lib/metanorma/html/standard_renderer.rb'
222
-
223
- # Offense count: 1
224
- # This cop supports unsafe autocorrection (--autocorrect-all).
225
- # Configuration parameters: EnforcedStyle.
226
- # SupportedStyles: literals, strict
227
- Style/MutableConstant:
228
- Exclude:
229
- - 'lib/metanorma/html/base_renderer.rb'
data/CLAUDE.md ADDED
@@ -0,0 +1,26 @@
1
+ # metanorma-document — Project Rules
2
+
3
+ ## ABSOLUTE RULE: NEVER use Nokogiri on document model XML
4
+
5
+ Document XML (presentation XML) is parsed into lutaml-model objects. All access to document content MUST go through the model object graph — typed attributes, `element_order`, `each_mixed_content`, etc.
6
+
7
+ NEVER:
8
+ - Call `to_xml` on a model object and then parse that XML with Nokogiri
9
+ - Use Nokogiri to strip, transform, or manipulate document XML content
10
+ - Use Nokogiri CSS selectors on document XML to find elements
11
+
12
+ The HTML renderer must use the model's typed attributes and the rendering pipeline (`render_paragraph`, `render_mixed_inline`, etc.) to produce HTML. Nokogiri is only acceptable for processing non-document strings (e.g., SVG logo files).
13
+
14
+ ## ABSOLUTE RULE: Use `each_mixed_content` for mixed content nodes
15
+
16
+ For any node with `mixed? == true`, you MUST use `each_mixed_content` to iterate children. This provides the full sequence of text nodes and elements in correct order.
17
+
18
+ Do NOT:
19
+ - Use only typed attributes (like `paragraphs`, `title`) for rendering — these only give mapped elements, not text nodes
20
+ - Use `element_order` for inline content — it only shows element children, not interleaved text
21
+
22
+ `render_ordered_content` uses `element_order` and is acceptable for block-level sections (which contain only element children like paragraphs and lists). But for paragraphs and inline content containers, `each_mixed_content` is mandatory.
23
+
24
+ ## ABSOLUTE RULE: No regex for HTML stripping
25
+
26
+ Never use regex (`/<[^>]+>/`) to strip HTML tags from rendered output. Text extraction must work directly on model objects via `extract_plain_text` (which walks `element_order` and typed attributes). If you need plain text from a model element, use `extract_plain_text(element)`, not `strip_html(render(element))`.
data/README.adoc CHANGED
@@ -210,6 +210,150 @@ This library targets the modern Metanorma XML format which uses `<metanorma>` as
210
210
 
211
211
  Legacy XML formats that use flavor-specific root elements (e.g. `<iso-standard>`, `<m3d-standard>`, `<csa-standard>`, `<un-standard>`) are *not supported*.
212
212
 
213
+ == Mirror Format
214
+
215
+ The `Metanorma::Mirror` module is a ProseMirror-style document model that captures the structural and inline content of a Metanorma document. It serves as an intermediate representation suitable for serialization, round-tripping, alternate rendering pipelines, and editor integrations.
216
+
217
+ The mirror format is fully model-driven: every node, mark, and document is an instance of a `Model` class. There are no hashes at the API boundary.
218
+
219
+ === Model Layer
220
+
221
+ [horizontal]
222
+ `Model::Container` :: A node with children (`#content`)
223
+ `Model::Leaf` :: A node without children (e.g. `image`, `sourcecode`)
224
+ `Model::Text` :: Inline text carrying a string and a list of `Mark`s
225
+ `Model::SoftBreak` :: A soft line break inside a paragraph
226
+ `Model::Mark` :: An inline decoration (`emphasis`, `strong`, `link`, `xref`, `eref`, `footnote`, `stem`, ...)
227
+ `Model::Guide` :: Output wrapper with `content`, `meta`, and `title`
228
+ `Model::Factory.from_h(hash)` :: Rebuilds a model graph from a serialized hash
229
+
230
+ Each model class encapsulates its attributes via bounded accessors. `Mark` exposes `[]`, `[]=`, `set_attr`, and `fetch` for safe attribute mutation. `#to_h` is the canonical serialization method.
231
+
232
+ === Forward Conversion (Metanorma → Mirror)
233
+
234
+ [source,ruby]
235
+ require "metanorma/document"
236
+ require "metanorma/mirror"
237
+
238
+ xml = File.read("spec/fixtures/iso/is/document-en.presentation.xml")
239
+ doc = Metanorma::IsoDocument::Root.from_xml(xml)
240
+
241
+ transformer = Metanorma::Mirror::Transformer.new
242
+ document = transformer.from_metanorma(doc)
243
+ document.class # => Metanorma::Mirror::Model::Container
244
+
245
+ === Reverse Conversion (Mirror → Mirror, with Rewriting)
246
+
247
+ The `Rewriter` walks a Model graph and produces a new Model graph, applying skip rules and optional per-type customization. (The historical name `MirrorToMetanorma` is no longer accurate: the output is a Model graph, not Metanorma XML. The class was renamed to `Rewriter` to reflect this.)
248
+
249
+ [source,ruby]
250
+ rewriter = Metanorma::Mirror::Rewriter.new
251
+ rebuilt = rewriter.call(document)
252
+
253
+ Each `Rewriter` instance seeds its skip set and builder map from class-level defaults at `new` time, so two instances never share state. Instance-level mutation does not leak back to the class, and class-level mutation does not retroactively affect existing instances.
254
+
255
+ [source,ruby]
256
+ # Instance API — only affects this instance
257
+ rewriter.skip("my_type")
258
+ rewriter.register("my_type") do |node, _rewriter|
259
+ Metanorma::Mirror::Model::Leaf.new(type: "customized")
260
+ end
261
+
262
+ # Class API — seeds defaults copied into future instances
263
+ Metanorma::Mirror::Rewriter.skip("always_skip")
264
+ Metanorma::Mirror::Rewriter.register("always_customize") { |n, _r| ... }
265
+
266
+ Model dispatch is polymorphic: each `Model::*` class implements `accept_rewriter(rewriter)`, which calls the matching `rewriter#rewrite_container` / `#rewrite_leaf` / `#rewrite_text` / `#rewrite_soft_break`. There is no `case/when` on node classes in the dispatch path.
267
+
268
+ === Default Handler Registry
269
+
270
+ `Metanorma::Mirror::DefaultRegistry.build` returns a fresh `HandlerRegistry` pre-populated with the standard Metanorma → Mirror mappings (paragraphs, blocks, lists, sections, terms, structural containers). It is the single source of truth for the default handler set — adding a new model-to-handler mapping is a single `register` call here, with no edits to `HandlerRegistry` itself.
271
+
272
+ [source,ruby]
273
+ Metanorma::Mirror.build_default_registry
274
+ # => #<Metanorma::Mirror::HandlerRegistry> (fresh instance each call)
275
+
276
+ === Metadata Service
277
+
278
+ `Metanorma::Mirror::Metadata` is a standalone service that extracts document metadata (title, etc.) from a parsed bibdata object. It is consumed by both the forward pipeline (`Output::Pipeline::AttachMetadata`) and the forward transformer (`Transformer#extract_root_title`), keeping title-extraction logic in one place rather than duplicated across the Handlers layer.
279
+
280
+ [source,ruby]
281
+ Metanorma::Mirror::Metadata.title_from_bibdata(doc.bibdata)
282
+ # => "First title string"
283
+
284
+ === Serialization
285
+
286
+ Mirror documents round-trip cleanly through JSON and YAML:
287
+
288
+ [source,ruby]
289
+ json = Metanorma::Mirror::Serialization::JsonSerializer.serialize(document)
290
+ restored = Metanorma::Mirror::Serialization::JsonSerializer.deserialize(json)
291
+
292
+ === ID Strategies
293
+
294
+ ID assignment is pluggable via `Metanorma::Mirror::IdStrategy`:
295
+
296
+ [horizontal]
297
+ `IdStrategy::Preserve` :: Default. Keeps element IDs as-is.
298
+ `IdStrategy::Positional` :: Rewrites UUID-style IDs to positional IDs (`sec-X.Y.Z`, `anx-X`, `fig-N`, `table-N`) and translates cross-reference targets.
299
+
300
+ The `Positional` strategy exposes `register_category(model_class, category)` so new flavors can declare their model classes without editing dispatch code.
301
+
302
+ === HTML Rendering from Mirror
303
+
304
+ `Metanorma::Mirror::Output::HtmlRenderer` produces SSR-ready HTML directly from a mirror Model graph. Renderer behavior is split into pluggable modules registered by node type (`clause`, `paragraph`, `bullet_list`, `table`, ...) and by mark type (`emphasis`, `link`, `xref`, ...). All dispatch is registry-driven (no `case/when` on types), so new node and mark types can be added without modifying core rendering code.
305
+
306
+ All HTML construction goes through `Nokogiri::HTML4::Builder` (or `Nokogiri::HTML5::Builder` for full documents). String concatenation of HTML is not permitted anywhere in the renderer layer. The `HtmlRenderers` module exposes shared helpers (`build`, `build_fragment`, `embed`, `wrap`, `escape_text`) so each renderer stays declarative.
307
+
308
+ Node handlers are stored as `UnboundMethod`s bound to the renderer instance at dispatch time. Register a new node handler by passing an instance method reference:
309
+
310
+ [source,ruby]
311
+ module MyRenderers
312
+ def self.register(registry)
313
+ registry.register_node_handler("callout",
314
+ instance_method(:render_callout))
315
+ end
316
+
317
+ def render_callout(node, depth: 0)
318
+ HtmlRenderers.build { |doc| doc.span(class: "callout") { doc.text node.attrs["text"] } }
319
+ end
320
+ end
321
+ MyRenderers.register(Metanorma::Mirror::Output::HtmlRenderer)
322
+
323
+ Mark handlers are stored as `Proc`s that receive the already-rendered inner HTML and the `Mark` instance:
324
+
325
+ [source,ruby]
326
+ Metanorma::Mirror::Output::HtmlRenderer.register_mark_handler("kbd",
327
+ ->(inner_html, _mark) { HtmlRenderers.wrap(:kbd, inner_html) })
328
+
329
+ === Output Pipeline
330
+
331
+ `Metanorma::Mirror::Output::Pipeline` runs the full conversion: parse XML → forward transform → attach metadata. `Metanorma::Mirror::Output::Builder` orchestrates the pipeline and writes through a pluggable `Formats` registry (default: `InlineFormat`).
332
+
333
+ [source,ruby]
334
+ builder = Metanorma::Mirror::Output::Builder.new(
335
+ xml_path: "document.presentation.xml",
336
+ output_path: "output.html",
337
+ format: :inline,
338
+ flavor: "iso",
339
+ id_strategy: Metanorma::Mirror::IdStrategy::Positional.new,
340
+ )
341
+ builder.build
342
+
343
+ New formats register themselves without editing lookup code:
344
+
345
+ [source,ruby]
346
+ Metanorma::Mirror::Output::Formats.register(:pdf, MyPdfFormat)
347
+
348
+ === CLI
349
+
350
+ [source,bash]
351
+ # Convert presentation XML to mirror JSON
352
+ metanorma-document to-mirror document.presentation.xml \
353
+ -o document.mirror.json \
354
+ --id-strategy positional \
355
+ --title "My Document"
356
+
213
357
  == Documentation
214
358
 
215
359
  Detailed architecture documentation is in the `docs/` directory:
data/Rakefile CHANGED
@@ -9,6 +9,18 @@ require "rubocop/rake_task"
9
9
 
10
10
  RuboCop::RakeTask.new
11
11
 
12
- task default: %i[spec rubocop]
12
+ # Load custom tasks
13
+ Dir.glob("lib/tasks/*.rake").each { |task| load task }
14
+
15
+ # Build the frontend SPA (frontend/dist/)
16
+ desc "Build frontend SPA assets"
17
+ task :build_frontend do
18
+ frontend_dir = File.join(__dir__, "frontend")
19
+ puts "Building frontend..."
20
+ system("cd #{frontend_dir} && npm install && npm run build") || raise("Frontend build failed")
21
+ end
13
22
 
14
- Dir[File.join(__dir__, "tasks", "*.rake")].each { |f| import f }
23
+ # Hook into bundler's release task to ensure frontend is built
24
+ Rake::Task["release"].enhance(["build_frontend"]) if Rake::Task.task_defined?("release")
25
+
26
+ task default: %i[spec rubocop]
@@ -0,0 +1,8 @@
1
+ # 01 — Split multi-class files into one-class-per-file — DONE
2
+
3
+ All model files now contain exactly one class per file. Verified: every file under `lib/metanorma/document/`, `lib/metanorma/standard_document/`, `lib/metanorma/iso_document/`, `lib/metanorma/bipm_document/`, `lib/metanorma/itu_document/`, `lib/metanorma/ietf_document/`, `lib/metanorma/cc_document/` is single-class.
4
+
5
+ Nested classes that are only used internally by their parent (e.g. `AnnotationContainer::Annotation`) are legitimate and NOT split.
6
+
7
+ ## Code quality requirements
8
+ Ensure code cleanliness and OOP and MECE and fully model-driven, semantically-driven and open/closed principle, DRY, performance. ultrathink. Always think about what can we improve here in architecture and code? Make sure we have good specs throughout. Never use private send methods (breaks encapsulation), instance_variable_set/get, and never use respond_to? (poor typing).
@@ -0,0 +1,6 @@
1
+ # 02 — Remove misplaced `data/dist/iso_document/frontend/` artifacts — DONE
2
+
3
+ `data/dist/iso_document/frontend/` directory no longer exists. Frontend build artifacts are correctly placed in `frontend/dist/` and included in gemspec via `Dir.glob("frontend/dist/*")`.
4
+
5
+ ## Code quality requirements
6
+ Ensure code cleanliness and OOP and MECE and fully model-driven, semantically-driven and open/closed principle, DRY, performance. ultrathink. Always think about what can we improve here in architecture and code? Make sure we have good specs throughout. Never use private send methods (breaks encapsulation), instance_variable_set/get, and never use respond_to? (poor typing).
@@ -0,0 +1,11 @@
1
+ # 03 — Align frontend IIFE build and GHA gem release with docbook pattern — DONE
2
+
3
+ Aligned with docbook project:
4
+ - `Rakefile` has `build_frontend` task + release hook
5
+ - `gemspec` includes `frontend/dist/*`
6
+ - `.github/workflows/rake.yml` has `after-setup-ruby: cd frontend && npm install && npm run build`
7
+ - `frontend/vite.config.ts` produces `app.iife.js` + `app.css`
8
+ - `.gitignore` has `frontend/node_modules/` and `frontend/dist/`
9
+
10
+ ## Code quality requirements
11
+ Ensure code cleanliness and OOP and MECE and fully model-driven, semantically-driven and open/closed principle, DRY, performance. ultrathink. Always think about what can we improve here in architecture and code? Make sure we have good specs throughout. Never use private send methods (breaks encapsulation), instance_variable_set/get, and never use respond_to? (poor typing).
@@ -0,0 +1,9 @@
1
+ # 04 — Data-centric theme system: config files per flavor — DONE
2
+
3
+ All 13 flavor themes extracted to `data/themes/{flavor}.yaml`. `Theme.load(:iso)` reads YAML and applies overrides. BaseRenderer provides default `Theme.new`. Every flavor renderer uses `Theme.load(:flavor_name)`.
4
+
5
+ ### Remaining enhancement
6
+ Publisher metadata (publishers, publisher_name, logo_map, font_url) still hardcoded in flavor renderers. These should move into theme YAML config so flavor renderers can be auto-generated from config alone.
7
+
8
+ ## Code quality requirements
9
+ Ensure code cleanliness and OOP and MECE and fully model-driven, semantically-driven and open/closed principle, DRY, performance. ultrathink. Always think about what can we improve here in architecture and code? Make sure we have good specs throughout. Never use private send methods (breaks encapsulation), instance_variable_set/get, and never use respond_to? (poor typing).
@@ -0,0 +1,16 @@
1
+ # 05 — Migrate all HTML generation to Liquid templates — DONE
2
+
3
+ All HTML element generation uses Liquid templates. Zero raw HTML string injection via `@output <<` remains. The `tag()` helper has been removed from `base_renderer.rb`. All remaining `@output <<` calls are text streaming (`escape_html`, whitespace, raw content passthrough) — not HTML element generation.
4
+
5
+ 32+ Liquid templates exist in `lib/metanorma/html/templates/`. The generic `_element.html.liquid` handles most tag wrapping. Specialized templates exist for tables, lists, figures, headings, math, links, etc.
6
+
7
+ ### OOP Liquid pattern
8
+ All rendering uses the `capture_output` + `render_liquid` pattern:
9
+ 1. Capture child content into a Ruby string via `capture_output`
10
+ 2. Pass captured content to `render_liquid("_template.html.liquid", assigns)`
11
+ 3. Result is appended to `@output` by the caller
12
+
13
+ Drop classes (`Drops::*`) are used for complex objects that need pre-rendered HTML fields (e.g. `BiblioEntryDrop`, `FigureDrop`).
14
+
15
+ ## Code quality requirements
16
+ Ensure code cleanliness and OOP and MECE and fully model-driven, semantically-driven and open/closed principle, DRY, performance. ultrathink. Always think about what can we improve here in architecture and code? Make sure we have good specs throughout. Never use private send methods (breaks encapsulation), instance_variable_set/get, and never use respond_to? (poor typing).
@@ -0,0 +1,16 @@
1
+ # 06 — Integrate pubid library for publication identifier handling — DONE
2
+
3
+ ## What was done
4
+ 1. Added `pubid ~> 2.0` to gemspec dependency
5
+ 2. Added `gem "pubid", path: "../../mn/pubid"` to Gemfile for local dev
6
+ 3. Created `lib/metanorma/html/concerns/pubid_rendering.rb` — `PubidRendering` module with:
7
+ - `FLAVOR_PUBID_MAP` mapping document class names to pubid modules
8
+ - `parse_pubid(docidentifier_string)` — flavor-aware pubid parsing
9
+ - `pubid_to_html(identifier)` — renders to semantic HTML with CSS classes
10
+ - `resolve_pubid_module` — auto-detects flavor from renderer class
11
+ 4. Wired into `BaseRenderer` via `include PubidRendering`
12
+ 5. Refactored `render_bibitem_content` in `standard_renderer.rb` to try pubid first
13
+ 6. Created specs in `pubid_rendering_spec.rb`
14
+
15
+ ## Code quality requirements
16
+ Ensure code cleanliness and OOP and MECE and fully model-driven, semantically-driven and open/closed principle, DRY, performance. ultrathink. Always think about what can we improve here in architecture and code? Make sure we have good specs throughout. Never use private send methods (breaks encapsulation), instance_variable_set/get, and never use respond_to? (poor typing).
@@ -0,0 +1,39 @@
1
+ # 07 — Integrate relaton-bib library (partially complete)
2
+
3
+ ## Status
4
+ - **Done**: relaton-bib dependency added, 4 models replaced via inheritance, `require` at module level
5
+ - **Blocked**: Most models have incompatible XML schemas (different attribute names/types, different XML mappings)
6
+
7
+ ## What was done
8
+ 1. Added `relaton-bib ~> 2.1` to gemspec dependency
9
+ 2. Added `require "relaton/bib"` to `lib/metanorma/document/relaton.rb`
10
+ 3. Replaced 4 models with inheritance from relaton-bib:
11
+ - `Phone` < `::Relaton::Bib::Phone` (exact match)
12
+ - `PriceType` < `::Relaton::Bib::Price` (exact match)
13
+ - `BibItemLocality` < `::Relaton::Bib::Locality` (exact match)
14
+ - `Edition` < `::Relaton::Bib::Edition` (inherits + adds `language` attribute)
15
+
16
+ ## Why most models can't be directly replaced
17
+ The 53 relaton models in our project have different XML schemas from relaton-bib's models:
18
+
19
+ | Difference | Our models | relaton-bib |
20
+ |---|---|---|
21
+ | Attribute names | `on` (BibliographicDate), `bib_locality` (LocalityStack), `validity_begins` (ValidityType) | `at`, `locality`, `begins` |
22
+ | Attribute types | `Components::DataTypes::*` throughout | `LocalizedString`, `LocalizedMarkedUpString` |
23
+ | XML mapping | `map_attribute` for medium, `map_all_content` for logo | `map_element` for medium, `map_element "image"` for logo |
24
+ | Extra attributes | `language` on Edition, `id` on DocumentIdentifier, `stage_abbreviation` on DocumentStatus | Not present |
25
+ | Mixed content | Used in TypedTitleString (em/fn/stem/strong/sub/sup/tt/variant) | Plain content |
26
+ | Model nesting | `CopyrightOwner` wraps `Organization` | `Contributor` as owner |
27
+
28
+ A full replacement requires updating all 86 consuming references and adapting to relaton-bib's API. This is a separate project-level migration.
29
+
30
+ ## Remaining work for full migration
31
+ 1. Update consuming code (86 references) to use relaton-bib attribute names
32
+ 2. For models with extra attributes: inherit from relaton-bib and add extras
33
+ 3. For models with structural differences: create adapter classes
34
+ 4. Remove `lib/metanorma/document/relaton/` once fully migrated
35
+ 5. Update autoload configuration
36
+ 6. Integration specs for relaton-bib model usage
37
+
38
+ ## Code quality requirements
39
+ Ensure code cleanliness and OOP and MECE and fully model-driven, semantically-driven and open/closed principle, DRY, performance. ultrathink. Always think about what can we improve here in architecture and code? Make sure we have good specs throughout. Never use private send methods (breaks encapsulation), instance_variable_set/get, and never use respond_to? (poor typing).
@@ -0,0 +1,25 @@
1
+ # 08 — Refactor renderer architecture for OCP and DRY — DONE
2
+
3
+ ### Completed decomposition (Phase 1)
4
+ BaseRenderer originally included three concern modules:
5
+ - `InlineRendering` (~590 lines, 39 methods)
6
+ - `BlockRendering` (~295 lines, 24 methods)
7
+ - `SectionRendering` (~90 lines, 8 methods)
8
+
9
+ ### Completed composition (Phase 2 — TODO 13)
10
+ Module includes replaced with composition. Each module became a standalone class:
11
+ - `Renderers::InlineRenderer` at `lib/metanorma/html/renderers/inline_renderer.rb`
12
+ - `Renderers::BlockRenderer` at `lib/metanorma/html/renderers/block_renderer.rb`
13
+ - `Renderers::SectionRenderer` at `lib/metanorma/html/renderers/section_renderer.rb`
14
+ - `Renderers::PubidRenderer` at `lib/metanorma/html/renderers/pubid_renderer.rb`
15
+
16
+ BaseRenderer is now a thin coordinator with delegation wrappers for backward compatibility.
17
+ Old concern modules at `lib/metanorma/html/concerns/` are no longer referenced and can be removed.
18
+
19
+ ### Remaining enhancements
20
+ - Publisher metadata (flavor_publishers, flavor_publisher_name, publisher_logo_map) duplicated across 12+ flavor renderers. See TODO 11 for config-driven migration.
21
+ - Config-driven section ordering. See TODO 14.
22
+ - Theme directories per flavor. See TODO 15.
23
+
24
+ ## Code quality requirements
25
+ Ensure code cleanliness and OOP and MECE and fully model-driven, semantically-driven and open/closed principle, DRY, performance. ultrathink. Always think about what can we improve here in architecture and code? Make sure we have good specs throughout. Never use private send methods (breaks encapsulation), instance_variable_set/get, and never use respond_to? (poor typing).