Class: Asciidoctor::ISO::Converter

Inherits:
Standoc::Converter
  • Object
show all
Defined in:
lib/asciidoctor/iso/converter.rb,
lib/asciidoctor/iso/base.rb,
lib/asciidoctor/iso/front.rb,
lib/asciidoctor/iso/cleanup.rb,
lib/asciidoctor/iso/section.rb,
lib/asciidoctor/iso/front_id.rb,
lib/asciidoctor/iso/validate.rb,
lib/asciidoctor/iso/validate_image.rb,
lib/asciidoctor/iso/validate_style.rb,
lib/asciidoctor/iso/validate_title.rb,
lib/asciidoctor/iso/validate_section.rb,
lib/asciidoctor/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
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
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
ASSETS_TO_STYLE =
"//termsource | //formula | //termnote | "\
"//p[not(ancestor::boilerplate)] | //li[not(p)] | //dt | "\
"//dd[not(p)] | //td[not(p)] | //th[not(p)]".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



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

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



129
130
131
132
133
# File 'lib/asciidoctor/iso/front_id.rb', line 129

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

#appendix_parse(attrs, xml, node) ⇒ Object



17
18
19
20
21
22
23
24
# File 'lib/asciidoctor/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

#asset_style(root) ⇒ Object



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

def asset_style(root)
  root.xpath("//example | //termexample").each { |e| example_style(e) }
  root.xpath("//definition").each { |e| definition_style(e) }
  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_validate(doc) ⇒ Object



127
128
129
130
131
132
133
# File 'lib/asciidoctor/iso/validate.rb', line 127

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

#bibitem_cleanup(xmldoc) ⇒ Object



146
147
148
149
# File 'lib/asciidoctor/iso/cleanup.rb', line 146

def bibitem_cleanup(xmldoc)
  super
  unpublished_note(xmldoc)
end

#bibitem_validate(xmldoc) ⇒ Object



149
150
151
152
153
154
155
156
# File 'lib/asciidoctor/iso/validate.rb', line 149

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



126
127
128
129
# File 'lib/asciidoctor/iso/cleanup.rb', line 126

def boilerplate_file(_xmldoc)
  file = @lang == "fr" ? "boilerplate-fr.xml" : "boilerplate.xml"
  File.join(@libdir, file)
end

#clause_parse(attrs, xml, node) ⇒ Object



7
8
9
10
# File 'lib/asciidoctor/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



135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/asciidoctor/iso/validate.rb', line 135

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)
  see_erefs_validate(doc.root)
  locality_erefs_validate(doc.root)
  bibdata_validate(doc.root)
  bibitem_validate(doc.root)
  figure_validate(doc.root)
end

#cover_stage_abbr(node) ⇒ Object



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

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, 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/asciidoctor/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/asciidoctor/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



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

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

#docidentifier_cleanup(xmldoc) ⇒ Object

ISO as a prefix goes first



50
51
52
53
54
55
56
57
58
59
60
# File 'lib/asciidoctor/iso/cleanup.rb', line 50

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)
  id = xmldoc.at("//bibdata/docidentifier[@type = 'iso-with-lang']") and
    id.content = id_prefix(prefix, id)
  id = xmldoc.at("//bibdata/docidentifier[@type = 'iso-reference']") and
    id.content = id_prefix(prefix, id)
end

#doctype_validate(xmldoc) ⇒ Object



90
91
92
93
94
95
96
97
# File 'lib/asciidoctor/iso/validate.rb', line 90

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

#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/asciidoctor/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/asciidoctor/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_text(node) ⇒ Object



8
9
10
11
12
13
14
15
16
# File 'lib/asciidoctor/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/asciidoctor/iso/validate_image.rb', line 91

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

#footnote_cleanup(xmldoc) ⇒ Object



131
132
133
134
# File 'lib/asciidoctor/iso/cleanup.rb', line 131

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/asciidoctor/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/asciidoctor/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



21
22
23
24
25
# File 'lib/asciidoctor/iso/validate_section.rb', line 21

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



62
63
64
65
# File 'lib/asciidoctor/iso/cleanup.rb', line 62

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

#get_id_prefix(xmldoc) ⇒ Object



39
40
41
42
43
44
45
46
47
# File 'lib/asciidoctor/iso/cleanup.rb', line 39

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

#get_stage(node) ⇒ Object



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

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



202
203
204
205
206
207
# File 'lib/asciidoctor/iso/front_id.rb', line 202

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



209
210
211
212
213
214
215
216
217
218
219
# File 'lib/asciidoctor/iso/front_id.rb', line 209

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 " : "")
  else
    nil
  end
end

#html_converter(node) ⇒ Object



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

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

#html_converter_alt(node) ⇒ Object



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

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

#id_add_year(docnum, node) ⇒ Object



188
189
190
191
192
193
# File 'lib/asciidoctor/iso/front_id.rb', line 188

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



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

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

#id_prefix(prefix, id) ⇒ Object



32
33
34
35
36
37
# File 'lib/asciidoctor/iso/cleanup.rb', line 32

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



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/asciidoctor/iso/front_id.rb', line 135

def id_stage_abbr(stage, substage, node, bare = false)
  ret = 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
  if %w(amendment technical-corrigendum technical-report
        technical-specification).include?(doctype(node))
    ret = "#{ret} " unless %w(D FD).include?(ret)
  end
  ret
end

#id_stage_prefix(docnum, node, force_year) ⇒ Object



163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/asciidoctor/iso/front_id.rb', line 163

def id_stage_prefix(docnum, node, force_year)
  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
  (force_year || !(stage && (stage.to_i < 60))) and
    docnum = id_add_year(docnum, node)
  docnum
end

#image_name_parse(img, prefix) ⇒ Object



47
48
49
50
51
52
53
54
55
# File 'lib/asciidoctor/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/asciidoctor/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/asciidoctor/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/asciidoctor/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:")

    if /^ISO_\d+_/.match?(File.basename(i["src"]))
    elsif /^(SL)?#{prefix}fig/.match?(File.basename(i["src"]))
      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/asciidoctor/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



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

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

#introduction_style(node) ⇒ Object

ISO/IEC DIR 2, 13.2



33
34
35
36
37
38
# File 'lib/asciidoctor/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



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

def iso_id(node, xml)
  !@amd && node.attr("docnumber") || @amd && node.attr("updates") or
    return

  dn = iso_id1(node)
  dn1 = id_stage_prefix(dn, node, false)
  dn2 = id_stage_prefix(dn, node, true)
  xml.docidentifier dn1, **attr_code(type: "ISO")
  xml.docidentifier(id_langsuffix(dn1, node),
                    **attr_code(type: "iso-with-lang"))
  xml.docidentifier(id_langsuffix(dn2, node),
                    **attr_code(type: "iso-reference"))
end

#iso_id1(node) ⇒ Object



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

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

#isosubgroup_validate(root) ⇒ Object



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

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



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

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

#locality_erefs_validate(root) ⇒ Object

ISO/IEC DIR 2, 10.4



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

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_author(node, xml) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
# File 'lib/asciidoctor/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
87
88
89
90
# File 'lib/asciidoctor/iso/front.rb', line 83

def (node, xml)
  xml.editorialgroup do |a|
    committee_component("technical-committee", node, a)
    committee_component("subcommittee", node, a)
    committee_component("workgroup", node, a)
    node.attr("secretariat") && a.secretariat(node.attr("secretariat"))
  end
end


55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/asciidoctor/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_doctype(node, xml) ⇒ Object



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

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

#metadata_ext(node, xml) ⇒ Object



12
13
14
15
16
17
18
19
# File 'lib/asciidoctor/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



63
64
65
66
67
68
69
# File 'lib/asciidoctor/iso/front_id.rb', line 63

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/asciidoctor/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/asciidoctor/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

#norm_bibitem_style(root) ⇒ Object

ISO/IEC DIR 2, 10.2



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

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



28
29
30
31
32
# File 'lib/asciidoctor/iso/validate_section.rb', line 28

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/asciidoctor/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



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

def ol_attrs(node)
  attr_code(keep_attrs(node)
            .merge(id: ::Metanorma::Utils::anchor_or_uuid(node)))
end

#onlychild_clause_validate(root) ⇒ Object

ISO/IEC DIR 2, 22.3.2



235
236
237
238
239
240
241
242
243
244
# File 'lib/asciidoctor/iso/validate_section.rb', line 235

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/asciidoctor/iso/front.rb', line 26

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

#other_footnote_renumber(xmldoc) ⇒ Object



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

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



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

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/asciidoctor/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



28
29
30
31
32
# File 'lib/asciidoctor/iso/base.rb', line 28

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

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

#permission_check(text) ⇒ Object



67
68
69
70
71
72
# File 'lib/asciidoctor/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/asciidoctor/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/asciidoctor/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/asciidoctor/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



40
41
42
# File 'lib/asciidoctor/iso/base.rb', line 40

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

#pub_class(bib) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
# File 'lib/asciidoctor/iso/cleanup.rb', line 76

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})]")

  4
end

#recommendation_check(text) ⇒ Object



46
47
48
49
50
51
# File 'lib/asciidoctor/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/asciidoctor/iso/validate_requirements.rb', line 41

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

#requirement_check(text) ⇒ Object



26
27
28
29
30
31
# File 'lib/asciidoctor/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/asciidoctor/iso/validate_requirements.rb', line 21

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

#scope_parse(attrs, xml, node) ⇒ Object



12
13
14
15
# File 'lib/asciidoctor/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/asciidoctor/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



99
100
101
102
103
104
# File 'lib/asciidoctor/iso/validate.rb', line 99

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

#section_style(root) ⇒ Object



180
181
182
183
184
185
186
187
188
# File 'lib/asciidoctor/iso/validate_section.rb', line 180

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
# File 'lib/asciidoctor/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)
  super
end

#sections_cleanup(xml) ⇒ Object



117
118
119
120
121
122
123
124
# File 'lib/asciidoctor/iso/cleanup.rb', line 117

def sections_cleanup(xml)
  super
  return unless @amd

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

#sections_presence_validate(root) ⇒ Object



63
64
65
66
67
68
69
70
# File 'lib/asciidoctor/iso/validate_section.rb', line 63

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



101
102
103
104
105
106
107
108
109
# File 'lib/asciidoctor/iso/validate_section.rb', line 101

def sections_sequence_validate(root)
  names, n = sections_sequence_validate_start(root)
  if root&.at("//bibdata/ext/subdoctype")&.text == "vocabulary"
    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



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/asciidoctor/iso/validate_section.rb', line 124

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



147
148
149
150
151
152
153
154
155
# File 'lib/asciidoctor/iso/validate_section.rb', line 147

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



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/asciidoctor/iso/validate_section.rb', line 157

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



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/asciidoctor/iso/validate_section.rb', line 111

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/asciidoctor/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



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

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



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

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



52
53
54
55
56
57
58
59
60
61
# File 'lib/asciidoctor/iso/validate_section.rb', line 52

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

#sort_biblio(bib) ⇒ Object



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

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

#sort_biblio_key(bib) ⇒ Object

TODO sort by authors 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



102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/asciidoctor/iso/cleanup.rb', line 102

def sort_biblio_key(bib)
  pubclass = pub_class(bib)
  num = bib&.at("./docnumber")&.text
  id = 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)} :: "\
    "#{partid} :: #{id&.text} :: #{title}"
end

#stage_abbr(stage, substage, doctype) ⇒ Object



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

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



51
52
53
54
55
56
57
58
59
60
61
# File 'lib/asciidoctor/iso/front_id.rb', line 51

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



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

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

#structured_id(node, xml) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/asciidoctor/iso/front_id.rb', line 115

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



34
35
36
37
38
# File 'lib/asciidoctor/iso/base.rb', line 34

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
# File 'lib/asciidoctor/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)
end

#style_abbrev(node, text) ⇒ Object

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



125
126
127
128
129
130
131
# File 'lib/asciidoctor/iso/validate_style.rb', line 125

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/asciidoctor/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



154
155
156
157
158
159
# File 'lib/asciidoctor/iso/validate_style.rb', line 154

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



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

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)
end

#style_percent(node, text) ⇒ Object

ISO/IEC DIR 2, 9.2.1



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

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_regex(regex, warning, n, text) ⇒ Object



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

def style_regex(regex, warning, n, text)
  (m = regex.match(text)) && style_warning(n, 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/asciidoctor/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



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

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



161
162
163
164
165
166
167
# File 'lib/asciidoctor/iso/validate_style.rb', line 161

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



227
228
229
230
231
232
# File 'lib/asciidoctor/iso/validate_section.rb', line 227

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/asciidoctor/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



113
114
115
116
117
118
# File 'lib/asciidoctor/iso/validate.rb', line 113

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
# File 'lib/asciidoctor/iso/validate_section.rb', line 40

def symbols_validate(root)
  f = root.xpath("//definitions")
  f.empty? && return
  (f.size == 1) || @log.add("Style", f.first, ONE_SYMBOLS_WARNING)
  f.first.elements.each do |e|
    unless %w(title dl).include? e.name
      @log.add("Style", f.first, NON_DL_SYMBOLS_WARNING)
      return
    end
  end
end

#tech_report_style(root) ⇒ Object



190
191
192
193
194
195
196
197
198
# File 'lib/asciidoctor/iso/validate_section.rb', line 190

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_def_subclause_parse(attrs, xml, node) ⇒ Object



42
43
44
45
46
# File 'lib/asciidoctor/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



169
170
171
172
# File 'lib/asciidoctor/iso/cleanup.rb', line 169

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

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



164
165
166
167
# File 'lib/asciidoctor/iso/cleanup.rb', line 164

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

#termdef_style(xmldoc) ⇒ Object

ISO/IEC DIR 2, 16.5.6



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

def termdef_style(xmldoc)
  xmldoc.xpath("//term").each do |t|
    para = t.at("./definition") || return
    term = t.at("./preferred").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



74
75
76
# File 'lib/asciidoctor/iso/validate.rb', line 74

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

#title(node, xml) ⇒ Object



137
138
139
140
141
142
143
144
145
146
# File 'lib/asciidoctor/iso/front.rb', line 137

def title(node, xml)
  ["en", "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/asciidoctor/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



114
115
116
117
118
119
120
121
122
# File 'lib/asciidoctor/iso/front.rb', line 114

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/asciidoctor/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



124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/asciidoctor/iso/front.rb', line 124

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



92
93
94
95
96
97
98
# File 'lib/asciidoctor/iso/front.rb', line 92

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/asciidoctor/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/asciidoctor/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



100
101
102
103
104
# File 'lib/asciidoctor/iso/front.rb', line 100

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/asciidoctor/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/asciidoctor/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_part(node, xml, lang, at) ⇒ Object



106
107
108
109
110
111
112
# File 'lib/asciidoctor/iso/front.rb', line 106

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/asciidoctor/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/asciidoctor/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



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

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)")
end

#unpub_footnotes(xmldoc) ⇒ Object



136
137
138
139
140
141
142
143
144
# File 'lib/asciidoctor/iso/cleanup.rb', line 136

def unpub_footnotes(xmldoc)
  xmldoc.xpath("//bibitem/note[@type = 'Unpublished-Status']").each do |n|
    id = n.parent["id"]
    e = xmldoc.at("//eref[@bibitemid = '#{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



176
177
178
179
180
181
182
183
184
185
186
# File 'lib/asciidoctor/iso/front_id.rb', line 176

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
  return "/#{abbr}#{typeabbr} #{docnum}#{itersuffix}" unless @amd

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

#unpublished_note(xmldoc) ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/asciidoctor/iso/cleanup.rb', line 151

def unpublished_note(xmldoc)
  xmldoc.xpath("//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
    b.at("./language | ./script | ./abstract | ./status")
      .previous = %(<note type="Unpublished-Status">
                    <p>#{@i18n.under_preparation.sub(/%/, id)}</p></note>)
  end
end

#validate(doc) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/asciidoctor/iso/validate.rb', line 158

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