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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +72 -81
- data/CLAUDE.md +26 -0
- data/README.adoc +144 -0
- data/Rakefile +14 -2
- data/TODO.finalize/01-split-multi-class-files.md +8 -0
- data/TODO.finalize/02-remove-data-dist-iso-document-frontend.md +6 -0
- data/TODO.finalize/03-align-frontend-iife-and-gha-build.md +11 -0
- data/TODO.finalize/04-theme-per-flavor-config-driven.md +9 -0
- data/TODO.finalize/05-migrate-all-html-to-liquid.md +16 -0
- data/TODO.finalize/06-integrate-pubid-library.md +16 -0
- data/TODO.finalize/07-replace-relaton-models-with-relaton-bib.md +39 -0
- data/TODO.finalize/08-refactor-renderer-architecture.md +25 -0
- data/TODO.finalize/09-eliminate-code-smells.md +14 -0
- data/TODO.finalize/10-comprehensive-spec-coverage.md +24 -0
- data/TODO.finalize/11-config-driven-flavor-metadata.md +20 -0
- data/TODO.finalize/12-add-frozen-string-literal.md +6 -0
- data/TODO.finalize/13-extract-composition-from-god-class.md +96 -0
- data/TODO.finalize/14-config-driven-section-ordering.md +31 -0
- data/TODO.finalize/15-theme-directories-per-flavor.md +46 -0
- data/TODO.finalize/16-theme-lutaml-model-schema.md +44 -0
- data/data/javascripts/components/mn-footnotes.js +68 -0
- data/data/javascripts/components/mn-lightbox.js +18 -2
- data/data/javascripts/components/mn-shortcuts.js +2 -0
- data/data/javascripts/core/mn-navigation.js +8 -0
- data/data/javascripts/core/mn-theme.js +9 -9
- data/data/logos/oiml-logo-full-dark.svg +1 -0
- data/data/logos/oiml-logo-full-light.svg +1 -0
- data/data/logos/oiml-logo-icon-dark.svg +1 -0
- data/data/logos/oiml-logo-icon-light.svg +1 -0
- data/data/logos/oiml-logo.svg +1 -0
- data/data/logos/pdfa-logo.svg +31 -94
- data/data/stylesheets/base/_layout.css +3 -0
- data/data/stylesheets/base/_reset.css +3 -3
- data/data/stylesheets/base/_typography.css +0 -5
- data/data/stylesheets/components/bibliography.css +3 -3
- data/data/stylesheets/components/cover.css +3 -1
- data/data/stylesheets/components/figure.css +13 -1
- data/data/stylesheets/components/footnote.css +65 -5
- data/data/stylesheets/components/formula.css +44 -0
- data/data/stylesheets/components/header.css +17 -2
- data/data/stylesheets/components/inline.css +1 -1
- data/data/stylesheets/components/table.css +15 -2
- data/data/stylesheets/components/term.css +0 -1
- data/data/themes/bipm.yaml +60 -0
- data/data/themes/cc.yaml +43 -0
- data/data/themes/icc.yaml +39 -0
- data/data/themes/iec.yaml +44 -0
- data/data/themes/ieee.yaml +43 -0
- data/data/themes/ietf.yaml +45 -0
- data/data/themes/iho.yaml +46 -0
- data/data/themes/iso.yaml +40 -0
- data/data/themes/itu.yaml +45 -0
- data/data/themes/ogc.yaml +51 -0
- data/data/themes/oiml.yaml +56 -0
- data/data/themes/pdfa.yaml +44 -0
- data/data/themes/ribose.yaml +43 -0
- data/exe/metanorma-document +11 -0
- data/frontend/e2e/sample-html.test.ts +256 -0
- data/frontend/index.html +12 -0
- data/frontend/package-lock.json +4725 -0
- data/frontend/package.json +32 -0
- data/frontend/postcss.config.js +6 -0
- data/frontend/src/App.vue +79 -0
- data/frontend/src/app.ts +21 -0
- data/frontend/src/components/AppSidebar.vue +77 -0
- data/frontend/src/components/DocumentReader.vue +180 -0
- data/frontend/src/components/MirrorRenderer.vue +23 -0
- data/frontend/src/components/ReadingProgressBar.vue +29 -0
- data/frontend/src/components/TextRenderer.vue +131 -0
- data/frontend/src/components/TocItem.vue +57 -0
- data/frontend/src/components/TopBar.vue +49 -0
- data/frontend/src/components/__tests__/MirrorRenderer.test.ts +178 -0
- data/frontend/src/components/blocks/AdmonitionBlock.vue +74 -0
- data/frontend/src/components/blocks/ClauseBlock.vue +53 -0
- data/frontend/src/components/blocks/ExampleBlock.vue +21 -0
- data/frontend/src/components/blocks/FallbackBlock.vue +18 -0
- data/frontend/src/components/blocks/FigureBlock.vue +26 -0
- data/frontend/src/components/blocks/FootnotesBlock.vue +25 -0
- data/frontend/src/components/blocks/ListBlocks.vue +62 -0
- data/frontend/src/components/blocks/NoteBlock.vue +21 -0
- data/frontend/src/components/blocks/ParagraphBlock.vue +25 -0
- data/frontend/src/components/blocks/QuoteBlock.vue +25 -0
- data/frontend/src/components/blocks/ReviewBlock.vue +39 -0
- data/frontend/src/components/blocks/SourcecodeBlock.vue +52 -0
- data/frontend/src/components/blocks/TableBlock.vue +48 -0
- data/frontend/src/components/blocks/index.ts +36 -0
- data/frontend/src/stores/__tests__/documentStore.fixture.test.ts +272 -0
- data/frontend/src/stores/__tests__/documentStore.test.ts +143 -0
- data/frontend/src/stores/__tests__/ebookStore.test.ts +145 -0
- data/frontend/src/stores/__tests__/uiStore.test.ts +42 -0
- data/frontend/src/stores/documentStore.ts +153 -0
- data/frontend/src/stores/ebookStore.ts +179 -0
- data/frontend/src/stores/uiStore.ts +32 -0
- data/frontend/src/styles.css +156 -0
- data/frontend/src/types/__tests__/rendering-contract.test.ts +172 -0
- data/frontend/src/types/rendering-contract.ts +209 -0
- data/frontend/src/utils/__tests__/typeMetadata.test.ts +102 -0
- data/frontend/src/utils/typeMetadata.ts +121 -0
- data/frontend/tailwind.config.js +18 -0
- data/frontend/test/fixtures/iso-document.json +271 -0
- data/frontend/tmp/debug_e2e.cjs +46 -0
- data/frontend/tsconfig.json +25 -0
- data/frontend/tsconfig.node.json +11 -0
- data/frontend/vite.config.ts +37 -0
- data/frontend/vitest.e2e.config.ts +9 -0
- data/lib/metanorma/bipm_document/metadata/bipm_bibliographic_item.rb +0 -14
- data/lib/metanorma/bipm_document/metadata/depiction_element.rb +18 -0
- data/lib/metanorma/bipm_document/metadata.rb +1 -1
- data/lib/metanorma/document/cli.rb +116 -0
- data/lib/metanorma/document/components/ancillary_blocks/callout_annotation.rb +27 -0
- data/lib/metanorma/document/components/ancillary_blocks/sourcecode_block.rb +0 -65
- data/lib/metanorma/document/components/ancillary_blocks/sourcecode_body.rb +18 -0
- data/lib/metanorma/document/components/ancillary_blocks.rb +4 -0
- data/lib/metanorma/document/components/bib_data/bib_data.rb +7 -0
- data/lib/metanorma/document/components/blocks/classification_value.rb +21 -0
- data/lib/metanorma/document/components/blocks/permission_model.rb +28 -0
- data/lib/metanorma/document/components/blocks/recommendation_model.rb +28 -0
- data/lib/metanorma/document/components/blocks/requirement_base.rb +30 -0
- data/lib/metanorma/document/components/blocks/requirement_classification.rb +20 -0
- data/lib/metanorma/document/components/blocks/requirement_description.rb +43 -0
- data/lib/metanorma/document/components/blocks/requirement_inherit.rb +24 -0
- data/lib/metanorma/document/components/blocks/requirement_model.rb +0 -131
- data/lib/metanorma/document/components/blocks.rb +8 -7
- data/lib/metanorma/document/components/id_elements/image.rb +2 -0
- data/lib/metanorma/document/components/inline/asciimath_element.rb +2 -0
- data/lib/metanorma/document/components/inline/attribution_element.rb +2 -0
- data/lib/metanorma/document/components/inline/bcp14_element.rb +2 -0
- data/lib/metanorma/document/components/inline/biblio_tag_element.rb +2 -0
- data/lib/metanorma/document/components/inline/br_element.rb +2 -0
- data/lib/metanorma/document/components/inline/comma_element.rb +2 -0
- data/lib/metanorma/document/components/inline/concept_element.rb +2 -0
- data/lib/metanorma/document/components/inline/display_text_element.rb +2 -0
- data/lib/metanorma/document/components/inline/em_raw_element.rb +2 -0
- data/lib/metanorma/document/components/inline/enum_comma_element.rb +2 -0
- data/lib/metanorma/document/components/inline/eref_element.rb +2 -0
- data/lib/metanorma/document/components/inline/fmt_admitted_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fmt_annotation_body_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fmt_annotation_end_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fmt_annotation_start_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fmt_concept_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fmt_definition_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fmt_fn_body_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fmt_fn_label_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fmt_footnote_container_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fmt_identifier_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fmt_name_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fmt_preferred_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fmt_sourcecode_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fmt_stem_element.rb +10 -0
- data/lib/metanorma/document/components/inline/fmt_termsource_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fmt_title_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fmt_xref_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fmt_xref_label_element.rb +4 -0
- data/lib/metanorma/document/components/inline/fn_element.rb +2 -0
- data/lib/metanorma/document/components/inline/link_element.rb +2 -0
- data/lib/metanorma/document/components/inline/localized_string_element.rb +2 -0
- data/lib/metanorma/document/components/inline/localized_strings_element.rb +2 -0
- data/lib/metanorma/document/components/inline/location_element.rb +24 -0
- data/lib/metanorma/document/components/inline/math_element.rb +2 -0
- data/lib/metanorma/document/components/inline/name_with_id_element.rb +4 -0
- data/lib/metanorma/document/components/inline/rendered_display.rb +23 -0
- data/lib/metanorma/document/components/inline/rendered_math_element.rb +21 -0
- data/lib/metanorma/document/components/inline/semantic_content.rb +51 -0
- data/lib/metanorma/document/components/inline/semantic_math_element.rb +20 -0
- data/lib/metanorma/document/components/inline/semx_child_element.rb +2 -0
- data/lib/metanorma/document/components/inline/semx_element.rb +12 -1
- data/lib/metanorma/document/components/inline/small_cap_element.rb +2 -0
- data/lib/metanorma/document/components/inline/span_element.rb +10 -15
- data/lib/metanorma/document/components/inline/stem_inline_element.rb +7 -1
- data/lib/metanorma/document/components/inline/strong_raw_element.rb +2 -0
- data/lib/metanorma/document/components/inline/sub_element.rb +2 -0
- data/lib/metanorma/document/components/inline/sup_element.rb +2 -0
- data/lib/metanorma/document/components/inline/tab_element.rb +2 -0
- data/lib/metanorma/document/components/inline/title_with_annotation_element.rb +4 -0
- data/lib/metanorma/document/components/inline/tt_element.rb +2 -0
- data/lib/metanorma/document/components/inline/variant_title_element.rb +2 -0
- data/lib/metanorma/document/components/inline/vocabulary.rb +184 -0
- data/lib/metanorma/document/components/inline/xref_element.rb +10 -0
- data/lib/metanorma/document/components/inline.rb +12 -0
- data/lib/metanorma/document/components/multi_paragraph/quote_author_element.rb +20 -0
- data/lib/metanorma/document/components/multi_paragraph/quote_block.rb +0 -29
- data/lib/metanorma/document/components/multi_paragraph.rb +2 -0
- data/lib/metanorma/document/components/reference_elements/source_element.rb +0 -31
- data/lib/metanorma/document/components/reference_elements/source_modification.rb +21 -0
- data/lib/metanorma/document/components/reference_elements/source_origin.rb +28 -0
- data/lib/metanorma/document/components/reference_elements.rb +2 -2
- data/lib/metanorma/document/components/tables/col_element.rb +18 -0
- data/lib/metanorma/document/components/tables/col_group_element.rb +18 -0
- data/lib/metanorma/document/components/tables/table_block.rb +0 -22
- data/lib/metanorma/document/components/tables/table_body_section.rb +16 -0
- data/lib/metanorma/document/components/tables/table_foot_section.rb +16 -0
- data/lib/metanorma/document/components/tables/table_head_section.rb +16 -0
- data/lib/metanorma/document/components/tables/table_section.rb +0 -25
- data/lib/metanorma/document/components/tables.rb +5 -3
- data/lib/metanorma/document/relaton/address.rb +0 -27
- data/lib/metanorma/document/relaton/bib_item_locality.rb +3 -12
- data/lib/metanorma/document/relaton/copyright_association.rb +0 -10
- data/lib/metanorma/document/relaton/copyright_owner.rb +16 -0
- data/lib/metanorma/document/relaton/edition.rb +4 -7
- data/lib/metanorma/document/relaton/formatted_address.rb +29 -0
- data/lib/metanorma/document/relaton/logo_element.rb +18 -0
- data/lib/metanorma/document/relaton/organization.rb +0 -14
- data/lib/metanorma/document/relaton/phone.rb +3 -9
- data/lib/metanorma/document/relaton/place_type.rb +0 -13
- data/lib/metanorma/document/relaton/price_type.rb +3 -9
- data/lib/metanorma/document/relaton/region_element.rb +18 -0
- data/lib/metanorma/document/relaton.rb +6 -1
- data/lib/metanorma/document/version.rb +1 -1
- data/lib/metanorma/document.rb +2 -0
- data/lib/metanorma/html/asset_pipeline.rb +1 -0
- data/lib/metanorma/html/base_renderer.rb +284 -1057
- data/lib/metanorma/html/bipm_renderer.rb +15 -144
- data/lib/metanorma/html/cc_renderer.rb +1 -45
- data/lib/metanorma/html/component/base.rb +2 -14
- data/lib/metanorma/html/component/index_section.rb +41 -43
- data/lib/metanorma/html/drops/admonition_drop.rb +1 -4
- data/lib/metanorma/html/drops/biblio_entry_drop.rb +50 -0
- data/lib/metanorma/html/drops/block_element_drop.rb +7 -2
- data/lib/metanorma/html/drops/example_drop.rb +1 -4
- data/lib/metanorma/html/drops/figure_drop.rb +31 -22
- data/lib/metanorma/html/drops/footnote_drop.rb +1 -1
- data/lib/metanorma/html/drops/formula_drop.rb +45 -16
- data/lib/metanorma/html/drops/note_drop.rb +9 -7
- data/lib/metanorma/html/drops/sourcecode_drop.rb +12 -5
- data/lib/metanorma/html/drops.rb +2 -0
- data/lib/metanorma/html/flavor.rb +45 -0
- data/lib/metanorma/html/flavor_registry.rb +49 -0
- data/lib/metanorma/html/generator.rb +108 -31
- data/lib/metanorma/html/icc_renderer.rb +0 -39
- data/lib/metanorma/html/iec_renderer.rb +1 -44
- data/lib/metanorma/html/ieee_renderer.rb +1 -45
- data/lib/metanorma/html/ietf_renderer.rb +1 -48
- data/lib/metanorma/html/iho_renderer.rb +1 -46
- data/lib/metanorma/html/iso_renderer.rb +290 -343
- data/lib/metanorma/html/itu_renderer.rb +1 -45
- data/lib/metanorma/html/ogc_renderer.rb +15 -76
- data/lib/metanorma/html/oiml_renderer.rb +18 -38
- data/lib/metanorma/html/pdfa_renderer.rb +0 -39
- data/lib/metanorma/html/renderer_delegation.rb +21 -0
- data/lib/metanorma/html/renderers/block_renderer.rb +372 -0
- data/lib/metanorma/html/renderers/element_order_traversal.rb +34 -0
- data/lib/metanorma/html/renderers/inline_renderer.rb +642 -0
- data/lib/metanorma/html/renderers/pubid_renderer.rb +47 -0
- data/lib/metanorma/html/renderers/section_renderer.rb +193 -0
- data/lib/metanorma/html/renderers.rb +10 -0
- data/lib/metanorma/html/ribose_renderer.rb +1 -45
- data/lib/metanorma/html/standard_renderer.rb +344 -230
- data/lib/metanorma/html/templates/_audio.html.liquid +1 -0
- data/lib/metanorma/html/templates/_biblio_entry.html.liquid +1 -0
- data/lib/metanorma/html/templates/_bipm_cover.html.liquid +27 -0
- data/lib/metanorma/html/templates/_boilerplate.html.liquid +1 -0
- data/lib/metanorma/html/templates/_bookmark.html.liquid +1 -0
- data/lib/metanorma/html/templates/_br.html.liquid +1 -0
- data/lib/metanorma/html/templates/_definition_list.html.liquid +6 -0
- data/lib/metanorma/html/templates/_element.html.liquid +1 -0
- data/lib/metanorma/html/templates/_fn_marker.html.liquid +1 -0
- data/lib/metanorma/html/templates/_footnotes.html.liquid +6 -6
- data/lib/metanorma/html/templates/_formula.html.liquid +1 -1
- data/lib/metanorma/html/templates/_header.html.liquid +1 -1
- data/lib/metanorma/html/templates/_heading.html.liquid +1 -0
- data/lib/metanorma/html/templates/_image.html.liquid +1 -0
- data/lib/metanorma/html/templates/_inline_span.html.liquid +1 -0
- data/lib/metanorma/html/templates/_iso_cover.html.liquid +1 -1
- data/lib/metanorma/html/templates/_link.html.liquid +1 -0
- data/lib/metanorma/html/templates/_list.html.liquid +3 -0
- data/lib/metanorma/html/templates/_list_item.html.liquid +1 -0
- data/lib/metanorma/html/templates/_main_content.html.liquid +1 -0
- data/lib/metanorma/html/templates/_math_container.html.liquid +1 -0
- data/lib/metanorma/html/templates/_paragraph.html.liquid +1 -0
- data/lib/metanorma/html/templates/_prefatory_section.html.liquid +1 -0
- data/lib/metanorma/html/templates/_pubid_identifier.html.liquid +1 -0
- data/lib/metanorma/html/templates/_quote.html.liquid +4 -0
- data/lib/metanorma/html/templates/_ref_date.html.liquid +1 -0
- data/lib/metanorma/html/templates/_ref_title.html.liquid +1 -0
- data/lib/metanorma/html/templates/_section.html.liquid +5 -0
- data/lib/metanorma/html/templates/_standard_cover.html.liquid +31 -0
- data/lib/metanorma/html/templates/_standard_doc_title.html.liquid +3 -0
- data/lib/metanorma/html/templates/_stem_span.html.liquid +1 -0
- data/lib/metanorma/html/templates/_table.html.liquid +9 -0
- data/lib/metanorma/html/templates/_term_domain.html.liquid +1 -0
- data/lib/metanorma/html/templates/_term_example.html.liquid +1 -0
- data/lib/metanorma/html/templates/_term_note.html.liquid +1 -0
- data/lib/metanorma/html/templates/_term_number.html.liquid +1 -0
- data/lib/metanorma/html/templates/_term_source.html.liquid +1 -0
- data/lib/metanorma/html/templates/_term_text_note.html.liquid +1 -0
- data/lib/metanorma/html/templates/_video.html.liquid +1 -0
- data/lib/metanorma/html/templates/_wrapped_preface.html.liquid +1 -0
- data/lib/metanorma/html/templates/document.html.liquid +4 -0
- data/lib/metanorma/html/theme.rb +180 -107
- data/lib/metanorma/html.rb +16 -0
- data/lib/metanorma/ietf_document/metadata/ietf_bib_data_extension_type.rb +0 -25
- data/lib/metanorma/ietf_document/metadata/pi_settings.rb +32 -0
- data/lib/metanorma/ietf_document/metadata.rb +2 -0
- data/lib/metanorma/iso_document/metadata/doctype_element.rb +20 -0
- data/lib/metanorma/iso_document/metadata/iso_bib_data_extension_type.rb +0 -48
- data/lib/metanorma/iso_document/metadata/iso_bibliographic_item.rb +0 -72
- data/lib/metanorma/iso_document/metadata/iso_document_status.rb +0 -30
- data/lib/metanorma/iso_document/metadata/iso_localized_title.rb +4 -1
- data/lib/metanorma/iso_document/metadata/language_element.rb +18 -0
- data/lib/metanorma/iso_document/metadata/project_number.rb +22 -0
- data/lib/metanorma/iso_document/metadata/script_element.rb +18 -0
- data/lib/metanorma/iso_document/metadata/stage_element.rb +26 -0
- data/lib/metanorma/iso_document/metadata/stagename_element.rb +18 -0
- data/lib/metanorma/iso_document/metadata/structured_identifier.rb +0 -17
- data/lib/metanorma/iso_document/metadata/title_collection.rb +1 -0
- data/lib/metanorma/iso_document/metadata.rb +9 -4
- data/lib/metanorma/iso_document/terms/iso_term.rb +0 -13
- data/lib/metanorma/iso_document/terms/term_domain_element.rb +18 -0
- data/lib/metanorma/iso_document/terms.rb +1 -0
- data/lib/metanorma/itu_document/metadata/itu_bib_data_extension_type.rb +0 -37
- data/lib/metanorma/itu_document/metadata/meeting_date.rb +18 -0
- data/lib/metanorma/itu_document/metadata/meeting_element.rb +18 -0
- data/lib/metanorma/itu_document/metadata/study_period.rb +18 -0
- data/lib/metanorma/itu_document/metadata.rb +3 -4
- data/lib/metanorma/mirror/default_registry.rb +170 -0
- data/lib/metanorma/mirror/handler_registry.rb +60 -0
- data/lib/metanorma/mirror/handler_result.rb +37 -0
- data/lib/metanorma/mirror/handlers/admonition.rb +18 -0
- data/lib/metanorma/mirror/handlers/example.rb +21 -0
- data/lib/metanorma/mirror/handlers/figure.rb +53 -0
- data/lib/metanorma/mirror/handlers/formula.rb +35 -0
- data/lib/metanorma/mirror/handlers/inline/catalog.rb +57 -0
- data/lib/metanorma/mirror/handlers/inline/rich_html_renderer.rb +159 -0
- data/lib/metanorma/mirror/handlers/inline/text_extractor.rb +116 -0
- data/lib/metanorma/mirror/handlers/inline.rb +226 -0
- data/lib/metanorma/mirror/handlers/list.rb +110 -0
- data/lib/metanorma/mirror/handlers/note.rb +26 -0
- data/lib/metanorma/mirror/handlers/paragraph.rb +19 -0
- data/lib/metanorma/mirror/handlers/quote.rb +16 -0
- data/lib/metanorma/mirror/handlers/review.rb +19 -0
- data/lib/metanorma/mirror/handlers/section.rb +149 -0
- data/lib/metanorma/mirror/handlers/sourcecode.rb +27 -0
- data/lib/metanorma/mirror/handlers/structural.rb +68 -0
- data/lib/metanorma/mirror/handlers/table.rb +81 -0
- data/lib/metanorma/mirror/handlers/term.rb +100 -0
- data/lib/metanorma/mirror/handlers.rb +60 -0
- data/lib/metanorma/mirror/id_strategy/positional.rb +166 -0
- data/lib/metanorma/mirror/id_strategy/preserve.rb +12 -0
- data/lib/metanorma/mirror/id_strategy.rb +34 -0
- data/lib/metanorma/mirror/math_util.rb +53 -0
- data/lib/metanorma/mirror/metadata.rb +37 -0
- data/lib/metanorma/mirror/model/container.rb +47 -0
- data/lib/metanorma/mirror/model/factory.rb +52 -0
- data/lib/metanorma/mirror/model/guide.rb +25 -0
- data/lib/metanorma/mirror/model/leaf.rb +17 -0
- data/lib/metanorma/mirror/model/mark.rb +53 -0
- data/lib/metanorma/mirror/model/node.rb +47 -0
- data/lib/metanorma/mirror/model/soft_break.rb +33 -0
- data/lib/metanorma/mirror/model/text.rb +34 -0
- data/lib/metanorma/mirror/model.rb +16 -0
- data/lib/metanorma/mirror/output/builder.rb +39 -0
- data/lib/metanorma/mirror/output/formats/base_format.rb +90 -0
- data/lib/metanorma/mirror/output/formats/inline_format.rb +81 -0
- data/lib/metanorma/mirror/output/formats.rb +53 -0
- data/lib/metanorma/mirror/output/html_renderer.rb +95 -0
- data/lib/metanorma/mirror/output/html_renderers/block_renderers.rb +178 -0
- data/lib/metanorma/mirror/output/html_renderers/inline_renderer.rb +61 -0
- data/lib/metanorma/mirror/output/html_renderers/list_renderers.rb +96 -0
- data/lib/metanorma/mirror/output/html_renderers/mark_renderers.rb +41 -0
- data/lib/metanorma/mirror/output/html_renderers/section_renderers.rb +129 -0
- data/lib/metanorma/mirror/output/html_renderers/structural_renderers.rb +80 -0
- data/lib/metanorma/mirror/output/html_renderers/table_renderers.rb +87 -0
- data/lib/metanorma/mirror/output/html_renderers.rb +76 -0
- data/lib/metanorma/mirror/output/pipeline.rb +92 -0
- data/lib/metanorma/mirror/output/pipeline_context.rb +22 -0
- data/lib/metanorma/mirror/output.rb +14 -0
- data/lib/metanorma/mirror/rewriter.rb +121 -0
- data/lib/metanorma/mirror/safe_attr.rb +16 -0
- data/lib/metanorma/mirror/serialization/json_serializer.rb +25 -0
- data/lib/metanorma/mirror/serialization/yaml_serializer.rb +20 -0
- data/lib/metanorma/mirror/serialization.rb +10 -0
- data/lib/metanorma/mirror/transformer.rb +110 -0
- data/lib/metanorma/mirror.rb +31 -0
- data/lib/metanorma/standard_document/annotation.rb +37 -0
- data/lib/metanorma/standard_document/block_attributes.rb +15 -2
- data/lib/metanorma/standard_document/blocks/block_source.rb +0 -23
- data/lib/metanorma/standard_document/blocks/source_origin_element.rb +23 -0
- data/lib/metanorma/standard_document/blocks.rb +1 -1
- data/lib/metanorma/standard_document/terms/term_expression.rb +0 -31
- data/lib/metanorma/standard_document/terms/term_name_element.rb +29 -0
- data/lib/metanorma/standard_document/terms.rb +1 -0
- data/script/render_both.rb +18 -0
- metadata +272 -13
- data/.github/workflows/rake.yml +0 -21
- data/.github/workflows/release.yml +0 -25
- data/.github/workflows/roundtrip-samples.yml +0 -132
- data/.gitignore +0 -19
- data/.rspec +0 -3
- data/.rubocop.yml +0 -22
- data/Gemfile +0 -28
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/metanorma-document.gemspec +0 -35
|
@@ -6,15 +6,18 @@ require "cgi"
|
|
|
6
6
|
|
|
7
7
|
module Metanorma
|
|
8
8
|
module Html
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
module Renderers
|
|
10
|
+
autoload :InlineRenderer, "metanorma/html/renderers/inline_renderer"
|
|
11
|
+
autoload :BlockRenderer, "metanorma/html/renderers/block_renderer"
|
|
12
|
+
autoload :SectionRenderer, "metanorma/html/renderers/section_renderer"
|
|
13
|
+
autoload :PubidRenderer, "metanorma/html/renderers/pubid_renderer"
|
|
14
|
+
autoload :ElementOrderTraversal,
|
|
15
|
+
"metanorma/html/renderers/element_order_traversal"
|
|
16
|
+
end
|
|
17
|
+
|
|
13
18
|
class BaseRenderer
|
|
14
19
|
LOGO_DIR = File.expand_path("../../../data/logos", __dir__)
|
|
15
20
|
|
|
16
|
-
# HTML-specific class names for inline spans, keyed by the XML span role.
|
|
17
|
-
# The XML class_attr is INPUT only — we never emit it in HTML.
|
|
18
21
|
SPAN_ROLE_CLASSES = {
|
|
19
22
|
"boldtitle" => "title-text",
|
|
20
23
|
"nonboldtitle" => "subtitle-text",
|
|
@@ -44,8 +47,6 @@ module Metanorma
|
|
|
44
47
|
|
|
45
48
|
METANORMA_LOGO = "metanorma-logo.svg"
|
|
46
49
|
|
|
47
|
-
# Type-to-method registry for OCP dispatch.
|
|
48
|
-
# Each subclass gets its own hash; lookup traverses ancestors.
|
|
49
50
|
class << self
|
|
50
51
|
def render_registry
|
|
51
52
|
@render_registry ||= {}
|
|
@@ -64,8 +65,12 @@ module Metanorma
|
|
|
64
65
|
end
|
|
65
66
|
end
|
|
66
67
|
|
|
68
|
+
attr_reader :inline_renderer, :block_renderer, :section_renderer,
|
|
69
|
+
:pubid_renderer, :index_term_collector, :footnote_collector
|
|
70
|
+
private :inline_renderer, :block_renderer, :section_renderer,
|
|
71
|
+
:pubid_renderer
|
|
72
|
+
|
|
67
73
|
def initialize
|
|
68
|
-
@output = +""
|
|
69
74
|
@toc_entries = []
|
|
70
75
|
@figure_entries = []
|
|
71
76
|
@table_entries = []
|
|
@@ -73,86 +78,93 @@ module Metanorma
|
|
|
73
78
|
@footnote_collector = Component::FootnoteCollector.new
|
|
74
79
|
@current_section_id = nil
|
|
75
80
|
@current_section_number = nil
|
|
76
|
-
end
|
|
77
81
|
|
|
78
|
-
|
|
82
|
+
@inline_renderer = Renderers::InlineRenderer.new(self)
|
|
83
|
+
@block_renderer = Renderers::BlockRenderer.new(self)
|
|
84
|
+
@section_renderer = Renderers::SectionRenderer.new(self)
|
|
85
|
+
@pubid_renderer = Renderers::PubidRenderer.new(self)
|
|
86
|
+
end
|
|
79
87
|
|
|
80
|
-
# Facade object for Drops to call renderer methods without exposing
|
|
81
|
-
# the full private interface. Delegates via method_missing with an
|
|
82
|
-
# explicit allowlist — adding new delegations only requires updating
|
|
83
|
-
# DELEGATED_METHODS, not writing a new one-liner.
|
|
84
88
|
class RendererContext
|
|
85
|
-
|
|
86
|
-
safe_attr escape_html extract_block_label extract_plain_text
|
|
87
|
-
capture_output render_paragraph render_mixed_inline
|
|
88
|
-
render_inline_element render_unordered_list render_ordered_list
|
|
89
|
-
render_definition_list render_sourcecode render_table
|
|
90
|
-
render_figure render_quote render_formula render_note
|
|
91
|
-
render_image render_stem_content register_figure_entry
|
|
92
|
-
render_liquid render_block_children
|
|
93
|
-
].freeze
|
|
89
|
+
include RendererDelegation
|
|
94
90
|
|
|
95
91
|
def initialize(renderer)
|
|
96
92
|
@renderer = renderer
|
|
97
93
|
end
|
|
98
94
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
95
|
+
def render_paragraph(...) = @renderer.render_paragraph(...)
|
|
96
|
+
def render_inline_element(...)= @renderer.render_inline_element(...)
|
|
97
|
+
def render_unordered_list(...)= @renderer.render_unordered_list(...)
|
|
98
|
+
def render_ordered_list(...)= @renderer.render_ordered_list(...)
|
|
99
|
+
def render_definition_list(...)= @renderer.render_definition_list(...)
|
|
100
|
+
def render_sourcecode(...) = @renderer.render_sourcecode(...)
|
|
101
|
+
def render_table(...) = @renderer.render_table(...)
|
|
102
|
+
def render_figure(...) = @renderer.render_figure(...)
|
|
103
|
+
def render_quote(...) = @renderer.render_quote(...)
|
|
104
|
+
def render_formula(...) = @renderer.render_formula(...)
|
|
105
|
+
def render_note(...) = @renderer.render_note(...)
|
|
106
|
+
def render_image(...) = @renderer.render_image(...)
|
|
107
|
+
def render_stem_content(...)= @renderer.render_stem_content(...)
|
|
108
|
+
def register_figure_entry(...)= @renderer.register_figure_entry(...)
|
|
109
|
+
def render_note_children(...) = @renderer.render_note_children(...)
|
|
110
|
+
def render_simple_children(...) = @renderer.render_simple_children(...)
|
|
111
|
+
def render_full_block_children(...) = @renderer.render_full_block_children(...)
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
def renderer_context
|
|
115
115
|
@renderer_context ||= RendererContext.new(self)
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
def to_html
|
|
119
|
-
@output
|
|
120
|
-
end
|
|
121
|
-
|
|
122
118
|
def toc_entries
|
|
123
119
|
@toc_entries
|
|
124
120
|
end
|
|
125
121
|
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
attr_writer :document, :theme
|
|
123
|
+
|
|
124
|
+
def generate_full_document(document, **)
|
|
128
125
|
@document = document
|
|
129
126
|
validate_presentation_xml!
|
|
130
127
|
|
|
131
|
-
|
|
132
|
-
render(@document)
|
|
133
|
-
body = @output
|
|
128
|
+
body = render(@document) || ""
|
|
134
129
|
|
|
135
130
|
assemble_document(body)
|
|
136
131
|
end
|
|
137
132
|
|
|
138
|
-
# --- Flavor configuration hooks
|
|
133
|
+
# --- Flavor configuration hooks ---
|
|
139
134
|
|
|
140
135
|
def theme
|
|
141
|
-
@theme ||=
|
|
136
|
+
@theme ||= resolve_theme
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def resolve_theme
|
|
140
|
+
flavor = flavor_name
|
|
141
|
+
flavor ? Theme.load(flavor) : Theme.new
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def flavor_name
|
|
145
|
+
return nil unless defined?(@document) && @document
|
|
146
|
+
|
|
147
|
+
Metanorma::Html::Generator.flavors.name_for(@document.class)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Pubid module for the current document's flavor, or nil if the
|
|
151
|
+
# flavor has no Pubid support. Queries the shared FlavorRegistry.
|
|
152
|
+
def pubid_module
|
|
153
|
+
return nil unless defined?(@document) && @document
|
|
154
|
+
|
|
155
|
+
Metanorma::Html::Generator.flavors.pubid_module_for(@document.class)
|
|
142
156
|
end
|
|
143
157
|
|
|
144
158
|
def flavor_publishers(_doc_id)
|
|
145
|
-
|
|
159
|
+
theme.publishers
|
|
146
160
|
end
|
|
147
161
|
|
|
148
162
|
def flavor_publisher_name
|
|
149
|
-
|
|
150
|
-
pubs.empty? ? nil : pubs.join("/")
|
|
163
|
+
theme.publisher_name
|
|
151
164
|
end
|
|
152
165
|
|
|
153
|
-
# Map of publisher name => logo filename. Override in flavor renderers.
|
|
154
166
|
def publisher_logo_map
|
|
155
|
-
|
|
167
|
+
theme.logos_light
|
|
156
168
|
end
|
|
157
169
|
|
|
158
170
|
def flavor_font_url
|
|
@@ -165,7 +177,7 @@ module Metanorma
|
|
|
165
177
|
TEMPLATE_CACHE_MUTEX = Mutex.new
|
|
166
178
|
|
|
167
179
|
def render_liquid(template_name, assigns)
|
|
168
|
-
template_path =
|
|
180
|
+
template_path = theme.resolve_template(template_name)
|
|
169
181
|
template = TEMPLATE_CACHE_MUTEX.synchronize do
|
|
170
182
|
TEMPLATE_CACHE[template_path] ||= Liquid::Template.parse(File.read(template_path))
|
|
171
183
|
end
|
|
@@ -211,7 +223,7 @@ module Metanorma
|
|
|
211
223
|
end
|
|
212
224
|
|
|
213
225
|
def header_title_text
|
|
214
|
-
raw = html_title.to_s.split(" — ").first.to_s
|
|
226
|
+
raw = html_title.to_s.split(" — ").first.to_s
|
|
215
227
|
raw.length > 60 ? "#{raw[0, 57]}..." : raw
|
|
216
228
|
end
|
|
217
229
|
|
|
@@ -220,7 +232,7 @@ module Metanorma
|
|
|
220
232
|
logo_map = publisher_logo_map
|
|
221
233
|
return "" if publishers.empty? && logo_map.empty?
|
|
222
234
|
|
|
223
|
-
|
|
235
|
+
dark_logo_map = theme.logos_dark
|
|
224
236
|
display_pubs = publishers.empty? ? logo_map.keys : publishers
|
|
225
237
|
|
|
226
238
|
display_pubs.filter_map do |pub|
|
|
@@ -230,18 +242,28 @@ module Metanorma
|
|
|
230
242
|
svg = load_logo_svg(filename, height: 26)
|
|
231
243
|
next unless svg
|
|
232
244
|
|
|
233
|
-
"<span class=\"brand-logo\" aria-label=\"#{pub} logo\">#{svg}</span>"
|
|
245
|
+
light_span = "<span class=\"brand-logo brand-logo-light\" aria-label=\"#{pub} logo\">#{svg}</span>"
|
|
246
|
+
|
|
247
|
+
dark_span = ""
|
|
248
|
+
dark_filename = dark_logo_map[pub]
|
|
249
|
+
if dark_filename
|
|
250
|
+
dark_svg = load_logo_svg(dark_filename, height: 26)
|
|
251
|
+
if dark_svg
|
|
252
|
+
dark_span = "<span class=\"brand-logo brand-logo-dark\" aria-label=\"#{pub} logo\">#{dark_svg}</span>"
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
"#{light_span}\n#{dark_span}"
|
|
234
257
|
end.join("\n")
|
|
235
258
|
end
|
|
236
259
|
|
|
237
260
|
def load_logo_svg(filename, height: 32)
|
|
238
|
-
path = File.join(LOGO_DIR, filename)
|
|
261
|
+
path = theme.resolve_asset(filename) || File.join(LOGO_DIR, filename)
|
|
239
262
|
return nil unless File.exist?(path)
|
|
240
263
|
|
|
241
264
|
svg = File.read(path)
|
|
242
265
|
svg = svg.sub(/\A<\?xml[^?]*\?>\s*/, "")
|
|
243
266
|
svg = svg.sub(/\A\s*<!--.*?-->\s*/m, "")
|
|
244
|
-
svg = svg.sub(/<path[^>]*style="fill:#e3000f[^"]*"[^>]*\/>/, "")
|
|
245
267
|
svg = svg.sub(/<svg\s/, '<svg class="header-logo" ')
|
|
246
268
|
svg = if svg.match?(/<svg[^>]*\sheight="[^"]*"/)
|
|
247
269
|
svg.sub(/(<svg[^>]*?)(\sheight="[^"]*")/,
|
|
@@ -297,7 +319,10 @@ module Metanorma
|
|
|
297
319
|
def build_styles
|
|
298
320
|
pipeline = AssetPipeline.new
|
|
299
321
|
css = pipeline.compile_css(flavor_css: flavor_css_module)
|
|
300
|
-
|
|
322
|
+
parts = [theme.to_css_root, css, theme.to_css_extras]
|
|
323
|
+
custom_css_path = theme.theme_css_path
|
|
324
|
+
parts << File.read(custom_css_path) if custom_css_path
|
|
325
|
+
parts.join("\n")
|
|
301
326
|
end
|
|
302
327
|
|
|
303
328
|
# --- Validation ---
|
|
@@ -316,12 +341,11 @@ module Metanorma
|
|
|
316
341
|
return false unless node
|
|
317
342
|
return false if node.is_a?(String)
|
|
318
343
|
|
|
319
|
-
if node.is_a?(Metanorma::Document::Root) && node.type == "presentation"
|
|
320
|
-
return true
|
|
321
|
-
end
|
|
322
|
-
|
|
323
344
|
if node.is_a?(Lutaml::Model::Serializable)
|
|
324
345
|
node_attrs = node.class.attributes
|
|
346
|
+
if node_attrs.key?(:type) && node.type == "presentation"
|
|
347
|
+
return true
|
|
348
|
+
end
|
|
325
349
|
if node_attrs.key?(:fmt_title) && node.fmt_title
|
|
326
350
|
return true
|
|
327
351
|
end
|
|
@@ -363,16 +387,21 @@ module Metanorma
|
|
|
363
387
|
end
|
|
364
388
|
|
|
365
389
|
def html_title
|
|
366
|
-
|
|
367
|
-
|
|
390
|
+
extract_display_title(@document.bibdata) || "Document"
|
|
391
|
+
end
|
|
368
392
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
393
|
+
def extract_display_title(bibdata)
|
|
394
|
+
return nil unless bibdata
|
|
395
|
+
|
|
396
|
+
title = bibdata.title_for("en") if bibdata.is_a?(Metanorma::Document::Components::BibData::BibData)
|
|
397
|
+
return title.to_s if title && !title.to_s.empty?
|
|
398
|
+
|
|
399
|
+
titles = safe_attr(bibdata, :title)
|
|
400
|
+
return nil unless titles && !titles.is_a?(String) && !titles.empty?
|
|
401
|
+
|
|
402
|
+
en = titles.find { |t| safe_attr(t, :language) == "en" }
|
|
403
|
+
found = en || titles.first
|
|
404
|
+
extract_text_value(found).to_s
|
|
376
405
|
end
|
|
377
406
|
|
|
378
407
|
def extract_primary_doc_id
|
|
@@ -393,7 +422,7 @@ module Metanorma
|
|
|
393
422
|
text.strip.empty? ? nil : text.strip
|
|
394
423
|
end
|
|
395
424
|
|
|
396
|
-
# ---
|
|
425
|
+
# --- Registration helpers ---
|
|
397
426
|
|
|
398
427
|
def register_toc_entry(id:, level:, text:)
|
|
399
428
|
@toc_entries << { id: id, level: level, text: text }
|
|
@@ -403,12 +432,14 @@ module Metanorma
|
|
|
403
432
|
@figure_entries << { id: id, text: text }
|
|
404
433
|
end
|
|
405
434
|
|
|
435
|
+
def figure_entries
|
|
436
|
+
@figure_entries
|
|
437
|
+
end
|
|
438
|
+
|
|
406
439
|
def register_table_entry(id:, text:)
|
|
407
440
|
@table_entries << { id: id, text: text }
|
|
408
441
|
end
|
|
409
442
|
|
|
410
|
-
attr_reader :index_term_collector, :footnote_collector
|
|
411
|
-
|
|
412
443
|
def extract_plain_text(node)
|
|
413
444
|
return node.to_s if node.is_a?(String)
|
|
414
445
|
return extract_text_value(node).to_s unless node.is_a?(Lutaml::Model::Serializable)
|
|
@@ -417,12 +448,8 @@ module Metanorma
|
|
|
417
448
|
xml_mapping = node.class.mappings_for(:xml, node.lutaml_register)
|
|
418
449
|
|
|
419
450
|
if node.element_order.is_a?(Array) && xml_mapping
|
|
420
|
-
element_to_attr =
|
|
421
|
-
|
|
422
|
-
Array(rule_or_array).each do |rule|
|
|
423
|
-
element_to_attr[rule.name.to_s] = rule.to
|
|
424
|
-
end
|
|
425
|
-
end
|
|
451
|
+
element_to_attr =
|
|
452
|
+
Renderers::ElementOrderTraversal.element_to_attr_map(xml_mapping)
|
|
426
453
|
|
|
427
454
|
indices = Hash.new(0)
|
|
428
455
|
node.element_order.each do |el|
|
|
@@ -431,38 +458,39 @@ module Metanorma
|
|
|
431
458
|
if el.text?
|
|
432
459
|
parts << el.text_content.to_s
|
|
433
460
|
elsif el.name == "tab"
|
|
434
|
-
parts << "
|
|
461
|
+
parts << " "
|
|
435
462
|
elsif el.name == "br"
|
|
436
463
|
parts << " "
|
|
437
464
|
elsif el.element?
|
|
438
465
|
attr_name = element_to_attr[el.name]
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
466
|
+
if attr_name
|
|
467
|
+
coll = node.public_send(attr_name)
|
|
468
|
+
obj = if coll.is_a?(Array)
|
|
469
|
+
idx = indices[attr_name]
|
|
470
|
+
indices[attr_name] += 1
|
|
471
|
+
coll[idx]
|
|
472
|
+
else
|
|
473
|
+
coll
|
|
474
|
+
end
|
|
475
|
+
text = extract_plain_text(obj)
|
|
476
|
+
parts << (text.empty? ? " " : text)
|
|
477
|
+
elsif el.name == "span"
|
|
478
|
+
parts << " "
|
|
479
|
+
end
|
|
450
480
|
end
|
|
451
481
|
end
|
|
452
482
|
end
|
|
453
483
|
|
|
454
|
-
# Fallback: try .text
|
|
455
484
|
if parts.join.strip.empty?
|
|
456
485
|
t = safe_attr(node, :text)
|
|
457
486
|
parts << (t.is_a?(Array) ? t.join : t.to_s) if t
|
|
458
487
|
end
|
|
459
488
|
|
|
460
|
-
parts.join.strip.gsub("
|
|
489
|
+
parts.join.strip.gsub(" ", " ")
|
|
461
490
|
end
|
|
462
491
|
|
|
463
|
-
# Dispatch
|
|
464
|
-
|
|
465
|
-
# parent registrations and can override them independently.
|
|
492
|
+
# --- Dispatch ---
|
|
493
|
+
|
|
466
494
|
def render(node, **)
|
|
467
495
|
return escape_html(node) if node.is_a?(String)
|
|
468
496
|
|
|
@@ -470,20 +498,18 @@ module Metanorma
|
|
|
470
498
|
method ? public_send(method, node, **) : ""
|
|
471
499
|
end
|
|
472
500
|
|
|
473
|
-
# Dispatch to the appropriate inline render method via type registry.
|
|
474
501
|
def render_inline_element(element, **)
|
|
475
|
-
|
|
476
|
-
return escape_html(element) if element.is_a?(String)
|
|
477
|
-
|
|
478
|
-
method = lookup_dispatch(element.class, :inline_registry)
|
|
479
|
-
if method
|
|
480
|
-
public_send(method, element)
|
|
481
|
-
elsif element.is_a?(Lutaml::Model::Serializable) && element.mixed?
|
|
482
|
-
render_mixed_inline(element)
|
|
483
|
-
end
|
|
502
|
+
@inline_renderer.render_inline_element(element)
|
|
484
503
|
end
|
|
485
504
|
|
|
486
|
-
|
|
505
|
+
def is_title_element?(node, section)
|
|
506
|
+
title = safe_attr(section, :title)
|
|
507
|
+
return false unless title
|
|
508
|
+
|
|
509
|
+
node.equal?(title)
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
# --- Type registrations ---
|
|
487
513
|
|
|
488
514
|
register_render Metanorma::Document::Components::Paragraphs::ParagraphBlock,
|
|
489
515
|
:render_paragraph
|
|
@@ -582,51 +608,40 @@ module Metanorma
|
|
|
582
608
|
:render_index
|
|
583
609
|
register_inline_render Metanorma::Document::Components::Blocks::NoteBlock,
|
|
584
610
|
:render_note_inline
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
:render_mixed_inline
|
|
620
|
-
register_inline_render Metanorma::Document::Components::Inline::FmtDefinitionElement,
|
|
621
|
-
:render_mixed_inline
|
|
622
|
-
register_inline_render Metanorma::Document::Components::Inline::FmtTermsourceElement,
|
|
623
|
-
:render_mixed_inline
|
|
624
|
-
register_inline_render Metanorma::Document::Components::Inline::FmtAdmittedElement,
|
|
625
|
-
:render_mixed_inline
|
|
626
|
-
register_inline_render Metanorma::Document::Components::Inline::FmtIdentifierElement,
|
|
627
|
-
:render_mixed_inline
|
|
628
|
-
register_inline_render Metanorma::Document::Components::Inline::FmtSourcecodeElement,
|
|
629
|
-
:render_mixed_inline
|
|
611
|
+
|
|
612
|
+
# Transparent inline wrappers: element classes whose render
|
|
613
|
+
# semantics are "iterate mixed_content and render each child".
|
|
614
|
+
# Grouped here so adding a new transparent wrapper is one entry,
|
|
615
|
+
# not another register_inline_render line. Classes with a more
|
|
616
|
+
# specific handler register themselves above.
|
|
617
|
+
TRANSPARENT_INLINE_WRAPPERS = [
|
|
618
|
+
Metanorma::Document::Components::Inline::FmtNameElement,
|
|
619
|
+
Metanorma::Document::Components::Inline::FmtTitleElement,
|
|
620
|
+
Metanorma::Document::Components::Inline::FmtXrefLabelElement,
|
|
621
|
+
Metanorma::Document::Components::Inline::FmtFnLabelElement,
|
|
622
|
+
Metanorma::Document::Components::Inline::FmtConceptElement,
|
|
623
|
+
Metanorma::Document::Components::Inline::FmtAnnotationStartElement,
|
|
624
|
+
Metanorma::Document::Components::Inline::FmtAnnotationEndElement,
|
|
625
|
+
Metanorma::Document::Components::Inline::FmtAnnotationBodyElement,
|
|
626
|
+
Metanorma::Document::Components::Inline::VariantTitleElement,
|
|
627
|
+
Metanorma::Document::Components::Inline::LocalizedStringElement,
|
|
628
|
+
Metanorma::Document::Components::Inline::TitleWithAnnotationElement,
|
|
629
|
+
Metanorma::Document::Components::Inline::BiblioTagElement,
|
|
630
|
+
Metanorma::Document::Components::Inline::NameWithIdElement,
|
|
631
|
+
Metanorma::Document::Components::Inline::DisplayTextElement,
|
|
632
|
+
Metanorma::Document::Components::Inline::FmtFootnoteContainerElement,
|
|
633
|
+
Metanorma::Document::Components::Inline::FmtFnBodyElement,
|
|
634
|
+
Metanorma::Document::Components::Inline::FmtPreferredElement,
|
|
635
|
+
Metanorma::Document::Components::Inline::FmtDefinitionElement,
|
|
636
|
+
Metanorma::Document::Components::Inline::FmtTermsourceElement,
|
|
637
|
+
Metanorma::Document::Components::Inline::FmtAdmittedElement,
|
|
638
|
+
Metanorma::Document::Components::Inline::FmtIdentifierElement,
|
|
639
|
+
Metanorma::Document::Components::Inline::FmtSourcecodeElement,
|
|
640
|
+
].freeze
|
|
641
|
+
|
|
642
|
+
TRANSPARENT_INLINE_WRAPPERS.each do |klass|
|
|
643
|
+
register_inline_render klass, :render_mixed_inline
|
|
644
|
+
end
|
|
630
645
|
|
|
631
646
|
def lookup_dispatch(type_class, registry_method)
|
|
632
647
|
self.class.ancestors.each do |ancestor|
|
|
@@ -647,941 +662,163 @@ module Metanorma
|
|
|
647
662
|
nil
|
|
648
663
|
end
|
|
649
664
|
|
|
650
|
-
# ---
|
|
651
|
-
|
|
652
|
-
def render_paragraph(p, **_opts)
|
|
653
|
-
attrs = element_attrs(id: safe_attr(p, :id),
|
|
654
|
-
style: alignment_style(safe_attr(
|
|
655
|
-
p, :alignment
|
|
656
|
-
)))
|
|
657
|
-
tag("p", attrs) { render_mixed_inline(p) }
|
|
658
|
-
end
|
|
659
|
-
|
|
660
|
-
def render_table(table, **_opts)
|
|
661
|
-
attrs = element_attrs(id: safe_attr(table, :id), class: "table-block")
|
|
662
|
-
table_id = safe_attr(table, :id)
|
|
663
|
-
name_el = safe_attr(table, :fmt_name) || safe_attr(table, :name)
|
|
664
|
-
if table_id && name_el
|
|
665
|
-
register_table_entry(id: table_id, text: extract_plain_text(name_el))
|
|
666
|
-
end
|
|
667
|
-
col_count = table_column_count(table)
|
|
668
|
-
@output << "<div class=\"table-scroll-wrapper\">"
|
|
669
|
-
tag("table", attrs) do
|
|
670
|
-
name_el = safe_attr(table, :fmt_name) || safe_attr(table, :name)
|
|
671
|
-
if name_el
|
|
672
|
-
@output << "<caption>"
|
|
673
|
-
render_inline_element(name_el)
|
|
674
|
-
@output << "</caption>"
|
|
675
|
-
end
|
|
676
|
-
@output << "<colgroup>" if table.colgroup
|
|
677
|
-
render_table_colgroup(table.colgroup) if table.colgroup
|
|
678
|
-
@output << "</colgroup>" if table.colgroup
|
|
679
|
-
render_table_section(table.thead, "thead") if table.thead
|
|
680
|
-
render_table_section(table.tbody, "tbody") if table.tbody
|
|
681
|
-
if table.tfoot || (table.note && !table.note.empty?)
|
|
682
|
-
@output << "<tfoot>"
|
|
683
|
-
render_table_section_rows(table.tfoot) if table.tfoot
|
|
684
|
-
if table.note && !table.note.empty?
|
|
685
|
-
@output << "<tr><td colspan=\"#{col_count}\" class=\"table-notes\">"
|
|
686
|
-
table.note.each { |n| render_note(n) }
|
|
687
|
-
@output << "</td></tr>"
|
|
688
|
-
end
|
|
689
|
-
@output << "</tfoot>"
|
|
690
|
-
end
|
|
691
|
-
end
|
|
692
|
-
@output << "</div>"
|
|
693
|
-
end
|
|
694
|
-
|
|
695
|
-
def table_column_count(table)
|
|
696
|
-
if table.colgroup&.col && !table.colgroup.col.empty?
|
|
697
|
-
return table.colgroup.col.size
|
|
698
|
-
end
|
|
699
|
-
|
|
700
|
-
# Walk all rows to find max column count, accounting for colspan
|
|
701
|
-
max_cols = 0
|
|
702
|
-
%i[thead tbody tfoot].each do |section|
|
|
703
|
-
sec = table.public_send(section)
|
|
704
|
-
next unless sec&.tr
|
|
705
|
-
|
|
706
|
-
sec.tr.each do |tr|
|
|
707
|
-
cols = 0
|
|
708
|
-
Array(tr.th).each do |th|
|
|
709
|
-
cols += th.colspan && th.colspan > 1 ? th.colspan : 1
|
|
710
|
-
end
|
|
711
|
-
Array(tr.td).each do |td|
|
|
712
|
-
cols += td.colspan && td.colspan > 1 ? td.colspan : 1
|
|
713
|
-
end
|
|
714
|
-
max_cols = cols if cols > max_cols
|
|
715
|
-
end
|
|
716
|
-
end
|
|
717
|
-
max_cols.positive? ? max_cols : 1
|
|
718
|
-
end
|
|
719
|
-
|
|
720
|
-
def render_table_colgroup(colgroup)
|
|
721
|
-
colgroup.col&.each do |col|
|
|
722
|
-
attrs = element_attrs(style: col.width ? "width: #{col.width}" : nil)
|
|
723
|
-
@output << "<col#{attrs}>"
|
|
724
|
-
end
|
|
725
|
-
end
|
|
726
|
-
|
|
727
|
-
def render_table_section(section, tag_name)
|
|
728
|
-
@output << "<#{tag_name}>"
|
|
729
|
-
render_table_section_rows(section)
|
|
730
|
-
@output << "</#{tag_name}>"
|
|
731
|
-
end
|
|
732
|
-
|
|
733
|
-
def render_table_section_rows(section)
|
|
734
|
-
section.tr&.each do |tr|
|
|
735
|
-
@output << "<tr>"
|
|
736
|
-
walked = walk_ordered(tr) do |type, obj|
|
|
737
|
-
next unless type == :element
|
|
738
|
-
|
|
739
|
-
render_table_cell(obj)
|
|
740
|
-
end
|
|
741
|
-
unless walked
|
|
742
|
-
Array(tr.th).each { |th| render_table_cell(th, "th") }
|
|
743
|
-
Array(tr.td).each { |td| render_table_cell(td, "td") }
|
|
744
|
-
end
|
|
745
|
-
@output << "</tr>"
|
|
746
|
-
end
|
|
747
|
-
end
|
|
748
|
-
|
|
749
|
-
def render_unordered_list(ul, **_opts)
|
|
750
|
-
attrs = element_attrs(id: safe_attr(ul, :id))
|
|
751
|
-
tag("ul", attrs) do
|
|
752
|
-
ul.listitem&.each { |li| render_list_item(li) }
|
|
753
|
-
end
|
|
754
|
-
end
|
|
755
|
-
|
|
756
|
-
def render_table_cell(cell, force_tag = nil)
|
|
757
|
-
tag_name = force_tag || (cell.is_a?(Metanorma::Document::Components::Tables::HeaderTableCell) ? "th" : "td")
|
|
758
|
-
attrs = element_attrs(
|
|
759
|
-
colspan: safe_attr(cell, :colspan),
|
|
760
|
-
rowspan: safe_attr(cell, :rowspan),
|
|
761
|
-
align: safe_attr(cell, :alignment),
|
|
762
|
-
valign: safe_attr(cell, :vertical_alignment),
|
|
763
|
-
)
|
|
764
|
-
@output << "<#{tag_name}#{attrs}>"
|
|
765
|
-
render_cell_content(cell)
|
|
766
|
-
@output << "</#{tag_name}>"
|
|
767
|
-
end
|
|
768
|
-
|
|
769
|
-
def render_ordered_list(ol, **_opts)
|
|
770
|
-
attrs = element_attrs(id: safe_attr(ol, :id),
|
|
771
|
-
start: safe_attr(ol, :start), type: safe_attr(ol, :type_attr))
|
|
772
|
-
tag("ol", attrs) do
|
|
773
|
-
ol.listitem&.each { |li| render_list_item(li) }
|
|
774
|
-
end
|
|
775
|
-
end
|
|
776
|
-
|
|
777
|
-
def render_list_item(li)
|
|
778
|
-
li_id = safe_attr(li, :id)
|
|
779
|
-
attrs = li_id ? %( id="#{escape_html(li_id)}") : ""
|
|
780
|
-
@output << "<li#{attrs}>"
|
|
781
|
-
render_mixed_content_in_order(li)
|
|
782
|
-
@output << "</li>"
|
|
783
|
-
end
|
|
665
|
+
# --- Delegation to sub-renderers ---
|
|
784
666
|
|
|
785
|
-
#
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
node.each_mixed_content do |child|
|
|
789
|
-
case child
|
|
790
|
-
when String
|
|
791
|
-
@output << escape_html(child)
|
|
792
|
-
else
|
|
793
|
-
if block_element?(child)
|
|
794
|
-
render(child)
|
|
795
|
-
else
|
|
796
|
-
render_inline_element(child)
|
|
797
|
-
end
|
|
798
|
-
end
|
|
799
|
-
end
|
|
800
|
-
end
|
|
801
|
-
|
|
802
|
-
# Collect all renderable children from a node in document order,
|
|
803
|
-
# sorted by displayorder when available. Uses walk_ordered to traverse
|
|
804
|
-
# element_order, and also gathers typed attributes that may not appear
|
|
805
|
-
# in element_order (e.g. terms, definitions on section models).
|
|
806
|
-
def collect_ordered_children(section)
|
|
807
|
-
children = []
|
|
808
|
-
|
|
809
|
-
walk_ordered(section) do |type, obj|
|
|
810
|
-
next if %i[text tab].include?(type)
|
|
811
|
-
|
|
812
|
-
children << obj
|
|
813
|
-
end
|
|
814
|
-
|
|
815
|
-
# Gather typed attributes that may not appear in element_order
|
|
816
|
-
supplementary_attrs = %i[terms definitions]
|
|
817
|
-
supplementary_attrs.each do |attr|
|
|
818
|
-
val = safe_attr(section, attr)
|
|
819
|
-
next if val.nil?
|
|
820
|
-
|
|
821
|
-
Array(val).each do |v|
|
|
822
|
-
children << v unless children.include?(v)
|
|
823
|
-
end
|
|
824
|
-
end
|
|
825
|
-
|
|
826
|
-
children.compact!
|
|
827
|
-
sort_by_displayorder(children)
|
|
828
|
-
end
|
|
829
|
-
|
|
830
|
-
# Render children of a section in displayorder, skipping title elements.
|
|
831
|
-
def render_ordered_content(section, level = 1)
|
|
832
|
-
children = collect_ordered_children(section)
|
|
833
|
-
children.each do |node|
|
|
834
|
-
next if node.is_a?(String)
|
|
835
|
-
next if is_title_element?(node, section)
|
|
836
|
-
|
|
837
|
-
render(node, level: level + 1)
|
|
838
|
-
end
|
|
839
|
-
end
|
|
840
|
-
|
|
841
|
-
def sort_by_displayorder(children)
|
|
842
|
-
children.sort_by do |node|
|
|
843
|
-
order = if node.is_a?(Lutaml::Model::Serializable) &&
|
|
844
|
-
node.class.attributes.key?(:displayorder)
|
|
845
|
-
node.displayorder
|
|
846
|
-
end
|
|
847
|
-
order &&= order.to_i
|
|
848
|
-
order || Float::INFINITY
|
|
849
|
-
end
|
|
850
|
-
end
|
|
851
|
-
|
|
852
|
-
def render_definition_list(dl, **_opts)
|
|
853
|
-
attrs = element_attrs(id: safe_attr(dl, :id))
|
|
854
|
-
tag("dl", attrs) do
|
|
855
|
-
dl.dt&.each_with_index do |dt, i|
|
|
856
|
-
@output << "<dt>"
|
|
857
|
-
render_mixed_inline(dt)
|
|
858
|
-
@output << "</dt>"
|
|
859
|
-
dd = dl.dd&.[](i)
|
|
860
|
-
if dd
|
|
861
|
-
@output << "<dd>"
|
|
862
|
-
render_mixed_inline(dd)
|
|
863
|
-
@output << "</dd>"
|
|
864
|
-
end
|
|
865
|
-
end
|
|
866
|
-
end
|
|
867
|
-
end
|
|
868
|
-
|
|
869
|
-
def render_figure(figure, **_opts)
|
|
870
|
-
drop = Drops::FigureDrop.from_model(figure, renderer: renderer_context)
|
|
871
|
-
@output << render_liquid("_figure.html.liquid", { "block" => drop })
|
|
872
|
-
end
|
|
873
|
-
|
|
874
|
-
def render_image(image)
|
|
875
|
-
src_val = safe_attr(image, :src) || safe_attr(image, :source)
|
|
876
|
-
attrs = element_attrs(
|
|
877
|
-
id: safe_attr(image, :id),
|
|
878
|
-
src: src_val,
|
|
879
|
-
alt: safe_attr(image, :alt),
|
|
880
|
-
height: safe_attr(image, :height),
|
|
881
|
-
width: safe_attr(image, :width),
|
|
882
|
-
)
|
|
883
|
-
@output << "<img#{attrs} />"
|
|
884
|
-
end
|
|
885
|
-
|
|
886
|
-
def render_video(video)
|
|
887
|
-
attrs = element_attrs(
|
|
888
|
-
id: safe_attr(video, :id),
|
|
889
|
-
src: safe_attr(video, :src),
|
|
890
|
-
)
|
|
891
|
-
@output << "<video#{attrs} controls></video>"
|
|
892
|
-
end
|
|
893
|
-
|
|
894
|
-
def render_audio(audio)
|
|
895
|
-
attrs = element_attrs(
|
|
896
|
-
id: safe_attr(audio, :id),
|
|
897
|
-
src: safe_attr(audio, :src),
|
|
898
|
-
)
|
|
899
|
-
@output << "<audio#{attrs} controls></audio>"
|
|
900
|
-
end
|
|
901
|
-
|
|
902
|
-
def render_note(note, **_opts)
|
|
903
|
-
drop = Drops::NoteDrop.from_model(note, renderer: renderer_context)
|
|
904
|
-
@output << render_liquid("_note.html.liquid", { "block" => drop })
|
|
905
|
-
end
|
|
906
|
-
|
|
907
|
-
def render_example(example, **_opts)
|
|
908
|
-
drop = Drops::ExampleDrop.from_model(example,
|
|
909
|
-
renderer: renderer_context)
|
|
910
|
-
@output << render_liquid("_example.html.liquid", { "block" => drop })
|
|
911
|
-
end
|
|
912
|
-
|
|
913
|
-
def render_sourcecode(sc, **_opts)
|
|
914
|
-
drop = Drops::SourcecodeDrop.from_model(sc, renderer: renderer_context)
|
|
915
|
-
@output << render_liquid("_sourcecode.html.liquid", { "block" => drop })
|
|
916
|
-
end
|
|
917
|
-
|
|
918
|
-
def render_formula(formula, **_opts)
|
|
919
|
-
drop = Drops::FormulaDrop.from_model(formula,
|
|
920
|
-
renderer: renderer_context)
|
|
921
|
-
@output << render_liquid("_formula.html.liquid", { "block" => drop })
|
|
922
|
-
end
|
|
923
|
-
|
|
924
|
-
def render_quote(quote, **_opts)
|
|
925
|
-
attrs = element_attrs(id: safe_attr(quote, :id), class: "quote")
|
|
926
|
-
tag("blockquote", attrs) do
|
|
927
|
-
quote.paragraphs&.each { |para| render_paragraph(para) }
|
|
928
|
-
quote.ul&.each { |ul| render_unordered_list(ul) }
|
|
929
|
-
quote.ol&.each { |ol| render_ordered_list(ol) }
|
|
930
|
-
if quote.attribution
|
|
931
|
-
@output << "<footer>"
|
|
932
|
-
render_mixed_inline(quote.attribution)
|
|
933
|
-
@output << "</footer>"
|
|
934
|
-
end
|
|
935
|
-
end
|
|
936
|
-
end
|
|
937
|
-
|
|
938
|
-
def render_admonition(admonition, **_opts)
|
|
939
|
-
drop = Drops::AdmonitionDrop.from_model(admonition,
|
|
940
|
-
renderer: renderer_context)
|
|
941
|
-
@output << render_liquid("_admonition.html.liquid", { "block" => drop })
|
|
942
|
-
end
|
|
943
|
-
|
|
944
|
-
def render_bookmark(bookmark, **_opts)
|
|
945
|
-
@output << %(<a id="#{escape_html(safe_attr(bookmark, :id).to_s)}"></a>)
|
|
946
|
-
end
|
|
947
|
-
|
|
948
|
-
# Renders the typed child collections of a block element (paragraphs,
|
|
949
|
-
# lists, nested blocks) in a standard order. Used by Drops and section
|
|
950
|
-
# rendering to avoid duplicating the enumeration in each consumer.
|
|
951
|
-
#
|
|
952
|
-
# +children+ is a Hash mapping attr names to render-method symbols:
|
|
953
|
-
# { paragraphs: :render_paragraph, ul: :render_unordered_list, ... }
|
|
954
|
-
#
|
|
955
|
-
# Each key is sent to +model+ via safe_attr; non-nil results are
|
|
956
|
-
# dispatched to the corresponding render method.
|
|
957
|
-
def render_block_children(model, children:)
|
|
958
|
-
children.each do |attr, render_method|
|
|
959
|
-
values = safe_attr(model, attr)
|
|
960
|
-
next if values.nil?
|
|
961
|
-
|
|
962
|
-
Array(values).each { |v| public_send(render_method, v) }
|
|
963
|
-
end
|
|
964
|
-
end
|
|
965
|
-
|
|
966
|
-
# Standard child set for container blocks (example, note, etc.)
|
|
967
|
-
BLOCK_CHILDREN = {
|
|
968
|
-
paragraphs: :render_paragraph,
|
|
969
|
-
ul: :render_unordered_list,
|
|
970
|
-
ol: :render_ordered_list,
|
|
971
|
-
dl: :render_definition_list,
|
|
972
|
-
sourcecode: :render_sourcecode,
|
|
973
|
-
table: :render_table,
|
|
974
|
-
figure: :render_figure,
|
|
975
|
-
quote: :render_quote,
|
|
976
|
-
formula: :render_formula,
|
|
977
|
-
}.freeze
|
|
978
|
-
|
|
979
|
-
# Minimal child set for simple blocks (admonition, note with content only)
|
|
980
|
-
SIMPLE_CHILDREN = {
|
|
981
|
-
paragraphs: :render_paragraph,
|
|
982
|
-
}.freeze
|
|
983
|
-
|
|
984
|
-
# Note-style child set (paragraphs + lists + dl, no nested blocks)
|
|
985
|
-
NOTE_CHILDREN = {
|
|
986
|
-
paragraphs: :render_paragraph,
|
|
987
|
-
ul: :render_unordered_list,
|
|
988
|
-
ol: :render_ordered_list,
|
|
989
|
-
dl: :render_definition_list,
|
|
990
|
-
quote: :render_quote,
|
|
991
|
-
}.freeze
|
|
992
|
-
|
|
993
|
-
# --- Section rendering ---
|
|
994
|
-
|
|
995
|
-
def render_basic_section(section, level: 1, **_opts)
|
|
996
|
-
attrs = element_attrs(id: safe_attr(section, :id))
|
|
997
|
-
tag("div", attrs) do
|
|
998
|
-
render_section_title(section, level)
|
|
999
|
-
section.blocks&.each { |block| render(block) }
|
|
1000
|
-
section.notes&.each { |note| render_note(note) }
|
|
1001
|
-
end
|
|
1002
|
-
end
|
|
1003
|
-
|
|
1004
|
-
def render_hierarchical_section(section, level: 1, **_opts)
|
|
1005
|
-
attrs = element_attrs(id: safe_attr(section, :id))
|
|
1006
|
-
tag("div", attrs) do
|
|
1007
|
-
render_section_title(section, level)
|
|
1008
|
-
render_section_content(section, level)
|
|
1009
|
-
section.subsections&.each { |sub| render(sub, level: level + 1) }
|
|
1010
|
-
end
|
|
1011
|
-
end
|
|
1012
|
-
|
|
1013
|
-
def render_content_section(section, level: 1, **_opts)
|
|
1014
|
-
render_hierarchical_section(section, level: level, **_opts)
|
|
1015
|
-
end
|
|
1016
|
-
|
|
1017
|
-
def render_section_title(section, level)
|
|
1018
|
-
titles = section.title
|
|
1019
|
-
return unless titles && !titles.empty?
|
|
1020
|
-
|
|
1021
|
-
h = "h#{[[level, 6].min, 1].max}"
|
|
1022
|
-
title_text = extract_title_text(titles)
|
|
1023
|
-
@output << "<#{h}>#{escape_html(title_text)}</#{h}>"
|
|
1024
|
-
end
|
|
1025
|
-
|
|
1026
|
-
def render_section_content(section, _level)
|
|
1027
|
-
section.blocks&.each { |block| render(block) }
|
|
1028
|
-
section.notes&.each { |note| render_note(note) }
|
|
1029
|
-
end
|
|
1030
|
-
|
|
1031
|
-
# --- Inline content rendering ---
|
|
1032
|
-
|
|
1033
|
-
# Walk element_order in document order, resolving each element to its
|
|
1034
|
-
# Ruby object via the XML mapping, and yielding (element_order_entry, resolved_object)
|
|
1035
|
-
# to the given block. Handles tab elements as nbsp. Skips elements not in the mapping.
|
|
1036
|
-
#
|
|
1037
|
-
# This is the single ordered-walk primitive used by all mixed content renderers:
|
|
1038
|
-
# - render_mixed_inline (inline-only)
|
|
1039
|
-
# - render_cell_content (mixed block/inline)
|
|
1040
|
-
# - render_semx_content (filtered display attrs only)
|
|
1041
|
-
def walk_ordered(node, allow_filter: nil)
|
|
1042
|
-
return false unless node.is_a?(Lutaml::Model::Serializable)
|
|
1043
|
-
return false unless node.element_order.is_a?(Array) && !node.element_order.empty?
|
|
1044
|
-
|
|
1045
|
-
xml_mapping = node.class.mappings_for(:xml, node.lutaml_register)
|
|
1046
|
-
return false unless xml_mapping
|
|
1047
|
-
|
|
1048
|
-
element_to_attr = {}
|
|
1049
|
-
xml_mapping.mapping_elements_hash.each_value do |rule_or_array|
|
|
1050
|
-
Array(rule_or_array).each do |rule|
|
|
1051
|
-
element_to_attr[rule.name] = rule.to
|
|
1052
|
-
element_to_attr[rule.name.to_s] = rule.to if rule.name.is_a?(Symbol)
|
|
1053
|
-
end
|
|
1054
|
-
end
|
|
1055
|
-
|
|
1056
|
-
skip_indices = build_semx_skip_set(node)
|
|
1057
|
-
|
|
1058
|
-
indices = Hash.new(0)
|
|
1059
|
-
|
|
1060
|
-
node.element_order.each_with_index do |el, i|
|
|
1061
|
-
next if skip_indices.include?(i)
|
|
1062
|
-
|
|
1063
|
-
if el.text?
|
|
1064
|
-
text = el.text_content
|
|
1065
|
-
yield :text, text if text && block_given?
|
|
1066
|
-
elsif el.element?
|
|
1067
|
-
# Handle <tab/> elements
|
|
1068
|
-
if el.name == "tab"
|
|
1069
|
-
yield :tab, nil if block_given?
|
|
1070
|
-
next
|
|
1071
|
-
end
|
|
1072
|
-
|
|
1073
|
-
attr_name = element_to_attr[el.name]
|
|
1074
|
-
next unless attr_name
|
|
1075
|
-
|
|
1076
|
-
# Apply optional filter (used by semx to skip semantic attrs)
|
|
1077
|
-
next if allow_filter && !allow_filter.include?(attr_name)
|
|
1078
|
-
|
|
1079
|
-
coll = node.public_send(attr_name)
|
|
1080
|
-
obj = if coll.is_a?(Array)
|
|
1081
|
-
idx = indices[attr_name]
|
|
1082
|
-
indices[attr_name] += 1
|
|
1083
|
-
coll[idx]
|
|
1084
|
-
else
|
|
1085
|
-
coll
|
|
1086
|
-
end
|
|
1087
|
-
yield :element, obj if obj && block_given?
|
|
1088
|
-
end
|
|
1089
|
-
end
|
|
1090
|
-
true
|
|
1091
|
-
end
|
|
1092
|
-
|
|
1093
|
-
# In presentation XML, semantic elements are followed by <semx> wrappers
|
|
1094
|
-
# or <fmt-*> display elements. Skip source elements to avoid duplicates.
|
|
1095
|
-
def build_semx_skip_set(node)
|
|
1096
|
-
skip_after = {
|
|
1097
|
-
"link" => "semx",
|
|
1098
|
-
"xref" => "semx",
|
|
1099
|
-
"eref" => "semx",
|
|
1100
|
-
"stem" => nil,
|
|
1101
|
-
"concept" => "fmt-concept",
|
|
1102
|
-
"refterm" => nil,
|
|
1103
|
-
"renderterm" => nil,
|
|
1104
|
-
"origin" => "semx",
|
|
1105
|
-
}
|
|
1106
|
-
skip = {}
|
|
1107
|
-
node.element_order.each_with_index do |el, i|
|
|
1108
|
-
next unless el.element?
|
|
1109
|
-
|
|
1110
|
-
next_tag = skip_after[el.name]
|
|
1111
|
-
next unless next_tag
|
|
1112
|
-
|
|
1113
|
-
next_el = node.element_order[i + 1]
|
|
1114
|
-
if next_tag.nil?
|
|
1115
|
-
skip[i] = true
|
|
1116
|
-
elsif next_el&.element? && next_el.name == next_tag
|
|
1117
|
-
skip[i] = true
|
|
1118
|
-
end
|
|
1119
|
-
end
|
|
1120
|
-
skip
|
|
1121
|
-
end
|
|
1122
|
-
|
|
1123
|
-
# Table cells can contain both block-level content (p, ul, ol, dl)
|
|
1124
|
-
# and inline content (text, em, strong, etc.) in document order.
|
|
1125
|
-
def render_cell_content(cell)
|
|
1126
|
-
walked = walk_ordered(cell) do |type, obj|
|
|
1127
|
-
case type
|
|
1128
|
-
when :text
|
|
1129
|
-
@output << escape_html(obj)
|
|
1130
|
-
when :tab
|
|
1131
|
-
@output << "\u00a0\u00a0"
|
|
1132
|
-
when :element
|
|
1133
|
-
if block_element?(obj)
|
|
1134
|
-
render(obj)
|
|
1135
|
-
else
|
|
1136
|
-
render_inline_element(obj)
|
|
1137
|
-
end
|
|
1138
|
-
end
|
|
1139
|
-
end
|
|
1140
|
-
unless walked
|
|
1141
|
-
render_mixed_content_in_order(cell)
|
|
1142
|
-
end
|
|
667
|
+
# Inline rendering delegation
|
|
668
|
+
def walk_ordered(node, allow_filter: nil, &)
|
|
669
|
+
@inline_renderer.walk_ordered(node, allow_filter: allow_filter, &)
|
|
1143
670
|
end
|
|
1144
671
|
|
|
1145
672
|
def render_mixed_inline(node)
|
|
1146
|
-
|
|
1147
|
-
if raw_content_node?(node)
|
|
1148
|
-
raw = node.content
|
|
1149
|
-
if raw.is_a?(String) && !raw.strip.empty?
|
|
1150
|
-
@output << render_raw_content(raw)
|
|
1151
|
-
return
|
|
1152
|
-
end
|
|
1153
|
-
end
|
|
1154
|
-
|
|
1155
|
-
if node.is_a?(Lutaml::Model::Serializable) && node.element_order && !node.element_order.empty?
|
|
1156
|
-
render_ordered_inline(node)
|
|
1157
|
-
elsif node.is_a?(Lutaml::Model::Serializable)
|
|
1158
|
-
node.each_mixed_content do |child|
|
|
1159
|
-
case child
|
|
1160
|
-
when String
|
|
1161
|
-
@output << escape_html(child)
|
|
1162
|
-
else
|
|
1163
|
-
render_inline_element(child)
|
|
1164
|
-
end
|
|
1165
|
-
end
|
|
1166
|
-
else
|
|
1167
|
-
render_inline_collections(node)
|
|
1168
|
-
end
|
|
673
|
+
@inline_renderer.render_mixed_inline(node)
|
|
1169
674
|
end
|
|
1170
675
|
|
|
1171
|
-
def
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
676
|
+
def render_cell_content(cell) = @inline_renderer.render_cell_content(cell)
|
|
677
|
+
def render_em(el) = @inline_renderer.render_em(el)
|
|
678
|
+
def render_strong(el) = @inline_renderer.render_strong(el)
|
|
679
|
+
def render_tt(el) = @inline_renderer.render_tt(el)
|
|
680
|
+
def render_sub(el) = @inline_renderer.render_sub(el)
|
|
681
|
+
def render_sup(el) = @inline_renderer.render_sup(el)
|
|
682
|
+
def render_small_caps(el) = @inline_renderer.render_small_caps(el)
|
|
683
|
+
def render_underline(el) = @inline_renderer.render_underline(el)
|
|
684
|
+
def render_strike(el) = @inline_renderer.render_strike(el)
|
|
685
|
+
def render_br(*) = @inline_renderer.render_br
|
|
686
|
+
def render_tab(*) = @inline_renderer.render_tab
|
|
687
|
+
def render_span(el) = @inline_renderer.render_span(el)
|
|
688
|
+
def render_fn_inline(el) = @inline_renderer.render_fn_inline(el)
|
|
689
|
+
def render_stem(el) = @inline_renderer.render_stem(el)
|
|
690
|
+
def render_semx_inline(el) = @inline_renderer.render_semx_inline(el)
|
|
691
|
+
def render_fmt_xref(el) = @inline_renderer.render_fmt_xref(el)
|
|
692
|
+
def render_comma(*) = @inline_renderer.render_comma
|
|
693
|
+
def render_math(el) = @inline_renderer.render_math(el)
|
|
694
|
+
def render_asciimath(el) = @inline_renderer.render_asciimath(el)
|
|
695
|
+
def render_index(el) = @inline_renderer.render_index(el)
|
|
696
|
+
def render_note_inline(el) = @inline_renderer.render_note_inline(el)
|
|
1177
697
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
walked = walk_ordered(node) do |type, obj|
|
|
1182
|
-
case type
|
|
1183
|
-
when :text
|
|
1184
|
-
@output << escape_html(obj)
|
|
1185
|
-
when :tab
|
|
1186
|
-
@output << "\u00a0\u00a0"
|
|
1187
|
-
when :element
|
|
1188
|
-
render_inline_element(obj)
|
|
1189
|
-
end
|
|
1190
|
-
end
|
|
1191
|
-
unless walked
|
|
1192
|
-
node.each_mixed_content do |child|
|
|
1193
|
-
case child
|
|
1194
|
-
when String
|
|
1195
|
-
@output << escape_html(child)
|
|
1196
|
-
else
|
|
1197
|
-
render_inline_element(child)
|
|
1198
|
-
end
|
|
1199
|
-
end
|
|
1200
|
-
end
|
|
698
|
+
def render_semx_content(el,
|
|
699
|
+
**)
|
|
700
|
+
@inline_renderer.render_semx_content(el, **)
|
|
1201
701
|
end
|
|
1202
702
|
|
|
1203
|
-
|
|
703
|
+
def render_stem_content(stem) = @inline_renderer.render_stem_content(stem)
|
|
704
|
+
def render_link(link) = @inline_renderer.render_link(link)
|
|
705
|
+
def render_xref(xref) = @inline_renderer.render_xref(xref)
|
|
706
|
+
def render_eref(eref) = @inline_renderer.render_eref(eref)
|
|
707
|
+
def render_fn(fn) = @inline_renderer.render_fn(fn)
|
|
708
|
+
def render_concept(concept) = @inline_renderer.render_concept(concept)
|
|
709
|
+
def render_fmt_stem(fmt_stem) = @inline_renderer.render_fmt_stem(fmt_stem)
|
|
710
|
+
def render_mixed_content_in_order(node) = @inline_renderer.render_mixed_content_in_order(node)
|
|
1204
711
|
|
|
1205
|
-
|
|
1206
|
-
|
|
712
|
+
# Block rendering delegation
|
|
713
|
+
def render_paragraph(p,
|
|
714
|
+
**)
|
|
715
|
+
@block_renderer.render_paragraph(p, **)
|
|
1207
716
|
end
|
|
1208
717
|
|
|
1209
|
-
def
|
|
1210
|
-
|
|
718
|
+
def render_table(table,
|
|
719
|
+
**)
|
|
720
|
+
@block_renderer.render_table(table, **)
|
|
1211
721
|
end
|
|
1212
722
|
|
|
1213
|
-
def
|
|
1214
|
-
|
|
723
|
+
def render_unordered_list(ul,
|
|
724
|
+
**)
|
|
725
|
+
@block_renderer.render_unordered_list(ul, **)
|
|
1215
726
|
end
|
|
1216
727
|
|
|
1217
|
-
def
|
|
1218
|
-
|
|
728
|
+
def render_ordered_list(ol,
|
|
729
|
+
**)
|
|
730
|
+
@block_renderer.render_ordered_list(ol, **)
|
|
1219
731
|
end
|
|
1220
732
|
|
|
1221
|
-
def
|
|
1222
|
-
|
|
733
|
+
def render_definition_list(dl,
|
|
734
|
+
**)
|
|
735
|
+
@block_renderer.render_definition_list(dl, **)
|
|
1223
736
|
end
|
|
1224
737
|
|
|
1225
|
-
def
|
|
1226
|
-
|
|
738
|
+
def render_figure(figure,
|
|
739
|
+
**)
|
|
740
|
+
@block_renderer.render_figure(figure, **)
|
|
1227
741
|
end
|
|
1228
742
|
|
|
1229
|
-
def
|
|
1230
|
-
|
|
1231
|
-
|
|
743
|
+
def render_image(image) = @block_renderer.render_image(image)
|
|
744
|
+
def render_video(video) = @block_renderer.render_video(video)
|
|
745
|
+
def render_audio(audio) = @block_renderer.render_audio(audio)
|
|
746
|
+
def render_note(note, **) = @block_renderer.render_note(note, **)
|
|
1232
747
|
|
|
1233
|
-
def
|
|
1234
|
-
|
|
748
|
+
def render_example(example,
|
|
749
|
+
**)
|
|
750
|
+
@block_renderer.render_example(example, **)
|
|
1235
751
|
end
|
|
1236
752
|
|
|
1237
|
-
def
|
|
1238
|
-
|
|
753
|
+
def render_sourcecode(sc,
|
|
754
|
+
**)
|
|
755
|
+
@block_renderer.render_sourcecode(sc, **)
|
|
1239
756
|
end
|
|
1240
757
|
|
|
1241
|
-
def
|
|
1242
|
-
|
|
758
|
+
def render_formula(formula,
|
|
759
|
+
**)
|
|
760
|
+
@block_renderer.render_formula(formula, **)
|
|
1243
761
|
end
|
|
1244
762
|
|
|
1245
|
-
def
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
attrs = element_attrs(style: safe_attr(el, :style), class: html_class)
|
|
1249
|
-
tag("span", attrs) { render_mixed_inline(el) }
|
|
763
|
+
def render_quote(quote,
|
|
764
|
+
**)
|
|
765
|
+
@block_renderer.render_quote(quote, **)
|
|
1250
766
|
end
|
|
1251
767
|
|
|
1252
|
-
def
|
|
1253
|
-
|
|
768
|
+
def render_admonition(admonition,
|
|
769
|
+
**)
|
|
770
|
+
@block_renderer.render_admonition(admonition, **)
|
|
1254
771
|
end
|
|
1255
772
|
|
|
1256
|
-
def
|
|
1257
|
-
|
|
773
|
+
def render_bookmark(bookmark,
|
|
774
|
+
**)
|
|
775
|
+
@block_renderer.render_bookmark(bookmark, **)
|
|
1258
776
|
end
|
|
1259
777
|
|
|
1260
|
-
def
|
|
1261
|
-
|
|
778
|
+
def render_block_children(model,
|
|
779
|
+
children:)
|
|
780
|
+
@block_renderer.render_block_children(model, children: children)
|
|
1262
781
|
end
|
|
1263
782
|
|
|
1264
|
-
def
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
attrs = element_attrs(href: "##{escape_html(target)}", class: "xref")
|
|
1268
|
-
tag("a", attrs) { render_mixed_inline(el) }
|
|
1269
|
-
else
|
|
1270
|
-
render_mixed_inline(el)
|
|
1271
|
-
end
|
|
1272
|
-
end
|
|
783
|
+
def render_note_children(model) = @block_renderer.render_note_children(model)
|
|
784
|
+
def render_simple_children(model) = @block_renderer.render_simple_children(model)
|
|
785
|
+
def render_full_block_children(model) = @block_renderer.render_full_block_children(model)
|
|
1273
786
|
|
|
1274
|
-
|
|
1275
|
-
|
|
787
|
+
# Section rendering delegation
|
|
788
|
+
def render_basic_section(section,
|
|
789
|
+
**)
|
|
790
|
+
@section_renderer.render_basic_section(section, **)
|
|
1276
791
|
end
|
|
1277
792
|
|
|
1278
|
-
def
|
|
1279
|
-
|
|
793
|
+
def render_hierarchical_section(section,
|
|
794
|
+
**)
|
|
795
|
+
@section_renderer.render_hierarchical_section(section, **)
|
|
1280
796
|
end
|
|
1281
797
|
|
|
1282
|
-
def
|
|
1283
|
-
|
|
798
|
+
def render_content_section(section,
|
|
799
|
+
**)
|
|
800
|
+
@section_renderer.render_content_section(section, **)
|
|
1284
801
|
end
|
|
1285
802
|
|
|
1286
|
-
def
|
|
1287
|
-
|
|
1288
|
-
|
|
803
|
+
def render_ordered_content(section,
|
|
804
|
+
level = 1)
|
|
805
|
+
@section_renderer.render_ordered_content(section, level)
|
|
1289
806
|
end
|
|
1290
807
|
|
|
1291
|
-
def
|
|
1292
|
-
|
|
1293
|
-
end
|
|
808
|
+
def collect_ordered_children(section) = @section_renderer.collect_ordered_children(section)
|
|
809
|
+
def sort_by_displayorder(children) = @section_renderer.sort_by_displayorder(children)
|
|
1294
810
|
|
|
1295
|
-
def
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
texts.each do |t|
|
|
1299
|
-
if t.is_a?(String)
|
|
1300
|
-
@output << escape_html(t)
|
|
1301
|
-
else
|
|
1302
|
-
render_inline_element(t)
|
|
1303
|
-
end
|
|
1304
|
-
end
|
|
1305
|
-
elsif texts.is_a?(String)
|
|
1306
|
-
@output << escape_html(texts)
|
|
1307
|
-
end
|
|
1308
|
-
|
|
1309
|
-
inline_attrs = %i[em strong smallcap sub sup tt underline strike
|
|
1310
|
-
xref eref link span stem concept fn br tab keyword
|
|
1311
|
-
fmt_annotation_start fmt_annotation_end
|
|
1312
|
-
fmt_stem fmt_fn_label fmt_concept
|
|
1313
|
-
bookmark image semx fmt_xref_label]
|
|
1314
|
-
inline_attrs.each do |attr|
|
|
1315
|
-
values = safe_attr(node, attr)
|
|
1316
|
-
next if values.nil?
|
|
1317
|
-
|
|
1318
|
-
Array(values).each { |v| render_inline_element(v) }
|
|
1319
|
-
end
|
|
811
|
+
def render_preface(preface,
|
|
812
|
+
**)
|
|
813
|
+
@section_renderer.render_preface(preface, **)
|
|
1320
814
|
end
|
|
1321
815
|
|
|
1322
|
-
#
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
def render_semx_content(element, **_opts)
|
|
1326
|
-
display_attrs = %i[text fmt_xref fmt_link fmt_concept span strong em sup p semx
|
|
1327
|
-
asciimath math sub_child tt_child br_child tab_child
|
|
1328
|
-
stem_child figure_child formula_child sourcecode_child]
|
|
1329
|
-
label_stripped = false
|
|
1330
|
-
|
|
1331
|
-
walked = walk_ordered(element,
|
|
1332
|
-
allow_filter: display_attrs) do |type, obj|
|
|
1333
|
-
case type
|
|
1334
|
-
when :text
|
|
1335
|
-
text = obj
|
|
1336
|
-
if !label_stripped
|
|
1337
|
-
text = deduplicate_semx_text(text, @output)
|
|
1338
|
-
label_stripped = true
|
|
1339
|
-
end
|
|
1340
|
-
@output << escape_html(text)
|
|
1341
|
-
when :element
|
|
1342
|
-
if obj.is_a?(Metanorma::Document::Components::Paragraphs::ParagraphBlock)
|
|
1343
|
-
render_paragraph(obj)
|
|
1344
|
-
else
|
|
1345
|
-
render_inline_element(obj)
|
|
1346
|
-
end
|
|
1347
|
-
end
|
|
1348
|
-
end
|
|
1349
|
-
|
|
1350
|
-
unless walked
|
|
1351
|
-
display_attrs.each do |attr|
|
|
1352
|
-
val = safe_attr(element, attr)
|
|
1353
|
-
next if val.nil?
|
|
1354
|
-
|
|
1355
|
-
if val.is_a?(Array)
|
|
1356
|
-
val.each do |v|
|
|
1357
|
-
if v.is_a?(Metanorma::Document::Components::Paragraphs::ParagraphBlock)
|
|
1358
|
-
render_paragraph(v)
|
|
1359
|
-
else
|
|
1360
|
-
render_inline_element(v)
|
|
1361
|
-
end
|
|
1362
|
-
end
|
|
1363
|
-
elsif val.is_a?(String)
|
|
1364
|
-
@output << escape_html(val)
|
|
1365
|
-
else
|
|
1366
|
-
render_inline_element(val)
|
|
1367
|
-
end
|
|
1368
|
-
end
|
|
1369
|
-
end
|
|
1370
|
-
end
|
|
1371
|
-
|
|
1372
|
-
def deduplicate_semx_text(semx_text, output)
|
|
1373
|
-
first_word = semx_text[/\A\s*(\S+)/, 1]
|
|
1374
|
-
return semx_text unless first_word
|
|
1375
|
-
|
|
1376
|
-
tail = output[-200..]
|
|
1377
|
-
return semx_text unless tail&.rstrip&.end_with?(first_word)
|
|
1378
|
-
|
|
1379
|
-
semx_text.sub(/\A\s*#{Regexp.escape(first_word)}\s*/, "")
|
|
1380
|
-
end
|
|
1381
|
-
|
|
1382
|
-
def render_inline_tag(tag_name, element, **extra_attrs)
|
|
1383
|
-
tag(tag_name, element_attrs(**extra_attrs)) do
|
|
1384
|
-
render_mixed_inline(element)
|
|
1385
|
-
end
|
|
1386
|
-
end
|
|
1387
|
-
|
|
1388
|
-
# Process raw XML content from map_all_content models.
|
|
1389
|
-
# Strips source elements (xref, eref, stem) that have a following <semx>
|
|
1390
|
-
# wrapper, keeping only the semx display content.
|
|
1391
|
-
def render_raw_content(raw_xml)
|
|
1392
|
-
doc = Nokogiri::XML.fragment(raw_xml)
|
|
1393
|
-
# Convert fmt-link elements to HTML <a> tags before stripping wrappers
|
|
1394
|
-
doc.css("fmt-link").each do |el|
|
|
1395
|
-
target = el["target"] || el["href"]
|
|
1396
|
-
if target
|
|
1397
|
-
display_text = target.delete_prefix("mailto:")
|
|
1398
|
-
a = doc.document.create_element("a", display_text, "href" => target)
|
|
1399
|
-
el.replace(a)
|
|
1400
|
-
else
|
|
1401
|
-
el.replace(el.children)
|
|
1402
|
-
end
|
|
1403
|
-
end
|
|
1404
|
-
# Remove source elements that precede a <semx> sibling,
|
|
1405
|
-
# deduplicating any label text that appears in both the source
|
|
1406
|
-
# paragraph and the semx display content.
|
|
1407
|
-
doc.traverse do |node|
|
|
1408
|
-
next unless node.element?
|
|
1409
|
-
next unless %w[xref eref stem link].include?(node.name)
|
|
1410
|
-
|
|
1411
|
-
next_sib = node.next_sibling
|
|
1412
|
-
while next_sib.is_a?(Nokogiri::XML::Text) && next_sib.text.strip.empty?
|
|
1413
|
-
next_sib = next_sib.next_sibling
|
|
1414
|
-
end
|
|
1415
|
-
next unless next_sib&.element? && next_sib.name == "semx"
|
|
1416
|
-
|
|
1417
|
-
deduplicate_semx_label(node, next_sib)
|
|
1418
|
-
node.remove
|
|
1419
|
-
end
|
|
1420
|
-
# Strip presentation wrappers, keeping inner content
|
|
1421
|
-
%w[semx fmt-xref].each do |tag|
|
|
1422
|
-
doc.css(tag).each { |el| el.replace(el.children) }
|
|
1423
|
-
end
|
|
1424
|
-
# Remap XML class names to HTML-specific class names
|
|
1425
|
-
doc.css("[class]").each do |el|
|
|
1426
|
-
el["class"] = el["class"].split(/\s+/).map do |c|
|
|
1427
|
-
html_class_for_span(c)
|
|
1428
|
-
end.join(" ")
|
|
1429
|
-
end
|
|
1430
|
-
doc.inner_html
|
|
1431
|
-
end
|
|
1432
|
-
|
|
1433
|
-
def deduplicate_semx_label(source_node, semx_node)
|
|
1434
|
-
first_text = semx_node.children.find do |c|
|
|
1435
|
-
c.text? && !c.text.strip.empty?
|
|
1436
|
-
end
|
|
1437
|
-
return unless first_text
|
|
1438
|
-
|
|
1439
|
-
semx_prefix = first_text.text[/\A(\s*\S+)/, 1]
|
|
1440
|
-
return unless semx_prefix && !semx_prefix.strip.empty?
|
|
1441
|
-
|
|
1442
|
-
prev = source_node.previous_sibling
|
|
1443
|
-
return unless prev.is_a?(Nokogiri::XML::Text)
|
|
1444
|
-
|
|
1445
|
-
label = semx_prefix.strip
|
|
1446
|
-
prev_text = prev.text.rstrip
|
|
1447
|
-
return unless prev_text.end_with?(label)
|
|
1448
|
-
|
|
1449
|
-
prev.content = prev_text.sub(/#{Regexp.escape(label)}\s*\z/, "")
|
|
1450
|
-
first_text.content = first_text.text.sub(
|
|
1451
|
-
/\A\s*#{Regexp.escape(label)}\s*/, ""
|
|
1452
|
-
)
|
|
1453
|
-
end
|
|
1454
|
-
|
|
1455
|
-
def render_link(link)
|
|
1456
|
-
target = safe_attr(link, :target) || safe_attr(link, :href)
|
|
1457
|
-
attrs = element_attrs(href: target, id: safe_attr(link, :id))
|
|
1458
|
-
tag("a", attrs) do
|
|
1459
|
-
content = safe_attr(link, :content)
|
|
1460
|
-
if content && !Array(content).join.strip.empty?
|
|
1461
|
-
render_mixed_inline(link)
|
|
1462
|
-
else
|
|
1463
|
-
display_text = target.to_s.delete_prefix("mailto:")
|
|
1464
|
-
@output << escape_html(display_text)
|
|
1465
|
-
end
|
|
1466
|
-
end
|
|
1467
|
-
end
|
|
1468
|
-
|
|
1469
|
-
def render_xref(xref)
|
|
1470
|
-
target = safe_attr(xref, :target) || safe_attr(xref, :to_attr)
|
|
1471
|
-
attrs = element_attrs(href: "##{escape_html(target)}",
|
|
1472
|
-
id: safe_attr(xref,
|
|
1473
|
-
:id))
|
|
1474
|
-
tag("a", attrs) { render_mixed_inline(xref) }
|
|
1475
|
-
end
|
|
1476
|
-
|
|
1477
|
-
def render_eref(eref)
|
|
1478
|
-
citeas = safe_attr(eref, :citeas)
|
|
1479
|
-
if citeas
|
|
1480
|
-
@output << escape_html(citeas)
|
|
1481
|
-
else
|
|
1482
|
-
render_mixed_inline(eref)
|
|
1483
|
-
end
|
|
1484
|
-
end
|
|
1485
|
-
|
|
1486
|
-
def render_fn(fn)
|
|
1487
|
-
fn_id = safe_attr(fn, :id)
|
|
1488
|
-
number = @footnote_collector.register(fn)
|
|
1489
|
-
|
|
1490
|
-
attrs = element_attrs(id: fn_id, class: "fn-marker")
|
|
1491
|
-
tag("span", attrs) do
|
|
1492
|
-
label = safe_attr(fn, :fn_label) || safe_attr(fn, :reference)
|
|
1493
|
-
if label
|
|
1494
|
-
@output << "<sup><a href=\"##{escape_html("footnote-#{number}")}\" " \
|
|
1495
|
-
"class=\"fn-link\" id=\"#{escape_html("fnref-#{number}")}\">" \
|
|
1496
|
-
"#{escape_html(label.to_s)}</a></sup>"
|
|
1497
|
-
end
|
|
1498
|
-
end
|
|
1499
|
-
end
|
|
1500
|
-
|
|
1501
|
-
def render_concept(concept)
|
|
1502
|
-
render_mixed_inline(concept)
|
|
1503
|
-
end
|
|
1504
|
-
|
|
1505
|
-
# --- Stem/math rendering ---
|
|
1506
|
-
|
|
1507
|
-
def render_fmt_stem(fmt_stem)
|
|
1508
|
-
semx_items = Array(fmt_stem.semx)
|
|
1509
|
-
return if semx_items.empty?
|
|
1510
|
-
|
|
1511
|
-
semx = semx_items.first
|
|
1512
|
-
math_items = Array(semx.math)
|
|
1513
|
-
ascii_items = Array(semx.asciimath)
|
|
1514
|
-
|
|
1515
|
-
# Collect source formats for interactive copy dropdown
|
|
1516
|
-
source_formats = {}
|
|
1517
|
-
if ascii_items.any?
|
|
1518
|
-
ascii_text = ascii_items.map { |a| a.text.to_s.strip }.join
|
|
1519
|
-
source_formats["asciimath"] = ascii_text unless ascii_text.empty?
|
|
1520
|
-
end
|
|
1521
|
-
|
|
1522
|
-
data_attrs = ""
|
|
1523
|
-
unless source_formats.empty?
|
|
1524
|
-
data_attrs = " data-stem-formats='#{escape_html(source_formats.to_json)}'"
|
|
1525
|
-
end
|
|
1526
|
-
|
|
1527
|
-
if math_items.any?
|
|
1528
|
-
content = math_items.map { |m| m.content.to_s }.join
|
|
1529
|
-
unless content.empty?
|
|
1530
|
-
@output << "<span class=\"math-container\"#{data_attrs}>"
|
|
1531
|
-
@output << "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">#{content}</math>"
|
|
1532
|
-
@output << "</span>"
|
|
1533
|
-
end
|
|
1534
|
-
elsif ascii_items.any?
|
|
1535
|
-
# No MathML — render asciimath as fallback
|
|
1536
|
-
text = ascii_items.map { |a| a.text.to_s.strip }.join
|
|
1537
|
-
@output << "<span class=\"stem\"#{data_attrs}>#{escape_html(text)}</span>"
|
|
1538
|
-
end
|
|
1539
|
-
end
|
|
1540
|
-
|
|
1541
|
-
def render_stem_content(stem)
|
|
1542
|
-
return "" if stem.nil?
|
|
1543
|
-
|
|
1544
|
-
# StemInlineElement — source element, skip (rendered via FmtStemElement)
|
|
1545
|
-
if stem.is_a?(Metanorma::Document::Components::Inline::StemInlineElement)
|
|
1546
|
-
return ""
|
|
1547
|
-
end
|
|
1548
|
-
|
|
1549
|
-
# FmtStemElement — already handled by render_fmt_stem
|
|
1550
|
-
if stem.is_a?(Metanorma::Document::Components::Inline::FmtStemElement)
|
|
1551
|
-
return ""
|
|
1552
|
-
end
|
|
1553
|
-
|
|
1554
|
-
# TextElements::StemElement — block math (no fmt- counterpart for display formulas)
|
|
1555
|
-
if stem.is_a?(Metanorma::Document::Components::TextElements::StemElement)
|
|
1556
|
-
if stem.math
|
|
1557
|
-
math_val = stem.math
|
|
1558
|
-
return math_val.to_xml if math_val.is_a?(Nokogiri::XML::Node)
|
|
1559
|
-
return math_val.to_xml if defined?(Moxml::Document) && math_val.is_a?(Moxml::Document)
|
|
1560
|
-
|
|
1561
|
-
math_items = Array(math_val)
|
|
1562
|
-
math_items.join
|
|
1563
|
-
elsif stem.asciimath
|
|
1564
|
-
text = extract_text_value(stem.asciimath)
|
|
1565
|
-
return %(<span class="stem">#{escape_html(text)}</span>)
|
|
1566
|
-
end
|
|
1567
|
-
if stem.latexmath
|
|
1568
|
-
text = extract_text_value(stem.latexmath)
|
|
1569
|
-
return %(<span class="stem">#{escape_html(text)}</span>)
|
|
1570
|
-
end
|
|
1571
|
-
end
|
|
1572
|
-
|
|
1573
|
-
text = extract_text_value(stem)
|
|
1574
|
-
text.empty? ? "" : %(<span class="stem">#{escape_html(text)}</span>)
|
|
1575
|
-
end
|
|
816
|
+
# Pubid rendering delegation
|
|
817
|
+
def parse_pubid(docidentifier_string) = @pubid_renderer.parse_pubid(docidentifier_string)
|
|
818
|
+
def pubid_to_html(identifier) = @pubid_renderer.pubid_to_html(identifier)
|
|
1576
819
|
|
|
1577
820
|
# --- Helper methods ---
|
|
1578
821
|
|
|
1579
|
-
def tag(name, attrs_str)
|
|
1580
|
-
@output << "<#{name}#{attrs_str}>"
|
|
1581
|
-
yield
|
|
1582
|
-
@output << "</#{name}>"
|
|
1583
|
-
end
|
|
1584
|
-
|
|
1585
822
|
def element_attrs(**attrs)
|
|
1586
823
|
parts = []
|
|
1587
824
|
attrs.each do |k, v|
|
|
@@ -1644,7 +881,6 @@ module Metanorma
|
|
|
1644
881
|
end
|
|
1645
882
|
|
|
1646
883
|
def extract_block_label(block, default)
|
|
1647
|
-
# Presentation XML puts label in <name> child element
|
|
1648
884
|
names = safe_attr(block, :name)
|
|
1649
885
|
if names && !names.empty?
|
|
1650
886
|
name = names.is_a?(Array) ? names.first : names
|
|
@@ -1652,7 +888,6 @@ module Metanorma
|
|
|
1652
888
|
return text unless text.to_s.strip.empty?
|
|
1653
889
|
end
|
|
1654
890
|
|
|
1655
|
-
# Fallback: autonum XML attribute
|
|
1656
891
|
autonum = safe_attr(block, :autonum)
|
|
1657
892
|
if autonum && !autonum.to_s.empty?
|
|
1658
893
|
number = autonum.to_s
|
|
@@ -1669,9 +904,10 @@ module Metanorma
|
|
|
1669
904
|
end
|
|
1670
905
|
|
|
1671
906
|
def extract_title_text(titles)
|
|
1672
|
-
return "" if titles.nil?
|
|
907
|
+
return "" if titles.nil?
|
|
908
|
+
return extract_text_value(titles).to_s unless titles.is_a?(Array)
|
|
909
|
+
return "" if titles.empty?
|
|
1673
910
|
|
|
1674
|
-
titles = Array(titles)
|
|
1675
911
|
title = titles.first
|
|
1676
912
|
extract_text_value(title).to_s
|
|
1677
913
|
end
|
|
@@ -1709,29 +945,20 @@ module Metanorma
|
|
|
1709
945
|
end
|
|
1710
946
|
|
|
1711
947
|
def render_footnotes_section
|
|
1712
|
-
return if @footnote_collector.empty?
|
|
948
|
+
return nil if @footnote_collector.empty?
|
|
1713
949
|
|
|
1714
950
|
drops = @footnote_collector.to_a.map do |entry|
|
|
1715
951
|
content_html = ""
|
|
1716
952
|
if entry.content && !entry.content.empty?
|
|
1717
|
-
content_html =
|
|
1718
|
-
|
|
1719
|
-
end
|
|
953
|
+
content_html = Array(entry.content).filter_map do |p|
|
|
954
|
+
render_paragraph(p)
|
|
955
|
+
end.join
|
|
1720
956
|
end
|
|
1721
957
|
Drops::FootnoteDrop.new(entry, content_html)
|
|
1722
958
|
end
|
|
1723
959
|
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
end
|
|
1727
|
-
|
|
1728
|
-
def capture_output
|
|
1729
|
-
old_output = @output
|
|
1730
|
-
@output = +""
|
|
1731
|
-
yield
|
|
1732
|
-
result = @output
|
|
1733
|
-
@output = old_output
|
|
1734
|
-
result
|
|
960
|
+
render_liquid("_footnotes.html.liquid",
|
|
961
|
+
{ "footnotes" => drops })
|
|
1735
962
|
end
|
|
1736
963
|
end
|
|
1737
964
|
end
|