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.
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