metanorma-iso 1.8.0 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,97 @@
1
+ module Asciidoctor
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
@@ -1,6 +1,4 @@
1
1
  require "metanorma-standoc"
2
- require "nokogiri"
3
- require "pp"
4
2
 
5
3
  module Asciidoctor
6
4
  module ISO
@@ -19,13 +17,15 @@ module Asciidoctor
19
17
  [.,:;]_do_not )
20
18
  \\b
21
19
  REGEXP
22
- REQUIREMENT_RE =
23
- Regexp.new(REQUIREMENT_RE_STR.gsub(/\s/, "").gsub(/_/, "\\s"),
24
- Regexp::IGNORECASE)
20
+
21
+ def requirement_re
22
+ Regexp.new(self.class::REQUIREMENT_RE_STR.gsub(/\s/, "")
23
+ .gsub(/_/, "\\s"), Regexp::IGNORECASE)
24
+ end
25
25
 
26
26
  def requirement_check(text)
27
27
  text.split(/\.\s+/).each do |t|
28
- return t if REQUIREMENT_RE.match t
28
+ return t if requirement_re.match t
29
29
  end
30
30
  nil
31
31
  end
@@ -37,13 +37,15 @@ module Asciidoctor
37
37
  it_is_(not_)?recommended_that
38
38
  \\b
39
39
  REGEXP
40
- RECOMMENDATION_RE =
41
- Regexp.new(RECOMMENDATION_RE_STR.gsub(/\s/, "").gsub(/_/, "\\s"),
42
- Regexp::IGNORECASE)
40
+
41
+ def recommendation_re
42
+ Regexp.new(self.class::RECOMMENDATION_RE_STR.gsub(/\s/, "")
43
+ .gsub(/_/, "\\s"), Regexp::IGNORECASE)
44
+ end
43
45
 
44
46
  def recommendation_check(text)
45
47
  text.split(/\.\s+/).each do |t|
46
- return t if RECOMMENDATION_RE.match t
48
+ return t if recommendation_re.match t
47
49
  end
48
50
  nil
49
51
  end
@@ -56,13 +58,15 @@ module Asciidoctor
56
58
  no\\b[^.,]+\\b(is|are)_required
57
59
  \\b
58
60
  REGEXP
59
- PERMISSION_RE =
60
- Regexp.new(PERMISSION_RE_STR.gsub(/\s/, "").gsub(/_/, "\\s"),
61
- Regexp::IGNORECASE)
61
+
62
+ def permission_re
63
+ Regexp.new(self.class::PERMISSION_RE_STR.gsub(/\s/, "")
64
+ .gsub(/_/, "\\s"), Regexp::IGNORECASE)
65
+ end
62
66
 
63
67
  def permission_check(text)
64
68
  text.split(/\.\s+/).each do |t|
65
- return t if PERMISSION_RE.match t
69
+ return t if permission_re.match t
66
70
  end
67
71
  nil
68
72
  end
@@ -76,18 +80,20 @@ module Asciidoctor
76
80
  it_is_not_possible_to
77
81
  \\b
78
82
  REGEXP
79
- POSSIBILITY_RE =
80
- Regexp.new(POSSIBILITY_RE_STR.gsub(/\s/, "").gsub(/_/, "\\s"),
81
- Regexp::IGNORECASE)
82
83
 
83
- def possibility(text)
84
- text.split(/\.\s+/).each { |t| return t if POSSIBILITY_RE.match t }
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 }
85
91
  nil
86
92
  end
87
93
 
88
94
  def external_constraint(text)
89
95
  text.split(/\.\s+/).each do |t|
90
- return t if /\b(must)\b/xi.match t
96
+ return t if /\b(must)\b/xi.match? t
91
97
  end
92
98
  nil
93
99
  end
@@ -50,10 +50,11 @@ module Asciidoctor
50
50
  end
51
51
 
52
52
  def seqcheck(names, msg, accepted)
53
- n = names.shift
53
+ n = names.shift
54
54
  return [] if n.nil?
55
+
55
56
  test = accepted.map { |a| n.at(a) }
56
- if test.all? { |a| a.nil? }
57
+ if test.all?(&:nil?)
57
58
  @log.add("Style", nil, msg)
58
59
  end
59
60
  names
@@ -74,37 +75,38 @@ module Asciidoctor
74
75
  [
75
76
  {
76
77
  msg: "Initial section must be (content) Foreword",
77
- val: ["./self::foreword"]
78
+ val: ["./self::foreword"],
78
79
  },
79
80
  {
80
81
  msg: "Prefatory material must be followed by (clause) Scope",
81
- val: ["./self::introduction", "./self::clause[@type = 'scope']" ]
82
+ val: ["./self::introduction", "./self::clause[@type = 'scope']"],
82
83
  },
83
84
  {
84
85
  msg: "Prefatory material must be followed by (clause) Scope",
85
- val: ["./self::clause[@type = 'scope']" ]
86
+ val: ["./self::clause[@type = 'scope']"],
86
87
  },
87
88
  {
88
89
  msg: "Normative References must be followed by "\
89
90
  "Terms and Definitions",
90
- val: ["./self::terms | .//terms"]
91
+ val: ["./self::terms | .//terms"],
91
92
  },
92
- ].freeze
93
+ ].freeze
93
94
 
94
95
  SECTIONS_XPATH =
95
96
  "//foreword | //introduction | //sections/terms | .//annex | "\
96
- "//sections/definitions | //sections/clause | //references[not(parent::clause)] | "\
97
+ "//sections/definitions | //sections/clause | "\
98
+ "//references[not(parent::clause)] | "\
97
99
  "//clause[descendant::references][not(parent::clause)]".freeze
98
100
 
99
101
  def sections_sequence_validate(root)
100
102
  names = root.xpath(SECTIONS_XPATH)
101
- names = seqcheck(names, SEQ[0][:msg], SEQ[0][:val])
103
+ names = seqcheck(names, SEQ[0][:msg], SEQ[0][:val])
102
104
  n = names[0]
103
- names = seqcheck(names, SEQ[1][:msg], SEQ[1][:val])
105
+ names = seqcheck(names, SEQ[1][:msg], SEQ[1][:val])
104
106
  if n&.at("./self::introduction")
105
- names = seqcheck(names, SEQ[2][:msg], SEQ[2][:val])
107
+ names = seqcheck(names, SEQ[2][:msg], SEQ[2][:val])
106
108
  end
107
- names = seqcheck(names, SEQ[3][:msg], SEQ[3][:val])
109
+ names = seqcheck(names, SEQ[3][:msg], SEQ[3][:val])
108
110
  n = names.shift
109
111
  if n&.at("./self::definitions")
110
112
  n = names.shift
@@ -117,11 +119,11 @@ module Asciidoctor
117
119
  "Terms and Definitions")
118
120
  n&.at("./self::clause[@type = 'scope']") &&
119
121
  @log.add("Style", nil, "Scope must occur before Terms and Definitions")
120
- n = names.shift
122
+ n = names.shift
121
123
  while n&.name == "clause"
122
124
  n&.at("./self::clause[@type = 'scope']")
123
125
  @log.add("Style", nil, "Scope must occur before Terms and Definitions")
124
- n = names.shift
126
+ n = names.shift
125
127
  end
126
128
  unless %w(annex references).include? n&.name
127
129
  @log.add("Style", nil, "Only annexes and references can follow clauses")
@@ -145,6 +147,7 @@ module Asciidoctor
145
147
 
146
148
  def style_warning(node, msg, text = nil)
147
149
  return if @novalid
150
+
148
151
  w = msg
149
152
  w += ": #{text}" if text
150
153
  @log.add("Style", node, w)
@@ -165,15 +168,18 @@ module Asciidoctor
165
168
 
166
169
  def tech_report_style(root)
167
170
  root.at("//bibdata/ext/doctype")&.text == "technical-report" or return
168
- root.xpath("//sections/clause[not(@type = 'scope')] | //annex").each do |s|
169
- r = requirement_check(extract_text(s))
170
- style_warning(s, "Technical Report clause may contain requirement", r) if r
171
+ root.xpath("//sections/clause[not(@type = 'scope')] | //annex")
172
+ .each do |s|
173
+ r = requirement_check(extract_text(s)) and
174
+ style_warning(s,
175
+ "Technical Report clause may contain requirement", r)
171
176
  end
172
177
  end
173
178
 
174
179
  ASSETS_TO_STYLE =
175
- "//termsource | //formula | //termnote | //p[not(ancestor::boilerplate)] | "\
176
- "//li[not(p)] | //dt | //dd[not(p)] | //td[not(p)] | //th[not(p)]".freeze
180
+ "//termsource | //formula | //termnote | "\
181
+ "//p[not(ancestor::boilerplate)] | //li[not(p)] | //dt | "\
182
+ "//dd[not(p)] | //td[not(p)] | //th[not(p)]".freeze
177
183
 
178
184
  NORM_BIBITEMS =
179
185
  "//references[@normative = 'true']/bibitem".freeze
@@ -198,10 +204,23 @@ module Asciidoctor
198
204
  end
199
205
 
200
206
  def subclause_validate(root)
201
- root.xpath("//clause/clause/clause/clause/clause/clause/clause/clause").each do |c|
207
+ root.xpath("//clause/clause/clause/clause/clause/clause/clause/clause")
208
+ .each do |c|
202
209
  style_warning(c, "Exceeds the maximum clause depth of 7", nil)
203
210
  end
204
211
  end
212
+
213
+ # ISO/IEC DIR 2, 22.3.2
214
+ def onlychild_clause_validate(root)
215
+ root.xpath(Standoc::Utils::SUBCLAUSE_XPATH).each do |c|
216
+ next unless c.xpath("../clause").size == 1
217
+
218
+ title = c.at("./title")
219
+ location = c["id"] || "#{c.text[0..60]}..."
220
+ location += ":#{title.text}" if c["id"] && !title.nil?
221
+ @log.add("Style", nil, "#{location}: subclause is only child")
222
+ end
223
+ end
205
224
  end
206
225
  end
207
226
  end
@@ -7,28 +7,32 @@ module Asciidoctor
7
7
  class Converter < Standoc::Converter
8
8
  def extract_text(node)
9
9
  return "" if node.nil?
10
+
10
11
  node1 = Nokogiri::XML.fragment(node.to_s)
11
12
  node1.xpath("//link | //locality | //localityStack").each(&:remove)
12
13
  ret = ""
13
14
  node1.traverse { |x| ret += x.text if x.text? }
14
- ret
15
+ HTMLEntities.new.decode(ret)
15
16
  end
16
17
 
17
18
  # ISO/IEC DIR 2, 12.2
18
19
  def foreword_style(node)
19
20
  return if @novalid
21
+
20
22
  style_no_guidance(node, extract_text(node), "Foreword")
21
23
  end
22
24
 
23
25
  # ISO/IEC DIR 2, 14.2
24
26
  def scope_style(node)
25
27
  return if @novalid
28
+
26
29
  style_no_guidance(node, extract_text(node), "Scope")
27
30
  end
28
31
 
29
32
  # ISO/IEC DIR 2, 13.2
30
33
  def introduction_style(node)
31
34
  return if @novalid
35
+
32
36
  r = requirement_check(extract_text(node))
33
37
  style_warning(node, "Introduction may contain requirement", r) if r
34
38
  end
@@ -36,6 +40,7 @@ module Asciidoctor
36
40
  # ISO/IEC DIR 2, 16.5.6
37
41
  def definition_style(node)
38
42
  return if @novalid
43
+
39
44
  r = requirement_check(extract_text(node))
40
45
  style_warning(node, "Definition may contain requirement", r) if r
41
46
  end
@@ -44,6 +49,7 @@ module Asciidoctor
44
49
  # ISO/IEC DIR 2, 25.5
45
50
  def example_style(node)
46
51
  return if @novalid
52
+
47
53
  style_no_guidance(node, extract_text(node), "Example")
48
54
  style(node, extract_text(node))
49
55
  end
@@ -51,6 +57,7 @@ module Asciidoctor
51
57
  # ISO/IEC DIR 2, 24.5
52
58
  def note_style(node)
53
59
  return if @novalid
60
+
54
61
  style_no_guidance(node, extract_text(node), "Note")
55
62
  style(node, extract_text(node))
56
63
  end
@@ -58,6 +65,7 @@ module Asciidoctor
58
65
  # ISO/IEC DIR 2, 26.5
59
66
  def footnote_style(node)
60
67
  return if @novalid
68
+
61
69
  style_no_guidance(node, extract_text(node), "Footnote")
62
70
  style(node, extract_text(node))
63
71
  end
@@ -70,6 +78,7 @@ module Asciidoctor
70
78
  # and a negative match on its preceding token
71
79
  def style_two_regex_not_prev(n, text, re, re_prev, warning)
72
80
  return if text.nil?
81
+
73
82
  arr = Tokenizer::WhitespaceTokenizer.new.tokenize(text)
74
83
  arr.each_index do |i|
75
84
  m = re.match arr[i]
@@ -80,43 +89,45 @@ module Asciidoctor
80
89
  end
81
90
  end
82
91
 
83
- def style(n, t)
92
+ def style(node, text)
84
93
  return if @novalid
85
- style_number(n, t)
86
- style_percent(n, t)
87
- style_abbrev(n, t)
88
- style_units(n, t)
94
+
95
+ style_number(node, text)
96
+ style_percent(node, text)
97
+ style_abbrev(node, text)
98
+ style_units(node, text)
89
99
  end
90
100
 
91
101
  # ISO/IEC DIR 2, 9.1
92
102
  # ISO/IEC DIR 2, Table B.1
93
- def style_number(n, t)
103
+ def style_number(node, text)
94
104
  style_two_regex_not_prev(
95
- n, t, /^(?<num>-?[0-9]{4,}[,0-9]*)$/,
105
+ node, text, /^(?<num>-?[0-9]{4,}[,0-9]*)$/,
96
106
  %r{\b(ISO|IEC|IEEE/|(in|January|February|March|April|May|June|August|September|October|November|December)\b)$},
97
- "number not broken up in threes")
107
+ "number not broken up in threes"
108
+ )
98
109
  style_regex(/\b(?<num>[0-9]+\.[0-9]+)/i,
99
- "possible decimal point", n, t)
100
- style_regex(/\b(?<num>billion[s]?)\b/i,
101
- "ambiguous number", n, t)
110
+ "possible decimal point", node, text)
111
+ style_regex(/\b(?<num>billions?)\b/i,
112
+ "ambiguous number", node, text)
102
113
  end
103
114
 
104
115
  # ISO/IEC DIR 2, 9.2.1
105
- def style_percent(n, t)
116
+ def style_percent(node, text)
106
117
  style_regex(/\b(?<num>[0-9.,]+%)/,
107
- "no space before percent sign", n, t)
118
+ "no space before percent sign", node, text)
108
119
  style_regex(/\b(?<num>[0-9.,]+ \u00b1 [0-9,.]+ %)/,
109
- "unbracketed tolerance before percent sign", n, t)
120
+ "unbracketed tolerance before percent sign", node, text)
110
121
  end
111
122
 
112
123
  # ISO/IEC DIR 2, 8.4
113
124
  # ISO/IEC DIR 2, 9.3
114
- def style_abbrev(n, t)
125
+ def style_abbrev(node, text)
115
126
  style_regex(/(^|\s)(?!e\.g\.|i\.e\.)
116
127
  (?<num>[a-z]{1,2}\.([a-z]{1,2}|\.))\b/ix,
117
- "no dots in abbreviations", n, t)
128
+ "no dots in abbreviations", node, text)
118
129
  style_regex(/\b(?<num>ppm)\b/i,
119
- "language-specific abbreviation", n, t)
130
+ "language-specific abbreviation", node, text)
120
131
  end
121
132
 
122
133
  # leaving out as problematic: N J K C S T H h d B o E
@@ -125,12 +136,13 @@ module Asciidoctor
125
136
  "bit|kB|MB|Hart|nat|Sh|var)".freeze
126
137
 
127
138
  # ISO/IEC DIR 2, 9.3
128
- def style_units(n, t)
139
+ def style_units(node, text)
129
140
  style_regex(/\b(?<num>[0-9][0-9,]*\s+[\u00b0\u2032\u2033])/,
130
- "space between number and degrees/minutes/seconds", n, t)
141
+ "space between number and degrees/minutes/seconds",
142
+ node, text)
131
143
  style_regex(/\b(?<num>[0-9][0-9,]*#{SI_UNIT})\b/,
132
- "no space between number and SI unit", n, t)
133
- style_non_std_units(n, t)
144
+ "no space between number and SI unit", node, text)
145
+ style_non_std_units(node, text)
134
146
  end
135
147
 
136
148
  NONSTD_UNITS = {
@@ -139,10 +151,10 @@ module Asciidoctor
139
151
  }.freeze
140
152
 
141
153
  # ISO/IEC DIR 2, 9.3
142
- def style_non_std_units(n, t)
154
+ def style_non_std_units(node, text)
143
155
  NONSTD_UNITS.each do |k, v|
144
156
  style_regex(/\b(?<num>[0-9][0-9,]*\s+#{k})\b/,
145
- "non-standard unit (should be #{v})", n, t)
157
+ "non-standard unit (should be #{v})", node, text)
146
158
  end
147
159
  end
148
160
  end