metanorma-iso 3.1.3 → 3.1.5

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.
@@ -84,7 +84,8 @@ module IsoDoc
84
84
  super
85
85
  end
86
86
 
87
- def middle_title(docxml)
87
+ # KILL
88
+ def middle_titlex(docxml)
88
89
  @meta.get[:doctitlemain].nil? || @meta.get[:doctitlemain].empty? and
89
90
  return
90
91
  s = docxml.at(ns("//sections")) or return
@@ -92,7 +93,8 @@ module IsoDoc
92
93
  s.add_first_child ret
93
94
  end
94
95
 
95
- def middle_title_main
96
+ # KILL
97
+ def middle_title_mainx
96
98
  ret = "<span class='boldtitle'>#{@meta.get[:doctitleintro]}"
97
99
  @meta.get[:doctitleintro] && @meta.get[:doctitlemain] and
98
100
  ret += " &#x2014; "
@@ -104,7 +106,17 @@ module IsoDoc
104
106
  "<p class='zzSTDTitle1'>#{ret}</p>"
105
107
  end
106
108
 
107
- def middle_title_part
109
+ def middle_title_main
110
+ <<~OUTPUT
111
+ <p class='zzSTDTitle1'><span class='boldtitle'>{{ doctitleintro -}}
112
+ {% if doctitleintro and doctitlemain %} &#x2014; {% endif %}{{ doctitlemain -}}
113
+ {% if doctitlemain %}{% if doctitlepart or doctitlecomplementary %} &#x2014; {% endif %}{% endif -%}
114
+ </span>#{middle_title_part}</p>
115
+ OUTPUT
116
+ end
117
+
118
+ # KILL
119
+ def middle_title_partx
108
120
  ret = ""
109
121
  if a = @meta.get[:doctitlecomplementary]
110
122
  ret += "<span class='boldtitle'>#{a}</span>"
@@ -116,7 +128,17 @@ module IsoDoc
116
128
  ret
117
129
  end
118
130
 
119
- def middle_title_amd
131
+ def middle_title_part
132
+ <<~OUTPUT.strip
133
+ {% if doctitlecomplementary %}<span class='boldtitle'>{{ doctitlecomplementary }}</span>
134
+ {% elsif doctitlepart -%}
135
+ {% if doctitlepartlabel %}<span class='nonboldtitle'>{{ doctitlepartlabel }}:</span>{% endif -%}
136
+ <span class='boldtitle'>{{ doctitlepart }}</span>{% endif %}
137
+ OUTPUT
138
+ end
139
+
140
+ # KILL
141
+ def middle_title_amdx
120
142
  ret = ""
121
143
  if a = @meta.get[:doctitleamdlabel]
122
144
  ret += "<p class='zzSTDTitle2'>#{a}"
@@ -128,6 +150,19 @@ module IsoDoc
128
150
  ret
129
151
  end
130
152
 
153
+ def middle_title_amd
154
+ <<~OUTPUT
155
+ {% if doctitleamdlabel %}<p class='zzSTDTitle2'>{{ doctitleamdlabel -}}
156
+ {% if doctitleamd %}: {{doctitleamd}}{% endif -%}
157
+ </p>{% endif %}
158
+ {% if doctitlecorrlabel %}<p class='zzSTDTitle2'>{{ doctitlecorrlabel }}</p>{% endif %}
159
+ OUTPUT
160
+ end
161
+
162
+ def middle_title_template
163
+ middle_title_main + middle_title_amd
164
+ end
165
+
131
166
  def move_norm_ref_to_sections(docxml)
132
167
  amd?(docxml) or super
133
168
  end
@@ -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
@@ -71,56 +71,18 @@ module Metanorma
71
71
  @log.add("Document Attributes", nil, err)
72
72
  end
73
73
 
74
- def title_component(node, xml, lang, attr, comp)
74
+ def title_component(node, xml, lang, comp)
75
75
  t = node.attr("title-#{comp[:name]}-#{lang}") or return
76
- xml.title(**attr_code(attr.merge(type: "title-#{comp[:abbr]}"))) do |t1|
77
- t1 << Metanorma::Utils::asciidoc_sub(t)
78
- end
76
+ add_title_xml(xml, t, lang, "title-#{comp[:abbr]}")
79
77
  end
80
78
 
81
- def title_intro(node, xml, lang, at)
82
- t = node.attr("title-intro-#{lang}") or return
83
- xml.title(**attr_code(at.merge(type: "title-intro"))) do |t1|
84
- t1 << Metanorma::Utils::asciidoc_sub(t)
85
- end
86
- end
87
-
88
- def title_main(node, xml, lang, at)
89
- xml.title **attr_code(at.merge(type: "title-main")) do |t1|
90
- t1 << Metanorma::Utils::asciidoc_sub(node.attr("title-main-#{lang}"))
91
- end
92
- end
93
-
94
- def title_part(node, xml, lang, at)
95
- t = node.attr("title-part-#{lang}") or return
96
- xml.title(**attr_code(at.merge(type: "title-part"))) do |t1|
97
- t1 << Metanorma::Utils::asciidoc_sub(t)
98
- end
99
- end
100
-
101
- def title_amd(node, xml, lang, at)
102
- t = node.attr("title-amendment-#{lang}") or return
103
- xml.title(**attr_code(at.merge(type: "title-amd"))) do |t1|
104
- t1 << Metanorma::Utils::asciidoc_sub(t)
105
- end
106
- end
107
-
108
- def title_add(node, xml, lang, at)
109
- t = node.attr("title-addendum-#{lang}") or return
110
- xml.title(**attr_code(at.merge(type: "title-add"))) do |t1|
111
- t1 << Metanorma::Utils::asciidoc_sub(t)
112
- end
113
- end
114
-
115
- def title_full(node, xml, lang, at)
79
+ def title_full(node, xml, lang)
116
80
  title, intro, part, amd, add = title_full_prep(node, lang)
117
81
  title = "#{intro} -- #{title}" if intro
118
82
  title = "#{title} -- #{part}" if part
119
83
  title = "#{title} -- #{amd}" if amd
120
84
  title = "#{title} -- #{add}" if add
121
- xml.title **attr_code(at.merge(type: "main")) do |t1|
122
- t1 << Metanorma::Utils::asciidoc_sub(title)
123
- end
85
+ add_title_xml(xml, title, lang, "main")
124
86
  end
125
87
 
126
88
  def title_full_prep(node, lang)
@@ -136,20 +98,19 @@ module Metanorma
136
98
 
137
99
  def title(node, xml)
138
100
  %w(en ru fr).each do |lang|
139
- at = { language: lang, format: "text/plain" }
140
- title1(node, xml, lang, at)
101
+ title1(node, xml, lang)
141
102
  end
142
103
  end
143
104
 
144
- def title1(node, xml, lang, at)
145
- title_full(node, xml, lang, at)
105
+ def title1(node, xml, lang)
106
+ title_full(node, xml, lang)
146
107
  %w(intro main part complementary).each do |w|
147
- title_component(node, xml, lang, at, { name: w, abbr: w })
108
+ title_component(node, xml, lang, { name: w, abbr: w })
148
109
  end
149
- @amd and title_component(node, xml, lang, at,
110
+ @amd and title_component(node, xml, lang,
150
111
  { name: "amendment", abbr: "amd" })
151
112
  node.attr("addendum-number") and
152
- title_component(node, xml, lang, at,
113
+ title_component(node, xml, lang,
153
114
  { name: "addendum", abbr: "add" })
154
115
  end
155
116
 
@@ -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.3 -->
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">
@@ -443,14 +455,7 @@ normative or informative references, some split references into sections organiz
443
455
  </ref>
444
456
  </zeroOrMore>
445
457
  <zeroOrMore>
446
- <ref name="doc_bibitem">
447
- <a:documentation>Bibliographic item cited in the document</a:documentation>
448
- </ref>
449
- <zeroOrMore>
450
- <ref name="note">
451
- <a:documentation>Annotation of the bibliographic item</a:documentation>
452
- </ref>
453
- </zeroOrMore>
458
+ <ref name="ReferenceEntry"/>
454
459
  </zeroOrMore>
455
460
  <zeroOrMore>
456
461
  <ref name="references">
@@ -592,18 +597,21 @@ gives an explicit page orientation</a:documentation>
592
597
  </element>
593
598
  </optional>
594
599
  </define>
595
- </include>
596
- <!-- end overrides -->
597
- <define name="FnAttributes" combine="interleave">
598
- <ref name="RequiredId"/>
599
- <optional>
600
- <attribute name="hiddenref">
601
- <a:documentation>If true, number the footnote as normal, but suppress display of the footnote reference in the document body.
600
+ <define name="FnAttributes">
601
+ <ref name="RequiredId"/>
602
+ <optional>
603
+ <attribute name="hiddenref">
604
+ <a:documentation>If true, number the footnote as normal, but suppress display of the footnote reference in the document body.
602
605
  This is done if the footnote reference is already presented in some other form, e.g. within a figure image.</a:documentation>
603
- <data type="boolean"/>
606
+ <data type="boolean"/>
607
+ </attribute>
608
+ </optional>
609
+ <attribute name="reference">
610
+ <a:documentation>The number of the footnote, used to identify it visually</a:documentation>
604
611
  </attribute>
605
- </optional>
606
- </define>
612
+ </define>
613
+ </include>
614
+ <!-- end overrides -->
607
615
  <define name="TdAttributes" combine="interleave">
608
616
  <ref name="RequiredId"/>
609
617
  <optional>
@@ -841,6 +849,17 @@ titlecase, or lowercase</a:documentation>
841
849
  <value>informative</value>
842
850
  </choice>
843
851
  </define>
852
+ <define name="ReferenceEntry">
853
+ <a:documentation>Entry in bibliography</a:documentation>
854
+ <ref name="doc_bibitem">
855
+ <a:documentation>Bibliographic item cited in the document</a:documentation>
856
+ </ref>
857
+ <zeroOrMore>
858
+ <ref name="note">
859
+ <a:documentation>Annotation of the bibliographic item</a:documentation>
860
+ </ref>
861
+ </zeroOrMore>
862
+ </define>
844
863
  <define name="doc_bibitem">
845
864
  <a:documentation>Standardisation document representation of bibliographic entry</a:documentation>
846
865
  <element name="bibitem">
@@ -871,6 +890,12 @@ titlecase, or lowercase</a:documentation>
871
890
  </oneOrMore>
872
891
  </element>
873
892
  </define>
893
+ <define name="ParagraphFnBody" combine="interleave">
894
+ <ref name="BlockSource">
895
+ <a:documentation>Bibliographic source for the information in the paragraph
896
+ parargaph sources are currently only rendered in metanorma-plateau</a:documentation>
897
+ </ref>
898
+ </define>
874
899
  <define name="BasicBlock" combine="choice">
875
900
  <ref name="columnbreak"/>
876
901
  </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