metanorma-utils 1.0.6 → 1.2.0
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 +5 -3
- data/.gitignore +1 -0
- data/.rubocop.yml +10 -2
- data/lib/metanorma-utils.rb +1 -0
- data/lib/utils/image.rb +143 -0
- data/lib/utils/log.rb +28 -25
- data/lib/utils/main.rb +51 -143
- data/lib/utils/version.rb +1 -1
- data/metanorma-utils.gemspec +3 -2
- data/spec/utils_spec.rb +275 -235
- metadata +27 -11
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: a355e89f8c66d323f0ca9954226b2255c1c4706ab9346db5004aa7984ceade6c
         | 
| 4 | 
            +
              data.tar.gz: cc240e1eec9ea056d87b38bae0fea45ac16f1e6f2cef1a6cb2d04c2d20de50e9
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 20d8d717e2113249d060bf15f15faf1150cf5e06d29368f9faf00b0a496aa7c3e711a80c83dbb6fd328cf39ac2e65359825a6632199fb6cdf13cb882934ac11f
         | 
| 7 | 
            +
              data.tar.gz: be4fe517ccc3731678ebaa5fedf2b7ec0923e510c83193a5f73d26db3b9a290cbe3be3aaeda4dbfa90fd76fd8c1bf14099f283b6bd158b00c5138d488c2fdf14
         | 
    
        data/.github/workflows/rake.yml
    CHANGED
    
    | @@ -30,7 +30,9 @@ jobs: | |
| 30 30 | 
             
                        os: 'macos-latest'
         | 
| 31 31 | 
             
                        experimental: true
         | 
| 32 32 | 
             
                steps:
         | 
| 33 | 
            -
                  - uses: actions/checkout@ | 
| 33 | 
            +
                  - uses: actions/checkout@v2
         | 
| 34 | 
            +
                    with:
         | 
| 35 | 
            +
                      submodules: true
         | 
| 34 36 |  | 
| 35 37 | 
             
                  - uses: ruby/setup-ruby@v1
         | 
| 36 38 | 
             
                    with:
         | 
| @@ -45,7 +47,7 @@ jobs: | |
| 45 47 | 
             
                steps:
         | 
| 46 48 | 
             
                  - uses: peter-evans/repository-dispatch@v1
         | 
| 47 49 | 
             
                    with:
         | 
| 48 | 
            -
                      token: ${{ secrets.GITHUB_TOKEN }}
         | 
| 50 | 
            +
                      token: ${{ secrets.METANORMA_CI_PAT_TOKEN || secrets.GITHUB_TOKEN }}
         | 
| 49 51 | 
             
                      repository: ${{ github.repository }}
         | 
| 50 52 | 
             
                      event-type: notify
         | 
| 51 | 
            -
                      client-payload: '{"ref": "${{ github.ref }}"}'
         | 
| 53 | 
            +
                      client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}'
         | 
    
        data/.gitignore
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            .rubocop-https--*
         | 
    
        data/.rubocop.yml
    CHANGED
    
    | @@ -1,6 +1,14 @@ | |
| 1 1 | 
             
            # This project follows the Ribose OSS style guide.
         | 
| 2 2 | 
             
            # https://github.com/riboseinc/oss-guides
         | 
| 3 3 | 
             
            # All project-specific additions and overrides should be specified in this file.
         | 
| 4 | 
            -
             | 
| 5 4 | 
             
            inherit_from:
         | 
| 6 | 
            -
              - https://raw.githubusercontent.com/riboseinc/oss-guides/master/ci/rubocop.yml
         | 
| 5 | 
            +
              - https://raw.githubusercontent.com/riboseinc/oss-guides/master/ci/rubocop.yml
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            # local repo-specific modifications
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            AllCops:
         | 
| 10 | 
            +
              DisplayCopNames: false
         | 
| 11 | 
            +
              StyleGuideCopsOnly: false
         | 
| 12 | 
            +
              TargetRubyVersion: 2.4
         | 
| 13 | 
            +
            Rails:
         | 
| 14 | 
            +
              Enabled: true
         | 
    
        data/lib/metanorma-utils.rb
    CHANGED
    
    
    
        data/lib/utils/image.rb
    ADDED
    
    | @@ -0,0 +1,143 @@ | |
| 1 | 
            +
            require "asciidoctor"
         | 
| 2 | 
            +
            require "tempfile"
         | 
| 3 | 
            +
            require "marcel"
         | 
| 4 | 
            +
            require "mime/types"
         | 
| 5 | 
            +
            require "base64"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Metanorma
         | 
| 8 | 
            +
              module Utils
         | 
| 9 | 
            +
                class << self
         | 
| 10 | 
            +
                  class Namespace
         | 
| 11 | 
            +
                    def initialize(xmldoc)
         | 
| 12 | 
            +
                      @namespace = xmldoc.root.namespace
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    def ns(path)
         | 
| 16 | 
            +
                      return path if @namespace.nil?
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                      path.gsub(%r{/([a-zA-z])}, "/xmlns:\\1")
         | 
| 19 | 
            +
                        .gsub(%r{::([a-zA-z])}, "::xmlns:\\1")
         | 
| 20 | 
            +
                        .gsub(%r{\[([a-zA-z][a-z0-9A-Z@/]* ?=)}, "[xmlns:\\1")
         | 
| 21 | 
            +
                        .gsub(%r{\[([a-zA-z][a-z0-9A-Z@/]*\])}, "[xmlns:\\1")
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def save_dataimage(uri)
         | 
| 26 | 
            +
                    %r{^data:(image|application)/(?<imgtype>[^;]+);base64,(?<imgdata>.+)$} =~ uri
         | 
| 27 | 
            +
                    imgtype.sub!(/\+[a-z0-9]+$/, "") # svg+xml
         | 
| 28 | 
            +
                    imgtype = "png" unless /^[a-z0-9]+$/.match? imgtype
         | 
| 29 | 
            +
                    Tempfile.open(["image", ".#{imgtype}"]) do |f|
         | 
| 30 | 
            +
                      f.binmode
         | 
| 31 | 
            +
                      f.write(Base64.strict_decode64(imgdata))
         | 
| 32 | 
            +
                      f.path
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  SVG_NS = "http://www.w3.org/2000/svg".freeze
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def svgmap_rewrite(xmldoc, localdirectory = "")
         | 
| 39 | 
            +
                    n = Namespace.new(xmldoc)
         | 
| 40 | 
            +
                    xmldoc.xpath(n.ns("//svgmap")).each do |s|
         | 
| 41 | 
            +
                      next unless svgmap_rewrite0(s, n, localdirectory)
         | 
| 42 | 
            +
                      next if s.at(n.ns("./target/eref"))
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                      s.replace(s.at(n.ns("./figure")))
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def svgmap_rewrite0(svgmap, namespace, localdirectory)
         | 
| 49 | 
            +
                    if (i = svgmap.at(namespace.ns(".//image"))) && (src = i["src"])
         | 
| 50 | 
            +
                      path = svgmap_rewrite0_path(src, localdirectory)
         | 
| 51 | 
            +
                      File.file?(path) or return false
         | 
| 52 | 
            +
                      svg = Nokogiri::XML(File.read(path, encoding: "utf-8"))
         | 
| 53 | 
            +
                      i.replace(svgmap_rewrite1(svgmap, svg, namespace))
         | 
| 54 | 
            +
                      /^data:/.match(src) and i["src"] = datauri(path)
         | 
| 55 | 
            +
                    elsif i = svgmap.at(".//m:svg", "m" => SVG_NS)
         | 
| 56 | 
            +
                      i.replace(svgmap_rewrite1(svgmap, i, namespace))
         | 
| 57 | 
            +
                    else
         | 
| 58 | 
            +
                      return false
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
                    true
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  def svgmap_rewrite0_path(src, localdirectory)
         | 
| 64 | 
            +
                    if /^data:/.match?(src)
         | 
| 65 | 
            +
                      save_dataimage(src)
         | 
| 66 | 
            +
                    else
         | 
| 67 | 
            +
                      File.file?(src) ? src : localdirectory + src
         | 
| 68 | 
            +
                    end
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  def svgmap_rewrite1(svgmap, svg, namespace)
         | 
| 72 | 
            +
                    targ = svgmap_rewrite1_targets(svgmap, namespace)
         | 
| 73 | 
            +
                    svg.xpath(".//m:a", "m" => SVG_NS).each do |a|
         | 
| 74 | 
            +
                      ["xlink:href", "href"].each do |p|
         | 
| 75 | 
            +
                        a[p] and x = targ[File.expand_path(a[p])] and a[p] = x
         | 
| 76 | 
            +
                      end
         | 
| 77 | 
            +
                    end
         | 
| 78 | 
            +
                    svg.to_xml
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  def svgmap_rewrite1_targets(svgmap, namespace)
         | 
| 82 | 
            +
                    svgmap.xpath(namespace.ns("./target"))
         | 
| 83 | 
            +
                      .each_with_object({}) do |t, m|
         | 
| 84 | 
            +
                      x = t.at(namespace.ns("./xref")) and
         | 
| 85 | 
            +
                        m[File.expand_path(t["href"])] = "##{x['target']}"
         | 
| 86 | 
            +
                      x = t.at(namespace.ns("./link")) and
         | 
| 87 | 
            +
                        m[File.expand_path(t["href"])] = x["target"]
         | 
| 88 | 
            +
                      t.remove if t.at(namespace.ns("./xref | ./link"))
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                  # sources/plantuml/plantuml20200524-90467-1iqek5i.png
         | 
| 93 | 
            +
                  # already includes localdir
         | 
| 94 | 
            +
                  def datauri(uri, localdirectory = ".")
         | 
| 95 | 
            +
                    return uri if /^data:/.match?(uri)
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                    path = datauri_path(uri, localdirectory)
         | 
| 98 | 
            +
                    return path unless File.exist?(path)
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                    types = MIME::Types.type_for(path)
         | 
| 101 | 
            +
                    type = types ? types.first.to_s : 'text/plain; charset="utf-8"'
         | 
| 102 | 
            +
                    bin = File.open(path, "rb", &:read)
         | 
| 103 | 
            +
                    data = Base64.strict_encode64(bin)
         | 
| 104 | 
            +
                    "data:#{type};base64,#{data}"
         | 
| 105 | 
            +
                  end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                  def datauri_path(uri, localdirectory)
         | 
| 108 | 
            +
                    path = if %r{^([A-Z]:)?/}.match?(uri) then uri
         | 
| 109 | 
            +
                           else
         | 
| 110 | 
            +
                             File.exist?(uri) ? uri : File.join(localdirectory, uri)
         | 
| 111 | 
            +
                           end
         | 
| 112 | 
            +
                    unless File.exist?(path)
         | 
| 113 | 
            +
                      warn "image at #{path} not found"
         | 
| 114 | 
            +
                      return uri
         | 
| 115 | 
            +
                    end
         | 
| 116 | 
            +
                    path
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                  def datauri2mime(uri)
         | 
| 120 | 
            +
                    %r{^data:image/(?<imgtype>[^;]+);base64,(?<imgdata>.+)$} =~ uri
         | 
| 121 | 
            +
                    type = nil
         | 
| 122 | 
            +
                    imgtype = "png" unless /^[a-z0-9]+$/.match? imgtype
         | 
| 123 | 
            +
                    ::Tempfile.open(["imageuri", ".#{imgtype}"]) do |file|
         | 
| 124 | 
            +
                      type = datauri2mime1(file, imgdata)
         | 
| 125 | 
            +
                    end
         | 
| 126 | 
            +
                    [type]
         | 
| 127 | 
            +
                  end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                  def datauri2mime1(file, imgdata)
         | 
| 130 | 
            +
                    type = nil
         | 
| 131 | 
            +
                    begin
         | 
| 132 | 
            +
                      file.binmode
         | 
| 133 | 
            +
                      file.write(Base64.strict_decode64(imgdata))
         | 
| 134 | 
            +
                      file.rewind
         | 
| 135 | 
            +
                      type = Marcel::MimeType.for file
         | 
| 136 | 
            +
                    ensure
         | 
| 137 | 
            +
                      file.close!
         | 
| 138 | 
            +
                    end
         | 
| 139 | 
            +
                    type
         | 
| 140 | 
            +
                  end
         | 
| 141 | 
            +
                end
         | 
| 142 | 
            +
              end
         | 
| 143 | 
            +
            end
         | 
    
        data/lib/utils/log.rb
    CHANGED
    
    | @@ -7,48 +7,51 @@ module Metanorma | |
| 7 7 |  | 
| 8 8 | 
             
                  def add(category, loc, msg)
         | 
| 9 9 | 
             
                    return if @novalid
         | 
| 10 | 
            +
             | 
| 10 11 | 
             
                    @log[category] = [] unless @log[category]
         | 
| 11 12 | 
             
                    @log[category] << { location: current_location(loc), message: msg,
         | 
| 12 13 | 
             
                                        context: context(loc) }
         | 
| 13 14 | 
             
                    loc = loc.nil? ? "" : "(#{current_location(loc)}): "
         | 
| 14 | 
            -
                    warn "#{category}: #{loc}#{msg}" | 
| 15 | 
            +
                    warn "#{category}: #{loc}#{msg}"
         | 
| 15 16 | 
             
                  end
         | 
| 16 17 |  | 
| 17 | 
            -
                  def current_location( | 
| 18 | 
            -
                     | 
| 19 | 
            -
                     | 
| 20 | 
            -
                     | 
| 21 | 
            -
                      ! | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
                     | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
                         | 
| 18 | 
            +
                  def current_location(node)
         | 
| 19 | 
            +
                    if node.nil? then ""
         | 
| 20 | 
            +
                    elsif node.is_a? String then node
         | 
| 21 | 
            +
                    elsif node.respond_to?(:lineno) && !node.lineno.nil? &&
         | 
| 22 | 
            +
                      !node.lineno.empty?
         | 
| 23 | 
            +
                      "Asciidoctor Line #{'%06d' % node.lineno}"
         | 
| 24 | 
            +
                    elsif node.respond_to?(:line) && !node.line.nil?
         | 
| 25 | 
            +
                      "XML Line #{'%06d' % node.line}"
         | 
| 26 | 
            +
                    elsif node.respond_to?(:id) && !node.id.nil? then "ID #{node.id}"
         | 
| 27 | 
            +
                    else
         | 
| 28 | 
            +
                      while !node.nil? &&
         | 
| 29 | 
            +
                          (!node.respond_to?(:level) || node.level.positive?) &&
         | 
| 30 | 
            +
                          (!node.respond_to?(:context) || node.context != :section)
         | 
| 31 | 
            +
                        node = node.parent
         | 
| 32 | 
            +
                        return "Section: #{node.title}" if node&.respond_to?(:context) &&
         | 
| 33 | 
            +
                          node&.context == :section
         | 
| 34 | 
            +
                      end
         | 
| 35 | 
            +
                      "??"
         | 
| 31 36 | 
             
                    end
         | 
| 32 | 
            -
                    "??"
         | 
| 33 37 | 
             
                  end
         | 
| 34 38 |  | 
| 35 | 
            -
                  def context( | 
| 36 | 
            -
                    return nil if  | 
| 37 | 
            -
             | 
| 38 | 
            -
                     | 
| 39 | 
            +
                  def context(node)
         | 
| 40 | 
            +
                    return nil if node.is_a? String
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    node.respond_to?(:to_xml) and return node.to_xml
         | 
| 43 | 
            +
                    node.respond_to?(:to_s) and return node.to_s
         | 
| 39 44 | 
             
                    nil
         | 
| 40 45 | 
             
                  end
         | 
| 41 46 |  | 
| 42 47 | 
             
                  def write(file)
         | 
| 43 48 | 
             
                    File.open(file, "w:UTF-8") do |f|
         | 
| 44 49 | 
             
                      f.puts "#{file} errors"
         | 
| 45 | 
            -
                      @log. | 
| 50 | 
            +
                      @log.each_key do |key|
         | 
| 46 51 | 
             
                        f.puts "\n\n== #{key}\n\n"
         | 
| 47 | 
            -
                        @log[key]. | 
| 48 | 
            -
                          a[:location] <=> b[:location]
         | 
| 49 | 
            -
                        end.each do |n|
         | 
| 52 | 
            +
                        @log[key].sort_by { |a| a[:location] }.each do |n|
         | 
| 50 53 | 
             
                          loc = n[:location] ? "(#{n[:location]}): " : ""
         | 
| 51 | 
            -
                          f.puts "#{loc}#{n[:message]}" | 
| 54 | 
            +
                          f.puts "#{loc}#{n[:message]}"
         | 
| 52 55 | 
             
                          n[:context]&.split(/\n/)&.first(5)&.each { |l| f.puts "\t#{l}" }
         | 
| 53 56 | 
             
                        end
         | 
| 54 57 | 
             
                      end
         | 
    
        data/lib/utils/main.rb
    CHANGED
    
    | @@ -2,56 +2,61 @@ require "asciidoctor" | |
| 2 2 | 
             
            require "tempfile"
         | 
| 3 3 | 
             
            require "sterile"
         | 
| 4 4 | 
             
            require "uuidtools"
         | 
| 5 | 
            -
            require "mimemagic"
         | 
| 6 | 
            -
            require "mime/types"
         | 
| 7 | 
            -
            require "base64"
         | 
| 8 5 |  | 
| 9 6 | 
             
            module Metanorma
         | 
| 10 7 | 
             
              module Utils
         | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
                   | 
| 16 | 
            -
                    "\u203f-\u2040".freeze
         | 
| 8 | 
            +
                NAMECHAR = "\u0000-\u0022\u0024\u002c\u002f\u003a-\u0040\\u005b-\u005e"\
         | 
| 9 | 
            +
                  "\u0060\u007b-\u00b6\u00b8-\u00bf\u00d7\u00f7\u037e\u2000-\u200b"\
         | 
| 10 | 
            +
                  "\u200e-\u203e\u2041-\u206f\u2190-\u2bff\u2ff0-\u3000".freeze
         | 
| 11 | 
            +
                NAMESTARTCHAR = "\\u002d\u002e\u0030-\u0039\u00b7\u0300-\u036f"\
         | 
| 12 | 
            +
                  "\u203f-\u2040".freeze
         | 
| 17 13 |  | 
| 18 14 | 
             
                class << self
         | 
| 19 | 
            -
                  def to_ncname( | 
| 20 | 
            -
                    start =  | 
| 21 | 
            -
                    ret1 = %r([#{NAMECHAR}#]).match(start) | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 15 | 
            +
                  def to_ncname(tag)
         | 
| 16 | 
            +
                    start = tag[0]
         | 
| 17 | 
            +
                    ret1 = if %r([#{NAMECHAR}#]).match?(start)
         | 
| 18 | 
            +
                             "_"
         | 
| 19 | 
            +
                           else
         | 
| 20 | 
            +
                             (%r([#{NAMESTARTCHAR}#]).match?(start) ? "_#{start}" : start)
         | 
| 21 | 
            +
                           end
         | 
| 22 | 
            +
                    ret2 = tag[1..-1] || ""
         | 
| 23 | 
            +
                    (ret1 || "") + ret2.gsub(%r([#{NAMECHAR}#]), "_")
         | 
| 26 24 | 
             
                  end
         | 
| 27 25 |  | 
| 28 26 | 
             
                  def anchor_or_uuid(node = nil)
         | 
| 29 27 | 
             
                    uuid = UUIDTools::UUID.random_create
         | 
| 30 | 
            -
                    node.nil? || node.id.nil? || node.id.empty? ? "_"  | 
| 28 | 
            +
                    node.nil? || node.id.nil? || node.id.empty? ? "_#{uuid}" : node.id
         | 
| 31 29 | 
             
                  end
         | 
| 32 30 |  | 
| 33 | 
            -
                  def asciidoc_sub( | 
| 34 | 
            -
                    return nil if  | 
| 35 | 
            -
                    return "" if  | 
| 36 | 
            -
             | 
| 31 | 
            +
                  def asciidoc_sub(text, flavour = :standoc)
         | 
| 32 | 
            +
                    return nil if text.nil?
         | 
| 33 | 
            +
                    return "" if text.empty?
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    d = Asciidoctor::Document.new(
         | 
| 36 | 
            +
                      text.lines.entries,
         | 
| 37 | 
            +
                      { header_footer: false, backend: flavour }
         | 
| 38 | 
            +
                    )
         | 
| 37 39 | 
             
                    b = d.parse.blocks.first
         | 
| 38 40 | 
             
                    b.apply_subs(b.source)
         | 
| 39 41 | 
             
                  end
         | 
| 40 42 |  | 
| 41 43 | 
             
                  def localdir(node)
         | 
| 42 44 | 
             
                    docfile = node.attr("docfile")
         | 
| 43 | 
            -
                    docfile.nil? ?  | 
| 45 | 
            +
                    docfile.nil? ? "./" : "#{Pathname.new(docfile).parent}/"
         | 
| 44 46 | 
             
                  end
         | 
| 45 47 |  | 
| 46 48 | 
             
                  # TODO needs internationalisation
         | 
| 47 | 
            -
                  def smartformat( | 
| 48 | 
            -
                     | 
| 49 | 
            -
                      gsub(/--/, "—").smart_format.gsub(/</, "<") | 
| 49 | 
            +
                  def smartformat(text)
         | 
| 50 | 
            +
                    text.gsub(/ --? /, " — ")
         | 
| 51 | 
            +
                      .gsub(/--/, "—").smart_format.gsub(/</, "<")
         | 
| 52 | 
            +
                      .gsub(/>/, ">")
         | 
| 50 53 | 
             
                  end
         | 
| 51 54 |  | 
| 52 55 | 
             
                  def endash_date(elem)
         | 
| 53 56 | 
             
                    elem.traverse do |n|
         | 
| 54 | 
            -
                       | 
| 57 | 
            +
                      next unless n.text?
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                      n.replace(n.text.gsub(/\s+--?\s+/, "–").gsub(/--/, "–"))
         | 
| 55 60 | 
             
                    end
         | 
| 56 61 | 
             
                  end
         | 
| 57 62 |  | 
| @@ -60,95 +65,34 @@ module Metanorma | |
| 60 65 | 
             
                  def set_nested_value(hash, keys, new_val)
         | 
| 61 66 | 
             
                    key = keys[0]
         | 
| 62 67 | 
             
                    if keys.length == 1
         | 
| 63 | 
            -
                      hash[key] = hash[key].is_a?(Array) | 
| 64 | 
            -
             | 
| 68 | 
            +
                      hash[key] = if hash[key].is_a?(Array)
         | 
| 69 | 
            +
                                    (hash[key] << new_val)
         | 
| 70 | 
            +
                                  else
         | 
| 71 | 
            +
                                    hash[key].nil? ? new_val : [hash[key], new_val]
         | 
| 72 | 
            +
                                  end
         | 
| 73 | 
            +
                    elsif hash[key].is_a?(Array)
         | 
| 74 | 
            +
                      hash[key][-1] = {} if !hash[key].empty? && hash[key][-1].nil?
         | 
| 75 | 
            +
                      hash[key] << {} if hash[key].empty? || !hash[key][-1].is_a?(Hash)
         | 
| 76 | 
            +
                      set_nested_value(hash[key][-1], keys[1..-1], new_val)
         | 
| 77 | 
            +
                    elsif hash[key].nil? || hash[key].empty?
         | 
| 78 | 
            +
                      hash[key] = {}
         | 
| 79 | 
            +
                      set_nested_value(hash[key], keys[1..-1], new_val)
         | 
| 80 | 
            +
                    elsif hash[key].is_a?(Hash) && !hash[key][keys[1]]
         | 
| 81 | 
            +
                      set_nested_value(hash[key], keys[1..-1], new_val)
         | 
| 82 | 
            +
                    elsif !hash[key][keys[1]]
         | 
| 83 | 
            +
                      hash[key] = [hash[key], {}]
         | 
| 84 | 
            +
                      set_nested_value(hash[key][-1], keys[1..-1], new_val)
         | 
| 65 85 | 
             
                    else
         | 
| 66 | 
            -
                       | 
| 67 | 
            -
                        hash[key][-1] = {} if !hash[key].empty? && hash[key][-1].nil?
         | 
| 68 | 
            -
                        hash[key] << {} if hash[key].empty? || !hash[key][-1].is_a?(Hash)
         | 
| 69 | 
            -
                        set_nested_value(hash[key][-1], keys[1..-1], new_val)
         | 
| 70 | 
            -
                      elsif hash[key].nil? || hash[key].empty?
         | 
| 71 | 
            -
                        hash[key] = {}
         | 
| 72 | 
            -
                        set_nested_value(hash[key], keys[1..-1], new_val)
         | 
| 73 | 
            -
                      elsif hash[key].is_a?(Hash) && !hash[key][keys[1]]
         | 
| 74 | 
            -
                        set_nested_value(hash[key], keys[1..-1], new_val)
         | 
| 75 | 
            -
                      elsif !hash[key][keys[1]]
         | 
| 76 | 
            -
                        hash[key] = [hash[key], {}]
         | 
| 77 | 
            -
                        set_nested_value(hash[key][-1], keys[1..-1], new_val)
         | 
| 78 | 
            -
                      else
         | 
| 79 | 
            -
                        set_nested_value(hash[key], keys[1..-1], new_val)
         | 
| 80 | 
            -
                      end
         | 
| 86 | 
            +
                      set_nested_value(hash[key], keys[1..-1], new_val)
         | 
| 81 87 | 
             
                    end
         | 
| 82 88 | 
             
                    hash
         | 
| 83 89 | 
             
                  end
         | 
| 84 90 |  | 
| 85 | 
            -
                  class Namespace
         | 
| 86 | 
            -
                    def initialize(xmldoc)
         | 
| 87 | 
            -
                      @namespace = xmldoc.root.namespace
         | 
| 88 | 
            -
                    end
         | 
| 89 | 
            -
             | 
| 90 | 
            -
                    def ns(path)
         | 
| 91 | 
            -
                      return path if @namespace.nil?
         | 
| 92 | 
            -
                      path.gsub(%r{/([a-zA-z])}, "/xmlns:\\1").
         | 
| 93 | 
            -
                        gsub(%r{::([a-zA-z])}, "::xmlns:\\1").
         | 
| 94 | 
            -
                        gsub(%r{\[([a-zA-z][a-z0-9A-Z@/]* ?=)}, "[xmlns:\\1").
         | 
| 95 | 
            -
                        gsub(%r{\[([a-zA-z][a-z0-9A-Z@/]*\])}, "[xmlns:\\1")
         | 
| 96 | 
            -
                    end
         | 
| 97 | 
            -
                  end
         | 
| 98 | 
            -
             | 
| 99 | 
            -
                  def save_dataimage(uri)
         | 
| 100 | 
            -
                    %r{^data:(image|application)/(?<imgtype>[^;]+);base64,(?<imgdata>.+)$} =~ uri
         | 
| 101 | 
            -
                    imgtype.sub!(/\+[a-z0-9]+$/, '') # svg+xml
         | 
| 102 | 
            -
                    imgtype = 'png' unless /^[a-z0-9]+$/.match imgtype
         | 
| 103 | 
            -
                    Tempfile.open(['image', ".#{imgtype}"]) do |f|
         | 
| 104 | 
            -
                      f.binmode
         | 
| 105 | 
            -
                      f.write(Base64.strict_decode64(imgdata))
         | 
| 106 | 
            -
                      f.path
         | 
| 107 | 
            -
                    end
         | 
| 108 | 
            -
                  end
         | 
| 109 | 
            -
             | 
| 110 | 
            -
                  SVG_NS = "http://www.w3.org/2000/svg".freeze
         | 
| 111 | 
            -
             | 
| 112 | 
            -
                  def svgmap_rewrite(xmldoc, localdir = "")
         | 
| 113 | 
            -
                    n = Namespace.new(xmldoc)
         | 
| 114 | 
            -
                    xmldoc.xpath(n.ns("//svgmap")).each do |s|
         | 
| 115 | 
            -
                      next unless svgmap_rewrite0(s, n)
         | 
| 116 | 
            -
                      next if s.at(n.ns("./target/eref"))
         | 
| 117 | 
            -
                      s.replace(s.at(n.ns("./figure")))
         | 
| 118 | 
            -
                    end
         | 
| 119 | 
            -
                  end
         | 
| 120 | 
            -
             | 
| 121 | 
            -
                  def svgmap_rewrite0(s, n)
         | 
| 122 | 
            -
                    if i = s.at(n.ns(".//image")) and src = i["src"]
         | 
| 123 | 
            -
                      path = /^data:/.match(src) ? save_dataimage(src) : File.file?(src) ? src : localdir + src
         | 
| 124 | 
            -
                      File.file?(path) or return false
         | 
| 125 | 
            -
                      svgmap_rewrite1(s, Nokogiri::XML(File.read(path, encoding: "utf-8")), path, n)
         | 
| 126 | 
            -
                      /^data:/.match(src) and i["src"] = datauri(path)
         | 
| 127 | 
            -
                    elsif i = s.at(".//m:svg", "m" => SVG_NS)
         | 
| 128 | 
            -
                      svgmap_rewrite1(s, i, nil, n)
         | 
| 129 | 
            -
                    else
         | 
| 130 | 
            -
                      return false
         | 
| 131 | 
            -
                    end                 
         | 
| 132 | 
            -
                    true
         | 
| 133 | 
            -
                  end
         | 
| 134 | 
            -
             | 
| 135 | 
            -
                  def svgmap_rewrite1(s, svg, path, n)
         | 
| 136 | 
            -
                    targets = s.xpath(n.ns("./target")).each_with_object({}) do |t, m|
         | 
| 137 | 
            -
                      x = t.at(n.ns("./xref")) and m[File.expand_path(t["href"])] = "##{x['target']}"
         | 
| 138 | 
            -
                      x = t.at(n.ns("./link")) and m[File.expand_path(t["href"])] = x['target']
         | 
| 139 | 
            -
                      t.remove if t.at(n.ns("./xref | ./link"))
         | 
| 140 | 
            -
                    end
         | 
| 141 | 
            -
                    svg.xpath(".//m:a", "m" => SVG_NS).each do |a|
         | 
| 142 | 
            -
                      a["xlink:href"] and x = targets[File.expand_path(a["xlink:href"])] and a["xlink:href"] = x
         | 
| 143 | 
            -
                      a["href"] and x = targets[File.expand_path(a["href"])] and a["href"] = x
         | 
| 144 | 
            -
                    end
         | 
| 145 | 
            -
                    path and File.open(path, "w", encoding: "utf-8") { |f| f.write(svg.to_xml) }
         | 
| 146 | 
            -
                  end
         | 
| 147 | 
            -
             | 
| 148 91 | 
             
                  # not currently used
         | 
| 149 92 | 
             
                  def flatten_rawtext_lines(node, result)
         | 
| 150 93 | 
             
                    node.lines.each do |x|
         | 
| 151 | 
            -
                      if node.respond_to?(:context) && | 
| 94 | 
            +
                      if node.respond_to?(:context) &&
         | 
| 95 | 
            +
                          (node.context == :literal || node.context == :listing)
         | 
| 152 96 | 
             
                        result << x.gsub(/</, "<").gsub(/>/, ">")
         | 
| 153 97 | 
             
                      else
         | 
| 154 98 | 
             
                        # strip not only HTML <tag>, and Asciidoc xrefs <<xref>>
         | 
| @@ -174,42 +118,6 @@ module Metanorma | |
| 174 118 | 
             
                    end
         | 
| 175 119 | 
             
                    result.reject(&:empty?)
         | 
| 176 120 | 
             
                  end
         | 
| 177 | 
            -
             | 
| 178 | 
            -
                  # FIXME: nested uri path error(
         | 
| 179 | 
            -
                  #   sources/plantuml/plantuml20200524-90467-1iqek5i.png ->
         | 
| 180 | 
            -
                  #   sources/sources/plantuml/plantuml20200524-90467-1iqek5i.png)
         | 
| 181 | 
            -
                  def datauri(uri, localdir = ".")
         | 
| 182 | 
            -
                    return uri if /^data:/.match(uri)
         | 
| 183 | 
            -
                    path = %r{^([A-Z]:)?/}.match?(uri) ? uri : File.join(localdir, uri)
         | 
| 184 | 
            -
                    types = MIME::Types.type_for(path)
         | 
| 185 | 
            -
                    type = types ? types.first.to_s : 'text/plain; charset="utf-8"'
         | 
| 186 | 
            -
                    bin = File.open(path, 'rb', &:read)
         | 
| 187 | 
            -
                    data = Base64.strict_encode64(bin)
         | 
| 188 | 
            -
                    "data:#{type};base64,#{data}"
         | 
| 189 | 
            -
                  end
         | 
| 190 | 
            -
             | 
| 191 | 
            -
                  def datauri2mime(uri)
         | 
| 192 | 
            -
                    %r{^data:image/(?<imgtype>[^;]+);base64,(?<imgdata>.+)$} =~ uri
         | 
| 193 | 
            -
                    type = nil
         | 
| 194 | 
            -
                    imgtype = "png" unless /^[a-z0-9]+$/.match imgtype
         | 
| 195 | 
            -
                    ::Tempfile.open(["imageuri", ".#{imgtype}"]) do |file|
         | 
| 196 | 
            -
                      type = datauri2mime1(file, imgdata)
         | 
| 197 | 
            -
                    end
         | 
| 198 | 
            -
                    [type]
         | 
| 199 | 
            -
                  end
         | 
| 200 | 
            -
             | 
| 201 | 
            -
                  def datauri2mime1(file, imgdata)
         | 
| 202 | 
            -
                    type = nil
         | 
| 203 | 
            -
                    begin
         | 
| 204 | 
            -
                      file.binmode
         | 
| 205 | 
            -
                      file.write(Base64.strict_decode64(imgdata))
         | 
| 206 | 
            -
                      file.rewind
         | 
| 207 | 
            -
                      type = MimeMagic.by_magic(file)
         | 
| 208 | 
            -
                    ensure
         | 
| 209 | 
            -
                      file.close!
         | 
| 210 | 
            -
                    end
         | 
| 211 | 
            -
                    type
         | 
| 212 | 
            -
                  end
         | 
| 213 121 | 
             
                end
         | 
| 214 122 | 
             
              end
         | 
| 215 123 | 
             
            end
         |