asciidoctor 1.5.6.2 → 1.5.7
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 +330 -143
- data/README-fr.adoc +441 -0
- data/README-jp.adoc +418 -0
- data/README-zh_CN.adoc +430 -0
- data/README.adoc +454 -0
- data/Rakefile +57 -0
- data/asciidoctor.gemspec +7 -1
- data/data/locale/attributes-ar.adoc +22 -0
- data/data/locale/attributes-bg.adoc +22 -0
- data/data/locale/attributes-ca.adoc +22 -0
- data/data/locale/attributes-cs.adoc +22 -0
- data/data/locale/attributes-da.adoc +22 -0
- data/data/locale/attributes-de.adoc +22 -0
- data/data/locale/attributes-en.adoc +23 -0
- data/data/locale/attributes-es.adoc +22 -0
- data/data/locale/attributes-fa.adoc +22 -0
- data/data/locale/attributes-fi.adoc +22 -0
- data/data/locale/attributes-fr.adoc +22 -0
- data/data/locale/attributes-hu.adoc +22 -0
- data/data/locale/attributes-id.adoc +22 -0
- data/data/locale/attributes-it.adoc +22 -0
- data/data/locale/attributes-ja.adoc +22 -0
- data/data/locale/attributes-kr.adoc +22 -0
- data/data/locale/attributes-nb.adoc +22 -0
- data/data/locale/attributes-nl.adoc +22 -0
- data/data/locale/attributes-nn.adoc +22 -0
- data/data/locale/attributes-pl.adoc +22 -0
- data/data/locale/attributes-pt.adoc +22 -0
- data/data/locale/attributes-pt_BR.adoc +22 -0
- data/data/locale/attributes-ro.adoc +22 -0
- data/data/locale/attributes-ru.adoc +22 -0
- data/data/locale/attributes-sr.adoc +22 -0
- data/data/locale/attributes-sr_Latn.adoc +22 -0
- data/data/locale/attributes-tr.adoc +22 -0
- data/data/locale/attributes-uk.adoc +22 -0
- data/data/locale/attributes-zh_CN.adoc +22 -0
- data/data/locale/attributes-zh_TW.adoc +22 -0
- data/data/locale/attributes.adoc +8 -649
- data/data/stylesheets/asciidoctor-default.css +77 -72
- data/features/xref.feature +366 -7
- data/lib/asciidoctor.rb +107 -93
- data/lib/asciidoctor/abstract_block.rb +247 -239
- data/lib/asciidoctor/abstract_node.rb +56 -58
- data/lib/asciidoctor/block.rb +3 -3
- data/lib/asciidoctor/callouts.rb +1 -1
- data/lib/asciidoctor/cli/invoker.rb +36 -9
- data/lib/asciidoctor/cli/options.rb +63 -25
- data/lib/asciidoctor/converter.rb +23 -13
- data/lib/asciidoctor/converter/base.rb +4 -0
- data/lib/asciidoctor/converter/docbook45.rb +16 -9
- data/lib/asciidoctor/converter/docbook5.rb +115 -97
- data/lib/asciidoctor/converter/factory.rb +29 -31
- data/lib/asciidoctor/converter/html5.rb +229 -192
- data/lib/asciidoctor/converter/manpage.rb +72 -50
- data/lib/asciidoctor/converter/template.rb +12 -12
- data/lib/asciidoctor/core_ext.rb +5 -1
- data/lib/asciidoctor/core_ext/1.8.7/base64/strict_encode64.rb +6 -0
- data/lib/asciidoctor/document.rb +168 -77
- data/lib/asciidoctor/extensions.rb +79 -47
- data/lib/asciidoctor/helpers.rb +33 -11
- data/lib/asciidoctor/inline.rb +3 -2
- data/lib/asciidoctor/list.rb +2 -1
- data/lib/asciidoctor/logging.rb +122 -0
- data/lib/asciidoctor/parser.rb +406 -382
- data/lib/asciidoctor/path_resolver.rb +169 -162
- data/lib/asciidoctor/reader.rb +166 -121
- data/lib/asciidoctor/section.rb +45 -28
- data/lib/asciidoctor/stylesheets.rb +13 -5
- data/lib/asciidoctor/substitutors.rb +328 -254
- data/lib/asciidoctor/table.rb +105 -48
- data/lib/asciidoctor/timings.rb +34 -6
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +41 -23
- data/man/asciidoctor.adoc +14 -8
- data/test/api_test.rb +1004 -0
- data/test/attributes_test.rb +241 -50
- data/test/blocks_test.rb +549 -124
- data/test/converter_test.rb +170 -78
- data/test/document_test.rb +208 -767
- data/test/extensions_test.rb +188 -53
- data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +1 -1
- data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +1 -1
- data/test/fixtures/file-with-missing-include.adoc +1 -0
- data/test/fixtures/include-file.jsx +8 -0
- data/test/fixtures/lists.adoc +96 -0
- data/test/fixtures/other-chapters.adoc +11 -0
- data/test/fixtures/outer-include.adoc +5 -0
- data/test/fixtures/sample.asciidoc +5 -1
- data/test/fixtures/subdir/index.adoc +3 -0
- data/test/fixtures/subdir/inner-include.adoc +3 -0
- data/test/fixtures/subdir/middle-include.adoc +5 -0
- data/test/fixtures/tagged-class-enclosed.rb +0 -1
- data/test/fixtures/unclosed-tag.adoc +3 -0
- data/test/fixtures/unexpected-end-tag.adoc +4 -0
- data/test/invoker_test.rb +101 -40
- data/test/links_test.rb +266 -72
- data/test/lists_test.rb +243 -45
- data/test/logger_test.rb +211 -0
- data/test/manpage_test.rb +124 -6
- data/test/options_test.rb +46 -1
- data/test/paragraphs_test.rb +23 -10
- data/test/parser_test.rb +30 -1
- data/test/paths_test.rb +115 -33
- data/test/preamble_test.rb +1 -1
- data/test/reader_test.rb +337 -81
- data/test/sections_test.rb +656 -72
- data/test/substitutions_test.rb +182 -57
- data/test/tables_test.rb +324 -57
- data/test/test_helper.rb +77 -32
- data/test/text_test.rb +7 -7
- metadata +67 -3
    
        data/lib/asciidoctor/section.rb
    CHANGED
    
    | @@ -38,14 +38,19 @@ class Section < AbstractBlock | |
| 38 38 |  | 
| 39 39 | 
             
              # Public: Initialize an Asciidoctor::Section object.
         | 
| 40 40 | 
             
              #
         | 
| 41 | 
            -
              # parent | 
| 42 | 
            -
               | 
| 41 | 
            +
              # parent   - The parent AbstractBlock. If set, must be a Document or Section object (default: nil)
         | 
| 42 | 
            +
              # level    - The Integer level of this section (default: 1 more than parent level or 1 if parent not defined)
         | 
| 43 | 
            +
              # numbered - A Boolean indicating whether numbering is enabled for this Section (default: false)
         | 
| 44 | 
            +
              # opts     - An optional Hash of options (default: {})
         | 
| 45 | 
            +
              def initialize parent = nil, level = nil, numbered = false, opts = {}
         | 
| 43 46 | 
             
                super parent, :section, opts
         | 
| 44 | 
            -
                 | 
| 45 | 
            -
             | 
| 46 | 
            -
                 | 
| 47 | 
            +
                if Section === parent
         | 
| 48 | 
            +
                  @level, @special = level || (parent.level + 1), parent.special
         | 
| 49 | 
            +
                else
         | 
| 50 | 
            +
                  @level, @special = level || 1, false
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
                @numbered = numbered
         | 
| 47 53 | 
             
                @index = 0
         | 
| 48 | 
            -
                @number = 1
         | 
| 49 54 | 
             
              end
         | 
| 50 55 |  | 
| 51 56 | 
             
              # Public: The name of this section, an alias of the section title
         | 
| @@ -60,10 +65,12 @@ class Section < AbstractBlock | |
| 60 65 |  | 
| 61 66 | 
             
              # Public: Get the section number for the current Section
         | 
| 62 67 | 
             
              #
         | 
| 63 | 
            -
              # The section number is a  | 
| 64 | 
            -
              #  | 
| 65 | 
            -
              # the  | 
| 66 | 
            -
              # | 
| 68 | 
            +
              # The section number is a dot-separated String that uniquely describes the position of this
         | 
| 69 | 
            +
              # Section in the document. Each entry represents a level of nesting. The value of each entry is
         | 
| 70 | 
            +
              # the 1-based outline number of the Section amongst its numbered sibling Sections.
         | 
| 71 | 
            +
              #
         | 
| 72 | 
            +
              # This method assumes that both the @level and @parent instance variables have been assigned.
         | 
| 73 | 
            +
              # The method also assumes that the value of @parent is either a Document or Section.
         | 
| 67 74 | 
             
              #
         | 
| 68 75 | 
             
              # delimiter - the delimiter to separate the number for each level
         | 
| 69 76 | 
             
              # append    - the String to append at the end of the section number
         | 
| @@ -103,10 +110,12 @@ class Section < AbstractBlock | |
| 103 110 | 
             
              # Returns the section number as a String
         | 
| 104 111 | 
             
              def sectnum(delimiter = '.', append = nil)
         | 
| 105 112 | 
             
                append ||= (append == false ? '' : delimiter)
         | 
| 106 | 
            -
                if @level  | 
| 107 | 
            -
                  %(#{@parent.sectnum(delimiter)}#{@number}#{append})
         | 
| 108 | 
            -
                else
         | 
| 113 | 
            +
                if @level == 1
         | 
| 109 114 | 
             
                  %(#{@number}#{append})
         | 
| 115 | 
            +
                elsif @level > 1
         | 
| 116 | 
            +
                  Section === @parent ? %(#{@parent.sectnum(delimiter)}#{@number}#{append}) : %(#{@number}#{append})
         | 
| 117 | 
            +
                else # @level == 0
         | 
| 118 | 
            +
                  %(#{Helpers.int_to_roman @number}#{append})
         | 
| 110 119 | 
             
                end
         | 
| 111 120 | 
             
              end
         | 
| 112 121 |  | 
| @@ -153,7 +162,7 @@ class Section < AbstractBlock | |
| 153 162 | 
             
              #
         | 
| 154 163 | 
             
              # Returns The parent Block
         | 
| 155 164 | 
             
              def << block
         | 
| 156 | 
            -
                 | 
| 165 | 
            +
                assign_numeral block if block.context == :section
         | 
| 157 166 | 
             
                super
         | 
| 158 167 | 
             
              end
         | 
| 159 168 |  | 
| @@ -169,32 +178,40 @@ class Section < AbstractBlock | |
| 169 178 | 
             
              # Public: Generate a String ID from the given section title.
         | 
| 170 179 | 
             
              #
         | 
| 171 180 | 
             
              # The generated ID is prefixed with value of the 'idprefix' attribute, which
         | 
| 172 | 
            -
              # is an underscore by default. Invalid characters are  | 
| 173 | 
            -
              # value of the 'idseparator' attribute, which is | 
| 181 | 
            +
              # is an underscore (_) by default. Invalid characters are then removed and
         | 
| 182 | 
            +
              # spaces are replaced with the value of the 'idseparator' attribute, which is
         | 
| 183 | 
            +
              # an underscore (_) by default.
         | 
| 174 184 | 
             
              #
         | 
| 175 | 
            -
              # If the generated ID is already in use in the document, a count is appended
         | 
| 176 | 
            -
              # until a unique  | 
| 185 | 
            +
              # If the generated ID is already in use in the document, a count is appended,
         | 
| 186 | 
            +
              # offset by the separator, until a unique ID is found.
         | 
| 177 187 | 
             
              #
         | 
| 178 | 
            -
              # Section ID generation can be disabled by  | 
| 188 | 
            +
              # Section ID generation can be disabled by unsetting the 'sectids' document attribute.
         | 
| 179 189 | 
             
              #
         | 
| 180 190 | 
             
              # Examples
         | 
| 181 191 | 
             
              #
         | 
| 182 192 | 
             
              #   Section.generate_id 'Foo', document
         | 
| 183 193 | 
             
              #   => "_foo"
         | 
| 184 194 | 
             
              #
         | 
| 195 | 
            +
              # Returns the generated [String] ID.
         | 
| 185 196 | 
             
              def self.generate_id title, document
         | 
| 186 197 | 
             
                attrs = document.attributes
         | 
| 187 | 
            -
                sep = attrs['idseparator'] || '_'
         | 
| 188 198 | 
             
                pre = attrs['idprefix'] || '_'
         | 
| 189 | 
            -
                 | 
| 190 | 
            -
             | 
| 191 | 
            -
             | 
| 192 | 
            -
                  gen_id = gen_id.tr_s sep, sep
         | 
| 193 | 
            -
                  gen_id = gen_id.chop if gen_id.end_with? sep
         | 
| 194 | 
            -
                  # ensure id doesn't begin with idseparator if idprefix is empty and idseparator is not empty
         | 
| 195 | 
            -
                  if pre.empty?
         | 
| 196 | 
            -
                    gen_id = gen_id.slice 1, gen_id.length while gen_id.start_with? sep
         | 
| 199 | 
            +
                if (sep = attrs['idseparator'])
         | 
| 200 | 
            +
                  if sep.length == 1 || (!(no_sep = sep.empty?) && (sep = attrs['idseparator'] = sep.chr))
         | 
| 201 | 
            +
                    sep_sub = sep == '-' || sep == '.' ? ' .-' : %( #{sep}.-)
         | 
| 197 202 | 
             
                  end
         | 
| 203 | 
            +
                else
         | 
| 204 | 
            +
                  sep, sep_sub = '_', ' _.-'
         | 
| 205 | 
            +
                end
         | 
| 206 | 
            +
                gen_id = %(#{pre}#{title.downcase.gsub InvalidSectionIdCharsRx, ''})
         | 
| 207 | 
            +
                if no_sep
         | 
| 208 | 
            +
                  gen_id = gen_id.delete ' '
         | 
| 209 | 
            +
                else
         | 
| 210 | 
            +
                  # replace space with separator and remove repeating and trailing separator characters
         | 
| 211 | 
            +
                  gen_id = gen_id.tr_s sep_sub, sep
         | 
| 212 | 
            +
                  gen_id = gen_id.chop if gen_id.end_with? sep
         | 
| 213 | 
            +
                  # ensure id doesn't begin with idseparator if idprefix is empty (assuming idseparator is not empty)
         | 
| 214 | 
            +
                  gen_id = gen_id.slice 1, gen_id.length if pre.empty? && (gen_id.start_with? sep)
         | 
| 198 215 | 
             
                end
         | 
| 199 216 | 
             
                if document.catalog[:ids].key? gen_id
         | 
| 200 217 | 
             
                  ids, cnt = document.catalog[:ids], Compliance.unique_id_start_index
         | 
| @@ -8,6 +8,7 @@ class Stylesheets | |
| 8 8 | 
             
              DEFAULT_STYLESHEET_NAME = 'asciidoctor.css'
         | 
| 9 9 | 
             
              DEFAULT_PYGMENTS_STYLE = 'default'
         | 
| 10 10 | 
             
              STYLESHEETS_DATA_PATH = ::File.join DATA_PATH, 'stylesheets'
         | 
| 11 | 
            +
              PygmentsBgColorRx = /^\.pygments +\{ *background: *([^;]+);/
         | 
| 11 12 |  | 
| 12 13 | 
             
              @__instance__ = new
         | 
| 13 14 |  | 
| @@ -23,7 +24,7 @@ class Stylesheets | |
| 23 24 | 
             
              #
         | 
| 24 25 | 
             
              # returns the [String] Asciidoctor stylesheet data
         | 
| 25 26 | 
             
              def primary_stylesheet_data
         | 
| 26 | 
            -
                @primary_stylesheet_data ||= ::IO.read(::File.join(STYLESHEETS_DATA_PATH, 'asciidoctor-default.css')). | 
| 27 | 
            +
                @primary_stylesheet_data ||= ::IO.read(::File.join(STYLESHEETS_DATA_PATH, 'asciidoctor-default.css')).rstrip
         | 
| 27 28 | 
             
              end
         | 
| 28 29 |  | 
| 29 30 | 
             
              def embed_primary_stylesheet
         | 
| @@ -48,7 +49,7 @@ class Stylesheets | |
| 48 49 | 
             
                # unless load_coderay.nil?
         | 
| 49 50 | 
             
                #   ::CodeRay::Encoders[:html]::CSS.new(:default).stylesheet
         | 
| 50 51 | 
             
                # end
         | 
| 51 | 
            -
                @coderay_stylesheet_data ||= ::IO.read(::File.join(STYLESHEETS_DATA_PATH, 'coderay-asciidoctor.css')). | 
| 52 | 
            +
                @coderay_stylesheet_data ||= ::IO.read(::File.join(STYLESHEETS_DATA_PATH, 'coderay-asciidoctor.css')).rstrip
         | 
| 52 53 | 
             
              end
         | 
| 53 54 |  | 
| 54 55 | 
             
              def embed_coderay_stylesheet
         | 
| @@ -65,16 +66,23 @@ class Stylesheets | |
| 65 66 | 
             
                %(pygments-#{style || DEFAULT_PYGMENTS_STYLE}.css)
         | 
| 66 67 | 
             
              end
         | 
| 67 68 |  | 
| 69 | 
            +
              def pygments_background style = nil
         | 
| 70 | 
            +
                if load_pygments && PygmentsBgColorRx =~ (::Pygments.css '.pygments', :style => style || DEFAULT_PYGMENTS_STYLE)
         | 
| 71 | 
            +
                  $1
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
              end
         | 
| 74 | 
            +
             | 
| 68 75 | 
             
              # Public: Generate the Pygments stylesheet with the specified style.
         | 
| 69 76 | 
             
              #
         | 
| 70 77 | 
             
              # returns the [String] Pygments stylesheet data
         | 
| 71 78 | 
             
              def pygments_stylesheet_data style = nil
         | 
| 72 79 | 
             
                if load_pygments
         | 
| 73 | 
            -
                   | 
| 74 | 
            -
             | 
| 80 | 
            +
                  style ||= DEFAULT_PYGMENTS_STYLE
         | 
| 81 | 
            +
                  (@pygments_stylesheet_data ||= {})[style] ||=
         | 
| 82 | 
            +
                      ((::Pygments.css '.listingblock .pygments', :classprefix => 'tok-', :style => style) || '/* Failed to load Pygments CSS. */').
         | 
| 75 83 | 
             
                      sub('.listingblock .pygments  {', '.listingblock .pygments, .listingblock .pygments code {')
         | 
| 76 84 | 
             
                else
         | 
| 77 | 
            -
                  '/* Pygments  | 
| 85 | 
            +
                  '/* Pygments CSS disabled. Pygments is not available. */'
         | 
| 78 86 | 
             
                end
         | 
| 79 87 | 
             
              end
         | 
| 80 88 |  | 
| @@ -43,14 +43,24 @@ module Substitutors | |
| 43 43 |  | 
| 44 44 | 
             
              SUB_HIGHLIGHT = ['coderay', 'pygments']
         | 
| 45 45 |  | 
| 46 | 
            -
               | 
| 47 | 
            -
             | 
| 46 | 
            +
              if ::RUBY_MIN_VERSION_1_9
         | 
| 47 | 
            +
                CAN = %(\u0018)
         | 
| 48 | 
            +
                DEL = %(\u007f)
         | 
| 48 49 |  | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 50 | 
            +
                # Delimiters and matchers for the passthrough placeholder
         | 
| 51 | 
            +
                # See http://www.aivosto.com/vbtips/control-characters.html#listabout for characters to use
         | 
| 51 52 |  | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 53 | 
            +
                # SPA, start of guarded protected area (\u0096)
         | 
| 54 | 
            +
                PASS_START = %(\u0096)
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                # EPA, end of guarded protected area (\u0097)
         | 
| 57 | 
            +
                PASS_END = %(\u0097)
         | 
| 58 | 
            +
              else
         | 
| 59 | 
            +
                CAN = 24.chr
         | 
| 60 | 
            +
                DEL = 127.chr
         | 
| 61 | 
            +
                PASS_START = 150.chr
         | 
| 62 | 
            +
                PASS_END = 151.chr
         | 
| 63 | 
            +
              end
         | 
| 54 64 |  | 
| 55 65 | 
             
              # match passthrough slot
         | 
| 56 66 | 
             
              PassSlotRx = /#{PASS_START}(\d+)#{PASS_END}/
         | 
| @@ -74,39 +84,20 @@ module Substitutors | |
| 74 84 | 
             
              # Internal: A String Array of passthough (unprocessed) text captured from this block
         | 
| 75 85 | 
             
              attr_reader :passthroughs
         | 
| 76 86 |  | 
| 77 | 
            -
              # Public: Apply the specified substitutions to the  | 
| 87 | 
            +
              # Public: Apply the specified substitutions to the text.
         | 
| 78 88 | 
             
              #
         | 
| 79 | 
            -
              #  | 
| 80 | 
            -
              # subs | 
| 81 | 
            -
              # expand  - A Boolean (or nil) to control whether substitution aliases are expanded (default: nil).
         | 
| 89 | 
            +
              # text  - The String or String Array of text to process; must not be nil.
         | 
| 90 | 
            +
              # subs  - The substitutions to perform; must be a Symbol Array or nil (default: NORMAL_SUBS).
         | 
| 82 91 | 
             
              #
         | 
| 83 | 
            -
              # Returns a String or String Array  | 
| 84 | 
            -
              def apply_subs  | 
| 85 | 
            -
                if  | 
| 86 | 
            -
                  return source
         | 
| 87 | 
            -
                elsif expand
         | 
| 88 | 
            -
                  if ::Symbol === subs
         | 
| 89 | 
            -
                    subs = SUB_GROUPS[subs] || [subs]
         | 
| 90 | 
            -
                  else
         | 
| 91 | 
            -
                    effective_subs = []
         | 
| 92 | 
            -
                    subs.each do |key|
         | 
| 93 | 
            -
                      if (sub_group = SUB_GROUPS[key])
         | 
| 94 | 
            -
                        effective_subs += sub_group unless sub_group.empty?
         | 
| 95 | 
            -
                      else
         | 
| 96 | 
            -
                        effective_subs << key
         | 
| 97 | 
            -
                      end
         | 
| 98 | 
            -
                    end
         | 
| 92 | 
            +
              # Returns a String or String Array to match the type of the text argument with substitutions applied.
         | 
| 93 | 
            +
              def apply_subs text, subs = NORMAL_SUBS
         | 
| 94 | 
            +
                return text if text.empty? || !subs
         | 
| 99 95 |  | 
| 100 | 
            -
             | 
| 101 | 
            -
             | 
| 102 | 
            -
             | 
| 103 | 
            -
                  end
         | 
| 104 | 
            -
                elsif subs.empty?
         | 
| 105 | 
            -
                  return source
         | 
| 96 | 
            +
                if (multiline = ::Array === text)
         | 
| 97 | 
            +
                  #text = text.size > 1 ? (text.join LF) : text[0]
         | 
| 98 | 
            +
                  text = text[1] ? (text.join LF) : text[0]
         | 
| 106 99 | 
             
                end
         | 
| 107 100 |  | 
| 108 | 
            -
                text = (multiline = ::Array === source) ? source * LF : source
         | 
| 109 | 
            -
             | 
| 110 101 | 
             
                if (has_passthroughs = subs.include? :macros)
         | 
| 111 102 | 
             
                  text = extract_passthroughs text
         | 
| 112 103 | 
             
                  has_passthroughs = false if @passthroughs.empty?
         | 
| @@ -119,7 +110,7 @@ module Substitutors | |
| 119 110 | 
             
                  when :quotes
         | 
| 120 111 | 
             
                    text = sub_quotes text
         | 
| 121 112 | 
             
                  when :attributes
         | 
| 122 | 
            -
                    text = sub_attributes | 
| 113 | 
            +
                    text = sub_attributes text if text.include? ATTR_REF_HEAD
         | 
| 123 114 | 
             
                  when :replacements
         | 
| 124 115 | 
             
                    text = sub_replacements text
         | 
| 125 116 | 
             
                  when :macros
         | 
| @@ -131,7 +122,7 @@ module Substitutors | |
| 131 122 | 
             
                  when :post_replacements
         | 
| 132 123 | 
             
                    text = sub_post_replacements text
         | 
| 133 124 | 
             
                  else
         | 
| 134 | 
            -
                    warn %( | 
| 125 | 
            +
                    logger.warn %(unknown substitution type #{type})
         | 
| 135 126 | 
             
                  end
         | 
| 136 127 | 
             
                end
         | 
| 137 128 | 
             
                text = restore_passthroughs text if has_passthroughs
         | 
| @@ -238,13 +229,13 @@ module Substitutors | |
| 238 229 | 
             
                      next m[0][1..-1]
         | 
| 239 230 | 
             
                    end
         | 
| 240 231 |  | 
| 241 | 
            -
                    @passthroughs[pass_key = @passthroughs.size] = {:text => (unescape_brackets m[8]), :subs => (m[7] ? (resolve_pass_subs m[7]) :  | 
| 232 | 
            +
                    @passthroughs[pass_key = @passthroughs.size] = {:text => (unescape_brackets m[8]), :subs => (m[7] ? (resolve_pass_subs m[7]) : nil)}
         | 
| 242 233 | 
             
                  end
         | 
| 243 234 |  | 
| 244 235 | 
             
                  %(#{preceding}#{PASS_START}#{pass_key}#{PASS_END})
         | 
| 245 236 | 
             
                } if (text.include? '++') || (text.include? '$$') || (text.include? 'ss:')
         | 
| 246 237 |  | 
| 247 | 
            -
                pass_inline_char1, pass_inline_char2, pass_inline_rx =  | 
| 238 | 
            +
                pass_inline_char1, pass_inline_char2, pass_inline_rx = InlinePassRx[compat_mode]
         | 
| 248 239 | 
             
                text = text.gsub(pass_inline_rx) {
         | 
| 249 240 | 
             
                  # alias match for Ruby 1.8.7 compat
         | 
| 250 241 | 
             
                  m = $~
         | 
| @@ -264,7 +255,8 @@ module Substitutors | |
| 264 255 |  | 
| 265 256 | 
             
                  if attributes
         | 
| 266 257 | 
             
                    if format_mark == '`' && !old_behavior
         | 
| 267 | 
            -
                       | 
| 258 | 
            +
                      # extract nested single-plus passthrough; otherwise return unprocessed
         | 
| 259 | 
            +
                      next (extract_inner_passthrough content, %(#{preceding}[#{attributes}]#{escape_mark}), attributes)
         | 
| 268 260 | 
             
                    end
         | 
| 269 261 |  | 
| 270 262 | 
             
                    if escape_mark
         | 
| @@ -278,7 +270,8 @@ module Substitutors | |
| 278 270 | 
             
                      attributes = parse_attributes attributes
         | 
| 279 271 | 
             
                    end
         | 
| 280 272 | 
             
                  elsif format_mark == '`' && !old_behavior
         | 
| 281 | 
            -
                     | 
| 273 | 
            +
                    # extract nested single-plus passthrough; otherwise return unprocessed
         | 
| 274 | 
            +
                    next (extract_inner_passthrough content, %(#{preceding}#{escape_mark}))
         | 
| 282 275 | 
             
                  elsif escape_mark
         | 
| 283 276 | 
             
                    # honor the escape of the formatting mark
         | 
| 284 277 | 
             
                    next %(#{preceding}#{m[3][1..-1]})
         | 
| @@ -311,10 +304,10 @@ module Substitutors | |
| 311 304 | 
             
                  end
         | 
| 312 305 |  | 
| 313 306 | 
             
                  if (type = m[1].to_sym) == :stem
         | 
| 314 | 
            -
                    type =  | 
| 307 | 
            +
                    type = STEM_TYPE_ALIASES[@document.attributes['stem']].to_sym
         | 
| 315 308 | 
             
                  end
         | 
| 316 309 | 
             
                  content = unescape_brackets m[3]
         | 
| 317 | 
            -
                  subs = m[2] ? (resolve_pass_subs m[2]) : ((@document.basebackend? 'html') ? BASIC_SUBS :  | 
| 310 | 
            +
                  subs = m[2] ? (resolve_pass_subs m[2]) : ((@document.basebackend? 'html') ? BASIC_SUBS : nil)
         | 
| 318 311 | 
             
                  @passthroughs[pass_key = @passthroughs.size] = {:text => content, :subs => subs, :type => type}
         | 
| 319 312 | 
             
                  %(#{PASS_START}#{pass_key}#{PASS_END})
         | 
| 320 313 | 
             
                } if (text.include? ':') && ((text.include? 'stem:') || (text.include? 'math:'))
         | 
| @@ -322,6 +315,21 @@ module Substitutors | |
| 322 315 | 
             
                text
         | 
| 323 316 | 
             
              end
         | 
| 324 317 |  | 
| 318 | 
            +
              def extract_inner_passthrough text, pre, attributes = nil
         | 
| 319 | 
            +
                if (text.end_with? '+') && (text.start_with? '+', '\+') && SinglePlusInlinePassRx =~ text
         | 
| 320 | 
            +
                  if $1
         | 
| 321 | 
            +
                    %(#{pre}`+#{$2}+`)
         | 
| 322 | 
            +
                  else
         | 
| 323 | 
            +
                    @passthroughs[pass_key = @passthroughs.size] = attributes ?
         | 
| 324 | 
            +
                        { :text => $2, :subs => BASIC_SUBS, :attributes => attributes, :type => :unquoted } :
         | 
| 325 | 
            +
                        { :text => $2, :subs => BASIC_SUBS }
         | 
| 326 | 
            +
                    %(#{pre}`#{PASS_START}#{pass_key}#{PASS_END}`)
         | 
| 327 | 
            +
                  end
         | 
| 328 | 
            +
                else
         | 
| 329 | 
            +
                  %(#{pre}`#{text}`)
         | 
| 330 | 
            +
                end
         | 
| 331 | 
            +
              end
         | 
| 332 | 
            +
             | 
| 325 333 | 
             
              # Internal: Restore the passthrough text by reinserting into the placeholder positions
         | 
| 326 334 | 
             
              #
         | 
| 327 335 | 
             
              # text  - The String text into which to restore the passthrough text
         | 
| @@ -366,10 +374,6 @@ module Substitutors | |
| 366 374 | 
             
                  end
         | 
| 367 375 | 
             
                  text
         | 
| 368 376 | 
             
                end
         | 
| 369 | 
            -
             | 
| 370 | 
            -
                def sub_specialchars text
         | 
| 371 | 
            -
                  (text.include? '<') || (text.include? '&') || (text.include? '>') ? (text.gsub SpecialCharsRx, SpecialCharsTr) : text
         | 
| 372 | 
            -
                end
         | 
| 373 377 | 
             
              else
         | 
| 374 378 | 
             
                # Public: Substitute quoted text (includes emphasis, strong, monospaced, etc)
         | 
| 375 379 | 
             
                #
         | 
| @@ -404,23 +408,23 @@ module Substitutors | |
| 404 408 | 
             
                  end
         | 
| 405 409 | 
             
                  text
         | 
| 406 410 | 
             
                end
         | 
| 411 | 
            +
              end
         | 
| 407 412 |  | 
| 408 | 
            -
             | 
| 409 | 
            -
             | 
| 410 | 
            -
             | 
| 411 | 
            -
             | 
| 412 | 
            -
             | 
| 413 | 
            -
             | 
| 414 | 
            -
             | 
| 415 | 
            -
             | 
| 416 | 
            -
             | 
| 417 | 
            -
             | 
| 418 | 
            -
             | 
| 419 | 
            -
             | 
| 420 | 
            -
             | 
| 421 | 
            -
             | 
| 422 | 
            -
             | 
| 423 | 
            -
                  end
         | 
| 413 | 
            +
              # Public: Substitute special characters (i.e., encode XML)
         | 
| 414 | 
            +
              #
         | 
| 415 | 
            +
              # The special characters <, &, and > get replaced with <,
         | 
| 416 | 
            +
              # &, and >, respectively.
         | 
| 417 | 
            +
              #
         | 
| 418 | 
            +
              # text - The String text to process.
         | 
| 419 | 
            +
              #
         | 
| 420 | 
            +
              # returns The String text with special characters replaced.
         | 
| 421 | 
            +
              if ::RUBY_MIN_VERSION_1_9
         | 
| 422 | 
            +
                def sub_specialchars text
         | 
| 423 | 
            +
                  (text.include? '<') || (text.include? '&') || (text.include? '>') ? (text.gsub SpecialCharsRx, SpecialCharsTr) : text
         | 
| 424 | 
            +
                end
         | 
| 425 | 
            +
              else
         | 
| 426 | 
            +
                def sub_specialchars text
         | 
| 427 | 
            +
                  (text.include? '<') || (text.include? '&') || (text.include? '>') ? (text.gsub(SpecialCharsRx) { SpecialCharsTr[$&] }) : text
         | 
| 424 428 | 
             
                end
         | 
| 425 429 | 
             
              end
         | 
| 426 430 | 
             
              alias sub_specialcharacters sub_specialchars
         | 
| @@ -444,74 +448,78 @@ module Substitutors | |
| 444 448 | 
             
                end
         | 
| 445 449 | 
             
              end
         | 
| 446 450 |  | 
| 447 | 
            -
              # Public:  | 
| 451 | 
            +
              # Public: Substitutes attribute references in the specified text
         | 
| 448 452 | 
             
              #
         | 
| 449 453 | 
             
              # Attribute references are in the format +{name}+.
         | 
| 450 454 | 
             
              #
         | 
| 451 | 
            -
              # If an attribute referenced in the line is missing, the line  | 
| 455 | 
            +
              # If an attribute referenced in the line is missing or undefined, the line may be dropped
         | 
| 456 | 
            +
              # based on the attribute-missing or attribute-undefined setting, respectively.
         | 
| 452 457 | 
             
              #
         | 
| 453 | 
            -
              # text | 
| 458 | 
            +
              # text - The String text to process
         | 
| 459 | 
            +
              # opts - A Hash of options to control processing: (default: {})
         | 
| 460 | 
            +
              #        * :attribute_missing controls how to handle a missing attribute
         | 
| 454 461 | 
             
              #
         | 
| 455 | 
            -
              #  | 
| 456 | 
            -
               | 
| 457 | 
            -
             | 
| 458 | 
            -
             | 
| 459 | 
            -
             | 
| 460 | 
            -
             | 
| 461 | 
            -
             | 
| 462 | 
            -
             | 
| 463 | 
            -
             | 
| 464 | 
            -
             | 
| 465 | 
            -
             | 
| 466 | 
            -
             | 
| 467 | 
            -
             | 
| 468 | 
            -
             | 
| 469 | 
            -
             | 
| 470 | 
            -
             | 
| 471 | 
            -
             | 
| 472 | 
            -
                      when 'set'
         | 
| 473 | 
            -
                        _, value = Parser.store_attribute args[0], args[1] || '', @document
         | 
| 474 | 
            -
                        # since this is an assignment, only drop-line applies here (skip and drop imply the same result)
         | 
| 475 | 
            -
                        if (doc_attrs.fetch 'attribute-undefined', Compliance.attribute_undefined) == 'drop-line'
         | 
| 476 | 
            -
                          reject = true
         | 
| 477 | 
            -
                          break ''
         | 
| 478 | 
            -
                        end unless value
         | 
| 479 | 
            -
                        reject_if_empty = true
         | 
| 480 | 
            -
                        ''
         | 
| 481 | 
            -
                      when 'counter2'
         | 
| 482 | 
            -
                        @document.counter(*args)
         | 
| 483 | 
            -
                        reject_if_empty = true
         | 
| 484 | 
            -
                        ''
         | 
| 485 | 
            -
                      else # 'counter'
         | 
| 486 | 
            -
                        @document.counter(*args)
         | 
| 487 | 
            -
                      end
         | 
| 488 | 
            -
                    elsif doc_attrs.key?(key = $2.downcase)
         | 
| 489 | 
            -
                      doc_attrs[key]
         | 
| 490 | 
            -
                    elsif INTRINSIC_ATTRIBUTES.key? key
         | 
| 491 | 
            -
                      INTRINSIC_ATTRIBUTES[key]
         | 
| 492 | 
            -
                    else
         | 
| 493 | 
            -
                      case (attribute_missing ||= opts[:attribute_missing] || (doc_attrs.fetch 'attribute-missing', Compliance.attribute_missing))
         | 
| 494 | 
            -
                      when 'drop'
         | 
| 495 | 
            -
                        # QUESTION should we warn in this case?
         | 
| 496 | 
            -
                        reject_if_empty = true
         | 
| 497 | 
            -
                        ''
         | 
| 498 | 
            -
                      when 'drop-line'
         | 
| 499 | 
            -
                        warn %(asciidoctor: WARNING: dropping line containing reference to missing attribute: #{key})
         | 
| 500 | 
            -
                        reject = true
         | 
| 501 | 
            -
                        break ''
         | 
| 502 | 
            -
                      when 'warn'
         | 
| 503 | 
            -
                        warn %(asciidoctor: WARNING: skipping reference to missing attribute: #{key})
         | 
| 504 | 
            -
                        $&
         | 
| 505 | 
            -
                      else # 'skip'
         | 
| 506 | 
            -
                        $&
         | 
| 462 | 
            +
              # Returns the [String] text with the attribute references replaced with resolved values
         | 
| 463 | 
            +
              def sub_attributes text, opts = {}
         | 
| 464 | 
            +
                doc_attrs = @document.attributes
         | 
| 465 | 
            +
                drop = drop_line = drop_empty_line = attribute_undefined = attribute_missing = nil
         | 
| 466 | 
            +
                result = text.gsub AttributeReferenceRx do
         | 
| 467 | 
            +
                  # escaped attribute, return unescaped
         | 
| 468 | 
            +
                  if $1 == RS || $4 == RS
         | 
| 469 | 
            +
                    %({#{$2}})
         | 
| 470 | 
            +
                  elsif $3
         | 
| 471 | 
            +
                    case (args = $2.split ':', 3).shift
         | 
| 472 | 
            +
                    when 'set'
         | 
| 473 | 
            +
                      _, value = Parser.store_attribute args[0], args[1] || '', @document
         | 
| 474 | 
            +
                      # NOTE since this is an assignment, only drop-line applies here (skip and drop imply the same result)
         | 
| 475 | 
            +
                      if value || (attribute_undefined ||= doc_attrs['attribute-undefined'] || Compliance.attribute_undefined) != 'drop-line'
         | 
| 476 | 
            +
                        drop = drop_empty_line = DEL
         | 
| 477 | 
            +
                      else
         | 
| 478 | 
            +
                        drop = drop_line = CAN
         | 
| 507 479 | 
             
                      end
         | 
| 480 | 
            +
                    when 'counter2'
         | 
| 481 | 
            +
                      @document.counter(*args)
         | 
| 482 | 
            +
                      drop = drop_empty_line = DEL
         | 
| 483 | 
            +
                    else # 'counter'
         | 
| 484 | 
            +
                      @document.counter(*args)
         | 
| 508 485 | 
             
                    end
         | 
| 509 | 
            -
                   | 
| 510 | 
            -
             | 
| 511 | 
            -
                   | 
| 486 | 
            +
                  elsif doc_attrs.key?(key = $2.downcase)
         | 
| 487 | 
            +
                    doc_attrs[key]
         | 
| 488 | 
            +
                  elsif (value = INTRINSIC_ATTRIBUTES[key])
         | 
| 489 | 
            +
                    value
         | 
| 490 | 
            +
                  else
         | 
| 491 | 
            +
                    case (attribute_missing ||= opts[:attribute_missing] || doc_attrs['attribute-missing'] || Compliance.attribute_missing)
         | 
| 492 | 
            +
                    when 'drop'
         | 
| 493 | 
            +
                      drop = drop_empty_line = DEL
         | 
| 494 | 
            +
                    when 'drop-line'
         | 
| 495 | 
            +
                      logger.warn %(dropping line containing reference to missing attribute: #{key})
         | 
| 496 | 
            +
                      drop = drop_line = CAN
         | 
| 497 | 
            +
                    when 'warn'
         | 
| 498 | 
            +
                      logger.warn %(skipping reference to missing attribute: #{key})
         | 
| 499 | 
            +
                      $&
         | 
| 500 | 
            +
                    else # 'skip'
         | 
| 501 | 
            +
                      $&
         | 
| 502 | 
            +
                    end
         | 
| 503 | 
            +
                  end
         | 
| 512 504 | 
             
                end
         | 
| 513 505 |  | 
| 514 | 
            -
                 | 
| 506 | 
            +
                if drop
         | 
| 507 | 
            +
                  # drop lines from result
         | 
| 508 | 
            +
                  if drop_empty_line
         | 
| 509 | 
            +
                    lines = (result.tr_s DEL, DEL).split LF, -1
         | 
| 510 | 
            +
                    if drop_line
         | 
| 511 | 
            +
                      (lines.reject {|line| line == DEL || line == CAN || (line.start_with? CAN) || (line.include? CAN) }.join LF).delete DEL
         | 
| 512 | 
            +
                    else
         | 
| 513 | 
            +
                      (lines.reject {|line| line == DEL }.join LF).delete DEL
         | 
| 514 | 
            +
                    end
         | 
| 515 | 
            +
                  elsif result.include? LF
         | 
| 516 | 
            +
                    (result.split LF, -1).reject {|line| line == CAN || (line.start_with? CAN) || (line.include? CAN) }.join LF
         | 
| 517 | 
            +
                  else
         | 
| 518 | 
            +
                    ''
         | 
| 519 | 
            +
                  end
         | 
| 520 | 
            +
                else
         | 
| 521 | 
            +
                  result
         | 
| 522 | 
            +
                end
         | 
| 515 523 | 
             
              end
         | 
| 516 524 |  | 
| 517 525 | 
             
              # Public: Substitute inline macros (e.g., links, images, etc)
         | 
| @@ -529,8 +537,7 @@ module Substitutors | |
| 529 537 | 
             
                found_colon = source.include? ':'
         | 
| 530 538 | 
             
                found_macroish = found[:macroish] = found_square_bracket && found_colon
         | 
| 531 539 | 
             
                found_macroish_short = found_macroish && (source.include? ':[')
         | 
| 532 | 
            -
                doc_attrs = @document.attributes
         | 
| 533 | 
            -
                use_link_attrs = doc_attrs.key? 'linkattrs'
         | 
| 540 | 
            +
                doc_attrs = (doc = @document).attributes
         | 
| 534 541 | 
             
                result = source
         | 
| 535 542 |  | 
| 536 543 | 
             
                if doc_attrs.key? 'experimental'
         | 
| @@ -591,7 +598,7 @@ module Substitutors | |
| 591 598 | 
             
                  end
         | 
| 592 599 |  | 
| 593 600 | 
             
                  if (result.include? '"') && (result.include? '>')
         | 
| 594 | 
            -
                    result = result.gsub( | 
| 601 | 
            +
                    result = result.gsub(InlineMenuRx) {
         | 
| 595 602 | 
             
                      # alias match for Ruby 1.8.7 compat
         | 
| 596 603 | 
             
                      m = $~
         | 
| 597 604 | 
             
                      # honor the escape
         | 
| @@ -610,7 +617,7 @@ module Substitutors | |
| 610 617 |  | 
| 611 618 | 
             
                # FIXME this location is somewhat arbitrary, probably need to be able to control ordering
         | 
| 612 619 | 
             
                # TODO this handling needs some cleanup
         | 
| 613 | 
            -
                if (extensions =  | 
| 620 | 
            +
                if (extensions = doc.extensions) && extensions.inline_macros? # && found_macroish
         | 
| 614 621 | 
             
                  extensions.inline_macros.each do |extension|
         | 
| 615 622 | 
             
                    result = result.gsub(extension.instance.regexp) {
         | 
| 616 623 | 
             
                      # alias match for Ruby 1.8.7 compat
         | 
| @@ -664,63 +671,72 @@ module Substitutors | |
| 664 671 | 
             
                      # TODO remove this special case once titles use normal substitution order
         | 
| 665 672 | 
             
                      target = sub_attributes target
         | 
| 666 673 | 
             
                    end
         | 
| 667 | 
            -
                     | 
| 674 | 
            +
                    doc.register(:images, target) unless type == 'icon'
         | 
| 668 675 | 
             
                    attrs = parse_attributes(m[2], posattrs, :unescape_input => true)
         | 
| 669 676 | 
             
                    attrs['alt'] ||= (attrs['default-alt'] = Helpers.basename(target, true).tr('_-', ' '))
         | 
| 670 677 | 
             
                    Inline.new(self, :image, nil, :type => type, :target => target, :attributes => attrs).convert
         | 
| 671 678 | 
             
                  }
         | 
| 672 679 | 
             
                end
         | 
| 673 680 |  | 
| 674 | 
            -
                if ((result.include? '((') && (result.include? '))')) ||
         | 
| 675 | 
            -
                    (found_macroish_short && (result.include? 'indexterm'))
         | 
| 681 | 
            +
                if ((result.include? '((') && (result.include? '))')) || (found_macroish_short && (result.include? 'dexterm'))
         | 
| 676 682 | 
             
                  # (((Tigers,Big cats)))
         | 
| 677 683 | 
             
                  # indexterm:[Tigers,Big cats]
         | 
| 678 684 | 
             
                  # ((Tigers))
         | 
| 679 685 | 
             
                  # indexterm2:[Tigers]
         | 
| 680 686 | 
             
                  result = result.gsub(InlineIndextermMacroRx) {
         | 
| 681 | 
            -
                     | 
| 682 | 
            -
                    m = $~
         | 
| 683 | 
            -
             | 
| 684 | 
            -
                    # honor the escape
         | 
| 685 | 
            -
                    if m[0].start_with? RS
         | 
| 686 | 
            -
                      next m[0][1..-1]
         | 
| 687 | 
            -
                    end
         | 
| 688 | 
            -
             | 
| 689 | 
            -
                    case m[1]
         | 
| 687 | 
            +
                    case $1
         | 
| 690 688 | 
             
                    when 'indexterm'
         | 
| 689 | 
            +
                      text = $2
         | 
| 690 | 
            +
                      # honor the escape
         | 
| 691 | 
            +
                      if (m0 = $&).start_with? RS
         | 
| 692 | 
            +
                        next m0.slice 1, m0.length
         | 
| 693 | 
            +
                      end
         | 
| 691 694 | 
             
                      # indexterm:[Tigers,Big cats]
         | 
| 692 | 
            -
                      terms = split_simple_csv | 
| 693 | 
            -
                       | 
| 695 | 
            +
                      terms = split_simple_csv normalize_string text, true
         | 
| 696 | 
            +
                      doc.register :indexterms, terms
         | 
| 694 697 | 
             
                      (Inline.new self, :indexterm, nil, :attributes => { 'terms' => terms }).convert
         | 
| 695 698 | 
             
                    when 'indexterm2'
         | 
| 699 | 
            +
                      text = $2
         | 
| 700 | 
            +
                      # honor the escape
         | 
| 701 | 
            +
                      if (m0 = $&).start_with? RS
         | 
| 702 | 
            +
                        next m0.slice 1, m0.length
         | 
| 703 | 
            +
                      end
         | 
| 696 704 | 
             
                      # indexterm2:[Tigers]
         | 
| 697 | 
            -
                      term = normalize_string  | 
| 698 | 
            -
                       | 
| 705 | 
            +
                      term = normalize_string text, true
         | 
| 706 | 
            +
                      doc.register :indexterms, [term]
         | 
| 699 707 | 
             
                      (Inline.new self, :indexterm, term, :type => :visible).convert
         | 
| 700 708 | 
             
                    else
         | 
| 701 | 
            -
                      text | 
| 702 | 
            -
                       | 
| 703 | 
            -
             | 
| 704 | 
            -
             | 
| 709 | 
            +
                      text = $3
         | 
| 710 | 
            +
                      # honor the escape
         | 
| 711 | 
            +
                      if (m0 = $&).start_with? RS
         | 
| 712 | 
            +
                        # escape concealed index term, but process nested flow index term
         | 
| 713 | 
            +
                        if (text.start_with? '(') && (text.end_with? ')')
         | 
| 714 | 
            +
                          text = text.slice 1, text.length - 2
         | 
| 715 | 
            +
                          visible, before, after = true, '(', ')'
         | 
| 705 716 | 
             
                        else
         | 
| 706 | 
            -
                           | 
| 717 | 
            +
                          next m0.slice 1, m0.length
         | 
| 707 718 | 
             
                        end
         | 
| 708 | 
            -
                       | 
| 719 | 
            +
                      else
         | 
| 720 | 
            +
                        visible = true
         | 
| 709 721 | 
             
                        if text.start_with? '('
         | 
| 710 | 
            -
                           | 
| 711 | 
            -
             | 
| 722 | 
            +
                          if text.end_with? ')'
         | 
| 723 | 
            +
                            text, visible = (text.slice 1, text.length - 2), false
         | 
| 724 | 
            +
                          else
         | 
| 725 | 
            +
                            text, before, after = (text.slice 1, text.length), '(', ''
         | 
| 726 | 
            +
                          end
         | 
| 727 | 
            +
                        elsif text.end_with? ')'
         | 
| 712 728 | 
             
                          text, before, after = (text.slice 0, text.length - 1), '', ')'
         | 
| 713 729 | 
             
                        end
         | 
| 714 730 | 
             
                      end
         | 
| 715 731 | 
             
                      if visible
         | 
| 716 732 | 
             
                        # ((Tigers))
         | 
| 717 733 | 
             
                        term = normalize_string text
         | 
| 718 | 
            -
                         | 
| 734 | 
            +
                        doc.register :indexterms, [term]
         | 
| 719 735 | 
             
                        result = (Inline.new self, :indexterm, term, :type => :visible).convert
         | 
| 720 736 | 
             
                      else
         | 
| 721 737 | 
             
                        # (((Tigers,Big cats)))
         | 
| 722 738 | 
             
                        terms = split_simple_csv(normalize_string text)
         | 
| 723 | 
            -
                         | 
| 739 | 
            +
                        doc.register :indexterms, terms
         | 
| 724 740 | 
             
                        result = (Inline.new self, :indexterm, nil, :attributes => { 'terms' => terms }).convert
         | 
| 725 741 | 
             
                      end
         | 
| 726 742 | 
             
                      before ? %(#{before}#{result}#{after}) : result
         | 
| @@ -730,7 +746,7 @@ module Substitutors | |
| 730 746 |  | 
| 731 747 | 
             
                if found_colon && (result.include? '://')
         | 
| 732 748 | 
             
                  # inline urls, target[text] (optionally prefixed with link: and optionally surrounded by <>)
         | 
| 733 | 
            -
                  result = result.gsub( | 
| 749 | 
            +
                  result = result.gsub(InlineLinkRx) {
         | 
| 734 750 | 
             
                    # alias match for Ruby 1.8.7 compat
         | 
| 735 751 | 
             
                    m = $~
         | 
| 736 752 | 
             
                    # honor the escape
         | 
| @@ -759,15 +775,13 @@ module Substitutors | |
| 759 775 | 
             
                        if prefix.start_with?('<') && target.end_with?('>')
         | 
| 760 776 | 
             
                          prefix = prefix[4..-1]
         | 
| 761 777 | 
             
                          target = target[0...-4]
         | 
| 778 | 
            +
                        # strip trailing ;
         | 
| 779 | 
            +
                        # check for trailing );
         | 
| 780 | 
            +
                        elsif (target = target.chop).end_with?(')')
         | 
| 781 | 
            +
                          target = target.chop
         | 
| 782 | 
            +
                          suffix = ');'
         | 
| 762 783 | 
             
                        else
         | 
| 763 | 
            -
                           | 
| 764 | 
            -
                          # check for trailing );
         | 
| 765 | 
            -
                          if (target = target.chop).end_with?(')')
         | 
| 766 | 
            -
                            target = target.chop
         | 
| 767 | 
            -
                            suffix = ');'
         | 
| 768 | 
            -
                          else
         | 
| 769 | 
            -
                            suffix = ';'
         | 
| 770 | 
            -
                          end
         | 
| 784 | 
            +
                          suffix = ';'
         | 
| 771 785 | 
             
                        end
         | 
| 772 786 | 
             
                      when ':'
         | 
| 773 787 | 
             
                        # strip trailing :
         | 
| @@ -779,15 +793,16 @@ module Substitutors | |
| 779 793 | 
             
                          suffix = ':'
         | 
| 780 794 | 
             
                        end
         | 
| 781 795 | 
             
                      end
         | 
| 796 | 
            +
                      # NOTE handle case when remaining target is a URI scheme (e.g., http://)
         | 
| 797 | 
            +
                      return m[0] if target.end_with? '://'
         | 
| 782 798 | 
             
                    end
         | 
| 783 799 |  | 
| 784 800 | 
             
                    attrs, link_opts = nil, { :type => :link }
         | 
| 785 801 | 
             
                    unless text.empty?
         | 
| 786 802 | 
             
                      text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
         | 
| 787 | 
            -
                      if  | 
| 788 | 
            -
                        attrs =  | 
| 803 | 
            +
                      if !doc.compat_mode && (text.include? '=')
         | 
| 804 | 
            +
                        text = (attrs = (AttributeList.new text, self).parse)[1] || ''
         | 
| 789 805 | 
             
                        link_opts[:id] = attrs.delete 'id' if attrs.key? 'id'
         | 
| 790 | 
            -
                        text = attrs[1] || ''
         | 
| 791 806 | 
             
                      end
         | 
| 792 807 |  | 
| 793 808 | 
             
                      # TODO enable in Asciidoctor 1.6.x
         | 
| @@ -818,7 +833,7 @@ module Substitutors | |
| 818 833 | 
             
                      end
         | 
| 819 834 | 
             
                    end
         | 
| 820 835 |  | 
| 821 | 
            -
                     | 
| 836 | 
            +
                    doc.register :links, (link_opts[:target] = target)
         | 
| 822 837 | 
             
                    link_opts[:attributes] = attrs if attrs
         | 
| 823 838 | 
             
                    %(#{prefix}#{Inline.new(self, :anchor, text, link_opts).convert}#{suffix})
         | 
| 824 839 | 
             
                  }
         | 
| @@ -837,10 +852,10 @@ module Substitutors | |
| 837 852 | 
             
                    attrs, link_opts = nil, { :type => :link }
         | 
| 838 853 | 
             
                    unless (text = m[3]).empty?
         | 
| 839 854 | 
             
                      text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
         | 
| 840 | 
            -
                      if  | 
| 841 | 
            -
                         | 
| 842 | 
            -
             | 
| 843 | 
            -
             | 
| 855 | 
            +
                      if mailto
         | 
| 856 | 
            +
                        if !doc.compat_mode && (text.include? ',')
         | 
| 857 | 
            +
                          text = (attrs = (AttributeList.new text, self).parse)[1] || ''
         | 
| 858 | 
            +
                          link_opts[:id] = attrs.delete 'id' if attrs.key? 'id'
         | 
| 844 859 | 
             
                          if attrs.key? 2
         | 
| 845 860 | 
             
                            if attrs.key? 3
         | 
| 846 861 | 
             
                              target = %(#{target}?subject=#{Helpers.uri_encode attrs[2]}&body=#{Helpers.uri_encode attrs[3]})
         | 
| @@ -849,7 +864,9 @@ module Substitutors | |
| 849 864 | 
             
                            end
         | 
| 850 865 | 
             
                          end
         | 
| 851 866 | 
             
                        end
         | 
| 852 | 
            -
             | 
| 867 | 
            +
                      elsif !doc.compat_mode && (text.include? '=')
         | 
| 868 | 
            +
                        text = (attrs = (AttributeList.new text, self).parse)[1] || ''
         | 
| 869 | 
            +
                        link_opts[:id] = attrs.delete 'id' if attrs.key? 'id'
         | 
| 853 870 | 
             
                      end
         | 
| 854 871 |  | 
| 855 872 | 
             
                      # TODO enable in Asciidoctor 1.6.x
         | 
| @@ -886,14 +903,14 @@ module Substitutors | |
| 886 903 | 
             
                    end
         | 
| 887 904 |  | 
| 888 905 | 
             
                    # QUESTION should a mailto be registered as an e-mail address?
         | 
| 889 | 
            -
                     | 
| 906 | 
            +
                    doc.register :links, (link_opts[:target] = target)
         | 
| 890 907 | 
             
                    link_opts[:attributes] = attrs if attrs
         | 
| 891 908 | 
             
                    Inline.new(self, :anchor, text, link_opts).convert
         | 
| 892 909 | 
             
                  }
         | 
| 893 910 | 
             
                end
         | 
| 894 911 |  | 
| 895 912 | 
             
                if result.include? '@'
         | 
| 896 | 
            -
                  result = result.gsub( | 
| 913 | 
            +
                  result = result.gsub(InlineEmailRx) {
         | 
| 897 914 | 
             
                    address, tip = $&, $1
         | 
| 898 915 | 
             
                    if tip
         | 
| 899 916 | 
             
                      next (tip == RS ? address[1..-1] : address)
         | 
| @@ -901,13 +918,13 @@ module Substitutors | |
| 901 918 |  | 
| 902 919 | 
             
                    target = %(mailto:#{address})
         | 
| 903 920 | 
             
                    # QUESTION should this be registered as an e-mail address?
         | 
| 904 | 
            -
                     | 
| 921 | 
            +
                    doc.register(:links, target)
         | 
| 905 922 |  | 
| 906 923 | 
             
                    Inline.new(self, :anchor, address, :type => :link, :target => target).convert
         | 
| 907 924 | 
             
                  }
         | 
| 908 925 | 
             
                end
         | 
| 909 926 |  | 
| 910 | 
            -
                if  | 
| 927 | 
            +
                if found_macroish && (result.include? 'tnote')
         | 
| 911 928 | 
             
                  result = result.gsub(InlineFootnoteMacroRx) {
         | 
| 912 929 | 
             
                    # alias match for Ruby 1.8.7 compat
         | 
| 913 930 | 
             
                    m = $~
         | 
| @@ -915,36 +932,35 @@ module Substitutors | |
| 915 932 | 
             
                    if m[0].start_with? RS
         | 
| 916 933 | 
             
                      next m[0][1..-1]
         | 
| 917 934 | 
             
                    end
         | 
| 918 | 
            -
                    if m[1]  | 
| 919 | 
            -
                      id =  | 
| 920 | 
            -
                      # REVIEW it's a dirty job, but somebody's gotta do it
         | 
| 921 | 
            -
                      text = restore_passthroughs(sub_inline_xrefs(sub_inline_anchors(normalize_string m[2], true)), false)
         | 
| 922 | 
            -
                      index = @document.counter('footnote-number')
         | 
| 923 | 
            -
                      @document.register(:footnotes, Document::Footnote.new(index, id, text))
         | 
| 924 | 
            -
                      type = nil
         | 
| 925 | 
            -
                      target = nil
         | 
| 935 | 
            +
                    if m[1] # footnoteref (legacy)
         | 
| 936 | 
            +
                      id, text = (m[3] || '').split(',', 2)
         | 
| 926 937 | 
             
                    else
         | 
| 927 | 
            -
                      id, text = m[2] | 
| 928 | 
            -
             | 
| 938 | 
            +
                      id, text = m[2], m[3]
         | 
| 939 | 
            +
                    end
         | 
| 940 | 
            +
                    if id
         | 
| 929 941 | 
             
                      if text
         | 
| 930 942 | 
             
                        # REVIEW it's a dirty job, but somebody's gotta do it
         | 
| 931 943 | 
             
                        text = restore_passthroughs(sub_inline_xrefs(sub_inline_anchors(normalize_string text, true)), false)
         | 
| 932 | 
            -
                        index =  | 
| 933 | 
            -
                         | 
| 934 | 
            -
                        type = :ref
         | 
| 935 | 
            -
                        target = nil
         | 
| 944 | 
            +
                        index = doc.counter('footnote-number')
         | 
| 945 | 
            +
                        doc.register(:footnotes, Document::Footnote.new(index, id, text))
         | 
| 946 | 
            +
                        type, target = :ref, nil
         | 
| 936 947 | 
             
                      else
         | 
| 937 | 
            -
                        if (footnote =  | 
| 938 | 
            -
                          index = footnote.index
         | 
| 939 | 
            -
                          text = footnote.text
         | 
| 948 | 
            +
                        if (footnote = doc.footnotes.find {|candidate| candidate.id == id })
         | 
| 949 | 
            +
                          index, text = footnote.index, footnote.text
         | 
| 940 950 | 
             
                        else
         | 
| 941 | 
            -
                           | 
| 942 | 
            -
                          text = id
         | 
| 951 | 
            +
                          logger.warn %(invalid footnote reference: #{id})
         | 
| 952 | 
            +
                          index, text = nil, id
         | 
| 943 953 | 
             
                        end
         | 
| 944 | 
            -
                        target = id
         | 
| 945 | 
            -
                        id = nil
         | 
| 946 | 
            -
                        type = :xref
         | 
| 954 | 
            +
                        type, target, id = :xref, id, nil
         | 
| 947 955 | 
             
                      end
         | 
| 956 | 
            +
                    elsif text
         | 
| 957 | 
            +
                      # REVIEW it's a dirty job, but somebody's gotta do it
         | 
| 958 | 
            +
                      text = restore_passthroughs(sub_inline_xrefs(sub_inline_anchors(normalize_string text, true)), false)
         | 
| 959 | 
            +
                      index = doc.counter('footnote-number')
         | 
| 960 | 
            +
                      doc.register(:footnotes, Document::Footnote.new(index, id, text))
         | 
| 961 | 
            +
                      type = target = nil
         | 
| 962 | 
            +
                    else
         | 
| 963 | 
            +
                      next m[0]
         | 
| 948 964 | 
             
                    end
         | 
| 949 965 | 
             
                    Inline.new(self, :footnote, text, :attributes => {'index' => index}, :id => id, :target => target, :type => type).convert
         | 
| 950 966 | 
             
                  }
         | 
| @@ -985,74 +1001,95 @@ module Substitutors | |
| 985 1001 | 
             
              end
         | 
| 986 1002 |  | 
| 987 1003 | 
             
              # Internal: Substitute cross reference links
         | 
| 988 | 
            -
              def sub_inline_xrefs( | 
| 989 | 
            -
                if ((found ? found[:macroish] : ( | 
| 990 | 
            -
             | 
| 991 | 
            -
                  text = text.gsub(InlineXrefMacroRx) {
         | 
| 1004 | 
            +
              def sub_inline_xrefs(content, found = nil)
         | 
| 1005 | 
            +
                if ((found ? found[:macroish] : (content.include? '[')) && (content.include? 'xref:')) || ((content.include? '&') && (content.include? 'lt;&'))
         | 
| 1006 | 
            +
                  content = content.gsub(InlineXrefMacroRx) {
         | 
| 992 1007 | 
             
                    # alias match for Ruby 1.8.7 compat
         | 
| 993 1008 | 
             
                    m = $~
         | 
| 994 1009 | 
             
                    # honor the escape
         | 
| 995 1010 | 
             
                    if m[0].start_with? RS
         | 
| 996 1011 | 
             
                      next m[0][1..-1]
         | 
| 997 1012 | 
             
                    end
         | 
| 998 | 
            -
                     | 
| 999 | 
            -
             | 
| 1000 | 
            -
                       | 
| 1013 | 
            +
                    attrs, doc = {}, @document
         | 
| 1014 | 
            +
                    if (refid = m[1])
         | 
| 1015 | 
            +
                      refid, text = refid.split ',', 2
         | 
| 1016 | 
            +
                      text = text.lstrip if text
         | 
| 1001 1017 | 
             
                    else
         | 
| 1002 | 
            -
                       | 
| 1003 | 
            -
                       | 
| 1004 | 
            -
             | 
| 1018 | 
            +
                      macro = true
         | 
| 1019 | 
            +
                      refid = m[2]
         | 
| 1020 | 
            +
                      if (text = m[3])
         | 
| 1021 | 
            +
                        text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
         | 
| 1022 | 
            +
                        # NOTE if an equal sign (=) is present, parse text as attributes
         | 
| 1023 | 
            +
                        text = ((AttributeList.new text, self).parse_into attrs)[1] if !doc.compat_mode && (text.include? '=')
         | 
| 1005 1024 | 
             
                      end
         | 
| 1006 1025 | 
             
                    end
         | 
| 1007 1026 |  | 
| 1008 | 
            -
                    if  | 
| 1027 | 
            +
                    if doc.compat_mode
         | 
| 1028 | 
            +
                      fragment = refid
         | 
| 1029 | 
            +
                    elsif (hash_idx = refid.index '#')
         | 
| 1009 1030 | 
             
                      if hash_idx > 0
         | 
| 1010 | 
            -
                        if (fragment_len =  | 
| 1011 | 
            -
                          path, fragment = ( | 
| 1031 | 
            +
                        if (fragment_len = refid.length - hash_idx - 1) > 0
         | 
| 1032 | 
            +
                          path, fragment = (refid.slice 0, hash_idx), (refid.slice hash_idx + 1, fragment_len)
         | 
| 1012 1033 | 
             
                        else
         | 
| 1013 | 
            -
                          path | 
| 1034 | 
            +
                          path = refid.slice 0, hash_idx
         | 
| 1035 | 
            +
                        end
         | 
| 1036 | 
            +
                        if (ext = ::File.extname path).empty?
         | 
| 1037 | 
            +
                          src2src = path
         | 
| 1038 | 
            +
                        elsif ASCIIDOC_EXTENSIONS[ext]
         | 
| 1039 | 
            +
                          src2src = (path = path.slice 0, path.length - ext.length)
         | 
| 1014 1040 | 
             
                        end
         | 
| 1015 1041 | 
             
                      else
         | 
| 1016 | 
            -
                        target,  | 
| 1042 | 
            +
                        target, fragment = refid, (refid.slice 1, refid.length)
         | 
| 1017 1043 | 
             
                      end
         | 
| 1044 | 
            +
                    elsif macro && (refid.end_with? '.adoc')
         | 
| 1045 | 
            +
                      src2src = (path = refid.slice 0, refid.length - 5)
         | 
| 1018 1046 | 
             
                    else
         | 
| 1019 | 
            -
                       | 
| 1047 | 
            +
                      fragment = refid
         | 
| 1020 1048 | 
             
                    end
         | 
| 1021 1049 |  | 
| 1022 1050 | 
             
                    # handles: #id
         | 
| 1023 1051 | 
             
                    if target
         | 
| 1024 1052 | 
             
                      refid = fragment
         | 
| 1025 | 
            -
             | 
| 1053 | 
            +
                      logger.warn %(invalid reference: #{refid}) if $VERBOSE && !(doc.catalog[:ids].key? refid)
         | 
| 1026 1054 | 
             
                    elsif path
         | 
| 1027 | 
            -
                       | 
| 1028 | 
            -
             | 
| 1029 | 
            -
                       | 
| 1030 | 
            -
             | 
| 1031 | 
            -
             | 
| 1032 | 
            -
             | 
| 1055 | 
            +
                      # handles: path#, path#id, path.adoc#, path.adoc#id, or path.adoc (xref macro only)
         | 
| 1056 | 
            +
                      # the referenced path is the current document, or its contents have been included in the current document
         | 
| 1057 | 
            +
                      if src2src && (doc.attributes['docname'] == path || doc.catalog[:includes][path])
         | 
| 1058 | 
            +
                        if fragment
         | 
| 1059 | 
            +
                          refid, path, target = fragment, nil, %(##{fragment})
         | 
| 1060 | 
            +
                          logger.warn %(invalid reference: #{refid}) if $VERBOSE && !(doc.catalog[:ids].key? refid)
         | 
| 1061 | 
            +
                        else
         | 
| 1062 | 
            +
                          refid, path, target = nil, nil, '#'
         | 
| 1063 | 
            +
                        end
         | 
| 1033 1064 | 
             
                      else
         | 
| 1034 | 
            -
                        refid =  | 
| 1035 | 
            -
                         | 
| 1036 | 
            -
             | 
| 1037 | 
            -
             | 
| 1038 | 
            -
             | 
| 1039 | 
            -
                    else
         | 
| 1040 | 
            -
                      # resolve fragment as reftext if it's not a known ID and resembles reftext (includes space or has uppercase char)
         | 
| 1041 | 
            -
                      unless @document.catalog[:ids].key? fragment
         | 
| 1042 | 
            -
                        if ((fragment.include? ' ') || fragment.downcase != fragment) &&
         | 
| 1043 | 
            -
                            (resolved_id = @document.catalog[:ids].key fragment)
         | 
| 1044 | 
            -
                          fragment = resolved_id
         | 
| 1045 | 
            -
                        elsif $VERBOSE
         | 
| 1046 | 
            -
                          warn %(asciidoctor: WARNING: invalid reference: #{fragment})
         | 
| 1065 | 
            +
                        refid, path = path, %(#{doc.attributes['relfileprefix']}#{path}#{src2src ? (doc.attributes.fetch 'relfilesuffix', doc.outfilesuffix) : ''})
         | 
| 1066 | 
            +
                        if fragment
         | 
| 1067 | 
            +
                          refid, target = %(#{refid}##{fragment}), %(#{path}##{fragment})
         | 
| 1068 | 
            +
                        else
         | 
| 1069 | 
            +
                          target = path
         | 
| 1047 1070 | 
             
                        end
         | 
| 1048 1071 | 
             
                      end
         | 
| 1072 | 
            +
                    # handles: id (in compat mode or when natural xrefs are disabled)
         | 
| 1073 | 
            +
                    elsif doc.compat_mode || !Compliance.natural_xrefs
         | 
| 1074 | 
            +
                      refid, target = fragment, %(##{fragment})
         | 
| 1075 | 
            +
                      logger.warn %(invalid reference: #{refid}) if $VERBOSE && !(doc.catalog[:ids].key? refid)
         | 
| 1076 | 
            +
                    # handles: id
         | 
| 1077 | 
            +
                    elsif doc.catalog[:ids].key? fragment
         | 
| 1078 | 
            +
                      refid, target = fragment, %(##{fragment})
         | 
| 1079 | 
            +
                    # handles: Node Title or Reference Text
         | 
| 1080 | 
            +
                    # do reverse lookup on fragment if not a known ID and resembles reftext (contains a space or uppercase char)
         | 
| 1081 | 
            +
                    elsif (refid = doc.catalog[:ids].key fragment) && ((fragment.include? ' ') || fragment.downcase != fragment)
         | 
| 1082 | 
            +
                      fragment, target = refid, %(##{refid})
         | 
| 1083 | 
            +
                    else
         | 
| 1049 1084 | 
             
                      refid, target = fragment, %(##{fragment})
         | 
| 1085 | 
            +
                      logger.warn %(invalid reference: #{refid}) if $VERBOSE
         | 
| 1050 1086 | 
             
                    end
         | 
| 1051 | 
            -
                     | 
| 1087 | 
            +
                    attrs['path'], attrs['fragment'], attrs['refid'] = path, fragment, refid
         | 
| 1088 | 
            +
                    Inline.new(self, :anchor, text, :type => :xref, :target => target, :attributes => attrs).convert
         | 
| 1052 1089 | 
             
                  }
         | 
| 1053 1090 | 
             
                end
         | 
| 1054 1091 |  | 
| 1055 | 
            -
                 | 
| 1092 | 
            +
                content
         | 
| 1056 1093 | 
             
              end
         | 
| 1057 1094 |  | 
| 1058 1095 | 
             
              # Public: Substitute callout source references
         | 
| @@ -1135,7 +1172,7 @@ module Substitutors | |
| 1135 1172 | 
             
              # Returns a Hash of attributes (role and id only)
         | 
| 1136 1173 | 
             
              def parse_quoted_text_attributes str
         | 
| 1137 1174 | 
             
                # NOTE attributes are typically resolved after quoted text, so substitute eagerly
         | 
| 1138 | 
            -
                str = sub_attributes str if str.include? ATTR_REF_HEAD
         | 
| 1175 | 
            +
                str = sub_attributes str, :multiline => true if str.include? ATTR_REF_HEAD
         | 
| 1139 1176 | 
             
                # for compliance, only consider first positional attribute
         | 
| 1140 1177 | 
             
                str = str.slice 0, (str.index ',') if str.include? ','
         | 
| 1141 1178 |  | 
| @@ -1178,8 +1215,8 @@ module Substitutors | |
| 1178 1215 | 
             
              def parse_attributes(attrline, posattrs = ['role'], opts = {})
         | 
| 1179 1216 | 
             
                return unless attrline
         | 
| 1180 1217 | 
             
                return {} if attrline.empty?
         | 
| 1181 | 
            -
                attrline = @document.sub_attributes | 
| 1182 | 
            -
                attrline = unescape_bracketed_text | 
| 1218 | 
            +
                attrline = @document.sub_attributes attrline if opts[:sub_input] && (attrline.include? ATTR_REF_HEAD)
         | 
| 1219 | 
            +
                attrline = unescape_bracketed_text attrline if opts[:unescape_input]
         | 
| 1183 1220 | 
             
                # substitutions are only performed on attribute values if block is not nil
         | 
| 1184 1221 | 
             
                block = opts.fetch(:sub_result, true) ? self : nil
         | 
| 1185 1222 | 
             
                if (into = opts[:into])
         | 
| @@ -1189,7 +1226,33 @@ module Substitutors | |
| 1189 1226 | 
             
                end
         | 
| 1190 1227 | 
             
              end
         | 
| 1191 1228 |  | 
| 1192 | 
            -
              #  | 
| 1229 | 
            +
              # Expand all groups in the subs list and return. If no subs are resolve, return nil.
         | 
| 1230 | 
            +
              #
         | 
| 1231 | 
            +
              # subs - The substitutions to expand; can be a Symbol, Symbol Array or nil
         | 
| 1232 | 
            +
              #
         | 
| 1233 | 
            +
              # Returns a Symbol Array of substitutions to pass to apply_subs or nil if no substitutions were resolved.
         | 
| 1234 | 
            +
              def expand_subs subs
         | 
| 1235 | 
            +
                if ::Symbol === subs
         | 
| 1236 | 
            +
                  unless subs == :none
         | 
| 1237 | 
            +
                    SUB_GROUPS[subs] || [subs]
         | 
| 1238 | 
            +
                  end
         | 
| 1239 | 
            +
                else
         | 
| 1240 | 
            +
                  expanded_subs = []
         | 
| 1241 | 
            +
                  subs.each do |key|
         | 
| 1242 | 
            +
                    unless key == :none
         | 
| 1243 | 
            +
                      if (sub_group = SUB_GROUPS[key])
         | 
| 1244 | 
            +
                        expanded_subs += sub_group
         | 
| 1245 | 
            +
                      else
         | 
| 1246 | 
            +
                        expanded_subs << key
         | 
| 1247 | 
            +
                      end
         | 
| 1248 | 
            +
                    end
         | 
| 1249 | 
            +
                  end
         | 
| 1250 | 
            +
             | 
| 1251 | 
            +
                  expanded_subs.empty? ? nil : expanded_subs
         | 
| 1252 | 
            +
                end
         | 
| 1253 | 
            +
              end
         | 
| 1254 | 
            +
             | 
| 1255 | 
            +
              # Internal: Strip bounding whitespace, fold endlines and unescape closing
         | 
| 1193 1256 | 
             
              # square brackets from text extracted from brackets
         | 
| 1194 1257 | 
             
              def unescape_bracketed_text text
         | 
| 1195 1258 | 
             
                if (text = text.strip.tr LF, ' ').include? R_SB
         | 
| @@ -1253,9 +1316,9 @@ module Substitutors | |
| 1253 1316 | 
             
              #
         | 
| 1254 1317 | 
             
              # subs - A comma-delimited String of substitution aliases
         | 
| 1255 1318 | 
             
              #
         | 
| 1256 | 
            -
              # returns An Array of Symbols representing the substitution operation
         | 
| 1319 | 
            +
              # returns An Array of Symbols representing the substitution operation or nothing if no subs are found.
         | 
| 1257 1320 | 
             
              def resolve_subs subs, type = :block, defaults = nil, subject = nil
         | 
| 1258 | 
            -
                return  | 
| 1321 | 
            +
                return if subs.nil_or_empty?
         | 
| 1259 1322 | 
             
                # QUESTION should we store candidates as a Set instead of an Array?
         | 
| 1260 1323 | 
             
                candidates = nil
         | 
| 1261 1324 | 
             
                subs = subs.delete ' ' if subs.include? ' '
         | 
| @@ -1306,12 +1369,12 @@ module Substitutors | |
| 1306 1369 | 
             
                    candidates += resolved_keys
         | 
| 1307 1370 | 
             
                  end
         | 
| 1308 1371 | 
             
                end
         | 
| 1309 | 
            -
                return  | 
| 1372 | 
            +
                return unless candidates
         | 
| 1310 1373 | 
             
                # weed out invalid options and remove duplicates (order is preserved; first occurence wins)
         | 
| 1311 1374 | 
             
                resolved = candidates & SUB_OPTIONS[type]
         | 
| 1312 1375 | 
             
                unless (candidates - resolved).empty?
         | 
| 1313 1376 | 
             
                  invalid = candidates - resolved
         | 
| 1314 | 
            -
                  warn %( | 
| 1377 | 
            +
                  logger.warn %(invalid substitution type#{invalid.size > 1 ? 's' : ''}#{subject ? ' for ' : ''}#{subject}: #{invalid * ', '})
         | 
| 1315 1378 | 
             
                end
         | 
| 1316 1379 | 
             
                resolved
         | 
| 1317 1380 | 
             
              end
         | 
| @@ -1339,19 +1402,21 @@ module Substitutors | |
| 1339 1402 | 
             
              def highlight_source source, process_callouts, highlighter = nil
         | 
| 1340 1403 | 
             
                case (highlighter ||= @document.attributes['source-highlighter'])
         | 
| 1341 1404 | 
             
                when 'coderay'
         | 
| 1342 | 
            -
                  unless (highlighter_loaded = defined? ::CodeRay) || | 
| 1405 | 
            +
                  unless (highlighter_loaded = defined? ::CodeRay) ||
         | 
| 1406 | 
            +
                      (defined? @@coderay_unavailable) || @document.attributes['coderay-unavailable']
         | 
| 1343 1407 | 
             
                    if (Helpers.require_library 'coderay', true, :warn).nil?
         | 
| 1344 | 
            -
                      # prevent further attempts to load CodeRay
         | 
| 1345 | 
            -
                       | 
| 1408 | 
            +
                      # prevent further attempts to load CodeRay in this process
         | 
| 1409 | 
            +
                      @@coderay_unavailable = true
         | 
| 1346 1410 | 
             
                    else
         | 
| 1347 1411 | 
             
                      highlighter_loaded = true
         | 
| 1348 1412 | 
             
                    end
         | 
| 1349 1413 | 
             
                  end
         | 
| 1350 1414 | 
             
                when 'pygments'
         | 
| 1351 | 
            -
                  unless (highlighter_loaded = defined? ::Pygments) || | 
| 1415 | 
            +
                  unless (highlighter_loaded = defined? ::Pygments) ||
         | 
| 1416 | 
            +
                      (defined? @@pygments_unavailable) || @document.attributes['pygments-unavailable']
         | 
| 1352 1417 | 
             
                    if (Helpers.require_library 'pygments', 'pygments.rb', :warn).nil?
         | 
| 1353 | 
            -
                      # prevent further attempts to load Pygments
         | 
| 1354 | 
            -
                       | 
| 1418 | 
            +
                      # prevent further attempts to load Pygments in this process
         | 
| 1419 | 
            +
                      @@pygments_unavailable = true
         | 
| 1355 1420 | 
             
                    else
         | 
| 1356 1421 | 
             
                      highlighter_loaded = true
         | 
| 1357 1422 | 
             
                    end
         | 
| @@ -1422,21 +1487,26 @@ module Substitutors | |
| 1422 1487 | 
             
                      opts[:hl_lines] = highlight_lines * ' '
         | 
| 1423 1488 | 
             
                    end
         | 
| 1424 1489 | 
             
                  end
         | 
| 1490 | 
            +
                  # NOTE highlight can return nil if something goes wrong; fallback to source if this happens
         | 
| 1425 1491 | 
             
                  # TODO we could add the line numbers in ourselves instead of having to strip out the junk
         | 
| 1426 1492 | 
             
                  if (attr? 'linenums', nil, false) && (opts[:linenos] = @document.attributes['pygments-linenums-mode'] || 'table') == 'table'
         | 
| 1427 1493 | 
             
                    linenums_mode = :table
         | 
| 1428 | 
            -
                    result = lexer.highlight | 
| 1429 | 
            -
             | 
| 1430 | 
            -
                     | 
| 1494 | 
            +
                    if (result = lexer.highlight source, :options => opts)
         | 
| 1495 | 
            +
                      result = (result.sub PygmentsWrapperDivRx, '\1').gsub PygmentsWrapperPreRx, '\1'
         | 
| 1496 | 
            +
                    else
         | 
| 1497 | 
            +
                      result = sub_specialchars source
         | 
| 1498 | 
            +
                    end
         | 
| 1499 | 
            +
                  elsif (result = lexer.highlight source, :options => opts)
         | 
| 1500 | 
            +
                    if PygmentsWrapperPreRx =~ result
         | 
| 1431 1501 | 
             
                      result = $1
         | 
| 1432 1502 | 
             
                    end
         | 
| 1503 | 
            +
                  else
         | 
| 1504 | 
            +
                    result = sub_specialchars source
         | 
| 1433 1505 | 
             
                  end
         | 
| 1434 1506 | 
             
                end
         | 
| 1435 1507 |  | 
| 1436 1508 | 
             
                # fix passthrough placeholders that got caught up in syntax highlighting
         | 
| 1437 | 
            -
                unless @passthroughs.empty?
         | 
| 1438 | 
            -
                  result = result.gsub HighlightedPassSlotRx, %(#{PASS_START}\\1#{PASS_END})
         | 
| 1439 | 
            -
                end
         | 
| 1509 | 
            +
                result = result.gsub HighlightedPassSlotRx, %(#{PASS_START}\\1#{PASS_END}) unless @passthroughs.empty?
         | 
| 1440 1510 |  | 
| 1441 1511 | 
             
                if process_callouts && callout_marks
         | 
| 1442 1512 | 
             
                  lineno = 0
         | 
| @@ -1539,7 +1609,11 @@ module Substitutors | |
| 1539 1609 | 
             
                  end
         | 
| 1540 1610 | 
             
                end
         | 
| 1541 1611 |  | 
| 1542 | 
            -
                 | 
| 1612 | 
            +
                if (custom_subs = @attributes['subs'])
         | 
| 1613 | 
            +
                  @subs = (resolve_block_subs custom_subs, default_subs, @context) || []
         | 
| 1614 | 
            +
                else
         | 
| 1615 | 
            +
                  @subs = default_subs.dup
         | 
| 1616 | 
            +
                end
         | 
| 1543 1617 |  | 
| 1544 1618 | 
             
                # QUESION delegate this logic to a method?
         | 
| 1545 1619 | 
             
                if @context == :listing && @style == 'source' && (@attributes.key? 'language') && (@document.basebackend? 'html') &&
         |