metanorma-iso 1.8.0 → 1.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/rake.yml +3 -13
- data/.hound.yml +3 -1
- data/.rubocop.yml +4 -8
- data/lib/asciidoctor/iso/base.rb +14 -11
- data/lib/asciidoctor/iso/biblio.rng +1 -0
- data/lib/asciidoctor/iso/cleanup.rb +40 -24
- data/lib/asciidoctor/iso/front.rb +28 -16
- data/lib/asciidoctor/iso/front_id.rb +66 -50
- data/lib/asciidoctor/iso/isodoc.rng +191 -3
- data/lib/asciidoctor/iso/isostandard-amd.rng +3 -0
- data/lib/asciidoctor/iso/isostandard.rng +12 -0
- data/lib/asciidoctor/iso/section.rb +2 -1
- data/lib/asciidoctor/iso/validate.rb +22 -110
- data/lib/asciidoctor/iso/validate_image.rb +97 -0
- data/lib/asciidoctor/iso/validate_requirements.rb +26 -20
- data/lib/asciidoctor/iso/validate_section.rb +55 -29
- data/lib/asciidoctor/iso/validate_style.rb +36 -24
- data/lib/asciidoctor/iso/validate_title.rb +2 -4
- data/lib/isodoc/iso/base_convert.rb +20 -14
- data/lib/isodoc/iso/html/isodoc.css +475 -20
- data/lib/isodoc/iso/html/isodoc.scss +456 -23
- data/lib/isodoc/iso/html/wordstyle.css +202 -31
- data/lib/isodoc/iso/html/wordstyle.scss +194 -32
- data/lib/isodoc/iso/iso.amendment.xsl +666 -193
- data/lib/isodoc/iso/iso.international-standard.xsl +666 -193
- data/lib/isodoc/iso/metadata.rb +3 -2
- data/lib/isodoc/iso/presentation_xml_convert.rb +15 -14
- data/lib/isodoc/iso/sts_convert.rb +10 -13
- data/lib/isodoc/iso/word_convert.rb +153 -39
- data/lib/isodoc/iso/xref.rb +44 -29
- data/lib/metanorma/iso/processor.rb +1 -0
- data/lib/metanorma/iso/version.rb +1 -1
- data/metanorma-iso.gemspec +3 -3
- data/spec/asciidoctor/base_spec.rb +426 -305
- data/spec/asciidoctor/blocks_spec.rb +96 -34
- data/spec/asciidoctor/cleanup_spec.rb +383 -25
- data/spec/asciidoctor/section_spec.rb +0 -14
- data/spec/asciidoctor/validate_spec.rb +218 -83
- data/spec/isodoc/amd_spec.rb +193 -201
- data/spec/isodoc/blocks_spec.rb +100 -88
- data/spec/isodoc/i18n_spec.rb +36 -36
- data/spec/isodoc/inline_spec.rb +2 -2
- data/spec/isodoc/iso_spec.rb +86 -138
- data/spec/isodoc/postproc_spec.rb +492 -442
- data/spec/isodoc/ref_spec.rb +6 -6
- data/spec/isodoc/section_spec.rb +301 -306
- data/spec/isodoc/table_spec.rb +166 -231
- data/spec/isodoc/terms_spec.rb +11 -8
- data/spec/isodoc/xref_spec.rb +147 -118
- data/spec/spec_helper.rb +16 -15
- metadata +8 -7
| @@ -0,0 +1,97 @@ | |
| 1 | 
            +
            module Asciidoctor
         | 
| 2 | 
            +
              module ISO
         | 
| 3 | 
            +
                class Converter < Standoc::Converter
         | 
| 4 | 
            +
                  # DRG directives 3.7; but anticipated by standoc
         | 
| 5 | 
            +
                  def subfigure_validate(xmldoc)
         | 
| 6 | 
            +
                    xmldoc.xpath("//figure//figure").each do |f|
         | 
| 7 | 
            +
                      { footnote: "fn", note: "note", key: "dl" }.each do |k, v|
         | 
| 8 | 
            +
                        f.xpath(".//#{v}").each do |n|
         | 
| 9 | 
            +
                          @log.add("Style", n, "#{k} is not permitted in a subfigure")
         | 
| 10 | 
            +
                        end
         | 
| 11 | 
            +
                      end
         | 
| 12 | 
            +
                    end
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def image_name_prefix(xmldoc)
         | 
| 16 | 
            +
                    std = xmldoc&.at("//bibdata/ext/structuredidentifier/project-number") or
         | 
| 17 | 
            +
                      return
         | 
| 18 | 
            +
                    num = xmldoc&.at("//bibdata/docnumber")&.text or return
         | 
| 19 | 
            +
                    ed = xmldoc&.at("//bibdata/edition")&.text || "1"
         | 
| 20 | 
            +
                    prefix = num
         | 
| 21 | 
            +
                    std["part"] and prefix += "-#{std['part']}"
         | 
| 22 | 
            +
                    prefix += "_ed#{ed}"
         | 
| 23 | 
            +
                    amd = std["amendment"] and prefix += "amd#{amd}"
         | 
| 24 | 
            +
                    prefix
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def image_name_suffix(xmldoc)
         | 
| 28 | 
            +
                    case xmldoc&.at("//bibdata/language")&.text
         | 
| 29 | 
            +
                    when "fr" then "_f"
         | 
| 30 | 
            +
                    when "de" then "_d"
         | 
| 31 | 
            +
                    when "ru" then "_r"
         | 
| 32 | 
            +
                    when "es" then "_s"
         | 
| 33 | 
            +
                    when "ar" then "_a"
         | 
| 34 | 
            +
                    when "en" then "_e"
         | 
| 35 | 
            +
                    else
         | 
| 36 | 
            +
                      "_e"
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  def disjunct_error(img, cond1, cond2, msg1, msg2)
         | 
| 41 | 
            +
                    cond1 && !cond2 and
         | 
| 42 | 
            +
                      @log.add("Style", img, "image name #{img['src']} #{msg1}")
         | 
| 43 | 
            +
                    !cond1 && cond2 and
         | 
| 44 | 
            +
                      @log.add("Style", img, "image name #{img['src']} #{msg2}")
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  def image_name_parse(img, prefix)
         | 
| 48 | 
            +
                    m = %r[(SL)?#{prefix}fig(?<tab>Tab)?(?<annex>[A-Z])?(Text)?(?<num>\d+)
         | 
| 49 | 
            +
                        (?<subfig>[a-z])?(?<key>_key\d+)?(?<lang>_[a-z])?$]x
         | 
| 50 | 
            +
                      .match(File.basename(img["src"], ".*"))
         | 
| 51 | 
            +
                    m.nil? and
         | 
| 52 | 
            +
                      @log.add("Style", img,
         | 
| 53 | 
            +
                               "image name #{img['src']} does not match DRG requirements")
         | 
| 54 | 
            +
                    m
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  def image_name_validate1(i, prefix)
         | 
| 58 | 
            +
                    m = image_name_parse(i, prefix) or return
         | 
| 59 | 
            +
                    warn i["src"]
         | 
| 60 | 
            +
                    disjunct_error(i, i.at("./ancestor::table"), !m[:tab].nil?,
         | 
| 61 | 
            +
                                   "is under a table but is not so labelled",
         | 
| 62 | 
            +
                                   "is labelled as under a table but is not")
         | 
| 63 | 
            +
                    disjunct_error(i, i.at("./ancestor::annex"), !m[:annex].nil?,
         | 
| 64 | 
            +
                                   "is under an annex but is not so labelled",
         | 
| 65 | 
            +
                                   "is labelled as under an annex but is not")
         | 
| 66 | 
            +
                    disjunct_error(i, i.xpath("./ancestor::figure").size > 1, !m[:subfig].nil?,
         | 
| 67 | 
            +
                                   "does not have a subfigure letter but is a subfigure",
         | 
| 68 | 
            +
                                   "has a subfigure letter but is not a subfigure")
         | 
| 69 | 
            +
                    lang = image_name_suffix(i.document.root)
         | 
| 70 | 
            +
                    (m[:lang] || "_e") == lang or
         | 
| 71 | 
            +
                      @log.add("Style", i,
         | 
| 72 | 
            +
                               "image name #{i['src']} expected to have suffix #{lang}")
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  # DRG directives 3.2
         | 
| 76 | 
            +
                  def image_name_validate(xmldoc)
         | 
| 77 | 
            +
                    prefix = image_name_prefix(xmldoc) or return
         | 
| 78 | 
            +
                    xmldoc.xpath("//image").each do |i|
         | 
| 79 | 
            +
                      next if i["src"].start_with?("data:")
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                      if /^ISO_\d+_/.match?(File.basename(i["src"]))
         | 
| 82 | 
            +
                      elsif /^(SL)?#{prefix}fig/.match?(File.basename(i["src"]))
         | 
| 83 | 
            +
                        image_name_validate1(i, prefix)
         | 
| 84 | 
            +
                      else
         | 
| 85 | 
            +
                        @log.add("Style", i,
         | 
| 86 | 
            +
                                 "image name #{i['src']} does not match DRG requirements: expect #{prefix}fig")
         | 
| 87 | 
            +
                      end
         | 
| 88 | 
            +
                    end
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  def figure_validate(xmldoc)
         | 
| 92 | 
            +
                    image_name_validate(xmldoc)
         | 
| 93 | 
            +
                    subfigure_validate(xmldoc)
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
              end
         | 
| 97 | 
            +
            end
         | 
| @@ -1,6 +1,4 @@ | |
| 1 1 | 
             
            require "metanorma-standoc"
         | 
| 2 | 
            -
            require "nokogiri"
         | 
| 3 | 
            -
            require "pp"
         | 
| 4 2 |  | 
| 5 3 | 
             
            module Asciidoctor
         | 
| 6 4 | 
             
              module ISO
         | 
| @@ -19,13 +17,15 @@ module Asciidoctor | |
| 19 17 | 
             
                       [.,:;]_do_not )
         | 
| 20 18 | 
             
                    \\b
         | 
| 21 19 | 
             
                  REGEXP
         | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def requirement_re
         | 
| 22 | 
            +
                    Regexp.new(self.class::REQUIREMENT_RE_STR.gsub(/\s/, "")
         | 
| 23 | 
            +
                      .gsub(/_/, "\\s"), Regexp::IGNORECASE)
         | 
| 24 | 
            +
                  end
         | 
| 25 25 |  | 
| 26 26 | 
             
                  def requirement_check(text)
         | 
| 27 27 | 
             
                    text.split(/\.\s+/).each do |t|
         | 
| 28 | 
            -
                      return t if  | 
| 28 | 
            +
                      return t if requirement_re.match t
         | 
| 29 29 | 
             
                    end
         | 
| 30 30 | 
             
                    nil
         | 
| 31 31 | 
             
                  end
         | 
| @@ -37,13 +37,15 @@ module Asciidoctor | |
| 37 37 | 
             
                        it_is_(not_)?recommended_that
         | 
| 38 38 | 
             
                    \\b
         | 
| 39 39 | 
             
                  REGEXP
         | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def recommendation_re
         | 
| 42 | 
            +
                    Regexp.new(self.class::RECOMMENDATION_RE_STR.gsub(/\s/, "")
         | 
| 43 | 
            +
                      .gsub(/_/, "\\s"), Regexp::IGNORECASE)
         | 
| 44 | 
            +
                  end
         | 
| 43 45 |  | 
| 44 46 | 
             
                  def recommendation_check(text)
         | 
| 45 47 | 
             
                    text.split(/\.\s+/).each do |t|
         | 
| 46 | 
            -
                      return t if  | 
| 48 | 
            +
                      return t if recommendation_re.match t
         | 
| 47 49 | 
             
                    end
         | 
| 48 50 | 
             
                    nil
         | 
| 49 51 | 
             
                  end
         | 
| @@ -56,13 +58,15 @@ module Asciidoctor | |
| 56 58 | 
             
                        no\\b[^.,]+\\b(is|are)_required
         | 
| 57 59 | 
             
                    \\b
         | 
| 58 60 | 
             
                  REGEXP
         | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 61 | 
            +
             | 
| 62 | 
            +
                  def permission_re
         | 
| 63 | 
            +
                    Regexp.new(self.class::PERMISSION_RE_STR.gsub(/\s/, "")
         | 
| 64 | 
            +
                      .gsub(/_/, "\\s"), Regexp::IGNORECASE)
         | 
| 65 | 
            +
                  end
         | 
| 62 66 |  | 
| 63 67 | 
             
                  def permission_check(text)
         | 
| 64 68 | 
             
                    text.split(/\.\s+/).each do |t|
         | 
| 65 | 
            -
                      return t if  | 
| 69 | 
            +
                      return t if permission_re.match t
         | 
| 66 70 | 
             
                    end
         | 
| 67 71 | 
             
                    nil
         | 
| 68 72 | 
             
                  end
         | 
| @@ -76,18 +80,20 @@ module Asciidoctor | |
| 76 80 | 
             
                       it_is_not_possible_to
         | 
| 77 81 | 
             
                    \\b
         | 
| 78 82 | 
             
                  REGEXP
         | 
| 79 | 
            -
                  POSSIBILITY_RE =
         | 
| 80 | 
            -
                    Regexp.new(POSSIBILITY_RE_STR.gsub(/\s/, "").gsub(/_/, "\\s"),
         | 
| 81 | 
            -
                               Regexp::IGNORECASE)
         | 
| 82 83 |  | 
| 83 | 
            -
                  def  | 
| 84 | 
            -
                     | 
| 84 | 
            +
                  def possibility_re
         | 
| 85 | 
            +
                    Regexp.new(self.class::POSSIBILITY_RE_STR.gsub(/\s/, "")
         | 
| 86 | 
            +
                      .gsub(/_/, "\\s"), Regexp::IGNORECASE)
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                  def possibility_check(text)
         | 
| 90 | 
            +
                    text.split(/\.\s+/).each { |t| return t if possibility_re.match t }
         | 
| 85 91 | 
             
                    nil
         | 
| 86 92 | 
             
                  end
         | 
| 87 93 |  | 
| 88 94 | 
             
                  def external_constraint(text)
         | 
| 89 95 | 
             
                    text.split(/\.\s+/).each do |t|
         | 
| 90 | 
            -
                      return t if /\b(must)\b/xi.match t
         | 
| 96 | 
            +
                      return t if /\b(must)\b/xi.match? t
         | 
| 91 97 | 
             
                    end
         | 
| 92 98 | 
             
                    nil
         | 
| 93 99 | 
             
                  end
         | 
| @@ -50,10 +50,11 @@ module Asciidoctor | |
| 50 50 | 
             
                  end
         | 
| 51 51 |  | 
| 52 52 | 
             
                  def seqcheck(names, msg, accepted)
         | 
| 53 | 
            -
                    n = names.shift | 
| 53 | 
            +
                    n = names.shift
         | 
| 54 54 | 
             
                    return [] if n.nil?
         | 
| 55 | 
            +
             | 
| 55 56 | 
             
                    test = accepted.map { |a| n.at(a) }
         | 
| 56 | 
            -
                    if test.all? | 
| 57 | 
            +
                    if test.all?(&:nil?)
         | 
| 57 58 | 
             
                      @log.add("Style", nil, msg)
         | 
| 58 59 | 
             
                    end
         | 
| 59 60 | 
             
                    names
         | 
| @@ -74,57 +75,65 @@ module Asciidoctor | |
| 74 75 | 
             
                    [
         | 
| 75 76 | 
             
                      {
         | 
| 76 77 | 
             
                        msg: "Initial section must be (content) Foreword",
         | 
| 77 | 
            -
                        val: | 
| 78 | 
            +
                        val: ["./self::foreword"],
         | 
| 78 79 | 
             
                      },
         | 
| 79 80 | 
             
                      {
         | 
| 80 81 | 
             
                        msg: "Prefatory material must be followed by (clause) Scope",
         | 
| 81 | 
            -
                        val: ["./self::introduction", "./self::clause[@type = 'scope']" | 
| 82 | 
            +
                        val: ["./self::introduction", "./self::clause[@type = 'scope']"],
         | 
| 82 83 | 
             
                      },
         | 
| 83 84 | 
             
                      {
         | 
| 84 85 | 
             
                        msg: "Prefatory material must be followed by (clause) Scope",
         | 
| 85 | 
            -
                        val: ["./self::clause[@type = 'scope']" | 
| 86 | 
            +
                        val: ["./self::clause[@type = 'scope']"],
         | 
| 86 87 | 
             
                      },
         | 
| 87 88 | 
             
                      {
         | 
| 88 89 | 
             
                        msg: "Normative References must be followed by "\
         | 
| 89 90 | 
             
                        "Terms and Definitions",
         | 
| 90 | 
            -
                        val: ["./self::terms | .//terms"]
         | 
| 91 | 
            +
                        val: ["./self::terms | .//terms"],
         | 
| 91 92 | 
             
                      },
         | 
| 92 | 
            -
             | 
| 93 | 
            +
                    ].freeze
         | 
| 93 94 |  | 
| 94 95 | 
             
                  SECTIONS_XPATH =
         | 
| 95 96 | 
             
                    "//foreword | //introduction | //sections/terms | .//annex | "\
         | 
| 96 | 
            -
                    "//sections/definitions | //sections/clause |  | 
| 97 | 
            +
                    "//sections/definitions | //sections/clause | "\
         | 
| 98 | 
            +
                    "//references[not(parent::clause)] | "\
         | 
| 97 99 | 
             
                    "//clause[descendant::references][not(parent::clause)]".freeze
         | 
| 98 100 |  | 
| 99 101 | 
             
                  def sections_sequence_validate(root)
         | 
| 102 | 
            +
                    vocab = root&.at("//bibdata/ext/subdoctype")&.text == "vocabulary"
         | 
| 100 103 | 
             
                    names = root.xpath(SECTIONS_XPATH)
         | 
| 101 | 
            -
                    names = seqcheck(names, SEQ[0][:msg], SEQ[0][:val]) | 
| 104 | 
            +
                    names = seqcheck(names, SEQ[0][:msg], SEQ[0][:val])
         | 
| 102 105 | 
             
                    n = names[0]
         | 
| 103 | 
            -
                    names = seqcheck(names, SEQ[1][:msg], SEQ[1][:val]) | 
| 106 | 
            +
                    names = seqcheck(names, SEQ[1][:msg], SEQ[1][:val])
         | 
| 104 107 | 
             
                    if n&.at("./self::introduction")
         | 
| 105 | 
            -
                      names = seqcheck(names, SEQ[2][:msg], SEQ[2][:val]) | 
| 108 | 
            +
                      names = seqcheck(names, SEQ[2][:msg], SEQ[2][:val])
         | 
| 106 109 | 
             
                    end
         | 
| 107 | 
            -
                    names = seqcheck(names, SEQ[3][:msg], SEQ[3][:val]) | 
| 110 | 
            +
                    names = seqcheck(names, SEQ[3][:msg], SEQ[3][:val])
         | 
| 108 111 | 
             
                    n = names.shift
         | 
| 109 112 | 
             
                    if n&.at("./self::definitions")
         | 
| 110 113 | 
             
                      n = names.shift
         | 
| 111 114 | 
             
                    end
         | 
| 112 115 | 
             
                    if n.nil? || n.name != "clause"
         | 
| 113 | 
            -
                      @log.add("Style",  | 
| 116 | 
            +
                      @log.add("Style", n, "Document must contain at least one clause")
         | 
| 114 117 | 
             
                    end
         | 
| 115 118 | 
             
                    n&.at("./self::clause") ||
         | 
| 116 | 
            -
                      @log.add("Style",  | 
| 119 | 
            +
                      @log.add("Style", n, "Document must contain clause after "\
         | 
| 117 120 | 
             
                               "Terms and Definitions")
         | 
| 118 121 | 
             
                    n&.at("./self::clause[@type = 'scope']") &&
         | 
| 119 | 
            -
                      @log.add("Style",  | 
| 120 | 
            -
                    n = names.shift | 
| 121 | 
            -
                    while n&.name == "clause"
         | 
| 122 | 
            +
                      @log.add("Style", n, "Scope must occur before Terms and Definitions")
         | 
| 123 | 
            +
                    n = names.shift
         | 
| 124 | 
            +
                    while n&.name == "clause" || (vocab && n&.name == "terms")
         | 
| 122 125 | 
             
                      n&.at("./self::clause[@type = 'scope']")
         | 
| 123 | 
            -
                      @log.add("Style",  | 
| 124 | 
            -
                      n = names.shift | 
| 126 | 
            +
                      @log.add("Style", n, "Scope must occur before Terms and Definitions")
         | 
| 127 | 
            +
                      n = names.shift
         | 
| 125 128 | 
             
                    end
         | 
| 126 | 
            -
                     | 
| 127 | 
            -
                       | 
| 129 | 
            +
                    if vocab
         | 
| 130 | 
            +
                      unless %w(annex references terms).include? n&.name
         | 
| 131 | 
            +
                        @log.add("Style", n, "Only terms, annexes and references can follow clauses")
         | 
| 132 | 
            +
                      end
         | 
| 133 | 
            +
                    else
         | 
| 134 | 
            +
                      unless %w(annex references).include? n&.name
         | 
| 135 | 
            +
                        @log.add("Style", n, "Only annexes and references can follow clauses")
         | 
| 136 | 
            +
                      end
         | 
| 128 137 | 
             
                    end
         | 
| 129 138 | 
             
                    while n&.name == "annex"
         | 
| 130 139 | 
             
                      n = names.shift
         | 
| @@ -138,13 +147,14 @@ module Asciidoctor | |
| 138 147 | 
             
                               "Normative References")
         | 
| 139 148 | 
             
                    n = names&.shift
         | 
| 140 149 | 
             
                    n&.at("./self::references[@normative = 'false']") ||
         | 
| 141 | 
            -
                      @log.add("Style",  | 
| 150 | 
            +
                      @log.add("Style", n, "Final section must be (references) Bibliography")
         | 
| 142 151 | 
             
                    names.empty? ||
         | 
| 143 | 
            -
                      @log.add("Style",  | 
| 152 | 
            +
                      @log.add("Style", n, "There are sections after the final Bibliography")
         | 
| 144 153 | 
             
                  end
         | 
| 145 154 |  | 
| 146 155 | 
             
                  def style_warning(node, msg, text = nil)
         | 
| 147 156 | 
             
                    return if @novalid
         | 
| 157 | 
            +
             | 
| 148 158 | 
             
                    w = msg
         | 
| 149 159 | 
             
                    w += ": #{text}" if text
         | 
| 150 160 | 
             
                    @log.add("Style", node, w)
         | 
| @@ -165,15 +175,18 @@ module Asciidoctor | |
| 165 175 |  | 
| 166 176 | 
             
                  def tech_report_style(root)
         | 
| 167 177 | 
             
                    root.at("//bibdata/ext/doctype")&.text == "technical-report" or return
         | 
| 168 | 
            -
                    root.xpath("//sections/clause[not(@type = 'scope')] | //annex") | 
| 169 | 
            -
                       | 
| 170 | 
            -
                       | 
| 178 | 
            +
                    root.xpath("//sections/clause[not(@type = 'scope')] | //annex")
         | 
| 179 | 
            +
                      .each do |s|
         | 
| 180 | 
            +
                      r = requirement_check(extract_text(s)) and
         | 
| 181 | 
            +
                        style_warning(s,
         | 
| 182 | 
            +
                                      "Technical Report clause may contain requirement", r)
         | 
| 171 183 | 
             
                    end
         | 
| 172 184 | 
             
                  end
         | 
| 173 185 |  | 
| 174 186 | 
             
                  ASSETS_TO_STYLE =
         | 
| 175 | 
            -
                    "//termsource | //formula | //termnote |  | 
| 176 | 
            -
                    "// | 
| 187 | 
            +
                    "//termsource | //formula | //termnote | "\
         | 
| 188 | 
            +
                    "//p[not(ancestor::boilerplate)] | //li[not(p)] | //dt | "\
         | 
| 189 | 
            +
                    "//dd[not(p)] | //td[not(p)] | //th[not(p)]".freeze
         | 
| 177 190 |  | 
| 178 191 | 
             
                  NORM_BIBITEMS =
         | 
| 179 192 | 
             
                    "//references[@normative = 'true']/bibitem".freeze
         | 
| @@ -198,10 +211,23 @@ module Asciidoctor | |
| 198 211 | 
             
                  end
         | 
| 199 212 |  | 
| 200 213 | 
             
                  def subclause_validate(root)
         | 
| 201 | 
            -
                    root.xpath("//clause/clause/clause/clause/clause/clause/clause/clause") | 
| 214 | 
            +
                    root.xpath("//clause/clause/clause/clause/clause/clause/clause/clause")
         | 
| 215 | 
            +
                      .each do |c|
         | 
| 202 216 | 
             
                      style_warning(c, "Exceeds the maximum clause depth of 7", nil)
         | 
| 203 217 | 
             
                    end
         | 
| 204 218 | 
             
                  end
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                  # ISO/IEC DIR 2, 22.3.2
         | 
| 221 | 
            +
                  def onlychild_clause_validate(root)
         | 
| 222 | 
            +
                    root.xpath(Standoc::Utils::SUBCLAUSE_XPATH).each do |c|
         | 
| 223 | 
            +
                      next unless c.xpath("../clause").size == 1
         | 
| 224 | 
            +
             | 
| 225 | 
            +
                      title = c.at("./title")
         | 
| 226 | 
            +
                      location = c["id"] || "#{c.text[0..60]}..."
         | 
| 227 | 
            +
                      location += ":#{title.text}" if c["id"] && !title.nil?
         | 
| 228 | 
            +
                      @log.add("Style", nil, "#{location}: subclause is only child")
         | 
| 229 | 
            +
                    end
         | 
| 230 | 
            +
                  end
         | 
| 205 231 | 
             
                end
         | 
| 206 232 | 
             
              end
         | 
| 207 233 | 
             
            end
         | 
| @@ -7,28 +7,32 @@ module Asciidoctor | |
| 7 7 | 
             
                class Converter < Standoc::Converter
         | 
| 8 8 | 
             
                  def extract_text(node)
         | 
| 9 9 | 
             
                    return "" if node.nil?
         | 
| 10 | 
            +
             | 
| 10 11 | 
             
                    node1 = Nokogiri::XML.fragment(node.to_s)
         | 
| 11 12 | 
             
                    node1.xpath("//link | //locality | //localityStack").each(&:remove)
         | 
| 12 13 | 
             
                    ret = ""
         | 
| 13 14 | 
             
                    node1.traverse { |x| ret += x.text if x.text? }
         | 
| 14 | 
            -
                    ret
         | 
| 15 | 
            +
                    HTMLEntities.new.decode(ret)
         | 
| 15 16 | 
             
                  end
         | 
| 16 17 |  | 
| 17 18 | 
             
                  # ISO/IEC DIR 2, 12.2
         | 
| 18 19 | 
             
                  def foreword_style(node)
         | 
| 19 20 | 
             
                    return if @novalid
         | 
| 21 | 
            +
             | 
| 20 22 | 
             
                    style_no_guidance(node, extract_text(node), "Foreword")
         | 
| 21 23 | 
             
                  end
         | 
| 22 24 |  | 
| 23 25 | 
             
                  # ISO/IEC DIR 2, 14.2
         | 
| 24 26 | 
             
                  def scope_style(node)
         | 
| 25 27 | 
             
                    return if @novalid
         | 
| 28 | 
            +
             | 
| 26 29 | 
             
                    style_no_guidance(node, extract_text(node), "Scope")
         | 
| 27 30 | 
             
                  end
         | 
| 28 31 |  | 
| 29 32 | 
             
                  # ISO/IEC DIR 2, 13.2
         | 
| 30 33 | 
             
                  def introduction_style(node)
         | 
| 31 34 | 
             
                    return if @novalid
         | 
| 35 | 
            +
             | 
| 32 36 | 
             
                    r = requirement_check(extract_text(node))
         | 
| 33 37 | 
             
                    style_warning(node, "Introduction may contain requirement", r) if r
         | 
| 34 38 | 
             
                  end
         | 
| @@ -36,6 +40,7 @@ module Asciidoctor | |
| 36 40 | 
             
                  # ISO/IEC DIR 2, 16.5.6
         | 
| 37 41 | 
             
                  def definition_style(node)
         | 
| 38 42 | 
             
                    return if @novalid
         | 
| 43 | 
            +
             | 
| 39 44 | 
             
                    r = requirement_check(extract_text(node))
         | 
| 40 45 | 
             
                    style_warning(node, "Definition may contain requirement", r) if r
         | 
| 41 46 | 
             
                  end
         | 
| @@ -44,6 +49,7 @@ module Asciidoctor | |
| 44 49 | 
             
                  # ISO/IEC DIR 2, 25.5
         | 
| 45 50 | 
             
                  def example_style(node)
         | 
| 46 51 | 
             
                    return if @novalid
         | 
| 52 | 
            +
             | 
| 47 53 | 
             
                    style_no_guidance(node, extract_text(node), "Example")
         | 
| 48 54 | 
             
                    style(node, extract_text(node))
         | 
| 49 55 | 
             
                  end
         | 
| @@ -51,6 +57,7 @@ module Asciidoctor | |
| 51 57 | 
             
                  # ISO/IEC DIR 2, 24.5
         | 
| 52 58 | 
             
                  def note_style(node)
         | 
| 53 59 | 
             
                    return if @novalid
         | 
| 60 | 
            +
             | 
| 54 61 | 
             
                    style_no_guidance(node, extract_text(node), "Note")
         | 
| 55 62 | 
             
                    style(node, extract_text(node))
         | 
| 56 63 | 
             
                  end
         | 
| @@ -58,6 +65,7 @@ module Asciidoctor | |
| 58 65 | 
             
                  # ISO/IEC DIR 2, 26.5
         | 
| 59 66 | 
             
                  def footnote_style(node)
         | 
| 60 67 | 
             
                    return if @novalid
         | 
| 68 | 
            +
             | 
| 61 69 | 
             
                    style_no_guidance(node, extract_text(node), "Footnote")
         | 
| 62 70 | 
             
                    style(node, extract_text(node))
         | 
| 63 71 | 
             
                  end
         | 
| @@ -70,6 +78,7 @@ module Asciidoctor | |
| 70 78 | 
             
                  # and a negative match on its preceding token
         | 
| 71 79 | 
             
                  def style_two_regex_not_prev(n, text, re, re_prev, warning)
         | 
| 72 80 | 
             
                    return if text.nil?
         | 
| 81 | 
            +
             | 
| 73 82 | 
             
                    arr = Tokenizer::WhitespaceTokenizer.new.tokenize(text)
         | 
| 74 83 | 
             
                    arr.each_index do |i|
         | 
| 75 84 | 
             
                      m = re.match arr[i]
         | 
| @@ -80,43 +89,45 @@ module Asciidoctor | |
| 80 89 | 
             
                    end
         | 
| 81 90 | 
             
                  end
         | 
| 82 91 |  | 
| 83 | 
            -
                  def style( | 
| 92 | 
            +
                  def style(node, text)
         | 
| 84 93 | 
             
                    return if @novalid
         | 
| 85 | 
            -
             | 
| 86 | 
            -
                     | 
| 87 | 
            -
                     | 
| 88 | 
            -
                     | 
| 94 | 
            +
             | 
| 95 | 
            +
                    style_number(node, text)
         | 
| 96 | 
            +
                    style_percent(node, text)
         | 
| 97 | 
            +
                    style_abbrev(node, text)
         | 
| 98 | 
            +
                    style_units(node, text)
         | 
| 89 99 | 
             
                  end
         | 
| 90 100 |  | 
| 91 101 | 
             
                  # ISO/IEC DIR 2, 9.1
         | 
| 92 102 | 
             
                  # ISO/IEC DIR 2, Table B.1
         | 
| 93 | 
            -
                  def style_number( | 
| 103 | 
            +
                  def style_number(node, text)
         | 
| 94 104 | 
             
                    style_two_regex_not_prev(
         | 
| 95 | 
            -
                       | 
| 105 | 
            +
                      node, text, /^(?<num>-?[0-9]{4,}[,0-9]*)$/,
         | 
| 96 106 | 
             
                      %r{\b(ISO|IEC|IEEE/|(in|January|February|March|April|May|June|August|September|October|November|December)\b)$},
         | 
| 97 | 
            -
                      "number not broken up in threes" | 
| 107 | 
            +
                      "number not broken up in threes"
         | 
| 108 | 
            +
                    )
         | 
| 98 109 | 
             
                    style_regex(/\b(?<num>[0-9]+\.[0-9]+)/i,
         | 
| 99 | 
            -
                                "possible decimal point",  | 
| 100 | 
            -
                    style_regex(/\b(?<num> | 
| 101 | 
            -
                                "ambiguous number",  | 
| 110 | 
            +
                                "possible decimal point", node, text)
         | 
| 111 | 
            +
                    style_regex(/\b(?<num>billions?)\b/i,
         | 
| 112 | 
            +
                                "ambiguous number", node, text)
         | 
| 102 113 | 
             
                  end
         | 
| 103 114 |  | 
| 104 115 | 
             
                  # ISO/IEC DIR 2, 9.2.1
         | 
| 105 | 
            -
                  def style_percent( | 
| 116 | 
            +
                  def style_percent(node, text)
         | 
| 106 117 | 
             
                    style_regex(/\b(?<num>[0-9.,]+%)/,
         | 
| 107 | 
            -
                                "no space before percent sign",  | 
| 118 | 
            +
                                "no space before percent sign", node, text)
         | 
| 108 119 | 
             
                    style_regex(/\b(?<num>[0-9.,]+ \u00b1 [0-9,.]+ %)/,
         | 
| 109 | 
            -
                                "unbracketed tolerance before percent sign",  | 
| 120 | 
            +
                                "unbracketed tolerance before percent sign", node, text)
         | 
| 110 121 | 
             
                  end
         | 
| 111 122 |  | 
| 112 123 | 
             
                  # ISO/IEC DIR 2, 8.4
         | 
| 113 124 | 
             
                  # ISO/IEC DIR 2, 9.3
         | 
| 114 | 
            -
                  def style_abbrev( | 
| 125 | 
            +
                  def style_abbrev(node, text)
         | 
| 115 126 | 
             
                    style_regex(/(^|\s)(?!e\.g\.|i\.e\.)
         | 
| 116 127 | 
             
                                (?<num>[a-z]{1,2}\.([a-z]{1,2}|\.))\b/ix,
         | 
| 117 | 
            -
             | 
| 128 | 
            +
                                "no dots in abbreviations", node, text)
         | 
| 118 129 | 
             
                    style_regex(/\b(?<num>ppm)\b/i,
         | 
| 119 | 
            -
                                "language-specific abbreviation",  | 
| 130 | 
            +
                                "language-specific abbreviation", node, text)
         | 
| 120 131 | 
             
                  end
         | 
| 121 132 |  | 
| 122 133 | 
             
                  # leaving out as problematic: N J K C S T H h d B o E
         | 
| @@ -125,12 +136,13 @@ module Asciidoctor | |
| 125 136 | 
             
                    "bit|kB|MB|Hart|nat|Sh|var)".freeze
         | 
| 126 137 |  | 
| 127 138 | 
             
                  # ISO/IEC DIR 2, 9.3
         | 
| 128 | 
            -
                  def style_units( | 
| 139 | 
            +
                  def style_units(node, text)
         | 
| 129 140 | 
             
                    style_regex(/\b(?<num>[0-9][0-9,]*\s+[\u00b0\u2032\u2033])/,
         | 
| 130 | 
            -
                                "space between number and degrees/minutes/seconds", | 
| 141 | 
            +
                                "space between number and degrees/minutes/seconds",
         | 
| 142 | 
            +
                                node, text)
         | 
| 131 143 | 
             
                    style_regex(/\b(?<num>[0-9][0-9,]*#{SI_UNIT})\b/,
         | 
| 132 | 
            -
                                "no space between number and SI unit",  | 
| 133 | 
            -
                    style_non_std_units( | 
| 144 | 
            +
                                "no space between number and SI unit", node, text)
         | 
| 145 | 
            +
                    style_non_std_units(node, text)
         | 
| 134 146 | 
             
                  end
         | 
| 135 147 |  | 
| 136 148 | 
             
                  NONSTD_UNITS = {
         | 
| @@ -139,10 +151,10 @@ module Asciidoctor | |
| 139 151 | 
             
                  }.freeze
         | 
| 140 152 |  | 
| 141 153 | 
             
                  # ISO/IEC DIR 2, 9.3
         | 
| 142 | 
            -
                  def style_non_std_units( | 
| 154 | 
            +
                  def style_non_std_units(node, text)
         | 
| 143 155 | 
             
                    NONSTD_UNITS.each do |k, v|
         | 
| 144 156 | 
             
                      style_regex(/\b(?<num>[0-9][0-9,]*\s+#{k})\b/,
         | 
| 145 | 
            -
                                  "non-standard unit (should be #{v})",  | 
| 157 | 
            +
                                  "non-standard unit (should be #{v})", node, text)
         | 
| 146 158 | 
             
                    end
         | 
| 147 159 | 
             
                  end
         | 
| 148 160 | 
             
                end
         |