isodoc 3.7.2 → 3.7.4.pre.alpha.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f336d204ef62fe18fa00d602cd7281ef876b14cc63c216e718eb8bf60be0f5d
4
- data.tar.gz: 354606fcf812e4d4378a36dac7aa771e875b947fa21ade182faec0b52cff63f9
3
+ metadata.gz: f9eca206080376f0d963170e2c45ec57945a257fda3ab170217b9719fb8c25a6
4
+ data.tar.gz: 10fa45dadb8322115235ab358ada67bb86a64ede4f25d05a10e8f6485773f035
5
5
  SHA512:
6
- metadata.gz: e29dd8f0a3e9181226c4df3dccc334497a313052b841671bfbf9b342fe577f4778578c3018eee82409a664f7a11cc937f7ed539dec6d74d2d2d8485a9d3f64cf
7
- data.tar.gz: 2e1de040cfb8293321f989e31250f31b7b3c8a33167eb0846e34e08ae40d329f9e69342d6010b05b23aae615260457b3023d4f02baf084af5c6429dbe94e115d
6
+ metadata.gz: 3e360b76748e8e03283b2b280363c7bf7a7b0c696e2bba62f9daf621e0006dfd791bc8ae5989d3a8229b9445fd02559f644ce6460ed31a9ca77ae495e6fe7323
7
+ data.tar.gz: 91162667360c2a04cd6142e78297c13d4c256f6bc908c1fdd1fc43e35da9e7ac1b4c75ba0cd96abf4e382239823bb5fee41e1fd2b1958fe069e335616d7ce3e0
data/.gitignore CHANGED
@@ -1 +1,26 @@
1
1
  spec/assets/img.svg
2
+
3
+ # Generated test output
4
+ test.doc
5
+ test.err.html
6
+ test.html
7
+ test.presentation.xml
8
+ test_*_files/
9
+ test_*_htmlimages/
10
+ _test_attachments/
11
+ spec/test.html
12
+ spec/test_*_htmlimages/
13
+ spec/assets/iso.doc
14
+ spec/assets/iso.headless.html
15
+ spec/assets/odf1.emf
16
+ spec/assets/test.html
17
+
18
+ # Tooling
19
+ coverage/
20
+ pkg/
21
+ .rspec_status
22
+ .rubocop-https---*
23
+ .rubocop-remote-*
24
+
25
+ # Gem library: resolve at install time
26
+ Gemfile.lock
data/README.adoc CHANGED
@@ -85,3 +85,18 @@ NOTE: In the HTML stylesheets specific to standards, the Cover page and Intro pa
85
85
  This gem relies on https://github.com/metanorma/html2doc[html2doc] to generate Microsoft Word documents.
86
86
 
87
87
  Please see https://github.com/metanorma/html2doc#converting-document-output-to-native-word-docx[this post-processing procedure] to convert output into a native-`docx` document.
88
+
89
+ == Stylesheet compilation and `sassc-embedded`
90
+
91
+ The `.scss` stylesheets under `lib/` are **precompiled to `.css` when this gem is packaged**, not at consumer render time:
92
+
93
+ * `lib/isodoc/gem_tasks.rb` enhances the standard `rake build` with a `build_scss` step: every `lib/**/*.scss` is compiled to a sibling `.css` (using `sassc-embedded`), the compiled files are packed into the released `.gem`, and are then removed from the git working tree. Compiled stylesheets are never committed.
94
+
95
+ At runtime, `lib/isodoc/css.rb#precompiled_style_or_original` prefers the sibling `.css` shipped inside the installed gem. **Consumers of released isodoc gems therefore never load `sassc-embedded`** — which is why the gemspec does not declare it.
96
+
97
+ The runtime SCSS compilation path (`css.rb#convert_scss`, which `require`s `sassc-embedded`) only runs when the sibling `.css` is absent, which is the case when:
98
+
99
+ * working from a git checkout of this repository (no compiled `.css` on disk), or
100
+ * a downstream project resolves isodoc from git (e.g. `gem "isodoc", github: "metanorma/isodoc"`): bundler installs the gemspec's file list, which carries only `.scss`
101
+
102
+ NOTE: Downstream maintainers: if your Gemfile pins isodoc from git, your HTML renders will `require "sassc-embedded"` at runtime even though released-gem consumers never do. A downstream `gem "sassc-embedded"` line in that setup is load-bearing, not dead weight — and it becomes droppable once you revert to a released isodoc gem.
data/isodoc.gemspec CHANGED
@@ -28,15 +28,22 @@ Gem::Specification.new do |spec|
28
28
  end
29
29
  spec.required_ruby_version = Gem::Requirement.new(">= 3.3.0")
30
30
 
31
- spec.add_dependency "base64"
31
+ spec.add_dependency "base64", ">= 0.1.0"
32
32
  spec.add_dependency "bigdecimal"
33
33
  spec.add_dependency "html2doc", "~> 1.12"
34
- # spec.add_dependency "isodoc-i18n", "~> 1.1.0" # already in relaton-render and mn-requirements
35
- spec.add_dependency "relaton-cli", "~> 2.1.0"
36
- # spec.add_dependency "metanorma-utils", "~> 1.5.0" # already in isodoc-i18n
34
+ # spec.add_dependency "isodoc-i18n", "~> 1.5" # already in relaton-render and mn-requirements
35
+ spec.add_dependency "relaton-cli", ">= 3.0.0.pre.alpha", "< 3.1.0"
36
+ # spec.add_dependency "metanorma-utils", "~> 2.0" # already in isodoc-i18n
37
37
  spec.add_dependency "mn2pdf", ">= 2.13"
38
38
  spec.add_dependency "mn-requirements", "~> 0.5.0"
39
- spec.add_dependency "relaton-render", "~> 1.3.0"
39
+ # Allowance for the relaton 3.0.0.pre family (mirrors the relaton-cli
40
+ # allowance in #824): relaton 3.0.0.pre requires relaton-render
41
+ # 3.0.0.pre, which joins the family's versioning and restores the 1.x
42
+ # leaves as deprecated shims, so flavor stacks boot against it.
43
+ # Released isodoc lines keep resolving relaton-render 1.3.0; only a
44
+ # stack that explicitly adopts the relaton prerelease co-resolves
45
+ # 3.0.0.pre.
46
+ spec.add_dependency "relaton-render", "~> 3.0.0.pre.alpha", "< 5"
40
47
  spec.add_dependency "roman-numerals"
41
48
  spec.add_dependency "rouge", "~> 4.0"
42
49
  spec.add_dependency "thread_safe"
@@ -44,7 +44,7 @@ module IsoDoc
44
44
  # bare: do not insert any prefatory material (coverpage, boilerplate)
45
45
  # tocfigures: add ToC for figures
46
46
  # toctables: add ToC for tables
47
- # tocrecommendations: add ToC for rcommendations
47
+ # tocrecommendations: add ToC for recommendations
48
48
  # tocexamples: add ToC for examples
49
49
  # fonts: fontist fonts to install
50
50
  # fontlicenseagreement: fontist font license agreement
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "sassc-embedded"
4
- require "isodoc/sassc_importer"
3
+ # sassc-embedded / sassc_importer load lazily in load_scss_paths: every
4
+ # flavor Rakefile requires this file, but only scss compilation needs the
5
+ # (undeclared) sassc dependency.
5
6
  require "rake/clean"
6
7
  require "tmpdir"
7
8
 
data/lib/isodoc/init.rb CHANGED
@@ -7,6 +7,7 @@ module IsoDoc
7
7
  def xref_init(lang, script, _klass, i18n, options)
8
8
  html = HtmlConvert.new(language: @lang, script: @script)
9
9
  @xrefs = Xref.new(lang, script, html, i18n, options)
10
+ @xrefs
10
11
  end
11
12
 
12
13
  def i18n_init(lang, script, locale, i18nyaml = nil)
@@ -141,14 +141,27 @@ module IsoDoc
141
141
  set(:edition_display,
142
142
  isoxml.at(ns("//bibdata/edition#{currlang}"))&.text)
143
143
  set(:docyear, isoxml.at(ns("//bibdata/copyright/from"))&.text)
144
- set(:draft, isoxml.at(ns("//bibdata/version"))&.text)
145
- revdate = isoxml.at(ns("//bibdata/date[@type='updated']/on"))&.text
144
+ ver = isoxml.at(ns("//bibdata/version"))
145
+ set(:draft, draft_from_version(ver))
146
+ revdate = ver&.at(ns("./revision-date"))&.text ||
147
+ isoxml.at(ns("//bibdata/date[@type='updated']/on"))&.text
146
148
  set(:revdate, revdate)
147
149
  set(:revdate_monthyear, monthyr(revdate))
148
150
  set(:draftinfo,
149
151
  draftinfo(get[:draft], get[:revdate]))
150
152
  end
151
153
 
154
+ # <version> carries either its draft directly (legacy hand-built
155
+ # documents) or revision-date/draft children (the semantic
156
+ # pipeline's serialization).
157
+ def draft_from_version(ver)
158
+ return nil if ver.nil?
159
+
160
+ draft = ver.at(ns("./draft"))&.text
161
+ draft ||= ver.children.select(&:text?).join.strip
162
+ draft&.empty? ? nil : draft
163
+ end
164
+
152
165
  def title(isoxml, _out)
153
166
  main = isoxml.at(ns("//bibdata/title[@language='#{@lang}']"))
154
167
  &.children and set(:doctitle, to_xml(main))
@@ -16,7 +16,10 @@ module IsoDoc
16
16
 
17
17
  def bibdata_from_xml(model, node)
18
18
  node or return
19
- model.bibdata = Relaton::Cli.parse_xml(node.adapter_node.native)
19
+ # Cli.parse_xml's contract is a Nokogiri::XML::Document
20
+ # (remove_namespaces!, at); under the moxml/leptris adapter the
21
+ # mapping node's native is not Nokogiri. Convert via markup.
22
+ model.bibdata = Relaton::Cli.parse_xml(Nokogiri::XML(node.to_xml))
20
23
  end
21
24
 
22
25
  def bibdata_to_xml(model, parent, doc); end
@@ -60,6 +63,7 @@ module IsoDoc
60
63
  # meta[:bibdata] with the resolved docidentifier.
61
64
  # See https://github.com/metanorma/metanorma/issues/558.
62
65
  stripped.xpath("//docidentifier[@boilerplate = 'true']").each(&:remove)
66
+ collapse_lang_variants!(stripped.root)
63
67
  bib = BibdataConfig.from_xml(
64
68
  "<metanorma>#{stripped.root.to_xml}</metanorma>",
65
69
  ).bibdata or return nil
@@ -72,6 +76,36 @@ module IsoDoc
72
76
  nil
73
77
  end
74
78
 
79
+ # Relaton models these fields as singular. Presentation i18n
80
+ # (PresentationXMLConvert#tag_translate, JIS #edition_integer?)
81
+ # intentionally emits language-variant siblings — language="",
82
+ # language="ja", numberonly="true", … — for rendering; feeding them
83
+ # to Relaton yields an Array and CollectionTrueMissingError /
84
+ # IncorrectModelError. Keep the canonical node (no @language, not
85
+ # @numberonly) so Liquid still sees the source value, matching
86
+ # Metadata#edition / #docstatus which read the NOLANG node.
87
+ SINGULAR_LANG_VARIANT_XPATHS = [
88
+ "./edition",
89
+ "./status/stage",
90
+ "./status/substage",
91
+ "./ext/doctype",
92
+ ].freeze
93
+
94
+ def collapse_lang_variants!(root)
95
+ SINGULAR_LANG_VARIANT_XPATHS.each do |xpath|
96
+ collapse_singular_nodes!(root, xpath)
97
+ end
98
+ end
99
+
100
+ def collapse_singular_nodes!(root, xpath)
101
+ nodes = root.xpath(xpath)
102
+ nodes.size > 1 or return
103
+ keep = nodes.find { |n| n["language"].to_s.empty? && n["numberonly"] != "true" } ||
104
+ nodes.find { |n| n["language"].to_s.empty? } ||
105
+ nodes.first
106
+ nodes.each { |n| n.remove unless n.equal?(keep) }
107
+ end
108
+
75
109
  # The Liquid `bibdata` object is built by round-tripping //bibdata through
76
110
  # the Relaton object model, which represents bibliographic-item fields but
77
111
  # not the Metanorma-document extension metadata under bibdata/ext
@@ -10,7 +10,7 @@ module IsoDoc
10
10
  bib["suppress_identifier"] == "true" and return nil
11
11
  ret = bib.xpath(ns("./docidentifier[@scope = 'biblio-tag']"))
12
12
  ret.empty? or return ret.map { |x| to_xml(x.children) }
13
- ret = pref_ref_code_parse(bib) or return nil
13
+ ret = pref_ref_code_markup(bib) || pref_ref_code_parse(bib) or return nil
14
14
  ins = bib.at(ns("./docidentifier[last()]"))
15
15
  ret.reverse_each do |r|
16
16
  id = docid_l10n(r, citation: false)
@@ -19,6 +19,18 @@ module IsoDoc
19
19
  ret
20
20
  end
21
21
 
22
+ # Markup-preserving identifier: when the source docidentifier itself
23
+ # carries inline structure (semx, sup, sub...), keep that markup for
24
+ # the biblio-tag instead of the renderer's flat text, which would
25
+ # drop presentation structure like the sup in "ISO 712<sup>1</sup>".
26
+ def pref_ref_code_markup(bib)
27
+ ids = bib.xpath(ns("./docidentifier[not(@scope)]"))
28
+ .reject { |x| %w(metanorma metanorma-ordinal).include?(x["type"]) }
29
+ .select { |x| x.elements.any? }
30
+ ids.empty? and return nil
31
+ [to_xml(ids.first.children)]
32
+ end
33
+
22
34
  def pref_ref_code_parse(bib)
23
35
  data, = @bibrender.parse(bib)
24
36
  ret = data[:authoritative_identifier] or return nil
@@ -16,18 +16,78 @@ module IsoDoc
16
16
  end
17
17
 
18
18
  def std_docid_semantic_parse(id)
19
- parsed = Pubid::Registry.parse(id)
19
+ result = pubid_parse(id)
20
20
  # Pubid::Ieee in particular happily parses any "letters+digits" token
21
21
  # as an IEEE Std identifier and synthesises an "IEEE Std" prefix that
22
22
  # wasn't in the input (e.g. "REF4" -> "IEEE Std REF4"). Refuse to
23
23
  # annotate when the parser has invented structure not present in the
24
24
  # input -- detected by a non-identity round-trip.
25
- return id if parsed.to_s != id
26
- parsed.to_s(annotated: true)
27
- rescue Pubid::Core::Errors::ParseError
25
+ return std_docid_semantic_full(id) if result.nil?
26
+
27
+ result
28
+ rescue StandardError
28
29
  std_docid_semantic_full(id)
29
30
  end
30
31
 
32
+ # pubid 2 has no universal Pubid::Registry.parse: try each registered
33
+ # flavor. Outcomes:
34
+ # 1. A flavor round-trips the input AND renders annotation markup —
35
+ # return it (some flavors' renderers ignore annotated: and
36
+ # return plain text; those need the regex fallback below).
37
+ # 2. A flavor parses but synthesizes structure the input lacks
38
+ # (e.g. "REF4" -> "IEEE Std REF4") — return the input unchanged
39
+ # (1.x's round-trip guard).
40
+ # 3. Nothing parses, a round-tripper renders plain, or the parse
41
+ # only canonicalizes FORM (e.g. em-dash year -> "--") — nil, so
42
+ # the caller uses the regex fallback, which preserves the
43
+ # original string (1.x's ParseError path).
44
+ def pubid_parse(string)
45
+ Pubid.eager_load_flavors!
46
+ parsed_any = false
47
+ plain_roundtrip = false
48
+ canonicalized = false
49
+ Pubid::Registry.flavors.each_value do |flavor|
50
+ next unless flavor.respond_to?(:parse)
51
+
52
+ begin
53
+ parsed = flavor.parse(string)
54
+ next if publisher_replaced?(string, parsed)
55
+
56
+ parsed_any = true
57
+ if parsed.to_s == string
58
+ annotated = parsed.to_s(annotated: true)
59
+ return annotated if annotated != parsed.to_s
60
+
61
+ plain_roundtrip = true
62
+ elsif dash_normalize(parsed.to_s) == dash_normalize(string)
63
+ canonicalized = true
64
+ end
65
+ rescue StandardError
66
+ next
67
+ end
68
+ end
69
+ return string if parsed_any && !plain_roundtrip && !canonicalized
70
+
71
+ nil
72
+ end
73
+
74
+ # Compare strings ignoring dash FORM (em-dash / double dash / single
75
+ # dash), so a parse that only canonicalizes punctuation is not
76
+ # mistaken for structure synthesis.
77
+ def dash_normalize(string)
78
+ string.tr("—", "-").gsub("--", "-")
79
+ end
80
+
81
+ # A parser that replaces the input's own leading publisher token
82
+ # with its own (ASHRAE parsing "IETF 6281" as "ASHRAE Standard
83
+ # 6281") has not parsed the identifier at all: keep trying, so the
84
+ # identifier reaches the regex fallback rather than the
85
+ # plain-string refusal
86
+ def publisher_replaced?(string, parsed)
87
+ own = string[/\A[A-Za-z][A-Za-z.-]*(?=\s)/] or return false
88
+ parsed.to_s[/\A[A-Za-z][A-Za-z.-]*(?=\s)/] != own
89
+ end
90
+
31
91
  # Regex-based fallback when Pubid::Registry cannot parse the id. Emits
32
92
  # the same generic class names that Pubid uses (publisher, docnumber,
33
93
  # part, year) so that flavors' `std_docid_span_classes` remaps work
@@ -14,6 +14,11 @@ module IsoDoc
14
14
  bib["suppress_identifier"] == true and
15
15
  bib.xpath(ns("./docidentifier")).each(&:remove)
16
16
  bib["type"] ||= "standard"
17
+ # The renderer takes flat identifier text; inline markup (semx, sup)
18
+ # in the source docidentifier yields an empty composition.
19
+ bib.xpath("./docidentifier").each do |d|
20
+ d.elements.any? and d.children = d.text
21
+ end
17
22
  end
18
23
 
19
24
  def bibitem(bib, renderings)
@@ -50,7 +55,8 @@ module IsoDoc
50
55
  def bibrender_formattedref(formattedref, bib); end
51
56
 
52
57
  def bibrender_relaton(bib, renderings)
53
- f = renderings[bib["id"]][:formattedref] or return
58
+ r = renderings[bib["id"]] or return
59
+ f = r[:formattedref] or return
54
60
  f &&= "<formattedref>#{f}</formattedref>"
55
61
  if x = bib.at(ns("./formattedref"))
56
62
  x.replace(f)
@@ -1,25 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "canon"
4
+ require "nokogiri"
4
5
 
5
- # Guard against leading "<?xml ?>" processing instructions in HTML
6
- # input strings passed to Canon's be_html_equivalent_to /
7
- # be_html4_equivalent_to / be_html5_equivalent_to matchers.
6
+ # Guards Canon's serialization matchers against input shapes that
7
+ # Canon's XML pretty-printer (preprocessing: :format in the metanorma
8
+ # profile) and the moxml/leptris backend cannot digest.
8
9
  #
9
- # WHY: Nokogiri::HTML5.fragment parses a leading "<?xml ?>" as a bogus
10
- # Nokogiri::XML::Comment at index 0 of the fragment. In Canon's
11
- # verbose mode (used by the matchers) comments are intentionally not
12
- # filtered, so the bogus comment shifts fragment child indices and
13
- # causes Canon's diff report to blame elements at the tail of the
14
- # document for an offence at the head — an "uninterpretable report"
15
- # remote from the actual cause. Canon's maintainer has chosen not to
16
- # normalise this at the input boundary (closed PR lutaml/canon#124);
17
- # this guard is the local defence.
10
+ # 1. Leading "<?xml ?>" processing instructions in HTML input strings
11
+ # passed to Canon's be_html_equivalent_to / be_html4_equivalent_to /
12
+ # be_html5_equivalent_to matchers.
13
+ #
14
+ # WHY: Nokogiri::HTML5.fragment parses a leading "<?xml ?>" as a
15
+ # bogus Nokogiri::XML::Comment at index 0 of the fragment. In
16
+ # Canon's verbose mode (used by the matchers) comments are
17
+ # intentionally not filtered, so the bogus comment shifts fragment
18
+ # child indices and causes Canon's diff report to blame elements at
19
+ # the tail of the document for an offence at the head — an
20
+ # "uninterpretable report" remote from the actual cause. Canon's
21
+ # maintainer has chosen not to normalise this at the input boundary
22
+ # (closed PR lutaml/canon#124); this guard is the local defence.
23
+ #
24
+ # 2. XML fragments (zero or many top-level nodes) and recoverable
25
+ # malformations (unclosed tags) passed to be_xml_equivalent_to.
26
+ # IsoDoc specs routinely compare such input — e.g. "<span>…</span>
27
+ # <semx>…</semx>" anchor labels, a Word <body> extract with the
28
+ # closing tag stripped, or an empty string for "nothing left after
29
+ # stripping Word chrome". Canon.format / PrettyPrinter::Xml#moxml_format
30
+ # runs Moxml::Context#parse in strict mode, which raises
31
+ # Moxml::ParseError ("malformed input" at the second root, "empty
32
+ # input", or an unclosed tag) instead of comparing the fragments.
33
+ # Wrapping both sides in a synthetic root makes them
34
+ # single-document inputs; when the wrapped form is still not
35
+ # well-formed, a Nokogiri recover-mode round-trip closes the
36
+ # unclosed tags. The transformation is identical on both sides, so
37
+ # equivalence of the original fragments is preserved.
18
38
  #
19
39
  # WHEN: required for the side effect. Requiring this file installs a
20
- # Module#prepend on Canon::RSpecMatchers::SerializationMatcher's
21
- # matches? method that raises IsoDoc::SpecHelpers::CanonGuardError if
22
- # either side of an HTML-format comparison begins with an XML PI.
40
+ # Module#prepend on Canon::RSpecMatchers::SerializationMatcher that
41
+ # raises IsoDoc::SpecHelpers::CanonGuardError on the HTML PI condition
42
+ # and wraps XML-fragment inputs before comparison.
23
43
  #
24
44
  # REUSE: downstream gems that build on isodoc's spec idioms (metanorma
25
45
  # family, mn2pdf, etc.) can opt in by adding a single line to their
@@ -34,10 +54,16 @@ module IsoDoc
34
54
  class CanonGuardError < StandardError; end
35
55
 
36
56
  # Prepended onto Canon::RSpecMatchers::SerializationMatcher to
37
- # intercept matches? before Canon's own implementation runs.
57
+ # intercept matches? / compute_equivalent before Canon's own
58
+ # implementation runs.
38
59
  module CanonHtmlInputGuard
39
60
  ILLEGAL_HTML_PREFIX = /\A\s*<\?xml\b/i.freeze
40
61
 
62
+ # Synthetic root used to give multi-node / empty XML fragments a
63
+ # single document element. Deliberately ugly so it cannot be
64
+ # confused with real IsoDoc vocabulary.
65
+ XML_FRAG_ROOT = "canon-xml-frag-root"
66
+
41
67
  def matches?(target)
42
68
  if html_format? &&
43
69
  (illegal_prefix?(@expected) || illegal_prefix?(target))
@@ -47,8 +73,74 @@ module IsoDoc
47
73
  super
48
74
  end
49
75
 
76
+ # This prepend's frames sit between the spec and canon's matcher;
77
+ # canon's rebaseliner resolves its rewrite target from the first
78
+ # non-rspec/non-canon frame, which would land here. Drop these
79
+ # frames so resolution reaches the actual spec call site.
80
+ def attempt_rebaseline
81
+ if Canon::Rebaseliner.enabled? && @rebaseliner_caller
82
+ @rebaseliner_caller = @rebaseliner_caller.drop_while do |loc|
83
+ (loc.absolute_path || loc.path).to_s.end_with?(
84
+ "canon_html_input_guard.rb",
85
+ )
86
+ end
87
+ end
88
+ super
89
+ end
90
+
50
91
  private
51
92
 
93
+ # Canon's Comparison already understands DocumentFragment nodes,
94
+ # but NodeParser parses strings as full documents — a multi-root
95
+ # fragment or an empty string then blows up in the :format
96
+ # preprocessing pretty-printer (strict Moxml parse) before the
97
+ # comparator ever runs. Wrap both sides so they parse as one
98
+ # document; the wrapper is shared, so it cannot create false
99
+ # equivalences.
100
+ def compute_equivalent(target)
101
+ if xml_format?
102
+ @expected = wrap_xml_fragment(@expected)
103
+ target = wrap_xml_fragment(target)
104
+ end
105
+ super(target)
106
+ end
107
+
108
+ def xml_format?
109
+ @format == :xml
110
+ end
111
+
112
+ def wrap_xml_fragment(value)
113
+ return value unless value.is_a?(String)
114
+ return value if already_wrapped?(value)
115
+
116
+ wrapped = "<#{XML_FRAG_ROOT}>#{value}</#{XML_FRAG_ROOT}>"
117
+ strict_parse_ok?(wrapped) ? wrapped : recover_wrap(wrapped)
118
+ end
119
+
120
+ # Nokogiri's recover-mode parse closes unclosed tags (Word body
121
+ # extracts routinely omit "</body>"). Applied only when the
122
+ # wrapped input is not already well-formed, because the recover
123
+ # round-trip also rewrites entities and can add xml:lang — both
124
+ # harmless when the input is already broken, both destructive
125
+ # when it is not.
126
+ def recover_wrap(wrapped)
127
+ Nokogiri::XML(wrapped).to_xml
128
+ rescue StandardError
129
+ wrapped
130
+ end
131
+
132
+ def strict_parse_ok?(xml)
133
+ Canon::XmlParsing.moxml_context.parse(xml, readonly: true, strict: true)
134
+ true
135
+ rescue StandardError
136
+ false
137
+ end
138
+
139
+ def already_wrapped?(value)
140
+ value.include?("<#{XML_FRAG_ROOT}") ||
141
+ value.match?(/\A\s*<\?xml\b[^>]*>\s*<#{XML_FRAG_ROOT}/)
142
+ end
143
+
52
144
  def html_format?
53
145
  %i[html html4 html5].include?(@format)
54
146
  end
@@ -1,3 +1,3 @@
1
1
  module IsoDoc
2
- VERSION = "3.7.2".freeze
2
+ VERSION = "3.7.4.pre.alpha.1".freeze
3
3
  end
@@ -105,6 +105,7 @@ module IsoDoc
105
105
  end
106
106
 
107
107
  def sequential_permission_names(clause, container: true)
108
+ @reqt_models or (warn "reqt_models not set, skipping permission numbering" and return)
108
109
  clause.xpath(ns(first_lvl_req)).noblank
109
110
  .each_with_object(ReqCounter.new) do |t, c|
110
111
  m = @reqt_models.model(t["model"])
@@ -117,6 +118,7 @@ module IsoDoc
117
118
  end
118
119
 
119
120
  def sequential_permission_children(elem, lbl, klass, container: false)
121
+ @reqt_models or (warn "reqt_models not set, skipping permission children numbering" and return)
120
122
  elem.xpath(ns(req_children)).noblank
121
123
  .each_with_object(ReqCounter.new) do |t, c|
122
124
  m = @reqt_models.model(t["model"])
@@ -229,6 +231,7 @@ container: false)
229
231
  end
230
232
 
231
233
  def hierarchical_permission_names(clauses, num)
234
+ @reqt_models or (warn "reqt_models not set, skipping hierarchical permission numbering" and return)
232
235
  nodeSet(clauses).each_with_object(ReqCounter.new) do |clause, c|
233
236
  clause.xpath(ns(first_lvl_req)).noblank.each do |t|
234
237
  m = @reqt_models.model(t["model"])
@@ -5,15 +5,11 @@ module Relaton
5
5
  module Render
6
6
  module IsoDoc
7
7
  class General < ::Relaton::Render::General
8
- Hash.include Metanorma::Utils::Hash
8
+ STYLE_PATH = File.join(__dir__, "styles/isodoc.yml")
9
9
 
10
- def config_loc
11
- YAML.load_file(File.join(File.dirname(__FILE__),
12
- "config.yml"))
13
- end
14
-
15
- def read_config
16
- super.deep_merge(config_loc)
10
+ def initialize(options = {})
11
+ options[:style] ||= STYLE_PATH
12
+ super
17
13
  end
18
14
  end
19
15
  end
@@ -0,0 +1,23 @@
1
+ # The IsoDoc presentation citation style: the author-date reference
2
+ # composition with (a) markup delimiters instead of plain-text
3
+ # emphasis — IsoDoc references embed into PresentationXML, where
4
+ # emphasis is an element (<em>), not the plain-text convention — and
5
+ # (b) no authoritative identifier in the reference composition: the
6
+ # identifier renders as the biblio-tag ahead of the formattedref
7
+ # ("ISO 712, _Cereals_ ..."), never duplicated inside it. Patents
8
+ # keep theirs: the patent number is the citation itself.
9
+ name: "IsoDoc (ISO 690, presentation markup)"
10
+ extends: author-date
11
+ templates:
12
+ titleOpen: "<em>"
13
+ titleClose: "</em>"
14
+ reference: "{{creator}}. {{title}}. {{medium}}. {{edition}}. {{series}}. {{production}}, {{date}}. {{location}}."
15
+ perType:
16
+ - type: monograph
17
+ template: "{{creator}}. {{title}}. {{edition}}. {{series}}. {{production}}, {{date}}. {{location}}."
18
+ - type: report
19
+ template: "{{creator}}. {{title}}. {{numeration}}. {{edition}}. {{series}}. {{production}}, {{date}}. {{location}}."
20
+ - type: continuing
21
+ template: "{{creator}}. {{title}}. {{componentPart}}. {{medium}}. {{edition}}. {{production}}, {{date}}. {{numeration}}. {{location}}."
22
+ - type: online
23
+ template: "{{creator}}. {{title}}. {{date}}. {{location}}."