metanorma-iso 3.1.3 → 3.1.4

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.
@@ -123,7 +123,9 @@ module IsoDoc
123
123
 
124
124
  def authority_copyright_style(auth)
125
125
  auth.xpath(".//p[not(@class)]").each { |p| p["class"] = "zzCopyright" }
126
- auth.xpath(".//p[@class = 'Tablebody']").each { |p| p["class"] = "zzCopyright" }
126
+ auth.xpath(".//p[@class = 'Tablebody']").each do |p|
127
+ p["class"] = "zzCopyright"
128
+ end
127
129
  auth.xpath(".//p[@id = 'boilerplate-message']").each do |p|
128
130
  p["class"] = "zzCopyright1"
129
131
  end
@@ -163,14 +165,27 @@ module IsoDoc
163
165
  toc = ""
164
166
  s = docxml.at("//div[@class = 'TOC']") and toc = to_xml(s.children)
165
167
  xpath = (1..level).each.map { |i| "//h#{i}" }.join (" | ")
168
+ annexid = 0
166
169
  docxml.xpath(xpath).each do |h|
167
170
  x = ""
168
- x = @anchor[h.parent["id"]][:xref] if h["class"] == "ANNEX"
171
+ if %w(ANNEX Annex).include?(h["class"])
172
+ x, annexid = annex_toc(annexid)
173
+ end
169
174
  toc += word_toc_entry(h.name[1].to_i, x + header_strip(h))
170
175
  end
171
176
  toc.sub(/(<p class="MsoToc1">)/,
172
177
  %{\\1#{word_toc_preface(level)}}) + WORD_TOC_SUFFIX1
173
178
  end
179
+
180
+ # do not use in IEC, BSI, where Word does not use list to generate
181
+ # "Annex A"
182
+ def annex_toc(annexid)
183
+ instance_of?(IsoDoc::Iso::WordConvert) ||
184
+ instance_of?(WordDISConvert) or return ""
185
+ annexid += 1
186
+ x = "#{@i18n.annex} #{('@'.ord + annexid).chr} "
187
+ [x, annexid]
188
+ end
174
189
  end
175
190
  end
176
191
  end
@@ -12,14 +12,12 @@ module Metanorma
12
12
  end
13
13
 
14
14
  def metadata_author(node, xml)
15
- org_contributor(node, xml,
16
- { source: ["publisher", "pub"], role: "author",
17
- default: default_publisher })
18
- committee_contributors(node, xml, default_publisher,
19
- { approval: false })
15
+ super
20
16
  secretariat_contributor(node, xml, default_publisher)
21
17
  end
22
18
 
19
+ def personal_author(node, xml); end
20
+
23
21
  def org_organization(node, xml, org)
24
22
  if org[:committee]
25
23
  contrib_committee_build(xml, org[:agency], org)
@@ -1,6 +1,6 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <grammar xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
3
- <!-- VERSION v2.1.1 -->
3
+ <!-- VERSION v2.1.2 -->
4
4
 
5
5
  <!--
6
6
  ALERT: cannot have root comments, because of https://github.com/metanorma/metanorma/issues/437
@@ -123,6 +123,10 @@ the type attribute defaults to `review` for reviews</a:documentation>
123
123
  <a:documentation>Notes whose scope is the current block</a:documentation>
124
124
  </ref>
125
125
  </zeroOrMore>
126
+ <ref name="BlockSource">
127
+ <a:documentation>Bibliographic source for the information in the list.
128
+ Sources are currently only rendered in metanorma-plateau</a:documentation>
129
+ </ref>
126
130
  </define>
127
131
  <define name="OlBody">
128
132
  <optional>
@@ -140,6 +144,10 @@ the type attribute defaults to `review` for reviews</a:documentation>
140
144
  <a:documentation>Notes whose scope is the current block</a:documentation>
141
145
  </ref>
142
146
  </zeroOrMore>
147
+ <ref name="BlockSource">
148
+ <a:documentation>Bibliographic source for the information in the list.
149
+ Sources are currently only rendered in metanorma-plateau</a:documentation>
150
+ </ref>
143
151
  </define>
144
152
  <define name="OlAttributes">
145
153
  <a:documentation>NOTE: `start` attribute is not included by default, because of problems it raises with DOC output</a:documentation>
@@ -188,6 +196,10 @@ the type attribute defaults to `review` for reviews</a:documentation>
188
196
  <a:documentation>Notes whose scope is the current block</a:documentation>
189
197
  </ref>
190
198
  </zeroOrMore>
199
+ <ref name="BlockSource">
200
+ <a:documentation>Bibliographic source for the information in the list.
201
+ Sources are currently only rendered in metanorma-plateau</a:documentation>
202
+ </ref>
191
203
  </define>
192
204
  <define name="dt">
193
205
  <element name="dt">
@@ -871,6 +883,12 @@ titlecase, or lowercase</a:documentation>
871
883
  </oneOrMore>
872
884
  </element>
873
885
  </define>
886
+ <define name="ParagraphFnBody" combine="interleave">
887
+ <ref name="BlockSource">
888
+ <a:documentation>Bibliographic source for the information in the paragraph
889
+ parargaph sources are currently only rendered in metanorma-plateau</a:documentation>
890
+ </ref>
891
+ </define>
874
892
  <define name="BasicBlock" combine="choice">
875
893
  <ref name="columnbreak"/>
876
894
  </define>
@@ -1,10 +1,11 @@
1
1
  require "metanorma-standoc"
2
2
  require_relative "validate_style"
3
+ require_relative "validate_numeric"
3
4
  require_relative "validate_requirements"
4
5
  require_relative "validate_section"
5
6
  require_relative "validate_title"
6
- require_relative "validate_image"
7
7
  require_relative "validate_list"
8
+ require_relative "validate_xref"
8
9
  require "nokogiri"
9
10
  require "jing"
10
11
  require "iev"
@@ -29,127 +30,10 @@ module Metanorma
29
30
  end
30
31
  end
31
32
 
32
- # ISO/IEC DIR 2, 15.5.3, 20.2
33
- # does not deal with preceding text marked up
34
- def see_xrefs_validate(root)
35
- @lang == "en" or return
36
- anchors = extract_anchor_norm(root)
37
- root.xpath("//xref").each do |t|
38
- preceding = t.at("./preceding-sibling::text()[last()]")
39
- !preceding.nil? &&
40
- /\b(see| refer to)\p{Zs}*\Z/mi.match(preceding) or next
41
- anchors[t["target"]] and
42
- @log.add("Style", t,
43
- "'see #{t['target']}' is pointing to a normative section")
44
- end
45
- end
46
-
47
- def extract_anchor_norm(root)
48
- nodes = root.xpath("//annex[@obligation = 'normative'] | " \
49
- "//references[@obligation = 'normative']")
50
- ret = nodes.each_with_object({}) do |n, m|
51
- n["anchor"] and m[n["anchor"]] = true
52
- end
53
- nodes.each do |n|
54
- n.xpath(".//*[@anchor]").each { |n1| ret[n1["anchor"]] = true }
55
- end
56
- ret
57
- end
58
-
59
- # ISO/IEC DIR 2, 15.5.3
60
- def see_erefs_validate(root)
61
- @lang == "en" or return
62
- bibitemids = extract_bibitem_anchors(root)
63
- root.xpath("//eref").each do |t|
64
- prec = t.at("./preceding-sibling::text()[last()]")
65
- !prec.nil? && /\b(see|refer to)\p{Zs}*\Z/mi.match(prec) or next
66
- unless target = bibitemids[t["bibitemid"]]
67
- # unless target = root.at("//bibitem[@anchor = '#{t['bibitemid']}']")
68
- @log.add("Bibliography", t,
69
- "'#{t} is not pointing to a real reference")
70
- next
71
- end
72
- target[:norm] and
73
- @log.add("Style", t,
74
- "'see #{t}' is pointing to a normative reference")
75
- end
76
- end
77
-
78
- def extract_bibitem_anchors(root)
79
- ret = root.xpath("//references[@normative = 'true']//bibitem")
80
- .each_with_object({}) do |b, m|
81
- m[b["anchor"]] = { bib: b, norm: true }
82
- end
83
- root.xpath("//references[not(@normative = 'true')]//bibitem")
84
- .each do |b|
85
- ret[b["anchor"]] = { bib: b, norm: false }
86
- end
87
- ret
88
- end
89
-
90
- # ISO/IEC DIR 2, 10.4
91
- def locality_erefs_validate(root)
92
- root.xpath("//eref[descendant::locality]").each do |t|
93
- if /^(ISO|IEC)/.match?(t["citeas"]) &&
94
- !/: ?(\d+{4}|–)$/.match?(t["citeas"])
95
- @log.add("Style", t,
96
- "undated reference #{t['citeas']} should not contain " \
97
- "specific elements")
98
- end
99
- end
100
- end
101
-
102
33
  def termdef_warn(text, regex, elem, term, msg)
103
34
  regex.match(text) && @log.add("Style", elem, "#{term}: #{msg}")
104
35
  end
105
36
 
106
- # https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-r-ref_clause3
107
- def term_xrefs_validate(xmldoc)
108
- termids = xmldoc
109
- .xpath("//sections/terms | //sections/clause[.//terms] | " \
110
- "//annex[.//terms]").each_with_object({}) do |t, m|
111
- t.xpath(".//*/@anchor").each { |a| m[a.text] = true }
112
- t.xpath(".//*/@id").each { |a| m[a.text] = true }
113
- t.name == "terms" and m[t["anchor"] || t["id"]] = true
114
- end
115
- xmldoc.xpath(".//xref").each do |x|
116
- term_xrefs_validate1(x, termids)
117
- end
118
- end
119
-
120
- def term_xrefs_validate1(xref, termids)
121
- closest_id = xref.xpath("./ancestor::*[@id]")&.last or return
122
- termids[xref["target"]] && !termids[closest_id["id"]] and
123
- @log.add("Style", xref,
124
- "only terms clauses can cross-reference terms clause " \
125
- "(#{xref['target']})")
126
- !termids[xref["target"]] && termids[closest_id["id"]] and
127
- @log.add("Style", xref,
128
- "non-terms clauses cannot cross-reference terms clause " \
129
- "(#{xref['target']})")
130
- end
131
-
132
- # require that all assets of a particular type be cross-referenced
133
- # within the document
134
- def xrefs_mandate_validate(xmldoc)
135
- xrefs_mandate_validate1(xmldoc, "//annex", "Annex")
136
- xrefs_mandate_validate1(xmldoc, "//table", "Table")
137
- xrefs_mandate_validate1(xmldoc, "//figure", "Figure")
138
- xrefs_mandate_validate1(xmldoc, "//formula", "Formula")
139
- end
140
-
141
- def xrefs_mandate_validate1(xmldoc, xpath, name)
142
- exc = %w(table note example figure).map { |x| "//#{x}#{xpath}" }
143
- .join(" | ")
144
- (xmldoc.xpath(xpath) - xmldoc.xpath(exc)).each do |x|
145
- x["unnumbered"] == "true" and next
146
- @doc_xrefs[x["anchor"]] or
147
- @log.add("Style", x, "#{name} #{x['anchor']} has not been " \
148
- "cross-referenced within document",
149
- severity: xpath == "//formula" ? 2 : 1)
150
- end
151
- end
152
-
153
37
  # ISO/IEC DIR 2, 16.5.6
154
38
  def termdef_style(xmldoc)
155
39
  xmldoc.xpath("//term").each do |t|
@@ -185,6 +69,22 @@ module Metanorma
185
69
  iteration_validate(doc)
186
70
  end
187
71
 
72
+ # DRG directives 3.7; but anticipated by standoc
73
+ def subfigure_validate(xmldoc)
74
+ elems = { footnote: "fn", note: "note", key: "dl" }
75
+ xmldoc.xpath("//figure//figure").each do |f|
76
+ elems.each do |k, v|
77
+ f.xpath(".//#{v}").each do |n|
78
+ @log.add("Style", n, "#{k} is not permitted in a subfigure")
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ def figure_validate(xmldoc)
85
+ subfigure_validate(xmldoc)
86
+ end
87
+
188
88
  def content_validate(doc)
189
89
  super
190
90
  root = doc.root
@@ -203,14 +103,6 @@ module Metanorma
203
103
  list_punctuation(doc)
204
104
  end
205
105
 
206
- def iso_xref_validate(doc)
207
- see_xrefs_validate(doc)
208
- term_xrefs_validate(doc)
209
- xrefs_mandate_validate(doc)
210
- see_erefs_validate(doc)
211
- locality_erefs_validate(doc)
212
- end
213
-
214
106
  def bibitem_validate(xmldoc)
215
107
  xmldoc.xpath("//bibitem[date/on = '–']").each do |b|
216
108
  b.at("./note[@type = 'Unpublished-Status']") or
@@ -0,0 +1,128 @@
1
+ module Metanorma
2
+ module Iso
3
+ class Converter < Standoc::Converter
4
+ # https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-s-quantity
5
+ def style_subscript(node)
6
+ style_subscript_proper(node)
7
+ style_subscript_mathml(node)
8
+ end
9
+
10
+ # Check HTML subscripts - only topmost level subs (no sub ancestors)
11
+ def style_subscript_proper(node)
12
+ node.xpath(".//sub[not(ancestor::sub)]").each do |x|
13
+ depth = calculate_subscript_depth(x)
14
+ depth < 2 and next # No warning for single level subscripts
15
+ if [2, 3].include?(depth)
16
+ style_warning(node, "may contain nested subscripts", x.to_xml)
17
+ else # depth >= 3
18
+ style_warning(node, "no more than 3 levels of subscript nesting allowed",
19
+ x.to_xml)
20
+ end
21
+ end
22
+ end
23
+
24
+ # Check MathML subscripts - only topmost level msubs (no msub ancestors)
25
+ def style_subscript_mathml(node)
26
+ node.xpath(".//m:msub[not(ancestor::m:msub)]",
27
+ "m" => MATHML_NS).each do |x|
28
+ depth = calculate_mathml_subscript_depth(x)
29
+ depth < 2 and next # No warning for single level subscripts
30
+ if [2, 3].include?(depth)
31
+ style_warning(node, "may contain nested subscripts", x.to_xml)
32
+ else # depth > 3
33
+ style_warning(node, "no more than 3 levels of subscript nesting allowed",
34
+ x.to_xml)
35
+ end
36
+ end
37
+ end
38
+
39
+ # ISO/IEC DIR 2, 9.1
40
+ # ISO/IEC DIR 2, Table B.1
41
+ # https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-n-numbers
42
+ def style_number(node, text)
43
+ style_number_grouping(node, text)
44
+ style_regex(/(?:^|\p{Zs})(?<num>[0-9]+\.[0-9]+)(?!\.[0-9])/i,
45
+ "possible decimal point: mark up numbers with stem:[]", node, text)
46
+ @lang == "en" and style_regex(/\b(?<num>billions?)\b/i,
47
+ "ambiguous number", node, text)
48
+ style_regex(/(?:^|\p{Zs})(?<num>-[0-9][0-9,.]*)/i,
49
+ "hyphen instead of minus sign U+2212", node, text)
50
+ @novalid_number = true
51
+ end
52
+
53
+ def style_number_grouping(node, text)
54
+ if @validate_years
55
+ style_two_regex_not_prev(
56
+ node, text, /^(?<num>-?[0-9]{4,}[,0-9]*)\Z/,
57
+ %r{\b(ISO|IEC|IEEE|(in|January|February|March|April|May|June|August|September|October|November|December)\b)\Z},
58
+ "number not broken up in threes: mark up numbers with stem:[]"
59
+ )
60
+ else
61
+ style_two_regex_not_prev(
62
+ node, text, /^(?<num>-?(?:[0-9]{5,}[,0-9]*|[03-9]\d\d\d|1[0-8]\d\d|2[1-9]\d\d|20[5-9]\d))\Z/,
63
+ %r{\b(ISO|IEC|IEEE|\b)\Z},
64
+ "number not broken up in threes: mark up numbers with stem:[]"
65
+ )
66
+ end
67
+ end
68
+
69
+ # ISO/IEC DIR 2, 9.2.1
70
+ def style_percent(node, text)
71
+ style_regex(/\b(?<num>[0-9.,]+%)/,
72
+ "no space before percent sign", node, text)
73
+ style_regex(/\b(?<num>[0-9.,]+ \u00b1 [0-9,.]+ %)/,
74
+ "unbracketed tolerance before percent sign", node, text)
75
+ end
76
+
77
+ # leaving out as problematic: N J K C S T H h d B o E
78
+ SI_UNIT = "(m|cm|mm|km|μm|nm|g|kg|mgmol|cd|rad|sr|Hz|Hz|MHz|Pa|hPa|kJ|" \
79
+ "V|kV|W|MW|kW|F|μF|Ω|Wb|°C|lm|lx|Bq|Gy|Sv|kat|l|t|eV|u|Np|Bd|" \
80
+ "bit|kB|MB|Hart|nat|Sh|var)".freeze
81
+
82
+ # ISO/IEC DIR 2, 9.3
83
+ def style_units(node, text)
84
+ style_regex(/\b(?<num>[0-9][0-9,]*\p{Zs}+[\u00b0\u2032\u2033])/,
85
+ "space between number and degrees/minutes/seconds",
86
+ node, text)
87
+ style_regex(/\b(?<num>[0-9][0-9,]*#{SI_UNIT})\b/o,
88
+ "no space between number and SI unit", node, text)
89
+ style_non_std_units(node, text)
90
+ end
91
+
92
+ NONSTD_UNITS = {
93
+ sec: "s", mins: "min", hrs: "h", hr: "h", cc: "cm^3",
94
+ lit: "l", amp: "A", amps: "A", rpm: "r/min"
95
+ }.freeze
96
+
97
+ # ISO/IEC DIR 2, 9.3
98
+ def style_non_std_units(node, text)
99
+ NONSTD_UNITS.each do |k, v|
100
+ style_regex(/\b(?<num>[0-9][0-9,]*\p{Zs}+#{k})\b/,
101
+ "non-standard unit (should be #{v})", node, text)
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ def calculate_subscript_depth(sub_element)
108
+ sub_element.xpath(".//sub").empty? and return 1
109
+ max_depth = 1
110
+ sub_element.xpath(".//sub").each do |nested_sub|
111
+ depth = 1 + calculate_subscript_depth(nested_sub)
112
+ max_depth = [max_depth, depth].max
113
+ end
114
+ max_depth
115
+ end
116
+
117
+ def calculate_mathml_subscript_depth(msub_element)
118
+ msub_element.xpath(".//m:msub", "m" => MATHML_NS).empty? and return 1
119
+ max_depth = 1
120
+ msub_element.xpath(".//m:msub", "m" => MATHML_NS).each do |nested_msub|
121
+ depth = 1 + calculate_mathml_subscript_depth(nested_msub)
122
+ max_depth = [max_depth, depth].max
123
+ end
124
+ max_depth
125
+ end
126
+ end
127
+ end
128
+ end
@@ -94,17 +94,6 @@ module Metanorma
94
94
  style_problem_words(node, text)
95
95
  end
96
96
 
97
- # https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-s-quantity
98
- def style_subscript(node)
99
- warning = "may contain nested subscripts (max 3 levels allowed)"
100
- node.xpath(".//sub[.//sub]").each do |x|
101
- style_warning(node, warning, x.to_xml)
102
- end
103
- node.xpath(".//m:msub[.//m:msub]", "m" => MATHML_NS).each do |x|
104
- style_warning(node, warning, x.to_xml)
105
- end
106
- end
107
-
108
97
  # https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-s-need
109
98
  # https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-s-might
110
99
  # https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-s-family
@@ -120,44 +109,6 @@ module Metanorma
120
109
  style_regex(/\b(?<num>billions?)\b/i, "ambiguous number", node, text)
121
110
  end
122
111
 
123
- # ISO/IEC DIR 2, 9.1
124
- # ISO/IEC DIR 2, Table B.1
125
- # https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-n-numbers
126
- def style_number(node, text)
127
- style_number_grouping(node, text)
128
- style_regex(/(?:^|\p{Zs})(?<num>[0-9]+\.[0-9]+)(?!\.[0-9])/i,
129
- "possible decimal point: mark up numbers with stem:[]", node, text)
130
- @lang == "en" and style_regex(/\b(?<num>billions?)\b/i,
131
- "ambiguous number", node, text)
132
- style_regex(/(?:^|\p{Zs})(?<num>-[0-9][0-9,.]*)/i,
133
- "hyphen instead of minus sign U+2212", node, text)
134
- @novalid_number = true
135
- end
136
-
137
- def style_number_grouping(node, text)
138
- if @validate_years
139
- style_two_regex_not_prev(
140
- node, text, /^(?<num>-?[0-9]{4,}[,0-9]*)\Z/,
141
- %r{\b(ISO|IEC|IEEE|(in|January|February|March|April|May|June|August|September|October|November|December)\b)\Z},
142
- "number not broken up in threes: mark up numbers with stem:[]"
143
- )
144
- else
145
- style_two_regex_not_prev(
146
- node, text, /^(?<num>-?(?:[0-9]{5,}[,0-9]*|[03-9]\d\d\d|1[0-8]\d\d|2[1-9]\d\d|20[5-9]\d))\Z/,
147
- %r{\b(ISO|IEC|IEEE|\b)\Z},
148
- "number not broken up in threes: mark up numbers with stem:[]"
149
- )
150
- end
151
- end
152
-
153
- # ISO/IEC DIR 2, 9.2.1
154
- def style_percent(node, text)
155
- style_regex(/\b(?<num>[0-9.,]+%)/,
156
- "no space before percent sign", node, text)
157
- style_regex(/\b(?<num>[0-9.,]+ \u00b1 [0-9,.]+ %)/,
158
- "unbracketed tolerance before percent sign", node, text)
159
- end
160
-
161
112
  # ISO/IEC DIR 2, 8.4
162
113
  # ISO/IEC DIR 2, 9.3
163
114
  def style_abbrev(node, text)
@@ -173,29 +124,6 @@ module Metanorma
173
124
  "V|kV|W|MW|kW|F|μF|Ω|Wb|°C|lm|lx|Bq|Gy|Sv|kat|l|t|eV|u|Np|Bd|" \
174
125
  "bit|kB|MB|Hart|nat|Sh|var)".freeze
175
126
 
176
- # ISO/IEC DIR 2, 9.3
177
- def style_units(node, text)
178
- style_regex(/\b(?<num>[0-9][0-9,]*\p{Zs}+[\u00b0\u2032\u2033])/,
179
- "space between number and degrees/minutes/seconds",
180
- node, text)
181
- style_regex(/\b(?<num>[0-9][0-9,]*#{SI_UNIT})\b/o,
182
- "no space between number and SI unit", node, text)
183
- style_non_std_units(node, text)
184
- end
185
-
186
- NONSTD_UNITS = {
187
- sec: "s", mins: "min", hrs: "h", hr: "h", cc: "cm^3",
188
- lit: "l", amp: "A", amps: "A", rpm: "r/min"
189
- }.freeze
190
-
191
- # ISO/IEC DIR 2, 9.3
192
- def style_non_std_units(node, text)
193
- NONSTD_UNITS.each do |k, v|
194
- style_regex(/\b(?<num>[0-9][0-9,]*\p{Zs}+#{k})\b/,
195
- "non-standard unit (should be #{v})", node, text)
196
- end
197
- end
198
-
199
127
  # https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-p-and
200
128
  # https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-p-andor
201
129
  def style_punct(node, text)
@@ -0,0 +1,130 @@
1
+ module Metanorma
2
+ module Iso
3
+ class Converter < Standoc::Converter
4
+ # ISO/IEC DIR 2, 15.5.3, 20.2
5
+ # does not deal with preceding text marked up
6
+ def see_xrefs_validate(root)
7
+ @lang == "en" or return
8
+ anchors = extract_anchor_norm(root)
9
+ root.xpath("//xref").each do |t|
10
+ preceding = t.at("./preceding-sibling::text()[last()]")
11
+ !preceding.nil? &&
12
+ /\b(see| refer to)\p{Zs}*\Z/mi.match(preceding) or next
13
+ anchors[t["target"]] and
14
+ @log.add("Style", t,
15
+ "'see #{t['target']}' is pointing to a normative section")
16
+ end
17
+ end
18
+
19
+ def extract_anchor_norm(root)
20
+ nodes = root.xpath("//annex[@obligation = 'normative'] | " \
21
+ "//references[@obligation = 'normative']")
22
+ ret = nodes.each_with_object({}) do |n, m|
23
+ n["anchor"] and m[n["anchor"]] = true
24
+ end
25
+ nodes.each do |n|
26
+ n.xpath(".//*[@anchor]").each { |n1| ret[n1["anchor"]] = true }
27
+ end
28
+ ret
29
+ end
30
+
31
+ # ISO/IEC DIR 2, 15.5.3
32
+ def see_erefs_validate(root)
33
+ @lang == "en" or return
34
+ bibitemids = extract_bibitem_anchors(root)
35
+ root.xpath("//eref").each do |t|
36
+ prec = t.at("./preceding-sibling::text()[last()]")
37
+ !prec.nil? && /\b(see|refer to)\p{Zs}*\Z/mi.match(prec) or next
38
+ unless target = bibitemids[t["bibitemid"]]
39
+ # unless target = root.at("//bibitem[@anchor = '#{t['bibitemid']}']")
40
+ @log.add("Bibliography", t,
41
+ "'#{t} is not pointing to a real reference")
42
+ next
43
+ end
44
+ target[:norm] and
45
+ @log.add("Style", t,
46
+ "'see #{t}' is pointing to a normative reference")
47
+ end
48
+ end
49
+
50
+ def extract_bibitem_anchors(root)
51
+ ret = root.xpath("//references[@normative = 'true']//bibitem")
52
+ .each_with_object({}) do |b, m|
53
+ m[b["anchor"]] = { bib: b, norm: true }
54
+ end
55
+ root.xpath("//references[not(@normative = 'true')]//bibitem")
56
+ .each do |b|
57
+ ret[b["anchor"]] = { bib: b, norm: false }
58
+ end
59
+ ret
60
+ end
61
+
62
+ # ISO/IEC DIR 2, 10.4
63
+ def locality_erefs_validate(root)
64
+ root.xpath("//eref[descendant::locality]").each do |t|
65
+ if /^(ISO|IEC)/.match?(t["citeas"]) &&
66
+ !/: ?(\d+{4}|–)$/.match?(t["citeas"])
67
+ @log.add("Style", t,
68
+ "undated reference #{t['citeas']} should not contain " \
69
+ "specific elements")
70
+ end
71
+ end
72
+ end
73
+
74
+ # https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-r-ref_clause3
75
+ def term_xrefs_validate(xmldoc)
76
+ termids = xmldoc
77
+ .xpath("//sections/terms | //sections/clause[.//terms] | " \
78
+ "//annex[.//terms]").each_with_object({}) do |t, m|
79
+ t.xpath(".//*/@anchor").each { |a| m[a.text] = true }
80
+ t.xpath(".//*/@id").each { |a| m[a.text] = true }
81
+ t.name == "terms" and m[t["anchor"] || t["id"]] = true
82
+ end
83
+ xmldoc.xpath(".//xref").each do |x|
84
+ term_xrefs_validate1(x, termids)
85
+ end
86
+ end
87
+
88
+ def term_xrefs_validate1(xref, termids)
89
+ closest_id = xref.xpath("./ancestor::*[@id]")&.last or return
90
+ termids[xref["target"]] && !termids[closest_id["id"]] and
91
+ @log.add("Style", xref,
92
+ "only terms clauses can cross-reference terms clause " \
93
+ "(#{xref['target']})")
94
+ !termids[xref["target"]] && termids[closest_id["id"]] and
95
+ @log.add("Style", xref,
96
+ "non-terms clauses cannot cross-reference terms clause " \
97
+ "(#{xref['target']})")
98
+ end
99
+
100
+ # require that all assets of a particular type be cross-referenced
101
+ # within the document
102
+ def xrefs_mandate_validate(xmldoc)
103
+ xrefs_mandate_validate1(xmldoc, "//annex", "Annex")
104
+ xrefs_mandate_validate1(xmldoc, "//table", "Table")
105
+ xrefs_mandate_validate1(xmldoc, "//figure", "Figure")
106
+ xrefs_mandate_validate1(xmldoc, "//formula", "Formula")
107
+ end
108
+
109
+ def xrefs_mandate_validate1(xmldoc, xpath, name)
110
+ exc = %w(table note example figure).map { |x| "//#{x}#{xpath}" }
111
+ .join(" | ")
112
+ (xmldoc.xpath(xpath) - xmldoc.xpath(exc)).each do |x|
113
+ x["unnumbered"] == "true" and next
114
+ @doc_xrefs[x["anchor"]] or
115
+ @log.add("Style", x, "#{name} #{x['anchor']} has not been " \
116
+ "cross-referenced within document",
117
+ severity: xpath == "//formula" ? 2 : 1)
118
+ end
119
+ end
120
+
121
+ def iso_xref_validate(doc)
122
+ see_xrefs_validate(doc)
123
+ term_xrefs_validate(doc)
124
+ xrefs_mandate_validate(doc)
125
+ see_erefs_validate(doc)
126
+ locality_erefs_validate(doc)
127
+ end
128
+ end
129
+ end
130
+ end
@@ -1,5 +1,5 @@
1
1
  module Metanorma
2
2
  module Iso
3
- VERSION = "3.1.3".freeze
3
+ VERSION = "3.1.4".freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metanorma-iso
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.3
4
+ version: 3.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-18 00:00:00.000000000 Z
11
+ date: 2025-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: metanorma-standoc
@@ -376,12 +376,13 @@ files:
376
376
  - lib/metanorma/iso/reqt.rng
377
377
  - lib/metanorma/iso/section.rb
378
378
  - lib/metanorma/iso/validate.rb
379
- - lib/metanorma/iso/validate_image.rb
380
379
  - lib/metanorma/iso/validate_list.rb
380
+ - lib/metanorma/iso/validate_numeric.rb
381
381
  - lib/metanorma/iso/validate_requirements.rb
382
382
  - lib/metanorma/iso/validate_section.rb
383
383
  - lib/metanorma/iso/validate_style.rb
384
384
  - lib/metanorma/iso/validate_title.rb
385
+ - lib/metanorma/iso/validate_xref.rb
385
386
  - lib/metanorma/iso/version.rb
386
387
  - lib/metanorma/requirements/modspec.rb
387
388
  - lib/metanorma/requirements/requirements.rb