Class: Metanorma::ISO::Converter

Inherits:
Standoc::Converter
  • Object
show all
Defined in:
lib/metanorma/iso/converter.rb,
lib/metanorma/iso/base.rb,
lib/metanorma/iso/front.rb,
lib/metanorma/iso/cleanup.rb,
lib/metanorma/iso/section.rb,
lib/metanorma/iso/front_id.rb,
lib/metanorma/iso/validate.rb,
lib/metanorma/iso/validate_list.rb,
lib/metanorma/iso/validate_image.rb,
lib/metanorma/iso/validate_style.rb,
lib/metanorma/iso/validate_title.rb,
lib/metanorma/iso/validate_section.rb,
lib/metanorma/iso/validate_requirements.rb

Overview

A Converter implementation that generates ISO output, and a document schema encapsulation of the document for validation

Constant Summary collapse

XML_ROOT_TAG =
"iso-standard".freeze
XML_NAMESPACE =
"https://www.metanorma.org/ns/iso".freeze
PRE_NORMREF_FOOTNOTES =
"//preface//fn | "\
"//clause[@type = 'scope']//fn".freeze
NORMREF_FOOTNOTES =
"//references[@normative = 'true']//fn".freeze
POST_NORMREF_FOOTNOTES =
"//sections//clause[not(@type = 'scope')]//fn | "\
"//annex//fn | //references[@normative = 'false']//fn".freeze
TERM_CLAUSE =
"//sections//terms | "\
        "//sections//clause[descendant::terms][not(descendant::definitions)]"
.freeze
PUBLISHER =
"./contributor[role/@type = 'publisher']/organization".freeze
OTHERIDS =
"@type = 'DOI' or @type = 'metanorma' or @type = 'ISSN' or "\
"@type = 'ISBN'".freeze
DEFAULT_EDGROUP_TYPE =
{ "technical-committee": "TC",
subcommittee: "SC", workgroup: "WG" }.freeze
STAGE_ABBRS =
{
  "00": "PWI",
  "10": "NP",
  "20": "WD",
  "30": "CD",
  "40": "DIS",
  "50": "FDIS",
  "60": "IS",
  "90": "(Review)",
  "95": "(Withdrawal)",
}.freeze
STAGE_NAMES =
{
  "00": "Preliminary work item",
  "10": "New work item proposal",
  "20": "Working draft",
  "30": "Committee draft",
  "40": "Draft",
  "50": "Final draft",
  "60": "International standard",
  "90": "Review",
  "95": "Withdrawal",
}.freeze
SI_UNIT =

leaving out as problematic: N J K C S T H h d B o E

"(m|cm|mm|km|μm|nm|g|kg|mgmol|cd|rad|sr|Hz|Hz|MHz|Pa|hPa|kJ|"\
"V|kV|W|MW|kW|F|μF|Ω|Wb|°C|lm|lx|Bq|Gy|Sv|kat|l|t|eV|u|Np|Bd|"\
"bit|kB|MB|Hart|nat|Sh|var)".freeze
NONSTD_UNITS =
{
  sec: "s", mins: "min", hrs: "h", hr: "h", cc: "cm^3",
  lit: "l", amp: "A", amps: "A", rpm: "r/min"
}.freeze
ASSETS_TO_STYLE =
"//termsource | //formula | //termnote | "\
"//p[not(ancestor::boilerplate)] | //li[not(p)] | //dt | "\
"//dd[not(p)] | //td[not(p)] | //th[not(p)]".freeze
ONE_SYMBOLS_WARNING =
"Only one Symbols and Abbreviated "\
"Terms section in the standard".freeze
NON_DL_SYMBOLS_WARNING =
"Symbols and Abbreviated Terms can "\
"only contain a definition list".freeze
SEQ =

spec of permissible section sequence we skip normative references, it goes to end of list

[
  { msg: "Initial section must be (content) Foreword",
    val: ["./self::foreword"] },
  { msg: "Prefatory material must be followed by (clause) Scope",
    val: ["./self::introduction", "./self::clause[@type = 'scope']"] },
  { msg: "Prefatory material must be followed by (clause) Scope",
    val: ["./self::clause[@type = 'scope']"] },
  { msg: "Normative References must be followed by "\
         "Terms and Definitions",
    val: ["./self::terms | .//terms"] },
].freeze
SECTIONS_XPATH =
"//foreword | //introduction | //sections/terms | .//annex | "\
"//sections/definitions | //sections/clause | "\
"//references[not(parent::clause)] | "\
"//clause[descendant::references][not(parent::clause)]".freeze
NORM_ISO_WARN =
"non-ISO/IEC reference not expected as normative".freeze
SCOPE_WARN =
"Scope contains subclauses: should be succinct".freeze
NORM_BIBITEMS =
"//references[@normative = 'true']/bibitem".freeze
REQUIREMENT_RE_STR =
<<~REGEXP.freeze
  \\b
   ( shall | (is|are)_to |
     (is|are)_required_(not_)?to |
     (is|are)_required_that |
     has_to |
     only\\b[^.,]+\\b(is|are)_permitted |
     it_is_necessary |
     (is|are)_not_(allowed | permitted |
                   acceptable | permissible) |
     (is|are)_not_to_be |
     [.,:;]_do_not )
  \\b
REGEXP
RECOMMENDATION_RE_STR =
<<~REGEXP.freeze
  \\b
      should |
      ought_(not_)?to |
      it_is_(not_)?recommended_that
  \\b
REGEXP
PERMISSION_RE_STR =
<<~REGEXP.freeze
  \\b
       may |
      (is|are)_(permitted | allowed | permissible ) |
      it_is_not_required_that |
      no\\b[^.,]+\\b(is|are)_required
  \\b
REGEXP
POSSIBILITY_RE_STR =
<<~REGEXP.freeze
  \\b
     can | cannot | be_able_to |
     there_is_a_possibility_of |
     it_is_possible_to | be_unable_to |
     there_is_no_possibility_of |
     it_is_not_possible_to
  \\b
REGEXP

Instance Method Summary collapse

Instance Method Details

#add_amd_parts(docnum, node) ⇒ Object



165
166
167
168
169
170
171
172
# File 'lib/metanorma/iso/front_id.rb', line 165

def add_amd_parts(docnum, node)
  case doctype(node)
  when "amendment"
    "#{docnum}/Amd #{node.attr('amendment-number')}"
  when "technical-corrigendum"
    "#{docnum}/Cor.#{node.attr('corrigendum-number')}"
  end
end

#add_id_parts(docnum, part, subpart) ⇒ Object



200
201
202
203
204
# File 'lib/metanorma/iso/front_id.rb', line 200

def add_id_parts(docnum, part, subpart)
  docnum += "-#{part}" if part
  docnum += "-#{subpart}" if subpart
  docnum
end

#admonition_name(node) ⇒ Object



64
65
66
67
68
69
70
# File 'lib/metanorma/iso/base.rb', line 64

def admonition_name(node)
  name = super
  a = node.attr("type") and ["editorial"].each do |t|
    name = t if a.casecmp(t).zero?
  end
  name
end

#appendix_parse(attrs, xml, node) ⇒ Object



17
18
19
20
21
22
23
24
# File 'lib/metanorma/iso/section.rb', line 17

def appendix_parse(attrs, xml, node)
  attrs[:"inline-header"] = node.option? "inline-header"
  set_obligation(attrs, node)
  xml.appendix **attr_code(attrs) do |xml_section|
    xml_section.title { |name| name << node.title }
    xml_section << node.content
  end
end

#approval_groups_rename(xmldoc) ⇒ Object



219
220
221
222
223
# File 'lib/metanorma/iso/cleanup.rb', line 219

def approval_groups_rename(xmldoc)
  %w(technical-committee subcommittee workgroup).each do |v|
    xmldoc.xpath("//bibdata//approval-#{v}").each { |a| a.name = v }
  end
end

#asset_style(root) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
# File 'lib/metanorma/iso/validate_style.rb', line 187

def asset_style(root)
  root.xpath("//example | //termexample").each { |e| example_style(e) }
  root.xpath("//definition/verbal-definition").each do |e|
    definition_style(e)
  end
  root.xpath("//note").each { |e| note_style(e) }
  root.xpath("//fn").each { |e| footnote_style(e) }
  root.xpath(ASSETS_TO_STYLE).each { |e| style(e, extract_text(e)) }
  norm_bibitem_style(root)
  super
end

#bibdata_cleanup(xmldoc) ⇒ Object



212
213
214
215
216
217
# File 'lib/metanorma/iso/cleanup.rb', line 212

def bibdata_cleanup(xmldoc)
  super
  approval_groups_rename(xmldoc)
  editorial_groups_agency(xmldoc)
  editorial_group_types(xmldoc)
end

#bibdata_validate(doc) ⇒ Object



153
154
155
156
157
158
159
# File 'lib/metanorma/iso/validate.rb', line 153

def bibdata_validate(doc)
  doctype_validate(doc)
  script_validate(doc)
  stage_validate(doc)
  substage_validate(doc)
  iteration_validate(doc)
end

#bibitem_cleanup(xmldoc) ⇒ Object



144
145
146
147
148
# File 'lib/metanorma/iso/cleanup.rb', line 144

def bibitem_cleanup(xmldoc)
  super
  unpublished_note(xmldoc)
  withdrawn_note(xmldoc)
end

#bibitem_validate(xmldoc) ⇒ Object



178
179
180
181
182
183
184
185
# File 'lib/metanorma/iso/validate.rb', line 178

def bibitem_validate(xmldoc)
  xmldoc.xpath("//bibitem[date/on = '–']").each do |b|
    b.at("./note[@type = 'Unpublished-Status']") or
      @log.add("Style", b,
               "Reference #{b&.at('./@id')&.text} does not have an "\
               "associated footnote indicating unpublished status")
  end
end

#boilerplate_file(_xmldoc) ⇒ Object



121
122
123
124
125
126
127
128
# File 'lib/metanorma/iso/cleanup.rb', line 121

def boilerplate_file(_xmldoc)
  file = case @lang
         when "fr" then "boilerplate-fr.xml"
         when "ru" then "boilerplate-ru.xml"
         else "boilerplate.xml"
         end
  File.join(@libdir, file)
end

#clause_parse(attrs, xml, node) ⇒ Object



7
8
9
10
# File 'lib/metanorma/iso/section.rb', line 7

def clause_parse(attrs, xml, node)
  node.option? "appendix" and return appendix_parse(attrs, xml, node)
  super
end

#content_validate(doc) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/metanorma/iso/validate.rb', line 161

def content_validate(doc)
  super
  title_validate(doc.root)
  isosubgroup_validate(doc.root)
  onlychild_clause_validate(doc.root)
  termdef_style(doc.root)
  see_xrefs_validate(doc.root)
  term_xrefs_validate(doc.root)
  see_erefs_validate(doc.root)
  locality_erefs_validate(doc.root)
  bibdata_validate(doc.root)
  bibitem_validate(doc.root)
  figure_validate(doc.root)
  listcount_validate(doc)
  list_punctuation(doc)
end

#cover_stage_abbr(node) ⇒ Object



229
230
231
232
233
234
235
236
237
# File 'lib/metanorma/iso/front_id.rb', line 229

def cover_stage_abbr(node)
  stage = get_stage(node)
  abbr = id_stage_abbr(get_stage(node), get_substage(node), node, true)
  typeabbr = get_typeabbr(node, amd: true)
  if stage.to_i > 50 || (stage.to_i == 60 && get_substage(node).to_i < 60)
    typeabbr = ""
  end
  "#{abbr}#{typeabbr}".strip
end

#definition_style(node) ⇒ Object

ISO/IEC DIR 2, 16.5.6



41
42
43
44
45
46
# File 'lib/metanorma/iso/validate_style.rb', line 41

def definition_style(node)
  return if @novalid

  r = requirement_check(extract_text(node))
  style_warning(node, "Definition may contain requirement", r) if r
end

#disjunct_error(img, cond1, cond2, msg1, msg2) ⇒ Object



40
41
42
43
44
45
# File 'lib/metanorma/iso/validate_image.rb', line 40

def disjunct_error(img, cond1, cond2, msg1, msg2)
  cond1 && !cond2 and
    @log.add("Style", img, "image name #{img['src']} #{msg1}")
  !cond1 && cond2 and
    @log.add("Style", img, "image name #{img['src']} #{msg2}")
end

#doc_converter(node) ⇒ Object



28
29
30
# File 'lib/metanorma/iso/base.rb', line 28

def doc_converter(node)
  IsoDoc::Iso::WordConvert.new(doc_extract_attributes(node))
end

#doc_extract_attributes(node) ⇒ Object



32
33
34
# File 'lib/metanorma/iso/base.rb', line 32

def doc_extract_attributes(node)
  super.merge(isowordtemplate: node.attr("iso-word-template"))
end

#docidentifier_cleanup(xmldoc) ⇒ Object

ISO as a prefix goes first



45
46
47
48
49
50
51
52
53
54
55
# File 'lib/metanorma/iso/cleanup.rb', line 45

def docidentifier_cleanup(xmldoc)
  prefix = get_id_prefix(xmldoc)
  id = xmldoc.at("//bibdata/docidentifier[@type = 'ISO']") or return
  id.content = id_prefix(prefix, id)
  id = xmldoc.at("//bibdata/ext/structuredidentifier/project-number") and
    id.content = id_prefix(prefix, id)
  %w(iso-with-lang iso-reference iso-undated).each do |t|
    id = xmldoc.at("//bibdata/docidentifier[@type = '#{t}']") and
      id.content = id_prefix(prefix, id)
  end
end

#doctype_validate(xmldoc) ⇒ Object



116
117
118
119
120
121
122
123
# File 'lib/metanorma/iso/validate.rb', line 116

def doctype_validate(xmldoc)
  doctype = xmldoc&.at("//bibdata/ext/doctype")&.text
  %w(international-standard technical-specification technical-report
     publicly-available-specification international-workshop-agreement
     guide amendment technical-corrigendum).include? doctype or
    @log.add("Document Attributes", nil,
             "#{doctype} is not a recognised document type")
end

#editorial_group_types(xmldoc) ⇒ Object



247
248
249
250
251
252
253
254
255
# File 'lib/metanorma/iso/cleanup.rb', line 247

def editorial_group_types(xmldoc)
  %w(technical-committee subcommittee workgroup).each do |v|
    xmldoc.xpath("//bibdata//#{v} | //bibdata//approval-#{v}").each do |g|
      next if g["type"]

      g["type"] = DEFAULT_EDGROUP_TYPE[v.to_sym]
    end
  end
end

#editorial_groups_agency(xmldoc) ⇒ Object



225
226
227
228
229
230
231
232
233
234
# File 'lib/metanorma/iso/cleanup.rb', line 225

def editorial_groups_agency(xmldoc)
  pubs = extract_publishers(xmldoc)
  xmldoc.xpath("//bibdata/ext/editorialgroup").each do |e|
    pubs.reverse.each do |p|
      if e.children.empty? then e << "<agency>#{p}</agency>"
      else e.children.first.previous = "<agency>#{p}</agency>"
      end
    end
  end
end

#example_style(node) ⇒ Object

ISO/IEC DIR 2, 16.5.7 ISO/IEC DIR 2, 25.5



50
51
52
53
54
55
# File 'lib/metanorma/iso/validate_style.rb', line 50

def example_style(node)
  return if @novalid

  style_no_guidance(node, extract_text(node), "Example")
  style(node, extract_text(node))
end

#external_constraint(text) ⇒ Object



94
95
96
97
98
99
# File 'lib/metanorma/iso/validate_requirements.rb', line 94

def external_constraint(text)
  text.split(/\.\s+/).each do |t|
    return t if /\b(must)\b/xi.match? t
  end
  nil
end

#extract_publishers(xmldoc) ⇒ Object



236
237
238
239
240
241
242
# File 'lib/metanorma/iso/cleanup.rb', line 236

def extract_publishers(xmldoc)
  xmldoc.xpath("//bibdata/contributor[role/@type = 'publisher']/"\
               "organization").each_with_object([]) do |p, m|
    x = p.at("./abbreviation") || p.at("./name") or next
    m << x.text
  end
end

#extract_text(node) ⇒ Object



8
9
10
11
12
13
14
15
16
# File 'lib/metanorma/iso/validate_style.rb', line 8

def extract_text(node)
  return "" if node.nil?

  node1 = Nokogiri::XML.fragment(node.to_s)
  node1.xpath("//link | //locality | //localityStack").each(&:remove)
  ret = ""
  node1.traverse { |x| ret += x.text if x.text? }
  HTMLEntities.new.decode(ret)
end

#figure_validate(xmldoc) ⇒ Object



91
92
93
94
# File 'lib/metanorma/iso/validate_image.rb', line 91

def figure_validate(xmldoc)
  image_name_validate(xmldoc)
  subfigure_validate(xmldoc)
end

#footnote_cleanup(xmldoc) ⇒ Object



130
131
132
133
# File 'lib/metanorma/iso/cleanup.rb', line 130

def footnote_cleanup(xmldoc)
  unpub_footnotes(xmldoc)
  super
end

#footnote_style(node) ⇒ Object

ISO/IEC DIR 2, 26.5



66
67
68
69
70
71
# File 'lib/metanorma/iso/validate_style.rb', line 66

def footnote_style(node)
  return if @novalid

  style_no_guidance(node, extract_text(node), "Footnote")
  style(node, extract_text(node))
end

#foreword_style(node) ⇒ Object

ISO/IEC DIR 2, 12.2



19
20
21
22
23
# File 'lib/metanorma/iso/validate_style.rb', line 19

def foreword_style(node)
  return if @novalid

  style_no_guidance(node, extract_text(node), "Foreword")
end

#foreword_validate(root) ⇒ Object

ISO/IEC DIR 2, 12.4



22
23
24
25
26
# File 'lib/metanorma/iso/validate_section.rb', line 22

def foreword_validate(root)
  f = root.at("//foreword") || return
  s = f.at("./clause")
  @log.add("Style", f, "foreword contains subclauses") unless s.nil?
end

#format_ref(ref, type) ⇒ Object



57
58
59
60
# File 'lib/metanorma/iso/cleanup.rb', line 57

def format_ref(ref, type)
  ref = ref.sub(/ \(All Parts\)/i, "")
  super
end

#get_id_prefix(xmldoc) ⇒ Object



35
36
37
38
39
40
41
42
# File 'lib/metanorma/iso/cleanup.rb', line 35

def get_id_prefix(xmldoc)
  xmldoc.xpath("//bibdata/contributor[role/@type = 'publisher']"\
               "/organization").each_with_object([]) do |x, prefix|
    x1 = x.at("abbreviation")&.text || x.at("name")&.text
    # (x1 == "ISO" and prefix.unshift("ISO")) or prefix << x1
    prefix << x1
  end
end

#get_stage(node) ⇒ Object



284
285
286
287
288
289
# File 'lib/metanorma/iso/front_id.rb', line 284

def get_stage(node)
  a = node.attr("status")
  a = node.attr("docstage") if a.nil? || a.empty?
  a = "60" if a.nil? || a.empty?
  a
end

#get_substage(node) ⇒ Object



291
292
293
294
295
296
# File 'lib/metanorma/iso/front_id.rb', line 291

def get_substage(node)
  stage = get_stage(node)
  ret = node.attr("docsubstage")
  ret = (stage == "60" ? "60" : "00") if ret.nil? || ret.empty?
  ret
end

#get_typeabbr(node, amd: false) ⇒ Object



92
93
94
95
96
97
98
99
100
# File 'lib/metanorma/iso/front_id.rb', line 92

def get_typeabbr(node, amd: false)
  case doctype(node)
  when "directive" then "DIR "
  when "technical-report" then "TR "
  when "technical-specification" then "TS "
  when "amendment" then (amd ? "Amd " : "")
  when "technical-corrigendum" then (amd ? "Cor " : "")
  end
end

#html_converter(node) ⇒ Object



19
20
21
# File 'lib/metanorma/iso/base.rb', line 19

def html_converter(node)
  IsoDoc::Iso::HtmlConvert.new(html_extract_attributes(node))
end

#html_converter_alt(node) ⇒ Object



23
24
25
26
# File 'lib/metanorma/iso/base.rb', line 23

def html_converter_alt(node)
  IsoDoc::Iso::HtmlConvert.new(html_extract_attributes(node)
                               .merge(alt: true))
end

#id_add_year(docnum, node) ⇒ Object



277
278
279
280
281
282
# File 'lib/metanorma/iso/front_id.rb', line 277

def id_add_year(docnum, node)
  year = node.attr("copyright-year")
  @amd and year ||= node.attr("updated-date")&.sub(/-.*$/, "")
  docnum += ":#{year}" if year
  docnum
end

#id_langsuffix(docnum, node) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
# File 'lib/metanorma/iso/front_id.rb', line 174

def id_langsuffix(docnum, node)
  lang = node.attr("language") || "en"
  suffix = case lang
           when "en" then "(E)"
           when "fr" then "(F)"
           when "ru" then "(R)"
           else
             "(X)"
           end
  "#{docnum}#{suffix}"
end

#id_prefix(prefix, id) ⇒ Object



28
29
30
31
32
33
# File 'lib/metanorma/iso/cleanup.rb', line 28

def id_prefix(prefix, id)
  # we're just inheriting the prefixes from parent doc
  return id.text if @amd

  prefix.join("/") + (id.text.match?(%{^/}) ? "" : " ") + id.text
end

#id_stage_abbr(stage, substage, node, bare = false) ⇒ Object



206
207
208
209
210
211
212
213
214
# File 'lib/metanorma/iso/front_id.rb', line 206

def id_stage_abbr(stage, substage, node, bare = false)
  ret = id_stage_abbr1(stage, substage, node, bare)
  if %w(amendment technical-corrigendum technical-report
        technical-specification).include?(doctype(node)) &&
      !%w(D FD).include?(ret)
    ret = "#{ret} "
  end
  ret
end

#id_stage_abbr1(stage, substage, node, bare) ⇒ Object



216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/metanorma/iso/front_id.rb', line 216

def id_stage_abbr1(stage, substage, node, bare)
  if bare
    IsoDoc::Iso::Metadata.new("en", "Latn", @i18n)
      .status_abbrev(stage_abbr(stage, substage, doctype(node)),
                     substage, nil, nil, doctype(node))
  else
    IsoDoc::Iso::Metadata.new("en", "Latn", @i18n)
      .status_abbrev(stage_abbr(stage, substage, doctype(node)),
                     substage, node.attr("iteration"),
                     node.attr("draft"), doctype(node))
  end
end

#id_stage_prefix(docnum, node) ⇒ Object



239
240
241
242
243
244
245
246
247
248
# File 'lib/metanorma/iso/front_id.rb', line 239

def id_stage_prefix(docnum, node)
  stage = get_stage(node)
  typeabbr = get_typeabbr(node)
  if stage && (stage.to_i < 60)
    docnum = unpub_stage_prefix(docnum, stage, typeabbr, node)
  elsif typeabbr == "DIR " then docnum = "#{typeabbr}#{docnum}"
  elsif typeabbr && !@amd then docnum = "/#{typeabbr}#{docnum}"
  end
  docnum
end

#id_year(docnum, node, mode: :default) ⇒ Object



250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/metanorma/iso/front_id.rb', line 250

def id_year(docnum, node, mode: :default)
  case mode
  when :strip then docnum.sub(/:(19|20)\d\d(?!\d)/, "")
  when :force then id_add_year(docnum, node)
  else
    stage = get_stage(node)
    if stage && (stage.to_i < 60)
      docnum
    else id_add_year(docnum, node)
    end
  end
end

#image_name_parse(img, prefix) ⇒ Object



47
48
49
50
51
52
53
54
55
# File 'lib/metanorma/iso/validate_image.rb', line 47

def image_name_parse(img, prefix)
  m = %r[(SL)?#{prefix}fig(?<tab>Tab)?(?<annex>[A-Z])?(Text)?(?<num>\d+)
      (?<subfig>[a-z])?(?<key>_key\d+)?(?<lang>_[a-z])?$]x
    .match(File.basename(img["src"], ".*"))
  m.nil? and
    @log.add("Style", img,
             "image name #{img['src']} does not match DRG requirements")
  m
end

#image_name_prefix(xmldoc) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
# File 'lib/metanorma/iso/validate_image.rb', line 15

def image_name_prefix(xmldoc)
  std = xmldoc&.at("//bibdata/ext/structuredidentifier/project-number") or
    return
  num = xmldoc&.at("//bibdata/docnumber")&.text or return
  ed = xmldoc&.at("//bibdata/edition")&.text || "1"
  prefix = num
  std["part"] and prefix += "-#{std['part']}"
  prefix += "_ed#{ed}"
  amd = std["amendment"] and prefix += "amd#{amd}"
  prefix
end

#image_name_suffix(xmldoc) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/metanorma/iso/validate_image.rb', line 27

def image_name_suffix(xmldoc)
  case xmldoc&.at("//bibdata/language")&.text
  when "fr" then "_f"
  when "de" then "_d"
  when "ru" then "_r"
  when "es" then "_s"
  when "ar" then "_a"
  # when "en" then "_e"
  else
    "_e"
  end
end

#image_name_validate(xmldoc) ⇒ Object

DRG directives 3.2



76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/metanorma/iso/validate_image.rb', line 76

def image_name_validate(xmldoc)
  prefix = image_name_prefix(xmldoc) or return
  xmldoc.xpath("//image").each do |i|
    next if i["src"].start_with?("data:")

    case File.basename(i["src"])
    when /^ISO_\d+_/
    when /^(SL)?#{prefix}fig/ then image_name_validate1(i, prefix)
    else
      @log.add("Style", i,
               "image name #{i['src']} does not match DRG requirements: expect #{prefix}fig")
    end
  end
end

#image_name_validate1(i, prefix) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/metanorma/iso/validate_image.rb', line 57

def image_name_validate1(i, prefix)
  m = image_name_parse(i, prefix) or return
  warn i["src"]
  disjunct_error(i, i.at("./ancestor::table"), !m[:tab].nil?,
                 "is under a table but is not so labelled",
                 "is labelled as under a table but is not")
  disjunct_error(i, i.at("./ancestor::annex"), !m[:annex].nil?,
                 "is under an annex but is not so labelled",
                 "is labelled as under an annex but is not")
  disjunct_error(i, i.xpath("./ancestor::figure").size > 1, !m[:subfig].nil?,
                 "does not have a subfigure letter but is a subfigure",
                 "has a subfigure letter but is not a subfigure")
  lang = image_name_suffix(i.document.root)
  (m[:lang] || "_e") == lang or
    @log.add("Style", i,
             "image name #{i['src']} expected to have suffix #{lang}")
end

#init(node) ⇒ Object



52
53
54
55
56
# File 'lib/metanorma/iso/base.rb', line 52

def init(node)
  super
  @amd = %w(amendment technical-corrigendum).include? doctype(node)
  @vocab = node.attr("docsubtype") == "vocabulary"
end

#insert_unpub_note(biblio, msg) ⇒ Object



187
188
189
190
# File 'lib/metanorma/iso/cleanup.rb', line 187

def insert_unpub_note(biblio, msg)
  biblio.at("./language | ./script | ./abstract | ./status")
    .previous = %(<note type="Unpublished-Status"><p>#{msg}</p></note>)
end

#introduction_style(node) ⇒ Object

ISO/IEC DIR 2, 13.2



33
34
35
36
37
38
# File 'lib/metanorma/iso/validate_style.rb', line 33

def introduction_style(node)
  return if @novalid

  r = requirement_check(extract_text(node))
  style_warning(node, "Introduction may contain requirement", r) if r
end

#iso_id(node, xml) ⇒ Object



73
74
75
76
77
78
79
80
81
# File 'lib/metanorma/iso/front_id.rb', line 73

def iso_id(node, xml)
  (!@amd && node.attr("docnumber")) || (@amd && node.attr("updates")) or
    return
  dn = id_stage_prefix(iso_id1(node), node)
  dns = [id_year(dn, node, mode: :default),
         id_year(dn, node, mode: :force),
         id_year(dn, node, mode: :strip)]
  iso_id_out(node, xml, dns)
end

#iso_id1(node) ⇒ Object

def get_typeabbr(node, amd: false)

  case doctype(node)
  when "directive" then "DIR"
  when "technical-report" then "TR"
  when "technical-specification" then "TS"
  else nil
  end
end

def iso_id(node, xml)
  (!@amd && node.attr("docnumber")) || (@amd && node.attr("updates")) or
    return
  stage = id_stage_abbr(get_stage(node), get_substage(node), node, true)&.strip
  stage = nil if %w{IS (Review) (Withdrwal)}.include?(stage.strip)
    urn_stage = "#{get_stage(node)}.#{get_substage(node)}"

pub = (node.attr(“publisher”) || “ISO”).split(//)

  params = {
    number: node.attr("docnumber"), # (@amd ? node.attr("updates") : node.attr("docnumber")),
    part: node.attr("partnumber"),
    language: node.attr("language") || "en",
    type: get_typeabbr(node),
    year: node.attr("copyright-year") || node.attr("updated-date")&.sub(/-.*$/, ""),
    publisher: pub[0],
    copublisher: pub[1..-1],
  }.compact
  if a = node.attr("amendment-number")
    params[:amendments] = { number: a, stage: stage }
    elsif a = node.attr("corrigendum-number")
    params[:corrigendums] = { number: a, stage: stage }
    else
      params.merge!( { stage: stage, urn_stage: urn_stage }.compact )
    end
  iso_id_out(xml, params)
end

def iso_id_out(xml, params)
  params_nolang = params.dup.tap { |hs| hs.delete(:language) }
  unpub = /^[0-5]/.match?(params[:urn_stage])
  params1 = unpub ? params_nolang.dup.tap { |hs| hs.delete(:year) } : params_nolang
  xml.docidentifier Pubid::Iso::Identifier.new(**params1), **attr_code(type: "ISO")
  params2 = params_nolang.dup.tap { |hs| hs.delete(:year) }
  xml.docidentifier Pubid::Iso::Identifier.new(**params2), **attr_code(type: "iso-undated")
  params1 = unpub ? params.dup.tap { |hs| hs.delete(:year) } : params
  xml.docidentifier(Pubid::Iso::Identifier.new(**params1),
                    **attr_code(type: "iso-with-lang"))
  warn params
  warn "Generated: #{Pubid::Iso::Identifier.new(**params).to_s}"
  xml.docidentifier(Pubid::Iso::Identifier.new(**params),
                    **attr_code(type: "iso-reference"))
end


155
156
157
158
159
160
161
162
163
# File 'lib/metanorma/iso/front_id.rb', line 155

def iso_id1(node)
  if @amd
    dn = node.attr("updates")
    add_amd_parts(dn, node)
  else
    part, subpart = node&.attr("partnumber")&.split(/-/)
    add_id_parts(node.attr("docnumber"), part, subpart)
  end
end

#iso_id_out(node, xml, dns) ⇒ Object



83
84
85
86
87
88
89
90
# File 'lib/metanorma/iso/front_id.rb', line 83

def iso_id_out(node, xml, dns)
  xml.docidentifier dns[0], **attr_code(type: "ISO")
  xml.docidentifier dns[2], **attr_code(type: "iso-undated")
  xml.docidentifier(id_langsuffix(dns[0], node),
                    **attr_code(type: "iso-with-lang"))
  xml.docidentifier(id_langsuffix(dns[1], node),
                    **attr_code(type: "iso-reference"))
end

#isosubgroup_validate(root) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/metanorma/iso/validate.rb', line 15

def isosubgroup_validate(root)
  root.xpath("//technical-committee/@type").each do |t|
    unless %w{TC PC JTC JPC}.include? t.text
      @log.add("Document Attributes", nil,
               "invalid technical committee type #{t}")
    end
  end
  root.xpath("//subcommittee/@type").each do |t|
    unless %w{SC JSC}.include? t.text
      @log.add("Document Attributes", nil,
               "invalid subcommittee type #{t}")
    end
  end
end

#iteration_validate(xmldoc) ⇒ Object



146
147
148
149
150
151
# File 'lib/metanorma/iso/validate.rb', line 146

def iteration_validate(xmldoc)
  iteration = xmldoc&.at("//bibdata/status/iteration")&.text or return
  /^\d+/.match(iteration) or
    @log.add("Document Attributes", nil,
             "#{iteration} is not a recognised iteration")
end

#li_depth_validate(doc) ⇒ Object



24
25
26
27
28
29
# File 'lib/metanorma/iso/validate_list.rb', line 24

def li_depth_validate(doc)
  doc.xpath("//li//li//li//li").each do |l|
    l.at(".//li") and
      style_warning(l, "List more than four levels deep", nil)
  end
end

#list_after_colon_punctuation(list, entries) ⇒ Object

if first list entry starts lowercase, treat as sentence broken up



68
69
70
71
72
73
74
75
76
77
# File 'lib/metanorma/iso/validate_list.rb', line 68

def list_after_colon_punctuation(list, entries)
  lower = starts_lowercase?(list.at(".//li").text)
  entries.each_with_index do |li, i|
    if lower
      list_semicolon_phrase(li, i == entries.size - 1)
    else
      list_full_sentence(li)
    end
  end
end

#list_full_sentence(elem) ⇒ Object



100
101
102
103
104
105
106
107
108
109
# File 'lib/metanorma/iso/validate_list.rb', line 100

def list_full_sentence(elem)
  text = elem.text.strip
  starts_uppercase?(text) or
    style_warning(elem, "List entry of separate sentences must start "\
                        "with uppercase letter", text)
  punct = text.strip.sub(/^.*?(\S)$/m, "\\1")
  punct == "." or
    style_warning(elem, "List entry of separate sentences must "\
                        "end with full stop", text)
end

#list_punctuation(doc) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
# File 'lib/metanorma/iso/validate_list.rb', line 32

def list_punctuation(doc)
  return if @novalid

  ((doc.xpath("//ol") - doc.xpath("//ul//ol | //ol//ol")) +
   (doc.xpath("//ul") - doc.xpath("//ul//ul | //ol//ul"))).each do |list|
    next if skip_list_punctuation(list)

    prec = list.at("./preceding::text()[normalize-space(.) != ''][1]")
    list_punctuation1(list, prec&.text)
  end
end

#list_punctuation1(list, prectext) ⇒ Object



56
57
58
59
60
61
62
63
64
65
# File 'lib/metanorma/iso/validate_list.rb', line 56

def list_punctuation1(list, prectext)
  prectext ||= ""
  entries = list.xpath(".//li")
  case prectext.strip.chars.last
  when ":", "" then list_after_colon_punctuation(list, entries)
  when "." then entries.each { |li| list_full_sentence(li) }
  else style_warning(list, "All lists must be preceded by "\
                           "colon or full stop", prectext)
  end
end

#list_semicolon_phrase(elem, last) ⇒ Object



79
80
81
82
83
84
85
# File 'lib/metanorma/iso/validate_list.rb', line 79

def list_semicolon_phrase(elem, last)
  text = elem.text.strip
  starts_lowercase?(text) or
    style_warning(elem, "List entry of broken up sentence must start "\
                        "with lowercase letter", text)
  list_semicolon_phrase_punct(elem, text, last)
end

#list_semicolon_phrase_punct(elem, text, last) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/metanorma/iso/validate_list.rb', line 87

def list_semicolon_phrase_punct(elem, text, last)
  punct = text.strip.sub(/^.*?(\S)$/m, "\\1")
  if last
    punct == "." or
      style_warning(elem, "Final list entry of broken up "\
                          "sentence must end with full stop", text)
  else
    punct == ";" or
      style_warning(elem, "List entry of broken up sentence must "\
                          "end with semicolon", text)
  end
end

#listcount_validate(doc) ⇒ Object



5
6
7
8
9
10
# File 'lib/metanorma/iso/validate_list.rb', line 5

def listcount_validate(doc)
  return if @novalid

  ol_count_validate(doc)
  li_depth_validate(doc)
end

#locality_erefs_validate(root) ⇒ Object

ISO/IEC DIR 2, 10.4



64
65
66
67
68
69
70
71
72
73
# File 'lib/metanorma/iso/validate.rb', line 64

def locality_erefs_validate(root)
  root.xpath("//eref[descendant::locality]").each do |t|
    if /^(ISO|IEC)/.match?(t["citeas"]) &&
        !/: ?(\d+{4}|–)$/.match?(t["citeas"])
      @log.add("Style", t,
               "undated reference #{t['citeas']} should not contain "\
               "specific elements")
    end
  end
end

#metadata_approval_agency(xml, list) ⇒ Object



116
117
118
119
120
121
# File 'lib/metanorma/iso/front.rb', line 116

def (xml, list)
  list = ["ISO"] if list.nil? || list.empty?
  list.each do |v|
    xml.agency v
  end
end

#metadata_approval_committee(node, xml) ⇒ Object



97
98
99
100
101
102
103
104
105
106
# File 'lib/metanorma/iso/front.rb', line 97

def (node, xml)
  types = (node)
  xml.approvalgroup do |a|
    (a, node.attr("approval-agency")
      &.split(%r{[/,;]}))
    types.each do |v|
      node.attr("#{v}-number") and committee_component(v, node, a)
    end
  end
end

#metadata_approval_committee_types(node) ⇒ Object



108
109
110
111
112
113
114
# File 'lib/metanorma/iso/front.rb', line 108

def (node)
  types = %w(technical-committee subcommittee workgroup)
  node.attr("approval-technical-committee-number") and
    types = %w(approval-technical-committee approval-subcommittee
               approval-workgroup)
  types
end

#metadata_author(node, xml) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
# File 'lib/metanorma/iso/front.rb', line 31

def (node, xml)
  publishers = node.attr("publisher") || "ISO"
  csv_split(publishers).each do |p|
    xml.contributor do |c|
      c.role **{ type: "author" }
      c.organization do |a|
        organization(a, p, false, node, !node.attr("publisher"))
      end
    end
  end
end

#metadata_committee(node, xml) ⇒ Object



83
84
85
86
# File 'lib/metanorma/iso/front.rb', line 83

def (node, xml)
  (node, xml)
  (node, xml)
end


55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/metanorma/iso/front.rb', line 55

def (node, xml)
  publishers = node.attr("copyright-holder") || node.attr("publisher") ||
    "ISO"
  csv_split(publishers).each do |p|
    xml.copyright do |c|
      c.from (node.attr("copyright-year") || Date.today.year)
      c.owner do |owner|
        owner.organization do |o|
          organization(
            o, p, true, node,
            !(node.attr("copyright-holder") || node.attr("publisher"))
          )
        end
      end
    end
  end
end

#metadata_editorial_committee(node, xml) ⇒ Object



88
89
90
91
92
93
94
95
# File 'lib/metanorma/iso/front.rb', line 88

def (node, xml)
  xml.editorialgroup do |a|
    %w(technical-committee subcommittee workgroup).each do |v|
      node.attr("#{v}-number") and committee_component(v, node, a)
    end
    node.attr("secretariat") and a.secretariat(node.attr("secretariat"))
  end
end

#metadata_ext(node, xml) ⇒ Object



12
13
14
15
16
17
18
19
# File 'lib/metanorma/iso/front.rb', line 12

def (node, xml)
  super
  structured_id(node, xml)
  xml.stagename stage_name(get_stage(node), get_substage(node),
                           doctype(node), node.attr("iteration"))
  @amd && a = node.attr("updates-document-type") and
    xml.updates_document_type a
end

#metadata_id(node, xml) ⇒ Object



65
66
67
68
69
70
71
# File 'lib/metanorma/iso/front_id.rb', line 65

def (node, xml)
  iso_id(node, xml)
  node&.attr("tc-docnumber")&.split(/,\s*/)&.each do |n|
    xml.docidentifier(n, **attr_code(type: "iso-tc"))
  end
  xml.docnumber node&.attr("docnumber")
end

#metadata_publisher(node, xml) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
# File 'lib/metanorma/iso/front.rb', line 43

def (node, xml)
  publishers = node.attr("publisher") || "ISO"
  csv_split(publishers).each do |p|
    xml.contributor do |c|
      c.role **{ type: "publisher" }
      c.organization do |a|
        organization(a, p, true, node, !node.attr("publisher"))
      end
    end
  end
end

#metadata_status(node, xml) ⇒ Object



73
74
75
76
77
78
79
80
81
# File 'lib/metanorma/iso/front.rb', line 73

def (node, xml)
  stage = get_stage(node)
  substage = get_substage(node)
  xml.status do |s|
    s.stage stage, **attr_code(abbreviation: cover_stage_abbr(node))
    s.substage substage
    node.attr("iteration") && (s.iteration node.attr("iteration"))
  end
end

#metadata_subdoctype(node, xml) ⇒ Object



21
22
23
24
# File 'lib/metanorma/iso/front.rb', line 21

def (node, xml)
  super
  a = node.attr("horizontal") and xml.horizontal a
end

#norm_bibitem_style(root) ⇒ Object

ISO/IEC DIR 2, 10.2



197
198
199
200
201
202
203
# File 'lib/metanorma/iso/validate_section.rb', line 197

def norm_bibitem_style(root)
  root.xpath(NORM_BIBITEMS).each do |b|
    if b.at(Standoc::Converter::ISO_PUBLISHER_XPATH).nil?
      @log.add("Style", b, "#{NORM_ISO_WARN}: #{b.text}")
    end
  end
end

#normref_validate(root) ⇒ Object

ISO/IEC DIR 2, 15.4



29
30
31
32
33
# File 'lib/metanorma/iso/validate_section.rb', line 29

def normref_validate(root)
  f = root.at("//references[@normative = 'true']") || return
  f.at("./references | ./clause") &&
    @log.add("Style", f, "normative references contains subclauses")
end

#note_style(node) ⇒ Object

ISO/IEC DIR 2, 24.5



58
59
60
61
62
63
# File 'lib/metanorma/iso/validate_style.rb', line 58

def note_style(node)
  return if @novalid

  style_no_guidance(node, extract_text(node), "Note")
  style(node, extract_text(node))
end

#ol_attrs(node) ⇒ Object



58
59
60
61
62
# File 'lib/metanorma/iso/base.rb', line 58

def ol_attrs(node)
  attr_code(keep_attrs(node)
            .merge(id: ::Metanorma::Utils::anchor_or_uuid(node),
                   start: node.attr("start")))
end

#ol_count_validate(doc) ⇒ Object



12
13
14
15
16
17
18
19
20
21
22
# File 'lib/metanorma/iso/validate_list.rb', line 12

def ol_count_validate(doc)
  doc.xpath("//clause | //annex").each do |c|
    next if c.xpath(".//ol").empty?

    ols = c.xpath(".//ol") -
      c.xpath(".//ul//ol | .//ol//ol | .//clause//ol")
    ols.size > 1 and
      style_warning(c, "More than 1 ordered list in a numbered clause",
                    nil)
  end
end

#onlychild_clause_validate(root) ⇒ Object

ISO/IEC DIR 2, 22.3.2



213
214
215
216
217
218
219
220
221
222
# File 'lib/metanorma/iso/validate_section.rb', line 213

def onlychild_clause_validate(root)
  root.xpath(Standoc::Utils::SUBCLAUSE_XPATH).each do |c|
    next unless c.xpath("../clause").size == 1

    title = c.at("./title")
    location = c["id"] || "#{c.text[0..60]}..."
    location += ":#{title.text}" if c["id"] && !title.nil?
    @log.add("Style", nil, "#{location}: subclause is only child")
  end
end

#org_abbrevObject



26
27
28
29
# File 'lib/metanorma/iso/front.rb', line 26

def org_abbrev
  { "International Organization for Standardization" => "ISO",
    "International Electrotechnical Commission" => "IEC" }
end

#other_footnote_renumber(xmldoc) ⇒ Object



17
18
19
20
21
22
23
24
25
26
# File 'lib/metanorma/iso/cleanup.rb', line 17

def other_footnote_renumber(xmldoc)
  seen = {}
  i = 0
  [PRE_NORMREF_FOOTNOTES, NORMREF_FOOTNOTES,
   POST_NORMREF_FOOTNOTES].each do |xpath|
    xmldoc.xpath(xpath).each do |fn|
      i, seen = other_footnote_renumber1(fn, i, seen)
    end
  end
end

#outputs(node, ret) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/metanorma/iso/base.rb', line 72

def outputs(node, ret)
  File.open("#{@filename}.xml", "w:UTF-8") { |f| f.write(ret) }
  presentation_xml_converter(node).convert("#{@filename}.xml")
  html_converter_alt(node).convert("#{@filename}.presentation.xml",
                                   nil, false, "#{@filename}_alt.html")
  html_converter(node).convert("#{@filename}.presentation.xml",
                               nil, false, "#{@filename}.html")
  doc_converter(node).convert("#{@filename}.presentation.xml",
                              nil, false, "#{@filename}.doc")
  pdf_converter(node)&.convert("#{@filename}.presentation.xml",
                               nil, false, "#{@filename}.pdf")
  # sts_converter(node)&.convert(@filename + ".xml")
end

#patent_notice_parse(xml, node) ⇒ Object



26
27
28
29
30
31
# File 'lib/metanorma/iso/section.rb', line 26

def patent_notice_parse(xml, node)
  # xml.patent_notice do |xml_section|
  #  xml_section << node.content
  # end
  xml << node.content
end

#pdf_converter(node) ⇒ Object



36
37
38
39
40
# File 'lib/metanorma/iso/base.rb', line 36

def pdf_converter(node)
  return nil if node.attr("no-pdf")

  IsoDoc::Iso::PdfConvert.new(pdf_extract_attributes(node))
end

#permission_check(text) ⇒ Object



67
68
69
70
71
72
# File 'lib/metanorma/iso/validate_requirements.rb', line 67

def permission_check(text)
  text.split(/\.\s+/).each do |t|
    return t if permission_re.match t
  end
  nil
end

#permission_reObject



62
63
64
65
# File 'lib/metanorma/iso/validate_requirements.rb', line 62

def permission_re
  Regexp.new(self.class::PERMISSION_RE_STR.gsub(/\s/, "")
    .gsub(/_/, "\\s"), Regexp::IGNORECASE)
end

#possibility_check(text) ⇒ Object



89
90
91
92
# File 'lib/metanorma/iso/validate_requirements.rb', line 89

def possibility_check(text)
  text.split(/\.\s+/).each { |t| return t if possibility_re.match t }
  nil
end

#possibility_reObject



84
85
86
87
# File 'lib/metanorma/iso/validate_requirements.rb', line 84

def possibility_re
  Regexp.new(self.class::POSSIBILITY_RE_STR.gsub(/\s/, "")
    .gsub(/_/, "\\s"), Regexp::IGNORECASE)
end

#presentation_xml_converter(node) ⇒ Object



48
49
50
# File 'lib/metanorma/iso/base.rb', line 48

def presentation_xml_converter(node)
  IsoDoc::Iso::PresentationXMLConvert.new(html_extract_attributes(node))
end

#pub_class(bib) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/metanorma/iso/cleanup.rb', line 72

def pub_class(bib)
  return 1 if bib.at("#{PUBLISHER}[abbreviation = 'ISO']")
  return 1 if bib.at("#{PUBLISHER}[name = 'International Organization "\
                     "for Standardization']")
  return 2 if bib.at("#{PUBLISHER}[abbreviation = 'IEC']")
  return 2 if bib.at("#{PUBLISHER}[name = 'International "\
                     "Electrotechnical Commission']")
  return 3 if bib.at("./docidentifier[@type][not(#{OTHERIDS})]") ||
    bib.at("./docidentifier[not(@type)]")

  4
end

#recommendation_check(text) ⇒ Object



46
47
48
49
50
51
# File 'lib/metanorma/iso/validate_requirements.rb', line 46

def recommendation_check(text)
  text.split(/\.\s+/).each do |t|
    return t if recommendation_re.match t
  end
  nil
end

#recommendation_reObject



41
42
43
44
# File 'lib/metanorma/iso/validate_requirements.rb', line 41

def recommendation_re
  Regexp.new(self.class::RECOMMENDATION_RE_STR.gsub(/\s/, "")
    .gsub(/_/, "\\s"), Regexp::IGNORECASE)
end

#relaton_relation_descriptionsObject



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/metanorma/iso/front.rb', line 184

def relaton_relation_descriptions
  super.merge(
    "amends" => "updates", "revises" => "updates",
    "replaces" => "obsoletes",
    "supersedes" => "obsoletes",
    "corrects" => "updates",
    "informatively-cited-in" => "isCitedIn",
    "informatively-cites" => "cites",
    "normatively-cited in" => "isCitedIn",
    "normatively-cites" => "cites",
    "identical-adopted-from" => "adoptedFrom",
    "modified-adopted-from" => "adoptedFrom",
    "related-directive" => "related",
    "related-mandate" => "related"
  )
end

#relaton_relationsObject



179
180
181
182
# File 'lib/metanorma/iso/front.rb', line 179

def relaton_relations
  super + %w(obsoletes successor-of manifestation-of related
             annotation-of)
end

#replacement_standard(biblio) ⇒ Object



180
181
182
183
184
185
# File 'lib/metanorma/iso/cleanup.rb', line 180

def replacement_standard(biblio)
  r = biblio.at("./relation[@type = 'updates']/bibitem") or return nil
  id = r.at("./formattedref | ./docidentifier[@primary = 'true'] | "\
            "./docidentifier | ./formattedref") or return nil
  id.text
end

#requirement_check(text) ⇒ Object



26
27
28
29
30
31
# File 'lib/metanorma/iso/validate_requirements.rb', line 26

def requirement_check(text)
  text.split(/\.\s+/).each do |t|
    return t if requirement_re.match t
  end
  nil
end

#requirement_reObject



21
22
23
24
# File 'lib/metanorma/iso/validate_requirements.rb', line 21

def requirement_re
  Regexp.new(self.class::REQUIREMENT_RE_STR.gsub(/\s/, "")
    .gsub(/_/, "\\s"), Regexp::IGNORECASE)
end

#requirements_processorObject



15
16
17
# File 'lib/metanorma/iso/base.rb', line 15

def requirements_processor
  ::Metanorma::Requirements::Iso
end

#scope_parse(attrs, xml, node) ⇒ Object



12
13
14
15
# File 'lib/metanorma/iso/section.rb', line 12

def scope_parse(attrs, xml, node)
  attrs = attrs.merge(type: "scope") unless @amd
  clause_parse(attrs, xml, node)
end

#scope_style(node) ⇒ Object

ISO/IEC DIR 2, 14.2



26
27
28
29
30
# File 'lib/metanorma/iso/validate_style.rb', line 26

def scope_style(node)
  return if @novalid

  style_no_guidance(node, extract_text(node), "Scope")
end

#script_validate(xmldoc) ⇒ Object



125
126
127
128
129
130
# File 'lib/metanorma/iso/validate.rb', line 125

def script_validate(xmldoc)
  script = xmldoc&.at("//bibdata/script")&.text
  %w(Cyrl Latn).include?(script) or
    @log.add("Document Attributes", nil,
             "#{script} is not a recognised script")
end

#section_names_terms_cleanup(xml) ⇒ Object



202
203
204
205
# File 'lib/metanorma/iso/cleanup.rb', line 202

def section_names_terms_cleanup(xml)
  @vocab and return
  super
end

#section_style(root) ⇒ Object



173
174
175
176
177
178
179
180
181
# File 'lib/metanorma/iso/validate_section.rb', line 173

def section_style(root)
  foreword_style(root.at("//foreword"))
  introduction_style(root.at("//introduction"))
  scope_style(root.at("//clause[@type = 'scope']"))
  scope = root.at("//clause[@type = 'scope']/clause")
  # ISO/IEC DIR 2, 14.4
  scope.nil? || style_warning(scope, SCOPE_WARN, nil)
  tech_report_style(root)
end

#section_validate(doc) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/metanorma/iso/validate_section.rb', line 6

def section_validate(doc)
  doctype = doc&.at("//bibdata/ext/doctype")&.text
  unless %w(amendment technical-corrigendum).include? doctype
    foreword_validate(doc.root)
    normref_validate(doc.root)
    symbols_validate(doc.root)
    sections_presence_validate(doc.root)
    sections_sequence_validate(doc.root)
  end
  section_style(doc.root)
  subclause_validate(doc.root)
  @vocab and vocab_terms_titles_validate(doc.root)
  super
end

#sections_cleanup(xml) ⇒ Object



114
115
116
117
118
119
# File 'lib/metanorma/iso/cleanup.rb', line 114

def sections_cleanup(xml)
  super
  return unless @amd

  xml.xpath("//*[@inline-header]").each { |h| h.delete("inline-header") }
end

#sections_presence_validate(root) ⇒ Object



65
66
67
68
69
70
71
72
# File 'lib/metanorma/iso/validate_section.rb', line 65

def sections_presence_validate(root)
  root.at("//sections/clause[@type = 'scope']") or
    @log.add("Style", nil, "Scope clause missing")
  root.at("//references[@normative = 'true']") or
    @log.add("Style", nil, "Normative references missing")
  root.at("//terms") or
    @log.add("Style", nil, "Terms & definitions missing")
end

#sections_sequence_validate(root) ⇒ Object



94
95
96
97
98
99
100
101
102
# File 'lib/metanorma/iso/validate_section.rb', line 94

def sections_sequence_validate(root)
  names, n = sections_sequence_validate_start(root)
  if @vocab
    names, n = sections_sequence_validate_body_vocab(names, n)
  else
    names, n = sections_sequence_validate_body(names, n)
  end
  sections_sequence_validate_end(names, n)
end

#sections_sequence_validate_body(names, elem) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/metanorma/iso/validate_section.rb', line 117

def sections_sequence_validate_body(names, elem)
  if elem.nil? || elem.name != "clause"
    @log.add("Style", elem, "Document must contain at least one clause")
  end
  elem&.at("./self::clause") ||
    @log.add("Style", elem, "Document must contain clause after "\
                            "Terms and Definitions")
  elem&.at("./self::clause[@type = 'scope']") &&
    @log.add("Style", elem,
             "Scope must occur before Terms and Definitions")
  elem = names.shift
  while elem&.name == "clause"
    elem&.at("./self::clause[@type = 'scope']")
    @log.add("Style", elem,
             "Scope must occur before Terms and Definitions")
    elem = names.shift
  end
  %w(annex references).include? elem&.name or
    @log.add("Style", elem,
             "Only annexes and references can follow clauses")
  [names, elem]
end

#sections_sequence_validate_body_vocab(names, elem) ⇒ Object



140
141
142
143
144
145
146
147
148
# File 'lib/metanorma/iso/validate_section.rb', line 140

def sections_sequence_validate_body_vocab(names, elem)
  while elem && %w(clause terms).include?(elem.name)
    elem = names.shift
  end
  %w(annex references).include? elem&.name or
    @log.add("Style", elem,
             "Only annexes and references can follow terms and clauses")
  [names, elem]
end

#sections_sequence_validate_end(names, elem) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/metanorma/iso/validate_section.rb', line 150

def sections_sequence_validate_end(names, elem)
  while elem&.name == "annex"
    elem = names.shift
    if elem.nil?
      @log.add("Style", nil, "Document must include (references) "\
                             "Normative References")
    end
  end
  elem&.at("./self::references[@normative = 'true']") ||
    @log.add("Style", nil, "Document must include (references) "\
                           "Normative References")
  elem = names&.shift
  elem&.at("./self::references[@normative = 'false']") ||
    @log.add("Style", elem,
             "Final section must be (references) Bibliography")
  names.empty? ||
    @log.add("Style", elem,
             "There are sections after the final Bibliography")
end

#sections_sequence_validate_start(root) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/metanorma/iso/validate_section.rb', line 104

def sections_sequence_validate_start(root)
  names = root.xpath(SECTIONS_XPATH)
  names = seqcheck(names, SEQ[0][:msg], SEQ[0][:val])
  n = names[0]
  names = seqcheck(names, SEQ[1][:msg], SEQ[1][:val])
  n&.at("./self::introduction") and
    names = seqcheck(names, SEQ[2][:msg], SEQ[2][:val])
  names = seqcheck(names, SEQ[3][:msg], SEQ[3][:val])
  n = names.shift
  n = names.shift if n&.at("./self::definitions")
  [names, n]
end

#sectiontype(node, level = true) ⇒ Object



33
34
35
36
37
38
39
40
# File 'lib/metanorma/iso/section.rb', line 33

def sectiontype(node, level = true)
  return nil if @amd

  ret = sectiontype_streamline(sectiontype1(node))
  return ret if ret == "terms and definitions" && @vocab

  super
end

#see_erefs_validate(root) ⇒ Object

ISO/IEC DIR 2, 15.5.3



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/metanorma/iso/validate.rb', line 47

def see_erefs_validate(root)
  root.xpath("//eref").each do |t|
    prec = t.at("./preceding-sibling::text()[last()]")
    next unless !prec.nil? && /\b(see|refer to)\s*\Z/mi.match(prec)

    unless target = root.at("//*[@id = '#{t['bibitemid']}']")
      @log.add("Bibliography", t,
               "'#{t} is not pointing to a real reference")
      next
    end
    target.at("./ancestor::references[@normative = 'true']") and
      @log.add("Style", t,
               "'see #{t}' is pointing to a normative reference")
  end
end

#see_xrefs_validate(root) ⇒ Object

ISO/IEC DIR 2, 15.5.3 does not deal with preceding text marked up



32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/metanorma/iso/validate.rb', line 32

def see_xrefs_validate(root)
  root.xpath("//xref").each do |t|
    preceding = t.at("./preceding-sibling::text()[last()]")
    next unless !preceding.nil? &&
      /\b(see| refer to)\s*\Z/mi.match(preceding)

    (target = root.at("//*[@id = '#{t['target']}']")) || next
    if target.at("./ancestor-or-self::*[@obligation = 'normative']")
      @log.add("Style", t,
               "'see #{t['target']}' is pointing to a normative section")
    end
  end
end

#seqcheck(names, msg, accepted) ⇒ Object



54
55
56
57
58
59
60
61
62
63
# File 'lib/metanorma/iso/validate_section.rb', line 54

def seqcheck(names, msg, accepted)
  n = names.shift
  return [] if n.nil?

  test = accepted.map { |a| n.at(a) }
  if test.all?(&:nil?)
    @log.add("Style", nil, msg)
  end
  names
end

#skip_list_punctuation(list) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
# File 'lib/metanorma/iso/validate_list.rb', line 44

def skip_list_punctuation(list)
  return true if list.at("./ancestor::table")
  return true if list.at("./following-sibling::term") # terms boilerplate

  list.xpath(".//li").each do |entry|
    l = entry.dup
    l.xpath(".//ol | .//ul").each(&:remove)
    l.text.split.size > 2 and return false
  end
  true
end

#sort_biblio(bib) ⇒ Object



85
86
87
88
89
# File 'lib/metanorma/iso/cleanup.rb', line 85

def sort_biblio(bib)
  bib.sort do |a, b|
    sort_biblio_key(a) <=> sort_biblio_key(b)
  end
end

#sort_biblio_key(bib) ⇒ Object

sort by: doc class (ISO, IEC, other standard (not DOI &c), other then standard class (docid class other than DOI &c) then docnumber if present, numeric sort

else alphanumeric metanorma id (abbreviation)

then doc part number if present, numeric sort then doc id (not DOI &c) then title



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/metanorma/iso/cleanup.rb', line 98

def sort_biblio_key(bib)
  pubclass = pub_class(bib)
  num = bib&.at("./docnumber")&.text
  id = bib&.at("./docidentifier[@primary]") ||
    bib&.at("./docidentifier[not(#{OTHERIDS})]")
  metaid = bib&.at("./docidentifier[@type = 'metanorma']")&.text
  abbrid = metaid unless /^\[\d+\]$/.match?(metaid)
  /\d-(?<partid>\d+)/ =~ id&.text
  type = id["type"] if id
  title = bib&.at("./title[@type = 'main']")&.text ||
    bib&.at("./title")&.text || bib&.at("./formattedref")&.text
  "#{pubclass} :: #{type} :: "\
    "#{num.nil? ? abbrid : sprintf('%09d', num.to_i)} :: "\
    "#{sprintf('%09d', partid.to_i)} :: #{id&.text} :: #{title}"
end

#stage_abbr(stage, substage, doctype) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/metanorma/iso/front_id.rb', line 37

def stage_abbr(stage, substage, doctype)
  return nil if stage.to_i > 60

  ret = STAGE_ABBRS[stage.to_sym]
  ret = "PRF" if stage == "60" && substage == "00"
  ret = "AWI" if stage == "10" && substage == "99"
  ret = "AWI" if stage == "20" && substage == "00"
  if %w(amendment technical-corrigendum technical-report
        technical-specification).include?(doctype)
    ret = "D" if stage == "40" && doctype == "amendment"
    ret = "FD" if stage == "50" && %w(amendment technical-corrigendum)
      .include?(doctype)
  end
  ret
end

#stage_name(stage, substage, _doctype, iteration = nil) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
# File 'lib/metanorma/iso/front_id.rb', line 53

def stage_name(stage, substage, _doctype, iteration = nil)
  return "Proof" if stage == "60" && substage == "00"

  ret = STAGE_NAMES[stage.to_sym]
  if iteration && %w(20 30).include?(stage)
    prefix = iteration.to_i.localize(@lang.to_sym)
      .to_rbnf_s("SpelloutRules", "spellout-ordinal")
    ret = "#{prefix.capitalize} #{ret.downcase}"
  end
  ret
end

#stage_validate(xmldoc) ⇒ Object



132
133
134
135
136
137
# File 'lib/metanorma/iso/validate.rb', line 132

def stage_validate(xmldoc)
  stage = xmldoc&.at("//bibdata/status/stage")&.text
  %w(00 10 20 30 40 50 60 90 95).include? stage or
    @log.add("Document Attributes", nil,
             "#{stage} is not a recognised stage")
end

#starts_lowercase?(text) ⇒ Boolean

allow that all-caps word (acronym) is agnostic as to lowercase

Returns:

  • (Boolean)


112
113
114
115
# File 'lib/metanorma/iso/validate_list.rb', line 112

def starts_lowercase?(text)
  text.match?(/^[^[[:upper:]][[:lower:]]]*[[:lower:]]/) ||
    text.match?(/^[^[[:upper:]][[:lower:]]]*[[:upper:]][[:upper:]]+[^[[:alpha:]]]/)
end

#starts_uppercase?(text) ⇒ Boolean

Returns:

  • (Boolean)


117
118
119
# File 'lib/metanorma/iso/validate_list.rb', line 117

def starts_uppercase?(text)
  text.match?(/^[^[[:upper:]][[:lower:]]]*[[:upper:]]/)
end

#structured_id(node, xml) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/metanorma/iso/front_id.rb', line 186

def structured_id(node, xml)
  return unless node.attr("docnumber")

  part, subpart = node&.attr("partnumber")&.split(/-/)
  xml.structuredidentifier do |i|
    i.project_number(node.attr("docnumber"), **attr_code(
      part: part, subpart: subpart,
      amendment: node.attr("amendment-number"),
      corrigendum: node.attr("corrigendum-number"),
      origyr: node.attr("created-date")
    ))
  end
end

#sts_converter(node) ⇒ Object



42
43
44
45
46
# File 'lib/metanorma/iso/base.rb', line 42

def sts_converter(node)
  return nil if node.attr("no-pdf")

  IsoDoc::Iso::StsConvert.new(html_extract_attributes(node))
end

#style(node, text) ⇒ Object



92
93
94
95
96
97
98
99
100
# File 'lib/metanorma/iso/validate_style.rb', line 92

def style(node, text)
  return if @novalid

  style_number(node, text)
  style_percent(node, text)
  style_abbrev(node, text)
  style_units(node, text)
  style_punct(node, text)
end

#style_abbrev(node, text) ⇒ Object

ISO/IEC DIR 2, 8.4 ISO/IEC DIR 2, 9.3



129
130
131
132
133
134
135
# File 'lib/metanorma/iso/validate_style.rb', line 129

def style_abbrev(node, text)
  style_regex(/(\A|\s)(?!e\.g\.|i\.e\.)
              (?<num>[a-z]{1,2}\.([a-z]{1,2}|\.))\b/ix,
              "no dots in abbreviations", node, text)
  style_regex(/\b(?<num>ppm)\b/i,
              "language-specific abbreviation", node, text)
end

#style_no_guidance(node, text, docpart) ⇒ Object



101
102
103
104
105
106
107
108
# File 'lib/metanorma/iso/validate_requirements.rb', line 101

def style_no_guidance(node, text, docpart)
  r = requirement_check(text)
  style_warning(node, "#{docpart} may contain requirement", r) if r
  r = permission_check(text)
  style_warning(node, "#{docpart} may contain permission", r) if r
  r = recommendation_check(text)
  style_warning(node, "#{docpart} may contain recommendation", r) if r
end

#style_non_std_units(node, text) ⇒ Object

ISO/IEC DIR 2, 9.3



158
159
160
161
162
163
# File 'lib/metanorma/iso/validate_style.rb', line 158

def style_non_std_units(node, text)
  NONSTD_UNITS.each do |k, v|
    style_regex(/\b(?<num>[0-9][0-9,]*\s+#{k})\b/,
                "non-standard unit (should be #{v})", node, text)
  end
end

#style_number(node, text) ⇒ Object

ISO/IEC DIR 2, 9.1 ISO/IEC DIR 2, Table B.1 www.iso.org/ISO-house-style.html#iso-hs-s-text-r-n-numbers



105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/metanorma/iso/validate_style.rb', line 105

def style_number(node, text)
  style_two_regex_not_prev(
    node, text, /^(?<num>-?[0-9]{4,}[,0-9]*)\Z/,
    %r{\b(ISO|IEC|IEEE/|(in|January|February|March|April|May|June|August|September|October|November|December)\b)\Z},
    "number not broken up in threes"
  )
  style_regex(/\b(?<num>[0-9]+\.[0-9]+)/i,
              "possible decimal point", node, text)
  style_regex(/\b(?<num>billions?)\b/i,
              "ambiguous number", node, text)
  style_regex(/(^|\s)(?<num>-[0-9][0-9,.]*)/i,
              "hyphen instead of minus sign U+2212", node, text)
end

#style_percent(node, text) ⇒ Object

ISO/IEC DIR 2, 9.2.1



120
121
122
123
124
125
# File 'lib/metanorma/iso/validate_style.rb', line 120

def style_percent(node, text)
  style_regex(/\b(?<num>[0-9.,]+%)/,
              "no space before percent sign", node, text)
  style_regex(/\b(?<num>[0-9.,]+ \u00b1 [0-9,.]+ %)/,
              "unbracketed tolerance before percent sign", node, text)
end

#style_punct(node, text) ⇒ Object



167
168
169
170
171
172
# File 'lib/metanorma/iso/validate_style.rb', line 167

def style_punct(node, text)
  style_regex(/\b(?<num>and\/?or)\b/i,
              "Use 'either x or y, or both'", node, text)
  style_regex(/\s(?<num>&)\s/i,
              "Avoid ampersand in ordinary text'", node, text)
end

#style_regex(regex, warning, node, text) ⇒ Object



73
74
75
# File 'lib/metanorma/iso/validate_style.rb', line 73

def style_regex(regex, warning, node, text)
  (m = regex.match(text)) && style_warning(node, warning, m[:num])
end

#style_two_regex_not_prev(n, text, regex, re_prev, warning) ⇒ Object

style check with a regex on a token and a negative match on its preceding token



79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/metanorma/iso/validate_style.rb', line 79

def style_two_regex_not_prev(n, text, regex, re_prev, warning)
  return if text.nil?

  arr = Tokenizer::WhitespaceTokenizer.new.tokenize(text)
  arr.each_index do |i|
    m = regex.match arr[i]
    m_prev = i.zero? ? nil : re_prev.match(arr[i - 1])
    if !m.nil? && m_prev.nil?
      style_warning(n, warning, m[:num])
    end
  end
end

#style_units(node, text) ⇒ Object

ISO/IEC DIR 2, 9.3



143
144
145
146
147
148
149
150
# File 'lib/metanorma/iso/validate_style.rb', line 143

def style_units(node, text)
  style_regex(/\b(?<num>[0-9][0-9,]*\s+[\u00b0\u2032\u2033])/,
              "space between number and degrees/minutes/seconds",
              node, text)
  style_regex(/\b(?<num>[0-9][0-9,]*#{SI_UNIT})\b/o,
              "no space between number and SI unit", node, text)
  style_non_std_units(node, text)
end

#style_warning(node, msg, text = nil) ⇒ Object



174
175
176
177
178
179
180
# File 'lib/metanorma/iso/validate_style.rb', line 174

def style_warning(node, msg, text = nil)
  return if @novalid

  w = msg
  w += ": #{text}" if text
  @log.add("Style", node, w)
end

#subclause_validate(root) ⇒ Object



205
206
207
208
209
210
# File 'lib/metanorma/iso/validate_section.rb', line 205

def subclause_validate(root)
  root.xpath("//clause/clause/clause/clause/clause/clause/clause/clause")
    .each do |c|
    style_warning(c, "Exceeds the maximum clause depth of 7", nil)
  end
end

#subfigure_validate(xmldoc) ⇒ Object

DRG directives 3.7; but anticipated by standoc



5
6
7
8
9
10
11
12
13
# File 'lib/metanorma/iso/validate_image.rb', line 5

def subfigure_validate(xmldoc)
  xmldoc.xpath("//figure//figure").each do |f|
    { footnote: "fn", note: "note", key: "dl" }.each do |k, v|
      f.xpath(".//#{v}").each do |n|
        @log.add("Style", n, "#{k} is not permitted in a subfigure")
      end
    end
  end
end

#substage_validate(xmldoc) ⇒ Object



139
140
141
142
143
144
# File 'lib/metanorma/iso/validate.rb', line 139

def substage_validate(xmldoc)
  substage = xmldoc&.at("//bibdata/status/substage")&.text or return
  %w(00 20 60 90 92 93 98 99).include? substage or
    @log.add("Document Attributes", nil,
             "#{substage} is not a recognised substage")
end

#symbols_validate(root) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/metanorma/iso/validate_section.rb', line 40

def symbols_validate(root)
  f = root.xpath("//definitions")
  f.empty? && return
  (f.size == 1 || @vocab) or
    @log.add("Style", f.first, ONE_SYMBOLS_WARNING)
  f.first.elements.reject { |e| %w(title dl).include? e.name }.empty? or
    @log.add("Style", f.first, NON_DL_SYMBOLS_WARNING)
  @vocab and f.each do |f1|
    f1.at("./ancestor::annex") or
      @log.add("Style", f1, "In vocabulary documents, Symbols and "\
                            "Abbreviated Terms are only permitted in annexes")
  end
end

#tech_report_style(root) ⇒ Object



183
184
185
186
187
188
189
190
191
# File 'lib/metanorma/iso/validate_section.rb', line 183

def tech_report_style(root)
  root.at("//bibdata/ext/doctype")&.text == "technical-report" or return
  root.xpath("//sections/clause[not(@type = 'scope')] | //annex")
    .each do |s|
    r = requirement_check(extract_text(s)) and
      style_warning(s,
                    "Technical Report clause may contain requirement", r)
  end
end

#term_children_cleanup(xmldoc) ⇒ Object



207
208
209
210
# File 'lib/metanorma/iso/cleanup.rb', line 207

def term_children_cleanup(xmldoc)
  @vocab and return
  super
end

#term_contains_subclauses(node) ⇒ Object



48
49
50
51
# File 'lib/metanorma/iso/section.rb', line 48

def term_contains_subclauses(node)
  @vocab and return false # treat this as a term
  super
end

#term_def_subclause_parse(attrs, xml, node) ⇒ Object



42
43
44
45
46
# File 'lib/metanorma/iso/section.rb', line 42

def term_def_subclause_parse(attrs, xml, node)
  node.role == "term" and
    return term_def_subclause_parse1(attrs, xml, node)
  super
end

#term_defs_boilerplate_cont(src, term, isodoc) ⇒ Object



197
198
199
200
# File 'lib/metanorma/iso/cleanup.rb', line 197

def term_defs_boilerplate_cont(src, term, isodoc)
  @vocab and src.empty? and return
  super
end

#term_xrefs_validate(xmldoc) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
# File 'lib/metanorma/iso/validate.rb', line 80

def term_xrefs_validate(xmldoc)
  termids = xmldoc
    .xpath("//sections/terms | //sections/clause[.//terms] | "\
           "//annex[.//terms]").each_with_object({}) do |t, m|
    t.xpath(".//*/@id").each { |a| m[a.text] = true }
    t.name == "terms" and m[t["id"]] = true
  end
  xmldoc.xpath(".//xref").each do |x|
    term_xrefs_validate1(x, termids)
  end
end

#term_xrefs_validate1(xref, termids) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
# File 'lib/metanorma/iso/validate.rb', line 92

def term_xrefs_validate1(xref, termids)
  closest_id = xref.xpath("./ancestor::*[@id]")&.last or return
  (termids[xref["target"]] && !termids[closest_id["id"]]) and
    @log.add("Style", xref,
             "only terms clauses can cross-reference terms clause "\
             "(#{xref['target']})")
  (!termids[xref["target"]] && termids[closest_id["id"]]) and
    @log.add("Style", xref,
             "non-terms clauses cannot cross-reference terms clause "\
             "(#{xref['target']})")
end

#termdef_boilerplate_insert(xmldoc, isodoc, once = false) ⇒ Object



192
193
194
195
# File 'lib/metanorma/iso/cleanup.rb', line 192

def termdef_boilerplate_insert(xmldoc, isodoc, once = false)
  once = true
  super
end

#termdef_style(xmldoc) ⇒ Object

ISO/IEC DIR 2, 16.5.6



105
106
107
108
109
110
111
112
113
114
# File 'lib/metanorma/iso/validate.rb', line 105

def termdef_style(xmldoc)
  xmldoc.xpath("//term").each do |t|
    para = t.at("./definition/verbal-definition") || return
    term = t.at("./preferred//name").text
    termdef_warn(para.text, /\A(the|a)\b/i, t, term,
                 "term definition starts with article")
    termdef_warn(para.text, /\.\Z/i, t, term,
                 "term definition ends with period")
  end
end

#termdef_warn(text, regex, elem, term, msg) ⇒ Object



75
76
77
# File 'lib/metanorma/iso/validate.rb', line 75

def termdef_warn(text, regex, elem, term, msg)
  regex.match(text) && @log.add("Style", elem, "#{term}: #{msg}")
end

#title(node, xml) ⇒ Object



168
169
170
171
172
173
174
175
176
177
# File 'lib/metanorma/iso/front.rb', line 168

def title(node, xml)
  %w(en ru fr).each do |lang|
    at = { language: lang, format: "text/plain" }
    title_full(node, xml, lang, at)
    title_intro(node, xml, lang, at)
    title_main(node, xml, lang, at)
    title_part(node, xml, lang, at)
    title_amd(node, xml, lang, at) if @amd
  end
end

#title_all_siblings(xpath, label) ⇒ Object

ISO/IEC DIR 2, 22.2



79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/metanorma/iso/validate_title.rb', line 79

def title_all_siblings(xpath, label)
  notitle = false
  withtitle = false
  xpath.each do |s|
    title_all_siblings(s.xpath("./clause | ./terms | ./references"),
                       s&.at("./title")&.text || s["id"])
    subtitle = s.at("./title")
    notitle = notitle || (!subtitle || subtitle.text.empty?)
    withtitle = withtitle || (subtitle && !subtitle.text.empty?)
  end
  notitle && withtitle &&
    @log.add("Style", nil,
             "#{label}: all subclauses must have a title, or none")
end

#title_amd(node, xml, lang, at) ⇒ Object



145
146
147
148
149
150
151
152
153
# File 'lib/metanorma/iso/front.rb', line 145

def title_amd(node, xml, lang, at)
  return unless node.attr("title-amendment-#{lang}")

  xml.title(**attr_code(at.merge(type: "title-amd"))) do |t1|
    t1 << Metanorma::Utils::asciidoc_sub(
      node.attr("title-amendment-#{lang}"),
    )
  end
end

#title_first_level_validate(root) ⇒ Object

ISO/IEC DIR 2, 22.2



66
67
68
69
70
71
72
73
74
75
76
# File 'lib/metanorma/iso/validate_title.rb', line 66

def title_first_level_validate(root)
  root.xpath(SECTIONS_XPATH).each do |s|
    title = s&.at("./title")&.text || s.name
    s.xpath("./clause | ./terms | ./references").each do |ss|
      subtitle = ss.at("./title")
      (!subtitle.nil? && !subtitle&.text&.empty?) or
        @log.add("Style", ss,
                 "#{title}: each first-level subclause must have a title")
    end
  end
end

#title_full(node, xml, lang, at) ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/metanorma/iso/front.rb', line 155

def title_full(node, xml, lang, at)
  title = node.attr("title-main-#{lang}")
  intro = node.attr("title-intro-#{lang}")
  part = node.attr("title-part-#{lang}")
  amd = node.attr("title-amendment-#{lang}")
  title = "#{intro} -- #{title}" if intro
  title = "#{title} -- #{part}" if part
  title = "#{title} -- #{amd}" if amd && @amd
  xml.title **attr_code(at.merge(type: "main")) do |t1|
    t1 << Metanorma::Utils::asciidoc_sub(title)
  end
end

#title_intro(node, xml, lang, at) ⇒ Object



123
124
125
126
127
128
129
# File 'lib/metanorma/iso/front.rb', line 123

def title_intro(node, xml, lang, at)
  return unless node.attr("title-intro-#{lang}")

  xml.title(**attr_code(at.merge(type: "title-intro"))) do |t1|
    t1 << Metanorma::Utils::asciidoc_sub(node.attr("title-intro-#{lang}"))
  end
end

#title_intro_validate(root) ⇒ Object



10
11
12
13
14
15
16
17
18
19
# File 'lib/metanorma/iso/validate_title.rb', line 10

def title_intro_validate(root)
  title_intro_en = title_lang_part(root, "intro", "en")
  title_intro_fr = title_lang_part(root, "intro", "fr")
  if title_intro_en.nil? && !title_intro_fr.nil?
    @log.add("Style", title_intro_fr, "No English Title Intro!")
  end
  if !title_intro_en.nil? && title_intro_fr.nil?
    @log.add("Style", title_intro_en, "No French Title Intro!")
  end
end

#title_lang_part(doc, part, lang) ⇒ Object



6
7
8
# File 'lib/metanorma/iso/validate_title.rb', line 6

def title_lang_part(doc, part, lang)
  doc.at("//bibdata/title[@type='title-#{part}' and @language='#{lang}']")
end

#title_main(node, xml, lang, at) ⇒ Object



131
132
133
134
135
# File 'lib/metanorma/iso/front.rb', line 131

def title_main(node, xml, lang, at)
  xml.title **attr_code(at.merge(type: "title-main")) do |t1|
    t1 << Metanorma::Utils::asciidoc_sub(node.attr("title-main-#{lang}"))
  end
end

#title_main_validate(root) ⇒ Object



21
22
23
24
25
26
27
28
29
30
# File 'lib/metanorma/iso/validate_title.rb', line 21

def title_main_validate(root)
  title_main_en = title_lang_part(root, "main", "en")
  title_main_fr = title_lang_part(root, "main", "fr")
  if title_main_en.nil? && !title_main_fr.nil?
    @log.add("Style", title_main_fr, "No English Title!")
  end
  if !title_main_en.nil? && title_main_fr.nil?
    @log.add("Style", title_main_en, "No French Title!")
  end
end

#title_names_type_validate(root) ⇒ Object

ISO/IEC DIR 2, 11.5.2



53
54
55
56
57
58
59
60
61
62
63
# File 'lib/metanorma/iso/validate_title.rb', line 53

def title_names_type_validate(root)
  doctypes = /International\sStandard | Technical\sSpecification |
  Publicly\sAvailable\sSpecification | Technical\sReport | Guide /xi
  title_main_en = title_lang_part(root, "main", "en")
  !title_main_en.nil? && doctypes.match(title_main_en.text) and
    @log.add("Style", title_main_en, "Main Title may name document type")
  title_intro_en = title_lang_part(root, "intro", "en")
  !title_intro_en.nil? && doctypes.match(title_intro_en.text) and
    @log.add("Style", title_intro_en,
             "Title Intro may name document type")
end

#title_no_full_stop_validate(root) ⇒ Object



95
96
97
98
99
100
101
102
103
# File 'lib/metanorma/iso/validate_title.rb', line 95

def title_no_full_stop_validate(root)
  root.xpath("//preface//title | //sections//title | //annex//title | "\
             "//references/title | //preface//name | //sections//name | "\
             "//annex//name").each do |t|
    style_regex(/\A(?<num>.+\.\Z)/i,
                "No full stop at end of title or caption",
                t, t.text.strip)
  end
end

#title_part(node, xml, lang, at) ⇒ Object



137
138
139
140
141
142
143
# File 'lib/metanorma/iso/front.rb', line 137

def title_part(node, xml, lang, at)
  return unless node.attr("title-part-#{lang}")

  xml.title(**attr_code(at.merge(type: "title-part"))) do |t1|
    t1 << Metanorma::Utils::asciidoc_sub(node.attr("title-part-#{lang}"))
  end
end

#title_part_validate(root) ⇒ Object



32
33
34
35
36
37
38
39
# File 'lib/metanorma/iso/validate_title.rb', line 32

def title_part_validate(root)
  title_part_en = title_lang_part(root, "part", "en")
  title_part_fr = title_lang_part(root, "part", "fr")
  (title_part_en.nil? && !title_part_fr.nil?) &&
    @log.add("Style", title_part_fr, "No English Title Part!")
  (!title_part_en.nil? && title_part_fr.nil?) &&
    @log.add("Style", title_part_en, "No French Title Part!")
end

#title_subpart_validate(root) ⇒ Object

ISO/IEC DIR 2, 11.4



42
43
44
45
46
47
48
49
50
# File 'lib/metanorma/iso/validate_title.rb', line 42

def title_subpart_validate(root)
  docid = root.at("//bibdata/docidentifier[@type = 'ISO']")
  subpart = /-\d+-\d+/.match docid
  iec = root.at("//bibdata/contributor[role/@type = 'publisher']/"\
                "organization[abbreviation = 'IEC' or "\
                "name = 'International Electrotechnical Commission']")
  subpart && !iec and
    @log.add("Style", docid, "Subpart defined on non-IEC document!")
end

#title_validate(root) ⇒ Object



105
106
107
108
109
110
111
112
113
114
# File 'lib/metanorma/iso/validate_title.rb', line 105

def title_validate(root)
  title_intro_validate(root)
  title_main_validate(root)
  title_part_validate(root)
  title_subpart_validate(root)
  title_names_type_validate(root)
  title_first_level_validate(root)
  title_all_siblings(root.xpath(SECTIONS_XPATH), "(top level)")
  title_no_full_stop_validate(root)
end

#unpub_footnotes(xmldoc) ⇒ Object



135
136
137
138
139
140
141
142
# File 'lib/metanorma/iso/cleanup.rb', line 135

def unpub_footnotes(xmldoc)
  xmldoc.xpath("//bibitem/note[@type = 'Unpublished-Status']").each do |n|
    e = xmldoc.at("//eref[@bibitemid = '#{n.parent['id']}']") or next
    fn = n.children.to_xml
    n.elements&.first&.name == "p" or fn = "<p>#{fn}</p>"
    e.next = "<fn>#{fn}</fn>"
  end
end

#unpub_stage_prefix(docnum, stage, typeabbr, node) ⇒ Object



263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/metanorma/iso/front_id.rb', line 263

def unpub_stage_prefix(docnum, stage, typeabbr, node)
  abbr = id_stage_abbr(stage, get_substage(node), node)
  %w(40 50).include?(stage) && i = node.attr("iteration") and
    itersuffix = ".#{i}"
  return docnum if abbr.nil? || abbr.empty? # prefixes added in cleanup

  typeabbr = "" if %w(DTS FDTS).include?(abbr.sub(/\s+$/, ""))
  return "/#{abbr}#{typeabbr} #{docnum}#{itersuffix}" unless @amd

  a = docnum.split(%r{/})
  a[-1] = "#{abbr}#{a[-1]}#{itersuffix}"
  a.join("/")
end

#unpublished_note(xmldoc) ⇒ Object



150
151
152
153
154
155
156
157
158
159
# File 'lib/metanorma/iso/cleanup.rb', line 150

def unpublished_note(xmldoc)
  xmldoc.xpath("//bibitem[not(./ancestor::bibitem)]"\
               "[not(note[@type = 'Unpublished-Status'])]").each do |b|
    next if pub_class(b) > 2
    next unless (s = b.at("./status/stage")) && (s.text.to_i < 60)

    id = b.at("docidentifier").text
    insert_unpub_note(b, @i18n.under_preparation.sub(/%/, id))
  end
end

#validate(doc) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/metanorma/iso/validate.rb', line 187

def validate(doc)
  content_validate(doc)
  doctype = doc&.at("//bibdata/ext/doctype")&.text
  schema = case doctype
           when "amendment", "technical-corrigendum" # @amd
             "isostandard-amd.rng"
           else
             "isostandard.rng"
           end
  schema_validate(formattedstr_strip(doc.dup),
                  File.join(File.dirname(__FILE__), schema))
end

#vocab_terms_titles_validate(root) ⇒ Object



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/metanorma/iso/validate_section.rb', line 225

def vocab_terms_titles_validate(root)
  terms = root.xpath("//sections/terms | //sections/clause[.//terms]")
  if terms.size == 1
    ((t = terms.first.at("./title")) && (t&.text == @i18n.termsdef)) or
      @log.add("Style", terms.first,
               "Single terms clause in vocabulary document "\
               "should have normal Terms and definitions heading")
  elsif terms.size > 1
    terms.each do |x|
      ((t = x.at("./title")) && /^#{@i18n.termsrelated}/.match?(t&.text)) or
        @log.add("Style", x,
                 "Multiple terms clauses in vocabulary document "\
                 "should have 'Terms related to' heading")
    end
  end
end

#withdrawn_note(xmldoc) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
# File 'lib/metanorma/iso/cleanup.rb', line 161

def withdrawn_note(xmldoc)
  xmldoc.xpath("//bibitem[not(note[@type = 'Unpublished-Status'])]")
    .each do |b|
      next unless withdrawn_ref?(b)

      if id = replacement_standard(b)
        insert_unpub_note(b, @i18n.cancelled_and_replaced.sub(/%/, id))
      else insert_unpub_note(b, @i18n.withdrawn)
      end
    end
end

#withdrawn_ref?(biblio) ⇒ Boolean

Returns:

  • (Boolean)


173
174
175
176
177
178
# File 'lib/metanorma/iso/cleanup.rb', line 173

def withdrawn_ref?(biblio)
  return false if pub_class(biblio) > 2

  (s = biblio.at("./status/stage")) && (s.text.to_i == 95) &&
    (t = biblio.at("./status/substage")) && (t.text.to_i == 99)
end