metanorma-standoc 2.0.2 → 2.0.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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/metanorma/standoc/base.rb +1 -1
  3. data/lib/metanorma/standoc/basicdoc.rng +5 -3
  4. data/lib/metanorma/standoc/biblio.rng +5 -3
  5. data/lib/metanorma/standoc/cleanup_image.rb +117 -3
  6. data/lib/metanorma/standoc/cleanup_ref.rb +1 -1
  7. data/lib/metanorma/standoc/cleanup_section.rb +1 -1
  8. data/lib/metanorma/standoc/cleanup_xref.rb +12 -5
  9. data/lib/metanorma/standoc/isodoc.rng +10 -0
  10. data/lib/metanorma/standoc/ref.rb +6 -1
  11. data/lib/metanorma/standoc/ref_utility.rb +2 -1
  12. data/lib/metanorma/standoc/term_lookup_cleanup.rb +8 -6
  13. data/lib/metanorma/standoc/terms.rb +10 -7
  14. data/lib/metanorma/standoc/validate.rb +7 -2
  15. data/lib/metanorma/standoc/version.rb +1 -1
  16. data/spec/metanorma/cleanup_blocks_spec.rb +136 -0
  17. data/spec/metanorma/cleanup_spec.rb +3 -3
  18. data/spec/metanorma/isobib_cache_spec.rb +2 -2
  19. data/spec/metanorma/macros_spec.rb +61 -0
  20. data/spec/metanorma/refs_spec.rb +505 -460
  21. data/spec/metanorma/section_spec.rb +1 -1
  22. data/spec/vcr_cassettes/dated_iso_ref_joint_iso_iec.yml +46 -46
  23. data/spec/vcr_cassettes/dated_iso_ref_joint_iso_iec1.yml +12 -12
  24. data/spec/vcr_cassettes/hide_refs.yml +98 -98
  25. data/spec/vcr_cassettes/isobib_get_123.yml +12 -12
  26. data/spec/vcr_cassettes/isobib_get_123_1.yml +25 -25
  27. data/spec/vcr_cassettes/isobib_get_123_1_fr.yml +35 -35
  28. data/spec/vcr_cassettes/isobib_get_123_2001.yml +12 -12
  29. data/spec/vcr_cassettes/isobib_get_124.yml +11 -11
  30. data/spec/vcr_cassettes/rfcbib_get_rfc8341.yml +14 -14
  31. data/spec/vcr_cassettes/separates_iev_citations_by_top_level_clause.yml +61 -51
  32. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9031c28646f9cd9a481ad67911f2f0f7e5bd058368f80888a788753e5f63fa3
4
- data.tar.gz: 25b16459f351f266f19b2f00d70b5bf7f6f5c95325a0d61d43fbe5f3fa518802
3
+ metadata.gz: cc4b55c7be36492191bd86bc19646a520c76e94bd2e2af9f96cf0d80d25fb05e
4
+ data.tar.gz: 533fc5380552ab239528a559ee41a47c246203e17cb33df64cb5cdef5df21f34
5
5
  SHA512:
6
- metadata.gz: 885e62405f3198482ffd7e152b25bef8ab09fc227a8107984297e8fa15b64552b2e745f4ce5e1d60dd37df6dc5cf5c505fb9acfeedd1bdc4ef77954e01218893
7
- data.tar.gz: 69cac251a0696b46dd25f37fb23828780e1945e57464e829c91ab5d88ac481311d130c4ff0e670ee89d56fd998eff4ea48e3c5a0c4b24c514021431fb785d852
6
+ metadata.gz: adc2d0b10dd30e5f59060b20574701dc6049b25a9d25eff06557ac51ca66645f62b553517690380009460f01fe135d26e83ab653b81c7746209e8f55221c3703
7
+ data.tar.gz: 8bc78cf1ab42e80d93c8a22f0028e44e7ba735914fa43c2dcdd5eb53cb57b3a3ee52ea1677cae5033dbc738f8b444bc0dc2e5021ff08a443d991535c84c373b2
@@ -71,7 +71,7 @@ module Metanorma
71
71
 
72
72
  def document(node)
73
73
  init(node)
74
- ret = makexml(node).to_xml(encoding: "US-ASCII", indent: 2)
74
+ ret = makexml(node).to_xml(encoding: "UTF-8", indent: 2)
75
75
  outputs(node, ret) unless node.attr("nodoc") || !node.attr("docfile")
76
76
  clean_exit
77
77
  ret
@@ -173,9 +173,11 @@
173
173
  <data type="dateTime"/>
174
174
  </attribute>
175
175
  </optional>
176
- <attribute name="from">
177
- <data type="IDREF"/>
178
- </attribute>
176
+ <optional>
177
+ <attribute name="from">
178
+ <data type="IDREF"/>
179
+ </attribute>
180
+ </optional>
179
181
  <optional>
180
182
  <attribute name="to">
181
183
  <data type="IDREF"/>
@@ -209,9 +209,6 @@
209
209
  <zeroOrMore>
210
210
  <ref name="contact"/>
211
211
  </zeroOrMore>
212
- <zeroOrMore>
213
- <ref name="uri"/>
214
- </zeroOrMore>
215
212
  </element>
216
213
  </define>
217
214
  <define name="fullname">
@@ -828,6 +825,11 @@
828
825
  <optional>
829
826
  <attribute name="scope"/>
830
827
  </optional>
828
+ <optional>
829
+ <attribute name="primary">
830
+ <data type="boolean"/>
831
+ </attribute>
832
+ </optional>
831
833
  <text/>
832
834
  </element>
833
835
  </define>
@@ -2,6 +2,7 @@ module Metanorma
2
2
  module Standoc
3
3
  module Cleanup
4
4
  def svgmap_cleanup(xmldoc)
5
+ svg_uniqueids(xmldoc)
5
6
  svgmap_moveattrs(xmldoc)
6
7
  svgmap_populate(xmldoc)
7
8
  Metanorma::Utils::svgmap_rewrite(xmldoc, @localdir)
@@ -59,12 +60,125 @@ module Metanorma
59
60
  end
60
61
 
61
62
  def img_cleanup(xmldoc)
62
- return xmldoc unless @datauriimage
63
+ if @datauriimage
64
+ xmldoc.xpath("//image").each do |i|
65
+ # do not datauri encode SVG, we need to deduplicate its IDs
66
+ unless read_in_if_svg(i, @localdir)
67
+ i["src"] = Metanorma::Utils::datauri(i["src"], @localdir)
68
+ end
69
+ end
70
+ end
71
+ svg_uniqueids(xmldoc)
72
+ xmldoc
73
+ end
74
+
75
+ def read_in_if_svg(img, localdir)
76
+ return false unless img["src"]
77
+
78
+ path = Metanorma::Utils::svgmap_rewrite0_path(img["src"], localdir)
79
+ File.file?(path) or return false
80
+ types = MIME::Types.type_for(path) or return false
81
+ types.first == "image/svg+xml" or return false
82
+ svg = File.read(path, encoding: "utf-8") or return false
83
+ img.replace(Nokogiri::XML(svg).root.to_xml)
84
+ true
85
+ end
86
+
87
+ IRI_TAG_PROPERTIES_MAP = {
88
+ clipPath: ["clip-path"],
89
+ "color-profile": nil,
90
+ cursor: nil,
91
+ filter: nil,
92
+ linearGradient: ["fill", "stroke"],
93
+ marker: ["marker", "marker-end", "marker-mid", "marker-start"],
94
+ mask: nil,
95
+ pattern: ["fill", "stroke"],
96
+ radialGradient: ["fill", "stroke"],
97
+ }.freeze
98
+
99
+ SVG_NS = "http://www.w3.org/2000/svg".freeze
100
+
101
+ def svg_uniqueids(xmldoc)
102
+ # only keep non-unique identifiers
103
+ ids = xmldoc.xpath("//m:svg//*/@id | //svg/@id", "m" => SVG_NS)
104
+ .map(&:text).group_by(&:itself).transform_values(&:count)
105
+ .delete_if { |_, v| v < 2 }
106
+ xmldoc.xpath("//m:svg", "m" => SVG_NS).each_with_index do |s, i|
107
+ ids = svg_uniqueids1(s, i, ids)
108
+ end
109
+ end
110
+
111
+ def svg_iri_properties(id_elems)
112
+ iri_tag_names = id_elems.each_with_object([]) do |e, m|
113
+ IRI_TAG_PROPERTIES_MAP.key?(e.name.to_sym) and m = m << e.name
114
+ end.uniq
115
+ iri_properties = iri_tag_names.each_with_object([]) do |t, m|
116
+ (IRI_TAG_PROPERTIES_MAP[t.to_sym] || [t]).each { |t1| m = m << t1 }
117
+ end.uniq
118
+ return [] if iri_properties.empty?
119
+
120
+ iri_properties << "style"
121
+ end
63
122
 
64
- xmldoc.xpath("//image").each do |i|
65
- i["src"] = Metanorma::Utils::datauri(i["src"], @localdir)
123
+ def svg_uniqueids1(svg, idx, ids)
124
+ id_elems = svg.xpath(".//*[@id] | ./@id/..")
125
+ iri_properties = svg_iri_properties(id_elems)
126
+ svg_uniqueids2(svg, iri_properties, idx, ids)
127
+ new_ids = id_elems.map { |x| x["id"] }
128
+ .map { |x| x + (ids[x] ? "_inject_#{idx}" : "") }
129
+ ids.merge(new_ids.each.map { |value| [value, true] }.to_h)
130
+ end
131
+
132
+ def svg_uniqueids2(svg, iri_properties, idx, ids)
133
+ svg.traverse do |e|
134
+ next unless e.element?
135
+
136
+ if e.name == "style"
137
+ svg_styleupdate(e, idx, ids)
138
+ elsif !e.attributes.empty?
139
+ svg_attrupdate(e, iri_properties, idx, ids)
140
+ end
141
+ svg_linkupdate(e, idx, ids)
142
+ svg_idupdate(e, idx, ids)
66
143
  end
67
144
  end
145
+
146
+ def svg_update_url(text, idx, ids)
147
+ text.gsub(/url\("?#([a-zA-Z][\w:.-]*)"?\)/) do |x|
148
+ if ids[$1] then "url(##{$1}_inject_#{idx})"
149
+ else x
150
+ end
151
+ end
152
+ end
153
+
154
+ def svg_styleupdate(elem, idx, ids)
155
+ elem.children = svg_update_url(elem.text, idx, ids)
156
+ end
157
+
158
+ def svg_attrupdate(elem, iri_properties, idx, ids)
159
+ iri_properties.each do |p|
160
+ next unless elem[p]
161
+
162
+ elem[p] = svg_update_url(elem[p], idx, ids)
163
+ end
164
+ end
165
+
166
+ def svg_linkupdate(elem, idx, ids)
167
+ %w(xlink:href href).each do |ref|
168
+ iri = elem[ref]&.strip
169
+ next unless /^#/.match?(iri)
170
+ next unless ids[iri.sub(/^#/, "")]
171
+
172
+ elem[ref] += "_inject_#{idx}"
173
+ end
174
+ end
175
+
176
+ def svg_idupdate(elem, idx, ids)
177
+ return unless elem["id"]
178
+ return unless ids[elem["id"]]
179
+
180
+ elem["id"] += "_inject_#{idx}"
181
+ end
68
182
  end
69
183
  end
70
184
  end
@@ -117,7 +117,7 @@ module Metanorma
117
117
  # isopub = ref.at(ISO_PUBLISHER_XPATH)
118
118
  docid = ref.at("./docidentifier[@type = 'metanorma']") ||
119
119
  ref.at("./docidentifier[not(@type = 'DOI')]") or next
120
- reference = format_ref(docid.text, docid["type"])
120
+ reference = format_ref(docid.children.to_xml, docid["type"])
121
121
  @anchors[ref["id"]] = { xref: reference }
122
122
  end
123
123
  end
@@ -193,7 +193,7 @@ module Metanorma
193
193
  next unless t.next_element.nil?
194
194
  next if %w(sections annex preface).include? t.parent.name
195
195
 
196
- t.parent.parent << t
196
+ t.parent.next = t
197
197
  found = true
198
198
  end
199
199
  break unless found
@@ -47,16 +47,23 @@ module Metanorma
47
47
 
48
48
  def xref_to_eref(elem)
49
49
  elem["bibitemid"] = elem["target"]
50
- unless elem["citeas"] = @anchors&.dig(elem["target"], :xref)
51
- @internal_eref_namespaces.include?(elem["type"]) or
52
- @log.add("Crossreferences", elem,
53
- "#{elem['target']} does not have a corresponding "\
54
- "anchor ID in the bibliography!")
50
+ if ref = @anchors&.dig(elem["target"], :xref)
51
+ elem["citeas"] = HTMLEntities.new.encode(ref, :hexadecimal)
52
+ else
53
+ elem["citeas"] = ""
54
+ xref_to_eref1(elem)
55
55
  end
56
56
  elem.delete("target")
57
57
  extract_localities(elem) unless elem.children.empty?
58
58
  end
59
59
 
60
+ def xref_to_eref1(elem)
61
+ @internal_eref_namespaces.include?(elem["type"]) or
62
+ @log.add("Crossreferences", elem,
63
+ "#{elem['target']} does not have a corresponding "\
64
+ "anchor ID in the bibliography!")
65
+ end
66
+
60
67
  def xref_cleanup(xmldoc)
61
68
  xmldoc.xpath("//xref").each do |x|
62
69
  /:/.match(x["target"]) and xref_to_internal_eref(x)
@@ -1098,6 +1098,16 @@
1098
1098
  </define>
1099
1099
  </include>
1100
1100
  <!-- end overrides -->
1101
+ <define name="image" combine="choice">
1102
+ <element name="svg">
1103
+ <oneOrMore>
1104
+ <choice>
1105
+ <text/>
1106
+ <ref name="AnyElement"/>
1107
+ </choice>
1108
+ </oneOrMore>
1109
+ </element>
1110
+ </define>
1101
1111
  <define name="MultilingualRenderingType">
1102
1112
  <choice>
1103
1113
  <value>common</value>
@@ -131,6 +131,7 @@ module Metanorma
131
131
  # TODO: alternative where only title is available
132
132
  def refitemcode(item, node)
133
133
  m = NON_ISO_REF.match(item) and return refitem1code(item, m).compact
134
+ m = NON_ISO_REF1.match(item) and return refitem1code(item, m).compact
134
135
  @log.add("AsciiDoc Input", node, "#{MALFORMED_REF}: #{item}")
135
136
  {}
136
137
  end
@@ -180,7 +181,11 @@ module Metanorma
180
181
  (<fn[^>]*>\s*<p>(?<fn>[^\]]+)</p>\s*</fn>,?\s?)?(?<text>.*)$}xm.freeze
181
182
 
182
183
  NON_ISO_REF = %r{^<ref\sid="(?<anchor>[^"]+)">
183
- \[(?<usrlbl>\([^)]+\))?(?<code>[^\]]+?)\]</ref>,?\s*(?<text>.*)$}xm
184
+ \[(?<usrlbl>\([^)]+\))?(?<code>.+?)\]</ref>,?\s*(?<text>.*)$}xm
185
+ .freeze
186
+
187
+ NON_ISO_REF1 = %r{^<ref\sid="(?<anchor>[^"]+)">
188
+ (?<usrlbl>\([^)]+\))?(?<code>.+?)</ref>,?\s*(?<text>.*)$}xm
184
189
  .freeze
185
190
 
186
191
  def reference1_matches(item)
@@ -75,7 +75,8 @@ module Metanorma
75
75
 
76
76
  def analyse_ref_repo_path(ret)
77
77
  return ret unless m =
78
- /^(?<type>repo|path):\((?<key>[^,]+),?(?<id>.*)\)$/.match(ret[:id])
78
+ /^(?<type>repo|path):\((?<key>[^,]+),?(?<id>.*)\)$/
79
+ .match(ret[:id])
79
80
 
80
81
  id = m[:id].empty? ? m[:key].sub(%r{^[^/]+/}, "") : m[:id]
81
82
  ret.merge(id: id, type: m[:type], key: m[:key], nofetch: true)
@@ -58,7 +58,7 @@ module Metanorma
58
58
 
59
59
  def set_termxref_tags_target
60
60
  xmldoc.xpath("//termxref").each do |node|
61
- target = normalize_ref_id(node.text)
61
+ target = normalize_ref_id(node)
62
62
  if termlookup[:term][target].nil? && termlookup[:symbol][target].nil?
63
63
  remove_missing_ref(node, target)
64
64
  next
@@ -78,7 +78,7 @@ module Metanorma
78
78
 
79
79
  def remove_missing_ref_term(node, target)
80
80
  log.add("AsciiDoc Input", node,
81
- %(Error: Term reference in `term[#{target}]` missing: \
81
+ %(Error: Term reference to `#{target}` missing: \
82
82
  "#{target}" is not defined in document))
83
83
  node.name = "strong"
84
84
  node&.at("../xrefrender")&.remove
@@ -141,7 +141,7 @@ module Metanorma
141
141
  end
142
142
 
143
143
  def normalize_id_and_memorize_init(node, res_table, text_selector, prefix)
144
- term_text = normalize_ref_id(node.at(text_selector).text)
144
+ term_text = normalize_ref_id(node.at(text_selector))
145
145
  unless AUTOMATIC_GENERATED_ID_REGEXP.match(node["id"]).nil? &&
146
146
  !node["id"].nil?
147
147
  id = unique_text_id(term_text, prefix)
@@ -155,12 +155,14 @@ module Metanorma
155
155
  node.xpath(text_selector).each_with_index do |p, i|
156
156
  next unless i.positive?
157
157
 
158
- res_table[normalize_ref_id(p.text)] = node["id"]
158
+ res_table[normalize_ref_id(p)] = node["id"]
159
159
  end
160
160
  end
161
161
 
162
- def normalize_ref_id(text)
163
- Metanorma::Utils::to_ncname(text.downcase.gsub(/[[:space:]]/, "-"))
162
+ def normalize_ref_id(term)
163
+ t = term.dup
164
+ t.xpath(".//index").map(&:remove)
165
+ Metanorma::Utils::to_ncname(t.text.downcase.gsub(/[[:space:]]/, "-"))
164
166
  end
165
167
 
166
168
  def unique_text_id(text, prefix)
@@ -92,7 +92,7 @@ module Metanorma
92
92
  end
93
93
  end
94
94
 
95
- def term_source_attrs(_node, seen_xref)
95
+ def termsource_origin_attrs(_node, seen_xref)
96
96
  { case: seen_xref.children[0]["case"],
97
97
  droploc: seen_xref.children[0]["droploc"],
98
98
  bibitemid: seen_xref.children[0]["target"],
@@ -103,7 +103,7 @@ module Metanorma
103
103
  if seen_xref.children[0].name == "concept"
104
104
  xml_t.origin { |o| o << seen_xref.children[0].to_xml }
105
105
  else
106
- attrs = term_source_attrs(node, seen_xref)
106
+ attrs = termsource_origin_attrs(node, seen_xref)
107
107
  attrs.delete(:text)
108
108
  xml_t.origin **attr_code(attrs) do |o|
109
109
  o << seen_xref.children[0].children.to_xml
@@ -135,13 +135,16 @@ module Metanorma
135
135
  matched
136
136
  end
137
137
 
138
+ def termsource_attrs(node, matched)
139
+ status = node.attr("status") ||
140
+ (matched[:text] ? "modified" : "identical")
141
+ { status: status, type: node.attr("type") || "authoritative" }
142
+ end
143
+
138
144
  def termsource(node)
139
- matched = extract_termsource_refs(node.content, node) || return
145
+ matched = extract_termsource_refs(node.content, node) or return
140
146
  noko do |xml|
141
- status = node.attr("status") ||
142
- (matched[:text] ? "modified" : "identical")
143
- attrs = { status: status, type: node.attr("type") || "authoritative" }
144
- xml.termsource **attrs do |xml_t|
147
+ xml.termsource **termsource_attrs(node, matched) do |xml_t|
145
148
  seen_xref = Nokogiri::XML.fragment(matched[:xref])
146
149
  add_term_source(node, xml_t, seen_xref, matched)
147
150
  end
@@ -120,20 +120,25 @@ module Metanorma
120
120
  end
121
121
  end
122
122
 
123
+ SVG_NS = "http://www.w3.org/2000/svg".freeze
124
+
123
125
  # RelaxNG cannot cope well with wildcard attributes. So we strip
124
126
  # any attributes from FormattedString instances (which can contain
125
127
  # xs:any markup, and are signalled with @format) before validation.
126
128
  def formattedstr_strip(doc)
127
129
  doc.xpath("//*[@format] | //stem | //bibdata//description | "\
128
130
  "//formattedref | //bibdata//note | //bibdata/abstract | "\
129
- "//bibitem/abstract | //bibitem/note | //misc-container")
130
- .each do |n|
131
+ "//bibitem/abstract | //bibitem/note | //misc-container",
132
+ "m" => SVG_NS).each do |n|
131
133
  n.elements.each do |e|
132
134
  e.traverse do |e1|
133
135
  e1.element? and e1.each { |k, _v| e1.delete(k) }
134
136
  end
135
137
  end
136
138
  end
139
+ doc.xpath("//m:svg", "m" => SVG_NS).each do |n|
140
+ n.replace("<svg/>")
141
+ end
137
142
  doc
138
143
  end
139
144
 
@@ -19,6 +19,6 @@ module Metanorma
19
19
  end
20
20
 
21
21
  module Standoc
22
- VERSION = "2.0.2".freeze
22
+ VERSION = "2.0.3".freeze
23
23
  end
24
24
  end
@@ -1039,4 +1039,140 @@ RSpec.describe Metanorma::Standoc do
1039
1039
  annotation_id = output.at("//xmlns:annotation/@id").text
1040
1040
  expect(callout_id).to eq(annotation_id)
1041
1041
  end
1042
+ it "deduplicates identifiers in inline SVGs" do
1043
+ input = <<~INPUT
1044
+ #{BLANK_HDR}
1045
+ <sections>
1046
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 256 256">
1047
+ <defs>
1048
+ <linearGradient id="gradient1">
1049
+ <stop class="stop1" offset="0%" xlink:href="#gradient1"/>
1050
+ <stop class="stop2" offset="100%"/>
1051
+ <style>url(#gradient1)</style>
1052
+ </linearGradient>
1053
+ </defs>
1054
+ <circle fill="url(#gradient1)" cx="128" cy="128" r="100" />
1055
+ </svg>
1056
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 256 256">
1057
+ <defs>
1058
+ <linearGradient id="gradient2">
1059
+ <stop class="stop1" offset="0%" xlink:href="#gradient2"/>
1060
+ <stop class="stop2" offset="100%"/>
1061
+ <style>url(#gradient2)</style>
1062
+ </linearGradient>
1063
+ </defs>
1064
+ <circle fill="url(#gradient2)" cx="128" cy="128" r="100" />
1065
+ </svg>
1066
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 256 256">
1067
+ <defs>
1068
+ <linearGradient id="gradient1">
1069
+ <stop class="stop1" offset="0%" xlink:href="#gradient1"/>
1070
+ <stop class="stop2" offset="100%"/>
1071
+ <style>url(#gradient1)</style>
1072
+ </linearGradient>
1073
+ </defs>
1074
+ <circle fill="url(#gradient1)" cx="128" cy="128" r="100" />
1075
+ </svg>
1076
+ </sections>
1077
+ </standard-document>
1078
+ INPUT
1079
+ output = <<~OUTPUT
1080
+ #{BLANK_HDR}
1081
+ <sections>
1082
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 256 256">
1083
+ <defs>
1084
+ <linearGradient id="gradient1_inject_0">
1085
+ <stop class="stop1" offset="0%" xlink:href="#gradient1_inject_0"/>
1086
+ <stop class="stop2" offset="100%"/>
1087
+ <style>url(#gradient1_inject_0)</style>
1088
+ </linearGradient>
1089
+ </defs>
1090
+ <circle fill="url(#gradient1_inject_0)" cx="128" cy="128" r="100"/>
1091
+ </svg>
1092
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 256 256">
1093
+ <defs>
1094
+ <linearGradient id="gradient2">
1095
+ <stop class="stop1" offset="0%" xlink:href="#gradient2"/>
1096
+ <stop class="stop2" offset="100%"/>
1097
+ <style>url(#gradient2)</style>
1098
+ </linearGradient>
1099
+ </defs>
1100
+ <circle fill="url(#gradient2)" cx="128" cy="128" r="100"/>
1101
+ </svg>
1102
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 256 256">
1103
+ <defs>
1104
+ <linearGradient id="gradient1_inject_2">
1105
+ <stop class="stop1" offset="0%" xlink:href="#gradient1_inject_2"/>
1106
+ <stop class="stop2" offset="100%"/>
1107
+ <style>url(#gradient1_inject_2)</style>
1108
+ </linearGradient>
1109
+ </defs>
1110
+ <circle fill="url(#gradient1_inject_2)" cx="128" cy="128" r="100"/>
1111
+ </svg>
1112
+ </sections>
1113
+ </standard-document>
1114
+ OUTPUT
1115
+ expect(Metanorma::Standoc::Converter.new(nil, *OPTIONS)
1116
+ .cleanup(Nokogiri::XML(input)).to_xml)
1117
+ .to be_equivalent_to xmlpp(output)
1118
+ end
1119
+
1120
+ it "deduplicates identifiers in embedded SVGs" do
1121
+ input = <<~INPUT
1122
+ #{ASCIIDOC_BLANK_HDR.sub(/:data-uri-image: false/, ':data-uri-image: true')}
1123
+
1124
+ image::spec/fixtures/action_schemaexpg1.svg[]
1125
+
1126
+ image::spec/examples/rice_images/rice_image1.png[]
1127
+
1128
+ image::spec/fixtures/action_schemaexpg1.svg[]
1129
+ INPUT
1130
+
1131
+ output = <<~OUTPUT
1132
+ #{BLANK_HDR}
1133
+ <sections>
1134
+ <figure id='_'>
1135
+ <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' id='Layer_1' x='0px' y='0px' viewBox='0 0 595.28 841.89' style='enable-background:new 0 0 595.28 841.89;' xml:space='preserve'>
1136
+ <style/>
1137
+ <image/>
1138
+ <a xlink:href='mn://action_schema'>
1139
+ <rect x='123.28' y='273.93' class='st0' width='88.05' height='41.84'/>
1140
+ </a>
1141
+ <a xlink:href='mn://basic_attribute_schema'>
1142
+ <rect x='324.69' y='450.52' class='st0' width='132.62' height='40.75'/>
1143
+ </a>
1144
+ <a xlink:href='mn://support_resource_schema'>
1145
+ <rect x='324.69' y='528.36' class='st0' width='148.16' height='40.75'/>
1146
+ </a>
1147
+ </svg>
1148
+ </figure>
1149
+ <figure id='_'>
1150
+ <image/>
1151
+ </figure>
1152
+ <figure id='_'>
1153
+ <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' id='Layer_1_inject_1' x='0px' y='0px' viewBox='0 0 595.28 841.89' style='enable-background:new 0 0 595.28 841.89;' xml:space='preserve'>
1154
+ <style/>
1155
+ <image/>
1156
+ <a xlink:href='mn://action_schema'>
1157
+ <rect x='123.28' y='273.93' class='st0' width='88.05' height='41.84'/>
1158
+ </a>
1159
+ <a xlink:href='mn://basic_attribute_schema'>
1160
+ <rect x='324.69' y='450.52' class='st0' width='132.62' height='40.75'/>
1161
+ </a>
1162
+ <a xlink:href='mn://support_resource_schema'>
1163
+ <rect x='324.69' y='528.36' class='st0' width='148.16' height='40.75'/>
1164
+ </a>
1165
+ </svg>
1166
+ </figure>
1167
+ </sections>
1168
+ </standard-document>
1169
+ OUTPUT
1170
+ xml = Nokogiri::XML(Asciidoctor.convert(input, *OPTIONS))
1171
+ xml.xpath("//*[local-name() = 'image']").each do |x|
1172
+ x.replace("<image/>")
1173
+ end
1174
+ expect(xmlpp(strip_guid(xml.to_xml)
1175
+ .gsub(%r{<style.*?</style>}m, "<style/>")))
1176
+ .to be_equivalent_to xmlpp(output)
1177
+ end
1042
1178
  end
@@ -585,7 +585,7 @@ RSpec.describe Metanorma::Standoc do
585
585
  <title type='main' format='text/plain' language='en' script='Latn'>International Electrotechnical Vocabulary (IEV) — Part 102: Mathematics — General concepts and linear algebra</title>
586
586
  <uri type="src">https://webstore.iec.ch/publication/160</uri>
587
587
  <uri type="obp">/preview/info_iec60050-102%7Bed1.0%7Db.pdf</uri>
588
- <docidentifier type="IEC">IEC 60050-102:2007</docidentifier>
588
+ <docidentifier type="IEC" primary="true">IEC 60050-102:2007</docidentifier>
589
589
  <docidentifier type='URN'>urn:iec:std:iec:60050-102:2007:::en</docidentifier>
590
590
  <date type="published">
591
591
  <on>2007-08-27</on>
@@ -624,7 +624,7 @@ RSpec.describe Metanorma::Standoc do
624
624
  <title type="main" format="text/plain" language="en" script="Latn">International Electrotechnical Vocabulary (IEV) — Part 103: Mathematics — Functions</title>
625
625
  <uri type="src">https://webstore.iec.ch/publication/161</uri>
626
626
  <uri type="obp">/preview/info_iec60050-103%7Bed1.0%7Db.pdf</uri>
627
- <docidentifier type="IEC">IEC 60050-103:2009</docidentifier>
627
+ <docidentifier type="IEC" primary="true">IEC 60050-103:2009</docidentifier>
628
628
  <docidentifier type='URN'>urn:iec:std:iec:60050-103:2009:::en</docidentifier>
629
629
  <date type="published">
630
630
  <on>2009-12-14</on>
@@ -1288,7 +1288,7 @@ RSpec.describe Metanorma::Standoc do
1288
1288
  <sourcecode id='L__xf6_we'>
1289
1289
  <name>
1290
1290
  See
1291
- <eref type='inline' bibitemid='L__xf6_wner2016' citeas='L&#246;wner et al. 2016'/>
1291
+ <eref type='inline' bibitemid='L__xf6_wner2016' citeas='L&amp;#xf6;wner et al. 2016'/>
1292
1292
  </name>
1293
1293
  ABC
1294
1294
  </sourcecode>
@@ -49,7 +49,7 @@ ISO_124_DATED = <<~XML.freeze
49
49
  <uri type="src">https://www.iso.org/standard/61884.html</uri>
50
50
  <uri type="obp">https://www.iso.org/obp/ui/#!iso:std:61884:en</uri>
51
51
  <uri type="rss">https://www.iso.org/contents/data/standard/06/18/61884.detail.rss</uri>
52
- <docidentifier type="ISO">ISO 124:2014</docidentifier>
52
+ <docidentifier type="ISO" primary="true">ISO 124:2014</docidentifier>
53
53
  <docidentifier type='URN'>urn:iso:std:iso:124:stage-90.93:ed-7:en</docidentifier>
54
54
  <docnumber>124</docnumber>
55
55
  <date type="published">
@@ -208,7 +208,7 @@ ISO_123_DATED = <<~XML.freeze
208
208
  <uri type="src">https://www.iso.org/standard/23281.html</uri>
209
209
  <uri type="obp">https://www.iso.org/obp/ui/#!iso:std:23281:en</uri>
210
210
  <uri type="rss">https://www.iso.org/contents/data/standard/02/32/23281.detail.rss</uri>
211
- <docidentifier type="ISO">ISO 123:2001</docidentifier>
211
+ <docidentifier type="ISO" primary="true">ISO 123:2001</docidentifier>
212
212
  <docidentifier type='URN'>urn:iso:std:iso:123:stage-90.93:ed-3:en</docidentifier>
213
213
  <docnumber>123</docnumber>
214
214
  <date type="published">