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.
- checksums.yaml +4 -4
- data/.gitignore +26 -0
- data/Makefile +1 -1
- data/lib/asciidoctor/iso/base.rb +2 -69
- data/lib/asciidoctor/iso/cleanup.rb +2 -175
- data/lib/asciidoctor/iso/converter.rb +2 -17
- data/lib/asciidoctor/iso/deprecated.rb +5 -0
- data/lib/asciidoctor/iso/front.rb +2 -169
- data/lib/asciidoctor/iso/front_id.rb +2 -224
- data/lib/asciidoctor/iso/section.rb +2 -48
- data/lib/asciidoctor/iso/validate.rb +2 -171
- data/lib/asciidoctor/iso/validate_image.rb +2 -96
- data/lib/asciidoctor/iso/validate_requirements.rb +2 -110
- data/lib/asciidoctor/iso/validate_section.rb +2 -246
- data/lib/asciidoctor/iso/validate_style.rb +2 -169
- data/lib/asciidoctor/iso/validate_title.rb +2 -104
- data/lib/isodoc/iso/html/htmlstyle.css +47 -0
- data/lib/isodoc/iso/html/isodoc.css +1327 -0
- data/lib/isodoc/iso/html/style-human.css +1010 -0
- data/lib/isodoc/iso/html/style-iso.css +1036 -0
- data/lib/isodoc/iso/html/wordstyle.css +1701 -0
- data/lib/isodoc/iso/html_convert.rb +6 -4
- data/lib/isodoc/iso/iso.amendment.xsl +96 -154
- data/lib/isodoc/iso/iso.international-standard.xsl +96 -154
- data/lib/metanorma/iso/base.rb +70 -0
- data/lib/{asciidoctor → metanorma}/iso/basicdoc.rng +0 -0
- data/lib/{asciidoctor → metanorma}/iso/biblio.rng +0 -0
- data/lib/{asciidoctor → metanorma}/iso/boilerplate-fr.xml +0 -0
- data/lib/{asciidoctor → metanorma}/iso/boilerplate.xml +0 -0
- data/lib/metanorma/iso/cleanup.rb +176 -0
- data/lib/metanorma/iso/converter.rb +18 -0
- data/lib/metanorma/iso/front.rb +170 -0
- data/lib/metanorma/iso/front_id.rb +225 -0
- data/lib/{asciidoctor → metanorma}/iso/isodoc.rng +29 -0
- data/lib/{asciidoctor → metanorma}/iso/isostandard-amd.rng +0 -0
- data/lib/{asciidoctor → metanorma}/iso/isostandard.rnc +0 -0
- data/lib/{asciidoctor → metanorma}/iso/isostandard.rng +0 -0
- data/lib/{asciidoctor → metanorma}/iso/reqt.rng +0 -0
- data/lib/metanorma/iso/section.rb +49 -0
- data/lib/metanorma/iso/validate.rb +172 -0
- data/lib/metanorma/iso/validate_image.rb +97 -0
- data/lib/metanorma/iso/validate_requirements.rb +111 -0
- data/lib/metanorma/iso/validate_section.rb +247 -0
- data/lib/metanorma/iso/validate_style.rb +170 -0
- data/lib/metanorma/iso/validate_title.rb +105 -0
- data/lib/metanorma/iso/version.rb +1 -1
- data/lib/metanorma-iso.rb +1 -1
- data/metanorma-iso.gemspec +1 -1
- data/spec/isodoc/ref_spec.rb +4 -2
- data/spec/{asciidoctor → metanorma}/amd_spec.rb +1 -1
- data/spec/{asciidoctor → metanorma}/base_spec.rb +1 -1
- data/spec/{asciidoctor → metanorma}/blank_spec.rb +1 -1
- data/spec/{asciidoctor → metanorma}/blocks_spec.rb +1 -1
- data/spec/{asciidoctor → metanorma}/cleanup_spec.rb +1 -1
- data/spec/{asciidoctor → metanorma}/inline_spec.rb +1 -1
- data/spec/{asciidoctor → metanorma}/lists_spec.rb +1 -1
- data/spec/{asciidoctor → metanorma}/refs_spec.rb +1 -1
- data/spec/{asciidoctor → metanorma}/section_spec.rb +1 -1
- data/spec/{asciidoctor → metanorma}/table_spec.rb +1 -1
- data/spec/{asciidoctor → metanorma}/validate_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- 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
|