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.
- 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 +1326 -0
- data/lib/isodoc/iso/html/isodoc.scss +0 -1
- data/lib/isodoc/iso/html/style-human.css +1015 -0
- data/lib/isodoc/iso/html/style-human.scss +8 -2
- data/lib/isodoc/iso/html/style-iso.css +1042 -0
- data/lib/isodoc/iso/html/style-iso.scss +9 -2
- 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 +2142 -1565
- data/lib/isodoc/iso/iso.international-standard.xsl +2142 -1565
- data/lib/isodoc/iso/word_convert.rb +2 -0
- data/lib/metanorma/iso/base.rb +70 -0
- data/lib/{asciidoctor → metanorma}/iso/basicdoc.rng +5 -3
- data/lib/{asciidoctor → metanorma}/iso/biblio.rng +7 -5
- 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 +98 -1
- 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 +248 -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/isodoc/xref_spec.rb +18 -18
- data/spec/{asciidoctor → metanorma}/amd_spec.rb +1 -1
- data/spec/{asciidoctor → metanorma}/base_spec.rb +158 -197
- 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 +2 -5
- 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 +28 -3
- data/spec/spec_helper.rb +1 -1
- metadata +46 -30
- data/spec/vcr_cassettes/docrels.yml +0 -393
- 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
|