metanorma-iso 2.0.3 → 2.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/lib/isodoc/iso/html/header.html +42 -33
- data/lib/isodoc/iso/html/html_iso_titlepage.html +1 -1
- data/lib/isodoc/iso/html/isodoc.css +9 -7
- data/lib/isodoc/iso/html/isodoc.scss +7 -5
- data/lib/isodoc/iso/html/style-human.css +0 -1
- data/lib/isodoc/iso/html/style-human.scss +0 -1
- data/lib/isodoc/iso/html/style-iso.css +0 -1
- data/lib/isodoc/iso/html/style-iso.scss +0 -1
- data/lib/isodoc/iso/html/word_iso_titlepage.html +12 -11
- data/lib/isodoc/iso/html/wordstyle.css +46 -19
- data/lib/isodoc/iso/html/wordstyle.scss +52 -25
- data/lib/isodoc/iso/i18n-en.yaml +29 -2
- data/lib/isodoc/iso/i18n-fr.yaml +27 -3
- data/lib/isodoc/iso/i18n-ru.yaml +45 -0
- data/lib/isodoc/iso/i18n-zh-Hans.yaml +27 -3
- data/lib/isodoc/iso/i18n.rb +1 -0
- data/lib/isodoc/iso/init.rb +1 -2
- data/lib/isodoc/iso/iso.amendment.xsl +1204 -615
- data/lib/isodoc/iso/iso.international-standard.xsl +1204 -615
- data/lib/isodoc/iso/metadata.rb +12 -2
- data/lib/isodoc/iso/presentation_xml_convert.rb +58 -21
- data/lib/metanorma/iso/boilerplate-fr.xml +6 -7
- data/lib/metanorma/iso/boilerplate-ru.xml +37 -0
- data/lib/metanorma/iso/boilerplate.xml +6 -7
- data/lib/metanorma/iso/cleanup.rb +48 -11
- data/lib/metanorma/iso/front.rb +1 -1
- data/lib/metanorma/iso/front_id.rb +2 -0
- data/lib/metanorma/iso/isodoc.rng +73 -3
- data/lib/metanorma/iso/processor.rb +14 -7
- data/lib/metanorma/iso/validate.rb +30 -2
- data/lib/metanorma/iso/validate_image.rb +3 -3
- data/lib/metanorma/iso/validate_list.rb +122 -0
- data/lib/metanorma/iso/validate_section.rb +39 -32
- data/lib/metanorma/iso/validate_style.rb +32 -2
- data/lib/metanorma/iso/validate_title.rb +13 -1
- data/lib/metanorma/iso/version.rb +1 -1
- data/metanorma-iso.gemspec +1 -1
- data/spec/isodoc/amd_spec.rb +10 -5
- data/spec/isodoc/i18n_spec.rb +350 -9
- data/spec/isodoc/inline_spec.rb +127 -10
- data/spec/isodoc/metadata_spec.rb +153 -3
- data/spec/isodoc/postproc_spec.rb +3 -4
- data/spec/isodoc/section_spec.rb +1 -0
- data/spec/isodoc/terms_spec.rb +4 -4
- data/spec/metanorma/base_spec.rb +434 -326
- data/spec/metanorma/cleanup_spec.rb +11 -11
- data/spec/metanorma/refs_spec.rb +273 -61
- data/spec/metanorma/section_spec.rb +18 -26
- data/spec/metanorma/validate_spec.rb +448 -17
- data/spec/spec_helper.rb +6 -4
- data/spec/vcr_cassettes/docrels.yml +393 -0
- data/spec/vcr_cassettes/withdrawn_iso.yml +301 -0
- metadata +10 -5
@@ -0,0 +1,122 @@
|
|
1
|
+
module Metanorma
|
2
|
+
module ISO
|
3
|
+
class Converter < Standoc::Converter
|
4
|
+
# https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-p-lists
|
5
|
+
def listcount_validate(doc)
|
6
|
+
return if @novalid
|
7
|
+
|
8
|
+
ol_count_validate(doc)
|
9
|
+
li_depth_validate(doc)
|
10
|
+
end
|
11
|
+
|
12
|
+
def ol_count_validate(doc)
|
13
|
+
doc.xpath("//clause | //annex").each do |c|
|
14
|
+
next if c.xpath(".//ol").empty?
|
15
|
+
|
16
|
+
ols = c.xpath(".//ol") -
|
17
|
+
c.xpath(".//ul//ol | .//ol//ol | .//clause//ol")
|
18
|
+
ols.size > 1 and
|
19
|
+
style_warning(c, "More than 1 ordered list in a numbered clause",
|
20
|
+
nil)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def li_depth_validate(doc)
|
25
|
+
doc.xpath("//li//li//li//li").each do |l|
|
26
|
+
l.at(".//li") and
|
27
|
+
style_warning(l, "List more than four levels deep", nil)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-p-lists
|
32
|
+
def list_punctuation(doc)
|
33
|
+
return if @novalid
|
34
|
+
|
35
|
+
((doc.xpath("//ol") - doc.xpath("//ul//ol | //ol//ol")) +
|
36
|
+
(doc.xpath("//ul") - doc.xpath("//ul//ul | //ol//ul"))).each do |list|
|
37
|
+
next if skip_list_punctuation(list)
|
38
|
+
|
39
|
+
prec = list.at("./preceding::text()[normalize-space(.) != ''][1]")
|
40
|
+
list_punctuation1(list, prec&.text)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def skip_list_punctuation(list)
|
45
|
+
return true if list.at("./ancestor::table")
|
46
|
+
return true if list.at("./following-sibling::term") # terms boilerplate
|
47
|
+
|
48
|
+
list.xpath(".//li").each do |entry|
|
49
|
+
l = entry.dup
|
50
|
+
l.xpath(".//ol | .//ul").each(&:remove)
|
51
|
+
l.text.split.size > 2 and return false
|
52
|
+
end
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def list_punctuation1(list, prectext)
|
57
|
+
prectext ||= ""
|
58
|
+
entries = list.xpath(".//li")
|
59
|
+
case prectext.strip.chars.last
|
60
|
+
when ":", "" then list_after_colon_punctuation(list, entries)
|
61
|
+
when "." then entries.each { |li| list_full_sentence(li) }
|
62
|
+
else style_warning(list, "All lists must be preceded by "\
|
63
|
+
"colon or full stop", prectext)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# if first list entry starts lowercase, treat as sentence broken up
|
68
|
+
def list_after_colon_punctuation(list, entries)
|
69
|
+
lower = starts_lowercase?(list.at(".//li").text)
|
70
|
+
entries.each_with_index do |li, i|
|
71
|
+
if lower
|
72
|
+
list_semicolon_phrase(li, i == entries.size - 1)
|
73
|
+
else
|
74
|
+
list_full_sentence(li)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def list_semicolon_phrase(elem, last)
|
80
|
+
text = elem.text.strip
|
81
|
+
starts_lowercase?(text) or
|
82
|
+
style_warning(elem, "List entry of broken up sentence must start "\
|
83
|
+
"with lowercase letter", text)
|
84
|
+
list_semicolon_phrase_punct(elem, text, last)
|
85
|
+
end
|
86
|
+
|
87
|
+
def list_semicolon_phrase_punct(elem, text, last)
|
88
|
+
punct = text.strip.sub(/^.*?(\S)$/m, "\\1")
|
89
|
+
if last
|
90
|
+
punct == "." or
|
91
|
+
style_warning(elem, "Final list entry of broken up "\
|
92
|
+
"sentence must end with full stop", text)
|
93
|
+
else
|
94
|
+
punct == ";" or
|
95
|
+
style_warning(elem, "List entry of broken up sentence must "\
|
96
|
+
"end with semicolon", text)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def list_full_sentence(elem)
|
101
|
+
text = elem.text.strip
|
102
|
+
starts_uppercase?(text) or
|
103
|
+
style_warning(elem, "List entry of separate sentences must start "\
|
104
|
+
"with uppercase letter", text)
|
105
|
+
punct = text.strip.sub(/^.*?(\S)$/m, "\\1")
|
106
|
+
punct == "." or
|
107
|
+
style_warning(elem, "List entry of separate sentences must "\
|
108
|
+
"end with full stop", text)
|
109
|
+
end
|
110
|
+
|
111
|
+
# allow that all-caps word (acronym) is agnostic as to lowercase
|
112
|
+
def starts_lowercase?(text)
|
113
|
+
text.match?(/^[^[[:upper:]][[:lower:]]]*[[:lower:]]/) ||
|
114
|
+
text.match?(/^[^[[:upper:]][[:lower:]]]*[[:upper:]][[:upper:]]+[^[[:alpha:]]]/)
|
115
|
+
end
|
116
|
+
|
117
|
+
def starts_uppercase?(text)
|
118
|
+
text.match?(/^[^[[:upper:]][[:lower:]]]*[[:upper:]]/)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -14,6 +14,7 @@ module Metanorma
|
|
14
14
|
end
|
15
15
|
section_style(doc.root)
|
16
16
|
subclause_validate(doc.root)
|
17
|
+
@vocab and vocab_terms_titles_validate(doc.root)
|
17
18
|
super
|
18
19
|
end
|
19
20
|
|
@@ -33,7 +34,6 @@ module Metanorma
|
|
33
34
|
|
34
35
|
ONE_SYMBOLS_WARNING = "Only one Symbols and Abbreviated "\
|
35
36
|
"Terms section in the standard".freeze
|
36
|
-
|
37
37
|
NON_DL_SYMBOLS_WARNING = "Symbols and Abbreviated Terms can "\
|
38
38
|
"only contain a definition list".freeze
|
39
39
|
|
@@ -42,11 +42,12 @@ module Metanorma
|
|
42
42
|
f.empty? && return
|
43
43
|
(f.size == 1 || @vocab) or
|
44
44
|
@log.add("Style", f.first, ONE_SYMBOLS_WARNING)
|
45
|
-
f.first.elements.
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
45
|
+
f.first.elements.reject { |e| %w(title dl).include? e.name }.empty? or
|
46
|
+
@log.add("Style", f.first, NON_DL_SYMBOLS_WARNING)
|
47
|
+
@vocab and f.each do |f1|
|
48
|
+
f1.at("./ancestor::annex") or
|
49
|
+
@log.add("Style", f1, "In vocabulary documents, Symbols and "\
|
50
|
+
"Abbreviated Terms are only permitted in annexes")
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
@@ -72,26 +73,17 @@ module Metanorma
|
|
72
73
|
|
73
74
|
# spec of permissible section sequence
|
74
75
|
# we skip normative references, it goes to end of list
|
75
|
-
SEQ =
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
76
|
+
SEQ = [
|
77
|
+
{ msg: "Initial section must be (content) Foreword",
|
78
|
+
val: ["./self::foreword"] },
|
79
|
+
{ msg: "Prefatory material must be followed by (clause) Scope",
|
80
|
+
val: ["./self::introduction", "./self::clause[@type = 'scope']"] },
|
81
|
+
{ msg: "Prefatory material must be followed by (clause) Scope",
|
82
|
+
val: ["./self::clause[@type = 'scope']"] },
|
83
|
+
{ msg: "Normative References must be followed by "\
|
84
|
+
"Terms and Definitions",
|
85
|
+
val: ["./self::terms | .//terms"] },
|
86
|
+
].freeze
|
95
87
|
|
96
88
|
SECTIONS_XPATH =
|
97
89
|
"//foreword | //introduction | //sections/terms | .//annex | "\
|
@@ -198,11 +190,6 @@ module Metanorma
|
|
198
190
|
end
|
199
191
|
end
|
200
192
|
|
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
193
|
NORM_BIBITEMS =
|
207
194
|
"//references[@normative = 'true']/bibitem".freeze
|
208
195
|
|
@@ -217,7 +204,9 @@ module Metanorma
|
|
217
204
|
|
218
205
|
def asset_style(root)
|
219
206
|
root.xpath("//example | //termexample").each { |e| example_style(e) }
|
220
|
-
root.xpath("//definition/verbal-definition").each
|
207
|
+
root.xpath("//definition/verbal-definition").each do |e|
|
208
|
+
definition_style(e)
|
209
|
+
end
|
221
210
|
root.xpath("//note").each { |e| note_style(e) }
|
222
211
|
root.xpath("//fn").each { |e| footnote_style(e) }
|
223
212
|
root.xpath(ASSETS_TO_STYLE).each { |e| style(e, extract_text(e)) }
|
@@ -243,6 +232,24 @@ module Metanorma
|
|
243
232
|
@log.add("Style", nil, "#{location}: subclause is only child")
|
244
233
|
end
|
245
234
|
end
|
235
|
+
|
236
|
+
# https://www.iso.org/ISO-house-style.html#iso-hs-s-formatting-r-vocabulary
|
237
|
+
def vocab_terms_titles_validate(root)
|
238
|
+
terms = root.xpath("//sections/terms | //sections/clause[.//terms]")
|
239
|
+
if terms.size == 1
|
240
|
+
((t = terms.first.at("./title")) && (t&.text == @i18n.termsdef)) or
|
241
|
+
@log.add("Style", terms.first,
|
242
|
+
"Single terms clause in vocabulary document "\
|
243
|
+
"should have normal Terms and definitions heading")
|
244
|
+
elsif terms.size > 1
|
245
|
+
terms.each do |x|
|
246
|
+
((t = x.at("./title")) && /^#{@i18n.termsrelated}/.match?(t&.text)) or
|
247
|
+
@log.add("Style", x,
|
248
|
+
"Multiple terms clauses in vocabulary document "\
|
249
|
+
"should have 'Terms related to' heading")
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
246
253
|
end
|
247
254
|
end
|
248
255
|
end
|
@@ -70,8 +70,8 @@ module Metanorma
|
|
70
70
|
style(node, extract_text(node))
|
71
71
|
end
|
72
72
|
|
73
|
-
def style_regex(regex, warning,
|
74
|
-
(m = regex.match(text)) && style_warning(
|
73
|
+
def style_regex(regex, warning, node, text)
|
74
|
+
(m = regex.match(text)) && style_warning(node, warning, m[:num])
|
75
75
|
end
|
76
76
|
|
77
77
|
# style check with a regex on a token
|
@@ -96,10 +96,12 @@ module Metanorma
|
|
96
96
|
style_percent(node, text)
|
97
97
|
style_abbrev(node, text)
|
98
98
|
style_units(node, text)
|
99
|
+
style_punct(node, text)
|
99
100
|
end
|
100
101
|
|
101
102
|
# ISO/IEC DIR 2, 9.1
|
102
103
|
# ISO/IEC DIR 2, Table B.1
|
104
|
+
# https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-n-numbers
|
103
105
|
def style_number(node, text)
|
104
106
|
style_two_regex_not_prev(
|
105
107
|
node, text, /^(?<num>-?[0-9]{4,}[,0-9]*)\Z/,
|
@@ -110,6 +112,8 @@ module Metanorma
|
|
110
112
|
"possible decimal point", node, text)
|
111
113
|
style_regex(/\b(?<num>billions?)\b/i,
|
112
114
|
"ambiguous number", node, text)
|
115
|
+
style_regex(/(^|\s)(?<num>-[0-9][0-9,.]*)/i,
|
116
|
+
"hyphen instead of minus sign U+2212", node, text)
|
113
117
|
end
|
114
118
|
|
115
119
|
# ISO/IEC DIR 2, 9.2.1
|
@@ -158,6 +162,15 @@ module Metanorma
|
|
158
162
|
end
|
159
163
|
end
|
160
164
|
|
165
|
+
# https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-p-and
|
166
|
+
# https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-p-andor
|
167
|
+
def style_punct(node, text)
|
168
|
+
style_regex(/\b(?<num>and\/?or)\b/i,
|
169
|
+
"Use 'either x or y, or both'", node, text)
|
170
|
+
style_regex(/\s(?<num>&)\s/i,
|
171
|
+
"Avoid ampersand in ordinary text'", node, text)
|
172
|
+
end
|
173
|
+
|
161
174
|
def style_warning(node, msg, text = nil)
|
162
175
|
return if @novalid
|
163
176
|
|
@@ -165,6 +178,23 @@ module Metanorma
|
|
165
178
|
w += ": #{text}" if text
|
166
179
|
@log.add("Style", node, w)
|
167
180
|
end
|
181
|
+
|
182
|
+
ASSETS_TO_STYLE =
|
183
|
+
"//termsource | //formula | //termnote | "\
|
184
|
+
"//p[not(ancestor::boilerplate)] | //li[not(p)] | //dt | "\
|
185
|
+
"//dd[not(p)] | //td[not(p)] | //th[not(p)]".freeze
|
186
|
+
|
187
|
+
def asset_style(root)
|
188
|
+
root.xpath("//example | //termexample").each { |e| example_style(e) }
|
189
|
+
root.xpath("//definition/verbal-definition").each do |e|
|
190
|
+
definition_style(e)
|
191
|
+
end
|
192
|
+
root.xpath("//note").each { |e| note_style(e) }
|
193
|
+
root.xpath("//fn").each { |e| footnote_style(e) }
|
194
|
+
root.xpath(ASSETS_TO_STYLE).each { |e| style(e, extract_text(e)) }
|
195
|
+
norm_bibitem_style(root)
|
196
|
+
super
|
197
|
+
end
|
168
198
|
end
|
169
199
|
end
|
170
200
|
end
|
@@ -68,7 +68,7 @@ module Metanorma
|
|
68
68
|
title = s&.at("./title")&.text || s.name
|
69
69
|
s.xpath("./clause | ./terms | ./references").each do |ss|
|
70
70
|
subtitle = ss.at("./title")
|
71
|
-
!subtitle.nil? && !subtitle&.text&.empty? or
|
71
|
+
(!subtitle.nil? && !subtitle&.text&.empty?) or
|
72
72
|
@log.add("Style", ss,
|
73
73
|
"#{title}: each first-level subclause must have a title")
|
74
74
|
end
|
@@ -91,6 +91,17 @@ module Metanorma
|
|
91
91
|
"#{label}: all subclauses must have a title, or none")
|
92
92
|
end
|
93
93
|
|
94
|
+
# https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-p-full
|
95
|
+
def title_no_full_stop_validate(root)
|
96
|
+
root.xpath("//preface//title | //sections//title | //annex//title | "\
|
97
|
+
"//references/title | //preface//name | //sections//name | "\
|
98
|
+
"//annex//name").each do |t|
|
99
|
+
style_regex(/\A(?<num>.+\.\Z)/i,
|
100
|
+
"No full stop at end of title or caption",
|
101
|
+
t, t.text.strip)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
94
105
|
def title_validate(root)
|
95
106
|
title_intro_validate(root)
|
96
107
|
title_main_validate(root)
|
@@ -99,6 +110,7 @@ module Metanorma
|
|
99
110
|
title_names_type_validate(root)
|
100
111
|
title_first_level_validate(root)
|
101
112
|
title_all_siblings(root.xpath(SECTIONS_XPATH), "(top level)")
|
113
|
+
title_no_full_stop_validate(root)
|
102
114
|
end
|
103
115
|
end
|
104
116
|
end
|
data/metanorma-iso.gemspec
CHANGED
@@ -46,6 +46,6 @@ Gem::Specification.new do |spec|
|
|
46
46
|
spec.add_development_dependency "sassc", "2.4.0"
|
47
47
|
spec.add_development_dependency "simplecov", "~> 0.15"
|
48
48
|
spec.add_development_dependency "timecop", "~> 0.9"
|
49
|
-
spec.add_development_dependency "vcr", "~>
|
49
|
+
spec.add_development_dependency "vcr", "~> 6.1.0"
|
50
50
|
spec.add_development_dependency "webmock"
|
51
51
|
end
|
data/spec/isodoc/amd_spec.rb
CHANGED
@@ -74,6 +74,7 @@ RSpec.describe IsoDoc do
|
|
74
74
|
<bibdata>
|
75
75
|
<ext>
|
76
76
|
<doctype language="">amendment</doctype>
|
77
|
+
<doctype language='en'>Amendment</doctype>
|
77
78
|
</ext>
|
78
79
|
</bibdata>
|
79
80
|
<preface>
|
@@ -243,9 +244,9 @@ RSpec.describe IsoDoc do
|
|
243
244
|
<bibdata>
|
244
245
|
<ext>
|
245
246
|
<doctype language=''>amendment</doctype>
|
247
|
+
<doctype language='en'>Amendment</doctype>
|
246
248
|
</ext>
|
247
249
|
</bibdata>
|
248
|
-
|
249
250
|
<preface>
|
250
251
|
<foreword obligation='informative' displayorder='1'>
|
251
252
|
<title>Foreword</title>
|
@@ -436,6 +437,7 @@ RSpec.describe IsoDoc do
|
|
436
437
|
<bibdata>
|
437
438
|
<ext>
|
438
439
|
<doctype language="">amendment</doctype>
|
440
|
+
<doctype language='en'>Amendment</doctype>
|
439
441
|
</ext>
|
440
442
|
</bibdata>
|
441
443
|
<boilerplate>
|
@@ -636,10 +638,13 @@ RSpec.describe IsoDoc do
|
|
636
638
|
</body>
|
637
639
|
</html>
|
638
640
|
OUTPUT
|
639
|
-
expect(xmlpp(IsoDoc::Iso::PresentationXMLConvert.new({})
|
640
|
-
.
|
641
|
-
|
642
|
-
|
641
|
+
expect(xmlpp(IsoDoc::Iso::PresentationXMLConvert.new({})
|
642
|
+
.convert("test", input, true))
|
643
|
+
.sub(%r{<localized-strings>.*</localized-strings>}m, ""))
|
644
|
+
.to be_equivalent_to xmlpp(presxml)
|
645
|
+
expect(xmlpp(IsoDoc::Iso::HtmlConvert.new({})
|
646
|
+
.convert("test", presxml, true)))
|
647
|
+
.to be_equivalent_to xmlpp(html)
|
643
648
|
end
|
644
649
|
|
645
650
|
it "processes IsoXML metadata" do
|