metanorma-document 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CLAUDE.md +3 -2
- data/Rakefile +30 -5
- data/frontend/dist/app.css +1 -0
- data/frontend/dist/app.iife.js +24 -0
- data/frontend/package-lock.json +7 -505
- data/frontend/tailwind.config.js +5 -0
- data/lib/metanorma/csa_document/root.rb +27 -0
- data/lib/metanorma/csa_document.rb +9 -0
- data/lib/metanorma/document/cli.rb +52 -0
- data/lib/metanorma/document/components/ancillary_blocks/sourcecode_body.rb +14 -0
- data/lib/metanorma/document/components/inline/comma_element.rb +3 -0
- data/lib/metanorma/document/components/inline/enum_comma_element.rb +3 -0
- data/lib/metanorma/document/components/inline/eref_element.rb +4 -1
- data/lib/metanorma/document/components/inline/fmt_termsource_element.rb +4 -2
- data/lib/metanorma/document/components/inline/math_element.rb +13 -7
- data/lib/metanorma/document/components/inline/vocabulary.rb +4 -0
- data/lib/metanorma/document/components/multi_paragraph/admonition_block.rb +6 -1
- data/lib/metanorma/document/components/paragraphs/paragraph_block.rb +3 -0
- data/lib/metanorma/document/components/reference_elements/callout.rb +3 -2
- data/lib/metanorma/document/components/tables/table_cell.rb +3 -0
- data/lib/metanorma/document/elements/input.rb +44 -0
- data/lib/metanorma/document/elements.rb +1 -0
- data/lib/metanorma/document/relaton/address.rb +5 -0
- data/lib/metanorma/document/relaton/affiliation.rb +3 -0
- data/lib/metanorma/document/relaton/bib_item_size.rb +8 -9
- data/lib/metanorma/document/relaton/bib_item_type.rb +3 -0
- data/lib/metanorma/document/relaton/bibliographic_date.rb +5 -0
- data/lib/metanorma/document/relaton/contact_method.rb +5 -0
- data/lib/metanorma/document/relaton/contribution_info.rb +6 -2
- data/lib/metanorma/document/relaton/contributor.rb +4 -0
- data/lib/metanorma/document/relaton/contributor_role.rb +3 -0
- data/lib/metanorma/document/relaton/copyright_association.rb +3 -0
- data/lib/metanorma/document/relaton/copyright_owner.rb +3 -0
- data/lib/metanorma/document/relaton/date_time.rb +3 -0
- data/lib/metanorma/document/relaton/document_identifier.rb +4 -0
- data/lib/metanorma/document/relaton/document_relation.rb +8 -0
- data/lib/metanorma/document/relaton/document_status.rb +4 -0
- data/lib/metanorma/document/relaton/formatted_address.rb +4 -0
- data/lib/metanorma/document/relaton/full_name.rb +14 -3
- data/lib/metanorma/document/relaton/keyword_type.rb +3 -0
- data/lib/metanorma/document/relaton/locality_stack.rb +6 -9
- data/lib/metanorma/document/relaton/localized_name.rb +8 -5
- data/lib/metanorma/document/relaton/logo_element.rb +5 -0
- data/lib/metanorma/document/relaton/medium_type.rb +5 -16
- data/lib/metanorma/document/relaton/org_identifier.rb +2 -0
- data/lib/metanorma/document/relaton/org_subdivision.rb +2 -0
- data/lib/metanorma/document/relaton/org_subdivision_identifier.rb +2 -0
- data/lib/metanorma/document/relaton/organization.rb +3 -0
- data/lib/metanorma/document/relaton/person.rb +7 -1
- data/lib/metanorma/document/relaton/person_identifier.rb +3 -0
- data/lib/metanorma/document/relaton/place_type.rb +5 -0
- data/lib/metanorma/document/relaton/region_element.rb +3 -9
- data/lib/metanorma/document/relaton/relaton_collection.rb +2 -0
- data/lib/metanorma/document/relaton/series_type.rb +7 -0
- data/lib/metanorma/document/relaton/typed_note.rb +4 -0
- data/lib/metanorma/document/relaton/typed_title_string.rb +4 -0
- data/lib/metanorma/document/relaton/typed_uri.rb +3 -8
- data/lib/metanorma/document/relaton/validity_type.rb +8 -10
- data/lib/metanorma/document/relaton/variant_full_name.rb +3 -0
- data/lib/metanorma/document/relaton/variant_org_name.rb +3 -0
- data/lib/metanorma/document/relaton/version_info.rb +5 -0
- data/lib/metanorma/document/relaton/vocab_id_type.rb +6 -12
- data/lib/metanorma/document/relaton.rb +0 -14
- data/lib/metanorma/document/version.rb +1 -1
- data/lib/metanorma/document.rb +1 -0
- data/lib/metanorma/html/base_renderer.rb +86 -234
- data/lib/metanorma/html/concerns/metadata_extraction.rb +61 -0
- data/lib/metanorma/html/concerns/presentation_validation.rb +61 -0
- data/lib/metanorma/html/concerns/svg_processing.rb +32 -0
- data/lib/metanorma/html/concerns/text_extraction.rb +93 -0
- data/lib/metanorma/html/concerns/toc_registry.rb +47 -0
- data/lib/metanorma/html/csa_renderer.rb +9 -0
- data/lib/metanorma/html/drops/figure_drop.rb +33 -10
- data/lib/metanorma/html/drops/sourcecode_drop.rb +3 -6
- data/lib/metanorma/html/flavor.rb +6 -1
- data/lib/metanorma/html/generator.rb +8 -1
- data/lib/metanorma/html/iso_renderer.rb +0 -142
- data/lib/metanorma/html/renderers/block_renderer.rb +10 -0
- data/lib/metanorma/html/renderers/element_order_traversal.rb +15 -1
- data/lib/metanorma/html/renderers/inline_renderer.rb +13 -75
- data/lib/metanorma/html/standard_renderer.rb +195 -35
- data/lib/metanorma/html.rb +1 -51
- data/lib/metanorma/iso_document/sections/iso_preface.rb +27 -13
- data/lib/metanorma/mirror/default_registry.rb +21 -0
- data/lib/metanorma/mirror/handlers/inline/catalog.rb +7 -12
- data/lib/metanorma/mirror/handlers/inline/rich_html_renderer.rb +2 -3
- data/lib/metanorma/mirror/handlers/sourcecode.rb +1 -1
- data/lib/metanorma/mirror/model/guide.rb +3 -2
- data/lib/metanorma/mirror/output/formats/inline_format.rb +64 -3
- data/lib/metanorma/mirror/output/pipeline.rb +1 -0
- data/lib/metanorma/mirror/output.rb +0 -2
- data/lib/metanorma/mirror/rewriter.rb +3 -1
- data/lib/metanorma/registers/setup.rb +10 -0
- data/lib/metanorma/standard_document/annotation_container.rb +2 -0
- data/lib/metanorma/standard_document/elements/input.rb +4 -25
- data/lib/metanorma/standard_document/metadata/metanorma_extension.rb +12 -2
- data/lib/metanorma/standard_document/metadata/presentation_metadata.rb +107 -0
- data/lib/metanorma/standard_document/metadata/semantic_metadata.rb +18 -0
- data/lib/metanorma/standard_document/metadata/unitsml/amount_of_substance.rb +19 -0
- data/lib/metanorma/standard_document/metadata/unitsml/base_quantity.rb +34 -0
- data/lib/metanorma/standard_document/metadata/unitsml/dimension.rb +39 -0
- data/lib/metanorma/standard_document/metadata/unitsml/dimension_set.rb +19 -0
- data/lib/metanorma/standard_document/metadata/unitsml/electric_current.rb +19 -0
- data/lib/metanorma/standard_document/metadata/unitsml/enumerated_root_unit.rb +23 -0
- data/lib/metanorma/standard_document/metadata/unitsml/length.rb +19 -0
- data/lib/metanorma/standard_document/metadata/unitsml/luminous_intensity.rb +19 -0
- data/lib/metanorma/standard_document/metadata/unitsml/mass.rb +19 -0
- data/lib/metanorma/standard_document/metadata/unitsml/namespace.rb +17 -0
- data/lib/metanorma/standard_document/metadata/unitsml/plane_angle.rb +19 -0
- data/lib/metanorma/standard_document/metadata/unitsml/prefix.rb +30 -0
- data/lib/metanorma/standard_document/metadata/unitsml/prefix_name.rb +21 -0
- data/lib/metanorma/standard_document/metadata/unitsml/prefix_set.rb +19 -0
- data/lib/metanorma/standard_document/metadata/unitsml/prefix_symbol.rb +21 -0
- data/lib/metanorma/standard_document/metadata/unitsml/quantity.rb +28 -0
- data/lib/metanorma/standard_document/metadata/unitsml/quantity_name.rb +21 -0
- data/lib/metanorma/standard_document/metadata/unitsml/quantity_set.rb +19 -0
- data/lib/metanorma/standard_document/metadata/unitsml/root_units.rb +19 -0
- data/lib/metanorma/standard_document/metadata/unitsml/thermodynamic_temperature.rb +19 -0
- data/lib/metanorma/standard_document/metadata/unitsml/time.rb +19 -0
- data/lib/metanorma/standard_document/metadata/unitsml/unit.rb +32 -0
- data/lib/metanorma/standard_document/metadata/unitsml/unit_name.rb +21 -0
- data/lib/metanorma/standard_document/metadata/unitsml/unit_set.rb +19 -0
- data/lib/metanorma/standard_document/metadata/unitsml/unit_symbol.rb +36 -0
- data/lib/metanorma/standard_document/metadata/unitsml/unit_system.rb +23 -0
- data/lib/metanorma/standard_document/metadata/unitsml/unitsml_root.rb +28 -0
- data/lib/metanorma/standard_document/metadata/unitsml.rb +39 -0
- data/lib/metanorma/standard_document/metadata.rb +4 -0
- data/lib/metanorma/standard_document/sections/misc_container.rb +8 -6
- data/lib/metanorma/standard_document/sections/sections.rb +7 -0
- data/lib/metanorma/un_document/blocks/un_admonition_block.rb +86 -0
- data/lib/metanorma/un_document/blocks.rb +9 -0
- data/lib/metanorma/un_document/root.rb +8 -0
- data/lib/metanorma/un_document/sections/un_abstract_section.rb +48 -0
- data/lib/metanorma/un_document/sections/un_preface.rb +19 -2
- data/lib/metanorma/un_document/sections.rb +1 -0
- data/lib/metanorma/un_document/un_text_element.rb +116 -0
- data/lib/metanorma/un_document.rb +2 -0
- data/script/benchmark_render.rb +35 -0
- data/tasks/roundtrip_samples.rake +1 -1
- metadata +63 -45
- data/.rubocop_todo.yml +0 -220
- 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/docs/html-renderer.adoc +0 -261
- data/lib/data/dist/iso_document/frontend/assets/index-mwzbzmnK.js +0 -22
- data/lib/data/dist/iso_document/frontend/assets/index-pGvKTNid.css +0 -1
- data/lib/data/dist/iso_document/frontend/assets/iso-red.svg +0 -53
- data/lib/data/dist/iso_document/frontend/index.html +0 -13
- data/lib/metanorma/document/relaton/bib_item_size_type.rb +0 -21
- data/lib/metanorma/document/relaton/bibliographic_date_type.rb +0 -22
- data/lib/metanorma/document/relaton/document_relation_type.rb +0 -31
- data/lib/metanorma/document/relaton/iso4217_code.rb +0 -17
- data/lib/metanorma/document/relaton/iso8601_date.rb +0 -17
- data/lib/metanorma/document/relaton/personal_identifier_type.rb +0 -21
- data/lib/metanorma/document/relaton/region_type.rb +0 -20
- data/lib/metanorma/document/relaton/series_type_type.rb +0 -21
- data/lib/metanorma/document/relaton/specific_locality_type.rb +0 -25
- data/lib/metanorma/document/relaton/title_type.rb +0 -21
- 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 -41
- 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/standard_document/annotation.rb +0 -37
|
@@ -15,7 +15,23 @@ module Metanorma
|
|
|
15
15
|
"metanorma/html/renderers/element_order_traversal"
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
module Concerns
|
|
19
|
+
autoload :MetadataExtraction,
|
|
20
|
+
"metanorma/html/concerns/metadata_extraction"
|
|
21
|
+
autoload :PresentationValidation,
|
|
22
|
+
"metanorma/html/concerns/presentation_validation"
|
|
23
|
+
autoload :SvgProcessing, "metanorma/html/concerns/svg_processing"
|
|
24
|
+
autoload :TextExtraction, "metanorma/html/concerns/text_extraction"
|
|
25
|
+
autoload :TocRegistry, "metanorma/html/concerns/toc_registry"
|
|
26
|
+
end
|
|
27
|
+
|
|
18
28
|
class BaseRenderer
|
|
29
|
+
include Concerns::MetadataExtraction
|
|
30
|
+
include Concerns::PresentationValidation
|
|
31
|
+
include Concerns::SvgProcessing
|
|
32
|
+
include Concerns::TextExtraction
|
|
33
|
+
include Concerns::TocRegistry
|
|
34
|
+
|
|
19
35
|
LOGO_DIR = File.expand_path("../../../data/logos", __dir__)
|
|
20
36
|
|
|
21
37
|
SPAN_ROLE_CLASSES = {
|
|
@@ -115,10 +131,6 @@ module Metanorma
|
|
|
115
131
|
@renderer_context ||= RendererContext.new(self)
|
|
116
132
|
end
|
|
117
133
|
|
|
118
|
-
def toc_entries
|
|
119
|
-
@toc_entries
|
|
120
|
-
end
|
|
121
|
-
|
|
122
134
|
attr_writer :document, :theme
|
|
123
135
|
|
|
124
136
|
def generate_full_document(document, **)
|
|
@@ -130,6 +142,14 @@ module Metanorma
|
|
|
130
142
|
assemble_document(body)
|
|
131
143
|
end
|
|
132
144
|
|
|
145
|
+
# Renders only the document body content (no html/head/styles
|
|
146
|
+
# assembly) — for embedding classic-rendered content into a host page.
|
|
147
|
+
def generate_body(document, **)
|
|
148
|
+
@document = document
|
|
149
|
+
validate_presentation_xml!
|
|
150
|
+
render(@document) || ""
|
|
151
|
+
end
|
|
152
|
+
|
|
133
153
|
# --- Flavor configuration hooks ---
|
|
134
154
|
|
|
135
155
|
def theme
|
|
@@ -257,25 +277,6 @@ module Metanorma
|
|
|
257
277
|
end.join("\n")
|
|
258
278
|
end
|
|
259
279
|
|
|
260
|
-
def load_logo_svg(filename, height: 32)
|
|
261
|
-
path = theme.resolve_asset(filename) || File.join(LOGO_DIR, filename)
|
|
262
|
-
return nil unless File.exist?(path)
|
|
263
|
-
|
|
264
|
-
svg = File.read(path)
|
|
265
|
-
svg = svg.sub(/\A<\?xml[^?]*\?>\s*/, "")
|
|
266
|
-
svg = svg.sub(/\A\s*<!--.*?-->\s*/m, "")
|
|
267
|
-
svg = svg.sub(/<svg\s/, '<svg class="header-logo" ')
|
|
268
|
-
svg = if svg.match?(/<svg[^>]*\sheight="[^"]*"/)
|
|
269
|
-
svg.sub(/(<svg[^>]*?)(\sheight="[^"]*")/,
|
|
270
|
-
"\\1 height=\"#{height}\"")
|
|
271
|
-
else
|
|
272
|
-
svg.sub(/(<svg\b)/, "\\1 height=\"#{height}\"")
|
|
273
|
-
end
|
|
274
|
-
svg.sub(/(<svg[^>]*?)\swidth="[^"]*"/, '\1')
|
|
275
|
-
rescue StandardError
|
|
276
|
-
nil
|
|
277
|
-
end
|
|
278
|
-
|
|
279
280
|
def build_footer
|
|
280
281
|
mn_logo = load_logo_svg(METANORMA_LOGO, height: 20)
|
|
281
282
|
render_liquid("_footer.html.liquid", {
|
|
@@ -284,22 +285,6 @@ module Metanorma
|
|
|
284
285
|
})
|
|
285
286
|
end
|
|
286
287
|
|
|
287
|
-
# --- ToC generation ---
|
|
288
|
-
|
|
289
|
-
def build_toc_html(entries)
|
|
290
|
-
entry_drops = entries.map { |e| Drops::TocEntryDrop.new(e) }
|
|
291
|
-
figure_drops = @figure_entries.map { |f| Drops::FigureListEntryDrop.new(f) }
|
|
292
|
-
table_drops = @table_entries.map { |t| Drops::FigureListEntryDrop.new(t) }
|
|
293
|
-
has_special_lists = !@figure_entries.empty? || !@table_entries.empty?
|
|
294
|
-
|
|
295
|
-
render_liquid("_toc.html.liquid", {
|
|
296
|
-
"entries" => entry_drops,
|
|
297
|
-
"figures" => figure_drops,
|
|
298
|
-
"tables" => table_drops,
|
|
299
|
-
"has_special_lists" => has_special_lists,
|
|
300
|
-
})
|
|
301
|
-
end
|
|
302
|
-
|
|
303
288
|
# --- Scripts ---
|
|
304
289
|
|
|
305
290
|
def build_scripts
|
|
@@ -325,177 +310,28 @@ module Metanorma
|
|
|
325
310
|
parts.join("\n")
|
|
326
311
|
end
|
|
327
312
|
|
|
328
|
-
# --- Validation ---
|
|
329
|
-
|
|
330
|
-
def validate_presentation_xml!
|
|
331
|
-
has_presentation = check_presentation_markers(@document)
|
|
332
|
-
return if has_presentation
|
|
333
|
-
|
|
334
|
-
raise ArgumentError,
|
|
335
|
-
"HTML generation requires Presentation XML input. " \
|
|
336
|
-
"Semantic XML does not contain formatting data needed for HTML. " \
|
|
337
|
-
"Use a '.presentation.xml' file instead."
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
def check_presentation_markers(node)
|
|
341
|
-
return false unless node
|
|
342
|
-
return false if node.is_a?(String)
|
|
343
|
-
|
|
344
|
-
if node.is_a?(Lutaml::Model::Serializable)
|
|
345
|
-
node_attrs = node.class.attributes
|
|
346
|
-
if node_attrs.key?(:type) && node.type == "presentation"
|
|
347
|
-
return true
|
|
348
|
-
end
|
|
349
|
-
if node_attrs.key?(:fmt_title) && node.fmt_title
|
|
350
|
-
return true
|
|
351
|
-
end
|
|
352
|
-
if node_attrs.key?(:displayorder) && node.displayorder
|
|
353
|
-
return true
|
|
354
|
-
end
|
|
355
|
-
|
|
356
|
-
%i[preface sections annex bibliography].each do |attr|
|
|
357
|
-
next unless node_attrs.key?(attr)
|
|
358
|
-
|
|
359
|
-
val = node.public_send(attr)
|
|
360
|
-
next unless val
|
|
361
|
-
|
|
362
|
-
Array(val).each { |v| return true if check_presentation_markers(v) }
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
node.each_mixed_content do |child|
|
|
366
|
-
next if child.is_a?(String)
|
|
367
|
-
return true if check_presentation_markers(child)
|
|
368
|
-
end
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
false
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
# --- Metadata extraction ---
|
|
375
|
-
|
|
376
|
-
def language
|
|
377
|
-
bibdata = @document.bibdata
|
|
378
|
-
return "en" unless bibdata
|
|
379
|
-
|
|
380
|
-
langs = bibdata.language
|
|
381
|
-
if langs && !langs.empty?
|
|
382
|
-
lang = langs.find { |l| l.current == "true" } || langs.first
|
|
383
|
-
lang.value || lang.to_s
|
|
384
|
-
else
|
|
385
|
-
"en"
|
|
386
|
-
end
|
|
387
|
-
end
|
|
388
|
-
|
|
389
|
-
def html_title
|
|
390
|
-
extract_display_title(@document.bibdata) || "Document"
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
def extract_display_title(bibdata)
|
|
394
|
-
return nil unless bibdata
|
|
395
|
-
|
|
396
|
-
title = bibdata.title_for("en") if bibdata.is_a?(Metanorma::Document::Components::BibData::BibData)
|
|
397
|
-
return title.to_s if title && !title.to_s.empty?
|
|
398
|
-
|
|
399
|
-
titles = safe_attr(bibdata, :title)
|
|
400
|
-
return nil unless titles && !titles.is_a?(String) && !titles.empty?
|
|
401
|
-
|
|
402
|
-
en = titles.find { |t| safe_attr(t, :language) == "en" }
|
|
403
|
-
found = en || titles.first
|
|
404
|
-
extract_text_value(found).to_s
|
|
405
|
-
end
|
|
406
|
-
|
|
407
|
-
def extract_primary_doc_id
|
|
408
|
-
bibdata = @document.bibdata
|
|
409
|
-
return nil unless bibdata
|
|
410
|
-
|
|
411
|
-
identifiers = bibdata.doc_identifier
|
|
412
|
-
return nil unless identifiers && !identifiers.empty?
|
|
413
|
-
|
|
414
|
-
first_id = identifiers.first
|
|
415
|
-
text = if first_id.is_a?(String)
|
|
416
|
-
first_id
|
|
417
|
-
elsif first_id.is_a?(Lutaml::Model::Serializable)
|
|
418
|
-
Array(first_id.value).join
|
|
419
|
-
else
|
|
420
|
-
first_id.to_s
|
|
421
|
-
end
|
|
422
|
-
text.strip.empty? ? nil : text.strip
|
|
423
|
-
end
|
|
424
|
-
|
|
425
|
-
# --- Registration helpers ---
|
|
426
|
-
|
|
427
|
-
def register_toc_entry(id:, level:, text:)
|
|
428
|
-
@toc_entries << { id: id, level: level, text: text }
|
|
429
|
-
end
|
|
430
|
-
|
|
431
|
-
def register_figure_entry(id:, text:)
|
|
432
|
-
@figure_entries << { id: id, text: text }
|
|
433
|
-
end
|
|
434
|
-
|
|
435
|
-
def figure_entries
|
|
436
|
-
@figure_entries
|
|
437
|
-
end
|
|
438
|
-
|
|
439
|
-
def register_table_entry(id:, text:)
|
|
440
|
-
@table_entries << { id: id, text: text }
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
def extract_plain_text(node)
|
|
444
|
-
return node.to_s if node.is_a?(String)
|
|
445
|
-
return extract_text_value(node).to_s unless node.is_a?(Lutaml::Model::Serializable)
|
|
446
|
-
|
|
447
|
-
parts = []
|
|
448
|
-
xml_mapping = node.class.mappings_for(:xml, node.lutaml_register)
|
|
449
|
-
|
|
450
|
-
if node.element_order.is_a?(Array) && xml_mapping
|
|
451
|
-
element_to_attr =
|
|
452
|
-
Renderers::ElementOrderTraversal.element_to_attr_map(xml_mapping)
|
|
453
|
-
|
|
454
|
-
indices = Hash.new(0)
|
|
455
|
-
node.element_order.each do |el|
|
|
456
|
-
next unless el.is_a?(Lutaml::Xml::Element)
|
|
457
|
-
|
|
458
|
-
if el.text?
|
|
459
|
-
parts << el.text_content.to_s
|
|
460
|
-
elsif el.name == "tab"
|
|
461
|
-
parts << " "
|
|
462
|
-
elsif el.name == "br"
|
|
463
|
-
parts << " "
|
|
464
|
-
elsif el.element?
|
|
465
|
-
attr_name = element_to_attr[el.name]
|
|
466
|
-
if attr_name
|
|
467
|
-
coll = node.public_send(attr_name)
|
|
468
|
-
obj = if coll.is_a?(Array)
|
|
469
|
-
idx = indices[attr_name]
|
|
470
|
-
indices[attr_name] += 1
|
|
471
|
-
coll[idx]
|
|
472
|
-
else
|
|
473
|
-
coll
|
|
474
|
-
end
|
|
475
|
-
text = extract_plain_text(obj)
|
|
476
|
-
parts << (text.empty? ? " " : text)
|
|
477
|
-
elsif el.name == "span"
|
|
478
|
-
parts << " "
|
|
479
|
-
end
|
|
480
|
-
end
|
|
481
|
-
end
|
|
482
|
-
end
|
|
483
|
-
|
|
484
|
-
if parts.join.strip.empty?
|
|
485
|
-
t = safe_attr(node, :text)
|
|
486
|
-
parts << (t.is_a?(Array) ? t.join : t.to_s) if t
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
parts.join.strip.gsub(" ", " ")
|
|
490
|
-
end
|
|
491
|
-
|
|
492
313
|
# --- Dispatch ---
|
|
493
314
|
|
|
494
315
|
def render(node, **)
|
|
495
316
|
return escape_html(node) if node.is_a?(String)
|
|
496
317
|
|
|
497
318
|
method = lookup_dispatch(node.class, :render_registry)
|
|
498
|
-
|
|
319
|
+
return public_send(method, node, **) if method
|
|
320
|
+
|
|
321
|
+
record_render_warning(
|
|
322
|
+
"no renderer registered for #{node.class} — content skipped",
|
|
323
|
+
)
|
|
324
|
+
""
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Emit a render warning once per distinct message. Warnings mark
|
|
328
|
+
# content that was skipped or degraded rather than rendered.
|
|
329
|
+
def record_render_warning(message)
|
|
330
|
+
@render_warnings ||= {}
|
|
331
|
+
return if @render_warnings.key?(message)
|
|
332
|
+
|
|
333
|
+
@render_warnings[message] = true
|
|
334
|
+
warn "metanorma-document: #{message}"
|
|
499
335
|
end
|
|
500
336
|
|
|
501
337
|
def render_inline_element(element, **)
|
|
@@ -527,6 +363,8 @@ module Metanorma
|
|
|
527
363
|
:render_note
|
|
528
364
|
register_render Metanorma::Document::Components::AncillaryBlocks::ExampleBlock,
|
|
529
365
|
:render_example
|
|
366
|
+
register_render Metanorma::StandardDocument::Blocks::Form,
|
|
367
|
+
:render_form
|
|
530
368
|
register_render Metanorma::Document::Components::AncillaryBlocks::SourcecodeBlock,
|
|
531
369
|
:render_sourcecode
|
|
532
370
|
register_render Metanorma::Document::Components::AncillaryBlocks::FormulaBlock,
|
|
@@ -592,6 +430,8 @@ module Metanorma
|
|
|
592
430
|
:render_fmt_stem
|
|
593
431
|
register_inline_render Metanorma::Document::Components::Inline::CommaElement,
|
|
594
432
|
:render_comma
|
|
433
|
+
register_inline_render Metanorma::StandardDocument::Elements::Input,
|
|
434
|
+
:render_input
|
|
595
435
|
register_inline_render Metanorma::Document::Components::Inline::EnumCommaElement,
|
|
596
436
|
:render_comma
|
|
597
437
|
register_inline_render Metanorma::Document::Components::IdElements::Bookmark,
|
|
@@ -643,7 +483,31 @@ module Metanorma
|
|
|
643
483
|
register_inline_render klass, :render_mixed_inline
|
|
644
484
|
end
|
|
645
485
|
|
|
486
|
+
# Inline-ish classes that are also reached through the BLOCK
|
|
487
|
+
# dispatch (as direct children of block containers, e.g. fmt-title
|
|
488
|
+
# as a child of clause). fmt-title and variant-title duplicate the
|
|
489
|
+
# semantic `title` attribute that sections render — skip them.
|
|
490
|
+
register_render Metanorma::Document::Components::Inline::FmtTitleElement,
|
|
491
|
+
:render_noop
|
|
492
|
+
register_render Metanorma::Document::Components::Inline::VariantTitleElement,
|
|
493
|
+
:render_noop
|
|
494
|
+
# fmt-xref-label carries the cross-reference link label; render it
|
|
495
|
+
# through when it appears in block context.
|
|
496
|
+
register_render Metanorma::Document::Components::Inline::FmtXrefLabelElement,
|
|
497
|
+
:render_block_inline_content
|
|
498
|
+
|
|
646
499
|
def lookup_dispatch(type_class, registry_method)
|
|
500
|
+
# Registry contents are fixed once renderer classes are loaded, so
|
|
501
|
+
# the resolved method per (registry, node class) is memoized for
|
|
502
|
+
# the lifetime of this renderer instance.
|
|
503
|
+
cache = (@dispatch_cache ||= {})
|
|
504
|
+
key = [registry_method, type_class]
|
|
505
|
+
return cache[key] if cache.key?(key)
|
|
506
|
+
|
|
507
|
+
cache[key] = resolve_dispatch(type_class, registry_method)
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def resolve_dispatch(type_class, registry_method)
|
|
647
511
|
self.class.ancestors.each do |ancestor|
|
|
648
512
|
next unless ancestor.is_a?(Class) && (ancestor == BaseRenderer || ancestor < BaseRenderer)
|
|
649
513
|
|
|
@@ -658,6 +522,13 @@ module Metanorma
|
|
|
658
522
|
""
|
|
659
523
|
end
|
|
660
524
|
|
|
525
|
+
# Block-dispatch entry point for inline content reached as a direct
|
|
526
|
+
# child of a block container (render passes keyword args; the inline
|
|
527
|
+
# pipeline does not accept them).
|
|
528
|
+
def render_block_inline_content(el, **)
|
|
529
|
+
render_mixed_inline(el)
|
|
530
|
+
end
|
|
531
|
+
|
|
661
532
|
def render_noop_inline(*)
|
|
662
533
|
nil
|
|
663
534
|
end
|
|
@@ -690,6 +561,7 @@ module Metanorma
|
|
|
690
561
|
def render_semx_inline(el) = @inline_renderer.render_semx_inline(el)
|
|
691
562
|
def render_fmt_xref(el) = @inline_renderer.render_fmt_xref(el)
|
|
692
563
|
def render_comma(*) = @inline_renderer.render_comma
|
|
564
|
+
def render_input(el) = @inline_renderer.render_input(el)
|
|
693
565
|
def render_math(el) = @inline_renderer.render_math(el)
|
|
694
566
|
def render_asciimath(el) = @inline_renderer.render_asciimath(el)
|
|
695
567
|
def render_index(el) = @inline_renderer.render_index(el)
|
|
@@ -750,6 +622,8 @@ module Metanorma
|
|
|
750
622
|
@block_renderer.render_example(example, **)
|
|
751
623
|
end
|
|
752
624
|
|
|
625
|
+
def render_form(form, **) = @block_renderer.render_form(form, **)
|
|
626
|
+
|
|
753
627
|
def render_sourcecode(sc,
|
|
754
628
|
**)
|
|
755
629
|
@block_renderer.render_sourcecode(sc, **)
|
|
@@ -861,7 +735,12 @@ level = 1)
|
|
|
861
735
|
end
|
|
862
736
|
|
|
863
737
|
obj.public_send(method_name)
|
|
864
|
-
rescue NoMethodError
|
|
738
|
+
rescue NoMethodError => e
|
|
739
|
+
# Only swallow "method missing on this object" (duck-typing across
|
|
740
|
+
# model classes). A NoMethodError raised *inside* the getter is a
|
|
741
|
+
# real bug — let it surface.
|
|
742
|
+
raise unless e.receiver.equal?(obj)
|
|
743
|
+
|
|
865
744
|
nil
|
|
866
745
|
end
|
|
867
746
|
|
|
@@ -876,7 +755,8 @@ level = 1)
|
|
|
876
755
|
target_id: @current_section_id,
|
|
877
756
|
target_text: @current_section_number,
|
|
878
757
|
)
|
|
879
|
-
rescue StandardError
|
|
758
|
+
rescue StandardError => e
|
|
759
|
+
record_render_warning("index term dropped: #{e.class}: #{e.message}")
|
|
880
760
|
nil
|
|
881
761
|
end
|
|
882
762
|
|
|
@@ -916,34 +796,6 @@ level = 1)
|
|
|
916
796
|
CGI.escapeHTML(text.to_s)
|
|
917
797
|
end
|
|
918
798
|
|
|
919
|
-
def extract_text_value(val)
|
|
920
|
-
return nil if val.nil?
|
|
921
|
-
return val if val.is_a?(String)
|
|
922
|
-
|
|
923
|
-
if val.is_a?(Array)
|
|
924
|
-
val.map { |v| extract_text_value(v) }.join
|
|
925
|
-
elsif val.is_a?(Lutaml::Model::Serializable)
|
|
926
|
-
c = safe_attr(val, :content)
|
|
927
|
-
if c && !c.equal?(val)
|
|
928
|
-
extract_text_value(c)
|
|
929
|
-
else
|
|
930
|
-
t = safe_attr(val, :text)
|
|
931
|
-
if t
|
|
932
|
-
extract_text_value(t)
|
|
933
|
-
else
|
|
934
|
-
v = safe_attr(val, :value)
|
|
935
|
-
if v
|
|
936
|
-
extract_text_value(v)
|
|
937
|
-
else
|
|
938
|
-
val.to_s
|
|
939
|
-
end
|
|
940
|
-
end
|
|
941
|
-
end
|
|
942
|
-
else
|
|
943
|
-
val.to_s
|
|
944
|
-
end
|
|
945
|
-
end
|
|
946
|
-
|
|
947
799
|
def render_footnotes_section
|
|
948
800
|
return nil if @footnote_collector.empty?
|
|
949
801
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Html
|
|
5
|
+
module Concerns
|
|
6
|
+
# Document metadata extraction from bibdata, mixed into
|
|
7
|
+
# BaseRenderer: page language, HTML title, and the primary document
|
|
8
|
+
# identifier used in the header.
|
|
9
|
+
module MetadataExtraction
|
|
10
|
+
def language
|
|
11
|
+
bibdata = @document.bibdata
|
|
12
|
+
return "en" unless bibdata
|
|
13
|
+
|
|
14
|
+
langs = bibdata.language
|
|
15
|
+
if langs && !langs.empty?
|
|
16
|
+
lang = langs.find { |l| l.current == "true" } || langs.first
|
|
17
|
+
lang.value || lang.to_s
|
|
18
|
+
else
|
|
19
|
+
"en"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def html_title
|
|
24
|
+
extract_display_title(@document.bibdata) || "Document"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def extract_display_title(bibdata)
|
|
28
|
+
return nil unless bibdata
|
|
29
|
+
|
|
30
|
+
title = bibdata.title_for("en") if bibdata.is_a?(Metanorma::Document::Components::BibData::BibData)
|
|
31
|
+
return title.to_s if title && !title.to_s.empty?
|
|
32
|
+
|
|
33
|
+
titles = safe_attr(bibdata, :title)
|
|
34
|
+
return nil unless titles && !titles.is_a?(String) && !titles.empty?
|
|
35
|
+
|
|
36
|
+
en = titles.find { |t| safe_attr(t, :language) == "en" }
|
|
37
|
+
found = en || titles.first
|
|
38
|
+
extract_text_value(found).to_s
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def extract_primary_doc_id
|
|
42
|
+
bibdata = @document.bibdata
|
|
43
|
+
return nil unless bibdata
|
|
44
|
+
|
|
45
|
+
identifiers = bibdata.doc_identifier
|
|
46
|
+
return nil unless identifiers && !identifiers.empty?
|
|
47
|
+
|
|
48
|
+
first_id = identifiers.first
|
|
49
|
+
text = if first_id.is_a?(String)
|
|
50
|
+
first_id
|
|
51
|
+
elsif first_id.is_a?(Lutaml::Model::Serializable)
|
|
52
|
+
Array(first_id.value).join
|
|
53
|
+
else
|
|
54
|
+
first_id.to_s
|
|
55
|
+
end
|
|
56
|
+
text.strip.empty? ? nil : text.strip
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Html
|
|
5
|
+
module Concerns
|
|
6
|
+
# Presentation-XML validation mixed into BaseRenderer. HTML
|
|
7
|
+
# generation requires Presentation XML input — semantic XML does not
|
|
8
|
+
# contain the formatting data needed for HTML — so the document is
|
|
9
|
+
# checked for presentation markers before rendering begins.
|
|
10
|
+
module PresentationValidation
|
|
11
|
+
def validate_presentation_xml!
|
|
12
|
+
has_presentation = check_presentation_markers(@document)
|
|
13
|
+
return if has_presentation
|
|
14
|
+
|
|
15
|
+
raise ArgumentError,
|
|
16
|
+
"HTML generation requires Presentation XML input. " \
|
|
17
|
+
"Semantic XML does not contain formatting data needed for HTML. " \
|
|
18
|
+
"Use a '.presentation.xml' file instead."
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Name kept for API stability (public renderer API); the boolean
|
|
22
|
+
# return predates the Naming/PredicateMethod convention.
|
|
23
|
+
# rubocop:disable Naming/PredicateMethod
|
|
24
|
+
def check_presentation_markers(node)
|
|
25
|
+
return false unless node
|
|
26
|
+
return false if node.is_a?(String)
|
|
27
|
+
|
|
28
|
+
if node.is_a?(Lutaml::Model::Serializable)
|
|
29
|
+
node_attrs = node.class.attributes
|
|
30
|
+
if node_attrs.key?(:type) && node.type == "presentation"
|
|
31
|
+
return true
|
|
32
|
+
end
|
|
33
|
+
if node_attrs.key?(:fmt_title) && node.fmt_title
|
|
34
|
+
return true
|
|
35
|
+
end
|
|
36
|
+
if node_attrs.key?(:displayorder) && node.displayorder
|
|
37
|
+
return true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
%i[preface sections annex bibliography].each do |attr|
|
|
41
|
+
next unless node_attrs.key?(attr)
|
|
42
|
+
|
|
43
|
+
val = node.public_send(attr)
|
|
44
|
+
next unless val
|
|
45
|
+
|
|
46
|
+
Array(val).each { |v| return true if check_presentation_markers(v) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
node.each_mixed_content do |child|
|
|
50
|
+
next if child.is_a?(String)
|
|
51
|
+
return true if check_presentation_markers(child)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
false
|
|
56
|
+
end
|
|
57
|
+
# rubocop:enable Naming/PredicateMethod
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Html
|
|
5
|
+
module Concerns
|
|
6
|
+
# SVG logo loading and normalization mixed into BaseRenderer.
|
|
7
|
+
# Logos ship as standalone SVG files; for inline embedding the xml
|
|
8
|
+
# prolog and leading comments are stripped, a CSS class is added,
|
|
9
|
+
# and width/height are normalized to the requested display height.
|
|
10
|
+
module SvgProcessing
|
|
11
|
+
def load_logo_svg(filename, height: 32)
|
|
12
|
+
path = theme.resolve_asset(filename) || File.join(BaseRenderer::LOGO_DIR, filename)
|
|
13
|
+
return nil unless File.exist?(path)
|
|
14
|
+
|
|
15
|
+
svg = File.read(path)
|
|
16
|
+
svg = svg.sub(/\A<\?xml[^?]*\?>\s*/, "")
|
|
17
|
+
svg = svg.sub(/\A\s*<!--.*?-->\s*/m, "")
|
|
18
|
+
svg = svg.sub(/<svg\s/, '<svg class="header-logo" ')
|
|
19
|
+
svg = if svg.match?(/<svg[^>]*\sheight="[^"]*"/)
|
|
20
|
+
svg.sub(/(<svg[^>]*?)(\sheight="[^"]*")/,
|
|
21
|
+
"\\1 height=\"#{height}\"")
|
|
22
|
+
else
|
|
23
|
+
svg.sub(/(<svg\b)/, "\\1 height=\"#{height}\"")
|
|
24
|
+
end
|
|
25
|
+
svg.sub(/(<svg[^>]*?)\swidth="[^"]*"/, '\1')
|
|
26
|
+
rescue StandardError
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Html
|
|
5
|
+
module Concerns
|
|
6
|
+
# Plain-text extraction from model nodes, mixed into BaseRenderer.
|
|
7
|
+
# Used wherever rendered output needs a text-only form (titles,
|
|
8
|
+
# labels, captions, ToC entries). Walks `element_order` and typed
|
|
9
|
+
# attributes directly on the model — never strips tags from
|
|
10
|
+
# rendered HTML.
|
|
11
|
+
module TextExtraction
|
|
12
|
+
def extract_plain_text(node)
|
|
13
|
+
return node.to_s if node.is_a?(String)
|
|
14
|
+
return extract_text_value(node).to_s unless node.is_a?(Lutaml::Model::Serializable)
|
|
15
|
+
|
|
16
|
+
parts = []
|
|
17
|
+
xml_mapping = node.class.mappings_for(:xml, node.lutaml_register)
|
|
18
|
+
|
|
19
|
+
if node.element_order.is_a?(Array) && xml_mapping
|
|
20
|
+
element_to_attr =
|
|
21
|
+
Renderers::ElementOrderTraversal.element_to_attr_map(xml_mapping)
|
|
22
|
+
|
|
23
|
+
indices = Hash.new(0)
|
|
24
|
+
node.element_order.each do |el|
|
|
25
|
+
next unless el.is_a?(Lutaml::Xml::Element)
|
|
26
|
+
|
|
27
|
+
if el.text?
|
|
28
|
+
parts << el.text_content.to_s
|
|
29
|
+
elsif el.name == "tab"
|
|
30
|
+
parts << " "
|
|
31
|
+
# rubocop:disable Lint/DuplicateBranch
|
|
32
|
+
elsif el.name == "br"
|
|
33
|
+
parts << " "
|
|
34
|
+
# rubocop:enable Lint/DuplicateBranch
|
|
35
|
+
elsif el.element?
|
|
36
|
+
attr_name = element_to_attr[el.name]
|
|
37
|
+
if attr_name
|
|
38
|
+
coll = node.public_send(attr_name)
|
|
39
|
+
obj = if coll.is_a?(Array)
|
|
40
|
+
idx = indices[attr_name]
|
|
41
|
+
indices[attr_name] += 1
|
|
42
|
+
coll[idx]
|
|
43
|
+
else
|
|
44
|
+
coll
|
|
45
|
+
end
|
|
46
|
+
text = extract_plain_text(obj)
|
|
47
|
+
parts << (text.empty? ? " " : text)
|
|
48
|
+
elsif el.name == "span"
|
|
49
|
+
parts << " "
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
if parts.join.strip.empty?
|
|
56
|
+
t = safe_attr(node, :text)
|
|
57
|
+
parts << (t.is_a?(Array) ? t.join : t.to_s) if t
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
parts.join.strip.gsub(" ", " ")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def extract_text_value(val)
|
|
64
|
+
return nil if val.nil?
|
|
65
|
+
return val if val.is_a?(String)
|
|
66
|
+
|
|
67
|
+
if val.is_a?(Array)
|
|
68
|
+
val.map { |v| extract_text_value(v) }.join
|
|
69
|
+
elsif val.is_a?(Lutaml::Model::Serializable)
|
|
70
|
+
c = safe_attr(val, :content)
|
|
71
|
+
if c && !c.equal?(val)
|
|
72
|
+
extract_text_value(c)
|
|
73
|
+
else
|
|
74
|
+
t = safe_attr(val, :text)
|
|
75
|
+
if t
|
|
76
|
+
extract_text_value(t)
|
|
77
|
+
else
|
|
78
|
+
v = safe_attr(val, :value)
|
|
79
|
+
if v
|
|
80
|
+
extract_text_value(v)
|
|
81
|
+
else
|
|
82
|
+
val.to_s
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
else
|
|
87
|
+
val.to_s
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|