asciidoctor 0.1.4 → 1.5.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.
Potentially problematic release.
This version of asciidoctor might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +209 -25
- data/{LICENSE → LICENSE.adoc} +4 -3
- data/README.adoc +392 -395
- data/Rakefile +94 -137
- data/benchmark/benchmark.rb +127 -0
- data/benchmark/sample-data/mdbasics.adoc +334 -0
- data/bin/asciidoctor +5 -8
- data/bin/asciidoctor-safe +4 -8
- data/compat/asciidoc.conf +78 -11
- data/compat/font-awesome-3-compat.css +397 -0
- data/data/stylesheets/asciidoctor-default.css +399 -0
- data/data/stylesheets/coderay-asciidoctor.css +89 -0
- data/features/open_block.feature +92 -0
- data/features/pass_block.feature +66 -0
- data/features/step_definitions.rb +42 -0
- data/features/text_formatting.feature +55 -0
- data/features/xref.feature +116 -0
- data/lib/asciidoctor.rb +1155 -605
- data/lib/asciidoctor/abstract_block.rb +157 -71
- data/lib/asciidoctor/abstract_node.rb +150 -93
- data/lib/asciidoctor/attribute_list.rb +85 -90
- data/lib/asciidoctor/block.rb +51 -24
- data/lib/asciidoctor/callouts.rb +4 -7
- data/lib/asciidoctor/cli.rb +3 -0
- data/lib/asciidoctor/cli/invoker.rb +86 -76
- data/lib/asciidoctor/cli/options.rb +111 -61
- data/lib/asciidoctor/converter.rb +232 -0
- data/lib/asciidoctor/converter/base.rb +58 -0
- data/lib/asciidoctor/converter/composite.rb +66 -0
- data/lib/asciidoctor/converter/docbook45.rb +94 -0
- data/lib/asciidoctor/converter/docbook5.rb +684 -0
- data/lib/asciidoctor/converter/factory.rb +225 -0
- data/lib/asciidoctor/converter/html5.rb +1081 -0
- data/lib/asciidoctor/converter/template.rb +296 -0
- data/lib/asciidoctor/core_ext.rb +7 -0
- data/lib/asciidoctor/core_ext/object/nil_or_empty.rb +23 -0
- data/lib/asciidoctor/core_ext/string/chr.rb +6 -0
- data/lib/asciidoctor/core_ext/symbol/length.rb +6 -0
- data/lib/asciidoctor/document.rb +590 -304
- data/lib/asciidoctor/extensions.rb +1100 -308
- data/lib/asciidoctor/helpers.rb +109 -46
- data/lib/asciidoctor/inline.rb +16 -9
- data/lib/asciidoctor/list.rb +23 -15
- data/lib/asciidoctor/opal_ext.rb +4 -0
- data/lib/asciidoctor/opal_ext/comparable.rb +38 -0
- data/lib/asciidoctor/opal_ext/dir.rb +13 -0
- data/lib/asciidoctor/opal_ext/error.rb +2 -0
- data/lib/asciidoctor/opal_ext/file.rb +125 -0
- data/lib/asciidoctor/{lexer.rb → parser.rb} +646 -455
- data/lib/asciidoctor/path_resolver.rb +141 -77
- data/lib/asciidoctor/reader.rb +257 -187
- data/lib/asciidoctor/section.rb +12 -16
- data/lib/asciidoctor/stylesheets.rb +91 -0
- data/lib/asciidoctor/substitutors.rb +1548 -0
- data/lib/asciidoctor/table.rb +73 -57
- data/lib/asciidoctor/timings.rb +39 -0
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +22 -14
- data/man/asciidoctor.adoc +18 -10
- data/test/attributes_test.rb +314 -14
- data/test/blocks_test.rb +763 -118
- data/test/converter_test.rb +352 -0
- data/test/document_test.rb +518 -199
- data/test/extensions_test.rb +273 -103
- data/test/fixtures/asciidoc_index.txt +27 -13
- data/test/fixtures/basic-docinfo.xml +1 -1
- data/test/fixtures/chapter-a.adoc +3 -0
- data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +6 -0
- data/test/fixtures/docinfo.xml +1 -1
- data/test/fixtures/include-file.asciidoc +2 -0
- data/test/fixtures/master.adoc +5 -0
- data/test/invoker_test.rb +173 -61
- data/test/links_test.rb +97 -21
- data/test/lists_test.rb +181 -22
- data/test/options_test.rb +86 -2
- data/test/paragraphs_test.rb +47 -5
- data/test/{lexer_test.rb → parser_test.rb} +128 -57
- data/test/paths_test.rb +36 -1
- data/test/preamble_test.rb +25 -17
- data/test/reader_test.rb +404 -249
- data/test/sections_test.rb +623 -58
- data/test/substitutions_test.rb +609 -132
- data/test/tables_test.rb +198 -24
- data/test/test_helper.rb +101 -31
- data/test/text_test.rb +88 -31
- metadata +160 -64
- data/Gemfile +0 -12
- data/Guardfile +0 -18
- data/asciidoctor.gemspec +0 -143
- data/lib/asciidoctor/backends/_stylesheets.rb +0 -466
- data/lib/asciidoctor/backends/base_template.rb +0 -114
- data/lib/asciidoctor/backends/docbook45.rb +0 -774
- data/lib/asciidoctor/backends/docbook5.rb +0 -103
- data/lib/asciidoctor/backends/html5.rb +0 -1214
- data/lib/asciidoctor/renderer.rb +0 -259
- data/lib/asciidoctor/substituters.rb +0 -1083
- data/test/fixtures/asciidoc.txt +0 -105
- data/test/fixtures/ascshort.txt +0 -32
- data/test/fixtures/list_elements.asciidoc +0 -10
- data/test/renderer_test.rb +0 -162
| @@ -6,9 +6,6 @@ class AbstractBlock < AbstractNode | |
| 6 6 | 
             
              # Public: Substitutions to be applied to content in this block
         | 
| 7 7 | 
             
              attr_reader :subs
         | 
| 8 8 |  | 
| 9 | 
            -
              # Public: Get/Set the String name of the render template
         | 
| 10 | 
            -
              attr_accessor :template_name
         | 
| 11 | 
            -
             | 
| 12 9 | 
             
              # Public: Get the Array of Asciidoctor::AbstractBlock sub-blocks for this block
         | 
| 13 10 | 
             
              attr_reader :blocks
         | 
| 14 11 |  | 
| @@ -24,40 +21,72 @@ class AbstractBlock < AbstractNode | |
| 24 21 | 
             
              # Public: Get/Set the caption for this block
         | 
| 25 22 | 
             
              attr_accessor :caption
         | 
| 26 23 |  | 
| 27 | 
            -
               | 
| 28 | 
            -
             | 
| 24 | 
            +
              # Public: Gets/Sets the location in the AsciiDoc source where this block begins
         | 
| 25 | 
            +
              attr_accessor :source_location
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              def initialize parent, context, opts = {}
         | 
| 28 | 
            +
                super
         | 
| 29 29 | 
             
                @content_model = :compound
         | 
| 30 30 | 
             
                @subs = []
         | 
| 31 | 
            -
                @ | 
| 31 | 
            +
                @default_subs = nil
         | 
| 32 32 | 
             
                @blocks = []
         | 
| 33 33 | 
             
                @id = nil
         | 
| 34 34 | 
             
                @title = nil
         | 
| 35 35 | 
             
                @caption = nil
         | 
| 36 36 | 
             
                @style = nil
         | 
| 37 | 
            -
                if context == :document
         | 
| 38 | 
            -
                   | 
| 39 | 
            -
                elsif  | 
| 40 | 
            -
                   | 
| 41 | 
            -
                else
         | 
| 42 | 
            -
                  @level = nil
         | 
| 37 | 
            +
                @level = if context == :document
         | 
| 38 | 
            +
                  0
         | 
| 39 | 
            +
                elsif parent && context != :section
         | 
| 40 | 
            +
                  parent.level
         | 
| 43 41 | 
             
                end
         | 
| 44 42 | 
             
                @next_section_index = 0
         | 
| 45 43 | 
             
                @next_section_number = 1
         | 
| 44 | 
            +
                @source_location = nil
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              def block?
         | 
| 48 | 
            +
                true
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              def inline?
         | 
| 52 | 
            +
                false
         | 
| 46 53 | 
             
              end
         | 
| 47 54 |  | 
| 48 | 
            -
              # Public:  | 
| 55 | 
            +
              # Public: Update the context of this block.
         | 
| 56 | 
            +
              #
         | 
| 57 | 
            +
              # This method changes the context of this block. It also
         | 
| 58 | 
            +
              # updates the node name accordingly.
         | 
| 59 | 
            +
              def context=(context)
         | 
| 60 | 
            +
                @context = context
         | 
| 61 | 
            +
                @node_name = context.to_s
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
              # Public: Get the converted String content for this Block.  If the block
         | 
| 49 65 | 
             
              # has child blocks, the content method should cause them to be
         | 
| 50 | 
            -
              #  | 
| 66 | 
            +
              # converted and returned as content that can be included in the
         | 
| 51 67 | 
             
              # parent block's template.
         | 
| 52 | 
            -
              def  | 
| 68 | 
            +
              def convert
         | 
| 53 69 | 
             
                @document.playback_attributes @attributes
         | 
| 54 | 
            -
                 | 
| 70 | 
            +
                converter.convert self
         | 
| 55 71 | 
             
              end
         | 
| 56 72 |  | 
| 57 | 
            -
              #  | 
| 73 | 
            +
              # Alias render to convert to maintain backwards compatibility
         | 
| 74 | 
            +
              alias :render :convert
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              # Public: Get the converted result of the child blocks by converting the
         | 
| 58 77 | 
             
              # children appropriate to content model that this block supports.
         | 
| 59 78 | 
             
              def content
         | 
| 60 | 
            -
                @blocks.map {|b| b. | 
| 79 | 
            +
                @blocks.map {|b| b.convert } * EOL
         | 
| 80 | 
            +
              end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
              # Public: Get the source file where this block started
         | 
| 83 | 
            +
              def file
         | 
| 84 | 
            +
                @source_location ? @source_location.file : nil
         | 
| 85 | 
            +
              end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
              # Public: Get the source line number where this block started
         | 
| 88 | 
            +
              def lineno
         | 
| 89 | 
            +
                @source_location ? @source_location.lineno : nil
         | 
| 61 90 | 
             
              end
         | 
| 62 91 |  | 
| 63 92 | 
             
              # Public: A convenience method that checks whether the specified
         | 
| @@ -74,7 +103,7 @@ class AbstractBlock < AbstractNode | |
| 74 103 | 
             
              # Public: A convenience method that indicates whether the title instance
         | 
| 75 104 | 
             
              # variable is blank (nil or empty)
         | 
| 76 105 | 
             
              def title?
         | 
| 77 | 
            -
                !@title. | 
| 106 | 
            +
                !@title.nil_or_empty?
         | 
| 78 107 | 
             
              end
         | 
| 79 108 |  | 
| 80 109 | 
             
              # Public: Get the String title of this Block with title substitions applied
         | 
| @@ -161,52 +190,100 @@ class AbstractBlock < AbstractNode | |
| 161 190 | 
             
              #   section.sections.size
         | 
| 162 191 | 
             
              #   # => 1
         | 
| 163 192 | 
             
              #
         | 
| 164 | 
            -
              #  | 
| 193 | 
            +
              # Returns an [Array] of Section objects
         | 
| 165 194 | 
             
              def sections
         | 
| 166 | 
            -
                @blocks. | 
| 167 | 
            -
                  collector << block if block.is_a?(Section)
         | 
| 168 | 
            -
                  collector
         | 
| 169 | 
            -
                }
         | 
| 195 | 
            +
                @blocks.select {|block| block.context == :section }
         | 
| 170 196 | 
             
              end
         | 
| 171 197 |  | 
| 172 | 
            -
             | 
| 173 | 
            -
             | 
| 174 | 
            -
               | 
| 175 | 
            -
             | 
| 176 | 
            -
              #  | 
| 177 | 
            -
              #  | 
| 198 | 
            +
            # stage the Enumerable mixin until we're sure we've got it right
         | 
| 199 | 
            +
            =begin
         | 
| 200 | 
            +
              include ::Enumerable
         | 
| 201 | 
            +
             | 
| 202 | 
            +
              # Public: Yield the block on this block node and all its descendant
         | 
| 203 | 
            +
              # block node children to satisfy the Enumerable contract.
         | 
| 178 204 | 
             
              #
         | 
| 179 205 | 
             
              # Returns nothing
         | 
| 180 | 
            -
              def  | 
| 181 | 
            -
                 | 
| 182 | 
            -
                 | 
| 183 | 
            -
             | 
| 184 | 
            -
             | 
| 185 | 
            -
             | 
| 186 | 
            -
                   | 
| 187 | 
            -
             | 
| 206 | 
            +
              def each &block
         | 
| 207 | 
            +
                # yucky, dlist is a special case
         | 
| 208 | 
            +
                if @context == :dlist
         | 
| 209 | 
            +
                  @blocks.flatten.each &block
         | 
| 210 | 
            +
                else
         | 
| 211 | 
            +
                  #yield self.header if @context == :document && header?
         | 
| 212 | 
            +
                  @blocks.each &block
         | 
| 213 | 
            +
                end
         | 
| 214 | 
            +
              end
         | 
| 215 | 
            +
             | 
| 216 | 
            +
              #--
         | 
| 217 | 
            +
              # TODO is there a way to make this lazy?
         | 
| 218 | 
            +
              def each_recursive &block
         | 
| 219 | 
            +
                block = lambda {|node| node } unless block_given?
         | 
| 220 | 
            +
                results = []
         | 
| 221 | 
            +
                self.each do |node|
         | 
| 222 | 
            +
                  results << block.call(node)
         | 
| 223 | 
            +
                  results.concat(node.each_recursive(&block)) if ::Enumerable === node
         | 
| 224 | 
            +
                end
         | 
| 225 | 
            +
                block_given? ? results : results.to_enum
         | 
| 226 | 
            +
              end
         | 
| 227 | 
            +
            =end
         | 
| 228 | 
            +
             | 
| 229 | 
            +
              # Public: Query for all descendant block nodes in the document tree that
         | 
| 230 | 
            +
              # match the specified Symbol filter_context and, optionally, the style and/or
         | 
| 231 | 
            +
              # role specified in the options Hash. If a block is provided, it's used as an
         | 
| 232 | 
            +
              # additional filter. If no filters are specified, all block nodes in the tree
         | 
| 233 | 
            +
              # are returned.
         | 
| 234 | 
            +
              #
         | 
| 235 | 
            +
              # Examples
         | 
| 236 | 
            +
              #
         | 
| 237 | 
            +
              #   doc.find_by context: :section 
         | 
| 238 | 
            +
              #   #=> Asciidoctor::Section@14459860 { level: 0, title: "Hello, AsciiDoc!", blocks: 0 }
         | 
| 239 | 
            +
              #   #=> Asciidoctor::Section@14505460 { level: 1, title: "First Section", blocks: 1 }
         | 
| 240 | 
            +
              #
         | 
| 241 | 
            +
              #   doc.find_by(context: :section) {|section| section.level == 1 }
         | 
| 242 | 
            +
              #   #=> Asciidoctor::Section@14505460 { level: 1, title: "First Section", blocks: 1 }
         | 
| 243 | 
            +
              #
         | 
| 244 | 
            +
              #   doc.find_by context: :listing, style: 'source'
         | 
| 245 | 
            +
              #   #=> Asciidoctor::Block@13136720 { context: :listing, content_model: :verbatim, style: "source", lines: 1 }
         | 
| 246 | 
            +
              #
         | 
| 247 | 
            +
              # Returns An Array of block nodes that match the given selector or nil if no matches are found
         | 
| 248 | 
            +
              #--
         | 
| 249 | 
            +
              # TODO support jQuery-style selector (e.g., image.thumb)
         | 
| 250 | 
            +
              def find_by selector = {}, &block
         | 
| 251 | 
            +
                result = []
         | 
| 252 | 
            +
             | 
| 253 | 
            +
                if ((any_context = !(context_selector = selector[:context])) || context_selector == @context) &&
         | 
| 254 | 
            +
                    (!(style_selector = selector[:style]) || style_selector == @style) &&
         | 
| 255 | 
            +
                    (!(role_selector = selector[:role]) || has_role?(role_selector)) &&
         | 
| 256 | 
            +
                    (!(id_selector = selector[:id]) || id_selector == @id)
         | 
| 257 | 
            +
                  if id_selector
         | 
| 258 | 
            +
                    return [(block_given? && yield(self) ? self : self)]
         | 
| 188 259 | 
             
                  else
         | 
| 189 | 
            -
                     | 
| 260 | 
            +
                    result << (block_given? && yield(self) ? self : self)
         | 
| 190 261 | 
             
                  end
         | 
| 191 | 
            -
                when :raw
         | 
| 192 | 
            -
                  default_subs = SUBS[:pass]
         | 
| 193 | 
            -
                else
         | 
| 194 | 
            -
                  return
         | 
| 195 262 | 
             
                end
         | 
| 196 263 |  | 
| 197 | 
            -
                 | 
| 198 | 
            -
             | 
| 199 | 
            -
             | 
| 200 | 
            -
                  @subs = default_subs.dup
         | 
| 264 | 
            +
                # process document header as a section if present
         | 
| 265 | 
            +
                if @context == :document && (any_context || context_selector == :section) && header?
         | 
| 266 | 
            +
                  result.concat(@header.find_by(selector, &block) || [])
         | 
| 201 267 | 
             
                end
         | 
| 202 268 |  | 
| 203 | 
            -
                #  | 
| 204 | 
            -
                 | 
| 205 | 
            -
                   | 
| 206 | 
            -
             | 
| 207 | 
            -
             | 
| 269 | 
            +
                # yuck, dlist is a special case
         | 
| 270 | 
            +
                unless context_selector == :document # optimization
         | 
| 271 | 
            +
                  if @context == :dlist
         | 
| 272 | 
            +
                    if any_context || context_selector != :section # optimization
         | 
| 273 | 
            +
                      @blocks.flatten.each do |li|
         | 
| 274 | 
            +
                        result.concat(li.find_by(selector, &block) || [])
         | 
| 275 | 
            +
                      end
         | 
| 276 | 
            +
                    end
         | 
| 277 | 
            +
                  elsif
         | 
| 278 | 
            +
                    @blocks.each do |b|
         | 
| 279 | 
            +
                      next if (context_selector == :section && b.context != :section) # optimization
         | 
| 280 | 
            +
                      result.concat(b.find_by(selector, &block) || [])
         | 
| 281 | 
            +
                    end
         | 
| 282 | 
            +
                  end
         | 
| 208 283 | 
             
                end
         | 
| 284 | 
            +
                result.empty? ? nil : result
         | 
| 209 285 | 
             
              end
         | 
| 286 | 
            +
              alias :query :find_by
         | 
| 210 287 |  | 
| 211 288 | 
             
              # Public: Remove a substitution from this block
         | 
| 212 289 | 
             
              #
         | 
| @@ -233,28 +310,23 @@ class AbstractBlock < AbstractNode | |
| 233 310 | 
             
              #               If not provided, the name of the context for this block
         | 
| 234 311 | 
             
              #               is used. (default: nil).
         | 
| 235 312 | 
             
              #
         | 
| 236 | 
            -
              #  | 
| 313 | 
            +
              # Returns nothing
         | 
| 237 314 | 
             
              def assign_caption(caption = nil, key = nil)
         | 
| 238 | 
            -
                unless title? ||  | 
| 239 | 
            -
                  return nil
         | 
| 240 | 
            -
                end
         | 
| 315 | 
            +
                return unless title? || !@caption
         | 
| 241 316 |  | 
| 242 | 
            -
                if caption | 
| 243 | 
            -
                   | 
| 244 | 
            -
             | 
| 317 | 
            +
                if caption
         | 
| 318 | 
            +
                  @caption = caption
         | 
| 319 | 
            +
                else
         | 
| 320 | 
            +
                  if (value = @document.attributes['caption'])
         | 
| 321 | 
            +
                    @caption = value
         | 
| 245 322 | 
             
                  elsif title?
         | 
| 246 323 | 
             
                    key ||= @context.to_s
         | 
| 247 324 | 
             
                    caption_key = "#{key}-caption"
         | 
| 248 | 
            -
                    if @document.attributes | 
| 249 | 
            -
                      caption_title = @document.attributes["#{key}-caption"]
         | 
| 325 | 
            +
                    if (caption_title = @document.attributes[caption_key])
         | 
| 250 326 | 
             
                      caption_num = @document.counter_increment("#{key}-number", self)
         | 
| 251 327 | 
             
                      @caption = "#{caption_title} #{caption_num}. "
         | 
| 252 328 | 
             
                    end
         | 
| 253 | 
            -
                  else
         | 
| 254 | 
            -
                    @caption = caption
         | 
| 255 329 | 
             
                  end
         | 
| 256 | 
            -
                else
         | 
| 257 | 
            -
                  @caption = caption
         | 
| 258 330 | 
             
                end
         | 
| 259 331 | 
             
                nil
         | 
| 260 332 | 
             
              end
         | 
| @@ -264,13 +336,27 @@ class AbstractBlock < AbstractNode | |
| 264 336 | 
             
              # Assign the next index of this section within the parent
         | 
| 265 337 | 
             
              # Block (in document order)
         | 
| 266 338 | 
             
              #
         | 
| 267 | 
            -
              #  | 
| 339 | 
            +
              # Returns nothing
         | 
| 268 340 | 
             
              def assign_index(section)
         | 
| 269 341 | 
             
                section.index = @next_section_index
         | 
| 270 342 | 
             
                @next_section_index += 1
         | 
| 271 | 
            -
             | 
| 272 | 
            -
             | 
| 273 | 
            -
                  @ | 
| 343 | 
            +
             | 
| 344 | 
            +
                if section.sectname == 'appendix'
         | 
| 345 | 
            +
                  appendix_number = @document.counter 'appendix-number', 'A'
         | 
| 346 | 
            +
                  section.number = appendix_number if section.numbered
         | 
| 347 | 
            +
                  if (caption = @document.attr 'appendix-caption', '') != ''
         | 
| 348 | 
            +
                    section.caption = %(#{caption} #{appendix_number}: )
         | 
| 349 | 
            +
                  else
         | 
| 350 | 
            +
                    section.caption = %(#{appendix_number}. )
         | 
| 351 | 
            +
                  end
         | 
| 352 | 
            +
                elsif section.numbered
         | 
| 353 | 
            +
                  # chapters in a book doctype should be sequential even when divided into parts
         | 
| 354 | 
            +
                  if (section.level == 1 || (section.level == 0 && section.special)) && @document.doctype == 'book'
         | 
| 355 | 
            +
                    section.number = @document.counter('chapter-number', 1)
         | 
| 356 | 
            +
                  else
         | 
| 357 | 
            +
                    section.number = @next_section_number
         | 
| 358 | 
            +
                    @next_section_number += 1
         | 
| 359 | 
            +
                  end
         | 
| 274 360 | 
             
                end
         | 
| 275 361 | 
             
              end
         | 
| 276 362 |  | 
| @@ -280,12 +366,12 @@ class AbstractBlock < AbstractNode | |
| 280 366 | 
             
              # and reassign the section 0-based index value to each Section
         | 
| 281 367 | 
             
              # as it appears in document order.
         | 
| 282 368 | 
             
              # 
         | 
| 283 | 
            -
              #  | 
| 369 | 
            +
              # Returns nothing
         | 
| 284 370 | 
             
              def reindex_sections
         | 
| 285 371 | 
             
                @next_section_index = 0
         | 
| 286 372 | 
             
                @next_section_number = 0
         | 
| 287 373 | 
             
                @blocks.each {|block|
         | 
| 288 | 
            -
                  if block. | 
| 374 | 
            +
                  if block.context == :section
         | 
| 289 375 | 
             
                    assign_index(block)
         | 
| 290 376 | 
             
                    block.reindex_sections
         | 
| 291 377 | 
             
                  end
         | 
| @@ -4,7 +4,7 @@ module Asciidoctor | |
| 4 4 | 
             
            # all content segments in an AsciiDoc document.
         | 
| 5 5 | 
             
            class AbstractNode
         | 
| 6 6 |  | 
| 7 | 
            -
              include  | 
| 7 | 
            +
              include Substitutors
         | 
| 8 8 |  | 
| 9 9 | 
             
              # Public: Get the element which is the parent of this node
         | 
| 10 10 | 
             
              attr_reader :parent
         | 
| @@ -15,24 +15,32 @@ class AbstractNode | |
| 15 15 | 
             
              # Public: Get the Symbol context for this node
         | 
| 16 16 | 
             
              attr_reader :context
         | 
| 17 17 |  | 
| 18 | 
            -
              # Public: Get the  | 
| 18 | 
            +
              # Public: Get the String name of this node
         | 
| 19 | 
            +
              attr_reader :node_name
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              # Public: Get/Set the id of this node
         | 
| 19 22 | 
             
              attr_accessor :id
         | 
| 20 23 |  | 
| 21 24 | 
             
              # Public: Get the Hash of attributes for this node
         | 
| 22 25 | 
             
              attr_reader :attributes
         | 
| 23 26 |  | 
| 24 | 
            -
              def initialize | 
| 27 | 
            +
              def initialize parent, context, opts = {}
         | 
| 25 28 | 
             
                # document is a special case, should refer to itself
         | 
| 26 29 | 
             
                if context == :document
         | 
| 27 30 | 
             
                  @parent = nil
         | 
| 28 31 | 
             
                  @document = parent
         | 
| 29 32 | 
             
                else
         | 
| 30 | 
            -
                  @parent = parent
         | 
| 31 | 
            -
             | 
| 33 | 
            +
                  if (@parent = parent)
         | 
| 34 | 
            +
                    @document = parent.document
         | 
| 35 | 
            +
                  else
         | 
| 36 | 
            +
                    @document = nil
         | 
| 37 | 
            +
                  end
         | 
| 32 38 | 
             
                end
         | 
| 33 39 | 
             
                @context = context
         | 
| 34 | 
            -
                @ | 
| 35 | 
            -
                 | 
| 40 | 
            +
                @node_name = context.to_s
         | 
| 41 | 
            +
                # QUESTION are we correct in duplicating the attributes (seems to be just as fast)
         | 
| 42 | 
            +
                @attributes = opts.key?(:attributes) ? (opts[:attributes] || {}).dup : {}
         | 
| 43 | 
            +
                @passthroughs = {}
         | 
| 36 44 | 
             
              end
         | 
| 37 45 |  | 
| 38 46 | 
             
              # Public: Associate this Block with a new parent Block
         | 
| @@ -46,6 +54,20 @@ class AbstractNode | |
| 46 54 | 
             
                nil
         | 
| 47 55 | 
             
              end
         | 
| 48 56 |  | 
| 57 | 
            +
              # Public: Returns whether this {AbstractNode} is an instance of {Inline}
         | 
| 58 | 
            +
              #
         | 
| 59 | 
            +
              # Returns [Boolean]
         | 
| 60 | 
            +
              def inline?
         | 
| 61 | 
            +
                raise ::NotImplementedError
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
              # Public: Returns whether this {AbstractNode} is an instance of {Block}
         | 
| 65 | 
            +
              #
         | 
| 66 | 
            +
              # Returns [Boolean]
         | 
| 67 | 
            +
              def block?
         | 
| 68 | 
            +
                raise ::NotImplementedError
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
             | 
| 49 71 | 
             
              # Public: Get the value of the specified attribute
         | 
| 50 72 | 
             
              #
         | 
| 51 73 | 
             
              # Get the value for the specified attribute. First look in the attributes on
         | 
| @@ -54,20 +76,20 @@ class AbstractNode | |
| 54 76 | 
             
              # Document node and return the value of the attribute if found. Otherwise,
         | 
| 55 77 | 
             
              # return the default value, which defaults to nil.
         | 
| 56 78 | 
             
              #
         | 
| 57 | 
            -
              # name | 
| 58 | 
            -
              #  | 
| 59 | 
            -
              # inherit | 
| 60 | 
            -
              # | 
| 79 | 
            +
              # name          - the String or Symbol name of the attribute to lookup
         | 
| 80 | 
            +
              # default_value - the Object value to return if the attribute is not found (default: nil)
         | 
| 81 | 
            +
              # inherit       - a Boolean indicating whether to check for the attribute on the
         | 
| 82 | 
            +
              #                 AsciiDoctor::Document if not found on this node (default: false)
         | 
| 61 83 | 
             
              #
         | 
| 62 84 | 
             
              # return the value of the attribute or the default value if the attribute
         | 
| 63 85 | 
             
              # is not found in the attributes of this node or the document node
         | 
| 64 | 
            -
              def attr(name,  | 
| 65 | 
            -
                name = name.to_s if name.is_a?(Symbol)
         | 
| 86 | 
            +
              def attr(name, default_value = nil, inherit = true)
         | 
| 87 | 
            +
                name = name.to_s if name.is_a?(::Symbol)
         | 
| 66 88 | 
             
                inherit = false if self == @document
         | 
| 67 89 | 
             
                if inherit
         | 
| 68 | 
            -
                  @attributes[name] || @document.attributes[name] ||  | 
| 90 | 
            +
                  @attributes[name] || @document.attributes[name] || default_value
         | 
| 69 91 | 
             
                else
         | 
| 70 | 
            -
                  @attributes[name] ||  | 
| 92 | 
            +
                  @attributes[name] || default_value
         | 
| 71 93 | 
             
                end
         | 
| 72 94 | 
             
              end
         | 
| 73 95 |  | 
| @@ -89,7 +111,7 @@ class AbstractNode | |
| 89 111 | 
             
              # comparison value is specified, whether the value of the attribute matches
         | 
| 90 112 | 
             
              # the comparison value
         | 
| 91 113 | 
             
              def attr?(name, expect = nil, inherit = true)
         | 
| 92 | 
            -
                name = name.to_s if name.is_a?(Symbol)
         | 
| 114 | 
            +
                name = name.to_s if name.is_a?(::Symbol)
         | 
| 93 115 | 
             
                inherit = false if self == @document
         | 
| 94 116 | 
             
                if expect.nil?
         | 
| 95 117 | 
             
                  @attributes.has_key?(name) || (inherit && @document.attributes.has_key?(name))
         | 
| @@ -100,20 +122,21 @@ class AbstractNode | |
| 100 122 | 
             
                end
         | 
| 101 123 | 
             
              end
         | 
| 102 124 |  | 
| 103 | 
            -
              # Public: Assign the value to the  | 
| 104 | 
            -
              # block's attributes hash.
         | 
| 125 | 
            +
              # Public: Assign the value to the attribute name for the current node.
         | 
| 105 126 | 
             
              #
         | 
| 106 | 
            -
              #  | 
| 107 | 
            -
              #  | 
| 127 | 
            +
              # name      - The String attribute name
         | 
| 128 | 
            +
              # value     - The Object value to assign to the attribute name
         | 
| 129 | 
            +
              # overwrite - A Boolean indicating whether to assign the attribute
         | 
| 130 | 
            +
              #             if currently present in the attributes Hash
         | 
| 108 131 | 
             
              #
         | 
| 109 | 
            -
              #  | 
| 110 | 
            -
              def set_attr | 
| 132 | 
            +
              # Returns a [Boolean] indicating whether the assignment was performed
         | 
| 133 | 
            +
              def set_attr name, value, overwrite = nil
         | 
| 111 134 | 
             
                if overwrite.nil?
         | 
| 112 | 
            -
                  @attributes[ | 
| 135 | 
            +
                  @attributes[name] = value
         | 
| 113 136 | 
             
                  true
         | 
| 114 137 | 
             
                else
         | 
| 115 | 
            -
                  if overwrite || @attributes. | 
| 116 | 
            -
                    @attributes[ | 
| 138 | 
            +
                  if overwrite || !(@attributes.key? name)
         | 
| 139 | 
            +
                    @attributes[name] = value
         | 
| 117 140 | 
             
                    true
         | 
| 118 141 | 
             
                  else
         | 
| 119 142 | 
             
                    false
         | 
| @@ -135,33 +158,13 @@ class AbstractNode | |
| 135 158 | 
             
              # enabled on the current node.
         | 
| 136 159 | 
             
              #
         | 
| 137 160 | 
             
              # Check if the option is enabled. This method simply checks to see if the
         | 
| 138 | 
            -
              #  | 
| 161 | 
            +
              # %name%-option attribute is defined on the current node.
         | 
| 139 162 | 
             
              #
         | 
| 140 163 | 
             
              # name    - the String or Symbol name of the option
         | 
| 141 164 | 
             
              #
         | 
| 142 165 | 
             
              # return a Boolean indicating whether the option has been specified
         | 
| 143 166 | 
             
              def option?(name)
         | 
| 144 | 
            -
                @attributes.has_key?  | 
| 145 | 
            -
              end
         | 
| 146 | 
            -
             | 
| 147 | 
            -
              # Public: Get the execution context of this object (via Kernel#binding).
         | 
| 148 | 
            -
              #
         | 
| 149 | 
            -
              # This method is used to set the 'self' reference as well as local variables
         | 
| 150 | 
            -
              # that map to this method's arguments during the evaluation of a backend
         | 
| 151 | 
            -
              # template.
         | 
| 152 | 
            -
              #
         | 
| 153 | 
            -
              # Each object in Ruby has a binding context that can be used to set the 'self'
         | 
| 154 | 
            -
              # reference in an evaluation context. Any arguments passed to this
         | 
| 155 | 
            -
              # method are also available in the execution environment.
         | 
| 156 | 
            -
              #
         | 
| 157 | 
            -
              # template -  The BaseTemplate instance in which this binding will be active.
         | 
| 158 | 
            -
              #             Bound to the local variable of the same name, template.
         | 
| 159 | 
            -
              #
         | 
| 160 | 
            -
              # returns the execution context for this object so it can be be transferred to
         | 
| 161 | 
            -
              # the backend template and binds the method arguments as local variables in
         | 
| 162 | 
            -
              # that same environment.
         | 
| 163 | 
            -
              def get_binding template
         | 
| 164 | 
            -
                binding
         | 
| 167 | 
            +
                @attributes.has_key? %(#{name}-option)
         | 
| 165 168 | 
             
              end
         | 
| 166 169 |  | 
| 167 170 | 
             
              # Public: Update the attributes of this node with the new values in
         | 
| @@ -172,16 +175,16 @@ class AbstractNode | |
| 172 175 | 
             
              #
         | 
| 173 176 | 
             
              # attributes - A Hash of attributes to assign to this node.
         | 
| 174 177 | 
             
              #
         | 
| 175 | 
            -
              #  | 
| 178 | 
            +
              # Returns nothing
         | 
| 176 179 | 
             
              def update_attributes(attributes)
         | 
| 177 180 | 
             
                @attributes.update(attributes)
         | 
| 178 181 | 
             
                nil
         | 
| 179 182 | 
             
              end
         | 
| 180 183 |  | 
| 181 | 
            -
              # Public: Get the Asciidoctor:: | 
| 182 | 
            -
              # Asciidoctor::Document | 
| 183 | 
            -
              def  | 
| 184 | 
            -
                @document. | 
| 184 | 
            +
              # Public: Get the Asciidoctor::Converter instance being used to convert the
         | 
| 185 | 
            +
              # current Asciidoctor::Document.
         | 
| 186 | 
            +
              def converter
         | 
| 187 | 
            +
                @document.converter
         | 
| 185 188 | 
             
              end
         | 
| 186 189 |  | 
| 187 190 | 
             
              # Public: A convenience method that checks if the role attribute is specified
         | 
| @@ -249,7 +252,7 @@ class AbstractNode | |
| 249 252 | 
             
                if attr? 'icon'
         | 
| 250 253 | 
             
                  image_uri(attr('icon'), nil)
         | 
| 251 254 | 
             
                else
         | 
| 252 | 
            -
                  image_uri( | 
| 255 | 
            +
                  image_uri(%(#{name}.#{@document.attr('icontype', 'png')}), 'iconsdir')
         | 
| 253 256 | 
             
                end
         | 
| 254 257 | 
             
              end
         | 
| 255 258 |  | 
| @@ -268,12 +271,10 @@ class AbstractNode | |
| 268 271 | 
             
              #
         | 
| 269 272 | 
             
              # Returns A String reference for the target media
         | 
| 270 273 | 
             
              def media_uri(target, asset_dir_key = 'imagesdir')
         | 
| 271 | 
            -
                if  | 
| 274 | 
            +
                if is_uri? target
         | 
| 272 275 | 
             
                  target
         | 
| 273 | 
            -
                elsif asset_dir_key && attr?(asset_dir_key)
         | 
| 274 | 
            -
                  normalize_web_path(target, @document.attr(asset_dir_key))
         | 
| 275 276 | 
             
                else
         | 
| 276 | 
            -
                  normalize_web_path( | 
| 277 | 
            +
                  normalize_web_path target, (asset_dir_key ? @document.attr(asset_dir_key) : nil)
         | 
| 277 278 | 
             
                end
         | 
| 278 279 | 
             
              end
         | 
| 279 280 |  | 
| @@ -297,14 +298,22 @@ class AbstractNode | |
| 297 298 | 
             
              #
         | 
| 298 299 | 
             
              # Returns A String reference or data URI for the target image
         | 
| 299 300 | 
             
              def image_uri(target_image, asset_dir_key = 'imagesdir')
         | 
| 300 | 
            -
                if  | 
| 301 | 
            +
                if (doc = @document).safe < SafeMode::SECURE && doc.attr?('data-uri')
         | 
| 302 | 
            +
                  if is_uri?(target_image) ||
         | 
| 303 | 
            +
                      (asset_dir_key && (images_base = doc.attr(asset_dir_key)) &&
         | 
| 304 | 
            +
                      is_uri?(images_base) && (target_image = normalize_web_path target_image, images_base))
         | 
| 305 | 
            +
                    if doc.attr? 'allow-uri-read'
         | 
| 306 | 
            +
                      generate_data_uri_from_uri target_image, doc.attr?('cache-uri')
         | 
| 307 | 
            +
                    else
         | 
| 308 | 
            +
                      target_image
         | 
| 309 | 
            +
                    end
         | 
| 310 | 
            +
                  else
         | 
| 311 | 
            +
                    generate_data_uri target_image, asset_dir_key
         | 
| 312 | 
            +
                  end
         | 
| 313 | 
            +
                elsif is_uri? target_image
         | 
| 301 314 | 
             
                  target_image
         | 
| 302 | 
            -
                elsif @document.safe < Asciidoctor::SafeMode::SECURE && @document.attr?('data-uri')
         | 
| 303 | 
            -
                  generate_data_uri(target_image, asset_dir_key)
         | 
| 304 | 
            -
                elsif asset_dir_key && attr?(asset_dir_key)
         | 
| 305 | 
            -
                  normalize_web_path(target_image, @document.attr(asset_dir_key))
         | 
| 306 315 | 
             
                else
         | 
| 307 | 
            -
                  normalize_web_path( | 
| 316 | 
            +
                  normalize_web_path target_image, (asset_dir_key ? doc.attr(asset_dir_key) : nil)
         | 
| 308 317 | 
             
                end
         | 
| 309 318 | 
             
              end
         | 
| 310 319 |  | 
| @@ -321,32 +330,69 @@ class AbstractNode | |
| 321 330 | 
             
              #
         | 
| 322 331 | 
             
              # Returns A String data URI containing the content of the target image
         | 
| 323 332 | 
             
              def generate_data_uri(target_image, asset_dir_key = nil)
         | 
| 324 | 
            -
                 | 
| 325 | 
            -
             | 
| 326 | 
            -
                ext = File.extname(target_image)[1..-1]
         | 
| 327 | 
            -
                mimetype = 'image/' + ext
         | 
| 328 | 
            -
                mimetype = "#{mimetype}+xml" if ext == 'svg'
         | 
| 333 | 
            +
                ext = ::File.extname(target_image)[1..-1]
         | 
| 334 | 
            +
                mimetype = (ext == 'svg' ? 'image/svg+xml' : %(image/#{ext}))
         | 
| 329 335 | 
             
                if asset_dir_key
         | 
| 330 | 
            -
                  #asset_dir_path = normalize_system_path(@document.attr(asset_dir_key), nil, nil, :target_name => asset_dir_key)
         | 
| 331 | 
            -
                  #image_path = normalize_system_path(target_image, asset_dir_path, nil, :target_name => 'image')
         | 
| 332 336 | 
             
                  image_path = normalize_system_path(target_image, @document.attr(asset_dir_key), nil, :target_name => 'image')
         | 
| 333 337 | 
             
                else
         | 
| 334 338 | 
             
                  image_path = normalize_system_path(target_image)
         | 
| 335 339 | 
             
                end
         | 
| 336 340 |  | 
| 337 | 
            -
                 | 
| 338 | 
            -
                  warn  | 
| 341 | 
            +
                unless ::File.readable? image_path
         | 
| 342 | 
            +
                  warn %(asciidoctor: WARNING: image to embed not found or not readable: #{image_path})
         | 
| 343 | 
            +
                  # must enclose string following return in " for Opal
         | 
| 339 344 | 
             
                  return "data:#{mimetype}:base64,"
         | 
| 345 | 
            +
                  # uncomment to return 1 pixel white dot instead
         | 
| 340 346 | 
             
                  #return 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
         | 
| 341 347 | 
             
                end
         | 
| 342 348 |  | 
| 343 349 | 
             
                bindata = nil
         | 
| 344 | 
            -
                if IO.respond_to? :binread
         | 
| 345 | 
            -
                  bindata = IO.binread(image_path)
         | 
| 350 | 
            +
                if ::IO.respond_to? :binread
         | 
| 351 | 
            +
                  bindata = ::IO.binread(image_path)
         | 
| 346 352 | 
             
                else
         | 
| 347 | 
            -
                  bindata = File.open(image_path, 'rb') {|file| file.read }
         | 
| 353 | 
            +
                  bindata = ::File.open(image_path, 'rb') {|file| file.read }
         | 
| 354 | 
            +
                end
         | 
| 355 | 
            +
                %(data:#{mimetype};base64,#{::Base64.encode64(bindata).delete EOL})
         | 
| 356 | 
            +
              end
         | 
| 357 | 
            +
             
         | 
| 358 | 
            +
              # Public: Read the image data from the specified URI and generate a data URI
         | 
| 359 | 
            +
              #
         | 
| 360 | 
            +
              # The image data is read from the URI and converted to Base64. A data URI is
         | 
| 361 | 
            +
              # constructed from the content_type header and Base64 data and returned,
         | 
| 362 | 
            +
              # which can then be used in an image tag.
         | 
| 363 | 
            +
              #
         | 
| 364 | 
            +
              # image_uri  - The URI from which to read the image data. Can be http://, https:// or ftp://
         | 
| 365 | 
            +
              # cache_uri  - A Boolean to control caching. When true, the open-uri-cached library
         | 
| 366 | 
            +
              #              is used to cache the image for subsequent reads. (default: false)
         | 
| 367 | 
            +
              #
         | 
| 368 | 
            +
              # Returns A data URI string built from Base64 encoded data read from the URI
         | 
| 369 | 
            +
              # and the mime type specified in the Content Type header.
         | 
| 370 | 
            +
              def generate_data_uri_from_uri image_uri, cache_uri = false
         | 
| 371 | 
            +
                Helpers.require_library 'base64'
         | 
| 372 | 
            +
                if cache_uri
         | 
| 373 | 
            +
                  # caching requires the open-uri-cached gem to be installed
         | 
| 374 | 
            +
                  # processing will be automatically aborted if these libraries can't be opened
         | 
| 375 | 
            +
                  Helpers.require_library 'open-uri/cached', 'open-uri-cached'
         | 
| 376 | 
            +
                elsif !::RUBY_ENGINE_OPAL
         | 
| 377 | 
            +
                  # autoload open-uri
         | 
| 378 | 
            +
                  ::OpenURI
         | 
| 379 | 
            +
                end
         | 
| 380 | 
            +
             | 
| 381 | 
            +
                begin
         | 
| 382 | 
            +
                  mimetype = nil
         | 
| 383 | 
            +
                  bindata = open(image_uri, 'rb') {|file|
         | 
| 384 | 
            +
                    mimetype = file.content_type 
         | 
| 385 | 
            +
                    file.read
         | 
| 386 | 
            +
                  }
         | 
| 387 | 
            +
                  %(data:#{mimetype};base64,#{Base64.encode64(bindata).delete EOL})
         | 
| 388 | 
            +
                rescue
         | 
| 389 | 
            +
                  warn %(asciidoctor: WARNING: could not retrieve image data from URI: #{image_uri})
         | 
| 390 | 
            +
                  image_uri
         | 
| 391 | 
            +
                  # uncomment to return empty data (however, mimetype needs to be resolved)
         | 
| 392 | 
            +
                  #%(data:#{mimetype}:base64,)
         | 
| 393 | 
            +
                  # uncomment to return 1 pixel white dot instead
         | 
| 394 | 
            +
                  #'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
         | 
| 348 395 | 
             
                end
         | 
| 349 | 
            -
                "data:#{mimetype};base64,#{Base64.encode64(bindata).delete("\n")}"
         | 
| 350 396 | 
             
              end
         | 
| 351 397 |  | 
| 352 398 | 
             
              # Public: Read the contents of the file at the specified path.
         | 
| @@ -357,11 +403,12 @@ class AbstractNode | |
| 357 403 | 
             
              # warn_on_failure - a Boolean that controls whether a warning is issued if
         | 
| 358 404 | 
             
              #                   the file cannot be read
         | 
| 359 405 | 
             
              #
         | 
| 360 | 
            -
              #  | 
| 406 | 
            +
              # Returns the [String] content of the file at the specified path, or nil
         | 
| 361 407 | 
             
              # if the file does not exist.
         | 
| 362 408 | 
             
              def read_asset(path, warn_on_failure = false)
         | 
| 363 | 
            -
                if File.readable? path
         | 
| 364 | 
            -
                   | 
| 409 | 
            +
                if ::File.readable? path
         | 
| 410 | 
            +
                  # QUESTION should we use strip or rstrip instead of chomp here?
         | 
| 411 | 
            +
                  ::File.read(path).chomp
         | 
| 365 412 | 
             
                else
         | 
| 366 413 | 
             
                  warn "asciidoctor: WARNING: file does not exist or cannot be read: #{path}" if warn_on_failure
         | 
| 367 414 | 
             
                  nil
         | 
| @@ -370,20 +417,20 @@ class AbstractNode | |
| 370 417 |  | 
| 371 418 | 
             
              # Public: Normalize the web page using the PathResolver.
         | 
| 372 419 | 
             
              #
         | 
| 373 | 
            -
              # See PathResolver | 
| 420 | 
            +
              # See {PathResolver#web_path} for details.
         | 
| 374 421 | 
             
              #
         | 
| 375 422 | 
             
              # target - the String target path
         | 
| 376 423 | 
             
              # start  - the String start (i.e, parent) path (optional, default: nil)
         | 
| 377 424 | 
             
              #
         | 
| 378 | 
            -
              #  | 
| 425 | 
            +
              # Returns the resolved [String] path 
         | 
| 379 426 | 
             
              def normalize_web_path(target, start = nil)
         | 
| 380 | 
            -
                PathResolver.new.web_path(target, start)
         | 
| 427 | 
            +
                (@path_resolver ||= PathResolver.new).web_path(target, start)
         | 
| 381 428 | 
             
              end
         | 
| 382 429 |  | 
| 383 430 | 
             
              # Public: Resolve and normalize a secure path from the target and start paths
         | 
| 384 431 | 
             
              # using the PathResolver.
         | 
| 385 432 | 
             
              #
         | 
| 386 | 
            -
              # See PathResolver | 
| 433 | 
            +
              # See {PathResolver#system_path} for details.
         | 
| 387 434 | 
             
              #
         | 
| 388 435 | 
             
              # The most important functionality in this method is to prevent resolving a
         | 
| 389 436 | 
             
              # path outside of the jail (which defaults to the directory of the source
         | 
| @@ -402,17 +449,21 @@ class AbstractNode | |
| 402 449 | 
             
              # raises a SecurityError if a jail is specified and the resolved path is
         | 
| 403 450 | 
             
              # outside the jail.
         | 
| 404 451 | 
             
              #
         | 
| 405 | 
            -
              #  | 
| 452 | 
            +
              # Returns the [String] path resolved from the start and target paths, with any
         | 
| 406 453 | 
             
              # parent references resolved and self references removed. If a jail is provided,
         | 
| 407 454 | 
             
              # this path will be guaranteed to be contained within the jail.
         | 
| 408 | 
            -
              def normalize_system_path | 
| 409 | 
            -
                if  | 
| 410 | 
            -
                  start | 
| 411 | 
            -
             | 
| 412 | 
            -
             | 
| 413 | 
            -
             | 
| 455 | 
            +
              def normalize_system_path target, start = nil, jail = nil, opts = {}
         | 
| 456 | 
            +
                if (doc = @document).safe < SafeMode::SAFE
         | 
| 457 | 
            +
                  if start
         | 
| 458 | 
            +
                    start = ::File.join doc.base_dir, start unless (@path_resolver ||= PathResolver.new).is_root? start
         | 
| 459 | 
            +
                  else
         | 
| 460 | 
            +
                    start = doc.base_dir
         | 
| 461 | 
            +
                  end
         | 
| 462 | 
            +
                else
         | 
| 463 | 
            +
                  start = doc.base_dir unless start
         | 
| 464 | 
            +
                  jail = doc.base_dir unless jail
         | 
| 414 465 | 
             
                end
         | 
| 415 | 
            -
                PathResolver.new.system_path | 
| 466 | 
            +
                (@path_resolver ||= PathResolver.new).system_path target, start, jail, opts
         | 
| 416 467 | 
             
              end
         | 
| 417 468 |  | 
| 418 469 | 
             
              # Public: Normalize the asset file or directory to a concrete and rinsed path
         | 
| @@ -426,7 +477,13 @@ class AbstractNode | |
| 426 477 |  | 
| 427 478 | 
             
              # Public: Calculate the relative path to this absolute filename from the Document#base_dir
         | 
| 428 479 | 
             
              def relative_path(filename)
         | 
| 429 | 
            -
                PathResolver.new.relative_path filename, @document.base_dir
         | 
| 480 | 
            +
                (@path_resolver ||= PathResolver.new).relative_path filename, @document.base_dir
         | 
| 481 | 
            +
              end
         | 
| 482 | 
            +
             | 
| 483 | 
            +
              # Public: Check whether the specified String is a URI by
         | 
| 484 | 
            +
              # matching it against the Asciidoctor::UriSniffRx regex.
         | 
| 485 | 
            +
              def is_uri? str
         | 
| 486 | 
            +
                str.include?(':') && UriSniffRx =~ str
         | 
| 430 487 | 
             
              end
         | 
| 431 488 |  | 
| 432 489 | 
             
              # Public: Retrieve the list marker keyword for the specified list type.
         | 
| @@ -435,7 +492,7 @@ class AbstractNode | |
| 435 492 | 
             
              #
         | 
| 436 493 | 
             
              # list_type - the type of list; default to the @style if not specified
         | 
| 437 494 | 
             
              #
         | 
| 438 | 
            -
              #  | 
| 495 | 
            +
              # Returns the single-character [String] keyword that represents the marker for the specified list type
         | 
| 439 496 | 
             
              def list_marker_keyword(list_type = nil)
         | 
| 440 497 | 
             
                ORDERED_LIST_KEYWORDS[list_type || @style]
         | 
| 441 498 | 
             
              end
         |