# File lib/asciidoctor/substituters.rb, line 337
  def sub_macros(text)
    return text if text.nil? || text.empty?

    result = text.dup

    # some look ahead assertions to cut unnecessary regex calls
    found = {}
    found[:square_bracket] = result.include?('[')
    found[:round_bracket] = result.include?('(')
    found[:colon] = result.include?(':')
    found[:at] = result.include?('@')
    found[:macroish] = (found[:square_bracket] && found[:colon])
    found[:macroish_short_form] = (found[:square_bracket] && found[:colon] && result.include?(':['))
    found[:uri] = (found[:colon] && result.include?('://'))
    use_link_attrs = @document.attributes.has_key?('linkattrs')
    experimental = @document.attributes.has_key?('experimental')

    if experimental
      if found[:macroish_short_form] && (result.include?('kbd:') || result.include?('btn:'))
        result.gsub!(REGEXP[:kbd_btn_macro]) {
          # alias match for Ruby 1.8.7 compat
          m = $~
          # honor the escape
          if (captured = m[0]).start_with? '\\'
            next captured[1..-1]
          end

          if captured.start_with?('kbd')
            keys = unescape_bracketed_text m[1]

            if keys == '+'
              keys = ['+']
            else
              # need to use closure to work around lack of negative lookbehind
              keys = keys.split(REGEXP[:kbd_delim]).inject([]) {|c, key|
                if key.end_with?('++')
                  c << key[0..-3].strip
                  c << '+'
                else
                  c << key.strip
                end
                c
              }
            end
            Inline.new(self, :kbd, nil, :attributes => {'keys' => keys}).render 
          elsif captured.start_with?('btn')
            label = unescape_bracketed_text m[1]
            Inline.new(self, :button, label).render
          end
        }
      end
      
      if found[:macroish] && result.include?('menu:')
        result.gsub!(REGEXP[:menu_macro]) {
          # alias match for Ruby 1.8.7 compat
          m = $~
          # honor the escape
          if (captured = m[0]).start_with? '\\'
            next captured[1..-1]
          end

          menu = m[1]
          items = m[2]

          if items.nil?
            submenus = []
            menuitem = nil
          else
            if (delim = items.include?('&gt;') ? '&gt;' : (items.include?(',') ? ',' : nil))
              submenus = items.split(delim).map(&:strip)
              menuitem = submenus.pop
            else
              submenus = []
              menuitem = items.rstrip
            end
          end

          Inline.new(self, :menu, nil, :attributes => {'menu' => menu, 'submenus' => submenus, 'menuitem' => menuitem}).render
        }
      end

      if result.include?('"') && result.include?('&gt;')
        result.gsub!(REGEXP[:menu_inline_macro]) {
          # alias match for Ruby 1.8.7 compat
          m = $~
          # honor the escape
          if (captured = m[0]).start_with? '\\'
            next captured[1..-1]
          end

          input = m[1]

          menu, *submenus = input.split('&gt;').map(&:strip)
          menuitem = submenus.pop 
          Inline.new(self, :menu, nil, :attributes => {'menu' => menu, 'submenus' => submenus, 'menuitem' => menuitem}).render
        }
      end
    end

    if found[:macroish] && result.include?('image:')
      # image:filename.png[Alt Text]
      result.gsub!(REGEXP[:image_macro]) {
        # alias match for Ruby 1.8.7 compat
        m = $~
        # honor the escape
        if m[0].start_with? '\\'
          next m[0][1..-1]
        end
        target = sub_attributes(m[1])
        @document.register(:images, target)
        attrs = parse_attributes(unescape_bracketed_text(m[2]), ['alt', 'width', 'height'])
        if !attrs['alt']
          attrs['alt'] = File.basename(target, File.extname(target))
        end
        Inline.new(self, :image, nil, :target => target, :attributes => attrs).render
      }
    end

    if found[:macroish_short_form] || found[:round_bracket]
      # indexterm:[Tigers,Big cats]
      # (((Tigers,Big cats)))
      result.gsub!(REGEXP[:indexterm_macro]) {
        # alias match for Ruby 1.8.7 compat
        m = $~
        # honor the escape
        if m[0].start_with? '\\'
          next m[0][1..-1]
        end

        terms = unescape_bracketed_text(m[1] || m[2]).split(',').map(&:strip)
        document.register(:indexterms, [*terms])
        Inline.new(self, :indexterm, text, :attributes => {'terms' => terms}).render
      }
    
      # indexterm2:[Tigers]
      # ((Tigers))
      result.gsub!(REGEXP[:indexterm2_macro]) {
        # alias match for Ruby 1.8.7 compat
        m = $~
        # honor the escape
        if m[0].start_with? '\\'
          next m[0][1..-1]
        end

        text = unescape_bracketed_text(m[1] || m[2])
        document.register(:indexterms, [text])
        Inline.new(self, :indexterm, text, :type => :visible).render
      }
    end

    if found[:uri]
      # inline urls, target[text] (optionally prefixed with link: and optionally surrounded by <>)
      result.gsub!(REGEXP[:link_inline]) {
        # alias match for Ruby 1.8.7 compat
        m = $~
        # honor the escape
        if m[2].start_with? '\\'
          next "#{m[1]}#{m[2][1..-1]}#{m[3]}"
        # not a valid macro syntax w/o trailing square brackets
        # we probably shouldn't even get here...our regex is doing too much
        elsif m[1] == 'link:' && m[3].nil?
          next m[0]
        end
        prefix = (m[1] != 'link:' ? m[1] : '')
        target = m[2]
        suffix = ''
        # strip the <> around the link
        if prefix.start_with?('&lt;') && target.end_with?('&gt;')
          prefix = prefix[4..-1]
          target = target[0..-5]
        elsif prefix.start_with?('(') && target.end_with?(')')
          target = target[0..-2]
          suffix = ')'
        elsif target.end_with?('):')
          target = target[0..-3]
          suffix = '):'
        end
        @document.register(:links, target)

        attrs = nil
        #text = !m[3].nil? ? sub_attributes(m[3].gsub('\]', ']')) : ''
        if !m[3].to_s.empty?
          if use_link_attrs && (m[3].start_with?('"') || m[3].include?(','))
            attrs = parse_attributes(sub_attributes(m[3].gsub('\]', ']')), [])
            text = attrs[1]
          else
            text = sub_attributes(m[3].gsub('\]', ']'))
          end

          if text.end_with? '^'
            text = text.chop
            attrs ||= {}
            attrs['window'] = '_blank' unless attrs.has_key?('window')
          end
        else
          text = ''
        end

        "#{prefix}#{Inline.new(self, :anchor, (!text.empty? ? text : target), :type => :link, :target => target, :attributes => attrs).render}#{suffix}"
      }
    end

    if found[:macroish] && (result.include?('link:') || result.include?('mailto:'))
      # inline link macros, link:target[text]
      result.gsub!(REGEXP[:link_macro]) {
        # alias match for Ruby 1.8.7 compat
        m = $~
        # honor the escape
        if m[0].start_with? '\\'
          next m[0][1..-1]
        end
        raw_target = m[1]
        mailto = m[0].start_with?('mailto:')
        target = mailto ? "mailto:#{raw_target}" : raw_target

        attrs = nil
        #text = sub_attributes(m[2].gsub('\]', ']'))
        if use_link_attrs && (m[2].start_with?('"') || m[2].include?(','))
          attrs = parse_attributes(sub_attributes(m[2].gsub('\]', ']')), [])
          text = attrs[1]
          if mailto
            if attrs.has_key? 2
              target = "#{target}?subject=#{Helpers.encode_uri(attrs[2])}"

              if attrs.has_key? 3
                target = "#{target}&amp;body=#{Helpers.encode_uri(attrs[3])}"
              end
            end
          end
        else
          text = sub_attributes(m[2].gsub('\]', ']'))
        end

        if text.end_with? '^'
          text = text.chop
          attrs ||= {}
          attrs['window'] = '_blank' unless attrs.has_key?('window')
        end

        # QUESTION should a mailto be registered as an e-mail address?
        @document.register(:links, target)

        Inline.new(self, :anchor, (!text.empty? ? text : raw_target), :type => :link, :target => target, :attributes => attrs).render
      }
    end

    if found[:at]
      result.gsub!(REGEXP[:email_inline]) {
        # alias match for Ruby 1.8.7 compat
        m = $~
        address = m[0]
        case address[0..0]
        when '\\'
          next address[1..-1]
        when '>', ':'
          next address
        end

        target = "mailto:#{address}"
        # QUESTION should this be registered as an e-mail address?
        @document.register(:links, target)

        Inline.new(self, :anchor, address, :type => :link, :target => target).render
      }
    end

    if found[:macroish_short_form] && result.include?('footnote')
      result.gsub!(REGEXP[:footnote_macro]) {
        # alias match for Ruby 1.8.7 compat
        m = $~
        # honor the escape
        if m[0].start_with? '\\'
          next m[0][1..-1]
        end
        if m[1] == 'footnote'
          # hmmmm
          text = restore_passthroughs(m[2])
          id = nil
          index = @document.counter('footnote-number')
          @document.register(:footnotes, Document::Footnote.new(index, id, text))
          type = nil
          target = nil
        else
          id, text = m[2].split(',', 2).map(&:strip)
          if !text.nil?
            # hmmmm
            text = restore_passthroughs(text)
            index = @document.counter('footnote-number')
            @document.register(:footnotes, Document::Footnote.new(index, id, text))
            type = :ref
            target = nil
          else
            footnote = @document.references[:footnotes].find {|fn| fn.id == id }
            target = id
            id = nil
            index = footnote.index
            text = footnote.text
            type = :xref
          end
        end
        Inline.new(self, :footnote, text, :attributes => {'index' => index}, :id => id, :target => target, :type => type).render
      }
    end

    if found[:macroish] || result.include?('&lt;&lt;')
      result.gsub!(REGEXP[:xref_macro]) {
        # alias match for Ruby 1.8.7 compat
        m = $~
        # honor the escape
        if m[0].start_with? '\\'
          next m[0][1..-1]
        end
        if !m[1].nil?
          id, reftext = m[1].split(',', 2).map(&:strip)
          id.sub!(REGEXP[:dbl_quoted], '\2')
          reftext.sub!(REGEXP[:m_dbl_quoted], '\2') unless reftext.nil?
        else
          id = m[2]
          reftext = !m[3].empty? ? m[3] : nil
        end
        Inline.new(self, :anchor, reftext, :type => :xref, :target => id).render
      }
    end

    if found[:square_bracket] && result.include?('[[[')
      result.gsub!(REGEXP[:biblio_macro]) {
        # alias match for Ruby 1.8.7 compat
        m = $~
        # honor the escape
        if m[0].start_with? '\\'
          next m[0][1..-1]
        end
        id = reftext = m[1]
        Inline.new(self, :anchor, reftext, :type => :bibref, :target => id).render
      }
    end

    if found[:square_bracket] && result.include?('[[')
      result.gsub!(REGEXP[:anchor_macro]) {
        # alias match for Ruby 1.8.7 compat
        m = $~
        # honor the escape
        if m[0].start_with? '\\'
          next m[0][1..-1]
        end
        id, reftext = m[1].split(',').map(&:strip)
        id.sub!(REGEXP[:dbl_quoted], '\2')
        if reftext.nil?
          reftext = "[#{id}]"
        else
          reftext.sub!(REGEXP[:m_dbl_quoted], '\2')
        end
        # NOTE the reftext should also match what's in our references dic
        if !@document.references[:ids].has_key? id
          Debug.debug { "Missing reference for anchor #{id}" }
        end
        Inline.new(self, :anchor, reftext, :type => :ref, :target => id).render
      }
    end

    result
  end