metanorma-iso 1.10.6 → 2.0.0

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 (62) 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 +1327 -0
  19. data/lib/isodoc/iso/html/style-human.css +1010 -0
  20. data/lib/isodoc/iso/html/style-iso.css +1036 -0
  21. data/lib/isodoc/iso/html/wordstyle.css +1701 -0
  22. data/lib/isodoc/iso/html_convert.rb +6 -4
  23. data/lib/isodoc/iso/iso.amendment.xsl +96 -154
  24. data/lib/isodoc/iso/iso.international-standard.xsl +96 -154
  25. data/lib/metanorma/iso/base.rb +70 -0
  26. data/lib/{asciidoctor → metanorma}/iso/basicdoc.rng +0 -0
  27. data/lib/{asciidoctor → metanorma}/iso/biblio.rng +0 -0
  28. data/lib/{asciidoctor → metanorma}/iso/boilerplate-fr.xml +0 -0
  29. data/lib/{asciidoctor → metanorma}/iso/boilerplate.xml +0 -0
  30. data/lib/metanorma/iso/cleanup.rb +176 -0
  31. data/lib/metanorma/iso/converter.rb +18 -0
  32. data/lib/metanorma/iso/front.rb +170 -0
  33. data/lib/metanorma/iso/front_id.rb +225 -0
  34. data/lib/{asciidoctor → metanorma}/iso/isodoc.rng +29 -0
  35. data/lib/{asciidoctor → metanorma}/iso/isostandard-amd.rng +0 -0
  36. data/lib/{asciidoctor → metanorma}/iso/isostandard.rnc +0 -0
  37. data/lib/{asciidoctor → metanorma}/iso/isostandard.rng +0 -0
  38. data/lib/{asciidoctor → metanorma}/iso/reqt.rng +0 -0
  39. data/lib/metanorma/iso/section.rb +49 -0
  40. data/lib/metanorma/iso/validate.rb +172 -0
  41. data/lib/metanorma/iso/validate_image.rb +97 -0
  42. data/lib/metanorma/iso/validate_requirements.rb +111 -0
  43. data/lib/metanorma/iso/validate_section.rb +247 -0
  44. data/lib/metanorma/iso/validate_style.rb +170 -0
  45. data/lib/metanorma/iso/validate_title.rb +105 -0
  46. data/lib/metanorma/iso/version.rb +1 -1
  47. data/lib/metanorma-iso.rb +1 -1
  48. data/metanorma-iso.gemspec +1 -1
  49. data/spec/isodoc/ref_spec.rb +4 -2
  50. data/spec/{asciidoctor → metanorma}/amd_spec.rb +1 -1
  51. data/spec/{asciidoctor → metanorma}/base_spec.rb +1 -1
  52. data/spec/{asciidoctor → metanorma}/blank_spec.rb +1 -1
  53. data/spec/{asciidoctor → metanorma}/blocks_spec.rb +1 -1
  54. data/spec/{asciidoctor → metanorma}/cleanup_spec.rb +1 -1
  55. data/spec/{asciidoctor → metanorma}/inline_spec.rb +1 -1
  56. data/spec/{asciidoctor → metanorma}/lists_spec.rb +1 -1
  57. data/spec/{asciidoctor → metanorma}/refs_spec.rb +1 -1
  58. data/spec/{asciidoctor → metanorma}/section_spec.rb +1 -1
  59. data/spec/{asciidoctor → metanorma}/table_spec.rb +1 -1
  60. data/spec/{asciidoctor → metanorma}/validate_spec.rb +1 -1
  61. data/spec/spec_helper.rb +1 -1
  62. metadata +46 -28
@@ -0,0 +1,49 @@
1
+ require "htmlentities"
2
+ require "uri" if /^2\./.match?(RUBY_VERSION)
3
+
4
+ module Metanorma
5
+ module ISO
6
+ class Converter < Standoc::Converter
7
+ def clause_parse(attrs, xml, node)
8
+ node.option? "appendix" and return appendix_parse(attrs, xml, node)
9
+ super
10
+ end
11
+
12
+ def scope_parse(attrs, xml, node)
13
+ attrs = attrs.merge(type: "scope") unless @amd
14
+ clause_parse(attrs, xml, node)
15
+ end
16
+
17
+ def appendix_parse(attrs, xml, node)
18
+ attrs[:"inline-header"] = node.option? "inline-header"
19
+ set_obligation(attrs, node)
20
+ xml.appendix **attr_code(attrs) do |xml_section|
21
+ xml_section.title { |name| name << node.title }
22
+ xml_section << node.content
23
+ end
24
+ end
25
+
26
+ def patent_notice_parse(xml, node)
27
+ # xml.patent_notice do |xml_section|
28
+ # xml_section << node.content
29
+ # end
30
+ xml << node.content
31
+ end
32
+
33
+ def sectiontype(node, level = true)
34
+ return nil if @amd
35
+
36
+ ret = sectiontype_streamline(sectiontype1(node))
37
+ return ret if ret == "terms and definitions" && @vocab
38
+
39
+ super
40
+ end
41
+
42
+ def term_def_subclause_parse(attrs, xml, node)
43
+ node.role == "term" and
44
+ return term_def_subclause_parse1(attrs, xml, node)
45
+ super
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,172 @@
1
+ require "metanorma-standoc"
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"
7
+ require "nokogiri"
8
+ require "jing"
9
+ require "iev"
10
+
11
+ module Metanorma
12
+ module ISO
13
+ class Converter < Standoc::Converter
14
+ def isosubgroup_validate(root)
15
+ root.xpath("//technical-committee/@type").each do |t|
16
+ unless %w{TC PC JTC JPC}.include? t.text
17
+ @log.add("Document Attributes", nil,
18
+ "invalid technical committee type #{t}")
19
+ end
20
+ end
21
+ root.xpath("//subcommittee/@type").each do |t|
22
+ unless %w{SC JSC}.include? t.text
23
+ @log.add("Document Attributes", nil,
24
+ "invalid subcommittee type #{t}")
25
+ end
26
+ end
27
+ end
28
+
29
+ # ISO/IEC DIR 2, 15.5.3
30
+ # does not deal with preceding text marked up
31
+ def see_xrefs_validate(root)
32
+ root.xpath("//xref").each do |t|
33
+ preceding = t.at("./preceding-sibling::text()[last()]")
34
+ next unless !preceding.nil? &&
35
+ /\b(see| refer to)\s*\Z/mi.match(preceding)
36
+
37
+ (target = root.at("//*[@id = '#{t['target']}']")) || next
38
+ if target&.at("./ancestor-or-self::*[@obligation = 'normative']")
39
+ @log.add("Style", t,
40
+ "'see #{t['target']}' is pointing to a normative section")
41
+ end
42
+ end
43
+ end
44
+
45
+ # ISO/IEC DIR 2, 15.5.3
46
+ def see_erefs_validate(root)
47
+ root.xpath("//eref").each do |t|
48
+ prec = t.at("./preceding-sibling::text()[last()]")
49
+ next unless !prec.nil? && /\b(see|refer to)\s*\Z/mi.match(prec)
50
+
51
+ unless target = root.at("//*[@id = '#{t['bibitemid']}']")
52
+ @log.add("Bibliography", t,
53
+ "'#{t} is not pointing to a real reference")
54
+ next
55
+ end
56
+ target.at("./ancestor::references[@normative = 'true']") and
57
+ @log.add("Style", t,
58
+ "'see #{t}' is pointing to a normative reference")
59
+ end
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
+ def termdef_warn(text, regex, elem, term, msg)
75
+ regex.match(text) && @log.add("Style", elem, "#{term}: #{msg}")
76
+ end
77
+
78
+ # ISO/IEC DIR 2, 16.5.6
79
+ def termdef_style(xmldoc)
80
+ xmldoc.xpath("//term").each do |t|
81
+ para = t.at("./definition/verbal-definition") || return
82
+ term = t.at("./preferred//name").text
83
+ termdef_warn(para.text, /\A(the|a)\b/i, t, term,
84
+ "term definition starts with article")
85
+ termdef_warn(para.text, /\.\Z/i, t, term,
86
+ "term definition ends with period")
87
+ end
88
+ end
89
+
90
+ def doctype_validate(xmldoc)
91
+ doctype = xmldoc&.at("//bibdata/ext/doctype")&.text
92
+ %w(international-standard technical-specification technical-report
93
+ publicly-available-specification international-workshop-agreement
94
+ guide amendment technical-corrigendum).include? doctype or
95
+ @log.add("Document Attributes", nil,
96
+ "#{doctype} is not a recognised document type")
97
+ end
98
+
99
+ def script_validate(xmldoc)
100
+ script = xmldoc&.at("//bibdata/script")&.text
101
+ script == "Latn" or
102
+ @log.add("Document Attributes", nil,
103
+ "#{script} is not a recognised script")
104
+ end
105
+
106
+ def stage_validate(xmldoc)
107
+ stage = xmldoc&.at("//bibdata/status/stage")&.text
108
+ %w(00 10 20 30 40 50 60 90 95).include? stage or
109
+ @log.add("Document Attributes", nil,
110
+ "#{stage} is not a recognised stage")
111
+ end
112
+
113
+ def substage_validate(xmldoc)
114
+ substage = xmldoc&.at("//bibdata/status/substage")&.text or return
115
+ %w(00 20 60 90 92 93 98 99).include? substage or
116
+ @log.add("Document Attributes", nil,
117
+ "#{substage} is not a recognised substage")
118
+ end
119
+
120
+ def iteration_validate(xmldoc)
121
+ iteration = xmldoc&.at("//bibdata/status/iteration")&.text or return
122
+ /^\d+/.match(iteration) or
123
+ @log.add("Document Attributes", nil,
124
+ "#{iteration} is not a recognised iteration")
125
+ end
126
+
127
+ def bibdata_validate(doc)
128
+ doctype_validate(doc)
129
+ script_validate(doc)
130
+ stage_validate(doc)
131
+ substage_validate(doc)
132
+ iteration_validate(doc)
133
+ end
134
+
135
+ def content_validate(doc)
136
+ super
137
+ title_validate(doc.root)
138
+ isosubgroup_validate(doc.root)
139
+ onlychild_clause_validate(doc.root)
140
+ termdef_style(doc.root)
141
+ see_xrefs_validate(doc.root)
142
+ see_erefs_validate(doc.root)
143
+ locality_erefs_validate(doc.root)
144
+ bibdata_validate(doc.root)
145
+ bibitem_validate(doc.root)
146
+ figure_validate(doc.root)
147
+ end
148
+
149
+ def bibitem_validate(xmldoc)
150
+ xmldoc.xpath("//bibitem[date/on = '–']").each do |b|
151
+ b.at("./note[@type = 'Unpublished-Status']") or
152
+ @log.add("Style", b,
153
+ "Reference #{b&.at('./@id')&.text} does not have an "\
154
+ "associated footnote indicating unpublished status")
155
+ end
156
+ end
157
+
158
+ def validate(doc)
159
+ content_validate(doc)
160
+ doctype = doc&.at("//bibdata/ext/doctype")&.text
161
+ schema = case doctype
162
+ when "amendment", "technical-corrigendum" # @amd
163
+ "isostandard-amd.rng"
164
+ else
165
+ "isostandard.rng"
166
+ end
167
+ schema_validate(formattedstr_strip(doc.dup),
168
+ File.join(File.dirname(__FILE__), schema))
169
+ end
170
+ end
171
+ end
172
+ end
@@ -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,247 @@
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) || @log.add("Style", f.first, ONE_SYMBOLS_WARNING)
44
+ f.first.elements.each do |e|
45
+ unless %w(title dl).include? e.name
46
+ @log.add("Style", f.first, NON_DL_SYMBOLS_WARNING)
47
+ return
48
+ end
49
+ end
50
+ end
51
+
52
+ def seqcheck(names, msg, accepted)
53
+ n = names.shift
54
+ return [] if n.nil?
55
+
56
+ test = accepted.map { |a| n.at(a) }
57
+ if test.all?(&:nil?)
58
+ @log.add("Style", nil, msg)
59
+ end
60
+ names
61
+ end
62
+
63
+ def sections_presence_validate(root)
64
+ root.at("//sections/clause[@type = 'scope']") or
65
+ @log.add("Style", nil, "Scope clause missing")
66
+ root.at("//references[@normative = 'true']") or
67
+ @log.add("Style", nil, "Normative references missing")
68
+ root.at("//terms") or
69
+ @log.add("Style", nil, "Terms & definitions missing")
70
+ end
71
+
72
+ # spec of permissible section sequence
73
+ # we skip normative references, it goes to end of list
74
+ SEQ =
75
+ [
76
+ {
77
+ msg: "Initial section must be (content) Foreword",
78
+ val: ["./self::foreword"],
79
+ },
80
+ {
81
+ msg: "Prefatory material must be followed by (clause) Scope",
82
+ val: ["./self::introduction", "./self::clause[@type = 'scope']"],
83
+ },
84
+ {
85
+ msg: "Prefatory material must be followed by (clause) Scope",
86
+ val: ["./self::clause[@type = 'scope']"],
87
+ },
88
+ {
89
+ msg: "Normative References must be followed by "\
90
+ "Terms and Definitions",
91
+ val: ["./self::terms | .//terms"],
92
+ },
93
+ ].freeze
94
+
95
+ SECTIONS_XPATH =
96
+ "//foreword | //introduction | //sections/terms | .//annex | "\
97
+ "//sections/definitions | //sections/clause | "\
98
+ "//references[not(parent::clause)] | "\
99
+ "//clause[descendant::references][not(parent::clause)]".freeze
100
+
101
+ def sections_sequence_validate(root)
102
+ names, n = sections_sequence_validate_start(root)
103
+ if root&.at("//bibdata/ext/subdoctype")&.text == "vocabulary"
104
+ names, n = sections_sequence_validate_body_vocab(names, n)
105
+ else
106
+ names, n = sections_sequence_validate_body(names, n)
107
+ end
108
+ sections_sequence_validate_end(names, n)
109
+ end
110
+
111
+ def sections_sequence_validate_start(root)
112
+ names = root.xpath(SECTIONS_XPATH)
113
+ names = seqcheck(names, SEQ[0][:msg], SEQ[0][:val])
114
+ n = names[0]
115
+ names = seqcheck(names, SEQ[1][:msg], SEQ[1][:val])
116
+ n&.at("./self::introduction") and
117
+ names = seqcheck(names, SEQ[2][:msg], SEQ[2][:val])
118
+ names = seqcheck(names, SEQ[3][:msg], SEQ[3][:val])
119
+ n = names.shift
120
+ n = names.shift if n&.at("./self::definitions")
121
+ [names, n]
122
+ end
123
+
124
+ def sections_sequence_validate_body(names, elem)
125
+ if elem.nil? || elem.name != "clause"
126
+ @log.add("Style", elem, "Document must contain at least one clause")
127
+ end
128
+ elem&.at("./self::clause") ||
129
+ @log.add("Style", elem, "Document must contain clause after "\
130
+ "Terms and Definitions")
131
+ elem&.at("./self::clause[@type = 'scope']") &&
132
+ @log.add("Style", elem,
133
+ "Scope must occur before Terms and Definitions")
134
+ elem = names.shift
135
+ while elem&.name == "clause"
136
+ elem&.at("./self::clause[@type = 'scope']")
137
+ @log.add("Style", elem,
138
+ "Scope must occur before Terms and Definitions")
139
+ elem = names.shift
140
+ end
141
+ %w(annex references).include? elem&.name or
142
+ @log.add("Style", elem,
143
+ "Only annexes and references can follow clauses")
144
+ [names, elem]
145
+ end
146
+
147
+ def sections_sequence_validate_body_vocab(names, elem)
148
+ while elem && %w(clause terms).include?(elem.name)
149
+ elem = names.shift
150
+ end
151
+ %w(annex references).include? elem&.name or
152
+ @log.add("Style", elem,
153
+ "Only annexes and references can follow terms and clauses")
154
+ [names, elem]
155
+ end
156
+
157
+ def sections_sequence_validate_end(names, elem)
158
+ while elem&.name == "annex"
159
+ elem = names.shift
160
+ if elem.nil?
161
+ @log.add("Style", nil, "Document must include (references) "\
162
+ "Normative References")
163
+ end
164
+ end
165
+ elem&.at("./self::references[@normative = 'true']") ||
166
+ @log.add("Style", nil, "Document must include (references) "\
167
+ "Normative References")
168
+ elem = names&.shift
169
+ elem&.at("./self::references[@normative = 'false']") ||
170
+ @log.add("Style", elem,
171
+ "Final section must be (references) Bibliography")
172
+ names.empty? ||
173
+ @log.add("Style", elem,
174
+ "There are sections after the final Bibliography")
175
+ end
176
+
177
+ NORM_ISO_WARN = "non-ISO/IEC reference not expected as normative".freeze
178
+ SCOPE_WARN = "Scope contains subclauses: should be succinct".freeze
179
+
180
+ def section_style(root)
181
+ foreword_style(root.at("//foreword"))
182
+ introduction_style(root.at("//introduction"))
183
+ scope_style(root.at("//clause[@type = 'scope']"))
184
+ scope = root.at("//clause[@type = 'scope']/clause")
185
+ # ISO/IEC DIR 2, 14.4
186
+ scope.nil? || style_warning(scope, SCOPE_WARN, nil)
187
+ tech_report_style(root)
188
+ end
189
+
190
+ def tech_report_style(root)
191
+ root.at("//bibdata/ext/doctype")&.text == "technical-report" or return
192
+ root.xpath("//sections/clause[not(@type = 'scope')] | //annex")
193
+ .each do |s|
194
+ r = requirement_check(extract_text(s)) and
195
+ style_warning(s,
196
+ "Technical Report clause may contain requirement", r)
197
+ end
198
+ end
199
+
200
+ ASSETS_TO_STYLE =
201
+ "//termsource | //formula | //termnote | "\
202
+ "//p[not(ancestor::boilerplate)] | //li[not(p)] | //dt | "\
203
+ "//dd[not(p)] | //td[not(p)] | //th[not(p)]".freeze
204
+
205
+ NORM_BIBITEMS =
206
+ "//references[@normative = 'true']/bibitem".freeze
207
+
208
+ # ISO/IEC DIR 2, 10.2
209
+ def norm_bibitem_style(root)
210
+ root.xpath(NORM_BIBITEMS).each do |b|
211
+ if b.at(Standoc::Converter::ISO_PUBLISHER_XPATH).nil?
212
+ @log.add("Style", b, "#{NORM_ISO_WARN}: #{b.text}")
213
+ end
214
+ end
215
+ end
216
+
217
+ def asset_style(root)
218
+ root.xpath("//example | //termexample").each { |e| example_style(e) }
219
+ root.xpath("//definition/verbal-definition").each { |e| definition_style(e) }
220
+ root.xpath("//note").each { |e| note_style(e) }
221
+ root.xpath("//fn").each { |e| footnote_style(e) }
222
+ root.xpath(ASSETS_TO_STYLE).each { |e| style(e, extract_text(e)) }
223
+ norm_bibitem_style(root)
224
+ super
225
+ end
226
+
227
+ def subclause_validate(root)
228
+ root.xpath("//clause/clause/clause/clause/clause/clause/clause/clause")
229
+ .each do |c|
230
+ style_warning(c, "Exceeds the maximum clause depth of 7", nil)
231
+ end
232
+ end
233
+
234
+ # ISO/IEC DIR 2, 22.3.2
235
+ def onlychild_clause_validate(root)
236
+ root.xpath(Standoc::Utils::SUBCLAUSE_XPATH).each do |c|
237
+ next unless c.xpath("../clause").size == 1
238
+
239
+ title = c.at("./title")
240
+ location = c["id"] || "#{c.text[0..60]}..."
241
+ location += ":#{title.text}" if c["id"] && !title.nil?
242
+ @log.add("Style", nil, "#{location}: subclause is only child")
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end