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/cleanup_biblio.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/front_contributor.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
DOCUMENT_SCHEMES =
[1951, 1972, 1979, 1987, 1989, 2012, 2013, 2024].freeze
STAGE_ERROR =
[Pubid::Core::Errors::HarmonizedStageCodeInvalidError,
Pubid::Core::Errors::TypeStageParseError,
Pubid::Core::Errors::StageInvalidError].freeze
PRE_NORMREF_FOOTNOTES =
"//preface//fn | " \
"//clause[@type = 'scope']//fn".freeze
NORMREF_FOOTNOTES =
"//references[@normative = 'true']//fn | " \
"//clause[.//references[@normative = 'true']]//fn".freeze
POST_NORMREF_FOOTNOTES =
"//sections//clause[not(@type = 'scope')]//fn | " \
"//annex//fn | //references[@normative = 'false']//fn | " \
"//clause[.//references[@normative = 'false']]//fn".freeze
NORM_REF =
"//bibliography/references[@normative = 'true'][not(@hidden)] | " \
        "//bibliography/clause[.//references[@normative = 'true']] | "\
        "//sections//references[@normative = 'true'][not(@hidden)]"
.freeze
TERM_CLAUSE =
"//sections//terms[not(preceding-sibling::clause)] | " \
"//sections//clause[descendant::terms][not(descendant::definitions)][@type = 'terms'] | " \
"//sections/clause[not(@type = 'terms')][not(descendant::definitions)]//terms".freeze
DEFAULT_EDGROUP_TYPE =
{ "technical-committee": "TC",
subcommittee: "SC", workgroup: "WG" }.freeze
DOCTYPE2HASHID =
{ directive: :dir, "technical-report": :tr, "guide": :guide,
"technical-specification": :ts,
"publicly-available-specification": :pas,
"committee-document": :tc, "recommendation": :r }.freeze
PUBLISHER =
"./contributor[role/@type = 'publisher']/organization".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
ISO_PUBLISHER_XPATH =
<<~XPATH.freeze
  ./contributor[role/@type = 'publisher']/organization[abbreviation = 'ISO' or abbreviation = 'IEC' or name = 'International Organization for Standardization' or name = 'International Electrotechnical Commission']
XPATH
COMMITTEE_ABBREVS =
{ "technical-committee" => "TC", "subcommittee" => "SC",
"workgroup" => "WG" }.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
AMBIG_WORDS_RE_STR =
<<~REGEXP.freeze
  \\b
      need_to | needs_to | might | could
  \\b
REGEXP

Instance Method Summary collapse

Instance Method Details

#admonition_name(node) ⇒ Object



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

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

#ambig_words_check(text) ⇒ Object



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

def ambig_words_check(text)
  @lang == "en" or return
  text.split(/\.\s+/).each do |t|
    return t if ambig_words_re.match t
  end
  nil
end

#ambig_words_reObject



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

def ambig_words_re
  @lang == "en" or return
  Regexp.new(self.class::AMBIG_WORDS_RE_STR.gsub(/\s/, "")
    .gsub("_", "\\s"), Regexp::IGNORECASE)
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



105
106
107
108
109
# File 'lib/metanorma/iso/cleanup.rb', line 105

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



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

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

#base_pubidObject



66
67
68
# File 'lib/metanorma/iso/front_id.rb', line 66

def base_pubid
  Pubid::Iso::Identifier
end

#bibdata_cleanup(xmldoc) ⇒ Object



98
99
100
101
102
103
# File 'lib/metanorma/iso/cleanup.rb', line 98

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

#bibdata_validate(doc) ⇒ Object



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

def bibdata_validate(doc)
  doctype_validate(doc)
  iteration_validate(doc)
end

#bibitem_cleanup(xmldoc) ⇒ Object



73
74
75
76
77
# File 'lib/metanorma/iso/cleanup_biblio.rb', line 73

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

#bibitem_validate(xmldoc) ⇒ Object



186
187
188
189
190
191
192
193
# File 'lib/metanorma/iso/validate.rb', line 186

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



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

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

#cen?(str) ⇒ Boolean

Returns:

  • (Boolean)


51
52
53
# File 'lib/metanorma/iso/front_id.rb', line 51

def cen?(str)
  /^C?EN/.match?(str)
end

#cen_id_out(xml, params) ⇒ Object



163
164
165
166
# File 'lib/metanorma/iso/front_id.rb', line 163

def cen_id_out(xml, params)
  xml.docidentifier iso_id_default(params).to_s,
                    **attr_code(type: "CEN", primary: "true")
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

#committee_abbrev(type, number, level) ⇒ Object



70
71
72
73
74
# File 'lib/metanorma/iso/front_contributor.rb', line 70

def committee_abbrev(type, number, level)
  type ||= COMMITTEE_ABBREVS[level.sub(/^approval-/, "")]
  type == "Other" and type = ""
  "#{type} #{number}".strip
end

#committee_contributors(node, xml, approval, agency) ⇒ Object



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

def committee_contributors(node, xml, approval, agency)
  (approval ? node : nil).each do |v|
    node.attr("#{v}-number") or next
    node.attr(v) or node.set_attr(v, "")
    o = { source: [v], role: approval ? "authorizer" : "author",
          default_org: false, committee: true, agency:,
          desc: v.sub(/^approval-/, "").tr("-", " ").capitalize }
    org_contributor(node, xml, o)
  end
  approval or committee_contributors_approval(node, xml, agency)
end

#committee_contributors_approval(node, xml, agency) ⇒ Object



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

def committee_contributors_approval(node, xml, agency)
  o = { name: agency, role: "authorizer", default_org: false,
        desc: "Agency", committee: false }
  org_contributor(node, xml, o)
end

#compact_blank(hash) ⇒ Object



77
78
79
# File 'lib/metanorma/iso/front_id.rb', line 77

def compact_blank(hash)
  hash.compact.reject { |_, v| v.is_a?(String) && v.empty? }
end

#content_validate(doc) ⇒ Object



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

def content_validate(doc)
  super
  root = doc.root
  title_validate(root)
  isosubgroup_validate(root)
  termdef_style(root)
  iso_xref_validate(root)
  bibdata_validate(root)
  bibitem_validate(root)
  figure_validate(root)
  list_validate(doc)
end

#contrib_committee_build(xml, agency, committee) ⇒ Object



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

def contrib_committee_build(xml, agency, committee)
  name = org_abbrev.invert[agency] and agency = name
  xml.name agency
  xml.subdivision do |s|
    s.organization do |o|
      o.name committee[:name]
      committee[:abbr] and o.abbreviation committee[:abbr]
      committee[:ident] and o.identifier committee[:ident]
    end
  end
end

#default_publisherObject

def home_agency “ISO” end



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

def default_publisher
  "ISO"
end

#definition_style(node) ⇒ Object

ISO/IEC DIR 2, 16.5.6



38
39
40
41
42
# File 'lib/metanorma/iso/validate_style.rb', line 38

def definition_style(node)
  @novalid and return
  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



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

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



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

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

#doc_extract_attributes(node) ⇒ Object



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

def doc_extract_attributes(node)
  super.merge(isowordtemplate: node.attr("iso-word-template"),
              isowordbgstripcolor: node.attr("iso-word-bg-strip-color"))
end

#docidentifier_cleanup(xmldoc) ⇒ Object

ISO as a prefix goes first



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

def docidentifier_cleanup(xmldoc)
  prefix = get_id_prefix(xmldoc)
  id = xmldoc.at("//bibdata/ext/structuredidentifier/project-number") and
    id.content = id_prefix(prefix, id)
end

#doctype_validate(_xmldoc) ⇒ Object



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

def doctype_validate(_xmldoc)
  %w(international-standard technical-specification technical-report
     publicly-available-specification international-workshop-agreement
     guide amendment technical-corrigendum committee-document recommendation)
    .include? @doctype or
    @log.add("Document Attributes", nil,
             "#{@doctype} is not a recognised document type")
end

#document_scheme(node) ⇒ Object



84
85
86
87
88
89
90
91
92
# File 'lib/metanorma/iso/base.rb', line 84

def document_scheme(node)
  r = node.attr("document-scheme") and return r
  r = node.attr("copyright-year")&.to_i or return "2024"
  DOCUMENT_SCHEMES.each_index do |i|
    i.zero? and next
    r < DOCUMENT_SCHEMES[i] and return DOCUMENT_SCHEMES[i - 1].to_s
  end
  "2024"
end

#editorial_group_types(xmldoc) ⇒ Object



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

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

#editorial_groups_agency(xmldoc) ⇒ Object



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

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

#eref_style_punct(node) ⇒ Object



203
204
205
206
207
208
209
# File 'lib/metanorma/iso/validate_style.rb', line 203

def eref_style_punct(node)
  node.xpath(".//eref[@type='footnote']").each do |e|
    /^\p{P}/.match?(e.next&.text) or next
    style_warning(node, "superscript cross-reference followed by punctuation",
                  node.to_xml)
  end
end

#example_style(node) ⇒ Object

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



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

def example_style(node)
  @novalid and return
  style_no_guidance(node, extract_text(node), "Example")
  style(node, extract_text(node))
end

#external_constraint(text) ⇒ Object



98
99
100
101
102
103
# File 'lib/metanorma/iso/validate_requirements.rb', line 98

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

#extract_org_attrs_complex(node, opts, source, suffix) ⇒ Object



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

def extract_org_attrs_complex(node, opts, source, suffix)
  n = node.attr("#{source}-number#{suffix}")
  t = committee_abbrev(node.attr("#{source}-type#{suffix}"), n, source)
  super.merge(ident: t).compact
end

#extract_publishers(xmldoc) ⇒ Object



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

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.children.to_xml
  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)
  node.nil? and return ""
  node1 = Nokogiri::XML.fragment(node.to_s)
  node1.xpath(".//link | .//locality | .//localityStack | .//stem")
    .each(&:remove)
  ret = ""
  node1.traverse { |x| ret += x.text if x.text? }
  HTMLEntities.new.decode(ret)
end

#figure_validate(xmldoc) ⇒ Object



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

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

#footnote_cleanup(xmldoc) ⇒ Object



64
65
66
67
# File 'lib/metanorma/iso/cleanup.rb', line 64

def footnote_cleanup(xmldoc)
  unpub_footnotes(xmldoc)
  super
end

#footnote_style(node) ⇒ Object

ISO/IEC DIR 2, 26.5



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

def footnote_style(node)
  @novalid and return
  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
# File 'lib/metanorma/iso/validate_style.rb', line 19

def foreword_style(node)
  @novalid and return
  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



25
26
27
28
# File 'lib/metanorma/iso/cleanup_biblio.rb', line 25

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

#get_id_prefix(xmldoc) ⇒ Object



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

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
    prefix << x1
  end
end

#get_stage(node) ⇒ Object



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

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



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

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

document’s type, eg. :tr, :ts, :amd, :cor, Type.new(:tr)

Parameters:

  • type (nil, :tr, :ts, :amd, :cor, :guide, :dir, :tc, Type)


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

def get_typeabbr(node, amd: false)
  node.attr("amendment-number") and return :amd
  node.attr("corrigendum-number") and return :cor
  DOCTYPE2HASHID[doctype(node).to_sym]
end

#html_converter(node) ⇒ Object



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

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

#html_converter_alt(node) ⇒ Object



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

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

#id_add_year(docnum, node) ⇒ Object



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

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_prefix(prefix, id) ⇒ Object



4
5
6
7
8
# File 'lib/metanorma/iso/cleanup_biblio.rb', line 4

def id_prefix(prefix, id)
  # we're just inheriting the prefixes from parent doc
  @amd and return id.text
  prefix.join("/") + (id.text.match?(%{^/}) ? "" : " ") + id.text
end

#image_name_parse(img, prefix) ⇒ Object



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

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



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

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



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

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



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

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



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

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



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

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

#insert_unpub_note(biblio, msg) ⇒ Object



114
115
116
117
# File 'lib/metanorma/iso/cleanup_biblio.rb', line 114

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



31
32
33
34
35
# File 'lib/metanorma/iso/validate_style.rb', line 31

def introduction_style(node)
  @novalid and return
  r = requirement_check(extract_text(node))
  style_warning(node, "Introduction may contain requirement", r) if r
end

#iso_id(node, xml) ⇒ Object



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

def iso_id(node, xml)
  (!@amd && node.attr("docnumber") || node.attr("adopted-from")) ||
    (@amd && node.attr("updates")) or return
  params = iso_id_params(node)
  iso_id_out(xml, params, true)
end

#iso_id_default(params) ⇒ Object



185
186
187
188
189
190
191
192
# File 'lib/metanorma/iso/front_id.rb', line 185

def iso_id_default(params)
  params_nolang = params.dup.tap { |hs| hs.delete(:language) }
  params1 = if params[:unpublished]
              params_nolang.dup.tap { |hs| hs.delete(:year) }
            else params_nolang end
  params1.delete(:unpublished)
  pubid_select(params1).create(**params1)
end

#iso_id_out(xml, params, with_prf) ⇒ Object



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

def iso_id_out(xml, params, with_prf)
  cen?(params[:publisher]) and return cen_id_out(xml, params)
  iso_id_out_common(xml, params, with_prf)
  @amd and return
  iso_id_out_non_amd(xml, params, with_prf)
rescue StandardError, *STAGE_ERROR => e
  clean_abort("Document identifier: #{e}", xml)
end

#iso_id_out_common(xml, params, with_prf) ⇒ Object



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

def iso_id_out_common(xml, params, with_prf)
  xml.docidentifier iso_id_default(params).to_s(with_prf:),
                    **attr_code(type: "ISO", primary: "true")
  xml.docidentifier iso_id_reference(params)
    .to_s(format: :ref_num_short, with_prf:),
                    **attr_code(type: "iso-reference")
  xml.docidentifier iso_id_reference(params).urn, **attr_code(type: "URN")
end

#iso_id_out_non_amd(xml, params, with_prf) ⇒ Object



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

def iso_id_out_non_amd(xml, params, with_prf)
  xml.docidentifier iso_id_undated(params).to_s(with_prf:),
                    **attr_code(type: "iso-undated")
  xml.docidentifier iso_id_with_lang(params)
    .to_s(format: :ref_num_long, with_prf:),
                    **attr_code(type: "iso-with-lang")
end

#iso_id_params(node) ⇒ Object



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

def iso_id_params(node)
  params = iso_id_params_core(node)
  params2 = iso_id_params_add(node)
  num = node.attr("docnumber")
  orig = node.attr("updates") || node.attr("adopted-from")
  /[[:alpha:]]/.match?(num) and orig ||= num
  orig and orig_id = orig_id_parse(orig)
  iso_id_params_resolve(params, params2, node, orig_id)
end

#iso_id_params_add(node) ⇒ Object



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

def iso_id_params_add(node)
  stage = iso_id_stage(node)
  ret = { number: node.attr("amendment-number") ||
    node.attr("corrigendum-number"),
          year: iso_id_year(node),
          iteration: node.attr("iteration") }
  iso_id_stage_populate(ret, node, stage)
  tc_number(ret, node)
  compact_blank(ret)
end

#iso_id_params_core(node) ⇒ Object

unpublished is for internal use



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

def iso_id_params_core(node)
  pub = iso_id_pub(node)
  ret = { number: node.attr("docnumber"),
          part: node.attr("partnumber"),
          language: node.attr("language") || "en",
          type: get_typeabbr(node),
          publisher: pub[0],
          unpublished: /^[0-5]/.match?(get_stage(node)),
          copublisher: pub[1..] }
  ret[:copublisher].empty? and ret.delete(:copublisher)
  compact_blank(ret)
end

#iso_id_params_resolve(params, params2, node, orig_id) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/metanorma/iso/front_id.rb', line 140

def iso_id_params_resolve(params, params2, node, orig_id)
  if orig_id && (node.attr("amendment-number") ||
      node.attr("corrigendum-number"))
    %i(unpublished part).each { |x| params.delete(x) }
    params2[:base] = orig_id
  elsif orig_id &&
      ![Pubid::Iso::Identifier,
        Pubid::Iec::Identifier].include?(pubid_select(params))
    params2[:adopted] = orig_id
  end
  params.merge!(params2)
  params
end

#iso_id_pub(node) ⇒ Object



95
96
97
98
# File 'lib/metanorma/iso/front_id.rb', line 95

def iso_id_pub(node)
  (node.attr("publisher") || default_publisher).split(/[;,]/)
    .map(&:strip).map { |x| org_abbrev[x] || x }
end

#iso_id_reference(params) ⇒ Object



211
212
213
214
# File 'lib/metanorma/iso/front_id.rb', line 211

def iso_id_reference(params)
  params1 = params.dup.tap { |hs| hs.delete(:unpublished) }
  pubid_select(params1).create(**params1)
end

#iso_id_stage(node) ⇒ Object



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

def iso_id_stage(node)
  "#{get_stage(node)}.#{get_substage(node)}"
end

#iso_id_stage_populate(ret, node, stage) ⇒ Object



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

def iso_id_stage_populate(ret, node, stage)
  if stage && !cen?(node.attr("publisher"))
    ret[:stage] = stage
    ret[:stage] == "60.00" and ret[:stage] = :PRF
  end
  ret
end

#iso_id_undated(params) ⇒ Object



194
195
196
197
198
199
200
201
# File 'lib/metanorma/iso/front_id.rb', line 194

def iso_id_undated(params)
  params_nolang = params.dup.tap { |hs| hs.delete(:language) }
  params2 = params_nolang.dup.tap do |hs|
    hs.delete(:year)
    hs.delete(:unpublished)
  end
  pubid_select(params2).create(**params2)
end

#iso_id_with_lang(params) ⇒ Object



203
204
205
206
207
208
209
# File 'lib/metanorma/iso/front_id.rb', line 203

def iso_id_with_lang(params)
  params1 = if params[:unpublished]
              params.dup.tap { |hs| hs.delete(:year) }
            else params end
  params1.delete(:unpublished)
  pubid_select(params1).create(**params1)
end

#iso_id_year(node) ⇒ Object



134
135
136
137
138
# File 'lib/metanorma/iso/front_id.rb', line 134

def iso_id_year(node)
  (node.attr("copyright-year") || node.attr("updated-date") ||
   node.attr("published-date"))
    &.sub(/-.*$/, "") || Date.today.year
end

#iso_xref_validate(doc) ⇒ Object



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

def iso_xref_validate(doc)
  see_xrefs_validate(doc)
  term_xrefs_validate(doc)
  xrefs_mandate_validate(doc)
  see_erefs_validate(doc)
  locality_erefs_validate(doc)
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



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

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



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

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



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

def list_full_sentence(elem)
  %w(Cyrl Latn Grek).include?(@script) or return
  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
66
# File 'lib/metanorma/iso/validate_list.rb', line 56

def list_punctuation1(list, prectext)
  prectext ||= ""
  entries = list.xpath(".//li")
  %w(Cyrl Latn Grek).include?(@script) or return
  case prectext.strip[-1]
  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



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

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



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

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

#list_validate(doc) ⇒ Object



173
174
175
176
# File 'lib/metanorma/iso/validate.rb', line 173

def list_validate(doc)
  listcount_validate(doc)
  list_punctuation(doc)
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



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

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



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

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

#metadata_approval_committee(node, xml) ⇒ Object



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

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



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

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

#metadata_author(node, xml) ⇒ Object



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

def (node, xml)
  org_contributor(node, xml,
                  { source: ["publisher", "pub"], role: "author",
                    default: default_publisher })
  committee_contributors(node, xml, false, default_publisher)
end

#metadata_committee(node, xml) ⇒ Object



90
91
92
93
# File 'lib/metanorma/iso/front_contributor.rb', line 90

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

#metadata_editorial_committee(node, xml) ⇒ Object



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

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



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

def (node, xml)
  super
  structured_id(node, xml)
  (node, xml)
  @amd && a = node.attr("updates-document-type") and
    xml.updates_document_type a
  a = node.attr("fast-track") and xml.send "fast-track", a != "false"
end

#metadata_id(node, xml) ⇒ Object



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

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

#metadata_publisher(node, xml) ⇒ Object



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

def (node, xml)
  super
  # approvals
  committee_contributors(node, xml, true,
                         node.attr("approval-agency") || default_publisher)
end

#metadata_stage(node, xml) ⇒ Object



26
27
28
29
30
31
32
33
34
35
# File 'lib/metanorma/iso/front.rb', line 26

def (node, xml)
  id = iso_id_default(iso_id_params(node))
  id.stage or return
  if abbr = id.typed_stage_abbrev
    abbr = abbr.to_s.upcase.strip
  end
  xml.stagename (id)&.strip,
                **attr_code(abbreviation: abbr)
rescue *STAGE_ERROR
end

#metadata_stagename(id) ⇒ Object



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

def (id)
  if @amd
    id.amendments&.first&.stage&.name ||
      id.corrigendums&.first&.stage&.name
  else
    begin
      id.typed_stage_name
    rescue StandardError
      id.stage&.name
    end
  end
end

#metadata_status(node, xml) ⇒ Object



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

def (node, xml)
  stage = get_stage(node)
  substage = get_substage(node)
  abbrev = iso_id_default(iso_id_params(node)).stage&.abbr&.upcase
  xml.status do |s|
    s.stage stage, **attr_code(abbreviation: abbrev)
    s.substage substage
    i = node.attr("iteration") and s.iteration i
  end
rescue *STAGE_ERROR
  report_illegal_stage(stage, substage)
end

#metadata_subdoctype(node, xml) ⇒ Object



50
51
52
53
# File 'lib/metanorma/iso/front.rb', line 50

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

#norm_bibitem_style(root) ⇒ Object

ISO/IEC DIR 2, 10.2



201
202
203
204
205
206
207
# File 'lib/metanorma/iso/validate_section.rb', line 201

def norm_bibitem_style(root)
  root.xpath(NORM_BIBITEMS).each do |b|
    if b.at(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



53
54
55
56
57
# File 'lib/metanorma/iso/validate_style.rb', line 53

def note_style(node)
  @novalid and return
  style_no_guidance(node, extract_text(node), "Note")
  style(node, extract_text(node))
end

#ol_attrs(node) ⇒ Object



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

def ol_attrs(node)
  attr_code(keep_attrs(node)
            .merge(id: ::Metanorma::Utils::anchor_or_uuid(node),
                   "explicit-type": olist_style(node.attributes[1]),
                   start: node.attr("start")))
end

#ol_cleanup(doc) ⇒ Object



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

def ol_cleanup(doc)
  doc.xpath("//ol[@explicit-type]").each do |x|
    x["type"] = x["explicit-type"]
    x.delete("explicit-type")
    @log.add("Style", x,
             "Style override set for ordered list")
  end
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



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

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



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

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

#org_attrs_parse(node, opts) ⇒ Object



76
77
78
79
80
81
# File 'lib/metanorma/iso/front_contributor.rb', line 76

def org_attrs_parse(node, opts)
  super&.map do |x|
    x.merge(agency: opts[:agency], abbr: opts[:abbr],
            committee: opts[:committee], default_org: opts[:default_org])
  end
end

#org_organization(node, xml, org) ⇒ Object



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

def org_organization(node, xml, org)
  org[:committee] and
    contrib_committee_build(xml, org[:agency], org) or
    super
end

#orig_id_parse(orig) ⇒ Object



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

def orig_id_parse(orig)
  cen?(orig) and return Pubid::Cen::Identifier::Base.parse(orig)
  ret = case orig
        when /^ISO/ then Pubid::Iso::Identifier::Base.parse(orig)
        when /^IEC/ then Pubid::Iec::Identifier.parse(orig)
        else base_pubid::Base.parse(orig)
        end
  ret.edition ||= 1
  ret
end

#other_footnote_renumber(xmldoc) ⇒ Object



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

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



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

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



38
39
40
41
42
# File 'lib/metanorma/iso/base.rb', line 38

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

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

#permission_check(text) ⇒ Object



70
71
72
73
74
75
# File 'lib/metanorma/iso/validate_requirements.rb', line 70

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

#permission_reObject



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

def permission_re
  @lang == "en" or return
  Regexp.new(self.class::PERMISSION_RE_STR.gsub(/\s/, "")
    .gsub("_", "\\s"), Regexp::IGNORECASE)
end

#possibility_check(text) ⇒ Object



93
94
95
96
# File 'lib/metanorma/iso/validate_requirements.rb', line 93

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

#possibility_reObject



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

def possibility_re
  @lang == "en" or return
  Regexp.new(self.class::POSSIBILITY_RE_STR.gsub(/\s/, "")
    .gsub("_", "\\s"), Regexp::IGNORECASE)
end

#presentation_xml_converter(node) ⇒ Object



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

def presentation_xml_converter(node)
  IsoDoc::Iso::PresentationXMLConvert
    .new(html_extract_attributes(node)
    .merge(output_formats: ::Metanorma::Iso::Processor.new.output_formats))
end

#pub_class(bib) ⇒ Object



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

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(#{skip_docid} or @type = 'metanorma')]") ||
    bib.at("./docidentifier[not(@type)]")

  4
end

#pubid_select(params) ⇒ Object



70
71
72
73
74
75
# File 'lib/metanorma/iso/front_id.rb', line 70

def pubid_select(params)
  if cen?(Array(params[:publisher])&.first || "")
    Pubid::Cen::Identifier
  else base_pubid
  end
end

#recommendation_check(text) ⇒ Object



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

def recommendation_check(text)
  @lang == "en" or return
  text.split(/\.\s+/).each do |t|
    return t if recommendation_re.match t
  end
  nil
end

#recommendation_reObject



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

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

#relaton_relation_descriptionsObject



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/metanorma/iso/front.rb', line 134

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



129
130
131
132
# File 'lib/metanorma/iso/front.rb', line 129

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

#replacement_standard(biblio) ⇒ Object



107
108
109
110
111
112
# File 'lib/metanorma/iso/cleanup_biblio.rb', line 107

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

#report_illegal_stage(stage, substage) ⇒ Object



68
69
70
71
# File 'lib/metanorma/iso/front.rb', line 68

def report_illegal_stage(stage, substage)
  err = "Illegal document stage: #{stage}.#{substage}"
  @log.add("Document Attributes", nil, err)
end

#requirement_check(text) ⇒ Object



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

def requirement_check(text)
  @lang == "en" or return
  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



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

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



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

def scope_style(node)
  @novalid and return
  style_no_guidance(node, extract_text(node), "Scope")
end

#section_names_terms_cleanup(xml) ⇒ Object



88
89
90
91
# File 'lib/metanorma/iso/cleanup.rb', line 88

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)
  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)
  onlychild_clause_validate(doc.root)
  @vocab and vocab_terms_titles_validate(doc.root)
  super
end

#sections_cleanup(xml) ⇒ Object



49
50
51
52
53
# File 'lib/metanorma/iso/cleanup.rb', line 49

def sections_cleanup(xml)
  super
  @amd or return
  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



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

def see_erefs_validate(root)
  @lang == "en" or return
  root.xpath("//eref").each do |t|
    prec = t.at("./preceding-sibling::text()[last()]")
    !prec.nil? && /\b(see|refer to)\p{Zs}*\Z/mi.match(prec) or next
    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, 20.2 does not deal with preceding text marked up



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

def see_xrefs_validate(root)
  @lang == "en" or return
  root.xpath("//xref").each do |t|
    preceding = t.at("./preceding-sibling::text()[last()]")
    next unless !preceding.nil? &&
      /\b(see| refer to)\p{Zs}*\Z/mi.match(preceding)

    (target = root.at("//*[@id = '#{t['target']}']")) || next
    target.at("./ancestor-or-self::*[@obligation = 'normative']") &&
      !target.at("./ancestor::sections") and
      @log.add("Style", t,
               "'see #{t['target']}' is pointing to a normative section")
  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



46
47
48
# File 'lib/metanorma/iso/cleanup_biblio.rb', line 46

def sort_biblio(bib)
  bib.sort { |a, b| sort_biblio_key(a) <=> sort_biblio_key(b) }
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



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

def sort_biblio_key(bib)
  pubclass = pub_class(bib)
  num = bib.at("./docnumber")&.text
  id = bib.at("./docidentifier[@primary]") ||
    bib.at("./docidentifier[not(#{skip_docid} or @type = 'metanorma')]")
  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

#starts_lowercase?(text) ⇒ Boolean

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

Returns:

  • (Boolean)


114
115
116
117
# File 'lib/metanorma/iso/validate_list.rb', line 114

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

#starts_uppercase?(text) ⇒ Boolean

Returns:

  • (Boolean)


119
120
121
# File 'lib/metanorma/iso/validate_list.rb', line 119

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

#structured_id(node, xml) ⇒ Object



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

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

#sts_converter(node) ⇒ Object



44
45
46
47
48
# File 'lib/metanorma/iso/base.rb', line 44

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

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

#style(node, text) ⇒ Object



85
86
87
88
89
90
91
92
93
94
# File 'lib/metanorma/iso/validate_style.rb', line 85

def style(node, text)
  @novalid and return
  @novalid_number or style_number(node, text)
  style_percent(node, text)
  style_abbrev(node, text)
  style_units(node, text)
  style_punct(node, text)
  style_subscript(node)
  style_ambig_words(node, text)
end

#style_abbrev(node, text) ⇒ Object

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



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

def style_abbrev(node, text)
  style_regex(/(?:\A|\p{Zs})(?!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_ambig_words(node, text) ⇒ Object



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

def style_ambig_words(node, text)
  r = ambig_words_check(text) and
    style_warning(node, "may contain ambiguous provision", r)
  @lang == "en" and style_regex(/\b(?<num>billions?)\b/i,
                                "ambiguous number", node, text)
end

#style_no_guidance(node, text, docpart) ⇒ Object



125
126
127
128
129
130
131
132
133
# File 'lib/metanorma/iso/validate_requirements.rb', line 125

def style_no_guidance(node, text, docpart)
  @lang == "en" or return
  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



185
186
187
188
189
190
# File 'lib/metanorma/iso/validate_style.rb', line 185

def style_non_std_units(node, text)
  NONSTD_UNITS.each do |k, v|
    style_regex(/\b(?<num>[0-9][0-9,]*\p{Zs}+#{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



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

def style_number(node, text)
  style_number_grouping(node, text)
  style_regex(/(?:^|\p{Zs})(?<num>[0-9]+\.[0-9]+)(?!\.[0-9])/i,
              "possible decimal point: mark up numbers with stem:[]", node, text)
  @lang == "en" and style_regex(/\b(?<num>billions?)\b/i,
                                "ambiguous number", node, text)
  style_regex(/(?:^|\p{Zs})(?<num>-[0-9][0-9,.]*)/i,
              "hyphen instead of minus sign U+2212", node, text)
  @novalid_number = true
end

#style_number_grouping(node, text) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/metanorma/iso/validate_style.rb', line 130

def style_number_grouping(node, text)
  if @validate_years
    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: mark up numbers with stem:[]"
    )
  else
    style_two_regex_not_prev(
      node, text, /^(?<num>-?(?:[0-9]{5,}[,0-9]*|[03-9]\d\d\d|1[0-8]\d\d|2[1-9]\d\d|20[5-9]\d))\Z/,
      %r{\b(ISO|IEC|IEEE|\b)\Z},
      "number not broken up in threes: mark up numbers with stem:[]"
    )
  end
end

#style_percent(node, text) ⇒ Object

ISO/IEC DIR 2, 9.2.1



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

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



194
195
196
197
198
199
200
# File 'lib/metanorma/iso/validate_style.rb', line 194

def style_punct(node, text)
  @lang == "en" and style_regex(/\b(?<num>and\/?or)\b/i,
                                "Use 'either x or y, or both'", node, text)
  style_regex(/\p{Zs}(?<num>&)\p{Zs}/i,
              "Avoid ampersand in ordinary text'", node, text)
  eref_style_punct(node)
end

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



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

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

#style_subscript(node) ⇒ Object



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

def style_subscript(node)
  warning = "may contain nested subscripts (max 3 levels allowed)"
  node.xpath(".//sub[.//sub]").each do |x|
    style_warning(node, warning, x.to_xml)
  end
  node.xpath(".//m:msub[.//m:msub]", "m" => MATHML_NS).each do |x|
    style_warning(node, warning, x.to_xml)
  end
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



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

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



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

def style_units(node, text)
  style_regex(/\b(?<num>[0-9][0-9,]*\p{Zs}+[\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



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

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



209
210
211
212
213
214
# File 'lib/metanorma/iso/validate_section.rb', line 209

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
14
# File 'lib/metanorma/iso/validate_image.rb', line 5

def subfigure_validate(xmldoc)
  elems = { footnote: "fn", note: "note", key: "dl" }
  xmldoc.xpath("//figure//figure").each do |f|
    elems.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

#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

#tc_number(ret, node) ⇒ Object



111
112
113
114
115
116
117
118
119
120
# File 'lib/metanorma/iso/front_id.rb', line 111

def tc_number(ret, node)
  doctype(node) == "committee-document" or return ret
  { sc: "subcommittee", tc: "technical-committee",
    wg: "workgroup" }.each do |k, v|
    n = node.attr("#{v}-number") and
      ret.merge!({ "#{k}type": node.attr("#{v}-type") || k.to_s.upcase,
                   "#{k}number": n })
  end
  ret
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)
  @doctype == "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_contains_subclauses(node) ⇒ Object

in ISO, term has subterm, unless there is no definition to the term (subclauses start immediately), or it is labelled as “grouping”



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

def term_contains_subclauses(node)
  !node.sections? and return false
  node.level != node.blocks[0].level ||
    node.role == "grouping"
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



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

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

#term_xrefs_validate(xmldoc) ⇒ Object



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

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



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

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



78
79
80
81
# File 'lib/metanorma/iso/cleanup.rb', line 78

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

#termdef_boilerplate_insert_locationx(xmldoc) ⇒ Object



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

def termdef_boilerplate_insert_locationx(xmldoc)
  f = xmldoc.at(self.class::TERM_CLAUSE)
  root = xmldoc.at("//sections/terms | //sections/clause[.//terms]")
  !f || !root and return f || root
  f.at("./preceding-sibling::clause") and return root
  f
end

#termdef_style(xmldoc) ⇒ Object

ISO/IEC DIR 2, 16.5.6



127
128
129
130
131
132
133
134
135
136
137
# File 'lib/metanorma/iso/validate.rb', line 127

def termdef_style(xmldoc)
  xmldoc.xpath("//term").each do |t|
    para = t.at("./definition/verbal-definition") || return
    term = t.at("./preferred//name").text
    @lang == "en" and termdef_warn(para.text, /\A(the|a)\b/i, t, term,
                                   "term definition starts with article")
    %(Cyrl Latn).include?(@script) and
      termdef_warn(para.text, /\.\Z/i, t, term,
                   "term definition ends with period")
  end
end

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



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

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

#terms_terms_cleanup(xmldoc) ⇒ Object



93
94
95
96
# File 'lib/metanorma/iso/cleanup.rb', line 93

def terms_terms_cleanup(xmldoc)
  @vocab and return
  super
end

#title(node, xml) ⇒ Object



118
119
120
121
122
123
124
125
126
127
# File 'lib/metanorma/iso/front.rb', line 118

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



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

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



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

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



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

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



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

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



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

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



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

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
64
# File 'lib/metanorma/iso/validate_title.rb', line 53

def title_names_type_validate(root)
  @lang == "en" or return
  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



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

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



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

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



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

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

#toc_defaultObject



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

def toc_default
  { word_levels: 3, html_levels: 2, pdf_levels: 3 }
end

#unpub_footnotes(xmldoc) ⇒ Object



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

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

#unpublished_note(xmldoc) ⇒ Object



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

def unpublished_note(xmldoc)
  xmldoc.xpath("//bibitem[not(./ancestor::bibitem)]" \
               "[not(note[@type = 'Unpublished-Status'])]").each do |b|
    pub_class(b) > 2 and next
    ((s = b.at("./status/stage")) && s.text.match?(/\d/) &&
     (s.text.to_i < 60)) or next
    insert_unpub_note(b, @i18n.under_preparation
      .sub("%", b.at("docidentifier").text))
  end
end

#validate(doc) ⇒ Object



195
196
197
198
199
200
201
202
203
204
# File 'lib/metanorma/iso/validate.rb', line 195

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

#vocab_terms_titles_validate(root) ⇒ Object



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/metanorma/iso/validate_section.rb', line 229

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



90
91
92
93
94
95
96
97
98
99
# File 'lib/metanorma/iso/cleanup_biblio.rb', line 90

def withdrawn_note(xmldoc)
  xmldoc.xpath("//bibitem[not(note[@type = 'Unpublished-Status'])]")
    .each do |b|
      withdrawn_ref?(b) or next
      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)


101
102
103
104
105
# File 'lib/metanorma/iso/cleanup_biblio.rb', line 101

def withdrawn_ref?(biblio)
  pub_class(biblio) > 2 and return false
  (s = biblio.at("./status/stage")) && (s.text.to_i == 95) &&
    (t = biblio.at("./status/substage")) && (t.text.to_i == 99)
end

#xrefs_mandate_validate(xmldoc) ⇒ Object

require that all assets of a particular type be cross-referenced within the document



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

def xrefs_mandate_validate(xmldoc)
  xrefs_mandate_validate1(xmldoc, "//annex", "Annex")
  xrefs_mandate_validate1(xmldoc, "//table", "Table")
  xrefs_mandate_validate1(xmldoc, "//figure", "Figure")
  xrefs_mandate_validate1(xmldoc, "//formula", "Formula")
end

#xrefs_mandate_validate1(xmldoc, xpath, name) ⇒ Object



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

def xrefs_mandate_validate1(xmldoc, xpath, name)
  exc = %w(table note example figure).map { |x| "//#{x}#{xpath}" }
    .join(" | ")
  (xmldoc.xpath(xpath) - xmldoc.xpath(exc)).each do |x|
    x["unnumbered"] == "true" and next
    @doc_xrefs[x["id"]] or
      @log.add("Style", x, "#{name} #{x['id']} has not been " \
                           "cross-referenced within document",
               severity: xpath == "//formula" ? 2 : 1)
  end
end