metanorma-iso 1.10.6 → 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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +26 -0
  3. data/Makefile +1 -1
  4. data/lib/asciidoctor/iso/base.rb +2 -69
  5. data/lib/asciidoctor/iso/cleanup.rb +2 -175
  6. data/lib/asciidoctor/iso/converter.rb +2 -17
  7. data/lib/asciidoctor/iso/deprecated.rb +5 -0
  8. data/lib/asciidoctor/iso/front.rb +2 -169
  9. data/lib/asciidoctor/iso/front_id.rb +2 -224
  10. data/lib/asciidoctor/iso/section.rb +2 -48
  11. data/lib/asciidoctor/iso/validate.rb +2 -171
  12. data/lib/asciidoctor/iso/validate_image.rb +2 -96
  13. data/lib/asciidoctor/iso/validate_requirements.rb +2 -110
  14. data/lib/asciidoctor/iso/validate_section.rb +2 -246
  15. data/lib/asciidoctor/iso/validate_style.rb +2 -169
  16. data/lib/asciidoctor/iso/validate_title.rb +2 -104
  17. data/lib/isodoc/iso/html/htmlstyle.css +47 -0
  18. data/lib/isodoc/iso/html/isodoc.css +1326 -0
  19. data/lib/isodoc/iso/html/isodoc.scss +0 -1
  20. data/lib/isodoc/iso/html/style-human.css +1015 -0
  21. data/lib/isodoc/iso/html/style-human.scss +8 -2
  22. data/lib/isodoc/iso/html/style-iso.css +1042 -0
  23. data/lib/isodoc/iso/html/style-iso.scss +9 -2
  24. data/lib/isodoc/iso/html/wordstyle.css +1701 -0
  25. data/lib/isodoc/iso/html_convert.rb +6 -4
  26. data/lib/isodoc/iso/iso.amendment.xsl +2142 -1565
  27. data/lib/isodoc/iso/iso.international-standard.xsl +2142 -1565
  28. data/lib/isodoc/iso/word_convert.rb +2 -0
  29. data/lib/metanorma/iso/base.rb +70 -0
  30. data/lib/{asciidoctor → metanorma}/iso/basicdoc.rng +5 -3
  31. data/lib/{asciidoctor → metanorma}/iso/biblio.rng +7 -5
  32. data/lib/{asciidoctor → metanorma}/iso/boilerplate-fr.xml +0 -0
  33. data/lib/{asciidoctor → metanorma}/iso/boilerplate.xml +0 -0
  34. data/lib/metanorma/iso/cleanup.rb +176 -0
  35. data/lib/metanorma/iso/converter.rb +18 -0
  36. data/lib/metanorma/iso/front.rb +170 -0
  37. data/lib/metanorma/iso/front_id.rb +225 -0
  38. data/lib/{asciidoctor → metanorma}/iso/isodoc.rng +98 -1
  39. data/lib/{asciidoctor → metanorma}/iso/isostandard-amd.rng +0 -0
  40. data/lib/{asciidoctor → metanorma}/iso/isostandard.rnc +0 -0
  41. data/lib/{asciidoctor → metanorma}/iso/isostandard.rng +0 -0
  42. data/lib/{asciidoctor → metanorma}/iso/reqt.rng +0 -0
  43. data/lib/metanorma/iso/section.rb +49 -0
  44. data/lib/metanorma/iso/validate.rb +172 -0
  45. data/lib/metanorma/iso/validate_image.rb +97 -0
  46. data/lib/metanorma/iso/validate_requirements.rb +111 -0
  47. data/lib/metanorma/iso/validate_section.rb +248 -0
  48. data/lib/metanorma/iso/validate_style.rb +170 -0
  49. data/lib/metanorma/iso/validate_title.rb +105 -0
  50. data/lib/metanorma/iso/version.rb +1 -1
  51. data/lib/metanorma-iso.rb +1 -1
  52. data/metanorma-iso.gemspec +1 -1
  53. data/spec/isodoc/ref_spec.rb +4 -2
  54. data/spec/isodoc/xref_spec.rb +18 -18
  55. data/spec/{asciidoctor → metanorma}/amd_spec.rb +1 -1
  56. data/spec/{asciidoctor → metanorma}/base_spec.rb +158 -197
  57. data/spec/{asciidoctor → metanorma}/blank_spec.rb +1 -1
  58. data/spec/{asciidoctor → metanorma}/blocks_spec.rb +1 -1
  59. data/spec/{asciidoctor → metanorma}/cleanup_spec.rb +1 -1
  60. data/spec/{asciidoctor → metanorma}/inline_spec.rb +1 -1
  61. data/spec/{asciidoctor → metanorma}/lists_spec.rb +1 -1
  62. data/spec/{asciidoctor → metanorma}/refs_spec.rb +2 -5
  63. data/spec/{asciidoctor → metanorma}/section_spec.rb +1 -1
  64. data/spec/{asciidoctor → metanorma}/table_spec.rb +1 -1
  65. data/spec/{asciidoctor → metanorma}/validate_spec.rb +28 -3
  66. data/spec/spec_helper.rb +1 -1
  67. metadata +46 -30
  68. data/spec/vcr_cassettes/docrels.yml +0 -393
  69. data/spec/vcr_cassettes/sortrefs.yml +0 -599
@@ -0,0 +1,97 @@
1
+ module Metanorma
2
+ module ISO
3
+ class Converter < Standoc::Converter
4
+ # DRG directives 3.7; but anticipated by standoc
5
+ def subfigure_validate(xmldoc)
6
+ xmldoc.xpath("//figure//figure").each do |f|
7
+ { footnote: "fn", note: "note", key: "dl" }.each do |k, v|
8
+ f.xpath(".//#{v}").each do |n|
9
+ @log.add("Style", n, "#{k} is not permitted in a subfigure")
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ def image_name_prefix(xmldoc)
16
+ std = xmldoc&.at("//bibdata/ext/structuredidentifier/project-number") or
17
+ return
18
+ num = xmldoc&.at("//bibdata/docnumber")&.text or return
19
+ ed = xmldoc&.at("//bibdata/edition")&.text || "1"
20
+ prefix = num
21
+ std["part"] and prefix += "-#{std['part']}"
22
+ prefix += "_ed#{ed}"
23
+ amd = std["amendment"] and prefix += "amd#{amd}"
24
+ prefix
25
+ end
26
+
27
+ def image_name_suffix(xmldoc)
28
+ case xmldoc&.at("//bibdata/language")&.text
29
+ when "fr" then "_f"
30
+ when "de" then "_d"
31
+ when "ru" then "_r"
32
+ when "es" then "_s"
33
+ when "ar" then "_a"
34
+ # when "en" then "_e"
35
+ else
36
+ "_e"
37
+ end
38
+ end
39
+
40
+ def disjunct_error(img, cond1, cond2, msg1, msg2)
41
+ cond1 && !cond2 and
42
+ @log.add("Style", img, "image name #{img['src']} #{msg1}")
43
+ !cond1 && cond2 and
44
+ @log.add("Style", img, "image name #{img['src']} #{msg2}")
45
+ end
46
+
47
+ def image_name_parse(img, prefix)
48
+ m = %r[(SL)?#{prefix}fig(?<tab>Tab)?(?<annex>[A-Z])?(Text)?(?<num>\d+)
49
+ (?<subfig>[a-z])?(?<key>_key\d+)?(?<lang>_[a-z])?$]x
50
+ .match(File.basename(img["src"], ".*"))
51
+ m.nil? and
52
+ @log.add("Style", img,
53
+ "image name #{img['src']} does not match DRG requirements")
54
+ m
55
+ end
56
+
57
+ def image_name_validate1(i, prefix)
58
+ m = image_name_parse(i, prefix) or return
59
+ warn i["src"]
60
+ disjunct_error(i, i.at("./ancestor::table"), !m[:tab].nil?,
61
+ "is under a table but is not so labelled",
62
+ "is labelled as under a table but is not")
63
+ disjunct_error(i, i.at("./ancestor::annex"), !m[:annex].nil?,
64
+ "is under an annex but is not so labelled",
65
+ "is labelled as under an annex but is not")
66
+ disjunct_error(i, i.xpath("./ancestor::figure").size > 1, !m[:subfig].nil?,
67
+ "does not have a subfigure letter but is a subfigure",
68
+ "has a subfigure letter but is not a subfigure")
69
+ lang = image_name_suffix(i.document.root)
70
+ (m[:lang] || "_e") == lang or
71
+ @log.add("Style", i,
72
+ "image name #{i['src']} expected to have suffix #{lang}")
73
+ end
74
+
75
+ # DRG directives 3.2
76
+ def image_name_validate(xmldoc)
77
+ prefix = image_name_prefix(xmldoc) or return
78
+ xmldoc.xpath("//image").each do |i|
79
+ next if i["src"].start_with?("data:")
80
+
81
+ if /^ISO_\d+_/.match?(File.basename(i["src"]))
82
+ elsif /^(SL)?#{prefix}fig/.match?(File.basename(i["src"]))
83
+ image_name_validate1(i, prefix)
84
+ else
85
+ @log.add("Style", i,
86
+ "image name #{i['src']} does not match DRG requirements: expect #{prefix}fig")
87
+ end
88
+ end
89
+ end
90
+
91
+ def figure_validate(xmldoc)
92
+ image_name_validate(xmldoc)
93
+ subfigure_validate(xmldoc)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,111 @@
1
+ require "metanorma-standoc"
2
+
3
+ module Metanorma
4
+ module ISO
5
+ class Converter < Standoc::Converter
6
+ REQUIREMENT_RE_STR = <<~REGEXP.freeze
7
+ \\b
8
+ ( shall | (is|are)_to |
9
+ (is|are)_required_(not_)?to |
10
+ (is|are)_required_that |
11
+ has_to |
12
+ only\\b[^.,]+\\b(is|are)_permitted |
13
+ it_is_necessary |
14
+ (is|are)_not_(allowed | permitted |
15
+ acceptable | permissible) |
16
+ (is|are)_not_to_be |
17
+ [.,:;]_do_not )
18
+ \\b
19
+ REGEXP
20
+
21
+ def requirement_re
22
+ Regexp.new(self.class::REQUIREMENT_RE_STR.gsub(/\s/, "")
23
+ .gsub(/_/, "\\s"), Regexp::IGNORECASE)
24
+ end
25
+
26
+ def requirement_check(text)
27
+ text.split(/\.\s+/).each do |t|
28
+ return t if requirement_re.match t
29
+ end
30
+ nil
31
+ end
32
+
33
+ RECOMMENDATION_RE_STR = <<~REGEXP.freeze
34
+ \\b
35
+ should |
36
+ ought_(not_)?to |
37
+ it_is_(not_)?recommended_that
38
+ \\b
39
+ REGEXP
40
+
41
+ def recommendation_re
42
+ Regexp.new(self.class::RECOMMENDATION_RE_STR.gsub(/\s/, "")
43
+ .gsub(/_/, "\\s"), Regexp::IGNORECASE)
44
+ end
45
+
46
+ def recommendation_check(text)
47
+ text.split(/\.\s+/).each do |t|
48
+ return t if recommendation_re.match t
49
+ end
50
+ nil
51
+ end
52
+
53
+ PERMISSION_RE_STR = <<~REGEXP.freeze
54
+ \\b
55
+ may |
56
+ (is|are)_(permitted | allowed | permissible ) |
57
+ it_is_not_required_that |
58
+ no\\b[^.,]+\\b(is|are)_required
59
+ \\b
60
+ REGEXP
61
+
62
+ def permission_re
63
+ Regexp.new(self.class::PERMISSION_RE_STR.gsub(/\s/, "")
64
+ .gsub(/_/, "\\s"), Regexp::IGNORECASE)
65
+ end
66
+
67
+ def permission_check(text)
68
+ text.split(/\.\s+/).each do |t|
69
+ return t if permission_re.match t
70
+ end
71
+ nil
72
+ end
73
+
74
+ POSSIBILITY_RE_STR = <<~REGEXP.freeze
75
+ \\b
76
+ can | cannot | be_able_to |
77
+ there_is_a_possibility_of |
78
+ it_is_possible_to | be_unable_to |
79
+ there_is_no_possibility_of |
80
+ it_is_not_possible_to
81
+ \\b
82
+ REGEXP
83
+
84
+ def possibility_re
85
+ Regexp.new(self.class::POSSIBILITY_RE_STR.gsub(/\s/, "")
86
+ .gsub(/_/, "\\s"), Regexp::IGNORECASE)
87
+ end
88
+
89
+ def possibility_check(text)
90
+ text.split(/\.\s+/).each { |t| return t if possibility_re.match t }
91
+ nil
92
+ end
93
+
94
+ def external_constraint(text)
95
+ text.split(/\.\s+/).each do |t|
96
+ return t if /\b(must)\b/xi.match? t
97
+ end
98
+ nil
99
+ end
100
+
101
+ def style_no_guidance(node, text, docpart)
102
+ r = requirement_check(text)
103
+ style_warning(node, "#{docpart} may contain requirement", r) if r
104
+ r = permission_check(text)
105
+ style_warning(node, "#{docpart} may contain permission", r) if r
106
+ r = recommendation_check(text)
107
+ style_warning(node, "#{docpart} may contain recommendation", r) if r
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,248 @@
1
+ require "nokogiri"
2
+
3
+ module Metanorma
4
+ module ISO
5
+ class Converter < Standoc::Converter
6
+ def section_validate(doc)
7
+ doctype = doc&.at("//bibdata/ext/doctype")&.text
8
+ unless %w(amendment technical-corrigendum).include? doctype
9
+ foreword_validate(doc.root)
10
+ normref_validate(doc.root)
11
+ symbols_validate(doc.root)
12
+ sections_presence_validate(doc.root)
13
+ sections_sequence_validate(doc.root)
14
+ end
15
+ section_style(doc.root)
16
+ subclause_validate(doc.root)
17
+ super
18
+ end
19
+
20
+ # ISO/IEC DIR 2, 12.4
21
+ def foreword_validate(root)
22
+ f = root.at("//foreword") || return
23
+ s = f.at("./clause")
24
+ @log.add("Style", f, "foreword contains subclauses") unless s.nil?
25
+ end
26
+
27
+ # ISO/IEC DIR 2, 15.4
28
+ def normref_validate(root)
29
+ f = root.at("//references[@normative = 'true']") || return
30
+ f.at("./references | ./clause") &&
31
+ @log.add("Style", f, "normative references contains subclauses")
32
+ end
33
+
34
+ ONE_SYMBOLS_WARNING = "Only one Symbols and Abbreviated "\
35
+ "Terms section in the standard".freeze
36
+
37
+ NON_DL_SYMBOLS_WARNING = "Symbols and Abbreviated Terms can "\
38
+ "only contain a definition list".freeze
39
+
40
+ def symbols_validate(root)
41
+ f = root.xpath("//definitions")
42
+ f.empty? && return
43
+ (f.size == 1 || @vocab) or
44
+ @log.add("Style", f.first, ONE_SYMBOLS_WARNING)
45
+ f.first.elements.each do |e|
46
+ unless %w(title dl).include? e.name
47
+ @log.add("Style", f.first, NON_DL_SYMBOLS_WARNING)
48
+ return
49
+ end
50
+ end
51
+ end
52
+
53
+ def seqcheck(names, msg, accepted)
54
+ n = names.shift
55
+ return [] if n.nil?
56
+
57
+ test = accepted.map { |a| n.at(a) }
58
+ if test.all?(&:nil?)
59
+ @log.add("Style", nil, msg)
60
+ end
61
+ names
62
+ end
63
+
64
+ def sections_presence_validate(root)
65
+ root.at("//sections/clause[@type = 'scope']") or
66
+ @log.add("Style", nil, "Scope clause missing")
67
+ root.at("//references[@normative = 'true']") or
68
+ @log.add("Style", nil, "Normative references missing")
69
+ root.at("//terms") or
70
+ @log.add("Style", nil, "Terms & definitions missing")
71
+ end
72
+
73
+ # spec of permissible section sequence
74
+ # we skip normative references, it goes to end of list
75
+ SEQ =
76
+ [
77
+ {
78
+ msg: "Initial section must be (content) Foreword",
79
+ val: ["./self::foreword"],
80
+ },
81
+ {
82
+ msg: "Prefatory material must be followed by (clause) Scope",
83
+ val: ["./self::introduction", "./self::clause[@type = 'scope']"],
84
+ },
85
+ {
86
+ msg: "Prefatory material must be followed by (clause) Scope",
87
+ val: ["./self::clause[@type = 'scope']"],
88
+ },
89
+ {
90
+ msg: "Normative References must be followed by "\
91
+ "Terms and Definitions",
92
+ val: ["./self::terms | .//terms"],
93
+ },
94
+ ].freeze
95
+
96
+ SECTIONS_XPATH =
97
+ "//foreword | //introduction | //sections/terms | .//annex | "\
98
+ "//sections/definitions | //sections/clause | "\
99
+ "//references[not(parent::clause)] | "\
100
+ "//clause[descendant::references][not(parent::clause)]".freeze
101
+
102
+ def sections_sequence_validate(root)
103
+ names, n = sections_sequence_validate_start(root)
104
+ if @vocab
105
+ names, n = sections_sequence_validate_body_vocab(names, n)
106
+ else
107
+ names, n = sections_sequence_validate_body(names, n)
108
+ end
109
+ sections_sequence_validate_end(names, n)
110
+ end
111
+
112
+ def sections_sequence_validate_start(root)
113
+ names = root.xpath(SECTIONS_XPATH)
114
+ names = seqcheck(names, SEQ[0][:msg], SEQ[0][:val])
115
+ n = names[0]
116
+ names = seqcheck(names, SEQ[1][:msg], SEQ[1][:val])
117
+ n&.at("./self::introduction") and
118
+ names = seqcheck(names, SEQ[2][:msg], SEQ[2][:val])
119
+ names = seqcheck(names, SEQ[3][:msg], SEQ[3][:val])
120
+ n = names.shift
121
+ n = names.shift if n&.at("./self::definitions")
122
+ [names, n]
123
+ end
124
+
125
+ def sections_sequence_validate_body(names, elem)
126
+ if elem.nil? || elem.name != "clause"
127
+ @log.add("Style", elem, "Document must contain at least one clause")
128
+ end
129
+ elem&.at("./self::clause") ||
130
+ @log.add("Style", elem, "Document must contain clause after "\
131
+ "Terms and Definitions")
132
+ elem&.at("./self::clause[@type = 'scope']") &&
133
+ @log.add("Style", elem,
134
+ "Scope must occur before Terms and Definitions")
135
+ elem = names.shift
136
+ while elem&.name == "clause"
137
+ elem&.at("./self::clause[@type = 'scope']")
138
+ @log.add("Style", elem,
139
+ "Scope must occur before Terms and Definitions")
140
+ elem = names.shift
141
+ end
142
+ %w(annex references).include? elem&.name or
143
+ @log.add("Style", elem,
144
+ "Only annexes and references can follow clauses")
145
+ [names, elem]
146
+ end
147
+
148
+ def sections_sequence_validate_body_vocab(names, elem)
149
+ while elem && %w(clause terms).include?(elem.name)
150
+ elem = names.shift
151
+ end
152
+ %w(annex references).include? elem&.name or
153
+ @log.add("Style", elem,
154
+ "Only annexes and references can follow terms and clauses")
155
+ [names, elem]
156
+ end
157
+
158
+ def sections_sequence_validate_end(names, elem)
159
+ while elem&.name == "annex"
160
+ elem = names.shift
161
+ if elem.nil?
162
+ @log.add("Style", nil, "Document must include (references) "\
163
+ "Normative References")
164
+ end
165
+ end
166
+ elem&.at("./self::references[@normative = 'true']") ||
167
+ @log.add("Style", nil, "Document must include (references) "\
168
+ "Normative References")
169
+ elem = names&.shift
170
+ elem&.at("./self::references[@normative = 'false']") ||
171
+ @log.add("Style", elem,
172
+ "Final section must be (references) Bibliography")
173
+ names.empty? ||
174
+ @log.add("Style", elem,
175
+ "There are sections after the final Bibliography")
176
+ end
177
+
178
+ NORM_ISO_WARN = "non-ISO/IEC reference not expected as normative".freeze
179
+ SCOPE_WARN = "Scope contains subclauses: should be succinct".freeze
180
+
181
+ def section_style(root)
182
+ foreword_style(root.at("//foreword"))
183
+ introduction_style(root.at("//introduction"))
184
+ scope_style(root.at("//clause[@type = 'scope']"))
185
+ scope = root.at("//clause[@type = 'scope']/clause")
186
+ # ISO/IEC DIR 2, 14.4
187
+ scope.nil? || style_warning(scope, SCOPE_WARN, nil)
188
+ tech_report_style(root)
189
+ end
190
+
191
+ def tech_report_style(root)
192
+ root.at("//bibdata/ext/doctype")&.text == "technical-report" or return
193
+ root.xpath("//sections/clause[not(@type = 'scope')] | //annex")
194
+ .each do |s|
195
+ r = requirement_check(extract_text(s)) and
196
+ style_warning(s,
197
+ "Technical Report clause may contain requirement", r)
198
+ end
199
+ end
200
+
201
+ ASSETS_TO_STYLE =
202
+ "//termsource | //formula | //termnote | "\
203
+ "//p[not(ancestor::boilerplate)] | //li[not(p)] | //dt | "\
204
+ "//dd[not(p)] | //td[not(p)] | //th[not(p)]".freeze
205
+
206
+ NORM_BIBITEMS =
207
+ "//references[@normative = 'true']/bibitem".freeze
208
+
209
+ # ISO/IEC DIR 2, 10.2
210
+ def norm_bibitem_style(root)
211
+ root.xpath(NORM_BIBITEMS).each do |b|
212
+ if b.at(Standoc::Converter::ISO_PUBLISHER_XPATH).nil?
213
+ @log.add("Style", b, "#{NORM_ISO_WARN}: #{b.text}")
214
+ end
215
+ end
216
+ end
217
+
218
+ def asset_style(root)
219
+ root.xpath("//example | //termexample").each { |e| example_style(e) }
220
+ root.xpath("//definition/verbal-definition").each { |e| definition_style(e) }
221
+ root.xpath("//note").each { |e| note_style(e) }
222
+ root.xpath("//fn").each { |e| footnote_style(e) }
223
+ root.xpath(ASSETS_TO_STYLE).each { |e| style(e, extract_text(e)) }
224
+ norm_bibitem_style(root)
225
+ super
226
+ end
227
+
228
+ def subclause_validate(root)
229
+ root.xpath("//clause/clause/clause/clause/clause/clause/clause/clause")
230
+ .each do |c|
231
+ style_warning(c, "Exceeds the maximum clause depth of 7", nil)
232
+ end
233
+ end
234
+
235
+ # ISO/IEC DIR 2, 22.3.2
236
+ def onlychild_clause_validate(root)
237
+ root.xpath(Standoc::Utils::SUBCLAUSE_XPATH).each do |c|
238
+ next unless c.xpath("../clause").size == 1
239
+
240
+ title = c.at("./title")
241
+ location = c["id"] || "#{c.text[0..60]}..."
242
+ location += ":#{title.text}" if c["id"] && !title.nil?
243
+ @log.add("Style", nil, "#{location}: subclause is only child")
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,170 @@
1
+ require "metanorma-standoc"
2
+ require "nokogiri"
3
+ require "tokenizer"
4
+
5
+ module Metanorma
6
+ module ISO
7
+ class Converter < Standoc::Converter
8
+ def extract_text(node)
9
+ return "" if node.nil?
10
+
11
+ node1 = Nokogiri::XML.fragment(node.to_s)
12
+ node1.xpath("//link | //locality | //localityStack").each(&:remove)
13
+ ret = ""
14
+ node1.traverse { |x| ret += x.text if x.text? }
15
+ HTMLEntities.new.decode(ret)
16
+ end
17
+
18
+ # ISO/IEC DIR 2, 12.2
19
+ def foreword_style(node)
20
+ return if @novalid
21
+
22
+ style_no_guidance(node, extract_text(node), "Foreword")
23
+ end
24
+
25
+ # ISO/IEC DIR 2, 14.2
26
+ def scope_style(node)
27
+ return if @novalid
28
+
29
+ style_no_guidance(node, extract_text(node), "Scope")
30
+ end
31
+
32
+ # ISO/IEC DIR 2, 13.2
33
+ def introduction_style(node)
34
+ return if @novalid
35
+
36
+ r = requirement_check(extract_text(node))
37
+ style_warning(node, "Introduction may contain requirement", r) if r
38
+ end
39
+
40
+ # ISO/IEC DIR 2, 16.5.6
41
+ def definition_style(node)
42
+ return if @novalid
43
+
44
+ r = requirement_check(extract_text(node))
45
+ style_warning(node, "Definition may contain requirement", r) if r
46
+ end
47
+
48
+ # ISO/IEC DIR 2, 16.5.7
49
+ # ISO/IEC DIR 2, 25.5
50
+ def example_style(node)
51
+ return if @novalid
52
+
53
+ style_no_guidance(node, extract_text(node), "Example")
54
+ style(node, extract_text(node))
55
+ end
56
+
57
+ # ISO/IEC DIR 2, 24.5
58
+ def note_style(node)
59
+ return if @novalid
60
+
61
+ style_no_guidance(node, extract_text(node), "Note")
62
+ style(node, extract_text(node))
63
+ end
64
+
65
+ # ISO/IEC DIR 2, 26.5
66
+ def footnote_style(node)
67
+ return if @novalid
68
+
69
+ style_no_guidance(node, extract_text(node), "Footnote")
70
+ style(node, extract_text(node))
71
+ end
72
+
73
+ def style_regex(regex, warning, n, text)
74
+ (m = regex.match(text)) && style_warning(n, warning, m[:num])
75
+ end
76
+
77
+ # style check with a regex on a token
78
+ # and a negative match on its preceding token
79
+ def style_two_regex_not_prev(n, text, regex, re_prev, warning)
80
+ return if text.nil?
81
+
82
+ arr = Tokenizer::WhitespaceTokenizer.new.tokenize(text)
83
+ arr.each_index do |i|
84
+ m = regex.match arr[i]
85
+ m_prev = i.zero? ? nil : re_prev.match(arr[i - 1])
86
+ if !m.nil? && m_prev.nil?
87
+ style_warning(n, warning, m[:num])
88
+ end
89
+ end
90
+ end
91
+
92
+ def style(node, text)
93
+ return if @novalid
94
+
95
+ style_number(node, text)
96
+ style_percent(node, text)
97
+ style_abbrev(node, text)
98
+ style_units(node, text)
99
+ end
100
+
101
+ # ISO/IEC DIR 2, 9.1
102
+ # ISO/IEC DIR 2, Table B.1
103
+ def style_number(node, text)
104
+ style_two_regex_not_prev(
105
+ node, text, /^(?<num>-?[0-9]{4,}[,0-9]*)\Z/,
106
+ %r{\b(ISO|IEC|IEEE/|(in|January|February|March|April|May|June|August|September|October|November|December)\b)\Z},
107
+ "number not broken up in threes"
108
+ )
109
+ style_regex(/\b(?<num>[0-9]+\.[0-9]+)/i,
110
+ "possible decimal point", node, text)
111
+ style_regex(/\b(?<num>billions?)\b/i,
112
+ "ambiguous number", node, text)
113
+ end
114
+
115
+ # ISO/IEC DIR 2, 9.2.1
116
+ def style_percent(node, text)
117
+ style_regex(/\b(?<num>[0-9.,]+%)/,
118
+ "no space before percent sign", node, text)
119
+ style_regex(/\b(?<num>[0-9.,]+ \u00b1 [0-9,.]+ %)/,
120
+ "unbracketed tolerance before percent sign", node, text)
121
+ end
122
+
123
+ # ISO/IEC DIR 2, 8.4
124
+ # ISO/IEC DIR 2, 9.3
125
+ def style_abbrev(node, text)
126
+ style_regex(/(\A|\s)(?!e\.g\.|i\.e\.)
127
+ (?<num>[a-z]{1,2}\.([a-z]{1,2}|\.))\b/ix,
128
+ "no dots in abbreviations", node, text)
129
+ style_regex(/\b(?<num>ppm)\b/i,
130
+ "language-specific abbreviation", node, text)
131
+ end
132
+
133
+ # leaving out as problematic: N J K C S T H h d B o E
134
+ SI_UNIT = "(m|cm|mm|km|μm|nm|g|kg|mgmol|cd|rad|sr|Hz|Hz|MHz|Pa|hPa|kJ|"\
135
+ "V|kV|W|MW|kW|F|μF|Ω|Wb|°C|lm|lx|Bq|Gy|Sv|kat|l|t|eV|u|Np|Bd|"\
136
+ "bit|kB|MB|Hart|nat|Sh|var)".freeze
137
+
138
+ # ISO/IEC DIR 2, 9.3
139
+ def style_units(node, text)
140
+ style_regex(/\b(?<num>[0-9][0-9,]*\s+[\u00b0\u2032\u2033])/,
141
+ "space between number and degrees/minutes/seconds",
142
+ node, text)
143
+ style_regex(/\b(?<num>[0-9][0-9,]*#{SI_UNIT})\b/o,
144
+ "no space between number and SI unit", node, text)
145
+ style_non_std_units(node, text)
146
+ end
147
+
148
+ NONSTD_UNITS = {
149
+ sec: "s", mins: "min", hrs: "h", hr: "h", cc: "cm^3",
150
+ lit: "l", amp: "A", amps: "A", rpm: "r/min"
151
+ }.freeze
152
+
153
+ # ISO/IEC DIR 2, 9.3
154
+ def style_non_std_units(node, text)
155
+ NONSTD_UNITS.each do |k, v|
156
+ style_regex(/\b(?<num>[0-9][0-9,]*\s+#{k})\b/,
157
+ "non-standard unit (should be #{v})", node, text)
158
+ end
159
+ end
160
+
161
+ def style_warning(node, msg, text = nil)
162
+ return if @novalid
163
+
164
+ w = msg
165
+ w += ": #{text}" if text
166
+ @log.add("Style", node, w)
167
+ end
168
+ end
169
+ end
170
+ end