metanorma-iso 1.7.2 → 1.8.2

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +11 -41
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +7 -1
  5. data/lib/asciidoctor/iso/base.rb +14 -11
  6. data/lib/asciidoctor/iso/biblio.rng +4 -6
  7. data/lib/asciidoctor/iso/cleanup.rb +40 -24
  8. data/lib/asciidoctor/iso/front.rb +29 -17
  9. data/lib/asciidoctor/iso/front_id.rb +81 -60
  10. data/lib/asciidoctor/iso/isodoc.rng +327 -2
  11. data/lib/asciidoctor/iso/isostandard.rng +12 -97
  12. data/lib/asciidoctor/iso/section.rb +2 -1
  13. data/lib/asciidoctor/iso/validate.rb +22 -109
  14. data/lib/asciidoctor/iso/validate_image.rb +97 -0
  15. data/lib/asciidoctor/iso/validate_requirements.rb +26 -20
  16. data/lib/asciidoctor/iso/validate_section.rb +39 -20
  17. data/lib/asciidoctor/iso/validate_style.rb +36 -24
  18. data/lib/asciidoctor/iso/validate_title.rb +23 -17
  19. data/lib/isodoc/iso/base_convert.rb +19 -2
  20. data/lib/isodoc/iso/html/style-human.css +7 -0
  21. data/lib/isodoc/iso/html/style-iso.css +7 -0
  22. data/lib/isodoc/iso/html_convert.rb +0 -1
  23. data/lib/isodoc/iso/i18n-en.yaml +4 -0
  24. data/lib/isodoc/iso/i18n-fr.yaml +4 -0
  25. data/lib/isodoc/iso/i18n-zh-Hans.yaml +4 -0
  26. data/lib/isodoc/iso/index.rb +140 -0
  27. data/lib/isodoc/iso/iso.amendment.xsl +1157 -208
  28. data/lib/isodoc/iso/iso.international-standard.xsl +1157 -208
  29. data/lib/isodoc/iso/metadata.rb +1 -0
  30. data/lib/isodoc/iso/presentation_xml_convert.rb +45 -37
  31. data/lib/isodoc/iso/sts_convert.rb +10 -13
  32. data/lib/isodoc/iso/word_convert.rb +0 -1
  33. data/lib/isodoc/iso/xref.rb +46 -25
  34. data/lib/metanorma/iso/processor.rb +1 -0
  35. data/lib/metanorma/iso/version.rb +1 -1
  36. data/metanorma-iso.gemspec +8 -8
  37. data/spec/{asciidoctor-iso → asciidoctor}/amd_spec.rb +5 -5
  38. data/spec/asciidoctor/base_spec.rb +825 -0
  39. data/spec/{asciidoctor-iso → asciidoctor}/blocks_spec.rb +0 -0
  40. data/spec/{asciidoctor-iso → asciidoctor}/cleanup_spec.rb +383 -25
  41. data/spec/{asciidoctor-iso → asciidoctor}/inline_spec.rb +0 -0
  42. data/spec/{asciidoctor-iso → asciidoctor}/lists_spec.rb +0 -0
  43. data/spec/{asciidoctor-iso → asciidoctor}/refs_spec.rb +0 -0
  44. data/spec/{asciidoctor-iso → asciidoctor}/section_spec.rb +0 -14
  45. data/spec/{asciidoctor-iso → asciidoctor}/table_spec.rb +0 -0
  46. data/spec/{asciidoctor-iso → asciidoctor}/validate_spec.rb +188 -83
  47. data/spec/isodoc/postproc_spec.rb +481 -438
  48. data/spec/isodoc/section_spec.rb +219 -0
  49. data/spec/spec_helper.rb +2 -0
  50. metadata +65 -64
  51. data/lib/isodoc/iso/html/scripts.html +0 -178
  52. data/spec/asciidoctor-iso/base_spec.rb +0 -704
@@ -10,7 +10,7 @@ module Asciidoctor
10
10
  end
11
11
 
12
12
  def scope_parse(attrs, xml, node)
13
- attrs = attrs.merge(type: "scope") unless @amd
13
+ attrs = attrs.merge(type: "scope") unless @amd
14
14
  clause_parse(attrs, xml, node)
15
15
  end
16
16
 
@@ -32,6 +32,7 @@ module Asciidoctor
32
32
 
33
33
  def sectiontype(node, level = true)
34
34
  return nil if @amd
35
+
35
36
  super
36
37
  end
37
38
  end
@@ -1,8 +1,9 @@
1
1
  require "metanorma-standoc"
2
- require_relative "./validate_style.rb"
3
- require_relative "./validate_requirements.rb"
4
- require_relative "./validate_section.rb"
5
- require_relative "./validate_title.rb"
2
+ require_relative "./validate_style"
3
+ require_relative "./validate_requirements"
4
+ require_relative "./validate_section"
5
+ require_relative "./validate_title"
6
+ require_relative "./validate_image"
6
7
  require "nokogiri"
7
8
  require "jing"
8
9
  require "iev"
@@ -10,17 +11,6 @@ require "iev"
10
11
  module Asciidoctor
11
12
  module ISO
12
13
  class Converter < Standoc::Converter
13
- # ISO/IEC DIR 2, 22.3.2
14
- def onlychild_clause_validate(root)
15
- root.xpath(Standoc::Utils::SUBCLAUSE_XPATH).each do |c|
16
- next unless c.xpath("../clause").size == 1
17
- title = c.at("./title")
18
- location = c["id"] || c.text[0..60] + "..."
19
- location += ":#{title.text}" if c["id"] && !title.nil?
20
- @log.add("Style", nil, "#{location}: subclause is only child")
21
- end
22
- end
23
-
24
14
  def isosubgroup_validate(root)
25
15
  root.xpath("//technical-committee/@type").each do |t|
26
16
  unless %w{TC PC JTC JPC}.include? t.text
@@ -37,12 +27,13 @@ module Asciidoctor
37
27
  end
38
28
 
39
29
  # ISO/IEC DIR 2, 15.5.3
30
+ # does not deal with preceding text marked up
40
31
  def see_xrefs_validate(root)
41
32
  root.xpath("//xref").each do |t|
42
- # does not deal with preceding text marked up
43
33
  preceding = t.at("./preceding-sibling::text()[last()]")
44
34
  next unless !preceding.nil? &&
45
35
  /\b(see| refer to)\s*$/mi.match(preceding)
36
+
46
37
  (target = root.at("//*[@id = '#{t['target']}']")) || next
47
38
  if target&.at("./ancestor-or-self::*[@obligation = 'normative']")
48
39
  @log.add("Style", t,
@@ -54,36 +45,34 @@ module Asciidoctor
54
45
  # ISO/IEC DIR 2, 15.5.3
55
46
  def see_erefs_validate(root)
56
47
  root.xpath("//eref").each do |t|
57
- preceding = t.at("./preceding-sibling::text()[last()]")
58
- next unless !preceding.nil? &&
59
- /\b(see|refer to)\s*$/mi.match(preceding)
48
+ prec = t.at("./preceding-sibling::text()[last()]")
49
+ next unless !prec.nil? && /\b(see|refer to)\s*$/mi.match(prec)
50
+
60
51
  unless target = root.at("//*[@id = '#{t['bibitemid']}']")
61
52
  @log.add("Bibliography", t,
62
53
  "'#{t} is not pointing to a real reference")
63
54
  next
64
55
  end
65
- if target.at("./ancestor::references[@normative = 'true']")
56
+ target.at("./ancestor::references[@normative = 'true']") and
66
57
  @log.add("Style", t,
67
58
  "'see #{t}' is pointing to a normative reference")
68
- end
69
59
  end
70
60
  end
71
61
 
72
62
  # ISO/IEC DIR 2, 10.4
73
63
  def locality_erefs_validate(root)
74
64
  root.xpath("//eref[descendant::locality]").each do |t|
75
- if /^(ISO|IEC)/.match t["citeas"]
76
- unless /:[ ]?(\d+{4}|–)$/.match t["citeas"]
77
- @log.add("Style", t,
78
- "undated reference #{t['citeas']} should not contain "\
79
- "specific elements")
80
- end
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")
81
70
  end
82
71
  end
83
72
  end
84
73
 
85
- def termdef_warn(text, re, t, term, msg)
86
- re.match(text) && @log.add("Style", t, "#{term}: #{msg}")
74
+ def termdef_warn(text, regex, elem, term, msg)
75
+ regex.match(text) && @log.add("Style", elem, "#{term}: #{msg}")
87
76
  end
88
77
 
89
78
  # ISO/IEC DIR 2, 16.5.6
@@ -103,6 +92,7 @@ module Asciidoctor
103
92
  def cited_term_style(xmldoc)
104
93
  xmldoc.xpath("//term//xref").each do |x|
105
94
  next unless xmldoc.at("//term[@id = '#{x['target']}']")
95
+
106
96
  x&.previous&.text == " (" and x&.previous&.previous&.name == "em" or
107
97
  style_warning(x, "term citation not preceded with italicised term",
108
98
  x.parent.text)
@@ -111,8 +101,8 @@ module Asciidoctor
111
101
 
112
102
  def doctype_validate(xmldoc)
113
103
  doctype = xmldoc&.at("//bibdata/ext/doctype")&.text
114
- %w(international-standard technical-specification technical-report
115
- publicly-available-specification international-workshop-agreement
104
+ %w(international-standard technical-specification technical-report
105
+ publicly-available-specification international-workshop-agreement
116
106
  guide amendment technical-corrigendum).include? doctype or
117
107
  @log.add("Document Attributes", nil,
118
108
  "#{doctype} is not a recognised document type")
@@ -146,83 +136,6 @@ module Asciidoctor
146
136
  "#{iteration} is not a recognised iteration")
147
137
  end
148
138
 
149
- # DRG directives 3.7; but anticipated by standoc
150
- def subfigure_validate(xmldoc)
151
- xmldoc.xpath("//figure//figure").each do |f|
152
- { footnote: "fn", note: "note", key: "dl" }.each do |k, v|
153
- f.xpath(".//#{v}").each do |n|
154
- @log.add("Style", n, "#{k} is not permitted in a subfigure")
155
- end
156
- end
157
- end
158
- end
159
-
160
- def image_name_prefix(xmldoc)
161
- std = xmldoc&.at("//bibdata/ext/structuredidentifier/project-number") or return
162
- num = xmldoc&.at("//bibdata/docnumber")&.text or return
163
- ed = xmldoc&.at("//bibdata/edition")&.text || "1"
164
- prefix = num
165
- part = std["part"] and prefix += "-#{std['part']}"
166
- prefix += "_ed#{ed}"
167
- amd = std["amendment"] and prefix += "amd#{amd}"
168
- prefix
169
- end
170
-
171
- def image_name_suffix(xmldoc)
172
- case xmldoc&.at("//bibdata/language")&.text
173
- when "fr" then "_f"
174
- when "de" then "_d"
175
- when "ru" then "_r"
176
- when "es" then "_s"
177
- when "ar" then "_a"
178
- when "en" then "_e"
179
- else
180
- "_e"
181
- end
182
- end
183
-
184
- def disjunct_error(i, cond1, cond2, msg1, msg2)
185
- cond1 && !cond2 and @log.add("Style", i, "image name #{i['src']} #{msg1}")
186
- !cond1 && cond2 and @log.add("Style", i, "image name #{i['src']} #{msg2}")
187
- end
188
-
189
- def image_name_validate1(i, prefix)
190
- m = %r[(SL)?#{prefix}fig(?<tab>Tab)?(?<annex>[A-Z])?(Text)?(?<num>\d+)
191
- (?<subfig>[a-z])?(?<key>_key\d+)?(?<lang>_[a-z])?$]x.match(File.basename(i["src"], ".*"))
192
- if m.nil?
193
- @log.add("Style", i, "image name #{i['src']} does not match DRG requirements")
194
- return
195
- end
196
- warn i['src']
197
- disjunct_error(i, i.at("./ancestor::table"), !m[:tab].nil?,
198
- "is under a table but is not so labelled", "is labelled as under a table but is not")
199
- disjunct_error(i, i.at("./ancestor::annex"), !m[:annex].nil?,
200
- "is under an annex but is not so labelled", "is labelled as under an annex but is not")
201
- disjunct_error(i, i.xpath("./ancestor::figure").size > 1, !m[:subfig].nil?,
202
- "does not have a subfigure letter but is a subfigure",
203
- "has a subfigure letter but is not a subfigure")
204
- lang = image_name_suffix(i.document.root)
205
- (m[:lang] || "_e") == lang or @log.add("Style", i, "image name #{i['src']} expected to have suffix #{lang}")
206
- end
207
-
208
- # DRG directives 3.2
209
- def image_name_validate(xmldoc)
210
- prefix = image_name_prefix(xmldoc) or return
211
- xmldoc.xpath("//image").each do |i|
212
- if /^ISO_\d+_/.match(File.basename(i["src"]))
213
- elsif /^(SL)?#{prefix}fig/.match(File.basename(i["src"]))
214
- image_name_validate1(i, prefix)
215
- else
216
- @log.add("Style", i, "image name #{i['src']} does not match DRG requirements: expect #{prefix}fig")
217
- end
218
- end
219
- end
220
-
221
- def figure_validate(xmldoc)
222
- image_name_validate(xmldoc)
223
- subfigure_validate(xmldoc)
224
- end
225
-
226
139
  def bibdata_validate(doc)
227
140
  doctype_validate(doc)
228
141
  script_validate(doc)
@@ -250,7 +163,7 @@ module Asciidoctor
250
163
  xmldoc.xpath("//bibitem[date/on = '–']").each do |b|
251
164
  b.at("./note[@type = 'Unpublished-Status']") or
252
165
  @log.add("Style", b,
253
- "Reference #{b&.at("./@id")&.text} does not have an "\
166
+ "Reference #{b&.at('./@id')&.text} does not have an "\
254
167
  "associated footnote indicating unpublished status")
255
168
  end
256
169
  end
@@ -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