class Asciidoctor::ISO::Converter
def add_amd_parts(dn, node)
def add_amd_parts(dn, node) a = node.attr("amendment-number") c = node.attr("corrigendum-number") case node.attr("doctype") when "amendment" "#{dn}/Amd #{node.attr('amendment-number')}" when "technical-corrigendum" "#{dn}/Cor.#{node.attr('corrigendum-number')}" end end
def add_id_parts(dn, part, subpart)
def add_id_parts(dn, part, subpart) dn += "-#{part}" if part dn += "-#{subpart}" if subpart dn end
def appendix_parse(attrs, xml, node)
def appendix_parse(attrs, xml, node) attrs["inline-header".to_sym] = 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
def asset_style(root)
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
def bibdata_validate(doc)
def bibdata_validate(doc) doctype_validate(doc) script_validate(doc) stage_validate(doc) substage_validate(doc) iteration_validate(doc) end
def bibitem_validate(xmldoc)
def bibitem_validate(xmldoc) xmldoc.xpath("//bibitem[date/on = '–']").each do |b| b.at("./note[@type = 'ISO DATE']") or @log.add("Style", b, "Reference #{b&.at("./@id")&.text} does not have an "\ "associated footnote indicating unpublished status") end end
def cited_term_style(xmldoc)
def cited_term_style(xmldoc) xmldoc.xpath("//term//xref").each do |x| next unless xmldoc.at("//term[@id = '#{x['target']}']") x&.previous&.text == " (" and x&.previous&.previous&.name == "em" or style_warning(x, "term citation not preceded with italicised term", x.parent.text) end end
def clause_parse(attrs, xml, node)
def clause_parse(attrs, xml, node) node.option? "appendix" and return appendix_parse(attrs, xml, node) super end
def content_validate(doc)
def content_validate(doc) super title_validate(doc.root) isosubgroup_validate(doc.root) onlychild_clause_validate(doc.root) termdef_style(doc.root) iev_validate(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) end
def definition_style(node)
def definition_style(node) return if @novalid r = requirement_check(extract_text(node)) style_warning(node, "Definition may contain requirement", r) if r end
def doc_converter(node)
def doc_converter(node) IsoDoc::Iso::WordConvert.new(doc_extract_attributes(node)) end
def docidentifier_cleanup(xmldoc)
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
def doctype_validate(xmldoc)
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
def example_style(node)
ISO/IEC DIR 2, 16.5.7
def example_style(node) return if @novalid style_no_guidance(node, extract_text(node), "Example") style(node, extract_text(node)) end
def external_constraint(text)
def external_constraint(text) text.split(/\.\s+/).each do |t| return t if /\b(must)\b/xi.match t end nil end
def extract_text(node)
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? } ret end
def footnote_style(node)
def footnote_style(node) return if @novalid style_no_guidance(node, extract_text(node), "Footnote") style(node, extract_text(node)) end
def foreword_style(node)
def foreword_style(node) return if @novalid style_no_guidance(node, extract_text(node), "Foreword") end
def foreword_validate(root)
def foreword_validate(root) f = root.at("//foreword") || return s = f.at("./clause") @log.add("Style", f, "foreword contains subclauses") unless s.nil? end
def format_ref(ref, type, isopub)
def format_ref(ref, type, isopub) ref = ref.sub(/ \(All Parts\)/i, "") super end
def get_id_prefix(xmldoc)
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
def get_stage(node)
def get_stage(node) stage = node.attr("status") || node.attr("docstage") || "60" end
def get_substage(node)
def get_substage(node) stage = get_stage(node) node.attr("docsubstage") || ( stage == "60" ? "60" : "00" ) end
def get_typeabbr(node)
def get_typeabbr(node) case node.attr("doctype") when "technical-report" then "TR " when "technical-specification" then "TS " else nil end end
def html_converter(node)
def html_converter(node) IsoDoc::Iso::HtmlConvert.new(html_extract_attributes(node)) end
def html_converter_alt(node)
def html_converter_alt(node) IsoDoc::Iso::HtmlConvert.new(html_extract_attributes(node). merge(alt: true)) end
def id_add_year(dn, node)
def id_add_year(dn, node) year = node.attr("copyright-year") @amd and year ||= node.attr("updated-date")&.sub(/-.*$/, "") dn += ":#{year}" if year dn end
def id_langsuffix(dn, node)
def id_langsuffix(dn, node) lang = node.attr("language") || "en" suffix = case lang when "en" then "(E)" when "fr" then "(F)" else "(X)" end "#{dn}#{suffix}" end
def id_prefix(prefix, id)
def id_prefix(prefix, id) return id.text if @amd # we're just inheriting the prefixes from parent doc prefix.join("/") + ( id.text.match(%{^/}) ? "" : " " ) + id.text end
def id_stage_abbr(stage, substage, node)
def id_stage_abbr(stage, substage, node) ret = IsoDoc::Iso::Metadata.new("en", "Latn", @i18n). status_abbrev(stage_abbr(stage, substage, node.attr("doctype")), substage, node.attr("iteration"), node.attr("draft"), node.attr("doctype")) if %w(amendment technical-corrigendum amendment technical-corrigendum).include?(node.attr("doctype")) ret = ret + " " unless %w(40 50).include?(stage) end ret end
def id_stage_prefix(dn, node, force_year)
def id_stage_prefix(dn, node, force_year) stage = get_stage(node) typeabbr = get_typeabbr(node) if stage && (stage.to_i < 60) dn = unpub_stage_prefix(dn, stage, typeabbr, node) elsif typeabbr && !@amd then dn = "/#{typeabbr}#{dn}" end (force_year || !(stage && (stage.to_i < 60))) and dn = id_add_year(dn, node) dn end
def init(node)
def init(node) super @amd = %w(amendment technical-corrigendum).include? node.attr("doctype") end
def introduction_style(node)
def introduction_style(node) return if @novalid r = requirement_check(extract_text(node)) style_warning(node, "Introduction may contain requirement", r) if r end
def iso_id(node, xml)
def iso_id(node, xml) return unless !@amd && node.attr("docnumber") || @amd && node.attr("updates") 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
def iso_id1(node)
def iso_id1(node) if @amd dn = node.attr("updates") return add_amd_parts(dn, node) else part, subpart = node&.attr("partnumber")&.split(/-/) return add_id_parts(node.attr("docnumber"), part, subpart) end end
def isosubgroup_validate(root)
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
def iteration_validate(xmldoc)
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
def locality_erefs_validate(root)
def locality_erefs_validate(root) root.xpath("//eref[descendant::locality]").each do |t| if /^(ISO|IEC)/.match t["citeas"] unless /:[ ]?(\d+{4}|–)$/.match t["citeas"] @log.add("Style", t, "undated reference #{t['citeas']} should not contain "\ "specific elements") end end end end
def metadata_author(node, xml)
def metadata_author(node, xml) publishers = node.attr("publisher") || "ISO" csv_split(publishers).each do |p| xml.contributor do |c| c.role **{ type: "author" } c.organization { |a| organization(a, p) } end end end
def metadata_committee(node, xml)
def metadata_committee(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
def metadata_copyright(node, xml)
def metadata_copyright(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 { |o| organization(o, p) } end end end end
def metadata_ext(node, xml)
def metadata_ext(node, xml) super structured_id(node, xml) xml.stagename stage_name(get_stage(node), get_substage(node), node.attr("doctype"), node.attr("iteration")) @amd && a = node.attr("updates-document-type") and xml.updates_document_type a end
def metadata_id(node, xml)
def metadata_id(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
def metadata_publisher(node, xml)
def metadata_publisher(node, xml) publishers = node.attr("publisher") || "ISO" csv_split(publishers).each do |p| xml.contributor do |c| c.role **{ type: "publisher" } c.organization { |a| organization(a, p) } end end end
def metadata_status(node, xml)
def metadata_status(node, xml) stage = get_stage(node) substage = get_substage(node) xml.status do |s| s.stage stage, **attr_code(abbreviation: stage_abbr(stage, substage, node.attr("doctype"))) s.substage substage node.attr("iteration") && (s.iteration node.attr("iteration")) end end
def norm_bibitem_style(root)
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
def normref_validate(root)
def normref_validate(root) f = root.at("//references[@normative = 'true']") || return f.at("./references | ./clause") && @log.add("Style", f, "normative references contains subclauses") end
def note_style(node)
def note_style(node) return if @novalid style_no_guidance(node, extract_text(node), "Note") style(node, extract_text(node)) end
def onlychild_clause_validate(root)
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
def organization(org, orgname)
def organization(org, orgname) if ["ISO", "International Organization for Standardization"].include? orgname org.name "International Organization for Standardization" org.abbreviation "ISO" elsif ["IEC", "International Electrotechnical Commission"].include? orgname org.name "International Electrotechnical Commission" org.abbreviation "IEC" else org.name orgname end end
def other_footnote_renumber(xmldoc)
def other_footnote_renumber(xmldoc) seen = {} i = 0 xmldoc.xpath(PRE_NORMREF_FOOTNOTES).each do |fn| i, seen = other_footnote_renumber1(fn, i, seen) end xmldoc.xpath(NORMREF_FOOTNOTES).each do |fn| i, seen = other_footnote_renumber1(fn, i, seen) end xmldoc.xpath(POST_NORMREF_FOOTNOTES).each do |fn| i, seen = other_footnote_renumber1(fn, i, seen) end end
def outputs(node, ret)
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
def patent_notice_parse(xml, node)
def patent_notice_parse(xml, node) # xml.patent_notice do |xml_section| # xml_section << node.content # end xml << node.content end
def pdf_converter(node)
def pdf_converter(node) return nil if node.attr("no-pdf") IsoDoc::Iso::PdfConvert.new(doc_extract_attributes(node)) end
def permission_check(text)
def permission_check(text) text.split(/\.\s+/).each do |t| return t if PERMISSION_RE.match t end nil end
def possibility(text)
def possibility(text) text.split(/\.\s+/).each { |t| return t if POSSIBILITY_RE.match t } nil end
def presentation_xml_converter(node)
def presentation_xml_converter(node) IsoDoc::Iso::PresentationXMLConvert.new(html_extract_attributes(node)) end
def pub_class(bib)
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
def recommendation_check(text)
def recommendation_check(text) text.split(/\.\s+/).each do |t| return t if RECOMMENDATION_RE.match t end nil end
def requirement_check(text)
def requirement_check(text) text.split(/\.\s+/).each do |t| return t if REQUIREMENT_RE.match t end nil end
def scope_parse(attrs, xml, node)
def scope_parse(attrs, xml, node) attrs = attrs.merge(type: "scope") unless @amd clause_parse(attrs, xml, node) end
def scope_style(node)
def scope_style(node) return if @novalid style_no_guidance(node, extract_text(node), "Scope") end
def script_validate(xmldoc)
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
def section_attributes(node)
def section_attributes(node) super.merge( change: @amd ? node.attr("change") : nil, locality: @amd ? node.attr("locality") : nil, ) end
def section_style(root)
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) end
def section_validate(doc)
def section_validate(doc) foreword_validate(doc.root) normref_validate(doc.root) symbols_validate(doc.root) sections_presence_validate(doc.root) sections_sequence_validate(doc.root) section_style(doc.root) subclause_validate(doc.root) super end
def sections_cleanup(x)
def sections_cleanup(x) super return unless @amd x.xpath("//*[@inline-header]").each do |h| h.delete('inline-header') end end
def sections_presence_validate(root)
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
def sections_sequence_validate(root)
def sections_sequence_validate(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]) if n&.at("./self::introduction") names = seqcheck(names, SEQ[2][:msg], SEQ[2][:val]) end names = seqcheck(names, SEQ[3][:msg], SEQ[3][:val]) n = names.shift if n&.at("./self::definitions") n = names.shift end if n.nil? || n.name != "clause" @log.add("Style", nil, "Document must contain at least one clause") end n&.at("./self::clause") || @log.add("Style", nil, "Document must contain clause after "\ "Terms and Definitions") n&.at("./self::clause[@type = 'scope']") && @log.add("Style", nil, "Scope must occur before Terms and Definitions") n = names.shift while n&.name == "clause" n&.at("./self::clause[@type = 'scope']") @log.add("Style", nil, "Scope must occur before Terms and Definitions") n = names.shift end unless %w(annex references).include? n&.name @log.add("Style", nil, "Only annexes and references can follow clauses") end while n&.name == "annex" n = names.shift if n.nil? @log.add("Style", nil, "Document must include (references) "\ "Normative References") end end n&.at("./self::references[@normative = 'true']") || @log.add("Style", nil, "Document must include (references) "\ "Normative References") n = names&.shift n&.at("./self::references[@normative = 'false']") || @log.add("Style", nil, "Final section must be (references) Bibliography") names.empty? || @log.add("Style", nil, "There are sections after the final Bibliography") end
def sectiontype(node, level = true)
def sectiontype(node, level = true) return nil if @amd super end
def see_erefs_validate(root)
def see_erefs_validate(root) root.xpath("//eref").each do |t| preceding = t.at("./preceding-sibling::text()[last()]") next unless !preceding.nil? && /\b(see|refer to)\s*$/mi.match(preceding) unless target = root.at("//*[@id = '#{t['bibitemid']}']") @log.add("Bibliography", t, "'#{t} is not pointing to a real reference") next end if target.at("./ancestor::references[@normative = 'true']") @log.add("Style", t, "'see #{t}' is pointing to a normative reference") end end end
def see_xrefs_validate(root)
def see_xrefs_validate(root) root.xpath("//xref").each do |t| # does not deal with preceding text marked up preceding = t.at("./preceding-sibling::text()[last()]") next unless !preceding.nil? && /\b(see| refer to)\s*$/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
def seqcheck(names, msg, accepted)
def seqcheck(names, msg, accepted) n = names.shift return [] if n.nil? test = accepted.map { |a| n.at(a) } if test.all? { |a| a.nil? } @log.add("Style", nil, msg) end names end
def sort_biblio(bib)
def sort_biblio(bib) bib.sort do |a, b| sort_biblio_key(a) <=> sort_biblio_key(b) end end
def sort_biblio_key(bib)
then doc id (not DOI &c)
then doc part number if present, numeric sort
else alphanumeric metanorma id (abbreviation)
then docnumber if present, numeric sort
then standard class (docid class other than DOI &c)
sort by: doc class (ISO, IEC, other standard (not DOI &c), other
TODO sort by authors
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
def stage_abbr(stage, substage, doctype)
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" if %w(amendment technical-corrigendum technical-report technical-specification).include?(doctype) ret = "NP" if stage == "10" ret = "AWI" if stage == "10" && substage == "99" ret = "D" if stage == "40" and doctype == "amendment" ret = "FD" if stage == "50" and %w(amendment technical-corrigendum).include?(doctype) ret = "D" if stage == "50" and %w(technical-report technical-specification).include?(doctype) end ret end
def stage_name(stage, substage, doctype, iteration = nil)
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
def stage_validate(xmldoc)
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
def structured_id(node, xml)
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
def sts_converter(node)
def sts_converter(node) return nil if node.attr("no-pdf") IsoDoc::Iso::StsConvert.new(html_extract_attributes(node)) end
def style(n, t)
def style(n, t) return if @novalid style_number(n, t) style_percent(n, t) style_abbrev(n, t) style_units(n, t) end
def style_abbrev(n, t)
ISO/IEC DIR 2, 8.4
def style_abbrev(n, t) style_regex(/(^|\s)(?!e\.g\.|i\.e\.) (?<num>[a-z]{1,2}\.([a-z]{1,2}|\.))\b/ix, "no dots in abbreviations", n, t) style_regex(/\b(?<num>ppm)\b/i, "language-specific abbreviation", n, t) end
def style_no_guidance(node, text, docpart)
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
def style_non_std_units(n, t)
def style_non_std_units(n, t) NONSTD_UNITS.each do |k, v| style_regex(/\b(?<num>[0-9][0-9,]*\s+#{k})\b/, "non-standard unit (should be #{v})", n, t) end end
def style_number(n, t)
ISO/IEC DIR 2, 9.1
def style_number(n, t) style_two_regex_not_prev( n, t, /^(?<num>-?[0-9]{4,}[,0-9]*)$/, %r{\b(ISO|IEC|IEEE/|(in|January|February|March|April|May|June|August|September|October|November|December)\b)$}, "number not broken up in threes") style_regex(/\b(?<num>[0-9]+\.[0-9]+)/i, "possible decimal point", n, t) style_regex(/\b(?<num>billion[s]?)\b/i, "ambiguous number", n, t) end
def style_percent(n, t)
def style_percent(n, t) style_regex(/\b(?<num>[0-9.,]+%)/, "no space before percent sign", n, t) style_regex(/\b(?<num>[0-9.,]+ \u00b1 [0-9,.]+ %)/, "unbracketed tolerance before percent sign", n, t) end
def style_regex(re, warning, n, text)
def style_regex(re, warning, n, text) (m = re.match(text)) && style_warning(n, warning, m[:num]) end
def style_two_regex_not_prev(n, text, re, re_prev, warning)
style check with a regex on a token
def style_two_regex_not_prev(n, text, re, re_prev, warning) return if text.nil? arr = Tokenizer::WhitespaceTokenizer.new.tokenize(text) arr.each_index do |i| m = re.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
def style_units(n, t)
def style_units(n, t) style_regex(/\b(?<num>[0-9][0-9,]*\s+[\u00b0\u2032\u2033])/, "space between number and degrees/minutes/seconds", n, t) style_regex(/\b(?<num>[0-9][0-9,]*#{SI_UNIT})\b/, "no space between number and SI unit", n, t) style_non_std_units(n, t) end
def style_warning(node, msg, text = nil)
def style_warning(node, msg, text = nil) return if @novalid w = msg w += ": #{text}" if text @log.add("Style", node, w) end
def subclause_validate(root)
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
def substage_validate(xmldoc)
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
def symbols_validate(root)
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 e.name == "dl" @log.add("Style", f.first, NON_DL_SYMBOLS_WARNING) return end end end
def termdef_cleanup(xmldoc)
def termdef_cleanup(xmldoc) Asciidoctor::ISO::TermLookupCleanup.new(xmldoc, @log).call super end
def termdef_style(xmldoc)
def termdef_style(xmldoc) xmldoc.xpath("//term").each do |t| para = t.at("./definition") || return term = t.at("./preferred").text termdef_warn(para.text, /^(the|a)\b/i, t, term, "term definition starts with article") termdef_warn(para.text, /\.$/i, t, term, "term definition ends with period") end cited_term_style(xmldoc) end
def termdef_warn(text, re, t, term, msg)
def termdef_warn(text, re, t, term, msg) re.match(text) && @log.add("Style", t, "#{term}: #{msg}") end
def title(node, xml)
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
def title_all_siblings(xpath, label)
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
def title_amd(node, t, lang, at)
def title_amd(node, t, lang, at) return unless node.attr("title-amendment-#{lang}") t.title(**attr_code(at.merge(type: "title-amd"))) do |t1| t1 << Asciidoctor::Standoc::Utils::asciidoc_sub(node.attr("title-amendment-#{lang}")) end end
def title_first_level_validate(root)
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? || @log.add("Style", ss, "#{title}: each first-level subclause must have a title") end end end
def title_full(node, t, lang, at)
def title_full(node, t, 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 t.title **attr_code(at.merge(type: "main")) do |t1| t1 << Asciidoctor::Standoc::Utils::asciidoc_sub(title) end end
def title_intro(node, t, lang, at)
def title_intro(node, t, lang, at) return unless node.attr("title-intro-#{lang}") t.title(**attr_code(at.merge(type: "title-intro"))) do |t1| t1 << Asciidoctor::Standoc::Utils::asciidoc_sub(node.attr("title-intro-#{lang}")) end end
def title_intro_validate(root)
def title_intro_validate(root) title_intro_en = root.at("//title[@type='title-intro' and @language='en']") title_intro_fr = root.at("//title[@type='title-intro' and @language='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
def title_main(node, t, lang, at)
def title_main(node, t, lang, at) t.title **attr_code(at.merge(type: "title-main")) do |t1| t1 << Asciidoctor::Standoc::Utils::asciidoc_sub(node.attr("title-main-#{lang}")) end end
def title_main_validate(root)
def title_main_validate(root) title_main_en = root.at("//title[@type='title-main' and @language='en']") title_main_fr = root.at("//title[@type='title-main' and @language='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
def title_names_type_validate(root)
def title_names_type_validate(root) doctypes = /International\sStandard | Technical\sSpecification | Publicly\sAvailable\sSpecification | Technical\sReport | Guide /xi title_main_en = root.at("//title[@type='title-main' and @language='en']") if !title_main_en.nil? && doctypes.match(title_main_en.text) @log.add("Style", title_main_en, "Main Title may name document type") end title_intro_en = root.at("//title[@type='title-intro' and @language='en']") if !title_intro_en.nil? && doctypes.match(title_intro_en.text) @log.add("Style", title_intro_en, "Title Intro may name document type") end end
def title_part(node, t, lang, at)
def title_part(node, t, lang, at) return unless node.attr("title-part-#{lang}") t.title(**attr_code(at.merge(type: "title-part"))) do |t1| t1 << Asciidoctor::Standoc::Utils::asciidoc_sub(node.attr("title-part-#{lang}")) end end
def title_part_validate(root)
def title_part_validate(root) title_part_en = root.at("//title[@type='title-part' and @language='en']") title_part_fr = root.at("//title[@type='title-part' and @language='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
def title_subpart_validate(root)
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']") @log.add("Style", docid, "Subpart defined on non-IEC document!") if subpart && !iec end
def title_validate(root)
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
def unpub_stage_prefix(dn, stage, typeabbr, node)
def unpub_stage_prefix(dn, 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 dn if abbr.nil? || abbr.empty? # prefixes added in cleanup return "/#{abbr}#{typeabbr} #{dn}#{itersuffix}" unless @amd a = dn.split(%r{/}) a[-1] = "#{abbr}#{a[-1]}#{itersuffix}" a.join("/") end
def validate(doc)
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