metanorma-iso 2.0.2 → 2.0.5

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/lib/isodoc/iso/html/html_iso_titlepage.html +1 -1
  4. data/lib/isodoc/iso/html/isodoc.css +1 -2
  5. data/lib/isodoc/iso/html/isodoc.scss +0 -1
  6. data/lib/isodoc/iso/html/word_iso_titlepage.html +1 -1
  7. data/lib/isodoc/iso/html/wordstyle.css +20 -0
  8. data/lib/isodoc/iso/html/wordstyle.scss +20 -0
  9. data/lib/isodoc/iso/i18n-en.yaml +29 -2
  10. data/lib/isodoc/iso/i18n-fr.yaml +27 -3
  11. data/lib/isodoc/iso/i18n-ru.yaml +45 -0
  12. data/lib/isodoc/iso/i18n-zh-Hans.yaml +27 -3
  13. data/lib/isodoc/iso/i18n.rb +1 -0
  14. data/lib/isodoc/iso/init.rb +1 -2
  15. data/lib/isodoc/iso/iso.amendment.xsl +1752 -807
  16. data/lib/isodoc/iso/iso.international-standard.xsl +1752 -807
  17. data/lib/isodoc/iso/metadata.rb +12 -2
  18. data/lib/isodoc/iso/presentation_xml_convert.rb +58 -21
  19. data/lib/metanorma/iso/basicdoc.rng +5 -3
  20. data/lib/metanorma/iso/biblio.rng +5 -3
  21. data/lib/metanorma/iso/boilerplate-fr.xml +3 -3
  22. data/lib/metanorma/iso/boilerplate-ru.xml +39 -0
  23. data/lib/metanorma/iso/boilerplate.xml +3 -3
  24. data/lib/metanorma/iso/cleanup.rb +42 -11
  25. data/lib/metanorma/iso/front.rb +1 -1
  26. data/lib/metanorma/iso/front_id.rb +1 -0
  27. data/lib/metanorma/iso/isodoc.rng +73 -3
  28. data/lib/metanorma/iso/processor.rb +14 -7
  29. data/lib/metanorma/iso/validate.rb +30 -2
  30. data/lib/metanorma/iso/validate_image.rb +3 -3
  31. data/lib/metanorma/iso/validate_list.rb +107 -0
  32. data/lib/metanorma/iso/validate_section.rb +42 -34
  33. data/lib/metanorma/iso/validate_style.rb +32 -2
  34. data/lib/metanorma/iso/validate_title.rb +13 -1
  35. data/lib/metanorma/iso/version.rb +1 -1
  36. data/spec/isodoc/amd_spec.rb +10 -5
  37. data/spec/isodoc/i18n_spec.rb +350 -9
  38. data/spec/isodoc/inline_spec.rb +127 -10
  39. data/spec/isodoc/metadata_spec.rb +153 -3
  40. data/spec/isodoc/postproc_spec.rb +3 -4
  41. data/spec/isodoc/section_spec.rb +1 -0
  42. data/spec/isodoc/terms_spec.rb +4 -4
  43. data/spec/isodoc/xref_spec.rb +18 -18
  44. data/spec/metanorma/base_spec.rb +434 -375
  45. data/spec/metanorma/cleanup_spec.rb +11 -11
  46. data/spec/metanorma/refs_spec.rb +333 -65
  47. data/spec/metanorma/section_spec.rb +15 -20
  48. data/spec/metanorma/validate_spec.rb +449 -14
  49. data/spec/spec_helper.rb +6 -4
  50. data/spec/vcr_cassettes/docrels.yml +211 -16
  51. data/spec/vcr_cassettes/withdrawn_iso.yml +301 -0
  52. metadata +6 -3
  53. data/spec/vcr_cassettes/sortrefs.yml +0 -599
@@ -0,0 +1,107 @@
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
+
47
+ list.xpath(".//li").each do |entry|
48
+ l = entry.dup
49
+ l.xpath(".//ol | .//ul").each(&:remove)
50
+ l.text.split.size > 2 and return false
51
+ end
52
+ true
53
+ end
54
+
55
+ def list_punctuation1(list, prectext)
56
+ prectext ||= ""
57
+ entries = list.xpath(".//li")
58
+ case prectext.strip.chars.last
59
+ when ":", "" then list_after_colon_punctuation(list, entries)
60
+ when "." then entries.each { |li| list_full_sentence(li) }
61
+ else style_warning(list, "All lists must be preceded by "\
62
+ "colon or full stop", prectext)
63
+ end
64
+ end
65
+
66
+ # if first list entry starts lowercase, treat as sentence broken up
67
+ def list_after_colon_punctuation(list, entries)
68
+ lower = list.at(".//li").text.match?(/^[^A-Za-z]*[a-z]/)
69
+ entries.each_with_index do |li, i|
70
+ if lower
71
+ list_semicolon_phrase(li, i == entries.size - 1)
72
+ else
73
+ list_full_sentence(li)
74
+ end
75
+ end
76
+ end
77
+
78
+ def list_semicolon_phrase(elem, last)
79
+ text = elem.text.strip
80
+ text.match?(/^[^A-Za-z]*[a-z]/) or
81
+ style_warning(elem, "List entry of broken up sentence must start "\
82
+ "with lowercase letter", text)
83
+ punct = text.sub(/^.*?(\S)\s*$/, "\\1")
84
+ if last
85
+ punct == "." or
86
+ style_warning(elem, "Final list entry of broken up "\
87
+ "sentence must end with full stop", text)
88
+ else
89
+ punct == ";" or
90
+ style_warning(elem, "List entry of broken up sentence must "\
91
+ "end with semicolon", text)
92
+ end
93
+ end
94
+
95
+ def list_full_sentence(elem)
96
+ text = elem.text.strip
97
+ text.match?(/^[^A-Za-z]*[A-Z]/) or
98
+ style_warning(elem, "List entry of separate sentences must start "\
99
+ "with uppercase letter", text)
100
+ punct = text.sub(/^.*?(\S)\s*$/, "\\1")
101
+ punct == "." or
102
+ style_warning(elem, "List entry of separate sentences must "\
103
+ "end with full stop", text)
104
+ end
105
+ end
106
+ end
107
+ 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,19 +34,20 @@ 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
 
40
40
  def symbols_validate(root)
41
41
  f = root.xpath("//definitions")
42
42
  f.empty? && return
43
- (f.size == 1) || @log.add("Style", f.first, ONE_SYMBOLS_WARNING)
44
- f.first.elements.each do |e|
45
- unless %w(title dl).include? e.name
46
- @log.add("Style", f.first, NON_DL_SYMBOLS_WARNING)
47
- return
48
- end
43
+ (f.size == 1 || @vocab) or
44
+ @log.add("Style", f.first, ONE_SYMBOLS_WARNING)
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")
49
51
  end
50
52
  end
51
53
 
@@ -71,26 +73,17 @@ module Metanorma
71
73
 
72
74
  # spec of permissible section sequence
73
75
  # we skip normative references, it goes to end of list
74
- SEQ =
75
- [
76
- {
77
- msg: "Initial section must be (content) Foreword",
78
- val: ["./self::foreword"],
79
- },
80
- {
81
- msg: "Prefatory material must be followed by (clause) Scope",
82
- val: ["./self::introduction", "./self::clause[@type = 'scope']"],
83
- },
84
- {
85
- msg: "Prefatory material must be followed by (clause) Scope",
86
- val: ["./self::clause[@type = 'scope']"],
87
- },
88
- {
89
- msg: "Normative References must be followed by "\
90
- "Terms and Definitions",
91
- val: ["./self::terms | .//terms"],
92
- },
93
- ].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
94
87
 
95
88
  SECTIONS_XPATH =
96
89
  "//foreword | //introduction | //sections/terms | .//annex | "\
@@ -100,7 +93,7 @@ module Metanorma
100
93
 
101
94
  def sections_sequence_validate(root)
102
95
  names, n = sections_sequence_validate_start(root)
103
- if root&.at("//bibdata/ext/subdoctype")&.text == "vocabulary"
96
+ if @vocab
104
97
  names, n = sections_sequence_validate_body_vocab(names, n)
105
98
  else
106
99
  names, n = sections_sequence_validate_body(names, n)
@@ -197,11 +190,6 @@ module Metanorma
197
190
  end
198
191
  end
199
192
 
200
- ASSETS_TO_STYLE =
201
- "//termsource | //formula | //termnote | "\
202
- "//p[not(ancestor::boilerplate)] | //li[not(p)] | //dt | "\
203
- "//dd[not(p)] | //td[not(p)] | //th[not(p)]".freeze
204
-
205
193
  NORM_BIBITEMS =
206
194
  "//references[@normative = 'true']/bibitem".freeze
207
195
 
@@ -216,7 +204,9 @@ module Metanorma
216
204
 
217
205
  def asset_style(root)
218
206
  root.xpath("//example | //termexample").each { |e| example_style(e) }
219
- 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
220
210
  root.xpath("//note").each { |e| note_style(e) }
221
211
  root.xpath("//fn").each { |e| footnote_style(e) }
222
212
  root.xpath(ASSETS_TO_STYLE).each { |e| style(e, extract_text(e)) }
@@ -242,6 +232,24 @@ module Metanorma
242
232
  @log.add("Style", nil, "#{location}: subclause is only child")
243
233
  end
244
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
245
253
  end
246
254
  end
247
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.2".freeze
3
+ VERSION = "2.0.5".freeze
4
4
  end
5
5
  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