metanorma-document 0.2.9 → 0.2.12

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 (390) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +164 -66
  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 +13 -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 +261 -1008
  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 -7
  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/generator.rb +2 -2
  228. data/lib/metanorma/html/icc_renderer.rb +0 -39
  229. data/lib/metanorma/html/iec_renderer.rb +1 -44
  230. data/lib/metanorma/html/ieee_renderer.rb +1 -45
  231. data/lib/metanorma/html/ietf_renderer.rb +1 -48
  232. data/lib/metanorma/html/iho_renderer.rb +1 -46
  233. data/lib/metanorma/html/iso_renderer.rb +290 -343
  234. data/lib/metanorma/html/itu_renderer.rb +1 -45
  235. data/lib/metanorma/html/ogc_renderer.rb +15 -76
  236. data/lib/metanorma/html/oiml_renderer.rb +18 -38
  237. data/lib/metanorma/html/pdfa_renderer.rb +0 -39
  238. data/lib/metanorma/html/renderers/block_renderer.rb +372 -0
  239. data/lib/metanorma/html/renderers/inline_renderer.rb +655 -0
  240. data/lib/metanorma/html/renderers/pubid_renderer.rb +69 -0
  241. data/lib/metanorma/html/renderers/section_renderer.rb +193 -0
  242. data/lib/metanorma/html/renderers.rb +8 -0
  243. data/lib/metanorma/html/ribose_renderer.rb +1 -45
  244. data/lib/metanorma/html/standard_renderer.rb +341 -229
  245. data/lib/metanorma/html/templates/_audio.html.liquid +1 -0
  246. data/lib/metanorma/html/templates/_biblio_entry.html.liquid +1 -0
  247. data/lib/metanorma/html/templates/_bipm_cover.html.liquid +27 -0
  248. data/lib/metanorma/html/templates/_boilerplate.html.liquid +1 -0
  249. data/lib/metanorma/html/templates/_bookmark.html.liquid +1 -0
  250. data/lib/metanorma/html/templates/_br.html.liquid +1 -0
  251. data/lib/metanorma/html/templates/_definition_list.html.liquid +6 -0
  252. data/lib/metanorma/html/templates/_element.html.liquid +1 -0
  253. data/lib/metanorma/html/templates/_fn_marker.html.liquid +1 -0
  254. data/lib/metanorma/html/templates/_footnotes.html.liquid +6 -6
  255. data/lib/metanorma/html/templates/_formula.html.liquid +1 -1
  256. data/lib/metanorma/html/templates/_header.html.liquid +1 -1
  257. data/lib/metanorma/html/templates/_heading.html.liquid +1 -0
  258. data/lib/metanorma/html/templates/_image.html.liquid +1 -0
  259. data/lib/metanorma/html/templates/_inline_span.html.liquid +1 -0
  260. data/lib/metanorma/html/templates/_iso_cover.html.liquid +1 -1
  261. data/lib/metanorma/html/templates/_link.html.liquid +1 -0
  262. data/lib/metanorma/html/templates/_list.html.liquid +3 -0
  263. data/lib/metanorma/html/templates/_list_item.html.liquid +1 -0
  264. data/lib/metanorma/html/templates/_main_content.html.liquid +1 -0
  265. data/lib/metanorma/html/templates/_math_container.html.liquid +1 -0
  266. data/lib/metanorma/html/templates/_paragraph.html.liquid +1 -0
  267. data/lib/metanorma/html/templates/_prefatory_section.html.liquid +1 -0
  268. data/lib/metanorma/html/templates/_pubid_identifier.html.liquid +1 -0
  269. data/lib/metanorma/html/templates/_quote.html.liquid +4 -0
  270. data/lib/metanorma/html/templates/_ref_date.html.liquid +1 -0
  271. data/lib/metanorma/html/templates/_ref_title.html.liquid +1 -0
  272. data/lib/metanorma/html/templates/_section.html.liquid +5 -0
  273. data/lib/metanorma/html/templates/_standard_cover.html.liquid +31 -0
  274. data/lib/metanorma/html/templates/_standard_doc_title.html.liquid +3 -0
  275. data/lib/metanorma/html/templates/_stem_span.html.liquid +1 -0
  276. data/lib/metanorma/html/templates/_table.html.liquid +9 -0
  277. data/lib/metanorma/html/templates/_term_domain.html.liquid +1 -0
  278. data/lib/metanorma/html/templates/_term_example.html.liquid +1 -0
  279. data/lib/metanorma/html/templates/_term_note.html.liquid +1 -0
  280. data/lib/metanorma/html/templates/_term_number.html.liquid +1 -0
  281. data/lib/metanorma/html/templates/_term_source.html.liquid +1 -0
  282. data/lib/metanorma/html/templates/_term_text_note.html.liquid +1 -0
  283. data/lib/metanorma/html/templates/_video.html.liquid +1 -0
  284. data/lib/metanorma/html/templates/_wrapped_preface.html.liquid +1 -0
  285. data/lib/metanorma/html/templates/document.html.liquid +4 -0
  286. data/lib/metanorma/html/theme.rb +180 -107
  287. data/lib/metanorma/html.rb +13 -0
  288. data/lib/metanorma/ietf_document/metadata/ietf_bib_data_extension_type.rb +0 -25
  289. data/lib/metanorma/ietf_document/metadata/pi_settings.rb +32 -0
  290. data/lib/metanorma/ietf_document/metadata.rb +2 -0
  291. data/lib/metanorma/iso_document/metadata/doctype_element.rb +20 -0
  292. data/lib/metanorma/iso_document/metadata/iso_bib_data_extension_type.rb +0 -48
  293. data/lib/metanorma/iso_document/metadata/iso_bibliographic_item.rb +0 -72
  294. data/lib/metanorma/iso_document/metadata/iso_document_status.rb +0 -30
  295. data/lib/metanorma/iso_document/metadata/iso_localized_title.rb +4 -1
  296. data/lib/metanorma/iso_document/metadata/language_element.rb +18 -0
  297. data/lib/metanorma/iso_document/metadata/project_number.rb +22 -0
  298. data/lib/metanorma/iso_document/metadata/script_element.rb +18 -0
  299. data/lib/metanorma/iso_document/metadata/stage_element.rb +26 -0
  300. data/lib/metanorma/iso_document/metadata/stagename_element.rb +18 -0
  301. data/lib/metanorma/iso_document/metadata/structured_identifier.rb +0 -17
  302. data/lib/metanorma/iso_document/metadata/title_collection.rb +1 -0
  303. data/lib/metanorma/iso_document/metadata.rb +9 -4
  304. data/lib/metanorma/iso_document/terms/iso_term.rb +0 -13
  305. data/lib/metanorma/iso_document/terms/term_domain_element.rb +18 -0
  306. data/lib/metanorma/iso_document/terms.rb +1 -0
  307. data/lib/metanorma/itu_document/metadata/itu_bib_data_extension_type.rb +0 -37
  308. data/lib/metanorma/itu_document/metadata/meeting_date.rb +18 -0
  309. data/lib/metanorma/itu_document/metadata/meeting_element.rb +18 -0
  310. data/lib/metanorma/itu_document/metadata/study_period.rb +18 -0
  311. data/lib/metanorma/itu_document/metadata.rb +3 -4
  312. data/lib/metanorma/mirror/default_registry.rb +170 -0
  313. data/lib/metanorma/mirror/handler_registry.rb +60 -0
  314. data/lib/metanorma/mirror/handler_result.rb +37 -0
  315. data/lib/metanorma/mirror/handlers/admonition.rb +18 -0
  316. data/lib/metanorma/mirror/handlers/example.rb +21 -0
  317. data/lib/metanorma/mirror/handlers/figure.rb +53 -0
  318. data/lib/metanorma/mirror/handlers/formula.rb +35 -0
  319. data/lib/metanorma/mirror/handlers/inline/rich_html_renderer.rb +159 -0
  320. data/lib/metanorma/mirror/handlers/inline/text_extractor.rb +116 -0
  321. data/lib/metanorma/mirror/handlers/inline.rb +225 -0
  322. data/lib/metanorma/mirror/handlers/list.rb +110 -0
  323. data/lib/metanorma/mirror/handlers/note.rb +26 -0
  324. data/lib/metanorma/mirror/handlers/paragraph.rb +19 -0
  325. data/lib/metanorma/mirror/handlers/quote.rb +16 -0
  326. data/lib/metanorma/mirror/handlers/review.rb +19 -0
  327. data/lib/metanorma/mirror/handlers/section.rb +149 -0
  328. data/lib/metanorma/mirror/handlers/sourcecode.rb +27 -0
  329. data/lib/metanorma/mirror/handlers/structural.rb +68 -0
  330. data/lib/metanorma/mirror/handlers/table.rb +81 -0
  331. data/lib/metanorma/mirror/handlers/term.rb +100 -0
  332. data/lib/metanorma/mirror/handlers.rb +60 -0
  333. data/lib/metanorma/mirror/id_strategy/positional.rb +166 -0
  334. data/lib/metanorma/mirror/id_strategy/preserve.rb +12 -0
  335. data/lib/metanorma/mirror/id_strategy.rb +34 -0
  336. data/lib/metanorma/mirror/math_util.rb +53 -0
  337. data/lib/metanorma/mirror/metadata.rb +37 -0
  338. data/lib/metanorma/mirror/metanorma_to_mirror.rb +101 -0
  339. data/lib/metanorma/mirror/model/container.rb +47 -0
  340. data/lib/metanorma/mirror/model/factory.rb +52 -0
  341. data/lib/metanorma/mirror/model/guide.rb +25 -0
  342. data/lib/metanorma/mirror/model/leaf.rb +17 -0
  343. data/lib/metanorma/mirror/model/mark.rb +53 -0
  344. data/lib/metanorma/mirror/model/node.rb +47 -0
  345. data/lib/metanorma/mirror/model/soft_break.rb +33 -0
  346. data/lib/metanorma/mirror/model/text.rb +34 -0
  347. data/lib/metanorma/mirror/model.rb +16 -0
  348. data/lib/metanorma/mirror/output/builder.rb +39 -0
  349. data/lib/metanorma/mirror/output/formats/base_format.rb +90 -0
  350. data/lib/metanorma/mirror/output/formats/inline_format.rb +81 -0
  351. data/lib/metanorma/mirror/output/formats.rb +53 -0
  352. data/lib/metanorma/mirror/output/html_renderer.rb +95 -0
  353. data/lib/metanorma/mirror/output/html_renderers/block_renderers.rb +178 -0
  354. data/lib/metanorma/mirror/output/html_renderers/inline_renderer.rb +61 -0
  355. data/lib/metanorma/mirror/output/html_renderers/list_renderers.rb +96 -0
  356. data/lib/metanorma/mirror/output/html_renderers/mark_renderers.rb +64 -0
  357. data/lib/metanorma/mirror/output/html_renderers/section_renderers.rb +129 -0
  358. data/lib/metanorma/mirror/output/html_renderers/structural_renderers.rb +80 -0
  359. data/lib/metanorma/mirror/output/html_renderers/table_renderers.rb +87 -0
  360. data/lib/metanorma/mirror/output/html_renderers.rb +76 -0
  361. data/lib/metanorma/mirror/output/pipeline.rb +92 -0
  362. data/lib/metanorma/mirror/output/pipeline_context.rb +22 -0
  363. data/lib/metanorma/mirror/output.rb +14 -0
  364. data/lib/metanorma/mirror/rewriter.rb +121 -0
  365. data/lib/metanorma/mirror/safe_attr.rb +16 -0
  366. data/lib/metanorma/mirror/serialization/json_serializer.rb +25 -0
  367. data/lib/metanorma/mirror/serialization/yaml_serializer.rb +20 -0
  368. data/lib/metanorma/mirror/serialization.rb +10 -0
  369. data/lib/metanorma/mirror/transformer.rb +22 -0
  370. data/lib/metanorma/mirror.rb +32 -0
  371. data/lib/metanorma/standard_document/annotation.rb +37 -0
  372. data/lib/metanorma/standard_document/block_attributes.rb +15 -2
  373. data/lib/metanorma/standard_document/blocks/block_source.rb +0 -23
  374. data/lib/metanorma/standard_document/blocks/source_origin_element.rb +23 -0
  375. data/lib/metanorma/standard_document/blocks.rb +1 -1
  376. data/lib/metanorma/standard_document/terms/term_expression.rb +0 -31
  377. data/lib/metanorma/standard_document/terms/term_name_element.rb +29 -0
  378. data/lib/metanorma/standard_document/terms.rb +1 -0
  379. data/script/render_both.rb +18 -0
  380. metadata +262 -13
  381. data/.github/workflows/rake.yml +0 -21
  382. data/.github/workflows/release.yml +0 -25
  383. data/.github/workflows/roundtrip-samples.yml +0 -132
  384. data/.gitignore +0 -19
  385. data/.rspec +0 -3
  386. data/.rubocop.yml +0 -22
  387. data/Gemfile +0 -28
  388. data/bin/console +0 -15
  389. data/bin/setup +0 -8
  390. data/metanorma-document.gemspec +0 -35
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Metanorma
4
4
  module Html
5
- # Renders StandardDocument components to HTML.
6
- # Extends BaseRenderer with terms, bibliography, and standard sections.
7
5
  class StandardRenderer < BaseRenderer
8
6
  register_render Metanorma::StandardDocument::Root,
9
7
  :render_standard_document
@@ -31,46 +29,32 @@ module Metanorma
31
29
  register_render Metanorma::StandardDocument::Blocks::AmendBlock,
32
30
  :render_amend_block
33
31
 
34
- # --- Top-level document rendering ---
35
-
36
32
  def render_standard_document(doc, **_opts)
37
- render_coverpage(doc)
38
-
39
- @output << "<main class=\"main-section\">"
40
-
41
- # Preface sections
42
- render(doc.preface) if doc.preface
43
-
44
- # Document title line
45
- render_doc_title(doc)
46
-
47
- # Main sections
48
- render(doc.sections) if doc.sections
33
+ cover = render_coverpage(doc)
49
34
 
50
- # Annexes
51
- doc.annex&.each { |annex| render(annex) }
35
+ content_parts = []
36
+ content_parts << (render(doc.preface) || "") if doc.preface
37
+ content_parts << (render_doc_title(doc) || "")
38
+ content_parts << (render(doc.sections) || "") if doc.sections
39
+ doc.annex&.each { |annex| content_parts << (render(annex) || "") }
40
+ content_parts << (render(doc.bibliography) || "") if doc.bibliography
52
41
 
53
- # Bibliography
54
- render(doc.bibliography) if doc.bibliography
55
-
56
- # Index section (from collected index terms)
57
42
  unless @index_term_collector.empty?
58
43
  index_component = Component::IndexSection.new(self)
59
- index_component.render(@index_term_collector)
44
+ content_parts << (index_component.render(@index_term_collector) || "")
60
45
  end
61
46
 
62
- # Index section from indexsect element (presentation XML)
63
- render(doc.indexsect) if doc.indexsect
64
-
65
- # Footnotes section
66
- render_footnotes_section
47
+ content_parts << (render(doc.indexsect) || "") if doc.indexsect
48
+ content_parts << (render_footnotes_section || "")
67
49
 
68
- @output << "</main>"
50
+ cover + render_liquid("_main_content.html.liquid", {
51
+ "content" => content_parts.join,
52
+ })
69
53
  end
70
54
 
71
55
  def render_coverpage(doc)
72
56
  bibdata = doc.bibdata
73
- return unless bibdata
57
+ return "" unless bibdata
74
58
 
75
59
  cover_id = nil
76
60
  bibdata.doc_identifier&.each do |di|
@@ -83,63 +67,46 @@ module Metanorma
83
67
  break
84
68
  end
85
69
 
86
- title_text = nil
87
- if bibdata.is_a?(Metanorma::IsoDocument::Metadata::IsoBibliographicItem)
88
- en_title = bibdata.title_for("en")
89
- title_text = en_title.to_s if en_title
90
- elsif bibdata.is_a?(Metanorma::Document::Components::BibData::BibData)
91
- titles = bibdata.title
92
- if titles && !titles.empty?
93
- en_title = titles.find { |t| t.language == "en" }
94
- title_text = extract_text_value(en_title) if en_title
95
- end
96
- end
70
+ title_text = extract_display_title(bibdata)
97
71
 
98
- @output << render_liquid("_cover.html.liquid", {
99
- "doc_id" => cover_id,
100
- "title" => title_text,
101
- })
72
+ render_liquid("_cover.html.liquid", {
73
+ "doc_id" => cover_id,
74
+ "title" => title_text,
75
+ })
102
76
  end
103
77
 
104
78
  def render_doc_title(doc)
105
79
  bibdata = doc.bibdata
106
- return unless bibdata
80
+ return nil unless bibdata
107
81
 
108
- if bibdata.is_a?(Metanorma::IsoDocument::Metadata::IsoBibliographicItem)
109
- en_title = bibdata.title_for("en")
110
- return unless en_title
111
- elsif bibdata.is_a?(Metanorma::Document::Components::BibData::BibData)
112
- titles = bibdata.title
113
- return unless titles && !titles.empty?
82
+ title = extract_display_title(bibdata)
83
+ return nil unless title
114
84
 
115
- en_title = titles.find { |t| t.language == "en" }
116
- return unless en_title
117
-
118
- en_title = extract_text_value(en_title)
119
- else
120
- return
121
- end
122
-
123
- @output << render_liquid("_doc_title.html.liquid", {
124
- "title" => en_title,
125
- })
85
+ render_liquid("_doc_title.html.liquid", {
86
+ "title" => title,
87
+ })
126
88
  end
127
89
 
128
- # --- Section rendering ---
129
-
130
90
  def render_section(section, level: 1, title_class: nil,
131
- with_subsections: false, with_terms: false)
91
+ with_subsections: false, with_terms: false)
132
92
  attrs = element_attrs(id: safe_attr(section, :id))
133
- tag("div", attrs) do
134
- if title_class
135
- render_standard_title(section, level, default_class: title_class)
136
- else
137
- render_standard_title(section, level)
138
- end
139
- render_standard_section_blocks(section, level)
140
- render_subsections(section, level) if with_subsections
141
- section.terms&.each { |term| render_term(term) } if with_terms
93
+ parts = []
94
+ parts << if title_class
95
+ render_standard_title(section, level,
96
+ default_class: title_class) || ""
97
+ else
98
+ render_standard_title(section, level) || ""
99
+ end
100
+ parts << (render_standard_section_blocks(section, level) || "")
101
+ parts << (render_subsections(section, level) || "") if with_subsections
102
+ if with_terms
103
+ section.terms&.each { |term| parts << (render_term(term) || "") }
142
104
  end
105
+ render_liquid("_element.html.liquid", {
106
+ "tag" => "div",
107
+ "extra_attrs" => attrs,
108
+ "content" => parts.join,
109
+ })
143
110
  end
144
111
 
145
112
  def render_clause_section(section, level: 1, **)
@@ -174,70 +141,94 @@ with_subsections: false, with_terms: false)
174
141
  level = safe_attr(title_node, :level) || 1
175
142
  h = "h#{[[level, 6].min, 1].max}"
176
143
  attrs = element_attrs(id: safe_attr(title_node, :id))
177
- @output << "<#{h}#{attrs}>#{escape_html(title_node.text.to_s)}</#{h}>"
144
+ render_liquid("_heading.html.liquid", {
145
+ "tag" => h,
146
+ "class_attr" => attrs,
147
+ "content" => escape_html(title_node.text.to_s),
148
+ })
178
149
  end
179
150
 
180
151
  def render_amend_block(amend, **_opts)
181
152
  attrs = element_attrs(id: safe_attr(amend, :id))
182
- tag("div", attrs) do
183
- render_mixed_inline(amend)
184
- end
153
+ content = render_mixed_inline(amend)
154
+ render_liquid("_element.html.liquid", {
155
+ "tag" => "div",
156
+ "extra_attrs" => attrs,
157
+ "content" => content,
158
+ })
185
159
  end
186
160
 
187
161
  # --- Term rendering ---
188
162
 
189
163
  def render_term(term, **_opts)
190
164
  attrs = element_attrs(id: safe_attr(term, :id))
191
- tag("div", attrs) do
192
- term.preferred&.each { |d| render_term_designation(d, "preferred") }
193
- term.admitted&.each { |d| render_term_designation(d, "admitted") }
194
- term.deprecates&.each { |d| render_term_designation(d, "deprecated") }
195
-
196
- # Domain
197
- if term.domain
198
- domain_text = if term.domain.is_a?(String)
199
- term.domain
200
- else
201
- safe_attr(
202
- term.domain, :text
203
- ).to_s
204
- end
205
- @output << "<p class=\"term-domain\">&lt;#{escape_html(domain_text)}&gt;</p>" unless domain_text.to_s.empty?
206
- end
165
+ parts = []
166
+ term.preferred&.each do |d|
167
+ parts << (render_term_designation(d, "preferred") || "")
168
+ end
169
+ term.admitted&.each do |d|
170
+ parts << (render_term_designation(d, "admitted") || "")
171
+ end
172
+ term.deprecates&.each do |d|
173
+ parts << (render_term_designation(d, "deprecated") || "")
174
+ end
207
175
 
208
- # Definitions
209
- if term.definition
210
- Array(term.definition).each do |defn|
211
- render_term_definition(defn)
212
- end
176
+ if term.domain
177
+ domain_text = if term.domain.is_a?(String)
178
+ term.domain
179
+ else
180
+ safe_attr(term.domain, :text).to_s
181
+ end
182
+ unless domain_text.to_s.empty?
183
+ parts << render_liquid("_term_domain.html.liquid", {
184
+ "text" => escape_html(domain_text),
185
+ }).to_s
213
186
  end
187
+ end
214
188
 
215
- # Notes (mapped from <termnote>)
216
- term.note&.each_with_index do |note, i|
217
- if note.is_a?(String)
218
- @output << "<div class=\"note-block\"><p><span class=\"term-note-label\">Note #{i + 1} to entry: </span>#{escape_html(note)}</p></div>"
219
- else
220
- render_note(note)
221
- end
189
+ if term.definition
190
+ Array(term.definition).each do |defn|
191
+ parts << (render_term_definition(defn) || "")
222
192
  end
193
+ end
223
194
 
224
- # Examples (mapped from <termexample>)
225
- term.example&.each { |ex| render_paragraph(ex) }
226
-
227
- # Source references
228
- term.source&.each { |src| render_term_source(src) }
195
+ term.note&.each_with_index do |note, i|
196
+ if note.is_a?(String)
197
+ label = "Note #{i + 1} to entry: "
198
+ parts << render_liquid("_term_text_note.html.liquid", {
199
+ "label" => label,
200
+ "content" => escape_html(note),
201
+ }).to_s
202
+ else
203
+ parts << (render_note(note) || "")
204
+ end
229
205
  end
206
+
207
+ term.example&.each { |ex| parts << (render_paragraph(ex) || "") }
208
+ term.source&.each { |src| parts << (render_term_source(src) || "") }
209
+ render_liquid("_element.html.liquid", {
210
+ "tag" => "div",
211
+ "extra_attrs" => attrs,
212
+ "content" => parts.join,
213
+ })
230
214
  end
231
215
 
232
216
  def render_term_designation(designation, _type)
233
217
  name = extract_designation_name(designation)
234
- return unless name
235
-
236
- tag("p", " class=\"term-name\" style=\"text-align:left;\"") do
237
- tag("b") do
238
- tag("dfn") { @output << escape_html(name) }
239
- end
240
- end
218
+ return nil unless name
219
+
220
+ inner = escape_html(name)
221
+ dfn = render_liquid("_element.html.liquid",
222
+ { "tag" => "dfn", "extra_attrs" => "",
223
+ "content" => inner })
224
+ bold = render_liquid("_element.html.liquid",
225
+ { "tag" => "b", "extra_attrs" => "",
226
+ "content" => dfn })
227
+ render_liquid("_element.html.liquid", {
228
+ "tag" => "p",
229
+ "extra_attrs" => " class=\"term-name\" style=\"text-align:left;\"",
230
+ "content" => bold,
231
+ })
241
232
  end
242
233
 
243
234
  def extract_designation_name(designation)
@@ -254,143 +245,222 @@ with_subsections: false, with_terms: false)
254
245
  end
255
246
 
256
247
  def render_term_definition(definition)
257
- return unless definition
258
- return unless definition.is_a?(Metanorma::StandardDocument::Terms::TermDefinition)
248
+ return nil unless definition
249
+ return nil unless definition.is_a?(Metanorma::StandardDocument::Terms::TermDefinition)
259
250
 
260
251
  ve = definition.verbalexpression
261
- return unless ve
252
+ return nil unless ve
262
253
 
263
- ve.paragraph&.each { |para| render_paragraph(para) }
254
+ parts = []
255
+ ve.paragraph&.each { |para| parts << (render_paragraph(para) || "") }
256
+ parts.join
264
257
  end
265
258
 
266
259
  def render_term_note(note)
267
260
  attrs = element_attrs(id: safe_attr(note, :id), class: "note-block")
268
- tag("div", attrs) do
269
- label = extract_termnote_label(note)
270
- @output << "<p><span class=\"term-note-label\">#{escape_html(label)}: </span>"
271
- note.p&.each { |para| render_mixed_inline(para) }
272
- @output << "</p>"
273
- note.ul&.each { |ul| render_unordered_list(ul) }
274
- note.ol&.each { |ol| render_ordered_list(ol) }
275
- note.dl&.then { |dl| render_definition_list(dl) }
261
+ label = extract_termnote_label(note)
262
+ parts = []
263
+ note_content_parts = []
264
+ note.p&.each do |para|
265
+ note_content_parts << (render_mixed_inline(para) || "")
276
266
  end
267
+ note_content = note_content_parts.join
268
+ parts << render_liquid("_term_note.html.liquid", {
269
+ "label" => escape_html(label),
270
+ "content" => note_content,
271
+ })
272
+ note.ul&.each { |ul| parts << (render_unordered_list(ul) || "") }
273
+ note.ol&.each { |ol| parts << (render_ordered_list(ol) || "") }
274
+ note.dl&.then { |dl| parts << (render_definition_list(dl) || "") }
275
+ render_liquid("_element.html.liquid", {
276
+ "tag" => "div",
277
+ "extra_attrs" => attrs,
278
+ "content" => parts.join,
279
+ })
277
280
  end
278
281
 
279
282
  def render_term_example(example)
280
283
  attrs = element_attrs(id: safe_attr(example, :id), class: "example")
281
- tag("div", attrs) do
282
- label = extract_block_label(example, "EXAMPLE")
283
- @output << "<p><span class=\"example-label\">#{escape_html(label)}</span>&nbsp;"
284
- example.p&.each { |para| render_mixed_inline(para) }
285
- @output << "</p>"
286
- example.ul&.each { |ul| render_unordered_list(ul) }
287
- example.ol&.each { |ol| render_ordered_list(ol) }
288
- example.dl&.then { |dl| render_definition_list(dl) }
284
+ label = extract_block_label(example, "EXAMPLE")
285
+ parts = []
286
+ ex_content_parts = []
287
+ example.p&.each do |para|
288
+ ex_content_parts << (render_mixed_inline(para) || "")
289
289
  end
290
+ ex_content = ex_content_parts.join
291
+ parts << render_liquid("_term_example.html.liquid", {
292
+ "label" => escape_html(label),
293
+ "content" => ex_content,
294
+ })
295
+ example.ul&.each { |ul| parts << (render_unordered_list(ul) || "") }
296
+ example.ol&.each { |ol| parts << (render_ordered_list(ol) || "") }
297
+ example.dl&.then { |dl| parts << (render_definition_list(dl) || "") }
298
+ render_liquid("_element.html.liquid", {
299
+ "tag" => "div",
300
+ "extra_attrs" => attrs,
301
+ "content" => parts.join,
302
+ })
290
303
  end
291
304
 
292
305
  def render_term_source(source)
293
- return unless source
306
+ return nil unless source
294
307
 
295
- @output << "<p class=\"term-source\">[SOURCE: "
308
+ parts = []
296
309
  termsource = safe_attr(source, :termsource)
297
310
  origin = safe_attr(source, :origin)
298
311
 
299
312
  if termsource
300
- render_mixed_inline(termsource)
313
+ parts << (render_mixed_inline(termsource) || "")
301
314
  elsif origin
302
315
  citeas = safe_attr(origin, :citeas)
303
316
  bibitemid = safe_attr(origin, :bibitemid)
304
317
 
305
- if citeas && !citeas.to_s.empty?
306
- @output << if bibitemid && !bibitemid.to_s.empty?
307
- "<a href=\"##{escape_html(bibitemid.to_s)}\" class=\"bibref\">#{escape_html(citeas.to_s)}</a>"
308
- else
309
- escape_html(citeas.to_s)
310
- end
311
- else
312
- render_mixed_inline(origin)
313
- end
318
+ parts << if citeas && !citeas.to_s.empty?
319
+ if bibitemid && !bibitemid.to_s.empty?
320
+ render_liquid("_link.html.liquid", {
321
+ "attrs" => element_attrs(
322
+ href: "##{escape_html(bibitemid.to_s)}", class: "bibref",
323
+ ),
324
+ "content" => escape_html(citeas.to_s),
325
+ })
326
+ else
327
+ escape_html(citeas.to_s)
328
+ end
329
+ else
330
+ render_mixed_inline(origin) || ""
331
+ end
314
332
 
315
333
  modification = safe_attr(source, :modification)
316
334
  if modification && !modification.to_s.empty?
317
- @output << ", modified — #{escape_html(modification.to_s)}"
335
+ parts << ", modified — #{escape_html(modification.to_s)}"
318
336
  end
319
337
  else
320
- render_mixed_inline(source)
338
+ parts << (render_mixed_inline(source) || "")
321
339
  end
322
- @output << "]</p>"
340
+
341
+ render_liquid("_term_source.html.liquid", {
342
+ "content" => parts.join,
343
+ })
323
344
  end
324
345
 
325
346
  def render_term_source_element(element)
326
- return unless element
347
+ return nil unless element
327
348
 
328
- @output << "<p>"
329
- render_mixed_inline(element)
330
- @output << "</p>"
349
+ content = render_mixed_inline(element)
350
+ render_liquid("_paragraph.html.liquid", {
351
+ "attrs" => "",
352
+ "content" => content,
353
+ })
331
354
  end
332
355
 
333
356
  # --- Bibliography / References ---
334
357
 
335
358
  def render_bibliography(bib, level: 1, **_opts)
336
- tag("div", "") do
337
- bib.references&.each do |ref|
338
- render_references_section(ref, level: level)
339
- end
340
- bib.clause&.each { |cl| render(cl, level: level) }
359
+ parts = []
360
+ bib.references&.each do |ref|
361
+ parts << (render_references_section(ref, level: level) || "")
341
362
  end
363
+ bib.clause&.each { |cl| parts << (render(cl, level: level) || "") }
364
+ render_liquid("_element.html.liquid", {
365
+ "tag" => "div",
366
+ "extra_attrs" => "",
367
+ "content" => parts.join,
368
+ })
342
369
  end
343
370
 
344
371
  def render_references_section(section, level: 1, **_opts)
345
372
  is_normative = safe_attr(section, :normative) == "true"
346
373
  attrs = element_attrs(id: safe_attr(section, :id))
347
- tag("div", attrs) do
348
- render_standard_title(section, level,
349
- default_class: is_normative ? "" : "section-sub")
350
- section.p&.each { |para| render_paragraph(para) }
351
- section.note&.each { |note| render_paragraph(note) }
352
- section.references&.each_with_index do |bibitem, i|
353
- render_bibitem(bibitem, i + 1, normative: is_normative)
354
- end
355
- section.table&.each { |t| render_table(t) }
374
+ parts = []
375
+ parts << (render_standard_title(section, level,
376
+ default_class: is_normative ? "" : "section-sub") || "")
377
+ section.p&.each { |para| parts << (render_paragraph(para) || "") }
378
+ section.note&.each { |note| parts << (render_paragraph(note) || "") }
379
+ section.references&.each_with_index do |bibitem, i|
380
+ parts << (render_bibitem(bibitem, i + 1,
381
+ normative: is_normative) || "")
356
382
  end
383
+ section.table&.each { |t| parts << (render_table(t) || "") }
384
+ render_liquid("_element.html.liquid", {
385
+ "tag" => "div",
386
+ "extra_attrs" => attrs,
387
+ "content" => parts.join,
388
+ })
357
389
  end
358
390
 
359
391
  def render_bibitem(item, index, normative: false)
360
392
  css_class = normative ? "norm-ref-entry" : "biblio-entry"
361
- attrs = element_attrs(id: safe_attr(item, :id), class: css_class)
393
+ item_id = safe_attr(item, :id)
362
394
  url = bibitem_url(item)
363
395
 
364
- tag("p", attrs) do
365
- if url
366
- @output << "<a href=\"#{escape_html(url)}\" target=\"_blank\" rel=\"noopener\" class=\"biblio-link\">"
367
- end
396
+ if item.biblio_tag
397
+ prefix, rest = split_biblio_tag(item.biblio_tag)
398
+ ordinal_html = prefix.empty? ? nil : escape_html(prefix)
399
+ pubid_html = rest&.filter_map do |child|
400
+ render_inline_element(child)
401
+ end&.join
402
+ else
403
+ ordinal_html = "[#{index}]"
404
+ pubid_html = nil
405
+ end
368
406
 
369
- # Use biblio-tag from presentation XML if available
370
- if item.biblio_tag
371
- render_mixed_inline(item.biblio_tag)
372
- @output << "&nbsp;"
373
- else
374
- @output << "[#{index}]&nbsp;"
375
- end
407
+ content_html = render_bibitem_content(item)
408
+
409
+ drop = Drops::BiblioEntryDrop.new(
410
+ id: item_id,
411
+ css_class: css_class,
412
+ ordinal_html: ordinal_html,
413
+ pubid_html: pubid_html,
414
+ url: url ? escape_html(url) : nil,
415
+ content_html: content_html,
416
+ )
417
+ render_liquid("_biblio_entry.html.liquid", { "entry" => drop })
418
+ end
419
+
420
+ def split_biblio_tag(tag)
421
+ children = []
422
+ tag.each_mixed_content { |c| children << c }
376
423
 
377
- render_bibitem_content(item)
424
+ prefix = +""
425
+ rest = []
426
+ found_boundary = false
378
427
 
379
- if url
380
- @output << "</a>"
428
+ children.each do |child|
429
+ if found_boundary
430
+ rest << child
431
+ else
432
+ case child
433
+ when Metanorma::Document::Components::Inline::TabElement
434
+ found_boundary = true
435
+ next
436
+ when String
437
+ stripped = child.strip
438
+ if stripped.match?(/\A\[\d+\]\z/)
439
+ prefix << child
440
+ elsif child.match?(/\A\s*\z/)
441
+ prefix << child
442
+ else
443
+ found_boundary = true
444
+ rest << child
445
+ end
446
+ else
447
+ found_boundary = true
448
+ rest << child
449
+ end
381
450
  end
382
451
  end
452
+
453
+ prefix_text = prefix.strip
454
+ [prefix_text, rest.empty? ? nil : rest]
383
455
  end
384
456
 
385
457
  def bibitem_url(item)
386
458
  links = Array(item.link)
387
459
  return nil if links.empty?
388
460
 
389
- # Prefer src or citation type, skip RSS feeds
390
461
  preferred = links.find { |l| ["src", "citation"].include?(l.type) }
391
462
  return preferred.content.to_s if preferred && !preferred.content.to_s.empty?
392
463
 
393
- # Fallback: first non-RSS link
394
464
  non_rss = links.find do |l|
395
465
  !l.content.to_s.include?(".rss") && !l.content.to_s.empty?
396
466
  end
@@ -398,25 +468,15 @@ with_subsections: false, with_terms: false)
398
468
  end
399
469
 
400
470
  def render_bibitem_content(item)
401
- # Use formatted reference if available (presentation XML)
471
+ parts = []
402
472
  if item.formatted_ref
403
- render_mixed_inline(item.formatted_ref)
404
- return
473
+ parts << (render_mixed_inline(item.formatted_ref) || "")
474
+ return parts.join
405
475
  end
406
476
 
407
- # Fallback: construct from parts
408
- if item.docidentifier && !item.docidentifier.empty?
409
- docids = Array(item.docidentifier)
410
- # Skip bracket-number identifiers (e.g. "[1]") and iso-reference/URN variants;
411
- # render only the primary doc identifier
412
- primary = docids.find do |di|
413
- val = extract_text_value(di).to_s
414
- val.match?(/\A\[?\d+\]?\z/) ? false : !val.match?(/\A(?:iso-reference|URN)\s/)
415
- end
416
- if primary
417
- id_val = extract_text_value(primary)
418
- @output << "<span class=\"ref-doc-number\">#{escape_html(id_val)}</span>" unless id_val.to_s.empty?
419
- end
477
+ rendered_pubid = render_pubid_identifier(item)
478
+ unless rendered_pubid
479
+ render_docidentifier_fallback_into(parts, item)
420
480
  end
421
481
 
422
482
  if item.date && !item.date.empty?
@@ -424,7 +484,10 @@ with_subsections: false, with_terms: false)
424
484
  date_on = date.is_a?(Metanorma::Document::Relaton::BibliographicDate) ? date.on : nil
425
485
  date_val = extract_text_value(date_on || safe_attr(date, :text))
426
486
  if date_val && !date_val.to_s.empty?
427
- @output << ":<span class=\"ref-year\">#{escape_html(date_val.to_s)}</span>"
487
+ parts << render_liquid("_ref_date.html.liquid", {
488
+ "prefix" => ":",
489
+ "year" => escape_html(date_val.to_s),
490
+ })
428
491
  end
429
492
  end
430
493
  end
@@ -435,11 +498,51 @@ with_subsections: false, with_terms: false)
435
498
  safe_attr(t, :type) == "main"
436
499
  end || titles.first
437
500
  if main_title
438
- @output << ", <i>"
439
- render_mixed_inline(main_title)
440
- @output << "</i>"
501
+ title_content = render_mixed_inline(main_title)
502
+ parts << render_liquid("_ref_title.html.liquid", {
503
+ "content" => title_content,
504
+ })
441
505
  end
442
506
  end
507
+ parts.join
508
+ end
509
+
510
+ def render_pubid_identifier(item)
511
+ return nil unless item.docidentifier && !item.docidentifier.empty?
512
+
513
+ docids = Array(item.docidentifier)
514
+ primary = docids.find do |di|
515
+ val = extract_text_value(di).to_s
516
+ val.match?(/\A\[?\d+\]?\z/) ? false : !val.match?(/\A(?:iso-reference|URN)\s/)
517
+ end
518
+ return nil unless primary
519
+
520
+ id_string = extract_text_value(primary).to_s
521
+ return nil if id_string.empty?
522
+
523
+ identifier = parse_pubid(id_string)
524
+ return nil unless identifier
525
+
526
+ pubid_to_html(identifier)
527
+ end
528
+
529
+ def render_docidentifier_fallback_into(parts, item)
530
+ return unless item.docidentifier && !item.docidentifier.empty?
531
+
532
+ docids = Array(item.docidentifier)
533
+ primary = docids.find do |di|
534
+ val = extract_text_value(di).to_s
535
+ val.match?(/\A\[?\d+\]?\z/) ? false : !val.match?(/\A(?:iso-reference|URN)\s/)
536
+ end
537
+ return unless primary
538
+
539
+ id_val = extract_text_value(primary)
540
+ unless id_val.to_s.empty?
541
+ parts << render_liquid("_inline_span.html.liquid", {
542
+ "attrs" => " class=\"ref-doc-number\"",
543
+ "content" => escape_html(id_val),
544
+ })
545
+ end
443
546
  end
444
547
 
445
548
  # --- Standard section helpers ---
@@ -447,9 +550,10 @@ with_subsections: false, with_terms: false)
447
550
  def render_standard_title(section, level, default_class: "")
448
551
  title_element = safe_attr(section,
449
552
  :fmt_title) || safe_attr(section, :title)
450
- return unless title_element
553
+ return nil unless title_element
451
554
 
452
555
  section_id = safe_attr(section, :id)
556
+ title_content = render_mixed_inline(title_element)
453
557
  title_text = extract_plain_text(title_element)
454
558
  register_toc_entry(id: section_id, level: level, text: title_text)
455
559
 
@@ -458,58 +562,67 @@ with_subsections: false, with_terms: false)
458
562
 
459
563
  h = "h#{[[level, 6].min, 1].max}"
460
564
  title_class = default_class.empty? ? "" : " class=\"#{default_class}\""
461
- @output << "<#{h}#{title_class}>"
462
- render_mixed_inline(title_element)
463
- @output << "</#{h}>"
565
+ render_liquid("_heading.html.liquid", {
566
+ "tag" => h,
567
+ "class_attr" => title_class,
568
+ "content" => title_content,
569
+ })
464
570
  end
465
571
 
466
572
  def render_standard_section_blocks(section, level)
467
573
  if section.is_a?(Lutaml::Model::Serializable) && section.mixed?
574
+ parts = []
468
575
  section.each_mixed_content do |node|
469
576
  next if node.is_a?(String)
470
577
  next if is_title_element?(node, section)
471
578
 
472
- render(node, level: level + 1)
579
+ parts << (render(node, level: level + 1) || "")
473
580
  end
581
+ parts.join
474
582
  else
475
583
  render_section_block_collections(section, level)
476
584
  end
477
585
  end
478
586
 
479
587
  def render_section_block_collections(section, level)
480
- # Paragraphs
588
+ parts = []
481
589
  paragraphs = safe_attr(section, :paragraphs) || safe_attr(section, :p)
482
- Array(paragraphs).each { |p| render_paragraph(p) } if paragraphs
590
+ if paragraphs
591
+ Array(paragraphs).each do |p|
592
+ parts << (render_paragraph(p) || "")
593
+ end
594
+ end
483
595
 
484
- # Lists
485
596
  %i[unordered_lists ordered_lists definition_lists].each do |attr|
486
597
  values = safe_attr(section, attr)
487
- Array(values).each { |v| render(v, level: level + 1) } if values
598
+ if values
599
+ Array(values).each do |v|
600
+ parts << (render(v, level: level + 1) || "")
601
+ end
602
+ end
488
603
  end
489
604
 
490
- # Block elements
491
605
  %i[tables figures formulas examples notes admonitions sourcecode_blocks
492
606
  quote_blocks].each do |attr|
493
607
  values = safe_attr(section, attr)
494
- Array(values).each { |v| render(v, level: level + 1) } if values
608
+ if values
609
+ Array(values).each do |v|
610
+ parts << (render(v, level: level + 1) || "")
611
+ end
612
+ end
495
613
  end
614
+ parts.join
496
615
  end
497
616
 
498
617
  def render_subsections(section, level)
499
618
  clauses = safe_attr(section,
500
619
  :clause) || safe_attr(section, :subsections)
501
- Array(clauses).each { |cl| render(cl, level: level + 1) } if clauses
502
- end
503
-
504
- def is_title_element?(node, section)
505
- title = safe_attr(section, :title)
506
- return false unless title
620
+ return nil unless clauses
507
621
 
508
- node.equal?(title)
622
+ clauses.filter_map { |cl| render(cl, level: level + 1) }.join
509
623
  end
510
624
 
511
625
  def extract_termnote_label(note)
512
- # Presentation XML: <name>Note 1 to entry</name>
513
626
  names = safe_attr(note, :name)
514
627
  if names && !names.empty?
515
628
  name = names.is_a?(Array) ? names.first : names
@@ -517,7 +630,6 @@ with_subsections: false, with_terms: false)
517
630
  return text unless text.to_s.strip.empty?
518
631
  end
519
632
 
520
- # Fallback: autonum attribute
521
633
  autonum = safe_attr(note, :autonum)
522
634
  return "Note #{autonum} to entry" if autonum && !autonum.to_s.empty?
523
635