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,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Html
5
+ module Renderers
6
+ # Builds the reverse lookup `{ xml_element_name => attribute_name }`
7
+ # from a node's xml mapping. Shared between walkers that iterate
8
+ # element_order: InlineRenderer#walk_ordered (renders in document
9
+ # order) and BaseRenderer#extract_plain_text (extracts text).
10
+ #
11
+ # Both walkers previously reimplemented this hash construction
12
+ # inline; the construction is the genuinely-shared traversal
13
+ # concern. Per-element handling (recursion, span emission, tab/br
14
+ # spacing) differs and stays in each walker.
15
+ module ElementOrderTraversal
16
+ module_function
17
+
18
+ # Returns `{ xml_element_name => attr_name }` including both
19
+ # symbol and string keys for each mapped element, so callers
20
+ # can look up by either el.name form.
21
+ def element_to_attr_map(xml_mapping)
22
+ {}.tap do |map|
23
+ xml_mapping.mapping_elements_hash.each_value do |rule_or_array|
24
+ Array(rule_or_array).each do |rule|
25
+ map[rule.name] = rule.to
26
+ map[rule.name.to_s] = rule.to if rule.name.is_a?(Symbol)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,642 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Html
5
+ module Renderers
6
+ class InlineRenderer
7
+ def initialize(coordinator)
8
+ @coordinator = coordinator
9
+ end
10
+
11
+ def walk_ordered(node, allow_filter: nil)
12
+ return false unless node.is_a?(Lutaml::Model::Serializable)
13
+ return false unless node.element_order.is_a?(Array) && !node.element_order.empty?
14
+
15
+ xml_mapping = node.class.mappings_for(:xml, node.lutaml_register)
16
+ return false unless xml_mapping
17
+
18
+ element_to_attr =
19
+ Renderers::ElementOrderTraversal.element_to_attr_map(xml_mapping)
20
+
21
+ skip_indices = build_semx_skip_set(node)
22
+
23
+ indices = Hash.new(0)
24
+
25
+ node.element_order.each_with_index do |el, i|
26
+ next if skip_indices.include?(i)
27
+
28
+ if el.text?
29
+ text = el.text_content
30
+ yield :text, text if text && block_given?
31
+ elsif el.element?
32
+ if el.name == "tab"
33
+ yield :tab, nil if block_given?
34
+ next
35
+ end
36
+
37
+ attr_name = element_to_attr[el.name]
38
+ next unless attr_name
39
+
40
+ next if allow_filter && !allow_filter.include?(attr_name)
41
+
42
+ coll = node.public_send(attr_name)
43
+ obj = if coll.is_a?(Array)
44
+ idx = indices[attr_name]
45
+ indices[attr_name] += 1
46
+ coll[idx]
47
+ else
48
+ coll
49
+ end
50
+ yield :element, obj if obj && block_given?
51
+ end
52
+ end
53
+ true
54
+ end
55
+
56
+ def build_semx_skip_set(node)
57
+ skip_after = {
58
+ "link" => "semx",
59
+ "xref" => "semx",
60
+ "eref" => "semx",
61
+ "stem" => nil,
62
+ "concept" => "fmt-concept",
63
+ "refterm" => nil,
64
+ "renderterm" => nil,
65
+ "origin" => "semx",
66
+ }
67
+ skip = {}
68
+ node.element_order.each_with_index do |el, i|
69
+ next unless el.element?
70
+
71
+ next_tag = skip_after[el.name]
72
+ next unless next_tag
73
+
74
+ next_el = node.element_order[i + 1]
75
+ if next_tag.nil?
76
+ skip[i] = true
77
+ elsif next_el&.element? && next_el.name == next_tag
78
+ skip[i] = true
79
+ end
80
+ end
81
+ skip
82
+ end
83
+
84
+ def render_cell_content(cell)
85
+ parts = []
86
+ walked = walk_ordered(cell) do |type, obj|
87
+ case type
88
+ when :text
89
+ parts << escape_html(obj)
90
+ when :tab
91
+ parts << "  "
92
+ when :element
93
+ parts << if coordinator.block_element?(obj)
94
+ coordinator.render(obj) || ""
95
+ else
96
+ render_inline_element(obj) || ""
97
+ end
98
+ end
99
+ end
100
+ unless walked
101
+ parts << (render_mixed_content_in_order(cell) || "")
102
+ end
103
+ parts.join
104
+ end
105
+
106
+ def render_mixed_inline(node)
107
+ if raw_content_node?(node)
108
+ raw = node.content
109
+ if raw.is_a?(String) && !raw.strip.empty?
110
+ return render_raw_content(raw)
111
+ end
112
+ end
113
+
114
+ if node.is_a?(Lutaml::Model::Serializable) && node.element_order && !node.element_order.empty?
115
+ render_ordered_inline(node)
116
+ elsif node.is_a?(Lutaml::Model::Serializable)
117
+ parts = []
118
+ node.each_mixed_content do |child|
119
+ parts << case child
120
+ when String
121
+ escape_html(child)
122
+ else
123
+ render_inline_element(child) || ""
124
+ end
125
+ end
126
+ parts.join
127
+ else
128
+ render_inline_collections(node)
129
+ end
130
+ end
131
+
132
+ def raw_content_node?(node)
133
+ node.is_a?(Lutaml::Model::Serializable) &&
134
+ node.content.is_a?(String)
135
+ rescue NoMethodError
136
+ false
137
+ end
138
+
139
+ def render_ordered_inline(node)
140
+ parts = []
141
+ walked = walk_ordered(node) do |type, obj|
142
+ case type
143
+ when :text
144
+ parts << escape_html(obj)
145
+ when :tab
146
+ parts << "  "
147
+ when :element
148
+ parts << (render_inline_element(obj) || "")
149
+ end
150
+ end
151
+ unless walked
152
+ node.each_mixed_content do |child|
153
+ parts << case child
154
+ when String
155
+ escape_html(child)
156
+ else
157
+ render_inline_element(child) || ""
158
+ end
159
+ end
160
+ end
161
+ parts.join
162
+ end
163
+
164
+ def render_em(el)
165
+ render_inline_tag("em", el)
166
+ end
167
+
168
+ def render_strong(el)
169
+ render_inline_tag("strong", el)
170
+ end
171
+
172
+ def render_tt(el)
173
+ render_inline_tag("tt", el)
174
+ end
175
+
176
+ def render_sub(el)
177
+ render_inline_tag("sub", el)
178
+ end
179
+
180
+ def render_sup(el)
181
+ render_inline_tag("sup", el)
182
+ end
183
+
184
+ def render_small_caps(el)
185
+ render_inline_tag("span", el, class: "small-caps")
186
+ end
187
+
188
+ def render_underline(el)
189
+ render_inline_tag("u", el)
190
+ end
191
+
192
+ def render_strike(el)
193
+ render_inline_tag("s", el)
194
+ end
195
+
196
+ def render_br(*)
197
+ render_liquid("_br.html.liquid", {})
198
+ end
199
+
200
+ def render_tab(*)
201
+ "  "
202
+ end
203
+
204
+ def render_span(el)
205
+ xml_class = safe_attr(el, :class_attr).to_s
206
+ html_class = html_class_for_span(xml_class) unless xml_class.empty?
207
+ attrs = element_attrs(style: safe_attr(el, :style), class: html_class)
208
+ content = render_mixed_inline(el)
209
+ render_liquid("_inline_span.html.liquid", {
210
+ "attrs" => attrs,
211
+ "content" => content,
212
+ })
213
+ end
214
+
215
+ def render_fn_inline(el)
216
+ render_fn(el)
217
+ end
218
+
219
+ def render_stem(_el)
220
+ nil
221
+ end
222
+
223
+ def render_semx_inline(el)
224
+ render_semx_content(el)
225
+ end
226
+
227
+ def render_fmt_xref(el)
228
+ target = safe_attr(el, :target) || safe_attr(el, :to_attr)
229
+ if target
230
+ attrs = element_attrs(href: "##{escape_html(target)}",
231
+ class: "xref")
232
+ content = render_mixed_inline(el)
233
+ render_liquid("_link.html.liquid", {
234
+ "attrs" => attrs,
235
+ "content" => content,
236
+ })
237
+ else
238
+ render_mixed_inline(el)
239
+ end
240
+ end
241
+
242
+ def render_comma(*)
243
+ ", "
244
+ end
245
+
246
+ def render_math(el)
247
+ el.content.to_s
248
+ end
249
+
250
+ def render_asciimath(el)
251
+ text = escape_html(Array(el.text).join)
252
+ render_liquid("_stem_span.html.liquid", {
253
+ "data_attrs" => "",
254
+ "text" => text,
255
+ })
256
+ end
257
+
258
+ def render_index(el)
259
+ collect_index_term(el)
260
+ nil
261
+ end
262
+
263
+ def render_note_inline(el)
264
+ coordinator.render_note(el)
265
+ end
266
+
267
+ def render_bookmark(bookmark, **)
268
+ coordinator.render_bookmark(bookmark, **)
269
+ end
270
+
271
+ def render_image(image)
272
+ coordinator.render_image(image)
273
+ end
274
+
275
+ def render_noop_inline(*)
276
+ nil
277
+ end
278
+
279
+ def render_inline_collections(node)
280
+ parts = []
281
+ texts = node.text
282
+ if texts.is_a?(Array)
283
+ texts.each do |t|
284
+ parts << if t.is_a?(String)
285
+ escape_html(t)
286
+ else
287
+ render_inline_element(t) || ""
288
+ end
289
+ end
290
+ elsif texts.is_a?(String)
291
+ parts << escape_html(texts)
292
+ end
293
+
294
+ # Derive the element-mapped attribute list from the node's own
295
+ # xml_mapping rather than maintaining a parallel hardcoded list.
296
+ # The model is the single source of truth; adding a new inline
297
+ # element type via map_element on the model class is enough.
298
+ node.class.mappings[:xml].elements.each do |rule|
299
+ next if rule.to == :text
300
+
301
+ values = safe_attr(node, rule.to)
302
+ next if values.nil?
303
+
304
+ Array(values).each { |v| parts << (render_inline_element(v) || "") }
305
+ end
306
+ parts.join
307
+ end
308
+
309
+ def render_semx_content(element, **_opts)
310
+ parts = []
311
+ display_attrs = %i[text fmt_xref fmt_link fmt_concept span strong em sup p semx
312
+ asciimath math sub_child tt_child br_child tab_child
313
+ stem_child figure_child formula_child sourcecode_child]
314
+ label_stripped = false
315
+
316
+ walked = walk_ordered(element,
317
+ allow_filter: display_attrs) do |type, obj|
318
+ case type
319
+ when :text
320
+ text = obj
321
+ unless label_stripped
322
+ text = deduplicate_semx_text(text, parts.join)
323
+ label_stripped = true
324
+ end
325
+ parts << escape_html(text)
326
+ when :element
327
+ parts << if obj.is_a?(Metanorma::Document::Components::Paragraphs::ParagraphBlock)
328
+ coordinator.render_paragraph(obj) || ""
329
+ else
330
+ render_inline_element(obj) || ""
331
+ end
332
+ end
333
+ end
334
+
335
+ unless walked
336
+ display_attrs.each do |attr|
337
+ val = safe_attr(element, attr)
338
+ next if val.nil?
339
+
340
+ if val.is_a?(Array)
341
+ val.each do |v|
342
+ parts << if v.is_a?(Metanorma::Document::Components::Paragraphs::ParagraphBlock)
343
+ coordinator.render_paragraph(v) || ""
344
+ else
345
+ render_inline_element(v) || ""
346
+ end
347
+ end
348
+ elsif val.is_a?(String)
349
+ parts << escape_html(val)
350
+ else
351
+ parts << (render_inline_element(val) || "")
352
+ end
353
+ end
354
+ end
355
+ parts.join
356
+ end
357
+
358
+ def deduplicate_semx_text(semx_text, rendered_so_far)
359
+ first_word = semx_text[/\A\s*(\S+)/, 1]
360
+ return semx_text unless first_word
361
+
362
+ tail = rendered_so_far[-200..]
363
+ return semx_text unless tail&.rstrip&.end_with?(first_word)
364
+
365
+ semx_text.sub(/\A\s*#{Regexp.escape(first_word)}\s*/, "")
366
+ end
367
+
368
+ def render_inline_tag(tag_name, element, **extra_attrs)
369
+ content = render_mixed_inline(element)
370
+ render_liquid("_element.html.liquid", {
371
+ "tag" => tag_name,
372
+ "extra_attrs" => element_attrs(**extra_attrs),
373
+ "content" => content,
374
+ })
375
+ end
376
+
377
+ def render_raw_content(raw_xml)
378
+ doc = Nokogiri::XML.fragment(raw_xml)
379
+ doc.css("fmt-link").each do |el|
380
+ target = el["target"] || el["href"]
381
+ if target
382
+ display_text = target.delete_prefix("mailto:")
383
+ a = doc.document.create_element("a", display_text,
384
+ "href" => target)
385
+ el.replace(a)
386
+ else
387
+ el.replace(el.children)
388
+ end
389
+ end
390
+ doc.traverse do |node|
391
+ next unless node.element?
392
+ next unless %w[xref eref stem link].include?(node.name)
393
+
394
+ next_sib = node.next_sibling
395
+ while next_sib.is_a?(Nokogiri::XML::Text) && next_sib.text.strip.empty?
396
+ next_sib = next_sib.next_sibling
397
+ end
398
+ next unless next_sib&.element? && next_sib.name == "semx"
399
+
400
+ deduplicate_semx_label(node, next_sib)
401
+ node.remove
402
+ end
403
+ %w[semx fmt-xref].each do |tag|
404
+ doc.css(tag).each { |el| el.replace(el.children) }
405
+ end
406
+ doc.css("[class]").each do |el|
407
+ el["class"] = el["class"].split(/\s+/).map do |c|
408
+ html_class_for_span(c)
409
+ end.join(" ")
410
+ end
411
+ doc.inner_html
412
+ end
413
+
414
+ def deduplicate_semx_label(source_node, semx_node)
415
+ first_text = semx_node.children.find do |c|
416
+ c.text? && !c.text.strip.empty?
417
+ end
418
+ return unless first_text
419
+
420
+ semx_prefix = first_text.text[/\A(\s*\S+)/, 1]
421
+ return unless semx_prefix && !semx_prefix.strip.empty?
422
+
423
+ prev = source_node.previous_sibling
424
+ return unless prev.is_a?(Nokogiri::XML::Text)
425
+
426
+ label = semx_prefix.strip
427
+ prev_text = prev.text.rstrip
428
+ return unless prev_text.end_with?(label)
429
+
430
+ prev.content = prev_text.sub(/#{Regexp.escape(label)}\s*\z/, "")
431
+ first_text.content = first_text.text.sub(
432
+ /\A\s*#{Regexp.escape(label)}\s*/, ""
433
+ )
434
+ end
435
+
436
+ def render_link(link)
437
+ target = safe_attr(link, :target) || safe_attr(link, :href)
438
+ attrs = element_attrs(href: target, id: safe_attr(link, :id))
439
+ content = safe_attr(link, :content)
440
+ if content && !Array(content).join.strip.empty?
441
+ inner = render_mixed_inline(link)
442
+ render_liquid("_link.html.liquid", {
443
+ "attrs" => attrs,
444
+ "content" => inner,
445
+ })
446
+ else
447
+ display_text = escape_html(target.to_s.delete_prefix("mailto:"))
448
+ render_liquid("_link.html.liquid", {
449
+ "attrs" => attrs,
450
+ "display_text" => display_text,
451
+ })
452
+ end
453
+ end
454
+
455
+ def render_xref(xref)
456
+ target = safe_attr(xref, :target) || safe_attr(xref, :to_attr)
457
+ attrs = element_attrs(href: "##{escape_html(target)}",
458
+ id: safe_attr(xref, :id))
459
+ content = render_mixed_inline(xref)
460
+ render_liquid("_link.html.liquid", {
461
+ "attrs" => attrs,
462
+ "content" => content,
463
+ })
464
+ end
465
+
466
+ def render_eref(eref)
467
+ citeas = safe_attr(eref, :citeas)
468
+ if citeas
469
+ escape_html(citeas)
470
+ else
471
+ render_mixed_inline(eref)
472
+ end
473
+ end
474
+
475
+ def render_fn(fn)
476
+ fn_id = safe_attr(fn, :id)
477
+ number = coordinator.footnote_collector.register(fn)
478
+
479
+ label = safe_attr(fn, :fn_label) || safe_attr(fn, :reference)
480
+ return nil unless label
481
+
482
+ popup_parts = Array(fn.p).map do |para|
483
+ render_mixed_inline(para) || ""
484
+ end
485
+ popup_html = popup_parts.join
486
+
487
+ assigns = {
488
+ "attrs" => element_attrs(id: fn_id, class: "fn-marker"),
489
+ "number" => number,
490
+ "label" => escape_html(label.to_s),
491
+ "popup_html" => popup_html.strip.empty? ? nil : popup_html,
492
+ }
493
+ render_liquid("_fn_marker.html.liquid", assigns)
494
+ end
495
+
496
+ def render_concept(concept)
497
+ render_mixed_inline(concept)
498
+ end
499
+
500
+ def render_fmt_stem(fmt_stem)
501
+ semx_items = Array(fmt_stem.semx)
502
+ return nil if semx_items.empty?
503
+
504
+ semx = semx_items.first
505
+ math_items = Array(semx.math)
506
+ ascii_items = Array(semx.asciimath)
507
+
508
+ source_formats = {}
509
+ if ascii_items.any?
510
+ ascii_text = ascii_items.map { |a| a.text.to_s.strip }.join
511
+ source_formats["asciimath"] = ascii_text unless ascii_text.empty?
512
+ end
513
+
514
+ data_attrs = ""
515
+ unless source_formats.empty?
516
+ data_attrs = " data-stem-formats='#{escape_html(source_formats.to_json)}'"
517
+ end
518
+
519
+ if math_items.any?
520
+ content = math_items.map { |m| m.content.to_s }.join
521
+ unless content.empty?
522
+ return render_liquid("_math_container.html.liquid", {
523
+ "data_attrs" => data_attrs,
524
+ "content" => content,
525
+ })
526
+ end
527
+ elsif ascii_items.any?
528
+ text = escape_html(ascii_items.map { |a| a.text.to_s.strip }.join)
529
+ return render_liquid("_stem_span.html.liquid", {
530
+ "data_attrs" => data_attrs,
531
+ "text" => text,
532
+ })
533
+ end
534
+ nil
535
+ end
536
+
537
+ # Stem content formats in priority order. Each entry binds an
538
+ # attribute reader on the stem element to the value-render path
539
+ # (raw XML when serializable, escaped text otherwise). Adding a
540
+ # new format = one entry here.
541
+ STEM_FORMATS = [
542
+ { attr: :math, raw_xml: true },
543
+ { attr: :asciimath },
544
+ { attr: :latexmath },
545
+ ].freeze
546
+
547
+ def render_stem_content(stem)
548
+ return nil if stem.nil?
549
+
550
+ return nil if stem.is_a?(Metanorma::Document::Components::Inline::StemInlineElement) ||
551
+ stem.is_a?(Metanorma::Document::Components::Inline::FmtStemElement)
552
+
553
+ unless stem.is_a?(Metanorma::Document::Components::TextElements::StemElement)
554
+ text = coordinator.extract_text_value(stem)
555
+ unless text.empty?
556
+ return render_liquid("_stem_span.html.liquid", {
557
+ "data_attrs" => "",
558
+ "text" => escape_html(text),
559
+ })
560
+ end
561
+ return nil
562
+ end
563
+
564
+ STEM_FORMATS.each do |format|
565
+ value = stem.public_send(format[:attr])
566
+ next unless value
567
+
568
+ if format[:raw_xml] && value.is_a?(Lutaml::Model::Serializable)
569
+ return value.to_xml
570
+ end
571
+
572
+ return render_liquid("_stem_span.html.liquid", {
573
+ "data_attrs" => "",
574
+ "text" => escape_html(coordinator.extract_text_value(value)),
575
+ })
576
+ end
577
+ nil
578
+ end
579
+
580
+ def render_inline_element(element, **)
581
+ return nil if element.nil?
582
+
583
+ if element.is_a?(String)
584
+ return escape_html(element)
585
+ end
586
+
587
+ method = coordinator.lookup_dispatch(element.class, :inline_registry)
588
+ if method
589
+ coordinator.public_send(method, element)
590
+ elsif element.is_a?(Lutaml::Model::Serializable) && element.mixed?
591
+ render_mixed_inline(element)
592
+ end
593
+ end
594
+
595
+ def render_mixed_content_in_order(node)
596
+ parts = []
597
+ node.each_mixed_content do |child|
598
+ parts << case child
599
+ when String
600
+ escape_html(child)
601
+ else
602
+ if coordinator.block_element?(child)
603
+ coordinator.render(child) || ""
604
+ else
605
+ render_inline_element(child) || ""
606
+ end
607
+ end
608
+ end
609
+ parts.join
610
+ end
611
+
612
+ private
613
+
614
+ attr_reader :coordinator
615
+
616
+ def safe_attr(obj, method_name)
617
+ coordinator.safe_attr(obj, method_name)
618
+ end
619
+
620
+ def escape_html(text)
621
+ coordinator.escape_html(text)
622
+ end
623
+
624
+ def element_attrs(**attrs)
625
+ coordinator.element_attrs(**attrs)
626
+ end
627
+
628
+ def render_liquid(template_name, assigns)
629
+ coordinator.render_liquid(template_name, assigns)
630
+ end
631
+
632
+ def html_class_for_span(xml_class)
633
+ coordinator.html_class_for_span(xml_class)
634
+ end
635
+
636
+ def collect_index_term(element)
637
+ coordinator.collect_index_term(element)
638
+ end
639
+ end
640
+ end
641
+ end
642
+ end