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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/lib/isodoc/iso/html/header.html +42 -33
  4. data/lib/isodoc/iso/html/html_iso_titlepage.html +1 -1
  5. data/lib/isodoc/iso/html/isodoc.css +9 -7
  6. data/lib/isodoc/iso/html/isodoc.scss +7 -5
  7. data/lib/isodoc/iso/html/style-human.css +0 -1
  8. data/lib/isodoc/iso/html/style-human.scss +0 -1
  9. data/lib/isodoc/iso/html/style-iso.css +0 -1
  10. data/lib/isodoc/iso/html/style-iso.scss +0 -1
  11. data/lib/isodoc/iso/html/word_iso_titlepage.html +12 -11
  12. data/lib/isodoc/iso/html/wordstyle.css +46 -19
  13. data/lib/isodoc/iso/html/wordstyle.scss +52 -25
  14. data/lib/isodoc/iso/i18n-en.yaml +29 -2
  15. data/lib/isodoc/iso/i18n-fr.yaml +27 -3
  16. data/lib/isodoc/iso/i18n-ru.yaml +45 -0
  17. data/lib/isodoc/iso/i18n-zh-Hans.yaml +27 -3
  18. data/lib/isodoc/iso/i18n.rb +1 -0
  19. data/lib/isodoc/iso/init.rb +1 -2
  20. data/lib/isodoc/iso/iso.amendment.xsl +1204 -615
  21. data/lib/isodoc/iso/iso.international-standard.xsl +1204 -615
  22. data/lib/isodoc/iso/metadata.rb +12 -2
  23. data/lib/isodoc/iso/presentation_xml_convert.rb +58 -21
  24. data/lib/metanorma/iso/boilerplate-fr.xml +6 -7
  25. data/lib/metanorma/iso/boilerplate-ru.xml +37 -0
  26. data/lib/metanorma/iso/boilerplate.xml +6 -7
  27. data/lib/metanorma/iso/cleanup.rb +48 -11
  28. data/lib/metanorma/iso/front.rb +1 -1
  29. data/lib/metanorma/iso/front_id.rb +2 -0
  30. data/lib/metanorma/iso/isodoc.rng +73 -3
  31. data/lib/metanorma/iso/processor.rb +14 -7
  32. data/lib/metanorma/iso/validate.rb +30 -2
  33. data/lib/metanorma/iso/validate_image.rb +3 -3
  34. data/lib/metanorma/iso/validate_list.rb +122 -0
  35. data/lib/metanorma/iso/validate_section.rb +39 -32
  36. data/lib/metanorma/iso/validate_style.rb +32 -2
  37. data/lib/metanorma/iso/validate_title.rb +13 -1
  38. data/lib/metanorma/iso/version.rb +1 -1
  39. data/metanorma-iso.gemspec +1 -1
  40. data/spec/isodoc/amd_spec.rb +10 -5
  41. data/spec/isodoc/i18n_spec.rb +350 -9
  42. data/spec/isodoc/inline_spec.rb +127 -10
  43. data/spec/isodoc/metadata_spec.rb +153 -3
  44. data/spec/isodoc/postproc_spec.rb +3 -4
  45. data/spec/isodoc/section_spec.rb +1 -0
  46. data/spec/isodoc/terms_spec.rb +4 -4
  47. data/spec/metanorma/base_spec.rb +434 -326
  48. data/spec/metanorma/cleanup_spec.rb +11 -11
  49. data/spec/metanorma/refs_spec.rb +273 -61
  50. data/spec/metanorma/section_spec.rb +18 -26
  51. data/spec/metanorma/validate_spec.rb +448 -17
  52. data/spec/spec_helper.rb +6 -4
  53. data/spec/vcr_cassettes/docrels.yml +393 -0
  54. data/spec/vcr_cassettes/withdrawn_iso.yml +301 -0
  55. 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.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
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
- 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
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 { |e| definition_style(e) }
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, n, text)
74
- (m = regex.match(text)) && style_warning(n, warning, m[:num])
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
@@ -1,5 +1,5 @@
1
1
  module Metanorma
2
2
  module ISO
3
- VERSION = "2.0.3".freeze
3
+ VERSION = "2.0.6".freeze
4
4
  end
5
5
  end
@@ -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", "~> 5.0.0"
49
+ spec.add_development_dependency "vcr", "~> 6.1.0"
50
50
  spec.add_development_dependency "webmock"
51
51
  end
@@ -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({}).convert("test", input, true))
640
- .sub(%r{<localized-strings>.*</localized-strings>}m, "")).to be_equivalent_to xmlpp(presxml)
641
- expect(xmlpp(IsoDoc::Iso::HtmlConvert.new({}).convert("test", presxml,
642
- true))).to be_equivalent_to xmlpp(html)
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