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
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Mirror
5
+ # Single source of truth for the default Metanorma → Mirror handler
6
+ # registrations. `Mirror.default_registry` memoizes a frozen instance
7
+ # built from here; `Mirror.build_default_registry` returns a fresh
8
+ # instance each call.
9
+ #
10
+ # Adding a new model class to handler mapping = adding one
11
+ # `register_default` call here. No edits to HandlerRegistry (OCP).
12
+ module DefaultRegistry
13
+ class << self
14
+ def build
15
+ registry = HandlerRegistry.new
16
+ register_default(registry)
17
+ registry
18
+ end
19
+
20
+ private
21
+
22
+ def register_default(registry)
23
+ register_paragraphs(registry)
24
+ register_blocks(registry)
25
+ register_lists(registry)
26
+ register_sections(registry)
27
+ register_terms(registry)
28
+ register_structural(registry)
29
+ end
30
+
31
+ def register_paragraphs(registry)
32
+ registry.register(
33
+ Metanorma::Document::Components::Paragraphs::ParagraphBlock,
34
+ Handlers::Paragraph,
35
+ )
36
+ end
37
+
38
+ def register_blocks(registry)
39
+ registry.register(
40
+ Metanorma::Document::Components::Blocks::NoteBlock,
41
+ Handlers::Note,
42
+ )
43
+ registry.register(
44
+ Metanorma::Document::Components::MultiParagraph::AdmonitionBlock,
45
+ Handlers::Admonition,
46
+ )
47
+ registry.register(
48
+ Metanorma::Document::Components::AncillaryBlocks::ExampleBlock,
49
+ Handlers::Example,
50
+ )
51
+ registry.register(
52
+ Metanorma::Document::Components::AncillaryBlocks::FigureBlock,
53
+ Handlers::Figure,
54
+ )
55
+ registry.register(
56
+ Metanorma::Document::Components::AncillaryBlocks::SourcecodeBlock,
57
+ Handlers::Sourcecode,
58
+ )
59
+ registry.register(
60
+ Metanorma::Document::Components::AncillaryBlocks::FormulaBlock,
61
+ Handlers::Formula,
62
+ )
63
+ registry.register(
64
+ Metanorma::Document::Components::MultiParagraph::QuoteBlock,
65
+ Handlers::Quote,
66
+ )
67
+ registry.register(
68
+ Metanorma::Document::Components::Tables::TableBlock,
69
+ Handlers::Table,
70
+ )
71
+ registry.register(
72
+ Metanorma::Document::Components::MultiParagraph::ReviewBlock,
73
+ Handlers::Review,
74
+ )
75
+ end
76
+
77
+ def register_lists(registry)
78
+ registry.register(
79
+ Metanorma::Document::Components::Lists::UnorderedList,
80
+ Handlers::List,
81
+ method_name: :bullet,
82
+ )
83
+ registry.register(
84
+ Metanorma::Document::Components::Lists::OrderedList,
85
+ Handlers::List,
86
+ method_name: :ordered,
87
+ )
88
+ registry.register(
89
+ Metanorma::Document::Components::Lists::DefinitionList,
90
+ Handlers::List,
91
+ method_name: :definition,
92
+ )
93
+ registry.register(
94
+ Metanorma::Document::Components::Lists::ListItem,
95
+ Handlers::List,
96
+ method_name: :list_item,
97
+ )
98
+ end
99
+
100
+ def register_sections(registry)
101
+ registry.register(
102
+ Metanorma::StandardDocument::Sections::ClauseSection,
103
+ Handlers::Section,
104
+ method_name: :clause,
105
+ )
106
+ registry.register(
107
+ Metanorma::StandardDocument::Sections::AnnexSection,
108
+ Handlers::Section,
109
+ method_name: :annex,
110
+ )
111
+ registry.register(
112
+ Metanorma::StandardDocument::Sections::ContentSection,
113
+ Handlers::Section,
114
+ method_name: :content_section,
115
+ )
116
+ registry.register(
117
+ Metanorma::StandardDocument::Sections::TermsSection,
118
+ Handlers::Section,
119
+ method_name: :terms,
120
+ )
121
+ registry.register(
122
+ Metanorma::IsoDocument::Sections::IsoTermsSection,
123
+ Handlers::Section,
124
+ method_name: :terms,
125
+ )
126
+ registry.register(
127
+ Metanorma::StandardDocument::Sections::DefinitionSection,
128
+ Handlers::Section,
129
+ method_name: :definitions,
130
+ )
131
+ registry.register(
132
+ Metanorma::StandardDocument::Sections::StandardReferencesSection,
133
+ Handlers::Section,
134
+ method_name: :references,
135
+ )
136
+ registry.register(
137
+ Metanorma::StandardDocument::Sections::FloatingTitle,
138
+ Handlers::Section,
139
+ method_name: :floating_title,
140
+ )
141
+ end
142
+
143
+ def register_terms(registry)
144
+ registry.register(
145
+ Metanorma::IsoDocument::Terms::IsoTerm,
146
+ Handlers::Term,
147
+ )
148
+ end
149
+
150
+ def register_structural(registry)
151
+ registry.register(
152
+ Metanorma::StandardDocument::Sections::Preface,
153
+ Handlers::Structural,
154
+ method_name: :preface,
155
+ )
156
+ registry.register(
157
+ Metanorma::StandardDocument::Sections::Sections,
158
+ Handlers::Structural,
159
+ method_name: :sections,
160
+ )
161
+ registry.register(
162
+ Metanorma::StandardDocument::Sections::BibliographySection,
163
+ Handlers::Structural,
164
+ method_name: :bibliography,
165
+ )
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Mirror
5
+ class HandlerRegistry
6
+ Entry = Struct.new(:callable, :concat, :extra_kwargs, keyword_init: true)
7
+
8
+ def initialize
9
+ @handlers = {}
10
+ end
11
+
12
+ def register(model_class, handler, method_name: :call, concat: false,
13
+ extra_kwargs: {})
14
+ callable = resolve_callable(handler, method_name)
15
+ @handlers[model_class] = Entry.new(
16
+ callable: callable,
17
+ concat: concat,
18
+ extra_kwargs: extra_kwargs,
19
+ )
20
+ end
21
+
22
+ def registered?(model_class)
23
+ @handlers.key?(model_class)
24
+ end
25
+
26
+ def entry_for(model_element)
27
+ @handlers[model_element.class] || ancestor_entry(model_element)
28
+ end
29
+
30
+ def handle(model_element, context:)
31
+ entry = entry_for(model_element)
32
+ return HandlerResult.none unless entry
33
+
34
+ kwargs = { context: context }.merge(entry.extra_kwargs || {})
35
+ result = entry.callable.call(model_element, **kwargs)
36
+ HandlerResult.new(result, concat: entry.concat)
37
+ end
38
+
39
+ private
40
+
41
+ def resolve_callable(handler, method_name)
42
+ return handler if handler.is_a?(Proc)
43
+ return handler.method(:call) if method_name == :call
44
+
45
+ handler.method(method_name)
46
+ end
47
+
48
+ def ancestor_entry(model_element)
49
+ model_element.class.ancestors.each do |ancestor|
50
+ next if ancestor == model_element.class
51
+ break if ancestor == Object
52
+
53
+ entry = @handlers[ancestor]
54
+ return entry if entry
55
+ end
56
+ nil
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Mirror
5
+ class HandlerResult
6
+ attr_reader :nodes
7
+
8
+ def self.none
9
+ new(nil, concat: false)
10
+ end
11
+
12
+ def initialize(nodes, concat: false)
13
+ @nodes = nodes
14
+ @concat = concat
15
+ end
16
+
17
+ def none?
18
+ @nodes.nil?
19
+ end
20
+
21
+ def concat?
22
+ @concat
23
+ end
24
+
25
+ def append_to(content)
26
+ return content if none?
27
+
28
+ if concat?
29
+ content.concat(Array(@nodes))
30
+ else
31
+ content << @nodes
32
+ end
33
+ content
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Mirror
5
+ module Handlers
6
+ module Admonition
7
+ EXTRA = { type: nil, target: nil, unnumbered: nil }.freeze
8
+
9
+ def self.call(element, context:)
10
+ attrs = Handlers.extract_attrs(element, extra_attrs: EXTRA)
11
+ content = context.extract_blocks(element)
12
+
13
+ Handlers.build_node("admonition", attrs: attrs, content: content)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Mirror
5
+ module Handlers
6
+ module Example
7
+ EXTRA = { unnumbered: nil, subsequence: nil }.freeze
8
+
9
+ def self.call(element, context:)
10
+ attrs = Handlers.extract_attrs(element, extra_attrs: EXTRA)
11
+ content = context.extract_named_collections(element,
12
+ %i[paragraphs formula
13
+ ul ol quote sourcecode
14
+ table figure dl])
15
+
16
+ Handlers.build_node("example", attrs: attrs, content: content)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Mirror
5
+ module Handlers
6
+ module Figure
7
+ def self.call(element, context:)
8
+ attrs = figure_attrs(element)
9
+ content = []
10
+
11
+ img = SafeAttr.read(element, :image)
12
+ if img
13
+ img_attrs = {}
14
+ src = SafeAttr.read(img, :source)
15
+ img_attrs[:src] =
16
+ src && !src.strip.empty? ? src : SafeAttr.read(img, :filename)
17
+ img_attrs[:alt] = SafeAttr.read(img, :alt)
18
+ img_attrs[:height] = SafeAttr.read(img, :height)
19
+ img_attrs[:width] = SafeAttr.read(img, :width)
20
+ content << Handlers.build_node("image", attrs: img_attrs.compact)
21
+ end
22
+
23
+ subfigures = SafeAttr.read(element, :figure)
24
+ subfigures&.each do |sub|
25
+ result = context.registry.handle(sub, context: context)
26
+ result.append_to(content)
27
+ end
28
+
29
+ notes = SafeAttr.read(element, :note)
30
+ notes&.each do |n|
31
+ result = context.registry.handle(n, context: context)
32
+ result.append_to(content)
33
+ end
34
+
35
+ Handlers.build_node("figure", attrs: attrs, content: content)
36
+ end
37
+
38
+ def self.figure_attrs(element)
39
+ attrs = {}
40
+ attrs[:id] = SafeAttr.read(element, :id)
41
+ attrs[:unnumbered] = SafeAttr.read(element, :unnumbered)
42
+ attrs[:width] = SafeAttr.read(element, :width)
43
+ attrs[:align] = SafeAttr.read(element, :align)
44
+ attrs[:semx_id] = SafeAttr.read(element, :semx_id)
45
+
46
+ name = SafeAttr.read(element, :name)
47
+ attrs[:title] = Handlers.extract_name_text(name) if name
48
+ attrs.compact
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Mirror
5
+ module Handlers
6
+ module Formula
7
+ EXTRA = { unnumbered: nil, inequality: nil }.freeze
8
+
9
+ def self.call(element, context:)
10
+ attrs = Handlers.extract_attrs(element, extra_attrs: EXTRA)
11
+
12
+ stem = SafeAttr.read(element, :stem)
13
+ if stem
14
+ attrs[:stem_type] = SafeAttr.read(stem, :stem_type)
15
+
16
+ asciimath = SafeAttr.read(stem, :asciimath)
17
+ if asciimath&.value && !asciimath.value.strip.empty?
18
+ attrs[:asciimath] =
19
+ asciimath.value
20
+ end
21
+
22
+ math = SafeAttr.read(stem, :math)
23
+ attrs[:mathml] = MathUtil.mathml_from_math(math) if math
24
+ end
25
+
26
+ # Legacy fallback: some older documents store text directly
27
+ text = SafeAttr.read(element, :text)
28
+ attrs[:math_text] = Array(text).join if text && !attrs[:asciimath]
29
+
30
+ Handlers.build_node("formula", attrs: attrs.compact)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Mirror
5
+ module Handlers
6
+ module Inline
7
+ # Shared declarative table of inline marks whose HTML rendering is
8
+ # a fixed tag with optional static attrs. Two adapters consume it:
9
+ #
10
+ # - RichHtmlRenderer: renders Metanorma XML elements to HTML
11
+ # directly (the fallback / attribute-value path).
12
+ # - Output::HtmlRenderers::MarkRenderers: renders mirror Model marks
13
+ # to HTML (the primary SSR path).
14
+ #
15
+ # Both adapters must produce visually equivalent HTML for the same
16
+ # mark type — the table is the single source of truth for the
17
+ # mark-name to tag-and-attrs mapping. Marks whose attrs vary per
18
+ # instance (link, xref, eref, span) carry data in mark.attrs and
19
+ # are not in this table; each adapter keeps custom handlers for
20
+ # those.
21
+ module Catalog
22
+ SIMPLE_WRAPS = {
23
+ "emphasis" => { tag: :em },
24
+ "strong" => { tag: :strong },
25
+ "subscript" => { tag: :sub },
26
+ "superscript" => { tag: :sup },
27
+ "code" => { tag: :code },
28
+ "underline" => { tag: :u },
29
+ "strike" => { tag: :s },
30
+ "smallcap" => { tag: :span, style: "font-variant: small-caps" },
31
+ "concept" => { tag: :span, class: "concept" },
32
+ "bcp14" => { tag: :span, class: "bcp14" },
33
+ "footnote" => { tag: :sup, class: "footnote-inline" },
34
+ "stem" => { tag: :span, class: "stem" },
35
+ }.freeze
36
+
37
+ # XML element classes that render via SIMPLE_WRAPS without
38
+ # per-instance attrs. Maps element class → mark type (catalog
39
+ # key). RichHtmlRenderer uses this to dispatch simple elements
40
+ # through the shared table.
41
+ SIMPLE_ELEMENTS = {
42
+ Metanorma::Document::Components::Inline::EmRawElement => "emphasis",
43
+ Metanorma::Document::Components::Inline::StrongRawElement => "strong",
44
+ Metanorma::Document::Components::Inline::SubElement => "subscript",
45
+ Metanorma::Document::Components::Inline::SupElement => "superscript",
46
+ Metanorma::Document::Components::Inline::TtElement => "code",
47
+ Metanorma::Document::Components::TextElements::UnderlineElement => "underline",
48
+ Metanorma::Document::Components::TextElements::StrikeElement => "strike",
49
+ Metanorma::Document::Components::Inline::SmallCapElement => "smallcap",
50
+ Metanorma::Document::Components::Inline::Bcp14Element => "bcp14",
51
+ Metanorma::Document::Components::Inline::ConceptElement => "concept",
52
+ }.freeze
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module Metanorma
6
+ module Mirror
7
+ module Handlers
8
+ module Inline
9
+ # Renders inline Metanorma elements as HTML strings. Used when an HTML
10
+ # representation of inline content is required (e.g., attribute values,
11
+ # fallback rendering). All HTML construction goes through
12
+ # Nokogiri::HTML5::Builder to guarantee well-formed, escaped output.
13
+ #
14
+ # Simple-wrap elements (em, strong, sub, sup, code, u, s, smallcap,
15
+ # bcp14, concept) share their tag-and-attrs mapping with the Model-side
16
+ # renderer via Inline::Catalog, so the XML→HTML and Model→HTML paths
17
+ # produce visually equivalent output for the same mark type.
18
+ module RichHtmlRenderer
19
+ # Complex element handlers — these have per-instance attrs or
20
+ # multi-step rendering and bypass the shared Catalog.
21
+ COMPLEX_RENDERERS = {
22
+ Metanorma::Document::Components::Inline::StemInlineElement => ->(el) {
23
+ for_stem(el)
24
+ },
25
+ Metanorma::Document::Components::TextElements::StemElement => ->(el) {
26
+ for_stem(el)
27
+ },
28
+ Metanorma::Document::Components::Inline::XrefElement => ->(el) {
29
+ for_xref(el)
30
+ },
31
+ Metanorma::Document::Components::Inline::LinkElement => ->(el) {
32
+ for_link(el)
33
+ },
34
+ Metanorma::Document::Components::Inline::ErefElement => ->(el) {
35
+ for_eref(el)
36
+ },
37
+ Metanorma::Document::Components::Inline::FnElement => ->(el) {
38
+ for_fn(el)
39
+ },
40
+ Metanorma::Document::Components::Inline::SpanElement => ->(el) {
41
+ for_span(el)
42
+ },
43
+ Metanorma::Document::Components::Inline::BrElement => ->(_el) {
44
+ wrap(&:br)
45
+ },
46
+ }.freeze
47
+
48
+ def self.extract(element)
49
+ return "" unless element
50
+
51
+ unless element.is_a?(Lutaml::Model::Serializable)
52
+ return CGI.escapeHTML(element.to_s)
53
+ end
54
+
55
+ parts = []
56
+ element.each_mixed_content { |node| parts << render_node(node) }
57
+ result = parts.join.strip
58
+ result.empty? ? TextExtractor.extract_element_text(element) : result
59
+ end
60
+
61
+ def self.render_node(node)
62
+ case node
63
+ when String
64
+ CGI.escapeHTML(node)
65
+ when Lutaml::Model::Serializable
66
+ render_element(node)
67
+ else
68
+ ""
69
+ end
70
+ end
71
+
72
+ def self.render_element(element)
73
+ complex = COMPLEX_RENDERERS[element.class]
74
+ return complex.call(element) if complex
75
+
76
+ mark_type = Catalog::SIMPLE_ELEMENTS[element.class]
77
+ return render_simple(mark_type, element) if mark_type
78
+
79
+ extract(element)
80
+ end
81
+
82
+ def self.render_simple(mark_type, element)
83
+ spec = Catalog::SIMPLE_WRAPS[mark_type]
84
+ attrs = spec.except(:tag)
85
+ wrap do |d|
86
+ if attrs.empty?
87
+ d.public_send(spec[:tag]) { d << raw(extract(element)) }
88
+ else
89
+ d.public_send(spec[:tag], attrs) { d << raw(extract(element)) }
90
+ end
91
+ end
92
+ end
93
+
94
+ def self.for_stem(element)
95
+ math = SafeAttr.read(element, :math)
96
+ return TextExtractor.extract_element_text(element) unless math
97
+
98
+ mathml = MathUtil.mathml_from_math(math)
99
+ .sub(/\s*xmlns(:\w+)?="[^"]*"\s*/, " ")
100
+ .gsub(/\s+/, " ")
101
+ .strip
102
+ spec = Catalog::SIMPLE_WRAPS["stem"]
103
+ wrap do |d|
104
+ d.public_send(spec[:tag], spec.except(:tag)) { d << raw(mathml) }
105
+ end
106
+ end
107
+
108
+ def self.for_xref(element)
109
+ target = SafeAttr.read(element, :target) || ""
110
+ label = TextExtractor.extract_formatted_text(element)
111
+ label = target if label.strip.empty?
112
+ wrap { |d| d.a(href: "##{target}") { d.text label } }
113
+ end
114
+
115
+ def self.for_link(element)
116
+ href = SafeAttr.read(element,
117
+ :target) || SafeAttr.read(element, :href) || ""
118
+ label = TextExtractor.extract_formatted_text(element)
119
+ wrap { |d| d.a(href: href) { d.text label } }
120
+ end
121
+
122
+ def self.for_eref(element)
123
+ citeas = SafeAttr.read(element, :citeas) || ""
124
+ label = TextExtractor.extract_formatted_text(element)
125
+ wrap { |d| d.a(class: "eref", cite: citeas) { d.text label } }
126
+ end
127
+
128
+ def self.for_fn(element)
129
+ reference = SafeAttr.read(element, :reference) || ""
130
+ spec = Catalog::SIMPLE_WRAPS["footnote"]
131
+ wrap do |d|
132
+ d.public_send(spec[:tag], spec.except(:tag)) { d.text reference }
133
+ end
134
+ end
135
+
136
+ def self.for_span(element)
137
+ cls = SafeAttr.read(element, :class_attr)
138
+ inner = extract(element)
139
+ wrap do |d|
140
+ if cls
141
+ d.span(class: cls) { d << raw(inner) }
142
+ else
143
+ d.span { d << raw(inner) }
144
+ end
145
+ end
146
+ end
147
+
148
+ def self.wrap(&)
149
+ Nokogiri::HTML5::Builder.new(&).doc.root.to_html
150
+ end
151
+
152
+ def self.raw(html_string)
153
+ Nokogiri::HTML5::DocumentFragment.parse(html_string.to_s)
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end