metanorma-iso 2.0.4 → 2.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -47,37 +47,74 @@ module IsoDoc
47
47
 
48
48
  def eref_delim(delim, type)
49
49
  if delim == ";" then ";"
50
- else type == "list" ? "" : delim
50
+ else type == "list" ? " " : delim
51
51
  end
52
52
  end
53
53
 
54
- def eref_localities1_zh(target, type, from, upto, node, delim)
55
- subsection = from&.text&.match(/\./)
56
- ret = eref_delim(delim, type)
57
- ret += " 第#{from.text}" if from
58
- ret += "–#{upto.text}" if upto
59
- loc = (@i18n.locality[type] || type.sub(/^locality:/, "").capitalize)
60
- ret += " #{loc}" unless (subsection && type == "clause") ||
61
- type == "list" || target.match(/^IEV$|^IEC 60050-/) ||
62
- node["droploc"] == "true"
54
+ def can_conflate_eref_rendering?(refs)
55
+ super or return false
56
+
57
+ first = subclause?(nil, refs.first.at(ns("./locality/@type"))&.text,
58
+ refs.first.at(ns("./locality/referenceFrom"))&.text)
59
+ refs.all? do |r|
60
+ subclause?(nil, r.at(ns("./locality/@type"))&.text,
61
+ r.at(ns("./locality/referenceFrom"))&.text) == first
62
+ end
63
+ end
64
+
65
+ def locality_delimiter(loc)
66
+ loc&.next_element&.attribute("type")&.text == "list" and return " "
67
+ super
68
+ end
69
+
70
+ def eref_localities_conflated(refs, target, node)
71
+ droploc = node["droploc"]
72
+ node["droploc"] = true
73
+ ret = resolve_eref_connectives(eref_locality_stacks(refs, target,
74
+ node))
75
+ node["droploc"] = droploc
76
+ eref_localities1(target,
77
+ prefix_clause(target, refs.first.at(ns("./locality"))),
78
+ l10n(ret[1..-1].join), nil, node, @lang)
79
+ end
80
+
81
+ def prefix_clause(target, loc)
82
+ loc["type"] == "clause" or return loc["type"]
83
+
84
+ if subclause?(target, loc["type"], loc&.at(ns("./referenceFrom"))&.text)
85
+ ""
86
+ else
87
+ "clause"
88
+ end
89
+ end
90
+
91
+ def subclause?(target, type, from)
92
+ (from&.match?(/\./) && type == "clause") ||
93
+ type == "list" || target&.match(/^IEV$|^IEC 60050-/)
94
+ end
95
+
96
+ def eref_localities1_zh(target, type, from, upto, node)
97
+ ret = " 第#{from}" if from
98
+ ret += "–#{upto}" if upto
99
+ if node["droploc"] != "true" && !subclause?(target, type, from)
100
+ ret += eref_locality_populate(type, node)
101
+ end
63
102
  ret += ")" if type == "list"
64
103
  ret
65
104
  end
66
105
 
67
- def eref_localities1(target, type, from, upto, delim, node, lang = "en")
68
- return "" if type == "anchor"
106
+ def eref_localities1(target, type, from, upto, node, lang = "en")
107
+ return nil if type == "anchor"
69
108
 
70
- subsection = from&.text&.match(/\./)
71
109
  type = type.downcase
72
110
  lang == "zh" and
73
- return l10n(eref_localities1_zh(target, type, from, upto, node,
74
- delim))
75
- ret = eref_delim(delim, type)
76
- ret += eref_locality_populate(type, node) unless (subsection &&
77
- type == "clause") || type == "list" ||
78
- target.match(/^IEV$|^IEC 60050-/)
79
- ret += " #{from.text}" if from
80
- ret += "–#{upto.text}" if upto
111
+ return l10n(eref_localities1_zh(target, type, from, upto, node))
112
+ ret = if node["droploc"] != "true" && !subclause?(target, type, from)
113
+ eref_locality_populate(type, node)
114
+ else ""
115
+ end
116
+ ret += " #{from}" if from
117
+ ret += "–#{upto}" if upto
81
118
  ret += ")" if type == "list"
82
119
  l10n(ret)
83
120
  end
@@ -37,13 +37,11 @@ module Metanorma
37
37
  end
38
38
 
39
39
  def get_id_prefix(xmldoc)
40
- prefix = []
41
40
  xmldoc.xpath("//bibdata/contributor[role/@type = 'publisher']"\
42
- "/organization").each do |x|
41
+ "/organization").each_with_object([]) do |x, prefix|
43
42
  x1 = x.at("abbreviation")&.text || x.at("name")&.text
44
43
  (x1 == "ISO" and prefix.unshift("ISO")) or prefix << x1
45
44
  end
46
- prefix
47
45
  end
48
46
 
49
47
  # ISO as a prefix goes first
@@ -140,8 +138,7 @@ module Metanorma
140
138
 
141
139
  def unpub_footnotes(xmldoc)
142
140
  xmldoc.xpath("//bibitem/note[@type = 'Unpublished-Status']").each do |n|
143
- id = n.parent["id"]
144
- e = xmldoc.at("//eref[@bibitemid = '#{id}']") or next
141
+ e = xmldoc.at("//eref[@bibitemid = '#{n.parent['id']}']") or next
145
142
  fn = n.children.to_xml
146
143
  n&.elements&.first&.name == "p" or fn = "<p>#{fn}</p>"
147
144
  e.next = "<fn>#{fn}</fn>"
@@ -151,21 +148,46 @@ module Metanorma
151
148
  def bibitem_cleanup(xmldoc)
152
149
  super
153
150
  unpublished_note(xmldoc)
151
+ withdrawn_note(xmldoc)
154
152
  end
155
153
 
156
154
  def unpublished_note(xmldoc)
157
- xmldoc.xpath("//bibitem[not(note[@type = 'Unpublished-Status'])]")
158
- .each do |b|
155
+ xmldoc.xpath("//bibitem[not(./ancestor::bibitem)]"\
156
+ "[not(note[@type = 'Unpublished-Status'])]").each do |b|
159
157
  next if pub_class(b) > 2
160
158
  next unless (s = b.at("./status/stage")) && (s.text.to_i < 60)
161
159
 
162
160
  id = b.at("docidentifier").text
163
- b.at("./language | ./script | ./abstract | ./status")
164
- .previous = %(<note type="Unpublished-Status">
165
- <p>#{@i18n.under_preparation.sub(/%/, id)}</p></note>)
161
+ insert_unpub_note(b, @i18n.under_preparation.sub(/%/, id))
166
162
  end
167
163
  end
168
164
 
165
+ def withdrawn_note(xmldoc)
166
+ xmldoc.xpath("//bibitem[not(note[@type = 'Unpublished-Status'])]")
167
+ .each do |b|
168
+ next if pub_class(b) > 2
169
+ next unless (s = b.at("./status/stage")) && (s.text.to_i >= 90)
170
+
171
+ if id = replacement_standard(b)
172
+ insert_unpub_note(b, @i18n.cancelled_and_replaced.sub(/%/, id))
173
+ else
174
+ insert_unpub_note(b, @i18n.withdrawn)
175
+ end
176
+ end
177
+ end
178
+
179
+ def replacement_standard(biblio)
180
+ r = biblio.at("./relation[@type = 'updates']/bibitem") or return nil
181
+ id = r.at("./formattedref | ./docidentifier[@primary = 'true'] | "\
182
+ "./docidentifier | ./formattedref") or return nil
183
+ id.text
184
+ end
185
+
186
+ def insert_unpub_note(biblio, msg)
187
+ biblio.at("./language | ./script | ./abstract | ./status")
188
+ .previous = %(<note type="Unpublished-Status"><p>#{msg}</p></note>)
189
+ end
190
+
169
191
  def termdef_boilerplate_insert(xmldoc, isodoc, once = false)
170
192
  once = true
171
193
  super
@@ -175,6 +197,11 @@ module Metanorma
175
197
  @vocab and src.empty? and return
176
198
  super
177
199
  end
200
+
201
+ def section_names_terms_cleanup(xml)
202
+ @vocab and return
203
+ super
204
+ end
178
205
  end
179
206
  end
180
207
  end
@@ -45,19 +45,26 @@ module Metanorma
45
45
  def output(isodoc_node, inname, outname, format, options={})
46
46
  case format
47
47
  when :html
48
- IsoDoc::Iso::HtmlConvert.new(options).convert(inname, isodoc_node, nil, outname)
48
+ IsoDoc::Iso::HtmlConvert.new(options)
49
+ .convert(inname, isodoc_node, nil, outname)
49
50
  when :html_alt
50
- IsoDoc::Iso::HtmlConvert.new(options.merge(alt: true)).convert(inname, isodoc_node, nil, outname)
51
+ IsoDoc::Iso::HtmlConvert.new(options.merge(alt: true))
52
+ .convert(inname, isodoc_node, nil, outname)
51
53
  when :doc
52
- IsoDoc::Iso::WordConvert.new(options).convert(inname, isodoc_node, nil, outname)
54
+ IsoDoc::Iso::WordConvert.new(options)
55
+ .convert(inname, isodoc_node, nil, outname)
53
56
  when :pdf
54
- IsoDoc::Iso::PdfConvert.new(options).convert(inname, isodoc_node, nil, outname)
57
+ IsoDoc::Iso::PdfConvert.new(options)
58
+ .convert(inname, isodoc_node, nil, outname)
55
59
  when :sts
56
- IsoDoc::Iso::StsConvert.new(options).convert(inname, isodoc_node, nil, outname)
60
+ IsoDoc::Iso::StsConvert.new(options)
61
+ .convert(inname, isodoc_node, nil, outname)
57
62
  when :isosts
58
- IsoDoc::Iso::IsoStsConvert.new(options).convert(inname, isodoc_node, nil, outname)
63
+ IsoDoc::Iso::IsoStsConvert.new(options)
64
+ .convert(inname, isodoc_node, nil, outname)
59
65
  when :presentation
60
- IsoDoc::Iso::PresentationXMLConvert.new(options).convert(inname, isodoc_node, nil, outname)
66
+ IsoDoc::Iso::PresentationXMLConvert.new(options)
67
+ .convert(inname, isodoc_node, nil, outname)
61
68
  else
62
69
  super
63
70
  end
@@ -4,6 +4,7 @@ require_relative "./validate_requirements"
4
4
  require_relative "./validate_section"
5
5
  require_relative "./validate_title"
6
6
  require_relative "./validate_image"
7
+ require_relative "./validate_list"
7
8
  require "nokogiri"
8
9
  require "jing"
9
10
  require "iev"
@@ -35,7 +36,7 @@ module Metanorma
35
36
  /\b(see| refer to)\s*\Z/mi.match(preceding)
36
37
 
37
38
  (target = root.at("//*[@id = '#{t['target']}']")) || next
38
- if target&.at("./ancestor-or-self::*[@obligation = 'normative']")
39
+ if target.at("./ancestor-or-self::*[@obligation = 'normative']")
39
40
  @log.add("Style", t,
40
41
  "'see #{t['target']}' is pointing to a normative section")
41
42
  end
@@ -75,6 +76,30 @@ module Metanorma
75
76
  regex.match(text) && @log.add("Style", elem, "#{term}: #{msg}")
76
77
  end
77
78
 
79
+ # https://www.iso.org/ISO-house-style.html#iso-hs-s-text-r-r-ref_clause3
80
+ def term_xrefs_validate(xmldoc)
81
+ termids = xmldoc
82
+ .xpath("//sections/terms | //sections/clause[.//terms] | "\
83
+ "//annex[.//terms]").each_with_object({}) do |t, m|
84
+ t.xpath(".//*/@id").each { |a| m[a.text] = true }
85
+ t.name == "terms" and m[t["id"]] = true
86
+ end
87
+ xmldoc.xpath(".//xref").each do |x|
88
+ term_xrefs_validate1(x, termids)
89
+ end
90
+ end
91
+
92
+ def term_xrefs_validate1(xref, termids)
93
+ (termids[xref["target"]] && !termids[xref.parent["id"]]) and
94
+ @log.add("Style", xref,
95
+ "only terms clauses can cross-reference terms clause "\
96
+ "(#{xref['target']})")
97
+ (!termids[xref["target"]] && termids[xref.parent["id"]]) and
98
+ @log.add("Style", xref,
99
+ "non-terms clauses cannot cross-reference terms clause "\
100
+ "(#{xref['target']})")
101
+ end
102
+
78
103
  # ISO/IEC DIR 2, 16.5.6
79
104
  def termdef_style(xmldoc)
80
105
  xmldoc.xpath("//term").each do |t|
@@ -139,11 +164,14 @@ module Metanorma
139
164
  onlychild_clause_validate(doc.root)
140
165
  termdef_style(doc.root)
141
166
  see_xrefs_validate(doc.root)
167
+ term_xrefs_validate(doc.root)
142
168
  see_erefs_validate(doc.root)
143
169
  locality_erefs_validate(doc.root)
144
170
  bibdata_validate(doc.root)
145
171
  bibitem_validate(doc.root)
146
172
  figure_validate(doc.root)
173
+ listcount_validate(doc)
174
+ list_punctuation(doc)
147
175
  end
148
176
 
149
177
  def bibitem_validate(xmldoc)
@@ -78,9 +78,9 @@ module Metanorma
78
78
  xmldoc.xpath("//image").each do |i|
79
79
  next if i["src"].start_with?("data:")
80
80
 
81
- if /^ISO_\d+_/.match?(File.basename(i["src"]))
82
- elsif /^(SL)?#{prefix}fig/.match?(File.basename(i["src"]))
83
- image_name_validate1(i, prefix)
81
+ case File.basename(i["src"])
82
+ when /^ISO_\d+_/
83
+ when /^(SL)?#{prefix}fig/ then image_name_validate1(i, prefix)
84
84
  else
85
85
  @log.add("Style", i,
86
86
  "image name #{i['src']} does not match DRG requirements: expect #{prefix}fig")
@@ -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,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.4".freeze
3
+ VERSION = "2.0.5".freeze
4
4
  end
5
5
  end