metanorma-document 0.2.1 → 0.2.3

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.
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "liquid"
4
4
  require "nokogiri"
5
+ require "cgi"
5
6
  require_relative "drops/footnote_drop"
6
7
 
7
8
  module Metanorma
@@ -48,6 +49,26 @@ module Metanorma
48
49
 
49
50
  METANORMA_LOGO = "metanorma-logo.svg"
50
51
 
52
+ # Type-to-method registry for OCP dispatch.
53
+ # Each subclass gets its own hash; lookup traverses ancestors.
54
+ class << self
55
+ def render_registry
56
+ @render_registry ||= {}
57
+ end
58
+
59
+ def register_render(type_class, method_name)
60
+ render_registry[type_class] = method_name
61
+ end
62
+
63
+ def inline_registry
64
+ @inline_registry ||= {}
65
+ end
66
+
67
+ def register_inline_render(type_class, method_name)
68
+ inline_registry[type_class] = method_name
69
+ end
70
+ end
71
+
51
72
  def initialize
52
73
  @output = +""
53
74
  @toc_entries = []
@@ -141,11 +162,14 @@ module Metanorma
141
162
 
142
163
  # --- Document Assembly ---
143
164
 
144
- TEMPLATE_CACHE = Hash.new { |h, k| h[k] = Liquid::Template.parse(File.read(k)) }
165
+ TEMPLATE_CACHE = {}
166
+ TEMPLATE_CACHE_MUTEX = Mutex.new
145
167
 
146
168
  def render_liquid(template_name, assigns)
147
169
  template_path = File.join(TEMPLATES_ROOT, template_name)
148
- template = TEMPLATE_CACHE[template_path]
170
+ template = TEMPLATE_CACHE_MUTEX.synchronize do
171
+ TEMPLATE_CACHE[template_path] ||= Liquid::Template.parse(File.read(template_path))
172
+ end
149
173
  assigns = assigns.transform_keys(&:to_s) if assigns.is_a?(Hash)
150
174
  template.render(assigns)
151
175
  end
@@ -192,11 +216,6 @@ module Metanorma
192
216
  raw.length > 60 ? "#{raw[0, 57]}..." : raw
193
217
  end
194
218
 
195
- # Reader controls — kept for backward compat with flavor renderers
196
- def build_reader_controls
197
- ""
198
- end
199
-
200
219
  def build_publisher_logos
201
220
  publishers = flavor_publishers(extract_primary_doc_id)
202
221
  logo_map = publisher_logo_map
@@ -216,10 +235,6 @@ module Metanorma
216
235
  end.join("\n")
217
236
  end
218
237
 
219
- def detect_publishers
220
- flavor_publishers(extract_primary_doc_id)
221
- end
222
-
223
238
  def load_logo_svg(filename, height: 32)
224
239
  path = File.join(LOGO_DIR, filename)
225
240
  return nil unless File.exist?(path)
@@ -474,54 +489,125 @@ module Metanorma
474
489
  parts.join.strip.gsub("\u00A0", " ")
475
490
  end
476
491
 
477
- # Dispatch to the appropriate render method based on node class.
492
+ # Dispatch to the appropriate render method via type registry.
493
+ # Lookups traverse the ancestor chain so subclasses inherit
494
+ # parent registrations and can override them independently.
478
495
  def render(node, **)
479
- case node
480
- when Metanorma::Document::Components::Paragraphs::ParagraphBlock
481
- render_paragraph(node, **)
482
- when Metanorma::Document::Components::Tables::TableBlock
483
- render_table(node, **)
484
- when Metanorma::Document::Components::Lists::UnorderedList
485
- render_unordered_list(node, **)
486
- when Metanorma::Document::Components::Lists::OrderedList
487
- render_ordered_list(node, **)
488
- when Metanorma::Document::Components::Lists::DefinitionList
489
- render_definition_list(node, **)
490
- when Metanorma::Document::Components::AncillaryBlocks::FigureBlock
491
- render_figure(node, **)
492
- when Metanorma::Document::Components::Blocks::NoteBlock
493
- render_note(node, **)
494
- when Metanorma::Document::Components::AncillaryBlocks::ExampleBlock
495
- render_example(node, **)
496
- when Metanorma::Document::Components::AncillaryBlocks::SourcecodeBlock
497
- render_sourcecode(node, **)
498
- when Metanorma::Document::Components::AncillaryBlocks::FormulaBlock
499
- render_formula(node, **)
500
- when Metanorma::Document::Components::MultiParagraph::QuoteBlock
501
- render_quote(node, **)
502
- when Metanorma::Document::Components::MultiParagraph::AdmonitionBlock
503
- render_admonition(node, **)
504
- when Metanorma::Document::Components::Sections::HierarchicalSection
505
- render_hierarchical_section(node, **)
506
- when Metanorma::Document::Components::Sections::BasicSection
507
- render_basic_section(node, **)
508
- when Metanorma::Document::Components::Sections::ContentSection
509
- render_content_section(node, **)
510
- when Metanorma::Document::Components::EmptyElements::PageBreakElement
511
- ""
512
- when Metanorma::Document::Components::IdElements::Bookmark
513
- render_bookmark(node)
514
- when Metanorma::Document::Components::Inline::SemxElement
515
- render_semx_content(node)
516
- when String
517
- escape_html(node)
518
- else
519
- ""
496
+ return escape_html(node) if node.is_a?(String)
497
+
498
+ method = lookup_dispatch(node.class, :render_registry)
499
+ method ? send(method, node, **) : ""
500
+ end
501
+
502
+ # Dispatch to the appropriate inline render method via type registry.
503
+ def render_inline_element(element, **)
504
+ return "" if element.nil?
505
+ return escape_html(element) if element.is_a?(String)
506
+
507
+ method = lookup_dispatch(element.class, :inline_registry)
508
+ if method
509
+ send(method, element)
510
+ elsif element.is_a?(Lutaml::Model::Serializable) && element.mixed?
511
+ render_mixed_inline(element)
520
512
  end
521
513
  end
522
514
 
515
+ # --- Type registrations (class-level, evaluated at class load time) ---
516
+
517
+ register_render Metanorma::Document::Components::Paragraphs::ParagraphBlock, :render_paragraph
518
+ register_render Metanorma::Document::Components::Tables::TableBlock, :render_table
519
+ register_render Metanorma::Document::Components::Lists::UnorderedList, :render_unordered_list
520
+ register_render Metanorma::Document::Components::Lists::OrderedList, :render_ordered_list
521
+ register_render Metanorma::Document::Components::Lists::DefinitionList, :render_definition_list
522
+ register_render Metanorma::Document::Components::AncillaryBlocks::FigureBlock, :render_figure
523
+ register_render Metanorma::Document::Components::Blocks::NoteBlock, :render_note
524
+ register_render Metanorma::Document::Components::AncillaryBlocks::ExampleBlock, :render_example
525
+ register_render Metanorma::Document::Components::AncillaryBlocks::SourcecodeBlock, :render_sourcecode
526
+ register_render Metanorma::Document::Components::AncillaryBlocks::FormulaBlock, :render_formula
527
+ register_render Metanorma::Document::Components::MultiParagraph::QuoteBlock, :render_quote
528
+ register_render Metanorma::Document::Components::MultiParagraph::AdmonitionBlock, :render_admonition
529
+ register_render Metanorma::Document::Components::Sections::HierarchicalSection, :render_hierarchical_section
530
+ register_render Metanorma::Document::Components::Sections::BasicSection, :render_basic_section
531
+ register_render Metanorma::Document::Components::Sections::ContentSection, :render_content_section
532
+ register_render Metanorma::Document::Components::EmptyElements::PageBreakElement, :render_noop
533
+ register_render Metanorma::Document::Components::IdElements::Bookmark, :render_bookmark
534
+ register_render Metanorma::Document::Components::Inline::SemxElement, :render_semx_content
535
+
536
+ register_inline_render Metanorma::Document::Components::Inline::EmRawElement, :render_em
537
+ register_inline_render Metanorma::Document::Components::Inline::StrongRawElement, :render_strong
538
+ register_inline_render Metanorma::Document::Components::Inline::TtElement, :render_tt
539
+ register_inline_render Metanorma::Document::Components::Inline::SubElement, :render_sub
540
+ register_inline_render Metanorma::Document::Components::Inline::SupElement, :render_sup
541
+ register_inline_render Metanorma::Document::Components::Inline::SmallCapElement, :render_small_caps
542
+ register_inline_render Metanorma::Document::Components::TextElements::UnderlineElement, :render_underline
543
+ register_inline_render Metanorma::Document::Components::TextElements::StrikeElement, :render_strike
544
+ register_inline_render Metanorma::Document::Components::Inline::BrElement, :render_br
545
+ register_inline_render Metanorma::Document::Components::Inline::TabElement, :render_tab
546
+ register_inline_render Metanorma::Document::Components::Inline::LinkElement, :render_link
547
+ register_inline_render Metanorma::Document::Components::Inline::XrefElement, :render_noop_inline
548
+ register_inline_render Metanorma::Document::Components::Inline::ErefElement, :render_noop_inline
549
+ register_inline_render Metanorma::Document::Components::Inline::SpanElement, :render_span
550
+ register_inline_render Metanorma::Document::Components::Inline::FnElement, :render_fn_inline
551
+ register_inline_render Metanorma::Document::Components::Inline::ConceptElement, :render_concept
552
+ register_inline_render Metanorma::Document::Components::Inline::StemInlineElement, :render_noop_inline
553
+ register_inline_render Metanorma::Document::Components::TextElements::StemElement, :render_stem
554
+ register_inline_render Metanorma::Document::Components::Inline::SemxElement, :render_semx_inline
555
+ register_inline_render Metanorma::Document::Components::Inline::FmtXrefElement, :render_fmt_xref
556
+ register_inline_render Metanorma::Document::Components::Inline::FmtStemElement, :render_fmt_stem
557
+ register_inline_render Metanorma::Document::Components::Inline::CommaElement, :render_comma
558
+ register_inline_render Metanorma::Document::Components::Inline::EnumCommaElement, :render_comma
559
+ register_inline_render Metanorma::Document::Components::IdElements::Bookmark, :render_bookmark
560
+ register_inline_render Metanorma::Document::Components::IdElements::Image, :render_image
561
+ register_inline_render Metanorma::Document::Components::Inline::MathElement, :render_math
562
+ register_inline_render Metanorma::Document::Components::Inline::AsciimathElement, :render_asciimath
563
+ register_inline_render Metanorma::Document::Components::EmptyElements::IndexElement, :render_index
564
+ register_inline_render Metanorma::Document::Components::ReferenceElements::IndexXrefElement, :render_index
565
+ register_inline_render Metanorma::Document::Components::Blocks::NoteBlock, :render_note_inline
566
+ # All Fmt* elements delegate to render_mixed_inline
567
+ register_inline_render Metanorma::Document::Components::Inline::FmtNameElement, :render_mixed_inline
568
+ register_inline_render Metanorma::Document::Components::Inline::FmtTitleElement, :render_mixed_inline
569
+ register_inline_render Metanorma::Document::Components::Inline::FmtXrefLabelElement, :render_mixed_inline
570
+ register_inline_render Metanorma::Document::Components::Inline::FmtFnLabelElement, :render_mixed_inline
571
+ register_inline_render Metanorma::Document::Components::Inline::FmtConceptElement, :render_mixed_inline
572
+ register_inline_render Metanorma::Document::Components::Inline::FmtAnnotationStartElement, :render_mixed_inline
573
+ register_inline_render Metanorma::Document::Components::Inline::FmtAnnotationEndElement, :render_mixed_inline
574
+ register_inline_render Metanorma::Document::Components::Inline::FmtAnnotationBodyElement, :render_mixed_inline
575
+ register_inline_render Metanorma::Document::Components::Inline::VariantTitleElement, :render_mixed_inline
576
+ register_inline_render Metanorma::Document::Components::Inline::LocalizedStringElement, :render_mixed_inline
577
+ register_inline_render Metanorma::Document::Components::Inline::TitleWithAnnotationElement, :render_mixed_inline
578
+ register_inline_render Metanorma::Document::Components::Inline::BiblioTagElement, :render_mixed_inline
579
+ register_inline_render Metanorma::Document::Components::Inline::NameWithIdElement, :render_mixed_inline
580
+ register_inline_render Metanorma::Document::Components::Inline::DisplayTextElement, :render_mixed_inline
581
+ register_inline_render Metanorma::Document::Components::Inline::FmtFootnoteContainerElement, :render_mixed_inline
582
+ register_inline_render Metanorma::Document::Components::Inline::FmtFnBodyElement, :render_mixed_inline
583
+ register_inline_render Metanorma::Document::Components::Inline::FmtPreferredElement, :render_mixed_inline
584
+ register_inline_render Metanorma::Document::Components::Inline::FmtDefinitionElement, :render_mixed_inline
585
+ register_inline_render Metanorma::Document::Components::Inline::FmtTermsourceElement, :render_mixed_inline
586
+ register_inline_render Metanorma::Document::Components::Inline::FmtAdmittedElement, :render_mixed_inline
587
+ register_inline_render Metanorma::Document::Components::Inline::FmtIdentifierElement, :render_mixed_inline
588
+ register_inline_render Metanorma::Document::Components::Inline::FmtSourcecodeElement, :render_mixed_inline
589
+
523
590
  private
524
591
 
592
+ def lookup_dispatch(type_class, registry_method)
593
+ self.class.ancestors.each do |ancestor|
594
+ next unless ancestor.respond_to?(registry_method)
595
+
596
+ registry = ancestor.send(registry_method)
597
+ method_name = registry[type_class]
598
+ return method_name if method_name
599
+ end
600
+ nil
601
+ end
602
+
603
+ def render_noop(*)
604
+ ""
605
+ end
606
+
607
+ def render_noop_inline(*)
608
+ nil
609
+ end
610
+
525
611
  # --- Block-level rendering ---
526
612
 
527
613
  def render_paragraph(p, **_opts)
@@ -806,7 +892,7 @@ module Metanorma
806
892
  @output << render_liquid("_admonition.html.liquid", { "block" => drop })
807
893
  end
808
894
 
809
- def render_bookmark(bookmark)
895
+ def render_bookmark(bookmark, **_opts)
810
896
  @output << %(<a id="#{escape_html(safe_attr(bookmark, :id).to_s)}"></a>)
811
897
  end
812
898
 
@@ -989,7 +1075,9 @@ module Metanorma
989
1075
  end
990
1076
 
991
1077
  def raw_content_node?(node)
992
- node.is_a?(Metanorma::IsoDocument::RawParagraph)
1078
+ node.is_a?(Lutaml::Model::Serializable) &&
1079
+ node.respond_to?(:content) &&
1080
+ node.content.is_a?(String)
993
1081
  end
994
1082
 
995
1083
  # Iterate element_order directly, preserving whitespace text nodes
@@ -1026,114 +1114,98 @@ module Metanorma
1026
1114
  end
1027
1115
  end
1028
1116
 
1029
- def render_inline_element(element)
1030
- return "" if element.nil?
1117
+ # Inline adapter methods for registry dispatch
1031
1118
 
1032
- case element
1033
- when String
1034
- @output << escape_html(element)
1035
- when Metanorma::Document::Components::Inline::EmRawElement
1036
- render_inline_tag("em", element)
1037
- when Metanorma::Document::Components::Inline::StrongRawElement
1038
- render_inline_tag("strong", element)
1039
- when Metanorma::Document::Components::Inline::TtElement
1040
- render_inline_tag("tt", element)
1041
- when Metanorma::Document::Components::Inline::SubElement
1042
- render_inline_tag("sub", element)
1043
- when Metanorma::Document::Components::Inline::SupElement
1044
- render_inline_tag("sup", element)
1045
- when Metanorma::Document::Components::Inline::SmallCapElement
1046
- render_inline_tag("span", element, class: "small-caps")
1047
- when Metanorma::Document::Components::TextElements::UnderlineElement
1048
- render_inline_tag("u", element)
1049
- when Metanorma::Document::Components::TextElements::StrikeElement
1050
- render_inline_tag("s", element)
1051
- when Metanorma::Document::Components::Inline::BrElement
1052
- @output << "<br />"
1053
- when Metanorma::Document::Components::Inline::TabElement
1054
- @output << "\u00a0\u00a0"
1055
- when Metanorma::Document::Components::Inline::LinkElement
1056
- render_link(element)
1057
- when Metanorma::Document::Components::Inline::XrefElement
1058
- # Source element — skip; rendered via fmt-xref in semx wrapper
1059
- nil
1060
- when Metanorma::Document::Components::Inline::ErefElement
1061
- # Source element — skip; rendered via fmt-xref in semx wrapper
1062
- nil
1063
- when Metanorma::Document::Components::Inline::SpanElement
1064
- xml_class = safe_attr(element, :class_attr).to_s
1065
- html_class = html_class_for_span(xml_class) unless xml_class.empty?
1066
- attrs = element_attrs(style: safe_attr(element, :style), class: html_class)
1067
- tag("span", attrs) { render_mixed_inline(element) }
1068
- when Metanorma::Document::Components::Inline::FnElement
1069
- render_fn(element)
1070
- when Metanorma::Document::Components::Inline::ConceptElement
1071
- render_concept(element)
1072
- when Metanorma::Document::Components::Inline::StemInlineElement
1073
- # Source element — skip; rendered via FmtStemElement
1074
- nil
1075
- when Metanorma::Document::Components::TextElements::StemElement
1076
- @output << render_stem_content(element)
1077
- when Metanorma::Document::Components::Inline::SemxElement
1078
- render_semx_content(element)
1079
- when Metanorma::Document::Components::Inline::FmtNameElement,
1080
- Metanorma::Document::Components::Inline::FmtTitleElement,
1081
- Metanorma::Document::Components::Inline::FmtXrefLabelElement,
1082
- Metanorma::Document::Components::Inline::FmtFnLabelElement,
1083
- Metanorma::Document::Components::Inline::FmtConceptElement,
1084
- Metanorma::Document::Components::Inline::FmtAnnotationStartElement,
1085
- Metanorma::Document::Components::Inline::FmtAnnotationEndElement,
1086
- Metanorma::Document::Components::Inline::FmtAnnotationBodyElement,
1087
- Metanorma::Document::Components::Inline::VariantTitleElement,
1088
- Metanorma::Document::Components::Inline::LocalizedStringElement,
1089
- Metanorma::Document::Components::Inline::TitleWithAnnotationElement,
1090
- Metanorma::Document::Components::Inline::BiblioTagElement,
1091
- Metanorma::Document::Components::Inline::NameWithIdElement,
1092
- Metanorma::Document::Components::Inline::DisplayTextElement,
1093
- Metanorma::Document::Components::Inline::FmtFootnoteContainerElement,
1094
- Metanorma::Document::Components::Inline::FmtFnBodyElement,
1095
- Metanorma::Document::Components::Inline::FmtPreferredElement,
1096
- Metanorma::Document::Components::Inline::FmtDefinitionElement,
1097
- Metanorma::Document::Components::Inline::FmtTermsourceElement,
1098
- Metanorma::Document::Components::Inline::FmtAdmittedElement,
1099
- Metanorma::Document::Components::Inline::FmtIdentifierElement,
1100
- Metanorma::Document::Components::Inline::FmtSourcecodeElement
1101
- render_mixed_inline(element)
1102
- when Metanorma::Document::Components::Inline::FmtXrefElement
1103
- target = safe_attr(element, :target) || safe_attr(element, :to_attr)
1104
- if target
1105
- attrs = element_attrs(href: "##{escape_html(target)}", class: "xref")
1106
- tag("a", attrs) { render_mixed_inline(element) }
1107
- else
1108
- render_mixed_inline(element)
1109
- end
1110
- when Metanorma::Document::Components::Inline::FmtStemElement
1111
- render_fmt_stem(element)
1112
- when Metanorma::Document::Components::Inline::CommaElement,
1113
- Metanorma::Document::Components::Inline::EnumCommaElement
1114
- @output << ", "
1115
- when Metanorma::Document::Components::IdElements::Bookmark
1116
- render_bookmark(element)
1117
- when Metanorma::Document::Components::IdElements::Image
1118
- render_image(element)
1119
- when Metanorma::Document::Components::Inline::MathElement
1120
- @output << element.content.to_s
1121
- when Metanorma::Document::Components::Inline::AsciimathElement
1122
- @output << %(<span class="stem">#{escape_html(Array(element.text).join)}</span>)
1123
- when Metanorma::Document::Components::EmptyElements::IndexElement,
1124
- Metanorma::Document::Components::ReferenceElements::IndexXrefElement
1125
- collect_index_term(element)
1126
- ""
1127
- when Metanorma::Document::Components::Blocks::NoteBlock
1128
- render_note(element)
1119
+ def render_em(el)
1120
+ render_inline_tag("em", el)
1121
+ end
1122
+
1123
+ def render_strong(el)
1124
+ render_inline_tag("strong", el)
1125
+ end
1126
+
1127
+ def render_tt(el)
1128
+ render_inline_tag("tt", el)
1129
+ end
1130
+
1131
+ def render_sub(el)
1132
+ render_inline_tag("sub", el)
1133
+ end
1134
+
1135
+ def render_sup(el)
1136
+ render_inline_tag("sup", el)
1137
+ end
1138
+
1139
+ def render_small_caps(el)
1140
+ render_inline_tag("span", el, class: "small-caps")
1141
+ end
1142
+
1143
+ def render_underline(el)
1144
+ render_inline_tag("u", el)
1145
+ end
1146
+
1147
+ def render_strike(el)
1148
+ render_inline_tag("s", el)
1149
+ end
1150
+
1151
+ def render_br(*)
1152
+ @output << "<br />"
1153
+ end
1154
+
1155
+ def render_tab(*)
1156
+ @output << "\u00a0\u00a0"
1157
+ end
1158
+
1159
+ def render_span(el)
1160
+ xml_class = safe_attr(el, :class_attr).to_s
1161
+ html_class = html_class_for_span(xml_class) unless xml_class.empty?
1162
+ attrs = element_attrs(style: safe_attr(el, :style), class: html_class)
1163
+ tag("span", attrs) { render_mixed_inline(el) }
1164
+ end
1165
+
1166
+ def render_fn_inline(el)
1167
+ render_fn(el)
1168
+ end
1169
+
1170
+ def render_stem(el)
1171
+ @output << render_stem_content(el)
1172
+ end
1173
+
1174
+ def render_semx_inline(el)
1175
+ render_semx_content(el)
1176
+ end
1177
+
1178
+ def render_fmt_xref(el)
1179
+ target = safe_attr(el, :target) || safe_attr(el, :to_attr)
1180
+ if target
1181
+ attrs = element_attrs(href: "##{escape_html(target)}", class: "xref")
1182
+ tag("a", attrs) { render_mixed_inline(el) }
1129
1183
  else
1130
- # Attempt generic mixed content rendering for unknown inline types
1131
- if element.is_a?(Lutaml::Model::Serializable) && element.mixed?
1132
- render_mixed_inline(element)
1133
- end
1184
+ render_mixed_inline(el)
1134
1185
  end
1135
1186
  end
1136
1187
 
1188
+ def render_comma(*)
1189
+ @output << ", "
1190
+ end
1191
+
1192
+ def render_math(el)
1193
+ @output << el.content.to_s
1194
+ end
1195
+
1196
+ def render_asciimath(el)
1197
+ @output << %(<span class="stem">#{escape_html(Array(el.text).join)}</span>)
1198
+ end
1199
+
1200
+ def render_index(el)
1201
+ collect_index_term(el)
1202
+ ""
1203
+ end
1204
+
1205
+ def render_note_inline(el)
1206
+ render_note(el)
1207
+ end
1208
+
1137
1209
  def render_inline_collections(node)
1138
1210
  # Fallback: render text and inline collections sequentially
1139
1211
  texts = node.text
@@ -1167,7 +1239,7 @@ module Metanorma
1167
1239
  # Render SemxElement display content only, skipping semantic linkage.
1168
1240
  # semx wraps both semantic data (origin, xref, source, etc.) and
1169
1241
  # display content (fmt-xref, span, strong, etc.). Only render display.
1170
- def render_semx_content(element)
1242
+ def render_semx_content(element, **_opts)
1171
1243
  display_attrs = %i[text fmt_xref fmt_link fmt_concept span strong em sup p semx
1172
1244
  asciimath math sub_child tt_child br_child tab_child
1173
1245
  stem_child figure_child formula_child sourcecode_child]
@@ -1427,32 +1499,34 @@ module Metanorma
1427
1499
  parts.join
1428
1500
  end
1429
1501
 
1502
+ BLOCK_TYPES = Set[
1503
+ Metanorma::Document::Components::Paragraphs::ParagraphBlock,
1504
+ Metanorma::Document::Components::Tables::TableBlock,
1505
+ Metanorma::Document::Components::Lists::UnorderedList,
1506
+ Metanorma::Document::Components::Lists::OrderedList,
1507
+ Metanorma::Document::Components::Lists::DefinitionList,
1508
+ Metanorma::Document::Components::AncillaryBlocks::FigureBlock,
1509
+ Metanorma::Document::Components::Blocks::NoteBlock,
1510
+ Metanorma::Document::Components::AncillaryBlocks::ExampleBlock,
1511
+ Metanorma::Document::Components::AncillaryBlocks::SourcecodeBlock,
1512
+ Metanorma::Document::Components::AncillaryBlocks::FormulaBlock,
1513
+ Metanorma::Document::Components::MultiParagraph::QuoteBlock,
1514
+ Metanorma::Document::Components::MultiParagraph::AdmonitionBlock,
1515
+ Metanorma::Document::Components::Sections::HierarchicalSection,
1516
+ Metanorma::Document::Components::Sections::BasicSection,
1517
+ Metanorma::Document::Components::Sections::ContentSection,
1518
+ ].freeze
1519
+
1430
1520
  def html_class_for_span(xml_class)
1431
1521
  SPAN_ROLE_CLASSES[xml_class] || "span-#{xml_class}"
1432
1522
  end
1433
1523
 
1434
1524
  def block_element?(obj)
1435
- obj.is_a?(Metanorma::Document::Components::Paragraphs::ParagraphBlock) ||
1436
- obj.is_a?(Metanorma::Document::Components::Tables::TableBlock) ||
1437
- obj.is_a?(Metanorma::Document::Components::Lists::UnorderedList) ||
1438
- obj.is_a?(Metanorma::Document::Components::Lists::OrderedList) ||
1439
- obj.is_a?(Metanorma::Document::Components::Lists::DefinitionList) ||
1440
- obj.is_a?(Metanorma::Document::Components::AncillaryBlocks::FigureBlock) ||
1441
- obj.is_a?(Metanorma::Document::Components::Blocks::NoteBlock) ||
1442
- obj.is_a?(Metanorma::Document::Components::AncillaryBlocks::ExampleBlock) ||
1443
- obj.is_a?(Metanorma::Document::Components::AncillaryBlocks::SourcecodeBlock) ||
1444
- obj.is_a?(Metanorma::Document::Components::AncillaryBlocks::FormulaBlock) ||
1445
- obj.is_a?(Metanorma::Document::Components::MultiParagraph::QuoteBlock) ||
1446
- obj.is_a?(Metanorma::Document::Components::MultiParagraph::AdmonitionBlock) ||
1447
- obj.is_a?(Metanorma::Document::Components::Sections::HierarchicalSection) ||
1448
- obj.is_a?(Metanorma::Document::Components::Sections::BasicSection) ||
1449
- obj.is_a?(Metanorma::Document::Components::Sections::ContentSection)
1525
+ BLOCK_TYPES.any? { |type| obj.is_a?(type) }
1450
1526
  end
1451
1527
 
1452
1528
  def safe_attr(obj, method_name)
1453
- obj.public_send(method_name)
1454
- rescue NoMethodError
1455
- nil
1529
+ obj.public_send(method_name) if obj.respond_to?(method_name)
1456
1530
  end
1457
1531
 
1458
1532
  def collect_index_term(element)
@@ -1504,14 +1578,7 @@ module Metanorma
1504
1578
  end
1505
1579
 
1506
1580
  def escape_html(text)
1507
- return "" if text.nil?
1508
-
1509
- text
1510
- .to_s
1511
- .gsub("&", "&amp;")
1512
- .gsub("<", "&lt;")
1513
- .gsub(">", "&gt;")
1514
- .gsub('"', "&quot;")
1581
+ CGI.escapeHTML(text.to_s)
1515
1582
  end
1516
1583
 
1517
1584
  def extract_text_value(val)
@@ -5,7 +5,6 @@ module Metanorma
5
5
  # Renders BipmDocument components to HTML.
6
6
  # Extends IsoRenderer with BIPM-specific branding (institutional navy, scientific precision).
7
7
  class BipmRenderer < IsoRenderer
8
- registers_doc_type Metanorma::BipmDocument::Root
9
8
 
10
9
  def flavor_publishers(_doc_id)
11
10
  ["BIPM"]
@@ -5,7 +5,6 @@ module Metanorma
5
5
  # Renders CcDocument (CalConnect) components to HTML.
6
6
  # Extends IsoRenderer with CalConnect branding.
7
7
  class CcRenderer < IsoRenderer
8
- registers_doc_type Metanorma::CcDocument::Root
9
8
 
10
9
  def flavor_publishers(_doc_id)
11
10
  ["CalConnect"]
@@ -4,7 +4,6 @@ module Metanorma
4
4
  module Html
5
5
  # IEC brand: #0061a9 blue from logo
6
6
  class IecRenderer < IsoRenderer
7
- registers_doc_type Metanorma::IecDocument::Root
8
7
 
9
8
  def flavor_publishers(_doc_id)
10
9
  ["IEC"]
@@ -5,7 +5,6 @@ module Metanorma
5
5
  # Renders IeeeDocument components to HTML.
6
6
  # Extends IsoRenderer with IEEE branding.
7
7
  class IeeeRenderer < IsoRenderer
8
- registers_doc_type Metanorma::IeeeDocument::Root
9
8
 
10
9
  def flavor_publishers(_doc_id)
11
10
  ["IEEE"]
@@ -5,7 +5,6 @@ module Metanorma
5
5
  # Renders IetfDocument components to HTML.
6
6
  # Extends IsoRenderer with IETF/RFC branding.
7
7
  class IetfRenderer < IsoRenderer
8
- registers_doc_type Metanorma::IetfDocument::Root
9
8
 
10
9
  def flavor_publishers(_doc_id)
11
10
  ["IETF"]
@@ -4,7 +4,6 @@ module Metanorma
4
4
  module Html
5
5
  # IHO brand: #00AAA9 teal + #05164D navy + #FEDC5B gold from logo
6
6
  class IhoRenderer < IsoRenderer
7
- registers_doc_type Metanorma::IhoDocument::Root
8
7
 
9
8
  def flavor_publishers(_doc_id)
10
9
  ["IHO"]