metanorma-document 0.2.12 → 0.2.13
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/.github/workflows/rake.yml +21 -0
- data/.github/workflows/release.yml +25 -0
- data/.github/workflows/roundtrip-samples.yml +132 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.rubocop.yml +22 -0
- data/.rubocop_todo.yml +66 -164
- data/CHANGELOG.md +1 -0
- data/Gemfile +32 -0
- data/README.adoc +0 -144
- data/Rakefile +2 -14
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/data/javascripts/components/mn-lightbox.js +2 -18
- data/data/javascripts/components/mn-shortcuts.js +0 -2
- data/data/javascripts/core/mn-navigation.js +0 -8
- data/data/javascripts/core/mn-theme.js +9 -9
- data/data/logos/pdfa-logo.svg +94 -31
- data/data/stylesheets/base/_layout.css +0 -3
- data/data/stylesheets/base/_reset.css +3 -3
- data/data/stylesheets/base/_typography.css +5 -0
- data/data/stylesheets/components/bibliography.css +3 -3
- data/data/stylesheets/components/cover.css +1 -3
- data/data/stylesheets/components/figure.css +1 -13
- data/data/stylesheets/components/footnote.css +5 -65
- data/data/stylesheets/components/formula.css +0 -44
- data/data/stylesheets/components/header.css +2 -17
- data/data/stylesheets/components/inline.css +1 -1
- data/data/stylesheets/components/table.css +2 -15
- data/data/stylesheets/components/term.css +1 -0
- data/lib/metanorma/bipm_document/metadata/bipm_bibliographic_item.rb +14 -0
- data/lib/metanorma/bipm_document/metadata.rb +1 -1
- data/lib/metanorma/document/components/ancillary_blocks/sourcecode_block.rb +65 -0
- data/lib/metanorma/document/components/ancillary_blocks.rb +0 -4
- data/lib/metanorma/document/components/bib_data/bib_data.rb +0 -7
- data/lib/metanorma/document/components/blocks/requirement_model.rb +131 -0
- data/lib/metanorma/document/components/blocks.rb +7 -8
- data/lib/metanorma/document/components/id_elements/image.rb +0 -2
- data/lib/metanorma/document/components/inline/asciimath_element.rb +0 -2
- data/lib/metanorma/document/components/inline/attribution_element.rb +0 -2
- data/lib/metanorma/document/components/inline/bcp14_element.rb +0 -2
- data/lib/metanorma/document/components/inline/biblio_tag_element.rb +0 -2
- data/lib/metanorma/document/components/inline/br_element.rb +0 -2
- data/lib/metanorma/document/components/inline/comma_element.rb +0 -2
- data/lib/metanorma/document/components/inline/concept_element.rb +0 -2
- data/lib/metanorma/document/components/inline/display_text_element.rb +0 -2
- data/lib/metanorma/document/components/inline/em_raw_element.rb +0 -2
- data/lib/metanorma/document/components/inline/enum_comma_element.rb +0 -2
- data/lib/metanorma/document/components/inline/eref_element.rb +0 -2
- data/lib/metanorma/document/components/inline/fmt_admitted_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fmt_annotation_body_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fmt_annotation_end_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fmt_annotation_start_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fmt_concept_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fmt_definition_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fmt_fn_body_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fmt_fn_label_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fmt_footnote_container_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fmt_identifier_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fmt_name_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fmt_preferred_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fmt_sourcecode_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fmt_stem_element.rb +0 -10
- data/lib/metanorma/document/components/inline/fmt_termsource_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fmt_title_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fmt_xref_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fmt_xref_label_element.rb +0 -4
- data/lib/metanorma/document/components/inline/fn_element.rb +0 -2
- data/lib/metanorma/document/components/inline/link_element.rb +0 -2
- data/lib/metanorma/document/components/inline/localized_string_element.rb +0 -2
- data/lib/metanorma/document/components/inline/localized_strings_element.rb +0 -2
- data/lib/metanorma/document/components/inline/math_element.rb +0 -2
- data/lib/metanorma/document/components/inline/name_with_id_element.rb +0 -4
- data/lib/metanorma/document/components/inline/semx_child_element.rb +0 -2
- data/lib/metanorma/document/components/inline/semx_element.rb +1 -13
- data/lib/metanorma/document/components/inline/small_cap_element.rb +0 -2
- data/lib/metanorma/document/components/inline/span_element.rb +15 -10
- data/lib/metanorma/document/components/inline/stem_inline_element.rb +1 -7
- data/lib/metanorma/document/components/inline/strong_raw_element.rb +0 -2
- data/lib/metanorma/document/components/inline/sub_element.rb +0 -2
- data/lib/metanorma/document/components/inline/sup_element.rb +0 -2
- data/lib/metanorma/document/components/inline/tab_element.rb +0 -2
- data/lib/metanorma/document/components/inline/title_with_annotation_element.rb +0 -4
- data/lib/metanorma/document/components/inline/tt_element.rb +0 -2
- data/lib/metanorma/document/components/inline/variant_title_element.rb +0 -2
- data/lib/metanorma/document/components/inline/xref_element.rb +0 -10
- data/lib/metanorma/document/components/inline.rb +0 -12
- data/lib/metanorma/document/components/multi_paragraph/quote_block.rb +29 -0
- data/lib/metanorma/document/components/multi_paragraph.rb +0 -2
- data/lib/metanorma/document/components/reference_elements/source_element.rb +31 -0
- data/lib/metanorma/document/components/reference_elements.rb +2 -2
- data/lib/metanorma/document/components/tables/table_block.rb +22 -0
- data/lib/metanorma/document/components/tables/table_section.rb +25 -0
- data/lib/metanorma/document/components/tables.rb +3 -5
- data/lib/metanorma/document/relaton/address.rb +27 -0
- data/lib/metanorma/document/relaton/bib_item_locality.rb +12 -3
- data/lib/metanorma/document/relaton/copyright_association.rb +10 -0
- data/lib/metanorma/document/relaton/edition.rb +7 -4
- data/lib/metanorma/document/relaton/organization.rb +14 -0
- data/lib/metanorma/document/relaton/phone.rb +9 -3
- data/lib/metanorma/document/relaton/place_type.rb +13 -0
- data/lib/metanorma/document/relaton/price_type.rb +9 -3
- data/lib/metanorma/document/relaton.rb +1 -6
- data/lib/metanorma/document/version.rb +1 -1
- data/lib/metanorma/document.rb +0 -2
- data/lib/metanorma/html/asset_pipeline.rb +0 -1
- data/lib/metanorma/html/base_renderer.rb +1008 -261
- data/lib/metanorma/html/bipm_renderer.rb +144 -15
- data/lib/metanorma/html/cc_renderer.rb +45 -1
- data/lib/metanorma/html/component/base.rb +7 -2
- data/lib/metanorma/html/component/index_section.rb +43 -41
- data/lib/metanorma/html/drops/admonition_drop.rb +4 -1
- data/lib/metanorma/html/drops/block_element_drop.rb +2 -7
- data/lib/metanorma/html/drops/example_drop.rb +4 -1
- data/lib/metanorma/html/drops/figure_drop.rb +22 -31
- data/lib/metanorma/html/drops/footnote_drop.rb +1 -1
- data/lib/metanorma/html/drops/formula_drop.rb +16 -45
- data/lib/metanorma/html/drops/note_drop.rb +7 -9
- data/lib/metanorma/html/drops/sourcecode_drop.rb +5 -12
- data/lib/metanorma/html/drops.rb +0 -2
- data/lib/metanorma/html/generator.rb +2 -2
- data/lib/metanorma/html/icc_renderer.rb +39 -0
- data/lib/metanorma/html/iec_renderer.rb +44 -1
- data/lib/metanorma/html/ieee_renderer.rb +45 -1
- data/lib/metanorma/html/ietf_renderer.rb +48 -1
- data/lib/metanorma/html/iho_renderer.rb +46 -1
- data/lib/metanorma/html/iso_renderer.rb +343 -290
- data/lib/metanorma/html/itu_renderer.rb +45 -1
- data/lib/metanorma/html/ogc_renderer.rb +76 -15
- data/lib/metanorma/html/oiml_renderer.rb +38 -18
- data/lib/metanorma/html/pdfa_renderer.rb +39 -0
- data/lib/metanorma/html/ribose_renderer.rb +45 -1
- data/lib/metanorma/html/standard_renderer.rb +229 -341
- 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/_iso_cover.html.liquid +1 -1
- data/lib/metanorma/html/templates/document.html.liquid +0 -4
- data/lib/metanorma/html/theme.rb +107 -180
- data/lib/metanorma/html.rb +3 -14
- data/lib/metanorma/ietf_document/metadata/ietf_bib_data_extension_type.rb +25 -0
- data/lib/metanorma/ietf_document/metadata.rb +0 -2
- data/lib/metanorma/iso_document/metadata/iso_bib_data_extension_type.rb +48 -0
- data/lib/metanorma/iso_document/metadata/iso_bibliographic_item.rb +72 -0
- data/lib/metanorma/iso_document/metadata/iso_document_status.rb +30 -0
- data/lib/metanorma/iso_document/metadata/iso_localized_title.rb +1 -4
- data/lib/metanorma/iso_document/metadata/structured_identifier.rb +17 -0
- data/lib/metanorma/iso_document/metadata/title_collection.rb +0 -1
- data/lib/metanorma/iso_document/metadata.rb +4 -9
- data/lib/metanorma/iso_document/terms/iso_term.rb +13 -0
- data/lib/metanorma/iso_document/terms.rb +0 -1
- data/lib/metanorma/itu_document/metadata/itu_bib_data_extension_type.rb +37 -0
- data/lib/metanorma/itu_document/metadata.rb +4 -3
- data/lib/metanorma/standard_document/block_attributes.rb +9 -5
- data/lib/metanorma/standard_document/blocks/block_source.rb +23 -0
- data/lib/metanorma/standard_document/blocks.rb +1 -1
- data/lib/metanorma/standard_document/terms/term_expression.rb +31 -0
- data/lib/metanorma/standard_document/terms.rb +0 -1
- data/metanorma-document.gemspec +35 -0
- metadata +13 -262
- data/CLAUDE.md +0 -26
- data/TODO.finalize/01-split-multi-class-files.md +0 -8
- data/TODO.finalize/02-remove-data-dist-iso-document-frontend.md +0 -6
- data/TODO.finalize/03-align-frontend-iife-and-gha-build.md +0 -11
- data/TODO.finalize/04-theme-per-flavor-config-driven.md +0 -9
- data/TODO.finalize/05-migrate-all-html-to-liquid.md +0 -16
- data/TODO.finalize/06-integrate-pubid-library.md +0 -16
- data/TODO.finalize/07-replace-relaton-models-with-relaton-bib.md +0 -39
- data/TODO.finalize/08-refactor-renderer-architecture.md +0 -25
- data/TODO.finalize/09-eliminate-code-smells.md +0 -14
- data/TODO.finalize/10-comprehensive-spec-coverage.md +0 -24
- data/TODO.finalize/11-config-driven-flavor-metadata.md +0 -20
- data/TODO.finalize/12-add-frozen-string-literal.md +0 -6
- data/TODO.finalize/13-extract-composition-from-god-class.md +0 -96
- data/TODO.finalize/14-config-driven-section-ordering.md +0 -31
- data/TODO.finalize/15-theme-directories-per-flavor.md +0 -46
- data/TODO.finalize/16-theme-lutaml-model-schema.md +0 -44
- data/data/javascripts/components/mn-footnotes.js +0 -68
- data/data/logos/oiml-logo-full-dark.svg +0 -1
- data/data/logos/oiml-logo-full-light.svg +0 -1
- data/data/logos/oiml-logo-icon-dark.svg +0 -1
- data/data/logos/oiml-logo-icon-light.svg +0 -1
- data/data/logos/oiml-logo.svg +0 -1
- data/data/themes/bipm.yaml +0 -60
- data/data/themes/cc.yaml +0 -43
- data/data/themes/icc.yaml +0 -39
- data/data/themes/iec.yaml +0 -44
- data/data/themes/ieee.yaml +0 -43
- data/data/themes/ietf.yaml +0 -45
- data/data/themes/iho.yaml +0 -46
- data/data/themes/iso.yaml +0 -40
- data/data/themes/itu.yaml +0 -45
- data/data/themes/ogc.yaml +0 -51
- data/data/themes/oiml.yaml +0 -56
- data/data/themes/pdfa.yaml +0 -44
- data/data/themes/ribose.yaml +0 -43
- data/exe/metanorma-document +0 -11
- data/frontend/e2e/sample-html.test.ts +0 -256
- data/frontend/index.html +0 -12
- data/frontend/package-lock.json +0 -4725
- data/frontend/package.json +0 -32
- data/frontend/postcss.config.js +0 -6
- data/frontend/src/App.vue +0 -79
- data/frontend/src/app.ts +0 -21
- data/frontend/src/components/AppSidebar.vue +0 -77
- data/frontend/src/components/DocumentReader.vue +0 -180
- data/frontend/src/components/MirrorRenderer.vue +0 -23
- data/frontend/src/components/ReadingProgressBar.vue +0 -29
- data/frontend/src/components/TextRenderer.vue +0 -131
- data/frontend/src/components/TocItem.vue +0 -57
- data/frontend/src/components/TopBar.vue +0 -49
- data/frontend/src/components/__tests__/MirrorRenderer.test.ts +0 -178
- data/frontend/src/components/blocks/AdmonitionBlock.vue +0 -74
- data/frontend/src/components/blocks/ClauseBlock.vue +0 -53
- data/frontend/src/components/blocks/ExampleBlock.vue +0 -21
- data/frontend/src/components/blocks/FallbackBlock.vue +0 -18
- data/frontend/src/components/blocks/FigureBlock.vue +0 -26
- data/frontend/src/components/blocks/FootnotesBlock.vue +0 -25
- data/frontend/src/components/blocks/ListBlocks.vue +0 -62
- data/frontend/src/components/blocks/NoteBlock.vue +0 -21
- data/frontend/src/components/blocks/ParagraphBlock.vue +0 -25
- data/frontend/src/components/blocks/QuoteBlock.vue +0 -25
- data/frontend/src/components/blocks/ReviewBlock.vue +0 -39
- data/frontend/src/components/blocks/SourcecodeBlock.vue +0 -52
- data/frontend/src/components/blocks/TableBlock.vue +0 -48
- data/frontend/src/components/blocks/index.ts +0 -36
- data/frontend/src/stores/__tests__/documentStore.fixture.test.ts +0 -272
- data/frontend/src/stores/__tests__/documentStore.test.ts +0 -143
- data/frontend/src/stores/__tests__/ebookStore.test.ts +0 -145
- data/frontend/src/stores/__tests__/uiStore.test.ts +0 -42
- data/frontend/src/stores/documentStore.ts +0 -153
- data/frontend/src/stores/ebookStore.ts +0 -179
- data/frontend/src/stores/uiStore.ts +0 -32
- data/frontend/src/styles.css +0 -156
- data/frontend/src/types/__tests__/rendering-contract.test.ts +0 -172
- data/frontend/src/types/rendering-contract.ts +0 -209
- data/frontend/src/utils/__tests__/typeMetadata.test.ts +0 -102
- data/frontend/src/utils/typeMetadata.ts +0 -121
- data/frontend/tailwind.config.js +0 -18
- data/frontend/test/fixtures/iso-document.json +0 -271
- data/frontend/tmp/debug_e2e.cjs +0 -46
- data/frontend/tsconfig.json +0 -25
- data/frontend/tsconfig.node.json +0 -11
- data/frontend/vite.config.ts +0 -37
- data/frontend/vitest.e2e.config.ts +0 -9
- data/lib/metanorma/bipm_document/metadata/depiction_element.rb +0 -18
- data/lib/metanorma/document/cli.rb +0 -116
- data/lib/metanorma/document/components/ancillary_blocks/callout_annotation.rb +0 -27
- data/lib/metanorma/document/components/ancillary_blocks/sourcecode_body.rb +0 -18
- data/lib/metanorma/document/components/blocks/classification_value.rb +0 -21
- data/lib/metanorma/document/components/blocks/permission_model.rb +0 -28
- data/lib/metanorma/document/components/blocks/recommendation_model.rb +0 -28
- data/lib/metanorma/document/components/blocks/requirement_base.rb +0 -30
- data/lib/metanorma/document/components/blocks/requirement_classification.rb +0 -20
- data/lib/metanorma/document/components/blocks/requirement_description.rb +0 -43
- data/lib/metanorma/document/components/blocks/requirement_inherit.rb +0 -24
- data/lib/metanorma/document/components/inline/location_element.rb +0 -24
- data/lib/metanorma/document/components/inline/rendered_display.rb +0 -23
- data/lib/metanorma/document/components/inline/rendered_math_element.rb +0 -21
- data/lib/metanorma/document/components/inline/semantic_content.rb +0 -51
- data/lib/metanorma/document/components/inline/semantic_math_element.rb +0 -20
- data/lib/metanorma/document/components/inline/vocabulary.rb +0 -184
- data/lib/metanorma/document/components/multi_paragraph/quote_author_element.rb +0 -20
- data/lib/metanorma/document/components/reference_elements/source_modification.rb +0 -21
- data/lib/metanorma/document/components/reference_elements/source_origin.rb +0 -28
- data/lib/metanorma/document/components/tables/col_element.rb +0 -18
- data/lib/metanorma/document/components/tables/col_group_element.rb +0 -18
- data/lib/metanorma/document/components/tables/table_body_section.rb +0 -16
- data/lib/metanorma/document/components/tables/table_foot_section.rb +0 -16
- data/lib/metanorma/document/components/tables/table_head_section.rb +0 -16
- data/lib/metanorma/document/relaton/copyright_owner.rb +0 -16
- data/lib/metanorma/document/relaton/formatted_address.rb +0 -29
- data/lib/metanorma/document/relaton/logo_element.rb +0 -18
- data/lib/metanorma/document/relaton/region_element.rb +0 -18
- data/lib/metanorma/html/drops/biblio_entry_drop.rb +0 -50
- data/lib/metanorma/html/renderers/block_renderer.rb +0 -372
- data/lib/metanorma/html/renderers/inline_renderer.rb +0 -655
- data/lib/metanorma/html/renderers/pubid_renderer.rb +0 -69
- data/lib/metanorma/html/renderers/section_renderer.rb +0 -193
- data/lib/metanorma/html/renderers.rb +0 -8
- data/lib/metanorma/html/templates/_audio.html.liquid +0 -1
- data/lib/metanorma/html/templates/_biblio_entry.html.liquid +0 -1
- data/lib/metanorma/html/templates/_bipm_cover.html.liquid +0 -27
- data/lib/metanorma/html/templates/_boilerplate.html.liquid +0 -1
- data/lib/metanorma/html/templates/_bookmark.html.liquid +0 -1
- data/lib/metanorma/html/templates/_br.html.liquid +0 -1
- data/lib/metanorma/html/templates/_definition_list.html.liquid +0 -6
- data/lib/metanorma/html/templates/_element.html.liquid +0 -1
- data/lib/metanorma/html/templates/_fn_marker.html.liquid +0 -1
- data/lib/metanorma/html/templates/_heading.html.liquid +0 -1
- data/lib/metanorma/html/templates/_image.html.liquid +0 -1
- data/lib/metanorma/html/templates/_inline_span.html.liquid +0 -1
- data/lib/metanorma/html/templates/_link.html.liquid +0 -1
- data/lib/metanorma/html/templates/_list.html.liquid +0 -3
- data/lib/metanorma/html/templates/_list_item.html.liquid +0 -1
- data/lib/metanorma/html/templates/_main_content.html.liquid +0 -1
- data/lib/metanorma/html/templates/_math_container.html.liquid +0 -1
- data/lib/metanorma/html/templates/_paragraph.html.liquid +0 -1
- data/lib/metanorma/html/templates/_prefatory_section.html.liquid +0 -1
- data/lib/metanorma/html/templates/_pubid_identifier.html.liquid +0 -1
- data/lib/metanorma/html/templates/_quote.html.liquid +0 -4
- data/lib/metanorma/html/templates/_ref_date.html.liquid +0 -1
- data/lib/metanorma/html/templates/_ref_title.html.liquid +0 -1
- data/lib/metanorma/html/templates/_section.html.liquid +0 -5
- data/lib/metanorma/html/templates/_standard_cover.html.liquid +0 -31
- data/lib/metanorma/html/templates/_standard_doc_title.html.liquid +0 -3
- data/lib/metanorma/html/templates/_stem_span.html.liquid +0 -1
- data/lib/metanorma/html/templates/_table.html.liquid +0 -9
- data/lib/metanorma/html/templates/_term_domain.html.liquid +0 -1
- data/lib/metanorma/html/templates/_term_example.html.liquid +0 -1
- data/lib/metanorma/html/templates/_term_note.html.liquid +0 -1
- data/lib/metanorma/html/templates/_term_number.html.liquid +0 -1
- data/lib/metanorma/html/templates/_term_source.html.liquid +0 -1
- data/lib/metanorma/html/templates/_term_text_note.html.liquid +0 -1
- data/lib/metanorma/html/templates/_video.html.liquid +0 -1
- data/lib/metanorma/html/templates/_wrapped_preface.html.liquid +0 -1
- data/lib/metanorma/ietf_document/metadata/pi_settings.rb +0 -32
- data/lib/metanorma/iso_document/metadata/doctype_element.rb +0 -20
- data/lib/metanorma/iso_document/metadata/language_element.rb +0 -18
- data/lib/metanorma/iso_document/metadata/project_number.rb +0 -22
- data/lib/metanorma/iso_document/metadata/script_element.rb +0 -18
- data/lib/metanorma/iso_document/metadata/stage_element.rb +0 -26
- data/lib/metanorma/iso_document/metadata/stagename_element.rb +0 -18
- data/lib/metanorma/iso_document/terms/term_domain_element.rb +0 -18
- data/lib/metanorma/itu_document/metadata/meeting_date.rb +0 -18
- data/lib/metanorma/itu_document/metadata/meeting_element.rb +0 -18
- data/lib/metanorma/itu_document/metadata/study_period.rb +0 -18
- data/lib/metanorma/mirror/default_registry.rb +0 -170
- data/lib/metanorma/mirror/handler_registry.rb +0 -60
- data/lib/metanorma/mirror/handler_result.rb +0 -37
- data/lib/metanorma/mirror/handlers/admonition.rb +0 -18
- data/lib/metanorma/mirror/handlers/example.rb +0 -21
- data/lib/metanorma/mirror/handlers/figure.rb +0 -53
- data/lib/metanorma/mirror/handlers/formula.rb +0 -35
- data/lib/metanorma/mirror/handlers/inline/rich_html_renderer.rb +0 -159
- data/lib/metanorma/mirror/handlers/inline/text_extractor.rb +0 -116
- data/lib/metanorma/mirror/handlers/inline.rb +0 -225
- data/lib/metanorma/mirror/handlers/list.rb +0 -110
- data/lib/metanorma/mirror/handlers/note.rb +0 -26
- data/lib/metanorma/mirror/handlers/paragraph.rb +0 -19
- data/lib/metanorma/mirror/handlers/quote.rb +0 -16
- data/lib/metanorma/mirror/handlers/review.rb +0 -19
- data/lib/metanorma/mirror/handlers/section.rb +0 -149
- data/lib/metanorma/mirror/handlers/sourcecode.rb +0 -27
- data/lib/metanorma/mirror/handlers/structural.rb +0 -68
- data/lib/metanorma/mirror/handlers/table.rb +0 -81
- data/lib/metanorma/mirror/handlers/term.rb +0 -100
- data/lib/metanorma/mirror/handlers.rb +0 -60
- data/lib/metanorma/mirror/id_strategy/positional.rb +0 -166
- data/lib/metanorma/mirror/id_strategy/preserve.rb +0 -12
- data/lib/metanorma/mirror/id_strategy.rb +0 -34
- data/lib/metanorma/mirror/math_util.rb +0 -53
- data/lib/metanorma/mirror/metadata.rb +0 -37
- data/lib/metanorma/mirror/metanorma_to_mirror.rb +0 -101
- data/lib/metanorma/mirror/model/container.rb +0 -47
- data/lib/metanorma/mirror/model/factory.rb +0 -52
- data/lib/metanorma/mirror/model/guide.rb +0 -25
- data/lib/metanorma/mirror/model/leaf.rb +0 -17
- data/lib/metanorma/mirror/model/mark.rb +0 -53
- data/lib/metanorma/mirror/model/node.rb +0 -47
- data/lib/metanorma/mirror/model/soft_break.rb +0 -33
- data/lib/metanorma/mirror/model/text.rb +0 -34
- data/lib/metanorma/mirror/model.rb +0 -16
- data/lib/metanorma/mirror/output/builder.rb +0 -39
- data/lib/metanorma/mirror/output/formats/base_format.rb +0 -90
- data/lib/metanorma/mirror/output/formats/inline_format.rb +0 -81
- data/lib/metanorma/mirror/output/formats.rb +0 -53
- data/lib/metanorma/mirror/output/html_renderer.rb +0 -95
- data/lib/metanorma/mirror/output/html_renderers/block_renderers.rb +0 -178
- data/lib/metanorma/mirror/output/html_renderers/inline_renderer.rb +0 -61
- data/lib/metanorma/mirror/output/html_renderers/list_renderers.rb +0 -96
- data/lib/metanorma/mirror/output/html_renderers/mark_renderers.rb +0 -64
- data/lib/metanorma/mirror/output/html_renderers/section_renderers.rb +0 -129
- data/lib/metanorma/mirror/output/html_renderers/structural_renderers.rb +0 -80
- data/lib/metanorma/mirror/output/html_renderers/table_renderers.rb +0 -87
- data/lib/metanorma/mirror/output/html_renderers.rb +0 -76
- data/lib/metanorma/mirror/output/pipeline.rb +0 -92
- data/lib/metanorma/mirror/output/pipeline_context.rb +0 -22
- data/lib/metanorma/mirror/output.rb +0 -14
- data/lib/metanorma/mirror/rewriter.rb +0 -121
- data/lib/metanorma/mirror/safe_attr.rb +0 -16
- data/lib/metanorma/mirror/serialization/json_serializer.rb +0 -25
- data/lib/metanorma/mirror/serialization/yaml_serializer.rb +0 -20
- data/lib/metanorma/mirror/serialization.rb +0 -10
- data/lib/metanorma/mirror/transformer.rb +0 -22
- data/lib/metanorma/mirror.rb +0 -32
- data/lib/metanorma/standard_document/annotation.rb +0 -37
- data/lib/metanorma/standard_document/blocks/source_origin_element.rb +0 -23
- data/lib/metanorma/standard_document/terms/term_name_element.rb +0 -29
- data/script/render_both.rb +0 -18
|
@@ -6,16 +6,15 @@ require "cgi"
|
|
|
6
6
|
|
|
7
7
|
module Metanorma
|
|
8
8
|
module Html
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
autoload :PubidRenderer, "metanorma/html/renderers/pubid_renderer"
|
|
14
|
-
end
|
|
15
|
-
|
|
9
|
+
# Renders BasicDocument components to HTML.
|
|
10
|
+
# Subclassed by StandardRenderer and flavor-specific renderers.
|
|
11
|
+
# Owns the full HTML document generation pipeline: body content, header,
|
|
12
|
+
# footer, ToC sidebar, CSS (via Theme), and JavaScript.
|
|
16
13
|
class BaseRenderer
|
|
17
14
|
LOGO_DIR = File.expand_path("../../../data/logos", __dir__)
|
|
18
15
|
|
|
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.
|
|
19
18
|
SPAN_ROLE_CLASSES = {
|
|
20
19
|
"boldtitle" => "title-text",
|
|
21
20
|
"nonboldtitle" => "subtitle-text",
|
|
@@ -45,6 +44,8 @@ module Metanorma
|
|
|
45
44
|
|
|
46
45
|
METANORMA_LOGO = "metanorma-logo.svg"
|
|
47
46
|
|
|
47
|
+
# Type-to-method registry for OCP dispatch.
|
|
48
|
+
# Each subclass gets its own hash; lookup traverses ancestors.
|
|
48
49
|
class << self
|
|
49
50
|
def render_registry
|
|
50
51
|
@render_registry ||= {}
|
|
@@ -63,12 +64,8 @@ module Metanorma
|
|
|
63
64
|
end
|
|
64
65
|
end
|
|
65
66
|
|
|
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
|
-
|
|
71
67
|
def initialize
|
|
68
|
+
@output = +""
|
|
72
69
|
@toc_entries = []
|
|
73
70
|
@figure_entries = []
|
|
74
71
|
@table_entries = []
|
|
@@ -76,107 +73,86 @@ module Metanorma
|
|
|
76
73
|
@footnote_collector = Component::FootnoteCollector.new
|
|
77
74
|
@current_section_id = nil
|
|
78
75
|
@current_section_number = nil
|
|
79
|
-
|
|
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
76
|
end
|
|
85
77
|
|
|
78
|
+
# --- Public API ---
|
|
79
|
+
|
|
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.
|
|
86
84
|
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
|
+
|
|
87
95
|
def initialize(renderer)
|
|
88
96
|
@renderer = renderer
|
|
89
97
|
end
|
|
90
98
|
|
|
91
|
-
def
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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(...)
|
|
99
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
100
|
+
DELEGATED_METHODS.include?(method_name) || super
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def method_missing(method_name, ...)
|
|
106
|
+
if DELEGATED_METHODS.include?(method_name)
|
|
107
|
+
@renderer.public_send(method_name, ...)
|
|
108
|
+
else
|
|
109
|
+
super
|
|
110
|
+
end
|
|
111
|
+
end
|
|
114
112
|
end
|
|
115
113
|
|
|
116
114
|
def renderer_context
|
|
117
115
|
@renderer_context ||= RendererContext.new(self)
|
|
118
116
|
end
|
|
119
117
|
|
|
118
|
+
def to_html
|
|
119
|
+
@output
|
|
120
|
+
end
|
|
121
|
+
|
|
120
122
|
def toc_entries
|
|
121
123
|
@toc_entries
|
|
122
124
|
end
|
|
123
125
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def generate_full_document(document, **)
|
|
126
|
+
# Generate a complete HTML document from a presentation XML document.
|
|
127
|
+
def generate_full_document(document)
|
|
127
128
|
@document = document
|
|
128
129
|
validate_presentation_xml!
|
|
129
130
|
|
|
130
|
-
body
|
|
131
|
+
# First pass: render body content (collects ToC entries as side effect)
|
|
132
|
+
render(@document)
|
|
133
|
+
body = @output
|
|
131
134
|
|
|
132
135
|
assemble_document(body)
|
|
133
136
|
end
|
|
134
137
|
|
|
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
|
|
138
|
+
# --- Flavor configuration hooks (override in subclasses) ---
|
|
152
139
|
|
|
153
140
|
def theme
|
|
154
|
-
@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] }
|
|
141
|
+
@theme ||= Theme.new
|
|
168
142
|
end
|
|
169
143
|
|
|
170
144
|
def flavor_publishers(_doc_id)
|
|
171
|
-
|
|
145
|
+
[]
|
|
172
146
|
end
|
|
173
147
|
|
|
174
148
|
def flavor_publisher_name
|
|
175
|
-
|
|
149
|
+
pubs = flavor_publishers(extract_primary_doc_id)
|
|
150
|
+
pubs.empty? ? nil : pubs.join("/")
|
|
176
151
|
end
|
|
177
152
|
|
|
153
|
+
# Map of publisher name => logo filename. Override in flavor renderers.
|
|
178
154
|
def publisher_logo_map
|
|
179
|
-
|
|
155
|
+
{}
|
|
180
156
|
end
|
|
181
157
|
|
|
182
158
|
def flavor_font_url
|
|
@@ -189,7 +165,7 @@ module Metanorma
|
|
|
189
165
|
TEMPLATE_CACHE_MUTEX = Mutex.new
|
|
190
166
|
|
|
191
167
|
def render_liquid(template_name, assigns)
|
|
192
|
-
template_path =
|
|
168
|
+
template_path = File.join(TEMPLATES_ROOT, template_name)
|
|
193
169
|
template = TEMPLATE_CACHE_MUTEX.synchronize do
|
|
194
170
|
TEMPLATE_CACHE[template_path] ||= Liquid::Template.parse(File.read(template_path))
|
|
195
171
|
end
|
|
@@ -235,7 +211,7 @@ module Metanorma
|
|
|
235
211
|
end
|
|
236
212
|
|
|
237
213
|
def header_title_text
|
|
238
|
-
raw = html_title.to_s.split(" — ").first.to_s
|
|
214
|
+
raw = html_title.to_s.split(" — ").first.to_s.gsub(/<[^>]+>/, "")
|
|
239
215
|
raw.length > 60 ? "#{raw[0, 57]}..." : raw
|
|
240
216
|
end
|
|
241
217
|
|
|
@@ -244,7 +220,7 @@ module Metanorma
|
|
|
244
220
|
logo_map = publisher_logo_map
|
|
245
221
|
return "" if publishers.empty? && logo_map.empty?
|
|
246
222
|
|
|
247
|
-
|
|
223
|
+
# Use flavor-declared publishers; fall back to logo map keys
|
|
248
224
|
display_pubs = publishers.empty? ? logo_map.keys : publishers
|
|
249
225
|
|
|
250
226
|
display_pubs.filter_map do |pub|
|
|
@@ -254,28 +230,18 @@ module Metanorma
|
|
|
254
230
|
svg = load_logo_svg(filename, height: 26)
|
|
255
231
|
next unless svg
|
|
256
232
|
|
|
257
|
-
|
|
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}"
|
|
233
|
+
"<span class=\"brand-logo\" aria-label=\"#{pub} logo\">#{svg}</span>"
|
|
269
234
|
end.join("\n")
|
|
270
235
|
end
|
|
271
236
|
|
|
272
237
|
def load_logo_svg(filename, height: 32)
|
|
273
|
-
path =
|
|
238
|
+
path = File.join(LOGO_DIR, filename)
|
|
274
239
|
return nil unless File.exist?(path)
|
|
275
240
|
|
|
276
241
|
svg = File.read(path)
|
|
277
242
|
svg = svg.sub(/\A<\?xml[^?]*\?>\s*/, "")
|
|
278
243
|
svg = svg.sub(/\A\s*<!--.*?-->\s*/m, "")
|
|
244
|
+
svg = svg.sub(/<path[^>]*style="fill:#e3000f[^"]*"[^>]*\/>/, "")
|
|
279
245
|
svg = svg.sub(/<svg\s/, '<svg class="header-logo" ')
|
|
280
246
|
svg = if svg.match?(/<svg[^>]*\sheight="[^"]*"/)
|
|
281
247
|
svg.sub(/(<svg[^>]*?)(\sheight="[^"]*")/,
|
|
@@ -331,10 +297,7 @@ module Metanorma
|
|
|
331
297
|
def build_styles
|
|
332
298
|
pipeline = AssetPipeline.new
|
|
333
299
|
css = pipeline.compile_css(flavor_css: flavor_css_module)
|
|
334
|
-
|
|
335
|
-
custom_css_path = theme.theme_css_path
|
|
336
|
-
parts << File.read(custom_css_path) if custom_css_path
|
|
337
|
-
parts.join("\n")
|
|
300
|
+
"#{theme.to_css_root}\n#{css}\n#{theme.to_css_extras}"
|
|
338
301
|
end
|
|
339
302
|
|
|
340
303
|
# --- Validation ---
|
|
@@ -353,11 +316,12 @@ module Metanorma
|
|
|
353
316
|
return false unless node
|
|
354
317
|
return false if node.is_a?(String)
|
|
355
318
|
|
|
319
|
+
if node.is_a?(Metanorma::Document::Root) && node.type == "presentation"
|
|
320
|
+
return true
|
|
321
|
+
end
|
|
322
|
+
|
|
356
323
|
if node.is_a?(Lutaml::Model::Serializable)
|
|
357
324
|
node_attrs = node.class.attributes
|
|
358
|
-
if node_attrs.key?(:type) && node.type == "presentation"
|
|
359
|
-
return true
|
|
360
|
-
end
|
|
361
325
|
if node_attrs.key?(:fmt_title) && node.fmt_title
|
|
362
326
|
return true
|
|
363
327
|
end
|
|
@@ -399,21 +363,16 @@ module Metanorma
|
|
|
399
363
|
end
|
|
400
364
|
|
|
401
365
|
def html_title
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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?
|
|
366
|
+
bibdata = @document.bibdata
|
|
367
|
+
return "Document" unless bibdata
|
|
413
368
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
369
|
+
titles = bibdata.titles
|
|
370
|
+
if titles
|
|
371
|
+
title = bibdata.title_for("en")
|
|
372
|
+
title.to_s
|
|
373
|
+
else
|
|
374
|
+
"Document"
|
|
375
|
+
end
|
|
417
376
|
end
|
|
418
377
|
|
|
419
378
|
def extract_primary_doc_id
|
|
@@ -434,7 +393,7 @@ module Metanorma
|
|
|
434
393
|
text.strip.empty? ? nil : text.strip
|
|
435
394
|
end
|
|
436
395
|
|
|
437
|
-
# ---
|
|
396
|
+
# --- CSS loader ---
|
|
438
397
|
|
|
439
398
|
def register_toc_entry(id:, level:, text:)
|
|
440
399
|
@toc_entries << { id: id, level: level, text: text }
|
|
@@ -444,14 +403,12 @@ module Metanorma
|
|
|
444
403
|
@figure_entries << { id: id, text: text }
|
|
445
404
|
end
|
|
446
405
|
|
|
447
|
-
def figure_entries
|
|
448
|
-
@figure_entries
|
|
449
|
-
end
|
|
450
|
-
|
|
451
406
|
def register_table_entry(id:, text:)
|
|
452
407
|
@table_entries << { id: id, text: text }
|
|
453
408
|
end
|
|
454
409
|
|
|
410
|
+
attr_reader :index_term_collector, :footnote_collector
|
|
411
|
+
|
|
455
412
|
def extract_plain_text(node)
|
|
456
413
|
return node.to_s if node.is_a?(String)
|
|
457
414
|
return extract_text_value(node).to_s unless node.is_a?(Lutaml::Model::Serializable)
|
|
@@ -474,39 +431,38 @@ module Metanorma
|
|
|
474
431
|
if el.text?
|
|
475
432
|
parts << el.text_content.to_s
|
|
476
433
|
elsif el.name == "tab"
|
|
477
|
-
parts << "
|
|
434
|
+
parts << "\u00A0\u00A0"
|
|
478
435
|
elsif el.name == "br"
|
|
479
436
|
parts << " "
|
|
480
437
|
elsif el.element?
|
|
481
438
|
attr_name = element_to_attr[el.name]
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
elsif el.name == "span"
|
|
494
|
-
parts << " "
|
|
495
|
-
end
|
|
439
|
+
next unless attr_name
|
|
440
|
+
|
|
441
|
+
coll = node.public_send(attr_name)
|
|
442
|
+
obj = if coll.is_a?(Array)
|
|
443
|
+
idx = indices[attr_name]
|
|
444
|
+
indices[attr_name] += 1
|
|
445
|
+
coll[idx]
|
|
446
|
+
else
|
|
447
|
+
coll
|
|
448
|
+
end
|
|
449
|
+
parts << extract_plain_text(obj) if obj
|
|
496
450
|
end
|
|
497
451
|
end
|
|
498
452
|
end
|
|
499
453
|
|
|
454
|
+
# Fallback: try .text
|
|
500
455
|
if parts.join.strip.empty?
|
|
501
456
|
t = safe_attr(node, :text)
|
|
502
457
|
parts << (t.is_a?(Array) ? t.join : t.to_s) if t
|
|
503
458
|
end
|
|
504
459
|
|
|
505
|
-
parts.join.strip.gsub("
|
|
460
|
+
parts.join.strip.gsub("\u00A0", " ")
|
|
506
461
|
end
|
|
507
462
|
|
|
508
|
-
#
|
|
509
|
-
|
|
463
|
+
# Dispatch to the appropriate render method via type registry.
|
|
464
|
+
# Lookups traverse the ancestor chain so subclasses inherit
|
|
465
|
+
# parent registrations and can override them independently.
|
|
510
466
|
def render(node, **)
|
|
511
467
|
return escape_html(node) if node.is_a?(String)
|
|
512
468
|
|
|
@@ -514,18 +470,20 @@ module Metanorma
|
|
|
514
470
|
method ? public_send(method, node, **) : ""
|
|
515
471
|
end
|
|
516
472
|
|
|
473
|
+
# Dispatch to the appropriate inline render method via type registry.
|
|
517
474
|
def render_inline_element(element, **)
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
475
|
+
return "" if element.nil?
|
|
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
|
|
526
484
|
end
|
|
527
485
|
|
|
528
|
-
# --- Type registrations ---
|
|
486
|
+
# --- Type registrations (class-level, evaluated at class load time) ---
|
|
529
487
|
|
|
530
488
|
register_render Metanorma::Document::Components::Paragraphs::ParagraphBlock,
|
|
531
489
|
:render_paragraph
|
|
@@ -624,6 +582,7 @@ module Metanorma
|
|
|
624
582
|
:render_index
|
|
625
583
|
register_inline_render Metanorma::Document::Components::Blocks::NoteBlock,
|
|
626
584
|
:render_note_inline
|
|
585
|
+
# All Fmt* elements delegate to render_mixed_inline
|
|
627
586
|
register_inline_render Metanorma::Document::Components::Inline::FmtNameElement,
|
|
628
587
|
:render_mixed_inline
|
|
629
588
|
register_inline_render Metanorma::Document::Components::Inline::FmtTitleElement,
|
|
@@ -688,163 +647,941 @@ module Metanorma
|
|
|
688
647
|
nil
|
|
689
648
|
end
|
|
690
649
|
|
|
691
|
-
# ---
|
|
650
|
+
# --- Block-level rendering ---
|
|
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)
|
|
692
1057
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
|
696
1143
|
end
|
|
697
1144
|
|
|
698
1145
|
def render_mixed_inline(node)
|
|
699
|
-
|
|
1146
|
+
# Models using map_all_content: raw XML in content
|
|
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
|
|
700
1169
|
end
|
|
701
1170
|
|
|
702
|
-
def
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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)
|
|
1171
|
+
def raw_content_node?(node)
|
|
1172
|
+
node.is_a?(Lutaml::Model::Serializable) &&
|
|
1173
|
+
node.content.is_a?(String)
|
|
1174
|
+
rescue NoMethodError
|
|
1175
|
+
false
|
|
1176
|
+
end
|
|
723
1177
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
1178
|
+
# Iterate element_order directly, preserving whitespace text nodes
|
|
1179
|
+
# that each_mixed_content drops (it skips text where text.strip.empty?)
|
|
1180
|
+
def render_ordered_inline(node)
|
|
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
|
|
727
1201
|
end
|
|
728
1202
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
def
|
|
732
|
-
|
|
733
|
-
|
|
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)
|
|
1203
|
+
# Inline adapter methods for registry dispatch
|
|
1204
|
+
|
|
1205
|
+
def render_em(el)
|
|
1206
|
+
render_inline_tag("em", el)
|
|
1207
|
+
end
|
|
737
1208
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
**)
|
|
741
|
-
@block_renderer.render_paragraph(p, **)
|
|
1209
|
+
def render_strong(el)
|
|
1210
|
+
render_inline_tag("strong", el)
|
|
742
1211
|
end
|
|
743
1212
|
|
|
744
|
-
def
|
|
745
|
-
|
|
746
|
-
@block_renderer.render_table(table, **)
|
|
1213
|
+
def render_tt(el)
|
|
1214
|
+
render_inline_tag("tt", el)
|
|
747
1215
|
end
|
|
748
1216
|
|
|
749
|
-
def
|
|
750
|
-
|
|
751
|
-
@block_renderer.render_unordered_list(ul, **)
|
|
1217
|
+
def render_sub(el)
|
|
1218
|
+
render_inline_tag("sub", el)
|
|
752
1219
|
end
|
|
753
1220
|
|
|
754
|
-
def
|
|
755
|
-
|
|
756
|
-
@block_renderer.render_ordered_list(ol, **)
|
|
1221
|
+
def render_sup(el)
|
|
1222
|
+
render_inline_tag("sup", el)
|
|
757
1223
|
end
|
|
758
1224
|
|
|
759
|
-
def
|
|
760
|
-
|
|
761
|
-
@block_renderer.render_definition_list(dl, **)
|
|
1225
|
+
def render_small_caps(el)
|
|
1226
|
+
render_inline_tag("span", el, class: "small-caps")
|
|
762
1227
|
end
|
|
763
1228
|
|
|
764
|
-
def
|
|
765
|
-
|
|
766
|
-
@block_renderer.render_figure(figure, **)
|
|
1229
|
+
def render_underline(el)
|
|
1230
|
+
render_inline_tag("u", el)
|
|
767
1231
|
end
|
|
768
1232
|
|
|
769
|
-
def
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
def render_note(note, **) = @block_renderer.render_note(note, **)
|
|
1233
|
+
def render_strike(el)
|
|
1234
|
+
render_inline_tag("s", el)
|
|
1235
|
+
end
|
|
773
1236
|
|
|
774
|
-
def
|
|
775
|
-
|
|
776
|
-
@block_renderer.render_example(example, **)
|
|
1237
|
+
def render_br(*)
|
|
1238
|
+
@output << "<br />"
|
|
777
1239
|
end
|
|
778
1240
|
|
|
779
|
-
def
|
|
780
|
-
|
|
781
|
-
@block_renderer.render_sourcecode(sc, **)
|
|
1241
|
+
def render_tab(*)
|
|
1242
|
+
@output << "\u00a0\u00a0"
|
|
782
1243
|
end
|
|
783
1244
|
|
|
784
|
-
def
|
|
785
|
-
|
|
786
|
-
|
|
1245
|
+
def render_span(el)
|
|
1246
|
+
xml_class = safe_attr(el, :class_attr).to_s
|
|
1247
|
+
html_class = html_class_for_span(xml_class) unless xml_class.empty?
|
|
1248
|
+
attrs = element_attrs(style: safe_attr(el, :style), class: html_class)
|
|
1249
|
+
tag("span", attrs) { render_mixed_inline(el) }
|
|
787
1250
|
end
|
|
788
1251
|
|
|
789
|
-
def
|
|
790
|
-
|
|
791
|
-
@block_renderer.render_quote(quote, **)
|
|
1252
|
+
def render_fn_inline(el)
|
|
1253
|
+
render_fn(el)
|
|
792
1254
|
end
|
|
793
1255
|
|
|
794
|
-
def
|
|
795
|
-
|
|
796
|
-
@block_renderer.render_admonition(admonition, **)
|
|
1256
|
+
def render_stem(el)
|
|
1257
|
+
@output << render_stem_content(el)
|
|
797
1258
|
end
|
|
798
1259
|
|
|
799
|
-
def
|
|
800
|
-
|
|
801
|
-
@block_renderer.render_bookmark(bookmark, **)
|
|
1260
|
+
def render_semx_inline(el)
|
|
1261
|
+
render_semx_content(el)
|
|
802
1262
|
end
|
|
803
1263
|
|
|
804
|
-
def
|
|
805
|
-
|
|
806
|
-
|
|
1264
|
+
def render_fmt_xref(el)
|
|
1265
|
+
target = safe_attr(el, :target) || safe_attr(el, :to_attr)
|
|
1266
|
+
if target
|
|
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
|
|
807
1272
|
end
|
|
808
1273
|
|
|
809
|
-
def
|
|
810
|
-
|
|
811
|
-
|
|
1274
|
+
def render_comma(*)
|
|
1275
|
+
@output << ", "
|
|
1276
|
+
end
|
|
812
1277
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
**)
|
|
816
|
-
@section_renderer.render_basic_section(section, **)
|
|
1278
|
+
def render_math(el)
|
|
1279
|
+
@output << el.content.to_s
|
|
817
1280
|
end
|
|
818
1281
|
|
|
819
|
-
def
|
|
820
|
-
|
|
821
|
-
@section_renderer.render_hierarchical_section(section, **)
|
|
1282
|
+
def render_asciimath(el)
|
|
1283
|
+
@output << %(<span class="stem">#{escape_html(Array(el.text).join)}</span>)
|
|
822
1284
|
end
|
|
823
1285
|
|
|
824
|
-
def
|
|
825
|
-
|
|
826
|
-
|
|
1286
|
+
def render_index(el)
|
|
1287
|
+
collect_index_term(el)
|
|
1288
|
+
""
|
|
827
1289
|
end
|
|
828
1290
|
|
|
829
|
-
def
|
|
830
|
-
|
|
831
|
-
@section_renderer.render_ordered_content(section, level)
|
|
1291
|
+
def render_note_inline(el)
|
|
1292
|
+
render_note(el)
|
|
832
1293
|
end
|
|
833
1294
|
|
|
834
|
-
def
|
|
835
|
-
|
|
1295
|
+
def render_inline_collections(node)
|
|
1296
|
+
texts = node.text
|
|
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
|
|
836
1308
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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
|
|
840
1320
|
end
|
|
841
1321
|
|
|
842
|
-
#
|
|
843
|
-
|
|
844
|
-
|
|
1322
|
+
# Render SemxElement display content only, skipping semantic linkage.
|
|
1323
|
+
# semx wraps both semantic data (origin, xref, source, etc.) and
|
|
1324
|
+
# display content (fmt-xref, span, strong, etc.). Only render display.
|
|
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
|
|
845
1576
|
|
|
846
1577
|
# --- Helper methods ---
|
|
847
1578
|
|
|
1579
|
+
def tag(name, attrs_str)
|
|
1580
|
+
@output << "<#{name}#{attrs_str}>"
|
|
1581
|
+
yield
|
|
1582
|
+
@output << "</#{name}>"
|
|
1583
|
+
end
|
|
1584
|
+
|
|
848
1585
|
def element_attrs(**attrs)
|
|
849
1586
|
parts = []
|
|
850
1587
|
attrs.each do |k, v|
|
|
@@ -907,6 +1644,7 @@ level = 1)
|
|
|
907
1644
|
end
|
|
908
1645
|
|
|
909
1646
|
def extract_block_label(block, default)
|
|
1647
|
+
# Presentation XML puts label in <name> child element
|
|
910
1648
|
names = safe_attr(block, :name)
|
|
911
1649
|
if names && !names.empty?
|
|
912
1650
|
name = names.is_a?(Array) ? names.first : names
|
|
@@ -914,6 +1652,7 @@ level = 1)
|
|
|
914
1652
|
return text unless text.to_s.strip.empty?
|
|
915
1653
|
end
|
|
916
1654
|
|
|
1655
|
+
# Fallback: autonum XML attribute
|
|
917
1656
|
autonum = safe_attr(block, :autonum)
|
|
918
1657
|
if autonum && !autonum.to_s.empty?
|
|
919
1658
|
number = autonum.to_s
|
|
@@ -930,10 +1669,9 @@ level = 1)
|
|
|
930
1669
|
end
|
|
931
1670
|
|
|
932
1671
|
def extract_title_text(titles)
|
|
933
|
-
return "" if titles.nil?
|
|
934
|
-
return extract_text_value(titles).to_s unless titles.is_a?(Array)
|
|
935
|
-
return "" if titles.empty?
|
|
1672
|
+
return "" if titles.nil? || titles.empty?
|
|
936
1673
|
|
|
1674
|
+
titles = Array(titles)
|
|
937
1675
|
title = titles.first
|
|
938
1676
|
extract_text_value(title).to_s
|
|
939
1677
|
end
|
|
@@ -971,20 +1709,29 @@ level = 1)
|
|
|
971
1709
|
end
|
|
972
1710
|
|
|
973
1711
|
def render_footnotes_section
|
|
974
|
-
return
|
|
1712
|
+
return if @footnote_collector.empty?
|
|
975
1713
|
|
|
976
1714
|
drops = @footnote_collector.to_a.map do |entry|
|
|
977
1715
|
content_html = ""
|
|
978
1716
|
if entry.content && !entry.content.empty?
|
|
979
|
-
content_html =
|
|
980
|
-
render_paragraph(p)
|
|
981
|
-
end
|
|
1717
|
+
content_html = capture_output do
|
|
1718
|
+
Array(entry.content).each { |p| render_paragraph(p) }
|
|
1719
|
+
end
|
|
982
1720
|
end
|
|
983
1721
|
Drops::FootnoteDrop.new(entry, content_html)
|
|
984
1722
|
end
|
|
985
1723
|
|
|
986
|
-
render_liquid("_footnotes.html.liquid",
|
|
987
|
-
|
|
1724
|
+
@output << render_liquid("_footnotes.html.liquid",
|
|
1725
|
+
{ "footnotes" => drops })
|
|
1726
|
+
end
|
|
1727
|
+
|
|
1728
|
+
def capture_output
|
|
1729
|
+
old_output = @output
|
|
1730
|
+
@output = +""
|
|
1731
|
+
yield
|
|
1732
|
+
result = @output
|
|
1733
|
+
@output = old_output
|
|
1734
|
+
result
|
|
988
1735
|
end
|
|
989
1736
|
end
|
|
990
1737
|
end
|