Module: Metanorma::Core::Boilerplate
Overview
Inline-snippet boilerplate handling shared across metanorma-standoc and metanorma (collection layer). Provides Liquid + inline-Asciidoc substitution into docidentifier-like snippets, plus the option-isolating Asciidoctor convert wrapper used to keep nested conversions from leaking attribute / extension-registry state.
Standoc’s Cleanup::Boilerplate includes this module and overrides #boilerplate_snippet_cleanup to apply standoc-specific namespace cleanup and footnote separation; the metanorma collection layer calls into it as module functions (e.g. Metanorma::Core::Boilerplate.docidentifier_boilerplate_isodoc) without including the module — both forms are supported via extend self at the bottom of this module.
Constant Summary collapse
- SAFE_SHARED_ATTRIBUTES =
Asciidoctor attributes that are safe to inherit from an outer conversion context into an isolated nested convert. Anything not in this set is dropped.
{ "source-highlighter" => "html-pipeline", "nofooter" => "", "no-header-footer" => "", }.freeze
Instance Method Summary collapse
-
#adoc2xml(text, flavour, flush_caches: false, localdir: nil) ⇒ Nokogiri::XML::Node, String
Wrap
textin the standard headless dummy document used across the metanorma stack and run an isolated Asciidoctor convert against the given backend. -
#boilerplate_snippet_cleanup(node) ⇒ Nokogiri::XML::Node
Extension hook invoked by #boilerplate_snippet_convert on the output of #adoc2xml before localisation.
-
#boilerplate_snippet_convert(adoc, isodoc, lang:, script:, backend:, flush_caches: false, localdir: nil) ⇒ String
Convert a snippet of Asciidoc-with-Liquid text into the localised, cleaned-up XML string suitable for substitution into a surrounding document.
-
#docidentifier_boilerplate_isodoc(xmldoc, isodoc, lang:, script:, backend:, flush_caches: false, localdir: nil) ⇒ Nokogiri::XML::Document, Nokogiri::XML::Node
Iterate over every <docidentifier @boilerplate> element in
xmldocand replace its content with the Liquid-substituted, Asciidoc-rendered output. -
#docidentifier_templates?(xmldoc) ⇒ Boolean
Predicate: are there any <docidentifier @boilerplate=“true”> nodes in
xmldocthat #docidentifier_boilerplate_isodoc would substitute? Callers use this to decide whether to refresh downstream state (e.g. re-seedisodoc.metafrom the resolved bibdata) after the substitution pass — cheap pre-check, avoids depending on a return-value side channel from the mutating substitution method. -
#extract_preserved_options(user_opt) ⇒ Hash
Compute the option set carried over from outer conversion state: a curated subset (
:safe,:base_dir) plus the SAFE_SHARED_ATTRIBUTES hash if the caller did not supply:attributes. -
#isolated_asciidoctor_convert(content, options = {}) ⇒ String
Run
Asciidoctor.convertwith curated options so that attributes,base_dir, and safe-mode setting do NOT leak in from any outer conversion context.
Instance Method Details
#adoc2xml(text, flavour, flush_caches: false, localdir: nil) ⇒ Nokogiri::XML::Node, String
Wrap text in the standard headless dummy document used across the metanorma stack and run an isolated Asciidoctor convert against the given backend. Returns the //sections subtree as a Nokogiri node so callers can splice its children into a surrounding document.
If text is already valid XML (root element parses), it is returned verbatim — this lets callers stash pre-converted XML alongside Asciidoc snippets without a special case.
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/metanorma/core/boilerplate.rb', line 188 def adoc2xml(text, flavour, flush_caches: false, localdir: nil) Nokogiri::XML(text).root and return text f = flush_caches ? ":flush-caches:\n" : "" doc = <<~ADOC = X A :semantic-metadata-headless: true :no-isobib: #{f}:novalid: :!sectids: #{text} ADOC c = isolated_asciidoctor_convert( doc, backend: flavour, header_footer: true, localdir: localdir, ) Nokogiri::XML(c).at("//xmlns:sections") end |
#boilerplate_snippet_cleanup(node) ⇒ Nokogiri::XML::Node
Extension hook invoked by #boilerplate_snippet_convert on the output of #adoc2xml before localisation. Default implementation is the identity. Standoc’s Cleanup::Boilerplate overrides it to apply boilerplate_xml_cleanup and footnote renumbering; the metanorma collection layer leaves it as identity (no standoc-namespace cleanup needed at that stage).
91 92 93 |
# File 'lib/metanorma/core/boilerplate.rb', line 91 def boilerplate_snippet_cleanup(node) node end |
#boilerplate_snippet_convert(adoc, isodoc, lang:, script:, backend:, flush_caches: false, localdir: nil) ⇒ String
Convert a snippet of Asciidoc-with-Liquid text into the localised, cleaned-up XML string suitable for substitution into a surrounding document. Three stages run in order:
-
Liquid substitution via
isodoc.populate_template. -
Asciidoc-to-XML conversion via #adoc2xml (wraps in a headless dummy document, runs an isolated Asciidoctor convert, extracts the
//sectionssubtree). -
#boilerplate_snippet_cleanup extension hook (default identity; standoc overrides for namespace-cleanup + footnote separation).
-
Localisation via
isodoc.i18n.l10n.
71 72 73 74 75 76 77 78 |
# File 'lib/metanorma/core/boilerplate.rb', line 71 def boilerplate_snippet_convert(adoc, isodoc, lang:, script:, backend:, flush_caches: false, localdir: nil) b = isodoc.populate_template(adoc, nil) node = adoc2xml(b, backend, flush_caches: flush_caches, localdir: localdir) ret = boilerplate_snippet_cleanup(node) isodoc.i18n.l10n(ret.children.to_xml, lang, script).strip end |
#docidentifier_boilerplate_isodoc(xmldoc, isodoc, lang:, script:, backend:, flush_caches: false, localdir: nil) ⇒ Nokogiri::XML::Document, Nokogiri::XML::Node
Iterate over every <docidentifier @boilerplate> element in xmldoc and replace its content with the Liquid-substituted, Asciidoc-rendered output. Called from standoc’s cleanup pipeline (post-processing semantic XML) and from the metanorma collection layer (pre-processing the collection bibdata before MergeBibitems hands it to Relaton — see issue github.com/metanorma/metanorma/issues/558).
The @boilerplate attribute is removed in all matched cases; substitution is performed only when its value is “true”. The output of #boilerplate_snippet_convert is a serialised <sections><p>…</p></sections>; the inner <p> children are spliced into the docidentifier (or the raw output if no <p> was produced).
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/metanorma/core/boilerplate.rb', line 125 def docidentifier_boilerplate_isodoc(xmldoc, isodoc, lang:, script:, backend:, flush_caches: false, localdir: nil) xmldoc.xpath("//docidentifier[@boilerplate]").each do |d| do_substitute = d["boilerplate"] == "true" d.delete("boilerplate") do_substitute or next id = boilerplate_snippet_convert( d.children.to_xml, isodoc, lang: lang, script: script, backend: backend, flush_caches: flush_caches, localdir: localdir, ) p_node = Nokogiri::XML(id).at("//p") new_children = p_node ? p_node.children.to_xml : id # If the rendered template is blank (e.g. a Liquid template # that gates on a missing docnumeric), drop the # <docidentifier> entirely instead of leaving an empty # element behind. Downstream Relaton flavours # (relaton-iho, relaton-cc, …) eagerly call Pubid::*::Identifier.parse # on the docidentifier content; an empty string is truthy # in Ruby and would crash that parser, so the empty element # must not survive this pass. if new_children.to_s.strip.empty? d.remove else d.children = new_children end end xmldoc end |
#docidentifier_templates?(xmldoc) ⇒ Boolean
Predicate: are there any <docidentifier @boilerplate=“true”> nodes in xmldoc that #docidentifier_boilerplate_isodoc would substitute? Callers use this to decide whether to refresh downstream state (e.g. re-seed isodoc.meta from the resolved bibdata) after the substitution pass — cheap pre-check, avoids depending on a return-value side channel from the mutating substitution method.
166 167 168 |
# File 'lib/metanorma/core/boilerplate.rb', line 166 def docidentifier_templates?(xmldoc) xmldoc.xpath("//docidentifier[@boilerplate = 'true']").any? end |
#extract_preserved_options(user_opt) ⇒ Hash
Compute the option set carried over from outer conversion state: a curated subset (:safe, :base_dir) plus the SAFE_SHARED_ATTRIBUTES hash if the caller did not supply :attributes. Caller’s own option hash takes precedence for everything except “novalid”, which the caller of #isolated_asciidoctor_convert forces.
256 257 258 259 260 261 262 263 264 265 266 267 268 |
# File 'lib/metanorma/core/boilerplate.rb', line 256 def (user_opt) = {} [:safe] = user_opt[:safe] if user_opt.key?(:safe) localdir = user_opt[:localdir] || (defined?(@localdir) ? @localdir : nil) if localdir && !user_opt.key?(:base_dir) [:base_dir] = localdir end if user_opt[:attributes].nil? [:attributes] = SAFE_SHARED_ATTRIBUTES.dup end end |
#isolated_asciidoctor_convert(content, options = {}) ⇒ String
Run Asciidoctor.convert with curated options so that attributes, base_dir, and safe-mode setting do NOT leak in from any outer conversion context. Forces novalid for the inner conversion. The conversion stack is tracked in @isolated_conversion_stack for diagnostics; the ensure pop guarantees the marker is balanced even on exception.
localdir may be passed inside options as :localdir; if so it becomes :base_dir for the inner convert (unless the caller supplied an explicit :base_dir). The :localdir key is stripped before delegating to Asciidoctor.convert so it does not appear as an unknown option. Callers that include this module from a class with @localdir get :base_dir wired up from there as a fallback.
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/metanorma/core/boilerplate.rb', line 227 def isolated_asciidoctor_convert(content, = {}) @isolated_conversion_stack ||= [] @isolated_conversion_stack << true begin preserved = () = .dup .delete(:localdir) isolated = preserved.merge().merge( attributes: (preserved[:attributes] || {}).merge( "novalid" => "", ), ) ::Asciidoctor.convert(content, isolated) ensure @isolated_conversion_stack.pop end end |