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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +164 -66
- 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 +13 -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 +261 -1008
- 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 -7
- 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/generator.rb +2 -2
- 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/renderers/block_renderer.rb +372 -0
- data/lib/metanorma/html/renderers/inline_renderer.rb +655 -0
- data/lib/metanorma/html/renderers/pubid_renderer.rb +69 -0
- data/lib/metanorma/html/renderers/section_renderer.rb +193 -0
- data/lib/metanorma/html/renderers.rb +8 -0
- data/lib/metanorma/html/ribose_renderer.rb +1 -45
- data/lib/metanorma/html/standard_renderer.rb +341 -229
- 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 +13 -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/rich_html_renderer.rb +159 -0
- data/lib/metanorma/mirror/handlers/inline/text_extractor.rb +116 -0
- data/lib/metanorma/mirror/handlers/inline.rb +225 -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/metanorma_to_mirror.rb +101 -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 +64 -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 +22 -0
- data/lib/metanorma/mirror.rb +32 -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 +262 -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,16 @@ 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
|
+
end
|
|
15
|
+
|
|
13
16
|
class BaseRenderer
|
|
14
17
|
LOGO_DIR = File.expand_path("../../../data/logos", __dir__)
|
|
15
18
|
|
|
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
19
|
SPAN_ROLE_CLASSES = {
|
|
19
20
|
"boldtitle" => "title-text",
|
|
20
21
|
"nonboldtitle" => "subtitle-text",
|
|
@@ -44,8 +45,6 @@ module Metanorma
|
|
|
44
45
|
|
|
45
46
|
METANORMA_LOGO = "metanorma-logo.svg"
|
|
46
47
|
|
|
47
|
-
# Type-to-method registry for OCP dispatch.
|
|
48
|
-
# Each subclass gets its own hash; lookup traverses ancestors.
|
|
49
48
|
class << self
|
|
50
49
|
def render_registry
|
|
51
50
|
@render_registry ||= {}
|
|
@@ -64,8 +63,12 @@ module Metanorma
|
|
|
64
63
|
end
|
|
65
64
|
end
|
|
66
65
|
|
|
66
|
+
attr_reader :inline_renderer, :block_renderer, :section_renderer,
|
|
67
|
+
:pubid_renderer, :index_term_collector, :footnote_collector
|
|
68
|
+
private :inline_renderer, :block_renderer, :section_renderer,
|
|
69
|
+
:pubid_renderer
|
|
70
|
+
|
|
67
71
|
def initialize
|
|
68
|
-
@output = +""
|
|
69
72
|
@toc_entries = []
|
|
70
73
|
@figure_entries = []
|
|
71
74
|
@table_entries = []
|
|
@@ -73,86 +76,107 @@ module Metanorma
|
|
|
73
76
|
@footnote_collector = Component::FootnoteCollector.new
|
|
74
77
|
@current_section_id = nil
|
|
75
78
|
@current_section_number = nil
|
|
76
|
-
end
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
@inline_renderer = Renderers::InlineRenderer.new(self)
|
|
81
|
+
@block_renderer = Renderers::BlockRenderer.new(self)
|
|
82
|
+
@section_renderer = Renderers::SectionRenderer.new(self)
|
|
83
|
+
@pubid_renderer = Renderers::PubidRenderer.new(self)
|
|
84
|
+
end
|
|
79
85
|
|
|
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
86
|
class RendererContext
|
|
85
|
-
DELEGATED_METHODS = %i[
|
|
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
|
|
94
|
-
|
|
95
87
|
def initialize(renderer)
|
|
96
88
|
@renderer = renderer
|
|
97
89
|
end
|
|
98
90
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
91
|
+
def safe_attr(...) = @renderer.safe_attr(...)
|
|
92
|
+
def escape_html(...) = @renderer.escape_html(...)
|
|
93
|
+
def extract_block_label(...)= @renderer.extract_block_label(...)
|
|
94
|
+
def extract_plain_text(...)= @renderer.extract_plain_text(...)
|
|
95
|
+
def render_paragraph(...) = @renderer.render_paragraph(...)
|
|
96
|
+
def render_mixed_inline(...)= @renderer.render_mixed_inline(...)
|
|
97
|
+
def render_inline_element(...)= @renderer.render_inline_element(...)
|
|
98
|
+
def render_unordered_list(...)= @renderer.render_unordered_list(...)
|
|
99
|
+
def render_ordered_list(...)= @renderer.render_ordered_list(...)
|
|
100
|
+
def render_definition_list(...)= @renderer.render_definition_list(...)
|
|
101
|
+
def render_sourcecode(...) = @renderer.render_sourcecode(...)
|
|
102
|
+
def render_table(...) = @renderer.render_table(...)
|
|
103
|
+
def render_figure(...) = @renderer.render_figure(...)
|
|
104
|
+
def render_quote(...) = @renderer.render_quote(...)
|
|
105
|
+
def render_formula(...) = @renderer.render_formula(...)
|
|
106
|
+
def render_note(...) = @renderer.render_note(...)
|
|
107
|
+
def render_image(...) = @renderer.render_image(...)
|
|
108
|
+
def render_stem_content(...)= @renderer.render_stem_content(...)
|
|
109
|
+
def register_figure_entry(...)= @renderer.register_figure_entry(...)
|
|
110
|
+
def render_liquid(...) = @renderer.render_liquid(...)
|
|
111
|
+
def render_note_children(...) = @renderer.render_note_children(...)
|
|
112
|
+
def render_simple_children(...) = @renderer.render_simple_children(...)
|
|
113
|
+
def render_full_block_children(...) = @renderer.render_full_block_children(...)
|
|
112
114
|
end
|
|
113
115
|
|
|
114
116
|
def renderer_context
|
|
115
117
|
@renderer_context ||= RendererContext.new(self)
|
|
116
118
|
end
|
|
117
119
|
|
|
118
|
-
def to_html
|
|
119
|
-
@output
|
|
120
|
-
end
|
|
121
|
-
|
|
122
120
|
def toc_entries
|
|
123
121
|
@toc_entries
|
|
124
122
|
end
|
|
125
123
|
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
attr_writer :document, :theme
|
|
125
|
+
|
|
126
|
+
def generate_full_document(document, **)
|
|
128
127
|
@document = document
|
|
129
128
|
validate_presentation_xml!
|
|
130
129
|
|
|
131
|
-
|
|
132
|
-
render(@document)
|
|
133
|
-
body = @output
|
|
130
|
+
body = render(@document) || ""
|
|
134
131
|
|
|
135
132
|
assemble_document(body)
|
|
136
133
|
end
|
|
137
134
|
|
|
138
|
-
# --- Flavor configuration hooks
|
|
135
|
+
# --- Flavor configuration hooks ---
|
|
136
|
+
|
|
137
|
+
FLAVOR_MAP = {
|
|
138
|
+
"IsoDocument" => :iso,
|
|
139
|
+
"IecDocument" => :iec,
|
|
140
|
+
"IeeeDocument" => :ieee,
|
|
141
|
+
"IetfDocument" => :ietf,
|
|
142
|
+
"ItuDocument" => :itu,
|
|
143
|
+
"IhoDocument" => :iho,
|
|
144
|
+
"BipmDocument" => :bipm,
|
|
145
|
+
"OgcDocument" => :ogc,
|
|
146
|
+
"OimlDocument" => :oiml,
|
|
147
|
+
"CcDocument" => :cc,
|
|
148
|
+
"IccDocument" => :icc,
|
|
149
|
+
"RiboseDocument" => :ribose,
|
|
150
|
+
"PdfaDocument" => :pdfa,
|
|
151
|
+
}.freeze
|
|
139
152
|
|
|
140
153
|
def theme
|
|
141
|
-
@theme ||=
|
|
154
|
+
@theme ||= resolve_theme
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def resolve_theme
|
|
158
|
+
flavor = flavor_name
|
|
159
|
+
flavor ? Theme.load(flavor) : Theme.new
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def flavor_name
|
|
163
|
+
return nil unless defined?(@document) && @document
|
|
164
|
+
|
|
165
|
+
@document.class.name&.split("::")&.detect do |ns|
|
|
166
|
+
FLAVOR_MAP.key?(ns)
|
|
167
|
+
end&.then { |ns| FLAVOR_MAP[ns] }
|
|
142
168
|
end
|
|
143
169
|
|
|
144
170
|
def flavor_publishers(_doc_id)
|
|
145
|
-
|
|
171
|
+
theme.publishers
|
|
146
172
|
end
|
|
147
173
|
|
|
148
174
|
def flavor_publisher_name
|
|
149
|
-
|
|
150
|
-
pubs.empty? ? nil : pubs.join("/")
|
|
175
|
+
theme.publisher_name
|
|
151
176
|
end
|
|
152
177
|
|
|
153
|
-
# Map of publisher name => logo filename. Override in flavor renderers.
|
|
154
178
|
def publisher_logo_map
|
|
155
|
-
|
|
179
|
+
theme.logos_light
|
|
156
180
|
end
|
|
157
181
|
|
|
158
182
|
def flavor_font_url
|
|
@@ -165,7 +189,7 @@ module Metanorma
|
|
|
165
189
|
TEMPLATE_CACHE_MUTEX = Mutex.new
|
|
166
190
|
|
|
167
191
|
def render_liquid(template_name, assigns)
|
|
168
|
-
template_path =
|
|
192
|
+
template_path = theme.resolve_template(template_name)
|
|
169
193
|
template = TEMPLATE_CACHE_MUTEX.synchronize do
|
|
170
194
|
TEMPLATE_CACHE[template_path] ||= Liquid::Template.parse(File.read(template_path))
|
|
171
195
|
end
|
|
@@ -211,7 +235,7 @@ module Metanorma
|
|
|
211
235
|
end
|
|
212
236
|
|
|
213
237
|
def header_title_text
|
|
214
|
-
raw = html_title.to_s.split(" — ").first.to_s
|
|
238
|
+
raw = html_title.to_s.split(" — ").first.to_s
|
|
215
239
|
raw.length > 60 ? "#{raw[0, 57]}..." : raw
|
|
216
240
|
end
|
|
217
241
|
|
|
@@ -220,7 +244,7 @@ module Metanorma
|
|
|
220
244
|
logo_map = publisher_logo_map
|
|
221
245
|
return "" if publishers.empty? && logo_map.empty?
|
|
222
246
|
|
|
223
|
-
|
|
247
|
+
dark_logo_map = theme.logos_dark
|
|
224
248
|
display_pubs = publishers.empty? ? logo_map.keys : publishers
|
|
225
249
|
|
|
226
250
|
display_pubs.filter_map do |pub|
|
|
@@ -230,18 +254,28 @@ module Metanorma
|
|
|
230
254
|
svg = load_logo_svg(filename, height: 26)
|
|
231
255
|
next unless svg
|
|
232
256
|
|
|
233
|
-
"<span class=\"brand-logo\" aria-label=\"#{pub} logo\">#{svg}</span>"
|
|
257
|
+
light_span = "<span class=\"brand-logo brand-logo-light\" aria-label=\"#{pub} logo\">#{svg}</span>"
|
|
258
|
+
|
|
259
|
+
dark_span = ""
|
|
260
|
+
dark_filename = dark_logo_map[pub]
|
|
261
|
+
if dark_filename
|
|
262
|
+
dark_svg = load_logo_svg(dark_filename, height: 26)
|
|
263
|
+
if dark_svg
|
|
264
|
+
dark_span = "<span class=\"brand-logo brand-logo-dark\" aria-label=\"#{pub} logo\">#{dark_svg}</span>"
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
"#{light_span}\n#{dark_span}"
|
|
234
269
|
end.join("\n")
|
|
235
270
|
end
|
|
236
271
|
|
|
237
272
|
def load_logo_svg(filename, height: 32)
|
|
238
|
-
path = File.join(LOGO_DIR, filename)
|
|
273
|
+
path = theme.resolve_asset(filename) || File.join(LOGO_DIR, filename)
|
|
239
274
|
return nil unless File.exist?(path)
|
|
240
275
|
|
|
241
276
|
svg = File.read(path)
|
|
242
277
|
svg = svg.sub(/\A<\?xml[^?]*\?>\s*/, "")
|
|
243
278
|
svg = svg.sub(/\A\s*<!--.*?-->\s*/m, "")
|
|
244
|
-
svg = svg.sub(/<path[^>]*style="fill:#e3000f[^"]*"[^>]*\/>/, "")
|
|
245
279
|
svg = svg.sub(/<svg\s/, '<svg class="header-logo" ')
|
|
246
280
|
svg = if svg.match?(/<svg[^>]*\sheight="[^"]*"/)
|
|
247
281
|
svg.sub(/(<svg[^>]*?)(\sheight="[^"]*")/,
|
|
@@ -297,7 +331,10 @@ module Metanorma
|
|
|
297
331
|
def build_styles
|
|
298
332
|
pipeline = AssetPipeline.new
|
|
299
333
|
css = pipeline.compile_css(flavor_css: flavor_css_module)
|
|
300
|
-
|
|
334
|
+
parts = [theme.to_css_root, css, theme.to_css_extras]
|
|
335
|
+
custom_css_path = theme.theme_css_path
|
|
336
|
+
parts << File.read(custom_css_path) if custom_css_path
|
|
337
|
+
parts.join("\n")
|
|
301
338
|
end
|
|
302
339
|
|
|
303
340
|
# --- Validation ---
|
|
@@ -316,12 +353,11 @@ module Metanorma
|
|
|
316
353
|
return false unless node
|
|
317
354
|
return false if node.is_a?(String)
|
|
318
355
|
|
|
319
|
-
if node.is_a?(Metanorma::Document::Root) && node.type == "presentation"
|
|
320
|
-
return true
|
|
321
|
-
end
|
|
322
|
-
|
|
323
356
|
if node.is_a?(Lutaml::Model::Serializable)
|
|
324
357
|
node_attrs = node.class.attributes
|
|
358
|
+
if node_attrs.key?(:type) && node.type == "presentation"
|
|
359
|
+
return true
|
|
360
|
+
end
|
|
325
361
|
if node_attrs.key?(:fmt_title) && node.fmt_title
|
|
326
362
|
return true
|
|
327
363
|
end
|
|
@@ -363,16 +399,21 @@ module Metanorma
|
|
|
363
399
|
end
|
|
364
400
|
|
|
365
401
|
def html_title
|
|
366
|
-
|
|
367
|
-
|
|
402
|
+
extract_display_title(@document.bibdata) || "Document"
|
|
403
|
+
end
|
|
368
404
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
405
|
+
def extract_display_title(bibdata)
|
|
406
|
+
return nil unless bibdata
|
|
407
|
+
|
|
408
|
+
title = bibdata.title_for("en") if bibdata.is_a?(Metanorma::Document::Components::BibData::BibData)
|
|
409
|
+
return title.to_s if title && !title.to_s.empty?
|
|
410
|
+
|
|
411
|
+
titles = safe_attr(bibdata, :title)
|
|
412
|
+
return nil unless titles && !titles.is_a?(String) && !titles.empty?
|
|
413
|
+
|
|
414
|
+
en = titles.find { |t| safe_attr(t, :language) == "en" }
|
|
415
|
+
found = en || titles.first
|
|
416
|
+
extract_text_value(found).to_s
|
|
376
417
|
end
|
|
377
418
|
|
|
378
419
|
def extract_primary_doc_id
|
|
@@ -393,7 +434,7 @@ module Metanorma
|
|
|
393
434
|
text.strip.empty? ? nil : text.strip
|
|
394
435
|
end
|
|
395
436
|
|
|
396
|
-
# ---
|
|
437
|
+
# --- Registration helpers ---
|
|
397
438
|
|
|
398
439
|
def register_toc_entry(id:, level:, text:)
|
|
399
440
|
@toc_entries << { id: id, level: level, text: text }
|
|
@@ -403,12 +444,14 @@ module Metanorma
|
|
|
403
444
|
@figure_entries << { id: id, text: text }
|
|
404
445
|
end
|
|
405
446
|
|
|
447
|
+
def figure_entries
|
|
448
|
+
@figure_entries
|
|
449
|
+
end
|
|
450
|
+
|
|
406
451
|
def register_table_entry(id:, text:)
|
|
407
452
|
@table_entries << { id: id, text: text }
|
|
408
453
|
end
|
|
409
454
|
|
|
410
|
-
attr_reader :index_term_collector, :footnote_collector
|
|
411
|
-
|
|
412
455
|
def extract_plain_text(node)
|
|
413
456
|
return node.to_s if node.is_a?(String)
|
|
414
457
|
return extract_text_value(node).to_s unless node.is_a?(Lutaml::Model::Serializable)
|
|
@@ -431,38 +474,39 @@ module Metanorma
|
|
|
431
474
|
if el.text?
|
|
432
475
|
parts << el.text_content.to_s
|
|
433
476
|
elsif el.name == "tab"
|
|
434
|
-
parts << "
|
|
477
|
+
parts << " "
|
|
435
478
|
elsif el.name == "br"
|
|
436
479
|
parts << " "
|
|
437
480
|
elsif el.element?
|
|
438
481
|
attr_name = element_to_attr[el.name]
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
482
|
+
if attr_name
|
|
483
|
+
coll = node.public_send(attr_name)
|
|
484
|
+
obj = if coll.is_a?(Array)
|
|
485
|
+
idx = indices[attr_name]
|
|
486
|
+
indices[attr_name] += 1
|
|
487
|
+
coll[idx]
|
|
488
|
+
else
|
|
489
|
+
coll
|
|
490
|
+
end
|
|
491
|
+
text = extract_plain_text(obj)
|
|
492
|
+
parts << (text.empty? ? " " : text)
|
|
493
|
+
elsif el.name == "span"
|
|
494
|
+
parts << " "
|
|
495
|
+
end
|
|
450
496
|
end
|
|
451
497
|
end
|
|
452
498
|
end
|
|
453
499
|
|
|
454
|
-
# Fallback: try .text
|
|
455
500
|
if parts.join.strip.empty?
|
|
456
501
|
t = safe_attr(node, :text)
|
|
457
502
|
parts << (t.is_a?(Array) ? t.join : t.to_s) if t
|
|
458
503
|
end
|
|
459
504
|
|
|
460
|
-
parts.join.strip.gsub("
|
|
505
|
+
parts.join.strip.gsub(" ", " ")
|
|
461
506
|
end
|
|
462
507
|
|
|
463
|
-
# Dispatch
|
|
464
|
-
|
|
465
|
-
# parent registrations and can override them independently.
|
|
508
|
+
# --- Dispatch ---
|
|
509
|
+
|
|
466
510
|
def render(node, **)
|
|
467
511
|
return escape_html(node) if node.is_a?(String)
|
|
468
512
|
|
|
@@ -470,20 +514,18 @@ module Metanorma
|
|
|
470
514
|
method ? public_send(method, node, **) : ""
|
|
471
515
|
end
|
|
472
516
|
|
|
473
|
-
# Dispatch to the appropriate inline render method via type registry.
|
|
474
517
|
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
|
|
518
|
+
@inline_renderer.render_inline_element(element)
|
|
484
519
|
end
|
|
485
520
|
|
|
486
|
-
|
|
521
|
+
def is_title_element?(node, section)
|
|
522
|
+
title = safe_attr(section, :title)
|
|
523
|
+
return false unless title
|
|
524
|
+
|
|
525
|
+
node.equal?(title)
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
# --- Type registrations ---
|
|
487
529
|
|
|
488
530
|
register_render Metanorma::Document::Components::Paragraphs::ParagraphBlock,
|
|
489
531
|
:render_paragraph
|
|
@@ -582,7 +624,6 @@ module Metanorma
|
|
|
582
624
|
:render_index
|
|
583
625
|
register_inline_render Metanorma::Document::Components::Blocks::NoteBlock,
|
|
584
626
|
:render_note_inline
|
|
585
|
-
# All Fmt* elements delegate to render_mixed_inline
|
|
586
627
|
register_inline_render Metanorma::Document::Components::Inline::FmtNameElement,
|
|
587
628
|
:render_mixed_inline
|
|
588
629
|
register_inline_render Metanorma::Document::Components::Inline::FmtTitleElement,
|
|
@@ -647,941 +688,163 @@ module Metanorma
|
|
|
647
688
|
nil
|
|
648
689
|
end
|
|
649
690
|
|
|
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
|
|
784
|
-
|
|
785
|
-
# Render all children of a mixed-content node in document order,
|
|
786
|
-
# dispatching block elements to their render methods.
|
|
787
|
-
def render_mixed_content_in_order(node)
|
|
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)
|
|
691
|
+
# --- Delegation to sub-renderers ---
|
|
1057
692
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
node
|
|
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
|
|
693
|
+
# Inline rendering delegation
|
|
694
|
+
def walk_ordered(node, allow_filter: nil, &)
|
|
695
|
+
@inline_renderer.walk_ordered(node, allow_filter: allow_filter, &)
|
|
1143
696
|
end
|
|
1144
697
|
|
|
1145
698
|
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
|
|
699
|
+
@inline_renderer.render_mixed_inline(node)
|
|
1169
700
|
end
|
|
1170
701
|
|
|
1171
|
-
def
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
702
|
+
def render_cell_content(cell) = @inline_renderer.render_cell_content(cell)
|
|
703
|
+
def render_em(el) = @inline_renderer.render_em(el)
|
|
704
|
+
def render_strong(el) = @inline_renderer.render_strong(el)
|
|
705
|
+
def render_tt(el) = @inline_renderer.render_tt(el)
|
|
706
|
+
def render_sub(el) = @inline_renderer.render_sub(el)
|
|
707
|
+
def render_sup(el) = @inline_renderer.render_sup(el)
|
|
708
|
+
def render_small_caps(el) = @inline_renderer.render_small_caps(el)
|
|
709
|
+
def render_underline(el) = @inline_renderer.render_underline(el)
|
|
710
|
+
def render_strike(el) = @inline_renderer.render_strike(el)
|
|
711
|
+
def render_br(*) = @inline_renderer.render_br
|
|
712
|
+
def render_tab(*) = @inline_renderer.render_tab
|
|
713
|
+
def render_span(el) = @inline_renderer.render_span(el)
|
|
714
|
+
def render_fn_inline(el) = @inline_renderer.render_fn_inline(el)
|
|
715
|
+
def render_stem(el) = @inline_renderer.render_stem(el)
|
|
716
|
+
def render_semx_inline(el) = @inline_renderer.render_semx_inline(el)
|
|
717
|
+
def render_fmt_xref(el) = @inline_renderer.render_fmt_xref(el)
|
|
718
|
+
def render_comma(*) = @inline_renderer.render_comma
|
|
719
|
+
def render_math(el) = @inline_renderer.render_math(el)
|
|
720
|
+
def render_asciimath(el) = @inline_renderer.render_asciimath(el)
|
|
721
|
+
def render_index(el) = @inline_renderer.render_index(el)
|
|
722
|
+
def render_note_inline(el) = @inline_renderer.render_note_inline(el)
|
|
1177
723
|
|
|
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
|
|
724
|
+
def render_semx_content(el,
|
|
725
|
+
**)
|
|
726
|
+
@inline_renderer.render_semx_content(el, **)
|
|
1201
727
|
end
|
|
1202
728
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
def
|
|
1206
|
-
|
|
1207
|
-
|
|
729
|
+
def render_stem_content(stem) = @inline_renderer.render_stem_content(stem)
|
|
730
|
+
def render_link(link) = @inline_renderer.render_link(link)
|
|
731
|
+
def render_xref(xref) = @inline_renderer.render_xref(xref)
|
|
732
|
+
def render_eref(eref) = @inline_renderer.render_eref(eref)
|
|
733
|
+
def render_fn(fn) = @inline_renderer.render_fn(fn)
|
|
734
|
+
def render_concept(concept) = @inline_renderer.render_concept(concept)
|
|
735
|
+
def render_fmt_stem(fmt_stem) = @inline_renderer.render_fmt_stem(fmt_stem)
|
|
736
|
+
def render_mixed_content_in_order(node) = @inline_renderer.render_mixed_content_in_order(node)
|
|
1208
737
|
|
|
1209
|
-
|
|
1210
|
-
|
|
738
|
+
# Block rendering delegation
|
|
739
|
+
def render_paragraph(p,
|
|
740
|
+
**)
|
|
741
|
+
@block_renderer.render_paragraph(p, **)
|
|
1211
742
|
end
|
|
1212
743
|
|
|
1213
|
-
def
|
|
1214
|
-
|
|
744
|
+
def render_table(table,
|
|
745
|
+
**)
|
|
746
|
+
@block_renderer.render_table(table, **)
|
|
1215
747
|
end
|
|
1216
748
|
|
|
1217
|
-
def
|
|
1218
|
-
|
|
749
|
+
def render_unordered_list(ul,
|
|
750
|
+
**)
|
|
751
|
+
@block_renderer.render_unordered_list(ul, **)
|
|
1219
752
|
end
|
|
1220
753
|
|
|
1221
|
-
def
|
|
1222
|
-
|
|
754
|
+
def render_ordered_list(ol,
|
|
755
|
+
**)
|
|
756
|
+
@block_renderer.render_ordered_list(ol, **)
|
|
1223
757
|
end
|
|
1224
758
|
|
|
1225
|
-
def
|
|
1226
|
-
|
|
759
|
+
def render_definition_list(dl,
|
|
760
|
+
**)
|
|
761
|
+
@block_renderer.render_definition_list(dl, **)
|
|
1227
762
|
end
|
|
1228
763
|
|
|
1229
|
-
def
|
|
1230
|
-
|
|
764
|
+
def render_figure(figure,
|
|
765
|
+
**)
|
|
766
|
+
@block_renderer.render_figure(figure, **)
|
|
1231
767
|
end
|
|
1232
768
|
|
|
1233
|
-
def
|
|
1234
|
-
|
|
1235
|
-
|
|
769
|
+
def render_image(image) = @block_renderer.render_image(image)
|
|
770
|
+
def render_video(video) = @block_renderer.render_video(video)
|
|
771
|
+
def render_audio(audio) = @block_renderer.render_audio(audio)
|
|
772
|
+
def render_note(note, **) = @block_renderer.render_note(note, **)
|
|
1236
773
|
|
|
1237
|
-
def
|
|
1238
|
-
|
|
774
|
+
def render_example(example,
|
|
775
|
+
**)
|
|
776
|
+
@block_renderer.render_example(example, **)
|
|
1239
777
|
end
|
|
1240
778
|
|
|
1241
|
-
def
|
|
1242
|
-
|
|
779
|
+
def render_sourcecode(sc,
|
|
780
|
+
**)
|
|
781
|
+
@block_renderer.render_sourcecode(sc, **)
|
|
1243
782
|
end
|
|
1244
783
|
|
|
1245
|
-
def
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
attrs = element_attrs(style: safe_attr(el, :style), class: html_class)
|
|
1249
|
-
tag("span", attrs) { render_mixed_inline(el) }
|
|
784
|
+
def render_formula(formula,
|
|
785
|
+
**)
|
|
786
|
+
@block_renderer.render_formula(formula, **)
|
|
1250
787
|
end
|
|
1251
788
|
|
|
1252
|
-
def
|
|
1253
|
-
|
|
789
|
+
def render_quote(quote,
|
|
790
|
+
**)
|
|
791
|
+
@block_renderer.render_quote(quote, **)
|
|
1254
792
|
end
|
|
1255
793
|
|
|
1256
|
-
def
|
|
1257
|
-
|
|
794
|
+
def render_admonition(admonition,
|
|
795
|
+
**)
|
|
796
|
+
@block_renderer.render_admonition(admonition, **)
|
|
1258
797
|
end
|
|
1259
798
|
|
|
1260
|
-
def
|
|
1261
|
-
|
|
799
|
+
def render_bookmark(bookmark,
|
|
800
|
+
**)
|
|
801
|
+
@block_renderer.render_bookmark(bookmark, **)
|
|
1262
802
|
end
|
|
1263
803
|
|
|
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
|
|
804
|
+
def render_block_children(model,
|
|
805
|
+
children:)
|
|
806
|
+
@block_renderer.render_block_children(model, children: children)
|
|
1272
807
|
end
|
|
1273
808
|
|
|
1274
|
-
def
|
|
1275
|
-
|
|
1276
|
-
|
|
809
|
+
def render_note_children(model) = @block_renderer.render_note_children(model)
|
|
810
|
+
def render_simple_children(model) = @block_renderer.render_simple_children(model)
|
|
811
|
+
def render_full_block_children(model) = @block_renderer.render_full_block_children(model)
|
|
1277
812
|
|
|
1278
|
-
|
|
1279
|
-
|
|
813
|
+
# Section rendering delegation
|
|
814
|
+
def render_basic_section(section,
|
|
815
|
+
**)
|
|
816
|
+
@section_renderer.render_basic_section(section, **)
|
|
1280
817
|
end
|
|
1281
818
|
|
|
1282
|
-
def
|
|
1283
|
-
|
|
819
|
+
def render_hierarchical_section(section,
|
|
820
|
+
**)
|
|
821
|
+
@section_renderer.render_hierarchical_section(section, **)
|
|
1284
822
|
end
|
|
1285
823
|
|
|
1286
|
-
def
|
|
1287
|
-
|
|
1288
|
-
|
|
824
|
+
def render_content_section(section,
|
|
825
|
+
**)
|
|
826
|
+
@section_renderer.render_content_section(section, **)
|
|
1289
827
|
end
|
|
1290
828
|
|
|
1291
|
-
def
|
|
1292
|
-
|
|
829
|
+
def render_ordered_content(section,
|
|
830
|
+
level = 1)
|
|
831
|
+
@section_renderer.render_ordered_content(section, level)
|
|
1293
832
|
end
|
|
1294
833
|
|
|
1295
|
-
def
|
|
1296
|
-
|
|
1297
|
-
if texts.is_a?(Array)
|
|
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
|
|
834
|
+
def collect_ordered_children(section) = @section_renderer.collect_ordered_children(section)
|
|
835
|
+
def sort_by_displayorder(children) = @section_renderer.sort_by_displayorder(children)
|
|
1308
836
|
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
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
|
|
837
|
+
def render_preface(preface,
|
|
838
|
+
**)
|
|
839
|
+
@section_renderer.render_preface(preface, **)
|
|
1320
840
|
end
|
|
1321
841
|
|
|
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
|
|
842
|
+
# Pubid rendering delegation
|
|
843
|
+
def parse_pubid(docidentifier_string) = @pubid_renderer.parse_pubid(docidentifier_string)
|
|
844
|
+
def pubid_to_html(identifier) = @pubid_renderer.pubid_to_html(identifier)
|
|
1576
845
|
|
|
1577
846
|
# --- Helper methods ---
|
|
1578
847
|
|
|
1579
|
-
def tag(name, attrs_str)
|
|
1580
|
-
@output << "<#{name}#{attrs_str}>"
|
|
1581
|
-
yield
|
|
1582
|
-
@output << "</#{name}>"
|
|
1583
|
-
end
|
|
1584
|
-
|
|
1585
848
|
def element_attrs(**attrs)
|
|
1586
849
|
parts = []
|
|
1587
850
|
attrs.each do |k, v|
|
|
@@ -1644,7 +907,6 @@ module Metanorma
|
|
|
1644
907
|
end
|
|
1645
908
|
|
|
1646
909
|
def extract_block_label(block, default)
|
|
1647
|
-
# Presentation XML puts label in <name> child element
|
|
1648
910
|
names = safe_attr(block, :name)
|
|
1649
911
|
if names && !names.empty?
|
|
1650
912
|
name = names.is_a?(Array) ? names.first : names
|
|
@@ -1652,7 +914,6 @@ module Metanorma
|
|
|
1652
914
|
return text unless text.to_s.strip.empty?
|
|
1653
915
|
end
|
|
1654
916
|
|
|
1655
|
-
# Fallback: autonum XML attribute
|
|
1656
917
|
autonum = safe_attr(block, :autonum)
|
|
1657
918
|
if autonum && !autonum.to_s.empty?
|
|
1658
919
|
number = autonum.to_s
|
|
@@ -1669,9 +930,10 @@ module Metanorma
|
|
|
1669
930
|
end
|
|
1670
931
|
|
|
1671
932
|
def extract_title_text(titles)
|
|
1672
|
-
return "" if titles.nil?
|
|
933
|
+
return "" if titles.nil?
|
|
934
|
+
return extract_text_value(titles).to_s unless titles.is_a?(Array)
|
|
935
|
+
return "" if titles.empty?
|
|
1673
936
|
|
|
1674
|
-
titles = Array(titles)
|
|
1675
937
|
title = titles.first
|
|
1676
938
|
extract_text_value(title).to_s
|
|
1677
939
|
end
|
|
@@ -1709,29 +971,20 @@ module Metanorma
|
|
|
1709
971
|
end
|
|
1710
972
|
|
|
1711
973
|
def render_footnotes_section
|
|
1712
|
-
return if @footnote_collector.empty?
|
|
974
|
+
return nil if @footnote_collector.empty?
|
|
1713
975
|
|
|
1714
976
|
drops = @footnote_collector.to_a.map do |entry|
|
|
1715
977
|
content_html = ""
|
|
1716
978
|
if entry.content && !entry.content.empty?
|
|
1717
|
-
content_html =
|
|
1718
|
-
|
|
1719
|
-
end
|
|
979
|
+
content_html = Array(entry.content).filter_map do |p|
|
|
980
|
+
render_paragraph(p)
|
|
981
|
+
end.join
|
|
1720
982
|
end
|
|
1721
983
|
Drops::FootnoteDrop.new(entry, content_html)
|
|
1722
984
|
end
|
|
1723
985
|
|
|
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
|
|
986
|
+
render_liquid("_footnotes.html.liquid",
|
|
987
|
+
{ "footnotes" => drops })
|
|
1735
988
|
end
|
|
1736
989
|
end
|
|
1737
990
|
end
|