metanorma-iso 2.0.3 → 2.0.6
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/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
|