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
    
        data/lib/asciidoctor/renderer.rb
    DELETED
    
    | @@ -1,259 +0,0 @@ | |
| 1 | 
            -
            module Asciidoctor
         | 
| 2 | 
            -
            # Public: Methods for rendering Asciidoc Documents, Sections, and Blocks
         | 
| 3 | 
            -
            # using eRuby templates.
         | 
| 4 | 
            -
            class Renderer
         | 
| 5 | 
            -
              RE_ASCIIDOCTOR_NAMESPACE = /^Asciidoctor::/
         | 
| 6 | 
            -
              RE_TEMPLATE_CLASS_SUFFIX = /Template$/
         | 
| 7 | 
            -
              RE_CAMELCASE_BOUNDARY_1 = /([[:upper:]]+)([[:upper:]][[:alpha:]])/
         | 
| 8 | 
            -
              RE_CAMELCASE_BOUNDARY_2 = /([[:lower:]])([[:upper:]])/
         | 
| 9 | 
            -
             | 
| 10 | 
            -
              attr_reader :compact
         | 
| 11 | 
            -
              attr_reader :cache
         | 
| 12 | 
            -
             | 
| 13 | 
            -
              @@global_cache = nil
         | 
| 14 | 
            -
             | 
| 15 | 
            -
              # Public: Initialize an Asciidoctor::Renderer object.
         | 
| 16 | 
            -
              #
         | 
| 17 | 
            -
              def initialize(options={})
         | 
| 18 | 
            -
                @debug = !!options[:debug]
         | 
| 19 | 
            -
             | 
| 20 | 
            -
                @views = {}
         | 
| 21 | 
            -
                @compact = options[:compact]
         | 
| 22 | 
            -
                @cache = nil
         | 
| 23 | 
            -
             | 
| 24 | 
            -
                backend = options[:backend]
         | 
| 25 | 
            -
                case backend
         | 
| 26 | 
            -
                when 'html5', 'docbook45', 'docbook5'
         | 
| 27 | 
            -
                  eruby = load_eruby options[:eruby]
         | 
| 28 | 
            -
                  #Helpers.require_library 'asciidoctor/backends/' + backend
         | 
| 29 | 
            -
                  require 'asciidoctor/backends/' + backend
         | 
| 30 | 
            -
                  # Load up all the template classes that we know how to render for this backend
         | 
| 31 | 
            -
                  BaseTemplate.template_classes.each do |tc|
         | 
| 32 | 
            -
                    if tc.to_s.downcase.include?('::' + backend + '::') # optimization
         | 
| 33 | 
            -
                      view_name, view_backend = self.class.extract_view_mapping(tc)
         | 
| 34 | 
            -
                      if view_backend == backend
         | 
| 35 | 
            -
                        @views[view_name] = tc.new(view_name, backend, eruby)
         | 
| 36 | 
            -
                      end
         | 
| 37 | 
            -
                    end
         | 
| 38 | 
            -
                  end
         | 
| 39 | 
            -
                else
         | 
| 40 | 
            -
                  Debug.debug { "No built-in templates for backend: #{backend}" }
         | 
| 41 | 
            -
                end
         | 
| 42 | 
            -
             | 
| 43 | 
            -
                # If user passed in a template dir, let them override our base templates
         | 
| 44 | 
            -
                if (template_dirs = options.delete(:template_dirs))
         | 
| 45 | 
            -
                  Helpers.require_library 'tilt', true
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                  if (template_cache = options[:template_cache]) === true
         | 
| 48 | 
            -
                    # FIXME probably want to use our own cache object for more control
         | 
| 49 | 
            -
                    @cache = (@@global_cache ||= TemplateCache.new)
         | 
| 50 | 
            -
                  elsif template_cache
         | 
| 51 | 
            -
                    @cache = template_cache
         | 
| 52 | 
            -
                  end
         | 
| 53 | 
            -
             | 
| 54 | 
            -
                  view_opts = {
         | 
| 55 | 
            -
                    :erb =>  { :trim => '<>' },
         | 
| 56 | 
            -
                    :haml => { :format => :xhtml, :attr_wrapper => '"', :ugly => true, :escape_attrs => false },
         | 
| 57 | 
            -
                    :slim => { :disable_escape => true, :sort_attrs => false, :pretty => false }
         | 
| 58 | 
            -
                  }
         | 
| 59 | 
            -
             | 
| 60 | 
            -
                  # workaround until we have a proper way to configure
         | 
| 61 | 
            -
                  if {'html5' => true, 'dzslides' => true, 'deckjs' => true, 'revealjs' => true}.has_key? backend
         | 
| 62 | 
            -
                    view_opts[:haml][:format] = view_opts[:slim][:format] = :html5
         | 
| 63 | 
            -
                  end
         | 
| 64 | 
            -
             | 
| 65 | 
            -
                  slim_loaded = false
         | 
| 66 | 
            -
                  path_resolver = PathResolver.new
         | 
| 67 | 
            -
                  engine = options[:template_engine]
         | 
| 68 | 
            -
             | 
| 69 | 
            -
                  template_dirs.each do |template_dir|
         | 
| 70 | 
            -
                    # TODO need to think about safe mode restrictions here
         | 
| 71 | 
            -
                    template_dir = path_resolver.system_path template_dir, nil
         | 
| 72 | 
            -
                    template_glob = '*'
         | 
| 73 | 
            -
                    if engine
         | 
| 74 | 
            -
                      template_glob = "*.#{engine}"
         | 
| 75 | 
            -
                      # example: templates/haml
         | 
| 76 | 
            -
                      if File.directory? File.join(template_dir, engine)
         | 
| 77 | 
            -
                        template_dir = File.join template_dir, engine
         | 
| 78 | 
            -
                      end
         | 
| 79 | 
            -
                    end
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                    # example: templates/html5 or templates/haml/html5
         | 
| 82 | 
            -
                    if File.directory? File.join(template_dir, backend)
         | 
| 83 | 
            -
                      template_dir = File.join template_dir, backend
         | 
| 84 | 
            -
                    end
         | 
| 85 | 
            -
             | 
| 86 | 
            -
                    # skip scanning folder if we've already done it for same backend/engine
         | 
| 87 | 
            -
                    if @cache && @cache.cached?(:scan, template_dir, template_glob)
         | 
| 88 | 
            -
                      @views.update(@cache.fetch :scan, template_dir, template_glob)
         | 
| 89 | 
            -
                      next
         | 
| 90 | 
            -
                    end
         | 
| 91 | 
            -
             | 
| 92 | 
            -
                    helpers = nil
         | 
| 93 | 
            -
                    scan_result = {}
         | 
| 94 | 
            -
                    # Grab the files in the top level of the directory (we're not traversing)
         | 
| 95 | 
            -
                    Dir.glob(File.join(template_dir, template_glob)).
         | 
| 96 | 
            -
                        select{|f| File.file? f }.each do |template|
         | 
| 97 | 
            -
                      basename = File.basename(template)
         | 
| 98 | 
            -
                      if basename == 'helpers.rb'
         | 
| 99 | 
            -
                        helpers = template
         | 
| 100 | 
            -
                        next
         | 
| 101 | 
            -
                      end
         | 
| 102 | 
            -
                      name_parts = basename.split('.')
         | 
| 103 | 
            -
                      next if name_parts.size < 2
         | 
| 104 | 
            -
                      view_name = name_parts.first 
         | 
| 105 | 
            -
                      ext_name = name_parts.last
         | 
| 106 | 
            -
                      if ext_name == 'slim' && !slim_loaded
         | 
| 107 | 
            -
                        # slim doesn't get loaded by Tilt
         | 
| 108 | 
            -
                        Helpers.require_library 'slim', true
         | 
| 109 | 
            -
                      end
         | 
| 110 | 
            -
                      next unless Tilt.registered? ext_name
         | 
| 111 | 
            -
                      opts = view_opts[ext_name.to_sym]
         | 
| 112 | 
            -
                      if @cache
         | 
| 113 | 
            -
                        @views[view_name] = scan_result[view_name] = @cache.fetch(:view, template) {
         | 
| 114 | 
            -
                          Tilt.new(template, nil, opts)
         | 
| 115 | 
            -
                        }
         | 
| 116 | 
            -
                      else
         | 
| 117 | 
            -
                        @views[view_name] = Tilt.new template, nil, opts
         | 
| 118 | 
            -
                      end
         | 
| 119 | 
            -
                    end
         | 
| 120 | 
            -
             | 
| 121 | 
            -
                    require helpers unless helpers.nil?
         | 
| 122 | 
            -
                    @cache.store(scan_result, :scan, template_dir, template_glob) if @cache
         | 
| 123 | 
            -
                  end
         | 
| 124 | 
            -
                end
         | 
| 125 | 
            -
              end
         | 
| 126 | 
            -
             | 
| 127 | 
            -
              # Public: Render an Asciidoc object with a specified view template.
         | 
| 128 | 
            -
              #
         | 
| 129 | 
            -
              # view   - the String view template name.
         | 
| 130 | 
            -
              # object - the Object to be used as an evaluation scope.
         | 
| 131 | 
            -
              # locals - the optional Hash of locals to be passed to Tilt (default {}) (also ignored, really)
         | 
| 132 | 
            -
              def render(view, object, locals = {})
         | 
| 133 | 
            -
                if !@views.has_key? view
         | 
| 134 | 
            -
                  raise "Couldn't find a view in @views for #{view}"
         | 
| 135 | 
            -
                end
         | 
| 136 | 
            -
                
         | 
| 137 | 
            -
                @views[view].render(object, locals)
         | 
| 138 | 
            -
              end
         | 
| 139 | 
            -
             | 
| 140 | 
            -
              def views
         | 
| 141 | 
            -
                readonly_views = @views.dup
         | 
| 142 | 
            -
                readonly_views.freeze
         | 
| 143 | 
            -
                readonly_views
         | 
| 144 | 
            -
              end
         | 
| 145 | 
            -
             | 
| 146 | 
            -
              def register_view(view_name, tilt_template)
         | 
| 147 | 
            -
                # TODO need to figure out how to cache this
         | 
| 148 | 
            -
                @views[view_name] = tilt_template
         | 
| 149 | 
            -
              end
         | 
| 150 | 
            -
             | 
| 151 | 
            -
              # Internal: Load the eRuby implementation
         | 
| 152 | 
            -
              #
         | 
| 153 | 
            -
              # name - the String name of the eRuby implementation (default: 'erb')
         | 
| 154 | 
            -
              #
         | 
| 155 | 
            -
              # returns the eRuby implementation class
         | 
| 156 | 
            -
              def load_eruby(name)
         | 
| 157 | 
            -
                if name.nil? || !['erb', 'erubis'].include?(name)
         | 
| 158 | 
            -
                  name = 'erb'
         | 
| 159 | 
            -
                end
         | 
| 160 | 
            -
             | 
| 161 | 
            -
                if name == 'erb'
         | 
| 162 | 
            -
                  Helpers.require_library 'erb'
         | 
| 163 | 
            -
                  ::ERB
         | 
| 164 | 
            -
                elsif name == 'erubis'
         | 
| 165 | 
            -
                  Helpers.require_library 'erubis', true
         | 
| 166 | 
            -
                  ::Erubis::FastEruby
         | 
| 167 | 
            -
                end
         | 
| 168 | 
            -
              end
         | 
| 169 | 
            -
             | 
| 170 | 
            -
              # TODO better name for this method (and/or field)
         | 
| 171 | 
            -
              def self.global_cache
         | 
| 172 | 
            -
                @@global_cache
         | 
| 173 | 
            -
              end
         | 
| 174 | 
            -
             | 
| 175 | 
            -
              # TODO better name for this method (and/or field)
         | 
| 176 | 
            -
              def self.reset_global_cache
         | 
| 177 | 
            -
                @@global_cache.clear if @@global_cache
         | 
| 178 | 
            -
              end
         | 
| 179 | 
            -
             | 
| 180 | 
            -
              # Internal: Extracts the view name and backend from a qualified Ruby class
         | 
| 181 | 
            -
              #
         | 
| 182 | 
            -
              # The purpose of this method is to determine the view name and backend to
         | 
| 183 | 
            -
              # which a built-in template class maps. We can make certain assumption since
         | 
| 184 | 
            -
              # we have control over these class names. The Asciidoctor:: prefix and
         | 
| 185 | 
            -
              # Template suffix are stripped as the first step in the conversion.
         | 
| 186 | 
            -
              #
         | 
| 187 | 
            -
              # qualified_class - The Class or String qualified class name from which to extract the view name and backend
         | 
| 188 | 
            -
              #
         | 
| 189 | 
            -
              # Examples
         | 
| 190 | 
            -
              #
         | 
| 191 | 
            -
              #   Renderer.extract_view_mapping(Asciidoctor::HTML5::DocumentTemplate)
         | 
| 192 | 
            -
              #   # => ['document', 'html5']
         | 
| 193 | 
            -
              #
         | 
| 194 | 
            -
              #   Renderer.extract_view_mapping(Asciidoctor::DocBook45::BlockSidebarTemplate)
         | 
| 195 | 
            -
              #   # => ['block_sidebar', 'docbook45']
         | 
| 196 | 
            -
              #
         | 
| 197 | 
            -
              # Returns A two-element String Array mapped as [view_name, backend], where backend may be nil
         | 
| 198 | 
            -
              def self.extract_view_mapping(qualified_class)
         | 
| 199 | 
            -
                view_name, backend = qualified_class.to_s.
         | 
| 200 | 
            -
                    sub(RE_ASCIIDOCTOR_NAMESPACE, '').
         | 
| 201 | 
            -
                    sub(RE_TEMPLATE_CLASS_SUFFIX, '').
         | 
| 202 | 
            -
                    split('::').reverse
         | 
| 203 | 
            -
                view_name = camelcase_to_underscore(view_name)
         | 
| 204 | 
            -
                backend = backend.downcase unless backend.nil?
         | 
| 205 | 
            -
                [view_name, backend]
         | 
| 206 | 
            -
              end
         | 
| 207 | 
            -
             | 
| 208 | 
            -
              # Internal: Convert a CamelCase word to an underscore-delimited word
         | 
| 209 | 
            -
              #
         | 
| 210 | 
            -
              # Examples
         | 
| 211 | 
            -
              #
         | 
| 212 | 
            -
              #   Renderer.camelcase_to_underscore('BlockSidebar')
         | 
| 213 | 
            -
              #   # => 'block_sidebar'
         | 
| 214 | 
            -
              #
         | 
| 215 | 
            -
              #   Renderer.camelcase_to_underscore('BlockUlist')
         | 
| 216 | 
            -
              #   # => 'block_ulist'
         | 
| 217 | 
            -
              #
         | 
| 218 | 
            -
              # Returns the String converted from CamelCase to underscore-delimited
         | 
| 219 | 
            -
              def self.camelcase_to_underscore(str)
         | 
| 220 | 
            -
                str.gsub(RE_CAMELCASE_BOUNDARY_1, '\1_\2').
         | 
| 221 | 
            -
                    gsub(RE_CAMELCASE_BOUNDARY_2, '\1_\2').downcase
         | 
| 222 | 
            -
              end
         | 
| 223 | 
            -
             | 
| 224 | 
            -
            end
         | 
| 225 | 
            -
             | 
| 226 | 
            -
            class TemplateCache
         | 
| 227 | 
            -
              attr_reader :cache
         | 
| 228 | 
            -
             | 
| 229 | 
            -
              def initialize
         | 
| 230 | 
            -
                @cache = {}
         | 
| 231 | 
            -
              end
         | 
| 232 | 
            -
             | 
| 233 | 
            -
              # check if a key is available in the cache
         | 
| 234 | 
            -
              def cached? *key
         | 
| 235 | 
            -
                @cache.has_key? key
         | 
| 236 | 
            -
              end
         | 
| 237 | 
            -
             | 
| 238 | 
            -
              # retrieves an item from the cache stored in the cache key
         | 
| 239 | 
            -
              # if a block is given, the block is called and the return
         | 
| 240 | 
            -
              # value stored in the cache under the specified key
         | 
| 241 | 
            -
              def fetch(*key)
         | 
| 242 | 
            -
                if block_given?
         | 
| 243 | 
            -
                  @cache[key] ||= yield
         | 
| 244 | 
            -
                else
         | 
| 245 | 
            -
                  @cache[key]
         | 
| 246 | 
            -
                end
         | 
| 247 | 
            -
              end
         | 
| 248 | 
            -
             | 
| 249 | 
            -
              # stores an item in the cache under the specified key
         | 
| 250 | 
            -
              def store(value, *key)
         | 
| 251 | 
            -
                @cache[key] = value
         | 
| 252 | 
            -
              end
         | 
| 253 | 
            -
             | 
| 254 | 
            -
              # Clears the cache
         | 
| 255 | 
            -
              def clear
         | 
| 256 | 
            -
                @cache = {}
         | 
| 257 | 
            -
              end
         | 
| 258 | 
            -
            end
         | 
| 259 | 
            -
            end
         | 
| @@ -1,1083 +0,0 @@ | |
| 1 | 
            -
            module Asciidoctor
         | 
| 2 | 
            -
            # Public: Methods to perform substitutions on lines of AsciiDoc text. This module
         | 
| 3 | 
            -
            # is intented to be mixed-in to Section and Block to provide operations for performing
         | 
| 4 | 
            -
            # the necessary substitutions.
         | 
| 5 | 
            -
            module Substituters
         | 
| 6 | 
            -
             | 
| 7 | 
            -
              SUBS = {
         | 
| 8 | 
            -
                :basic    => [:specialcharacters],
         | 
| 9 | 
            -
                :normal   => [:specialcharacters, :quotes, :attributes, :replacements, :macros, :post_replacements],
         | 
| 10 | 
            -
                :verbatim => [:specialcharacters, :callouts],
         | 
| 11 | 
            -
                :title    => [:specialcharacters, :quotes, :replacements, :macros, :attributes, :post_replacements],
         | 
| 12 | 
            -
                :header   => [:specialcharacters, :attributes],
         | 
| 13 | 
            -
                :pass     => [:attributes, :macros]
         | 
| 14 | 
            -
              }
         | 
| 15 | 
            -
             | 
| 16 | 
            -
              COMPOSITE_SUBS = {
         | 
| 17 | 
            -
                :none => [],
         | 
| 18 | 
            -
                :normal => SUBS[:normal],
         | 
| 19 | 
            -
                :verbatim => SUBS[:verbatim]
         | 
| 20 | 
            -
              }
         | 
| 21 | 
            -
             | 
| 22 | 
            -
              SUB_OPTIONS = {
         | 
| 23 | 
            -
                :block  => COMPOSITE_SUBS.keys + SUBS[:normal] + [:callouts],
         | 
| 24 | 
            -
                :inline => COMPOSITE_SUBS.keys + SUBS[:normal]
         | 
| 25 | 
            -
              }
         | 
| 26 | 
            -
             | 
| 27 | 
            -
              # Internal: A String Array of passthough (unprocessed) text captured from this block
         | 
| 28 | 
            -
              attr_reader :passthroughs
         | 
| 29 | 
            -
             | 
| 30 | 
            -
              # Public: Apply the specified substitutions to the lines of text
         | 
| 31 | 
            -
              #
         | 
| 32 | 
            -
              # source  - The String or String Array of text to process
         | 
| 33 | 
            -
              # subs    - The substitutions to perform. Can be a Symbol or a Symbol Array (default: :normal)
         | 
| 34 | 
            -
              # expand -  A Boolean to control whether sub aliases are expanded (default: true)
         | 
| 35 | 
            -
              #
         | 
| 36 | 
            -
              # returns Either a String or String Array, whichever matches the type of the first argument
         | 
| 37 | 
            -
              def apply_subs source, subs = :normal, expand = false
         | 
| 38 | 
            -
                if subs == :normal
         | 
| 39 | 
            -
                  subs = SUBS[:normal]
         | 
| 40 | 
            -
                elsif subs.nil?
         | 
| 41 | 
            -
                  return source
         | 
| 42 | 
            -
                elsif expand
         | 
| 43 | 
            -
                  if subs.is_a? Symbol
         | 
| 44 | 
            -
                    subs = COMPOSITE_SUBS[subs] || [subs]
         | 
| 45 | 
            -
                  else
         | 
| 46 | 
            -
                    effective_subs = []
         | 
| 47 | 
            -
                    subs.each do |key|
         | 
| 48 | 
            -
                      if COMPOSITE_SUBS.has_key? key
         | 
| 49 | 
            -
                        effective_subs.push(*COMPOSITE_SUBS[key])
         | 
| 50 | 
            -
                      else
         | 
| 51 | 
            -
                        effective_subs << key
         | 
| 52 | 
            -
                      end
         | 
| 53 | 
            -
                    end
         | 
| 54 | 
            -
             | 
| 55 | 
            -
                    subs = effective_subs
         | 
| 56 | 
            -
                  end
         | 
| 57 | 
            -
                end
         | 
| 58 | 
            -
             | 
| 59 | 
            -
                return source if subs.empty?
         | 
| 60 | 
            -
             | 
| 61 | 
            -
                multiline = source.is_a?(Array)
         | 
| 62 | 
            -
                text = multiline ? source.join : source
         | 
| 63 | 
            -
             | 
| 64 | 
            -
                if (has_passthroughs = subs.include?(:macros))
         | 
| 65 | 
            -
                  text = extract_passthroughs(text)
         | 
| 66 | 
            -
                end
         | 
| 67 | 
            -
             | 
| 68 | 
            -
                subs.each do |type|
         | 
| 69 | 
            -
                  case type
         | 
| 70 | 
            -
                  when :specialcharacters
         | 
| 71 | 
            -
                    text = sub_specialcharacters(text)
         | 
| 72 | 
            -
                  when :quotes
         | 
| 73 | 
            -
                    text = sub_quotes(text)
         | 
| 74 | 
            -
                  when :attributes
         | 
| 75 | 
            -
                    text = sub_attributes(text.lines.entries).join
         | 
| 76 | 
            -
                  when :replacements
         | 
| 77 | 
            -
                    text = sub_replacements(text)
         | 
| 78 | 
            -
                  when :macros
         | 
| 79 | 
            -
                    text = sub_macros(text)
         | 
| 80 | 
            -
                  when :highlight
         | 
| 81 | 
            -
                    text = highlight_source(text, subs.include?(:callouts))
         | 
| 82 | 
            -
                  when :callouts
         | 
| 83 | 
            -
                    text = sub_callouts(text) unless subs.include?(:highlight)
         | 
| 84 | 
            -
                  when :post_replacements
         | 
| 85 | 
            -
                    text = sub_post_replacements(text)
         | 
| 86 | 
            -
                  else
         | 
| 87 | 
            -
                    warn "asciidoctor: WARNING: unknown substitution type #{type}"
         | 
| 88 | 
            -
                  end
         | 
| 89 | 
            -
                end
         | 
| 90 | 
            -
                text = restore_passthroughs(text) if has_passthroughs
         | 
| 91 | 
            -
             | 
| 92 | 
            -
                multiline ? text.lines.entries : text
         | 
| 93 | 
            -
              end
         | 
| 94 | 
            -
             | 
| 95 | 
            -
              # Public: Apply normal substitutions.
         | 
| 96 | 
            -
              #
         | 
| 97 | 
            -
              # lines  - The lines of text to process. Can be a String or a String Array
         | 
| 98 | 
            -
              #
         | 
| 99 | 
            -
              # returns - A String with normal substitutions performed
         | 
| 100 | 
            -
              def apply_normal_subs(lines)
         | 
| 101 | 
            -
                apply_subs lines.is_a?(Array) ? lines.join : lines
         | 
| 102 | 
            -
              end
         | 
| 103 | 
            -
             | 
| 104 | 
            -
              # Public: Apply substitutions for titles.
         | 
| 105 | 
            -
              #
         | 
| 106 | 
            -
              # title  - The String title to process
         | 
| 107 | 
            -
              #
         | 
| 108 | 
            -
              # returns - A String with title substitutions performed
         | 
| 109 | 
            -
              def apply_title_subs(title)
         | 
| 110 | 
            -
                apply_subs title, SUBS[:title]
         | 
| 111 | 
            -
              end
         | 
| 112 | 
            -
             | 
| 113 | 
            -
              # Public: Apply substitutions for header metadata and attribute assignments
         | 
| 114 | 
            -
              #
         | 
| 115 | 
            -
              # text    - String containing the text process
         | 
| 116 | 
            -
              #
         | 
| 117 | 
            -
              # returns - A String with header substitutions performed
         | 
| 118 | 
            -
              def apply_header_subs(text)
         | 
| 119 | 
            -
                apply_subs text, SUBS[:header]
         | 
| 120 | 
            -
              end
         | 
| 121 | 
            -
             | 
| 122 | 
            -
            =begin
         | 
| 123 | 
            -
              # Public: Apply explicit substitutions, if specified, otherwise normal substitutions.
         | 
| 124 | 
            -
              #
         | 
| 125 | 
            -
              # lines  - The lines of text to process. Can be a String or a String Array
         | 
| 126 | 
            -
              #
         | 
| 127 | 
            -
              # returns - A String with substitutions applied
         | 
| 128 | 
            -
              def apply_para_subs(lines)
         | 
| 129 | 
            -
                if (subs = attr('subs', nil, false))
         | 
| 130 | 
            -
                  apply_subs lines.join, resolve_subs(subs)
         | 
| 131 | 
            -
                else
         | 
| 132 | 
            -
                  apply_subs lines.join
         | 
| 133 | 
            -
                end
         | 
| 134 | 
            -
              end
         | 
| 135 | 
            -
             | 
| 136 | 
            -
              # Public: Apply substitutions for titles
         | 
| 137 | 
            -
              #
         | 
| 138 | 
            -
              # lines  - A String Array containing the lines of text process
         | 
| 139 | 
            -
              #
         | 
| 140 | 
            -
              # returns - A String with literal (verbatim) substitutions performed
         | 
| 141 | 
            -
              def apply_literal_subs(lines)
         | 
| 142 | 
            -
                if (subs = attr('subs', nil, false))
         | 
| 143 | 
            -
                  apply_subs lines.join, resolve_subs(subs)
         | 
| 144 | 
            -
                elsif @style == 'source' && @document.attributes['basebackend'] == 'html' &&
         | 
| 145 | 
            -
                  ((highlighter = @document.attributes['source-highlighter']) == 'coderay' ||
         | 
| 146 | 
            -
                  highlighter == 'pygments') && attr?('language')
         | 
| 147 | 
            -
                  highlight_source lines.join, highlighter, callouts
         | 
| 148 | 
            -
                else
         | 
| 149 | 
            -
                  apply_subs lines.join, SUBS[:verbatim]
         | 
| 150 | 
            -
                end
         | 
| 151 | 
            -
              end
         | 
| 152 | 
            -
             | 
| 153 | 
            -
              # Public: Apply substitutions for passthrough text
         | 
| 154 | 
            -
              #
         | 
| 155 | 
            -
              # lines  - A String Array containing the lines of text process
         | 
| 156 | 
            -
              #
         | 
| 157 | 
            -
              # returns - A String with passthrough substitutions performed
         | 
| 158 | 
            -
              def apply_passthrough_subs(lines)
         | 
| 159 | 
            -
                if (subs = attr('subs', nil, false))
         | 
| 160 | 
            -
                  subs = resolve_subs(subs)
         | 
| 161 | 
            -
                else
         | 
| 162 | 
            -
                  subs = SUBS[:pass]
         | 
| 163 | 
            -
                end
         | 
| 164 | 
            -
                apply_subs lines.join, subs
         | 
| 165 | 
            -
              end
         | 
| 166 | 
            -
            =end
         | 
| 167 | 
            -
             | 
| 168 | 
            -
              # Internal: Extract the passthrough text from the document for reinsertion after processing.
         | 
| 169 | 
            -
              #
         | 
| 170 | 
            -
              # text - The String from which to extract passthrough fragements
         | 
| 171 | 
            -
              #
         | 
| 172 | 
            -
              # returns - The text with the passthrough region substituted with placeholders
         | 
| 173 | 
            -
              def extract_passthroughs(text)
         | 
| 174 | 
            -
                result = text.dup
         | 
| 175 | 
            -
             | 
| 176 | 
            -
                result.gsub!(REGEXP[:pass_macro]) {
         | 
| 177 | 
            -
                  # alias match for Ruby 1.8.7 compat
         | 
| 178 | 
            -
                  m = $~
         | 
| 179 | 
            -
                  # honor the escape
         | 
| 180 | 
            -
                  if m[0].start_with? '\\'
         | 
| 181 | 
            -
                    next m[0][1..-1]
         | 
| 182 | 
            -
                  end
         | 
| 183 | 
            -
             | 
| 184 | 
            -
                  if m[1] == '$$'
         | 
| 185 | 
            -
                    subs = [:specialcharacters]
         | 
| 186 | 
            -
                  elsif m[3].nil? || m[3].empty?
         | 
| 187 | 
            -
                    subs = []
         | 
| 188 | 
            -
                  else
         | 
| 189 | 
            -
                    subs = resolve_pass_subs m[3]
         | 
| 190 | 
            -
                  end
         | 
| 191 | 
            -
             | 
| 192 | 
            -
                  # TODO move unescaping closing square bracket to an operation
         | 
| 193 | 
            -
                  @passthroughs << {:text => m[2] || m[4].gsub('\]', ']'), :subs => subs}
         | 
| 194 | 
            -
                  index = @passthroughs.size - 1
         | 
| 195 | 
            -
                  "\e#{index}\e"
         | 
| 196 | 
            -
                } unless !(result.include?('+++') || result.include?('$$') || result.include?('pass:'))
         | 
| 197 | 
            -
             | 
| 198 | 
            -
                result.gsub!(REGEXP[:pass_lit]) {
         | 
| 199 | 
            -
                  # alias match for Ruby 1.8.7 compat
         | 
| 200 | 
            -
                  m = $~
         | 
| 201 | 
            -
             | 
| 202 | 
            -
                  unescaped_attrs = nil
         | 
| 203 | 
            -
                  # honor the escape
         | 
| 204 | 
            -
                  if m[3].start_with? '\\'
         | 
| 205 | 
            -
                    next m[2].nil? ? "#{m[1]}#{m[3][1..-1]}" : "#{m[1]}[#{m[2]}]#{m[3][1..-1]}"
         | 
| 206 | 
            -
                  elsif m[1] == '\\' && !m[2].nil?
         | 
| 207 | 
            -
                    unescaped_attrs = "[#{m[2]}]"
         | 
| 208 | 
            -
                  end
         | 
| 209 | 
            -
             | 
| 210 | 
            -
                  if unescaped_attrs.nil? && !m[2].nil?
         | 
| 211 | 
            -
                    attributes = parse_attributes(m[2])
         | 
| 212 | 
            -
                  else
         | 
| 213 | 
            -
                    attributes = {}
         | 
| 214 | 
            -
                  end
         | 
| 215 | 
            -
             | 
| 216 | 
            -
                  @passthroughs << {:text => m[4], :subs => [:specialcharacters], :attributes => attributes, :literal => true}
         | 
| 217 | 
            -
                  index = @passthroughs.size - 1
         | 
| 218 | 
            -
                  "#{unescaped_attrs || m[1]}\e#{index}\e"
         | 
| 219 | 
            -
                } unless !result.include?('`')
         | 
| 220 | 
            -
             | 
| 221 | 
            -
                result
         | 
| 222 | 
            -
              end
         | 
| 223 | 
            -
             | 
| 224 | 
            -
              # Internal: Restore the passthrough text by reinserting into the placeholder positions
         | 
| 225 | 
            -
              #
         | 
| 226 | 
            -
              # text - The String text into which to restore the passthrough text
         | 
| 227 | 
            -
              #
         | 
| 228 | 
            -
              # returns The String text with the passthrough text restored
         | 
| 229 | 
            -
              def restore_passthroughs(text)
         | 
| 230 | 
            -
                return text if @passthroughs.nil? || @passthroughs.empty? || !text.include?("\e")
         | 
| 231 | 
            -
             | 
| 232 | 
            -
                text.gsub(REGEXP[:pass_placeholder]) {
         | 
| 233 | 
            -
                  pass = @passthroughs[$1.to_i];
         | 
| 234 | 
            -
                  text = apply_subs(pass[:text], pass.fetch(:subs, []))
         | 
| 235 | 
            -
                  pass[:literal] ? Inline.new(self, :quoted, text, :type => :monospaced, :attributes => pass.fetch(:attributes, {})).render : text
         | 
| 236 | 
            -
                }
         | 
| 237 | 
            -
              end
         | 
| 238 | 
            -
             | 
| 239 | 
            -
              # Public: Substitute special characters (i.e., encode XML)
         | 
| 240 | 
            -
              #
         | 
| 241 | 
            -
              # Special characters are defined in the Asciidoctor::SPECIAL_CHARS Array constant
         | 
| 242 | 
            -
              #
         | 
| 243 | 
            -
              # text - The String text to process
         | 
| 244 | 
            -
              #
         | 
| 245 | 
            -
              # returns The String text with special characters replaced
         | 
| 246 | 
            -
              def sub_specialcharacters(text)
         | 
| 247 | 
            -
                # this syntax only available in Ruby 1.9
         | 
| 248 | 
            -
                #text.gsub(SPECIAL_CHARS_PATTERN, SPECIAL_CHARS)
         | 
| 249 | 
            -
             | 
| 250 | 
            -
                text.gsub(SPECIAL_CHARS_PATTERN) { SPECIAL_CHARS[$&] }
         | 
| 251 | 
            -
              end
         | 
| 252 | 
            -
              alias :sub_specialchars :sub_specialcharacters
         | 
| 253 | 
            -
             | 
| 254 | 
            -
              # Public: Substitute quoted text (includes emphasis, strong, monospaced, etc)
         | 
| 255 | 
            -
              #
         | 
| 256 | 
            -
              # text - The String text to process
         | 
| 257 | 
            -
              #
         | 
| 258 | 
            -
              # returns The String text with quoted text rendered using the backend templates
         | 
| 259 | 
            -
              def sub_quotes(text)
         | 
| 260 | 
            -
                result = text.dup
         | 
| 261 | 
            -
             | 
| 262 | 
            -
                QUOTE_SUBS.each {|type, scope, pattern|
         | 
| 263 | 
            -
                  result.gsub!(pattern) { transform_quoted_text($~, type, scope) }
         | 
| 264 | 
            -
                }
         | 
| 265 | 
            -
             | 
| 266 | 
            -
                result
         | 
| 267 | 
            -
              end
         | 
| 268 | 
            -
             | 
| 269 | 
            -
              # Public: Substitute replacement characters (e.g., copyright, trademark, etc)
         | 
| 270 | 
            -
              #
         | 
| 271 | 
            -
              # text - The String text to process
         | 
| 272 | 
            -
              #
         | 
| 273 | 
            -
              # returns The String text with the replacement characters substituted
         | 
| 274 | 
            -
              def sub_replacements(text)
         | 
| 275 | 
            -
                result = text.dup
         | 
| 276 | 
            -
             | 
| 277 | 
            -
                REPLACEMENTS.each {|pattern, replacement, restore|
         | 
| 278 | 
            -
                  result.gsub!(pattern) {
         | 
| 279 | 
            -
                    matched = $&
         | 
| 280 | 
            -
                    head = $1
         | 
| 281 | 
            -
                    tail = $2
         | 
| 282 | 
            -
                    if matched.include?('\\')
         | 
| 283 | 
            -
                      matched.tr('\\', '')
         | 
| 284 | 
            -
                    else
         | 
| 285 | 
            -
                      case restore
         | 
| 286 | 
            -
                      when :none
         | 
| 287 | 
            -
                        replacement
         | 
| 288 | 
            -
                      when :leading
         | 
| 289 | 
            -
                        "#{head}#{replacement}"
         | 
| 290 | 
            -
                      when :bounding
         | 
| 291 | 
            -
                        "#{head}#{replacement}#{tail}"
         | 
| 292 | 
            -
                      end
         | 
| 293 | 
            -
                    end
         | 
| 294 | 
            -
                  }
         | 
| 295 | 
            -
                }
         | 
| 296 | 
            -
             | 
| 297 | 
            -
                result
         | 
| 298 | 
            -
              end
         | 
| 299 | 
            -
             | 
| 300 | 
            -
              # Public: Substitute attribute references
         | 
| 301 | 
            -
              #
         | 
| 302 | 
            -
              # Attribute references are in the format {name}.
         | 
| 303 | 
            -
              #
         | 
| 304 | 
            -
              # If an attribute referenced in the line is missing, the line is dropped.
         | 
| 305 | 
            -
              #
         | 
| 306 | 
            -
              # text     - The String text to process
         | 
| 307 | 
            -
              #
         | 
| 308 | 
            -
              # returns The String text with the attribute references replaced with attribute values
         | 
| 309 | 
            -
              #--
         | 
| 310 | 
            -
              # NOTE it's necessary to perform this substitution line-by-line
         | 
| 311 | 
            -
              # so that a missing key doesn't wipe out the whole block of data
         | 
| 312 | 
            -
              def sub_attributes(data, opts = {})
         | 
| 313 | 
            -
                return data if data.nil? || data.empty?
         | 
| 314 | 
            -
             | 
| 315 | 
            -
                string_data = data.is_a? String
         | 
| 316 | 
            -
                # normalizes data type to an array (string becomes single-element array)
         | 
| 317 | 
            -
                lines = string_data ? [data] : data
         | 
| 318 | 
            -
             | 
| 319 | 
            -
                result = []
         | 
| 320 | 
            -
                lines.each {|line|
         | 
| 321 | 
            -
                  reject = false
         | 
| 322 | 
            -
                  line = line.gsub(REGEXP[:attr_ref]) {
         | 
| 323 | 
            -
                    # alias match for Ruby 1.8.7 compat
         | 
| 324 | 
            -
                    m = $~
         | 
| 325 | 
            -
                    # escaped attribute, return unescaped
         | 
| 326 | 
            -
                    if !m[1].nil? || !m[4].nil?
         | 
| 327 | 
            -
                      "{#{m[2]}}"
         | 
| 328 | 
            -
                    elsif (directive = m[3])
         | 
| 329 | 
            -
                      offset = directive.length + 1
         | 
| 330 | 
            -
                      expr = m[2][offset..-1]
         | 
| 331 | 
            -
                      case directive
         | 
| 332 | 
            -
                      when 'set'
         | 
| 333 | 
            -
                        args = expr.split(':')
         | 
| 334 | 
            -
                        _, value = Lexer::store_attribute(args[0], args[1] || '', @document)
         | 
| 335 | 
            -
                        if value.nil?
         | 
| 336 | 
            -
                          # since this is an assignment, only drop-line applies here (skip and drop imply the same result)
         | 
| 337 | 
            -
                          if @document.attributes.fetch('attribute-undefined', COMPLIANCE[:attribute_undefined]) == 'drop-line'
         | 
| 338 | 
            -
                            Debug.debug { "Undefining attribute: #{key}, line marked for removal" }
         | 
| 339 | 
            -
                            break ''
         | 
| 340 | 
            -
                          end
         | 
| 341 | 
            -
                        end
         | 
| 342 | 
            -
                        ''
         | 
| 343 | 
            -
                      when 'counter', 'counter2'
         | 
| 344 | 
            -
                        args = expr.split(':')
         | 
| 345 | 
            -
                        val = @document.counter(args[0], args[1])
         | 
| 346 | 
            -
                        directive == 'counter2' ? '' : val
         | 
| 347 | 
            -
                      else
         | 
| 348 | 
            -
                        # if we get here, our attr_ref regex is too loose
         | 
| 349 | 
            -
                        warn "asciidoctor: WARNING: illegal attribute directive: #{m[2]}"
         | 
| 350 | 
            -
                        ''
         | 
| 351 | 
            -
                      end
         | 
| 352 | 
            -
                    elsif (key = m[2].downcase) && @document.attributes.has_key?(key)
         | 
| 353 | 
            -
                      @document.attributes[key]
         | 
| 354 | 
            -
                    elsif INTRINSICS.has_key? key
         | 
| 355 | 
            -
                      INTRINSICS[key]
         | 
| 356 | 
            -
                    else
         | 
| 357 | 
            -
                      case (opts[:attribute_missing] || @document.attributes.fetch('attribute-missing', COMPLIANCE[:attribute_missing]))
         | 
| 358 | 
            -
                      when 'skip'
         | 
| 359 | 
            -
                        "{#{key}}"
         | 
| 360 | 
            -
                      when 'drop-line'
         | 
| 361 | 
            -
                        Debug.debug { "Missing attribute: #{key}, line marked for removal" }
         | 
| 362 | 
            -
                        break ''
         | 
| 363 | 
            -
                      else # 'drop'
         | 
| 364 | 
            -
                        ''
         | 
| 365 | 
            -
                      end
         | 
| 366 | 
            -
                    end
         | 
| 367 | 
            -
                  } if line.include? '{'
         | 
| 368 | 
            -
             | 
| 369 | 
            -
                  result << line unless reject
         | 
| 370 | 
            -
                }
         | 
| 371 | 
            -
             | 
| 372 | 
            -
                string_data ? result.join : result
         | 
| 373 | 
            -
              end
         | 
| 374 | 
            -
             | 
| 375 | 
            -
              # Public: Substitute inline macros (e.g., links, images, etc)
         | 
| 376 | 
            -
              #
         | 
| 377 | 
            -
              # Replace inline macros, which may span multiple lines, in the provided text
         | 
| 378 | 
            -
              #
         | 
| 379 | 
            -
              # text - The String text to process
         | 
| 380 | 
            -
              #
         | 
| 381 | 
            -
              # returns The String with the inline macros rendered using the backend templates
         | 
| 382 | 
            -
              def sub_macros(text)
         | 
| 383 | 
            -
                return text if text.nil? || text.empty?
         | 
| 384 | 
            -
             | 
| 385 | 
            -
                result = text.dup
         | 
| 386 | 
            -
             | 
| 387 | 
            -
                # some look ahead assertions to cut unnecessary regex calls
         | 
| 388 | 
            -
                found = {}
         | 
| 389 | 
            -
                found[:square_bracket] = result.include?('[')
         | 
| 390 | 
            -
                found[:round_bracket] = result.include?('(')
         | 
| 391 | 
            -
                found[:colon] = result.include?(':')
         | 
| 392 | 
            -
                found[:at] = result.include?('@')
         | 
| 393 | 
            -
                found[:macroish] = (found[:square_bracket] && found[:colon])
         | 
| 394 | 
            -
                found[:macroish_short_form] = (found[:square_bracket] && found[:colon] && result.include?(':['))
         | 
| 395 | 
            -
                found[:uri] = (found[:colon] && result.include?('://'))
         | 
| 396 | 
            -
                use_link_attrs = @document.attributes.has_key?('linkattrs')
         | 
| 397 | 
            -
                experimental = @document.attributes.has_key?('experimental')
         | 
| 398 | 
            -
             | 
| 399 | 
            -
                if experimental
         | 
| 400 | 
            -
                  if found[:macroish_short_form] && (result.include?('kbd:') || result.include?('btn:'))
         | 
| 401 | 
            -
                    result.gsub!(REGEXP[:kbd_btn_macro]) {
         | 
| 402 | 
            -
                      # alias match for Ruby 1.8.7 compat
         | 
| 403 | 
            -
                      m = $~
         | 
| 404 | 
            -
                      # honor the escape
         | 
| 405 | 
            -
                      if (captured = m[0]).start_with? '\\'
         | 
| 406 | 
            -
                        next captured[1..-1]
         | 
| 407 | 
            -
                      end
         | 
| 408 | 
            -
             | 
| 409 | 
            -
                      if captured.start_with?('kbd')
         | 
| 410 | 
            -
                        keys = unescape_bracketed_text m[1]
         | 
| 411 | 
            -
             | 
| 412 | 
            -
                        if keys == '+'
         | 
| 413 | 
            -
                          keys = ['+']
         | 
| 414 | 
            -
                        else
         | 
| 415 | 
            -
                          # need to use closure to work around lack of negative lookbehind
         | 
| 416 | 
            -
                          keys = keys.split(REGEXP[:kbd_delim]).inject([]) {|c, key|
         | 
| 417 | 
            -
                            if key.end_with?('++')
         | 
| 418 | 
            -
                              c << key[0..-3].strip
         | 
| 419 | 
            -
                              c << '+'
         | 
| 420 | 
            -
                            else
         | 
| 421 | 
            -
                              c << key.strip
         | 
| 422 | 
            -
                            end
         | 
| 423 | 
            -
                            c
         | 
| 424 | 
            -
                          }
         | 
| 425 | 
            -
                        end
         | 
| 426 | 
            -
                        Inline.new(self, :kbd, nil, :attributes => {'keys' => keys}).render
         | 
| 427 | 
            -
                      elsif captured.start_with?('btn')
         | 
| 428 | 
            -
                        label = unescape_bracketed_text m[1]
         | 
| 429 | 
            -
                        Inline.new(self, :button, label).render
         | 
| 430 | 
            -
                      end
         | 
| 431 | 
            -
                    }
         | 
| 432 | 
            -
                  end
         | 
| 433 | 
            -
             | 
| 434 | 
            -
                  if found[:macroish] && result.include?('menu:')
         | 
| 435 | 
            -
                    result.gsub!(REGEXP[:menu_macro]) {
         | 
| 436 | 
            -
                      # alias match for Ruby 1.8.7 compat
         | 
| 437 | 
            -
                      m = $~
         | 
| 438 | 
            -
                      # honor the escape
         | 
| 439 | 
            -
                      if (captured = m[0]).start_with? '\\'
         | 
| 440 | 
            -
                        next captured[1..-1]
         | 
| 441 | 
            -
                      end
         | 
| 442 | 
            -
             | 
| 443 | 
            -
                      menu = m[1]
         | 
| 444 | 
            -
                      items = m[2]
         | 
| 445 | 
            -
             | 
| 446 | 
            -
                      if items.nil?
         | 
| 447 | 
            -
                        submenus = []
         | 
| 448 | 
            -
                        menuitem = nil
         | 
| 449 | 
            -
                      else
         | 
| 450 | 
            -
                        if (delim = items.include?('>') ? '>' : (items.include?(',') ? ',' : nil))
         | 
| 451 | 
            -
                          submenus = items.split(delim).map(&:strip)
         | 
| 452 | 
            -
                          menuitem = submenus.pop
         | 
| 453 | 
            -
                        else
         | 
| 454 | 
            -
                          submenus = []
         | 
| 455 | 
            -
                          menuitem = items.rstrip
         | 
| 456 | 
            -
                        end
         | 
| 457 | 
            -
                      end
         | 
| 458 | 
            -
             | 
| 459 | 
            -
                      Inline.new(self, :menu, nil, :attributes => {'menu' => menu, 'submenus' => submenus, 'menuitem' => menuitem}).render
         | 
| 460 | 
            -
                    }
         | 
| 461 | 
            -
                  end
         | 
| 462 | 
            -
             | 
| 463 | 
            -
                  if result.include?('"') && result.include?('>')
         | 
| 464 | 
            -
                    result.gsub!(REGEXP[:menu_inline_macro]) {
         | 
| 465 | 
            -
                      # alias match for Ruby 1.8.7 compat
         | 
| 466 | 
            -
                      m = $~
         | 
| 467 | 
            -
                      # honor the escape
         | 
| 468 | 
            -
                      if (captured = m[0]).start_with? '\\'
         | 
| 469 | 
            -
                        next captured[1..-1]
         | 
| 470 | 
            -
                      end
         | 
| 471 | 
            -
             | 
| 472 | 
            -
                      input = m[1]
         | 
| 473 | 
            -
             | 
| 474 | 
            -
                      menu, *submenus = input.split('>').map(&:strip)
         | 
| 475 | 
            -
                      menuitem = submenus.pop
         | 
| 476 | 
            -
                      Inline.new(self, :menu, nil, :attributes => {'menu' => menu, 'submenus' => submenus, 'menuitem' => menuitem}).render
         | 
| 477 | 
            -
                    }
         | 
| 478 | 
            -
                  end
         | 
| 479 | 
            -
                end
         | 
| 480 | 
            -
             | 
| 481 | 
            -
                # FIXME this location is somewhat arbitrary, probably need to be able to control ordering
         | 
| 482 | 
            -
                # TODO this handling needs some cleanup
         | 
| 483 | 
            -
                if (extensions = @document.extensions) && extensions.inline_macros? && found[:macroish]
         | 
| 484 | 
            -
                  extensions.load_inline_macro_processors(@document).each do |processor|
         | 
| 485 | 
            -
                    result.gsub!(processor.regexp) {
         | 
| 486 | 
            -
                      # alias match for Ruby 1.8.7 compat
         | 
| 487 | 
            -
                      m = $~
         | 
| 488 | 
            -
                      # honor the escape
         | 
| 489 | 
            -
                      if m[0].start_with? '\\'
         | 
| 490 | 
            -
                        next m[0][1..-1]
         | 
| 491 | 
            -
                      end
         | 
| 492 | 
            -
             | 
| 493 | 
            -
                      target = m[1]
         | 
| 494 | 
            -
                      if processor.options[:short_form]
         | 
| 495 | 
            -
                        attributes = {}
         | 
| 496 | 
            -
                      else
         | 
| 497 | 
            -
                        posattrs = processor.options.fetch(:pos_attrs, [])
         | 
| 498 | 
            -
                        attributes = parse_attributes(m[2], posattrs, :sub_input => true, :unescape_input => true)
         | 
| 499 | 
            -
                      end
         | 
| 500 | 
            -
                      processor.process self, target, attributes
         | 
| 501 | 
            -
                    }
         | 
| 502 | 
            -
                  end
         | 
| 503 | 
            -
                end
         | 
| 504 | 
            -
             | 
| 505 | 
            -
                if found[:macroish] && (result.include?('image:') || result.include?('icon:'))
         | 
| 506 | 
            -
                  # image:filename.png[Alt Text]
         | 
| 507 | 
            -
                  result.gsub!(REGEXP[:image_macro]) {
         | 
| 508 | 
            -
                    # alias match for Ruby 1.8.7 compat
         | 
| 509 | 
            -
                    m = $~
         | 
| 510 | 
            -
                    # honor the escape
         | 
| 511 | 
            -
                    if m[0].start_with? '\\'
         | 
| 512 | 
            -
                      next m[0][1..-1]
         | 
| 513 | 
            -
                    end
         | 
| 514 | 
            -
             | 
| 515 | 
            -
                    raw_attrs = unescape_bracketed_text m[2]
         | 
| 516 | 
            -
                    if m[0].start_with? 'icon:'
         | 
| 517 | 
            -
                      type = 'icon'
         | 
| 518 | 
            -
                      posattrs = ['size']
         | 
| 519 | 
            -
                    else
         | 
| 520 | 
            -
                      type = 'image'
         | 
| 521 | 
            -
                      posattrs = ['alt', 'width', 'height']
         | 
| 522 | 
            -
                    end
         | 
| 523 | 
            -
                    target = sub_attributes(m[1])
         | 
| 524 | 
            -
                    unless type == 'icon'
         | 
| 525 | 
            -
                      @document.register(:images, target)
         | 
| 526 | 
            -
                    end
         | 
| 527 | 
            -
                    attrs = parse_attributes(raw_attrs, posattrs)
         | 
| 528 | 
            -
                    if !attrs['alt']
         | 
| 529 | 
            -
                      attrs['alt'] = File.basename(target, File.extname(target))
         | 
| 530 | 
            -
                    end
         | 
| 531 | 
            -
                    Inline.new(self, :image, nil, :type => type, :target => target, :attributes => attrs).render
         | 
| 532 | 
            -
                  }
         | 
| 533 | 
            -
                end
         | 
| 534 | 
            -
             | 
| 535 | 
            -
                if found[:macroish_short_form] || found[:round_bracket]
         | 
| 536 | 
            -
                  # indexterm:[Tigers,Big cats]
         | 
| 537 | 
            -
                  # (((Tigers,Big cats)))
         | 
| 538 | 
            -
                  result.gsub!(REGEXP[:indexterm_macro]) {
         | 
| 539 | 
            -
                    # alias match for Ruby 1.8.7 compat
         | 
| 540 | 
            -
                    m = $~
         | 
| 541 | 
            -
                    # honor the escape
         | 
| 542 | 
            -
                    if m[0].start_with? '\\'
         | 
| 543 | 
            -
                      next m[0][1..-1]
         | 
| 544 | 
            -
                    end
         | 
| 545 | 
            -
             | 
| 546 | 
            -
                    terms = unescape_bracketed_text(m[1] || m[2]).split(',').map(&:strip)
         | 
| 547 | 
            -
                    @document.register(:indexterms, [*terms])
         | 
| 548 | 
            -
                    Inline.new(self, :indexterm, text, :attributes => {'terms' => terms}).render
         | 
| 549 | 
            -
                  }
         | 
| 550 | 
            -
             | 
| 551 | 
            -
                  # indexterm2:[Tigers]
         | 
| 552 | 
            -
                  # ((Tigers))
         | 
| 553 | 
            -
                  result.gsub!(REGEXP[:indexterm2_macro]) {
         | 
| 554 | 
            -
                    # alias match for Ruby 1.8.7 compat
         | 
| 555 | 
            -
                    m = $~
         | 
| 556 | 
            -
                    # honor the escape
         | 
| 557 | 
            -
                    if m[0].start_with? '\\'
         | 
| 558 | 
            -
                      next m[0][1..-1]
         | 
| 559 | 
            -
                    end
         | 
| 560 | 
            -
             | 
| 561 | 
            -
                    text = unescape_bracketed_text(m[1] || m[2])
         | 
| 562 | 
            -
                    @document.register(:indexterms, [text])
         | 
| 563 | 
            -
                    Inline.new(self, :indexterm, text, :type => :visible).render
         | 
| 564 | 
            -
                  }
         | 
| 565 | 
            -
                end
         | 
| 566 | 
            -
             | 
| 567 | 
            -
                if found[:uri]
         | 
| 568 | 
            -
                  # inline urls, target[text] (optionally prefixed with link: and optionally surrounded by <>)
         | 
| 569 | 
            -
                  result.gsub!(REGEXP[:link_inline]) {
         | 
| 570 | 
            -
                    # alias match for Ruby 1.8.7 compat
         | 
| 571 | 
            -
                    m = $~
         | 
| 572 | 
            -
                    # honor the escape
         | 
| 573 | 
            -
                    if m[2].start_with? '\\'
         | 
| 574 | 
            -
                      next "#{m[1]}#{m[2][1..-1]}#{m[3]}"
         | 
| 575 | 
            -
                    # not a valid macro syntax w/o trailing square brackets
         | 
| 576 | 
            -
                    # we probably shouldn't even get here...our regex is doing too much
         | 
| 577 | 
            -
                    elsif m[1] == 'link:' && m[3].nil?
         | 
| 578 | 
            -
                      next m[0]
         | 
| 579 | 
            -
                    end
         | 
| 580 | 
            -
                    prefix = (m[1] != 'link:' ? m[1] : '')
         | 
| 581 | 
            -
                    target = m[2]
         | 
| 582 | 
            -
                    suffix = ''
         | 
| 583 | 
            -
                    # strip the <> around the link
         | 
| 584 | 
            -
                    if prefix.start_with?('<') && target.end_with?('>')
         | 
| 585 | 
            -
                      prefix = prefix[4..-1]
         | 
| 586 | 
            -
                      target = target[0..-5]
         | 
| 587 | 
            -
                    elsif prefix.start_with?('(') && target.end_with?(')')
         | 
| 588 | 
            -
                      target = target[0..-2]
         | 
| 589 | 
            -
                      suffix = ')'
         | 
| 590 | 
            -
                    elsif target.end_with?('):')
         | 
| 591 | 
            -
                      target = target[0..-3]
         | 
| 592 | 
            -
                      suffix = '):'
         | 
| 593 | 
            -
                    end
         | 
| 594 | 
            -
                    @document.register(:links, target)
         | 
| 595 | 
            -
             | 
| 596 | 
            -
                    attrs = nil
         | 
| 597 | 
            -
                    #text = !m[3].nil? ? sub_attributes(m[3].gsub('\]', ']')) : ''
         | 
| 598 | 
            -
                    if !m[3].to_s.empty?
         | 
| 599 | 
            -
                      if use_link_attrs && (m[3].start_with?('"') || m[3].include?(','))
         | 
| 600 | 
            -
                        attrs = parse_attributes(sub_attributes(m[3].gsub('\]', ']')), [])
         | 
| 601 | 
            -
                        text = attrs[1]
         | 
| 602 | 
            -
                      else
         | 
| 603 | 
            -
                        text = sub_attributes(m[3].gsub('\]', ']'))
         | 
| 604 | 
            -
                      end
         | 
| 605 | 
            -
             | 
| 606 | 
            -
                      if text.end_with? '^'
         | 
| 607 | 
            -
                        text = text.chop
         | 
| 608 | 
            -
                        attrs ||= {}
         | 
| 609 | 
            -
                        attrs['window'] = '_blank' unless attrs.has_key?('window')
         | 
| 610 | 
            -
                      end
         | 
| 611 | 
            -
                    else
         | 
| 612 | 
            -
                      text = ''
         | 
| 613 | 
            -
                    end
         | 
| 614 | 
            -
             | 
| 615 | 
            -
                    "#{prefix}#{Inline.new(self, :anchor, (!text.empty? ? text : target), :type => :link, :target => target, :attributes => attrs).render}#{suffix}"
         | 
| 616 | 
            -
                  }
         | 
| 617 | 
            -
                end
         | 
| 618 | 
            -
             | 
| 619 | 
            -
                if found[:macroish] && (result.include?('link:') || result.include?('mailto:'))
         | 
| 620 | 
            -
                  # inline link macros, link:target[text]
         | 
| 621 | 
            -
                  result.gsub!(REGEXP[:link_macro]) {
         | 
| 622 | 
            -
                    # alias match for Ruby 1.8.7 compat
         | 
| 623 | 
            -
                    m = $~
         | 
| 624 | 
            -
                    # honor the escape
         | 
| 625 | 
            -
                    if m[0].start_with? '\\'
         | 
| 626 | 
            -
                      next m[0][1..-1]
         | 
| 627 | 
            -
                    end
         | 
| 628 | 
            -
                    raw_target = m[1]
         | 
| 629 | 
            -
                    mailto = m[0].start_with?('mailto:')
         | 
| 630 | 
            -
                    target = mailto ? "mailto:#{raw_target}" : raw_target
         | 
| 631 | 
            -
             | 
| 632 | 
            -
                    attrs = nil
         | 
| 633 | 
            -
                    #text = sub_attributes(m[2].gsub('\]', ']'))
         | 
| 634 | 
            -
                    if use_link_attrs && (m[2].start_with?('"') || m[2].include?(','))
         | 
| 635 | 
            -
                      attrs = parse_attributes(sub_attributes(m[2].gsub('\]', ']')), [])
         | 
| 636 | 
            -
                      text = attrs[1]
         | 
| 637 | 
            -
                      if mailto
         | 
| 638 | 
            -
                        if attrs.has_key? 2
         | 
| 639 | 
            -
                          target = "#{target}?subject=#{Helpers.encode_uri(attrs[2])}"
         | 
| 640 | 
            -
             | 
| 641 | 
            -
                          if attrs.has_key? 3
         | 
| 642 | 
            -
                            target = "#{target}&body=#{Helpers.encode_uri(attrs[3])}"
         | 
| 643 | 
            -
                          end
         | 
| 644 | 
            -
                        end
         | 
| 645 | 
            -
                      end
         | 
| 646 | 
            -
                    else
         | 
| 647 | 
            -
                      text = sub_attributes(m[2].gsub('\]', ']'))
         | 
| 648 | 
            -
                    end
         | 
| 649 | 
            -
             | 
| 650 | 
            -
                    if text.end_with? '^'
         | 
| 651 | 
            -
                      text = text.chop
         | 
| 652 | 
            -
                      attrs ||= {}
         | 
| 653 | 
            -
                      attrs['window'] = '_blank' unless attrs.has_key?('window')
         | 
| 654 | 
            -
                    end
         | 
| 655 | 
            -
             | 
| 656 | 
            -
                    # QUESTION should a mailto be registered as an e-mail address?
         | 
| 657 | 
            -
                    @document.register(:links, target)
         | 
| 658 | 
            -
             | 
| 659 | 
            -
                    Inline.new(self, :anchor, (!text.empty? ? text : raw_target), :type => :link, :target => target, :attributes => attrs).render
         | 
| 660 | 
            -
                  }
         | 
| 661 | 
            -
                end
         | 
| 662 | 
            -
             | 
| 663 | 
            -
                if found[:at]
         | 
| 664 | 
            -
                  result.gsub!(REGEXP[:email_inline]) {
         | 
| 665 | 
            -
                    # alias match for Ruby 1.8.7 compat
         | 
| 666 | 
            -
                    m = $~
         | 
| 667 | 
            -
                    address = m[0]
         | 
| 668 | 
            -
                    case address[0..0]
         | 
| 669 | 
            -
                    when '\\'
         | 
| 670 | 
            -
                      next address[1..-1]
         | 
| 671 | 
            -
                    when '>', ':'
         | 
| 672 | 
            -
                      next address
         | 
| 673 | 
            -
                    end
         | 
| 674 | 
            -
             | 
| 675 | 
            -
                    target = "mailto:#{address}"
         | 
| 676 | 
            -
                    # QUESTION should this be registered as an e-mail address?
         | 
| 677 | 
            -
                    @document.register(:links, target)
         | 
| 678 | 
            -
             | 
| 679 | 
            -
                    Inline.new(self, :anchor, address, :type => :link, :target => target).render
         | 
| 680 | 
            -
                  }
         | 
| 681 | 
            -
                end
         | 
| 682 | 
            -
             | 
| 683 | 
            -
                if found[:macroish_short_form] && result.include?('footnote')
         | 
| 684 | 
            -
                  result.gsub!(REGEXP[:footnote_macro]) {
         | 
| 685 | 
            -
                    # alias match for Ruby 1.8.7 compat
         | 
| 686 | 
            -
                    m = $~
         | 
| 687 | 
            -
                    # honor the escape
         | 
| 688 | 
            -
                    if m[0].start_with? '\\'
         | 
| 689 | 
            -
                      next m[0][1..-1]
         | 
| 690 | 
            -
                    end
         | 
| 691 | 
            -
                    if m[1] == 'footnote'
         | 
| 692 | 
            -
                      # hmmmm
         | 
| 693 | 
            -
                      text = restore_passthroughs(m[2])
         | 
| 694 | 
            -
                      id = nil
         | 
| 695 | 
            -
                      index = @document.counter('footnote-number')
         | 
| 696 | 
            -
                      @document.register(:footnotes, Document::Footnote.new(index, id, text))
         | 
| 697 | 
            -
                      type = nil
         | 
| 698 | 
            -
                      target = nil
         | 
| 699 | 
            -
                    else
         | 
| 700 | 
            -
                      id, text = m[2].split(',', 2).map(&:strip)
         | 
| 701 | 
            -
                      if !text.nil?
         | 
| 702 | 
            -
                        # hmmmm
         | 
| 703 | 
            -
                        text = restore_passthroughs(text)
         | 
| 704 | 
            -
                        index = @document.counter('footnote-number')
         | 
| 705 | 
            -
                        @document.register(:footnotes, Document::Footnote.new(index, id, text))
         | 
| 706 | 
            -
                        type = :ref
         | 
| 707 | 
            -
                        target = nil
         | 
| 708 | 
            -
                      else
         | 
| 709 | 
            -
                        footnote = @document.references[:footnotes].find {|fn| fn.id == id }
         | 
| 710 | 
            -
                        target = id
         | 
| 711 | 
            -
                        id = nil
         | 
| 712 | 
            -
                        index = footnote.index
         | 
| 713 | 
            -
                        text = footnote.text
         | 
| 714 | 
            -
                        type = :xref
         | 
| 715 | 
            -
                      end
         | 
| 716 | 
            -
                    end
         | 
| 717 | 
            -
                    Inline.new(self, :footnote, text, :attributes => {'index' => index}, :id => id, :target => target, :type => type).render
         | 
| 718 | 
            -
                  }
         | 
| 719 | 
            -
                end
         | 
| 720 | 
            -
             | 
| 721 | 
            -
                if found[:macroish] || result.include?('<<')
         | 
| 722 | 
            -
                  result.gsub!(REGEXP[:xref_macro]) {
         | 
| 723 | 
            -
                    # alias match for Ruby 1.8.7 compat
         | 
| 724 | 
            -
                    m = $~
         | 
| 725 | 
            -
                    # honor the escape
         | 
| 726 | 
            -
                    if m[0].start_with? '\\'
         | 
| 727 | 
            -
                      next m[0][1..-1]
         | 
| 728 | 
            -
                    end
         | 
| 729 | 
            -
                    if !m[1].nil?
         | 
| 730 | 
            -
                      id, reftext = m[1].split(',', 2).map(&:strip)
         | 
| 731 | 
            -
                      id.sub!(REGEXP[:dbl_quoted], '\2')
         | 
| 732 | 
            -
                      reftext.sub!(REGEXP[:m_dbl_quoted], '\2') unless reftext.nil?
         | 
| 733 | 
            -
                    else
         | 
| 734 | 
            -
                      id = m[2]
         | 
| 735 | 
            -
                      reftext = !m[3].empty? ? m[3] : nil
         | 
| 736 | 
            -
                    end
         | 
| 737 | 
            -
             | 
| 738 | 
            -
                    if id.include? '#'
         | 
| 739 | 
            -
                      path, fragment = id.split('#')
         | 
| 740 | 
            -
                    else
         | 
| 741 | 
            -
                      path = nil
         | 
| 742 | 
            -
                      fragment = id
         | 
| 743 | 
            -
                    end
         | 
| 744 | 
            -
             | 
| 745 | 
            -
                    # handles form: id
         | 
| 746 | 
            -
                    if path.nil?
         | 
| 747 | 
            -
                      refid = fragment
         | 
| 748 | 
            -
                      target = "##{fragment}"
         | 
| 749 | 
            -
                    # handles forms: doc#, doc.adoc#, doc#id and doc.adoc#id
         | 
| 750 | 
            -
                    else
         | 
| 751 | 
            -
                      path = Helpers.rootname(path)
         | 
| 752 | 
            -
                      # the referenced path is this document, or its contents has been included in this document
         | 
| 753 | 
            -
                      if @document.attr?('docname', path) || @document.references[:includes].include?(path)
         | 
| 754 | 
            -
                        refid = fragment
         | 
| 755 | 
            -
                        path = nil
         | 
| 756 | 
            -
                        target = "##{fragment}"
         | 
| 757 | 
            -
                      else
         | 
| 758 | 
            -
                        refid = fragment.nil? ? path : "#{path}##{fragment}"
         | 
| 759 | 
            -
                        path = "#{path}#{@document.attr 'outfilesuffix', '.html'}"
         | 
| 760 | 
            -
                        target = fragment.nil? ? path : "#{path}##{fragment}"
         | 
| 761 | 
            -
                      end
         | 
| 762 | 
            -
                    end
         | 
| 763 | 
            -
                    Inline.new(self, :anchor, reftext, :type => :xref, :target => target, :attributes => {'path' => path, 'fragment' => fragment, 'refid' => refid}).render
         | 
| 764 | 
            -
                  }
         | 
| 765 | 
            -
                end
         | 
| 766 | 
            -
             | 
| 767 | 
            -
                if found[:square_bracket] && result.include?('[[[')
         | 
| 768 | 
            -
                  result.gsub!(REGEXP[:biblio_macro]) {
         | 
| 769 | 
            -
                    # alias match for Ruby 1.8.7 compat
         | 
| 770 | 
            -
                    m = $~
         | 
| 771 | 
            -
                    # honor the escape
         | 
| 772 | 
            -
                    if m[0].start_with? '\\'
         | 
| 773 | 
            -
                      next m[0][1..-1]
         | 
| 774 | 
            -
                    end
         | 
| 775 | 
            -
                    id = reftext = m[1]
         | 
| 776 | 
            -
                    Inline.new(self, :anchor, reftext, :type => :bibref, :target => id).render
         | 
| 777 | 
            -
                  }
         | 
| 778 | 
            -
                end
         | 
| 779 | 
            -
             | 
| 780 | 
            -
                if found[:square_bracket] && result.include?('[[')
         | 
| 781 | 
            -
                  result.gsub!(REGEXP[:anchor_macro]) {
         | 
| 782 | 
            -
                    # alias match for Ruby 1.8.7 compat
         | 
| 783 | 
            -
                    m = $~
         | 
| 784 | 
            -
                    # honor the escape
         | 
| 785 | 
            -
                    if m[0].start_with? '\\'
         | 
| 786 | 
            -
                      next m[0][1..-1]
         | 
| 787 | 
            -
                    end
         | 
| 788 | 
            -
                    id, reftext = m[1].split(',').map(&:strip)
         | 
| 789 | 
            -
                    id.sub!(REGEXP[:dbl_quoted], '\2')
         | 
| 790 | 
            -
                    if reftext.nil?
         | 
| 791 | 
            -
                      reftext = "[#{id}]"
         | 
| 792 | 
            -
                    else
         | 
| 793 | 
            -
                      reftext.sub!(REGEXP[:m_dbl_quoted], '\2')
         | 
| 794 | 
            -
                    end
         | 
| 795 | 
            -
                    # NOTE the reftext should also match what's in our references dic
         | 
| 796 | 
            -
                    if !@document.references[:ids].has_key? id
         | 
| 797 | 
            -
                      Debug.debug { "Missing reference for anchor #{id}" }
         | 
| 798 | 
            -
                    end
         | 
| 799 | 
            -
                    Inline.new(self, :anchor, reftext, :type => :ref, :target => id).render
         | 
| 800 | 
            -
                  }
         | 
| 801 | 
            -
                end
         | 
| 802 | 
            -
             | 
| 803 | 
            -
                result
         | 
| 804 | 
            -
              end
         | 
| 805 | 
            -
             | 
| 806 | 
            -
              # Public: Substitute callout references
         | 
| 807 | 
            -
              #
         | 
| 808 | 
            -
              # text - The String text to process
         | 
| 809 | 
            -
              #
         | 
| 810 | 
            -
              # returns The String with the callout references rendered using the backend templates
         | 
| 811 | 
            -
              def sub_callouts(text)
         | 
| 812 | 
            -
                text.gsub(REGEXP[:callout_render]) {
         | 
| 813 | 
            -
                  # alias match for Ruby 1.8.7 compat
         | 
| 814 | 
            -
                  m = $~
         | 
| 815 | 
            -
                  # honor the escape
         | 
| 816 | 
            -
                  if m[1] == '\\'
         | 
| 817 | 
            -
                    # we have to do a sub since we aren't sure it's the first char
         | 
| 818 | 
            -
                    next m[0].sub('\\', '')
         | 
| 819 | 
            -
                  end
         | 
| 820 | 
            -
                  Inline.new(self, :callout, m[3], :id => @document.callouts.read_next_id).render
         | 
| 821 | 
            -
                }
         | 
| 822 | 
            -
              end
         | 
| 823 | 
            -
             | 
| 824 | 
            -
              # Public: Substitute post replacements
         | 
| 825 | 
            -
              #
         | 
| 826 | 
            -
              # text - The String text to process
         | 
| 827 | 
            -
              #
         | 
| 828 | 
            -
              # returns The String with the post replacements rendered using the backend templates
         | 
| 829 | 
            -
              def sub_post_replacements(text)
         | 
| 830 | 
            -
                if @document.attributes['hardbreaks']
         | 
| 831 | 
            -
                  lines = text.lines.entries
         | 
| 832 | 
            -
                  return text if lines.size == 1
         | 
| 833 | 
            -
                  last = lines.pop
         | 
| 834 | 
            -
                  lines.map {|line| Inline.new(self, :break, line.rstrip.chomp(LINE_BREAK), :type => :line).render }.push(last) * EOL
         | 
| 835 | 
            -
                else
         | 
| 836 | 
            -
                  text.gsub(REGEXP[:line_break]) { Inline.new(self, :break, $1, :type => :line).render }
         | 
| 837 | 
            -
                end
         | 
| 838 | 
            -
              end
         | 
| 839 | 
            -
             | 
| 840 | 
            -
              # Internal: Transform (render) a quoted text region
         | 
| 841 | 
            -
              #
         | 
| 842 | 
            -
              # match  - The MatchData for the quoted text region
         | 
| 843 | 
            -
              # type   - The quoting type (single, double, strong, emphasis, monospaced, etc)
         | 
| 844 | 
            -
              # scope  - The scope of the quoting (constrained or unconstrained)
         | 
| 845 | 
            -
              #
         | 
| 846 | 
            -
              # returns The rendered text for the quoted text region
         | 
| 847 | 
            -
              def transform_quoted_text(match, type, scope)
         | 
| 848 | 
            -
                unescaped_attrs = nil
         | 
| 849 | 
            -
                if match[0].start_with? '\\'
         | 
| 850 | 
            -
                  if scope == :constrained && !match[2].nil?
         | 
| 851 | 
            -
                    unescaped_attrs = "[#{match[2]}]"
         | 
| 852 | 
            -
                  else
         | 
| 853 | 
            -
                    return match[0][1..-1]
         | 
| 854 | 
            -
                  end
         | 
| 855 | 
            -
                end
         | 
| 856 | 
            -
             | 
| 857 | 
            -
                if scope == :constrained
         | 
| 858 | 
            -
                  if unescaped_attrs.nil?
         | 
| 859 | 
            -
                    attributes = parse_quoted_text_attributes(match[2])
         | 
| 860 | 
            -
                    id = attributes.nil? ? nil : attributes.delete('id')
         | 
| 861 | 
            -
                    "#{match[1]}#{Inline.new(self, :quoted, match[3], :type => type, :id => id, :attributes => attributes).render}"
         | 
| 862 | 
            -
                  else
         | 
| 863 | 
            -
                    "#{unescaped_attrs}#{Inline.new(self, :quoted, match[3], :type => type, :attributes => {}).render}"
         | 
| 864 | 
            -
                  end
         | 
| 865 | 
            -
                else
         | 
| 866 | 
            -
                  attributes = parse_quoted_text_attributes(match[1])
         | 
| 867 | 
            -
                  id = attributes.nil? ? nil : attributes.delete('id')
         | 
| 868 | 
            -
                  Inline.new(self, :quoted, match[2], :type => type, :id => id, :attributes => attributes).render
         | 
| 869 | 
            -
                end
         | 
| 870 | 
            -
              end
         | 
| 871 | 
            -
             | 
| 872 | 
            -
              # Internal: Parse the attributes that are defined on quoted text
         | 
| 873 | 
            -
              #
         | 
| 874 | 
            -
              # str       - A String of unprocessed attributes (space-separated roles or the id/role shorthand syntax)
         | 
| 875 | 
            -
              #
         | 
| 876 | 
            -
              # returns nil if str is nil, an empty Hash if str is empty, otherwise a Hash of attributes (role and id only)
         | 
| 877 | 
            -
              def parse_quoted_text_attributes(str)
         | 
| 878 | 
            -
                return nil if str.nil?
         | 
| 879 | 
            -
                return {} if str.empty?
         | 
| 880 | 
            -
                str = sub_attributes(str) if str.include?('{')
         | 
| 881 | 
            -
                str = str.strip
         | 
| 882 | 
            -
                # for compliance, only consider first positional attribute
         | 
| 883 | 
            -
                str, _ = str.split(',', 2) if str.include?(',')
         | 
| 884 | 
            -
             | 
| 885 | 
            -
                if str.empty?
         | 
| 886 | 
            -
                  {}
         | 
| 887 | 
            -
                elsif str.start_with?('.') || str.start_with?('#')
         | 
| 888 | 
            -
                  segments = str.split('#', 2)
         | 
| 889 | 
            -
             | 
| 890 | 
            -
                  if segments.length > 1
         | 
| 891 | 
            -
                    id, *more_roles = segments[1].split('.')
         | 
| 892 | 
            -
                  else
         | 
| 893 | 
            -
                    id = nil
         | 
| 894 | 
            -
                    more_roles = []
         | 
| 895 | 
            -
                  end
         | 
| 896 | 
            -
             | 
| 897 | 
            -
                  roles = segments[0].empty? ? [] : segments[0].split('.')
         | 
| 898 | 
            -
                  if roles.length > 1
         | 
| 899 | 
            -
                    roles.shift
         | 
| 900 | 
            -
                  end
         | 
| 901 | 
            -
             | 
| 902 | 
            -
                  if more_roles.length > 0
         | 
| 903 | 
            -
                    roles.concat more_roles
         | 
| 904 | 
            -
                  end
         | 
| 905 | 
            -
             | 
| 906 | 
            -
                  attrs = {}
         | 
| 907 | 
            -
                  attrs['id'] = id unless id.nil?
         | 
| 908 | 
            -
                  attrs['role'] = roles.empty? ? nil : (roles * ' ')
         | 
| 909 | 
            -
                  attrs
         | 
| 910 | 
            -
                else
         | 
| 911 | 
            -
                  {'role' => str}
         | 
| 912 | 
            -
                end
         | 
| 913 | 
            -
              end
         | 
| 914 | 
            -
             | 
| 915 | 
            -
              # Internal: Parse the attributes in the attribute line
         | 
| 916 | 
            -
              #
         | 
| 917 | 
            -
              # attrline  - A String of unprocessed attributes (key/value pairs)
         | 
| 918 | 
            -
              # posattrs  - The keys for positional attributes
         | 
| 919 | 
            -
              #
         | 
| 920 | 
            -
              # returns nil if attrline is nil, an empty Hash if attrline is empty, otherwise a Hash of parsed attributes
         | 
| 921 | 
            -
              def parse_attributes(attrline, posattrs = ['role'], opts = {})
         | 
| 922 | 
            -
                return nil if attrline.nil?
         | 
| 923 | 
            -
                return {} if attrline.empty?
         | 
| 924 | 
            -
                attrline = @document.sub_attributes(attrline) if opts[:sub_input]
         | 
| 925 | 
            -
                attrline = unescape_bracketed_text(attrline) if opts[:unescape_input]
         | 
| 926 | 
            -
                block = nil
         | 
| 927 | 
            -
                if opts.fetch(:sub_result, true)
         | 
| 928 | 
            -
                  # substitutions are only performed on attribute values if block is not nil
         | 
| 929 | 
            -
                  block = self
         | 
| 930 | 
            -
                end
         | 
| 931 | 
            -
             | 
| 932 | 
            -
                if opts.has_key?(:into)
         | 
| 933 | 
            -
                  AttributeList.new(attrline, block).parse_into(opts[:into], posattrs)
         | 
| 934 | 
            -
                else
         | 
| 935 | 
            -
                  AttributeList.new(attrline, block).parse(posattrs)
         | 
| 936 | 
            -
                end
         | 
| 937 | 
            -
              end
         | 
| 938 | 
            -
             | 
| 939 | 
            -
              # Internal: Strip bounding whitespace, fold endlines and unescaped closing
         | 
| 940 | 
            -
              # square brackets from text extracted from brackets
         | 
| 941 | 
            -
              def unescape_bracketed_text(text)
         | 
| 942 | 
            -
                return '' if text.empty?
         | 
| 943 | 
            -
                text.strip.tr(EOL, ' ').gsub('\]', ']')
         | 
| 944 | 
            -
              end
         | 
| 945 | 
            -
             | 
| 946 | 
            -
              # Internal: Resolve the list of comma-delimited subs against the possible options.
         | 
| 947 | 
            -
              #
         | 
| 948 | 
            -
              # subs - A comma-delimited String of substitution aliases
         | 
| 949 | 
            -
              #
         | 
| 950 | 
            -
              # returns An Array of Symbols representing the substitution operation
         | 
| 951 | 
            -
              def resolve_subs subs, type = :block, subject = nil
         | 
| 952 | 
            -
                return [] if subs.nil? || subs.empty?
         | 
| 953 | 
            -
                candidates = []
         | 
| 954 | 
            -
                subs.split(',').each do |val|
         | 
| 955 | 
            -
                  key = val.strip.to_sym
         | 
| 956 | 
            -
                  # special case to disable callouts for inline subs
         | 
| 957 | 
            -
                  if key == :verbatim && type == :inline
         | 
| 958 | 
            -
                    candidates << :specialcharacters
         | 
| 959 | 
            -
                  elsif COMPOSITE_SUBS.has_key? key
         | 
| 960 | 
            -
                    candidates.push(*COMPOSITE_SUBS[key])
         | 
| 961 | 
            -
                  else
         | 
| 962 | 
            -
                    candidates << key
         | 
| 963 | 
            -
                  end
         | 
| 964 | 
            -
                end
         | 
| 965 | 
            -
                # weed out invalid options and remove duplicates (first wins)
         | 
| 966 | 
            -
                resolved = candidates & SUB_OPTIONS[type]
         | 
| 967 | 
            -
                if (invalid = candidates - resolved).size > 0
         | 
| 968 | 
            -
                  warn "asciidoctor: WARNING: invalid substitution type#{invalid.size > 1 ? 's' : ''}#{subject ? ' for ' : nil}#{subject}: #{invalid * ', '}"
         | 
| 969 | 
            -
                end
         | 
| 970 | 
            -
                resolved
         | 
| 971 | 
            -
              end
         | 
| 972 | 
            -
             | 
| 973 | 
            -
              def resolve_block_subs subs, subject
         | 
| 974 | 
            -
                resolve_subs subs, :block, subject
         | 
| 975 | 
            -
              end
         | 
| 976 | 
            -
             | 
| 977 | 
            -
              def resolve_pass_subs subs
         | 
| 978 | 
            -
                resolve_subs subs, :inline, 'passthrough macro'
         | 
| 979 | 
            -
              end
         | 
| 980 | 
            -
             | 
| 981 | 
            -
              # Public: Highlight the source code if a source highlighter is defined
         | 
| 982 | 
            -
              # on the document, otherwise return the text unprocessed
         | 
| 983 | 
            -
              #
         | 
| 984 | 
            -
              # Callout marks are stripped from the source prior to passing it to the
         | 
| 985 | 
            -
              # highlighter, then later restored in rendered form, so they are not
         | 
| 986 | 
            -
              # incorrectly processed by the source highlighter.
         | 
| 987 | 
            -
              #
         | 
| 988 | 
            -
              # source - the source code String to highlight
         | 
| 989 | 
            -
              # sub_callouts - a Boolean flag indicating whether callout marks should be substituted
         | 
| 990 | 
            -
              #
         | 
| 991 | 
            -
              # returns the highlighted source code, if a source highlighter is defined
         | 
| 992 | 
            -
              # on the document, otherwise the unprocessed text
         | 
| 993 | 
            -
              def highlight_source(source, sub_callouts, highlighter = nil)
         | 
| 994 | 
            -
                highlighter ||= @document.attributes['source-highlighter']
         | 
| 995 | 
            -
                Helpers.require_library highlighter, (highlighter == 'pygments' ? 'pygments.rb' : highlighter)
         | 
| 996 | 
            -
                callout_marks = {}
         | 
| 997 | 
            -
                lineno = 0
         | 
| 998 | 
            -
                callout_on_last = false
         | 
| 999 | 
            -
                if sub_callouts
         | 
| 1000 | 
            -
                  last = -1
         | 
| 1001 | 
            -
                  # extract callout marks, indexed by line number
         | 
| 1002 | 
            -
                  source = source.split(EOL).map {|line|
         | 
| 1003 | 
            -
                    lineno = lineno + 1
         | 
| 1004 | 
            -
                    line.gsub(REGEXP[:callout_scan]) {
         | 
| 1005 | 
            -
                      # alias match for Ruby 1.8.7 compat
         | 
| 1006 | 
            -
                      m = $~
         | 
| 1007 | 
            -
                      # honor the escape
         | 
| 1008 | 
            -
                      if m[1] == '\\'
         | 
| 1009 | 
            -
                        m[0].sub('\\', '')
         | 
| 1010 | 
            -
                      else
         | 
| 1011 | 
            -
                        (callout_marks[lineno] ||= []) << m[3]
         | 
| 1012 | 
            -
                        last = lineno
         | 
| 1013 | 
            -
                        nil
         | 
| 1014 | 
            -
                      end
         | 
| 1015 | 
            -
                    }
         | 
| 1016 | 
            -
                  } * EOL
         | 
| 1017 | 
            -
                  callout_on_last = (last == lineno)
         | 
| 1018 | 
            -
                end
         | 
| 1019 | 
            -
             | 
| 1020 | 
            -
                linenums_mode = nil
         | 
| 1021 | 
            -
             | 
| 1022 | 
            -
                case highlighter
         | 
| 1023 | 
            -
                  when 'coderay'
         | 
| 1024 | 
            -
                    result = ::CodeRay::Duo[attr('language', 'text').to_sym, :html, {
         | 
| 1025 | 
            -
                        :css => @document.attributes.fetch('coderay-css', 'class').to_sym,
         | 
| 1026 | 
            -
                        :line_numbers => (linenums_mode = (attr?('linenums') ? @document.attributes.fetch('coderay-linenums-mode', 'table').to_sym : nil)),
         | 
| 1027 | 
            -
                        :line_number_anchors => false}].highlight(source)
         | 
| 1028 | 
            -
                  when 'pygments'
         | 
| 1029 | 
            -
                    lexer = ::Pygments::Lexer[attr('language')]
         | 
| 1030 | 
            -
                    if lexer
         | 
| 1031 | 
            -
                      opts = { :cssclass => 'pyhl', :classprefix => 'tok-', :nobackground => true }
         | 
| 1032 | 
            -
                      opts[:noclasses] = true unless @document.attributes.fetch('pygments-css', 'class') == 'class'
         | 
| 1033 | 
            -
                      if attr? 'linenums'
         | 
| 1034 | 
            -
                        opts[:linenos] = (linenums_mode = @document.attributes.fetch('pygments-linenums-mode', 'table').to_sym).to_s
         | 
| 1035 | 
            -
                      end
         | 
| 1036 | 
            -
             | 
| 1037 | 
            -
                      # FIXME stick these regexs into constants
         | 
| 1038 | 
            -
                      if linenums_mode == :table
         | 
| 1039 | 
            -
                        result = lexer.highlight(source, :options => opts).
         | 
| 1040 | 
            -
                            sub(/<div class="pyhl">(.*)<\/div>/m, '\1').
         | 
| 1041 | 
            -
                            gsub(/<pre[^>]*>(.*?)<\/pre>\s*/m, '\1')
         | 
| 1042 | 
            -
                      else
         | 
| 1043 | 
            -
                        result = lexer.highlight(source, :options => opts).
         | 
| 1044 | 
            -
                            sub(/<div class="pyhl"><pre[^>]*>(.*?)<\/pre><\/div>/m, '\1')
         | 
| 1045 | 
            -
                      end
         | 
| 1046 | 
            -
                    else
         | 
| 1047 | 
            -
                      result = source
         | 
| 1048 | 
            -
                    end
         | 
| 1049 | 
            -
                end
         | 
| 1050 | 
            -
             | 
| 1051 | 
            -
                if !sub_callouts || callout_marks.empty?
         | 
| 1052 | 
            -
                  result
         | 
| 1053 | 
            -
                else
         | 
| 1054 | 
            -
                  lineno = 0
         | 
| 1055 | 
            -
                  reached_code = linenums_mode != :table
         | 
| 1056 | 
            -
                  result.split(EOL).map {|line|
         | 
| 1057 | 
            -
                    unless reached_code
         | 
| 1058 | 
            -
                      unless line.include?('<td class="code">')
         | 
| 1059 | 
            -
                        next line
         | 
| 1060 | 
            -
                      end
         | 
| 1061 | 
            -
                      reached_code = true
         | 
| 1062 | 
            -
                    end
         | 
| 1063 | 
            -
                    lineno = lineno + 1
         | 
| 1064 | 
            -
                    if (conums = callout_marks.delete(lineno))
         | 
| 1065 | 
            -
                      tail = nil
         | 
| 1066 | 
            -
                      if callout_on_last && callout_marks.empty? && (pos = line.index '</pre>')
         | 
| 1067 | 
            -
                        tail = line[pos..-1]
         | 
| 1068 | 
            -
                        line = line[0...pos]
         | 
| 1069 | 
            -
                      end
         | 
| 1070 | 
            -
                      if conums.size == 1
         | 
| 1071 | 
            -
                        %(#{line}#{Inline.new(self, :callout, conums.first, :id => @document.callouts.read_next_id).render }#{tail})
         | 
| 1072 | 
            -
                      else
         | 
| 1073 | 
            -
                        conums_markup = conums.map {|conum| Inline.new(self, :callout, conum, :id => @document.callouts.read_next_id).render } * ' '
         | 
| 1074 | 
            -
                        %(#{line}#{conums_markup}#{tail})
         | 
| 1075 | 
            -
                      end
         | 
| 1076 | 
            -
                    else
         | 
| 1077 | 
            -
                      line
         | 
| 1078 | 
            -
                    end
         | 
| 1079 | 
            -
                  } * EOL
         | 
| 1080 | 
            -
                end
         | 
| 1081 | 
            -
              end
         | 
| 1082 | 
            -
            end
         | 
| 1083 | 
            -
            end
         |