metanorma-document 0.2.12 → 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/README.adoc +1 -1
- 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/semx_element.rb +7 -8
- 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 +133 -307
- data/lib/metanorma/html/component/base.rb +2 -9
- 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 +50 -0
- data/lib/metanorma/html/flavor_registry.rb +49 -0
- data/lib/metanorma/html/generator.rb +113 -29
- data/lib/metanorma/html/iso_renderer.rb +0 -142
- data/lib/metanorma/html/renderer_delegation.rb +21 -0
- data/lib/metanorma/html/renderers/block_renderer.rb +10 -0
- data/lib/metanorma/html/renderers/element_order_traversal.rb +48 -0
- data/lib/metanorma/html/renderers/inline_renderer.rb +49 -124
- data/lib/metanorma/html/renderers/pubid_renderer.rb +4 -26
- data/lib/metanorma/html/renderers.rb +2 -0
- data/lib/metanorma/html/standard_renderer.rb +198 -36
- data/lib/metanorma/html.rb +4 -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 +52 -0
- data/lib/metanorma/mirror/handlers/inline/rich_html_renderer.rb +34 -35
- data/lib/metanorma/mirror/handlers/inline.rb +1 -0
- 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/mirror/transformer.rb +91 -3
- data/lib/metanorma/mirror.rb +0 -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 +78 -50
- data/.rubocop_todo.yml +0 -327
- 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/metanorma_to_mirror.rb +0 -101
- 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/standard_document/annotation.rb +0 -37
|
@@ -11,9 +11,27 @@ module Metanorma
|
|
|
11
11
|
autoload :BlockRenderer, "metanorma/html/renderers/block_renderer"
|
|
12
12
|
autoload :SectionRenderer, "metanorma/html/renderers/section_renderer"
|
|
13
13
|
autoload :PubidRenderer, "metanorma/html/renderers/pubid_renderer"
|
|
14
|
+
autoload :ElementOrderTraversal,
|
|
15
|
+
"metanorma/html/renderers/element_order_traversal"
|
|
16
|
+
end
|
|
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"
|
|
14
26
|
end
|
|
15
27
|
|
|
16
28
|
class BaseRenderer
|
|
29
|
+
include Concerns::MetadataExtraction
|
|
30
|
+
include Concerns::PresentationValidation
|
|
31
|
+
include Concerns::SvgProcessing
|
|
32
|
+
include Concerns::TextExtraction
|
|
33
|
+
include Concerns::TocRegistry
|
|
34
|
+
|
|
17
35
|
LOGO_DIR = File.expand_path("../../../data/logos", __dir__)
|
|
18
36
|
|
|
19
37
|
SPAN_ROLE_CLASSES = {
|
|
@@ -84,16 +102,13 @@ module Metanorma
|
|
|
84
102
|
end
|
|
85
103
|
|
|
86
104
|
class RendererContext
|
|
105
|
+
include RendererDelegation
|
|
106
|
+
|
|
87
107
|
def initialize(renderer)
|
|
88
108
|
@renderer = renderer
|
|
89
109
|
end
|
|
90
110
|
|
|
91
|
-
def safe_attr(...) = @renderer.safe_attr(...)
|
|
92
|
-
def escape_html(...) = @renderer.escape_html(...)
|
|
93
|
-
def extract_block_label(...)= @renderer.extract_block_label(...)
|
|
94
|
-
def extract_plain_text(...)= @renderer.extract_plain_text(...)
|
|
95
111
|
def render_paragraph(...) = @renderer.render_paragraph(...)
|
|
96
|
-
def render_mixed_inline(...)= @renderer.render_mixed_inline(...)
|
|
97
112
|
def render_inline_element(...)= @renderer.render_inline_element(...)
|
|
98
113
|
def render_unordered_list(...)= @renderer.render_unordered_list(...)
|
|
99
114
|
def render_ordered_list(...)= @renderer.render_ordered_list(...)
|
|
@@ -107,7 +122,6 @@ module Metanorma
|
|
|
107
122
|
def render_image(...) = @renderer.render_image(...)
|
|
108
123
|
def render_stem_content(...)= @renderer.render_stem_content(...)
|
|
109
124
|
def register_figure_entry(...)= @renderer.register_figure_entry(...)
|
|
110
|
-
def render_liquid(...) = @renderer.render_liquid(...)
|
|
111
125
|
def render_note_children(...) = @renderer.render_note_children(...)
|
|
112
126
|
def render_simple_children(...) = @renderer.render_simple_children(...)
|
|
113
127
|
def render_full_block_children(...) = @renderer.render_full_block_children(...)
|
|
@@ -117,10 +131,6 @@ module Metanorma
|
|
|
117
131
|
@renderer_context ||= RendererContext.new(self)
|
|
118
132
|
end
|
|
119
133
|
|
|
120
|
-
def toc_entries
|
|
121
|
-
@toc_entries
|
|
122
|
-
end
|
|
123
|
-
|
|
124
134
|
attr_writer :document, :theme
|
|
125
135
|
|
|
126
136
|
def generate_full_document(document, **)
|
|
@@ -132,23 +142,15 @@ module Metanorma
|
|
|
132
142
|
assemble_document(body)
|
|
133
143
|
end
|
|
134
144
|
|
|
135
|
-
#
|
|
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
|
|
136
152
|
|
|
137
|
-
|
|
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
|
|
153
|
+
# --- Flavor configuration hooks ---
|
|
152
154
|
|
|
153
155
|
def theme
|
|
154
156
|
@theme ||= resolve_theme
|
|
@@ -162,9 +164,15 @@ module Metanorma
|
|
|
162
164
|
def flavor_name
|
|
163
165
|
return nil unless defined?(@document) && @document
|
|
164
166
|
|
|
165
|
-
@document.class
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
Metanorma::Html::Generator.flavors.name_for(@document.class)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Pubid module for the current document's flavor, or nil if the
|
|
171
|
+
# flavor has no Pubid support. Queries the shared FlavorRegistry.
|
|
172
|
+
def pubid_module
|
|
173
|
+
return nil unless defined?(@document) && @document
|
|
174
|
+
|
|
175
|
+
Metanorma::Html::Generator.flavors.pubid_module_for(@document.class)
|
|
168
176
|
end
|
|
169
177
|
|
|
170
178
|
def flavor_publishers(_doc_id)
|
|
@@ -269,25 +277,6 @@ module Metanorma
|
|
|
269
277
|
end.join("\n")
|
|
270
278
|
end
|
|
271
279
|
|
|
272
|
-
def load_logo_svg(filename, height: 32)
|
|
273
|
-
path = theme.resolve_asset(filename) || File.join(LOGO_DIR, filename)
|
|
274
|
-
return nil unless File.exist?(path)
|
|
275
|
-
|
|
276
|
-
svg = File.read(path)
|
|
277
|
-
svg = svg.sub(/\A<\?xml[^?]*\?>\s*/, "")
|
|
278
|
-
svg = svg.sub(/\A\s*<!--.*?-->\s*/m, "")
|
|
279
|
-
svg = svg.sub(/<svg\s/, '<svg class="header-logo" ')
|
|
280
|
-
svg = if svg.match?(/<svg[^>]*\sheight="[^"]*"/)
|
|
281
|
-
svg.sub(/(<svg[^>]*?)(\sheight="[^"]*")/,
|
|
282
|
-
"\\1 height=\"#{height}\"")
|
|
283
|
-
else
|
|
284
|
-
svg.sub(/(<svg\b)/, "\\1 height=\"#{height}\"")
|
|
285
|
-
end
|
|
286
|
-
svg.sub(/(<svg[^>]*?)\swidth="[^"]*"/, '\1')
|
|
287
|
-
rescue StandardError
|
|
288
|
-
nil
|
|
289
|
-
end
|
|
290
|
-
|
|
291
280
|
def build_footer
|
|
292
281
|
mn_logo = load_logo_svg(METANORMA_LOGO, height: 20)
|
|
293
282
|
render_liquid("_footer.html.liquid", {
|
|
@@ -296,22 +285,6 @@ module Metanorma
|
|
|
296
285
|
})
|
|
297
286
|
end
|
|
298
287
|
|
|
299
|
-
# --- ToC generation ---
|
|
300
|
-
|
|
301
|
-
def build_toc_html(entries)
|
|
302
|
-
entry_drops = entries.map { |e| Drops::TocEntryDrop.new(e) }
|
|
303
|
-
figure_drops = @figure_entries.map { |f| Drops::FigureListEntryDrop.new(f) }
|
|
304
|
-
table_drops = @table_entries.map { |t| Drops::FigureListEntryDrop.new(t) }
|
|
305
|
-
has_special_lists = !@figure_entries.empty? || !@table_entries.empty?
|
|
306
|
-
|
|
307
|
-
render_liquid("_toc.html.liquid", {
|
|
308
|
-
"entries" => entry_drops,
|
|
309
|
-
"figures" => figure_drops,
|
|
310
|
-
"tables" => table_drops,
|
|
311
|
-
"has_special_lists" => has_special_lists,
|
|
312
|
-
})
|
|
313
|
-
end
|
|
314
|
-
|
|
315
288
|
# --- Scripts ---
|
|
316
289
|
|
|
317
290
|
def build_scripts
|
|
@@ -337,181 +310,28 @@ module Metanorma
|
|
|
337
310
|
parts.join("\n")
|
|
338
311
|
end
|
|
339
312
|
|
|
340
|
-
# --- Validation ---
|
|
341
|
-
|
|
342
|
-
def validate_presentation_xml!
|
|
343
|
-
has_presentation = check_presentation_markers(@document)
|
|
344
|
-
return if has_presentation
|
|
345
|
-
|
|
346
|
-
raise ArgumentError,
|
|
347
|
-
"HTML generation requires Presentation XML input. " \
|
|
348
|
-
"Semantic XML does not contain formatting data needed for HTML. " \
|
|
349
|
-
"Use a '.presentation.xml' file instead."
|
|
350
|
-
end
|
|
351
|
-
|
|
352
|
-
def check_presentation_markers(node)
|
|
353
|
-
return false unless node
|
|
354
|
-
return false if node.is_a?(String)
|
|
355
|
-
|
|
356
|
-
if node.is_a?(Lutaml::Model::Serializable)
|
|
357
|
-
node_attrs = node.class.attributes
|
|
358
|
-
if node_attrs.key?(:type) && node.type == "presentation"
|
|
359
|
-
return true
|
|
360
|
-
end
|
|
361
|
-
if node_attrs.key?(:fmt_title) && node.fmt_title
|
|
362
|
-
return true
|
|
363
|
-
end
|
|
364
|
-
if node_attrs.key?(:displayorder) && node.displayorder
|
|
365
|
-
return true
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
%i[preface sections annex bibliography].each do |attr|
|
|
369
|
-
next unless node_attrs.key?(attr)
|
|
370
|
-
|
|
371
|
-
val = node.public_send(attr)
|
|
372
|
-
next unless val
|
|
373
|
-
|
|
374
|
-
Array(val).each { |v| return true if check_presentation_markers(v) }
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
node.each_mixed_content do |child|
|
|
378
|
-
next if child.is_a?(String)
|
|
379
|
-
return true if check_presentation_markers(child)
|
|
380
|
-
end
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
false
|
|
384
|
-
end
|
|
385
|
-
|
|
386
|
-
# --- Metadata extraction ---
|
|
387
|
-
|
|
388
|
-
def language
|
|
389
|
-
bibdata = @document.bibdata
|
|
390
|
-
return "en" unless bibdata
|
|
391
|
-
|
|
392
|
-
langs = bibdata.language
|
|
393
|
-
if langs && !langs.empty?
|
|
394
|
-
lang = langs.find { |l| l.current == "true" } || langs.first
|
|
395
|
-
lang.value || lang.to_s
|
|
396
|
-
else
|
|
397
|
-
"en"
|
|
398
|
-
end
|
|
399
|
-
end
|
|
400
|
-
|
|
401
|
-
def html_title
|
|
402
|
-
extract_display_title(@document.bibdata) || "Document"
|
|
403
|
-
end
|
|
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?
|
|
413
|
-
|
|
414
|
-
en = titles.find { |t| safe_attr(t, :language) == "en" }
|
|
415
|
-
found = en || titles.first
|
|
416
|
-
extract_text_value(found).to_s
|
|
417
|
-
end
|
|
418
|
-
|
|
419
|
-
def extract_primary_doc_id
|
|
420
|
-
bibdata = @document.bibdata
|
|
421
|
-
return nil unless bibdata
|
|
422
|
-
|
|
423
|
-
identifiers = bibdata.doc_identifier
|
|
424
|
-
return nil unless identifiers && !identifiers.empty?
|
|
425
|
-
|
|
426
|
-
first_id = identifiers.first
|
|
427
|
-
text = if first_id.is_a?(String)
|
|
428
|
-
first_id
|
|
429
|
-
elsif first_id.is_a?(Lutaml::Model::Serializable)
|
|
430
|
-
Array(first_id.value).join
|
|
431
|
-
else
|
|
432
|
-
first_id.to_s
|
|
433
|
-
end
|
|
434
|
-
text.strip.empty? ? nil : text.strip
|
|
435
|
-
end
|
|
436
|
-
|
|
437
|
-
# --- Registration helpers ---
|
|
438
|
-
|
|
439
|
-
def register_toc_entry(id:, level:, text:)
|
|
440
|
-
@toc_entries << { id: id, level: level, text: text }
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
def register_figure_entry(id:, text:)
|
|
444
|
-
@figure_entries << { id: id, text: text }
|
|
445
|
-
end
|
|
446
|
-
|
|
447
|
-
def figure_entries
|
|
448
|
-
@figure_entries
|
|
449
|
-
end
|
|
450
|
-
|
|
451
|
-
def register_table_entry(id:, text:)
|
|
452
|
-
@table_entries << { id: id, text: text }
|
|
453
|
-
end
|
|
454
|
-
|
|
455
|
-
def extract_plain_text(node)
|
|
456
|
-
return node.to_s if node.is_a?(String)
|
|
457
|
-
return extract_text_value(node).to_s unless node.is_a?(Lutaml::Model::Serializable)
|
|
458
|
-
|
|
459
|
-
parts = []
|
|
460
|
-
xml_mapping = node.class.mappings_for(:xml, node.lutaml_register)
|
|
461
|
-
|
|
462
|
-
if node.element_order.is_a?(Array) && xml_mapping
|
|
463
|
-
element_to_attr = {}
|
|
464
|
-
xml_mapping.mapping_elements_hash.each_value do |rule_or_array|
|
|
465
|
-
Array(rule_or_array).each do |rule|
|
|
466
|
-
element_to_attr[rule.name.to_s] = rule.to
|
|
467
|
-
end
|
|
468
|
-
end
|
|
469
|
-
|
|
470
|
-
indices = Hash.new(0)
|
|
471
|
-
node.element_order.each do |el|
|
|
472
|
-
next unless el.is_a?(Lutaml::Xml::Element)
|
|
473
|
-
|
|
474
|
-
if el.text?
|
|
475
|
-
parts << el.text_content.to_s
|
|
476
|
-
elsif el.name == "tab"
|
|
477
|
-
parts << " "
|
|
478
|
-
elsif el.name == "br"
|
|
479
|
-
parts << " "
|
|
480
|
-
elsif el.element?
|
|
481
|
-
attr_name = element_to_attr[el.name]
|
|
482
|
-
if attr_name
|
|
483
|
-
coll = node.public_send(attr_name)
|
|
484
|
-
obj = if coll.is_a?(Array)
|
|
485
|
-
idx = indices[attr_name]
|
|
486
|
-
indices[attr_name] += 1
|
|
487
|
-
coll[idx]
|
|
488
|
-
else
|
|
489
|
-
coll
|
|
490
|
-
end
|
|
491
|
-
text = extract_plain_text(obj)
|
|
492
|
-
parts << (text.empty? ? " " : text)
|
|
493
|
-
elsif el.name == "span"
|
|
494
|
-
parts << " "
|
|
495
|
-
end
|
|
496
|
-
end
|
|
497
|
-
end
|
|
498
|
-
end
|
|
499
|
-
|
|
500
|
-
if parts.join.strip.empty?
|
|
501
|
-
t = safe_attr(node, :text)
|
|
502
|
-
parts << (t.is_a?(Array) ? t.join : t.to_s) if t
|
|
503
|
-
end
|
|
504
|
-
|
|
505
|
-
parts.join.strip.gsub(" ", " ")
|
|
506
|
-
end
|
|
507
|
-
|
|
508
313
|
# --- Dispatch ---
|
|
509
314
|
|
|
510
315
|
def render(node, **)
|
|
511
316
|
return escape_html(node) if node.is_a?(String)
|
|
512
317
|
|
|
513
318
|
method = lookup_dispatch(node.class, :render_registry)
|
|
514
|
-
|
|
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}"
|
|
515
335
|
end
|
|
516
336
|
|
|
517
337
|
def render_inline_element(element, **)
|
|
@@ -543,6 +363,8 @@ module Metanorma
|
|
|
543
363
|
:render_note
|
|
544
364
|
register_render Metanorma::Document::Components::AncillaryBlocks::ExampleBlock,
|
|
545
365
|
:render_example
|
|
366
|
+
register_render Metanorma::StandardDocument::Blocks::Form,
|
|
367
|
+
:render_form
|
|
546
368
|
register_render Metanorma::Document::Components::AncillaryBlocks::SourcecodeBlock,
|
|
547
369
|
:render_sourcecode
|
|
548
370
|
register_render Metanorma::Document::Components::AncillaryBlocks::FormulaBlock,
|
|
@@ -608,6 +430,8 @@ module Metanorma
|
|
|
608
430
|
:render_fmt_stem
|
|
609
431
|
register_inline_render Metanorma::Document::Components::Inline::CommaElement,
|
|
610
432
|
:render_comma
|
|
433
|
+
register_inline_render Metanorma::StandardDocument::Elements::Input,
|
|
434
|
+
:render_input
|
|
611
435
|
register_inline_render Metanorma::Document::Components::Inline::EnumCommaElement,
|
|
612
436
|
:render_comma
|
|
613
437
|
register_inline_render Metanorma::Document::Components::IdElements::Bookmark,
|
|
@@ -624,52 +448,66 @@ module Metanorma
|
|
|
624
448
|
:render_index
|
|
625
449
|
register_inline_render Metanorma::Document::Components::Blocks::NoteBlock,
|
|
626
450
|
:render_note_inline
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
register_inline_render
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
451
|
+
|
|
452
|
+
# Transparent inline wrappers: element classes whose render
|
|
453
|
+
# semantics are "iterate mixed_content and render each child".
|
|
454
|
+
# Grouped here so adding a new transparent wrapper is one entry,
|
|
455
|
+
# not another register_inline_render line. Classes with a more
|
|
456
|
+
# specific handler register themselves above.
|
|
457
|
+
TRANSPARENT_INLINE_WRAPPERS = [
|
|
458
|
+
Metanorma::Document::Components::Inline::FmtNameElement,
|
|
459
|
+
Metanorma::Document::Components::Inline::FmtTitleElement,
|
|
460
|
+
Metanorma::Document::Components::Inline::FmtXrefLabelElement,
|
|
461
|
+
Metanorma::Document::Components::Inline::FmtFnLabelElement,
|
|
462
|
+
Metanorma::Document::Components::Inline::FmtConceptElement,
|
|
463
|
+
Metanorma::Document::Components::Inline::FmtAnnotationStartElement,
|
|
464
|
+
Metanorma::Document::Components::Inline::FmtAnnotationEndElement,
|
|
465
|
+
Metanorma::Document::Components::Inline::FmtAnnotationBodyElement,
|
|
466
|
+
Metanorma::Document::Components::Inline::VariantTitleElement,
|
|
467
|
+
Metanorma::Document::Components::Inline::LocalizedStringElement,
|
|
468
|
+
Metanorma::Document::Components::Inline::TitleWithAnnotationElement,
|
|
469
|
+
Metanorma::Document::Components::Inline::BiblioTagElement,
|
|
470
|
+
Metanorma::Document::Components::Inline::NameWithIdElement,
|
|
471
|
+
Metanorma::Document::Components::Inline::DisplayTextElement,
|
|
472
|
+
Metanorma::Document::Components::Inline::FmtFootnoteContainerElement,
|
|
473
|
+
Metanorma::Document::Components::Inline::FmtFnBodyElement,
|
|
474
|
+
Metanorma::Document::Components::Inline::FmtPreferredElement,
|
|
475
|
+
Metanorma::Document::Components::Inline::FmtDefinitionElement,
|
|
476
|
+
Metanorma::Document::Components::Inline::FmtTermsourceElement,
|
|
477
|
+
Metanorma::Document::Components::Inline::FmtAdmittedElement,
|
|
478
|
+
Metanorma::Document::Components::Inline::FmtIdentifierElement,
|
|
479
|
+
Metanorma::Document::Components::Inline::FmtSourcecodeElement,
|
|
480
|
+
].freeze
|
|
481
|
+
|
|
482
|
+
TRANSPARENT_INLINE_WRAPPERS.each do |klass|
|
|
483
|
+
register_inline_render klass, :render_mixed_inline
|
|
484
|
+
end
|
|
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
|
|
671
498
|
|
|
672
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)
|
|
673
511
|
self.class.ancestors.each do |ancestor|
|
|
674
512
|
next unless ancestor.is_a?(Class) && (ancestor == BaseRenderer || ancestor < BaseRenderer)
|
|
675
513
|
|
|
@@ -684,6 +522,13 @@ module Metanorma
|
|
|
684
522
|
""
|
|
685
523
|
end
|
|
686
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
|
+
|
|
687
532
|
def render_noop_inline(*)
|
|
688
533
|
nil
|
|
689
534
|
end
|
|
@@ -716,6 +561,7 @@ module Metanorma
|
|
|
716
561
|
def render_semx_inline(el) = @inline_renderer.render_semx_inline(el)
|
|
717
562
|
def render_fmt_xref(el) = @inline_renderer.render_fmt_xref(el)
|
|
718
563
|
def render_comma(*) = @inline_renderer.render_comma
|
|
564
|
+
def render_input(el) = @inline_renderer.render_input(el)
|
|
719
565
|
def render_math(el) = @inline_renderer.render_math(el)
|
|
720
566
|
def render_asciimath(el) = @inline_renderer.render_asciimath(el)
|
|
721
567
|
def render_index(el) = @inline_renderer.render_index(el)
|
|
@@ -776,6 +622,8 @@ module Metanorma
|
|
|
776
622
|
@block_renderer.render_example(example, **)
|
|
777
623
|
end
|
|
778
624
|
|
|
625
|
+
def render_form(form, **) = @block_renderer.render_form(form, **)
|
|
626
|
+
|
|
779
627
|
def render_sourcecode(sc,
|
|
780
628
|
**)
|
|
781
629
|
@block_renderer.render_sourcecode(sc, **)
|
|
@@ -887,7 +735,12 @@ level = 1)
|
|
|
887
735
|
end
|
|
888
736
|
|
|
889
737
|
obj.public_send(method_name)
|
|
890
|
-
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
|
+
|
|
891
744
|
nil
|
|
892
745
|
end
|
|
893
746
|
|
|
@@ -902,7 +755,8 @@ level = 1)
|
|
|
902
755
|
target_id: @current_section_id,
|
|
903
756
|
target_text: @current_section_number,
|
|
904
757
|
)
|
|
905
|
-
rescue StandardError
|
|
758
|
+
rescue StandardError => e
|
|
759
|
+
record_render_warning("index term dropped: #{e.class}: #{e.message}")
|
|
906
760
|
nil
|
|
907
761
|
end
|
|
908
762
|
|
|
@@ -942,34 +796,6 @@ level = 1)
|
|
|
942
796
|
CGI.escapeHTML(text.to_s)
|
|
943
797
|
end
|
|
944
798
|
|
|
945
|
-
def extract_text_value(val)
|
|
946
|
-
return nil if val.nil?
|
|
947
|
-
return val if val.is_a?(String)
|
|
948
|
-
|
|
949
|
-
if val.is_a?(Array)
|
|
950
|
-
val.map { |v| extract_text_value(v) }.join
|
|
951
|
-
elsif val.is_a?(Lutaml::Model::Serializable)
|
|
952
|
-
c = safe_attr(val, :content)
|
|
953
|
-
if c && !c.equal?(val)
|
|
954
|
-
extract_text_value(c)
|
|
955
|
-
else
|
|
956
|
-
t = safe_attr(val, :text)
|
|
957
|
-
if t
|
|
958
|
-
extract_text_value(t)
|
|
959
|
-
else
|
|
960
|
-
v = safe_attr(val, :value)
|
|
961
|
-
if v
|
|
962
|
-
extract_text_value(v)
|
|
963
|
-
else
|
|
964
|
-
val.to_s
|
|
965
|
-
end
|
|
966
|
-
end
|
|
967
|
-
end
|
|
968
|
-
else
|
|
969
|
-
val.to_s
|
|
970
|
-
end
|
|
971
|
-
end
|
|
972
|
-
|
|
973
799
|
def render_footnotes_section
|
|
974
800
|
return nil if @footnote_collector.empty?
|
|
975
801
|
|
|
@@ -4,6 +4,8 @@ module Metanorma
|
|
|
4
4
|
module Html
|
|
5
5
|
module Component
|
|
6
6
|
class Base
|
|
7
|
+
include Metanorma::Html::RendererDelegation
|
|
8
|
+
|
|
7
9
|
attr_reader :renderer
|
|
8
10
|
|
|
9
11
|
def initialize(renderer)
|
|
@@ -38,18 +40,9 @@ module Metanorma
|
|
|
38
40
|
raise NotImplementedError, "#{self.class}#render not implemented"
|
|
39
41
|
end
|
|
40
42
|
|
|
41
|
-
def escape_html(...) = renderer.escape_html(...)
|
|
42
|
-
def render_mixed_inline(...) = renderer.render_mixed_inline(...)
|
|
43
43
|
def render_mixed_content_in_order(...) = renderer.render_mixed_content_in_order(...)
|
|
44
44
|
def element_attrs(...) = renderer.element_attrs(...)
|
|
45
|
-
def safe_attr(obj, method_name) = renderer.safe_attr(obj, method_name)
|
|
46
|
-
def extract_plain_text(node) = renderer.extract_plain_text(node)
|
|
47
45
|
def extract_text_value(val) = renderer.extract_text_value(val)
|
|
48
|
-
def render_liquid(...) = renderer.render_liquid(...)
|
|
49
|
-
|
|
50
|
-
def extract_block_label(block, default)
|
|
51
|
-
renderer.extract_block_label(block, default)
|
|
52
|
-
end
|
|
53
46
|
|
|
54
47
|
def self.register_in(registry)
|
|
55
48
|
handled_classes.each do |klass|
|
|
@@ -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
|