module Asciidoctor::Substitutors - asciidoctor-1.5.6.2 Documentation

# module Asciidoctor::Substitutors

Public: Methods to perform substitutions on lines of AsciiDoc text. This module is intented to be mixed-in to Section and Block to provide operations for performing the necessary substitutions.

### Constants

BASIC_SUBS
ESC_R_SB
HEADER_SUBS
HighlightedPassSlotRx

  fix passthrough slot after syntax highlighting

NONE_SUBS
NORMAL_SUBS
PASS_END

  EPA, end of guarded protected area (u0097)

PASS_START

  SPA, start of guarded protected area (u0096)

PLUS
PassSlotRx

  match passthrough slot

PygmentsWrapperDivRx
PygmentsWrapperPreRx

  NOTE handles all permutations of <pre> wrapper NOTE trailing whitespace appears when pygments-linenums-mode=table; <pre> has style attribute when pygments-css=inline

QuotedTextSniffRx

  Detects if text is a possible candidate for the quotes substitution.

REFTEXT_SUBS
RS
R_SB
SUB_GROUPS
SUB_HIGHLIGHT
SUB_HINTS
SUB_OPTIONS
SpecialCharsRx
SpecialCharsTr
TITLE_SUBS
VERBATIM_SUBS

### Attributes

passthroughs[R]

Internal: A String Array of passthough (unprocessed) text captured from this block

### Public Instance Methods

apply_header_subs(text) click to toggle source

Public: Apply substitutions for header metadata and attribute assignments

text - String containing the text process

returns - A String with header substitutions performed

    # File lib/asciidoctor/substitutors.rb, line 175
    def apply_header_subs(text)
      apply_subs text, HEADER_SUBS
    end

apply_normal_subs(text) click to toggle source

Public: Apply normal substitutions.

An alias for #apply_subs with default remaining arguments.

text - The String text to which to apply normal substitutions

Returns the String with normal substitutions applied.

    # File lib/asciidoctor/substitutors.rb, line 148
    def apply_normal_subs text
      apply_subs text
    end

apply_reftext_subs(text) click to toggle source

Public: Apply substitutions for reftext.

text - The String to process

Returns a String with all substitutions from the reftext substitution group applied

    # File lib/asciidoctor/substitutors.rb, line 166
    def apply_reftext_subs text
      apply_subs text, REFTEXT_SUBS
    end

apply_subs(source, subs = NORMAL_SUBS, expand = nil) click to toggle source

Public: Apply the specified substitutions to the source.

source - The String or String Array of text to process; must not be nil. subs - The substitutions to perform; can be a Symbol, Symbol Array or nil (default: NORMAL_SUBS). expand - A Boolean (or nil) to control whether substitution aliases are expanded (default: nil).

Returns a String or String Array with substitutions applied, matching the type of source argument.

    # File lib/asciidoctor/substitutors.rb, line 83
    def apply_subs source, subs = NORMAL_SUBS, expand = nil
      if source.empty? || !subs
        return source
      elsif expand
        if ::Symbol === subs
          subs = SUB_GROUPS[subs] || [subs]
        else
          effective_subs = []
          subs.each do |key|
            if (sub_group = SUB_GROUPS[key])
              effective_subs += sub_group unless sub_group.empty?
            else
              effective_subs << key
            end
          end

          if (subs = effective_subs).empty?
            return source
          end
        end
      elsif subs.empty?
        return source
      end

      text = (multiline = ::Array === source) ? source * LF : source

      if (has_passthroughs = subs.include? :macros)
        text = extract_passthroughs text
        has_passthroughs = false if @passthroughs.empty?
      end

      subs.each do |type|
        case type
        when :specialcharacters
          text = sub_specialchars text
        when :quotes
          text = sub_quotes text
        when :attributes
          text = sub_attributes(text.split LF, -1) * LF if text.include? ATTR_REF_HEAD
        when :replacements
          text = sub_replacements text
        when :macros
          text = sub_macros text
        when :highlight
          text = highlight_source text, (subs.include? :callouts)
        when :callouts
          text = sub_callouts text unless subs.include? :highlight
        when :post_replacements
          text = sub_post_replacements text
        else
          warn %Q(asciidoctor: WARNING: unknown substitution type #{type})
        end
      end
      text = restore_passthroughs text if has_passthroughs

      multiline ? (text.split LF, -1) : text
    end

apply_title_subs(title) click to toggle source

Public: Apply substitutions for titles.

title - The String title to process

returns - A String with title substitutions performed

    # File lib/asciidoctor/substitutors.rb, line 157
    def apply_title_subs(title)
      apply_subs title, TITLE_SUBS
    end

convert_quoted_text(match, type, scope) click to toggle source

Internal: Convert a quoted text region

match - The MatchData for the quoted text region type - The quoting type (single, double, strong, emphasis, monospaced, etc) scope - The scope of the quoting (constrained or unconstrained)

Returns The converted String text for the quoted text region

    # File lib/asciidoctor/substitutors.rb, line 1101
    def convert_quoted_text(match, type, scope)
      if match[0].start_with? RS
        if scope == :constrained && (attrs = match[2])
          unescaped_attrs = %Q([#{attrs}])
        else
          return match[0][1..-1]
        end
      end

      if scope == :constrained
        if unescaped_attrs
          %Q(#{unescaped_attrs}#{Inline.new(self, :quoted, match[3], :type => type).convert})
        else
          if (attrlist = match[2])
            id = (attributes = parse_quoted_text_attributes attrlist).delete 'id'
            type = :unquoted if type == :mark
          end
          %Q(#{match[1]}#{Inline.new(self, :quoted, match[3], :type => type, :id => id, :attributes => attributes).convert})
        end
      else
        if (attrlist = match[1])
          id = (attributes = parse_quoted_text_attributes attrlist).delete 'id'
          type = :unquoted if type == :mark
        end
        Inline.new(self, :quoted, match[2], :type => type, :id => id, :attributes => attributes).convert
      end
    end

do_replacement(m, replacement, restore) click to toggle source

Internal: Substitute replacement text for matched location

returns The String text with the replacement characters substituted

    # File lib/asciidoctor/substitutors.rb, line 430
    def do_replacement m, replacement, restore
      if (captured = m[0]).include? RS
        # we have to use sub since we aren't sure it's the first char
        captured.sub RS, ''
      else
        case restore
        when :none
          replacement
        when :bounding
          %Q(#{m[1]}#{replacement}#{m[2]})
        else # :leading
          %Q(#{m[1]}#{replacement})
        end
      end
    end

extract_passthroughs(text) click to toggle source

Internal: Extract the passthrough text from the document for reinsertion after processing.

text - The String from which to extract passthrough fragements

returns - The text with the passthrough region substituted with placeholders

    # File lib/asciidoctor/substitutors.rb, line 184
    def extract_passthroughs(text)
      compat_mode = @document.compat_mode
      text = text.gsub(InlinePassMacroRx) {
        # alias match for Ruby 1.8.7 compat
        m = $~
        preceding = nil

        if (boundary = m[4]) # $$, ++, or +++
          # skip ++ in compat mode, handled as normal quoted text
          if compat_mode && boundary == '++'
            next m[2] ?
                %Q(#{m[1]}[#{m[2]}]#{m[3]}++#{extract_passthroughs m[5]}++) :
                %Q(#{m[1]}#{m[3]}++#{extract_passthroughs m[5]}++)
          end

          attributes = m[2]
          escape_count = m[3].length
          content = m[5]
          old_behavior = false

          if attributes
            if escape_count > 0
              # NOTE we don't look for nested unconstrained pass macros
              next %(#{m[1]}[#{attributes}]#{RS * (escape_count - 1)}#{boundary}#{m[5]}#{boundary})
            elsif m[1] == RS
              preceding = %Q([#{attributes}])
              attributes = nil
            else
              if boundary == '++' && (attributes.end_with? 'x-')
                old_behavior = true
                attributes = attributes[0...-2]
              end
              attributes = parse_attributes attributes
            end
          elsif escape_count > 0
            # NOTE we don't look for nested unconstrained pass macros
            next %(#{RS * (escape_count - 1)}#{boundary}#{m[5]}#{boundary})
          end
          subs = (boundary == '+++' ? [] : BASIC_SUBS)

          pass_key = @passthroughs.size
          if attributes
            if old_behavior
              @passthroughs[pass_key] = {:text => content, :subs => NORMAL_SUBS, :type => :monospaced, :attributes => attributes}
            else
              @passthroughs[pass_key] = {:text => content, :subs => subs, :type => :unquoted, :attributes => attributes}
            end
          else
            @passthroughs[pass_key] = {:text => content, :subs => subs}
          end
        else # pass:[]
          if m[6] == RS
            # NOTE we don't look for nested pass:[] macros
            next m[0][1..-1]
          end

          @passthroughs[pass_key = @passthroughs.size] = {:text => (unescape_brackets m[8]), :subs => (m[7] ? (resolve_pass_subs m[7]) : [])}
        end

        %Q(#{preceding}#{PASS_START}#{pass_key}#{PASS_END})
      } if (text.include? '++') || (text.include? '$$') || (text.include? 'ss:')

      pass_inline_char1, pass_inline_char2, pass_inline_rx = PassInlineRx[compat_mode]
      text = text.gsub(pass_inline_rx) {
        # alias match for Ruby 1.8.7 compat
        m = $~
        preceding = m[1]
        attributes = m[2]
        escape_mark = RS if m[3].start_with? RS
        format_mark = m[4]
        content = m[5]

        if compat_mode
          old_behavior = true
        else
          if (old_behavior = (attributes && (attributes.end_with? 'x-')))
            attributes = attributes[0...-2]
          end
        end

        if attributes
          if format_mark == '`' && !old_behavior
            next %(#{preceding}[#{attributes}]#{escape_mark}`#{extract_passthroughs content}`)
          end

          if escape_mark
            # honor the escape of the formatting mark
            next %(#{preceding}[#{attributes}]#{m[3][1..-1]})
          elsif preceding == RS
            # honor the escape of the attributes
            preceding = %Q([#{attributes}])
            attributes = nil
          else
            attributes = parse_attributes attributes
          end
        elsif format_mark == '`' && !old_behavior
          next %(#{preceding}#{escape_mark}`#{extract_passthroughs content}`)
        elsif escape_mark
          # honor the escape of the formatting mark
          next %(#{preceding}#{m[3][1..-1]})
        end

        pass_key = @passthroughs.size
        if compat_mode
          @passthroughs[pass_key] = {:text => content, :subs => BASIC_SUBS, :attributes => attributes, :type => :monospaced}
        elsif attributes
          if old_behavior
            subs = (format_mark == '`' ? BASIC_SUBS : NORMAL_SUBS)
            @passthroughs[pass_key] = {:text => content, :subs => subs, :attributes => attributes, :type => :monospaced}
          else
            @passthroughs[pass_key] = {:text => content, :subs => BASIC_SUBS, :attributes => attributes, :type => :unquoted}
          end
        else
          @passthroughs[pass_key] = {:text => content, :subs => BASIC_SUBS}
        end

        %Q(#{preceding}#{PASS_START}#{pass_key}#{PASS_END})
      } if (text.include? pass_inline_char1) || (pass_inline_char2 && (text.include? pass_inline_char2))

      # NOTE we need to do the stem in a subsequent step to allow it to be escaped by the former
      text = text.gsub(InlineStemMacroRx) {
        # alias match for Ruby 1.8.7 compat
        m = $~
        # honor the escape
        if m[0].start_with? RS
          next m[0][1..-1]
        end

        if (type = m[1].to_sym) == :stem
          type = ((default_stem_type = @document.attributes['stem']).nil_or_empty? ? 'asciimath' : default_stem_type).to_sym
        end
        content = unescape_brackets m[3]
        subs = m[2] ? (resolve_pass_subs m[2]) : ((@document.basebackend? 'html') ? BASIC_SUBS : [])
        @passthroughs[pass_key = @passthroughs.size] = {:text => content, :subs => subs, :type => type}
        %Q(#{PASS_START}#{pass_key}#{PASS_END})
      } if (text.include? ':') && ((text.include? 'stem:') || (text.include? 'math:'))

      text
    end

highlight_source(source, process_callouts, highlighter = nil) click to toggle source

Public: Highlight the source code if a source highlighter is defined on the document, otherwise return the text unprocessed

Callout marks are stripped from the source prior to passing it to the highlighter, then later restored in converted form, so they are not incorrectly processed by the source highlighter.

source - the source code String to highlight process_callouts - a Boolean flag indicating whether callout marks should be substituted

returns the highlighted source code, if a source highlighter is defined on the document, otherwise the source with verbatim substituions applied

    # File lib/asciidoctor/substitutors.rb, line 1338
    def highlight_source source, process_callouts, highlighter = nil
      case (highlighter ||= @document.attributes['source-highlighter'])
      when 'coderay'
        unless (highlighter_loaded = defined? ::CodeRay) || @document.attributes['coderay-unavailable']
          if (Helpers.require_library 'coderay', true, :warn).nil?
            # prevent further attempts to load CodeRay
            @document.set_attr 'coderay-unavailable'
          else
            highlighter_loaded = true
          end
        end
      when 'pygments'
        unless (highlighter_loaded = defined? ::Pygments) || @document.attributes['pygments-unavailable']
          if (Helpers.require_library 'pygments', 'pygments.rb', :warn).nil?
            # prevent further attempts to load Pygments
            @document.set_attr 'pygments-unavailable'
          else
            highlighter_loaded = true
          end
        end
      else
        # unknown highlighting library (something is misconfigured if we arrive here)
        highlighter_loaded = false
      end

      return sub_source source, process_callouts unless highlighter_loaded

      lineno = 0
      callout_on_last = false
      if process_callouts
        callout_marks = {}
        last = -1
        # FIXME cache this dynamic regex
        callout_rx = (attr? 'line-comment') ? /(?:#{::Regexp.escape(attr 'line-comment')} )?#{CalloutExtractRxt}/ : CalloutExtractRx
        # extract callout marks, indexed by line number
        source = source.split(LF, -1).map {|line|
          lineno = lineno + 1
          line.gsub(callout_rx) {
            # alias match for Ruby 1.8.7 compat
            m = $~
            # honor the escape
            if m[1] == RS
              # we have to use sub since we aren't sure it's the first char
              m[0].sub RS, ''
            else
              (callout_marks[lineno] ||= []) << m[3]
              last = lineno
              nil
            end
          }
        } * LF
        callout_on_last = (last == lineno)
        callout_marks = nil if callout_marks.empty?
      else
        callout_marks = nil
      end

      linenums_mode = nil
      highlight_lines = nil

      case highlighter
      when 'coderay'
        if (linenums_mode = (attr? 'linenums', nil, false) ? (@document.attributes['coderay-linenums-mode'] || :table).to_sym : nil)
          if attr? 'highlight', nil, false
            highlight_lines = resolve_highlight_lines(attr 'highlight', nil, false)
          end
        end
        result = ::CodeRay::Duo[attr('language', :text, false).to_sym, :html, {
            :css => (@document.attributes['coderay-css'] || :class).to_sym,
            :line_numbers => linenums_mode,
            :line_number_anchors => false,
            :highlight_lines => highlight_lines,
            :bold_every => false}].highlight source
      when 'pygments'
        lexer = ::Pygments::Lexer.find_by_alias(attr 'language', 'text', false) || ::Pygments::Lexer.find_by_mimetype('text/plain')
        opts = { :cssclass => 'pyhl', :classprefix => 'tok-', :nobackground => true, :stripnl => false }
        opts[:startinline] = !(option? 'mixed') if lexer.name == 'PHP'
        unless (@document.attributes['pygments-css'] || 'class') == 'class'
          opts[:noclasses] = true
          opts[:style] = (@document.attributes['pygments-style'] || Stylesheets::DEFAULT_PYGMENTS_STYLE)
        end
        if attr? 'highlight', nil, false
          unless (highlight_lines = resolve_highlight_lines(attr 'highlight', nil, false)).empty?
            opts[:hl_lines] = highlight_lines * ' '
          end
        end
        # TODO we could add the line numbers in ourselves instead of having to strip out the junk
        if (attr? 'linenums', nil, false) && (opts[:linenos] = @document.attributes['pygments-linenums-mode'] || 'table') == 'table'
          linenums_mode = :table
          result = lexer.highlight(source, :options => opts).sub(PygmentsWrapperDivRx, '\1').gsub(PygmentsWrapperPreRx, '\1')
        else
          if PygmentsWrapperPreRx =~ (result = lexer.highlight(source, :options => opts))
            result = $1
          end
        end
      end

      # fix passthrough placeholders that got caught up in syntax highlighting
      unless @passthroughs.empty?
        result = result.gsub HighlightedPassSlotRx, %Q(#{PASS_START}\\1#{PASS_END})
      end

      if process_callouts && callout_marks
        lineno = 0
        reached_code = linenums_mode != :table
        result.split(LF, -1).map {|line|
          unless reached_code
            next line unless line.include?('<td class="code">')
            reached_code = true
          end
          lineno += 1
          if (conums = callout_marks.delete(lineno))
            tail = nil
            if callout_on_last && callout_marks.empty? && linenums_mode == :table
              if highlighter == 'coderay' && (pos = line.index '</pre>')
                line, tail = (line.slice 0, pos), (line.slice pos, line.length)
              elsif highlighter == 'pygments' && (pos = line.start_with? '</td>')
                line, tail = '', line
              end
            end
            if conums.size == 1
              %Q(#{line}#{Inline.new(self, :callout, conums[0], :id => @document.callouts.read_next_id).convert}#{tail})
            else
              conums_markup = conums.map {|conum| Inline.new(self, :callout, conum, :id => @document.callouts.read_next_id).convert } * ' '
              %Q(#{line}#{conums_markup}#{tail})
            end
          else
            line
          end
        } * LF
      else
        result
      end
    end

lock_in_subs() click to toggle source

Internal: Lock-in the substitutions for this block

Looks for an attribute named “subs”. If present, resolves substitutions from the value of that attribute and assigns them to the subs property on this block. Otherwise, uses the substitutions assigned to the default_subs property, if specified, or selects a default set of substitutions based on the content model of the block.

Returns The Array of resolved substitutions now assigned to this block

    # File lib/asciidoctor/substitutors.rb, line 1520
    def lock_in_subs
      unless (default_subs = @default_subs)
        case @content_model
        when :simple
          default_subs = NORMAL_SUBS
        when :verbatim
          if @context == :listing || (@context == :literal && !(option? 'listparagraph'))
            default_subs = VERBATIM_SUBS
          elsif @context == :verse
            default_subs = NORMAL_SUBS
          else
            default_subs = BASIC_SUBS
          end
        when :raw
          # TODO make pass subs a compliance setting; AsciiDoc Python performs :attributes and :macros on a pass block
          default_subs = @context == :stem ? BASIC_SUBS : NONE_SUBS
        else
          return @subs
        end
      end

      @subs = (custom_subs = @attributes['subs']) ? (resolve_block_subs custom_subs, default_subs, @context) : default_subs.dup

      # QUESION delegate this logic to a method?
      if @context == :listing && @style == 'source' && (@attributes.key? 'language') && (@document.basebackend? 'html') &&
          (SUB_HIGHLIGHT.include? @document.attributes['source-highlighter']) && (idx = @subs.index :specialcharacters)
        @subs[idx] = :highlight
      end

      @subs
    end

normalize_string(str, unescape_brackets = false) click to toggle source

Internal: Strip bounding whitespace and fold endlines

    # File lib/asciidoctor/substitutors.rb, line 1201
    def normalize_string str, unescape_brackets = false
      unless str.empty?
        str = str.strip.tr LF, ' '
        str = str.gsub ESC_R_SB, R_SB if unescape_brackets && (str.include? R_SB)
      end
      str
    end

parse_attributes(attrline, posattrs = ['role'], opts = {}) click to toggle source

Internal: Parse the attributes in the attribute line

attrline - A String of unprocessed attributes (key/value pairs) posattrs - The keys for positional attributes

returns nil if attrline is nil, an empty Hash if attrline is empty, otherwise a Hash of parsed attributes

    # File lib/asciidoctor/substitutors.rb, line 1177
    def parse_attributes(attrline, posattrs = ['role'], opts = {})
      return unless attrline
      return {} if attrline.empty?
      attrline = @document.sub_attributes(attrline) if opts[:sub_input] && (attrline.include? ATTR_REF_HEAD)
      attrline = unescape_bracketed_text(attrline) if opts[:unescape_input]
      # substitutions are only performed on attribute values if block is not nil
      block = opts.fetch(:sub_result, true) ? self : nil
      if (into = opts[:into])
        AttributeList.new(attrline, block).parse_into(into, posattrs)
      else
        AttributeList.new(attrline, block).parse(posattrs)
      end
    end

parse_quoted_text_attributes(str) click to toggle source

Internal: Parse the attributes that are defined on quoted (aka formatted) text

str - A non-nil String of unprocessed attributes;

    space-separated roles (e.g., role1 role2) or the id/role shorthand syntax (e.g., #idname.role)

Returns a Hash of attributes (role and id only)

    # File lib/asciidoctor/substitutors.rb, line 1135
    def parse_quoted_text_attributes str
      # NOTE attributes are typically resolved after quoted text, so substitute eagerly
      str = sub_attributes str if str.include? ATTR_REF_HEAD
      # for compliance, only consider first positional attribute
      str = str.slice 0, (str.index ',') if str.include? ','

      if (str = str.strip).empty?
        {}
      elsif (str.start_with? '.', '#') && Compliance.shorthand_property_syntax
        segments = str.split('#', 2)

        if segments.size > 1
          id, *more_roles = segments[1].split('.')
        else
          id = nil
          more_roles = []
        end

        roles = segments[0].empty? ? [] : segments[0].split('.')
        if roles.size > 1
          roles.shift
        end

        if more_roles.size > 0
          roles.concat more_roles
        end

        attrs = {}
        attrs['id'] = id if id
        attrs['role'] = roles * ' ' unless roles.empty?
        attrs
      else
        {'role' => str}
      end
    end

resolve_block_subs(subs, defaults, subject) click to toggle source

    # File lib/asciidoctor/substitutors.rb, line 1318
    def resolve_block_subs subs, defaults, subject
      resolve_subs subs, :block, defaults, subject
    end

resolve_highlight_lines(spec) click to toggle source

e.g., highlight=“1-5, !2, 10” or highlight=1-5;!2,10

    # File lib/asciidoctor/substitutors.rb, line 1474
    def resolve_highlight_lines spec
      lines = []
      ((spec.include? ' ') ? (spec.delete ' ') : spec).split(DataDelimiterRx).map do |entry|
        negate = false
        if entry.start_with? '!'
          entry = entry[1..-1]
          negate = true
        end
        if entry.include? '-'
          s, e = entry.split '-', 2
          line_nums = (s.to_i..e.to_i).to_a
          if negate
            lines -= line_nums
          else
            lines.concat line_nums
          end
        else
          if negate
            lines.delete entry.to_i
          else
            lines << entry.to_i
          end
        end
      end
      lines.sort.uniq
    end

resolve_pass_subs(subs) click to toggle source

    # File lib/asciidoctor/substitutors.rb, line 1322
    def resolve_pass_subs subs
      resolve_subs subs, :inline, nil, 'passthrough macro'
    end

resolve_subs(subs, type = :block, defaults = nil, subject = nil) click to toggle source

Internal: Resolve the list of comma-delimited subs against the possible options.

subs - A comma-delimited String of substitution aliases

returns An Array of Symbols representing the substitution operation

    # File lib/asciidoctor/substitutors.rb, line 1256
    def resolve_subs subs, type = :block, defaults = nil, subject = nil
      return [] if subs.nil_or_empty?
      # QUESTION should we store candidates as a Set instead of an Array?
      candidates = nil
      subs = subs.delete ' ' if subs.include? ' '
      modifiers_present = SubModifierSniffRx.match? subs
      subs.split(',').each do |key|
        modifier_operation = nil
        if modifiers_present
          if (first = key.chr) == '+'
            modifier_operation = :append
            key = key[1..-1]
          elsif first == '-'
            modifier_operation = :remove
            key = key[1..-1]
          elsif key.end_with? '+'
            modifier_operation = :prepend
            key = key.chop
          end
        end
        key = key.to_sym
        # special case to disable callouts for inline subs
        if type == :inline && (key == :verbatim || key == :v)
          resolved_keys = BASIC_SUBS
        elsif SUB_GROUPS.key? key
          resolved_keys = SUB_GROUPS[key]
        elsif type == :inline && key.length == 1 && (SUB_HINTS.key? key)
          resolved_key = SUB_HINTS[key]
          if (candidate = SUB_GROUPS[resolved_key])
            resolved_keys = candidate
          else
            resolved_keys = [resolved_key]
          end
        else
          resolved_keys = [key]
        end

        if modifier_operation
          candidates ||= (defaults ? defaults.dup : [])
          case modifier_operation
          when :append
            candidates += resolved_keys
          when :prepend
            candidates = resolved_keys + candidates
          when :remove
            candidates -= resolved_keys
          end
        else
          candidates ||= []
          candidates += resolved_keys
        end
      end
      return [] unless candidates
      # weed out invalid options and remove duplicates (order is preserved; first occurence wins)
      resolved = candidates & SUB_OPTIONS[type]
      unless (candidates - resolved).empty?
        invalid = candidates - resolved
        warn %Q(asciidoctor: WARNING: invalid substitution type#{invalid.size > 1 ? 's' : ''}#{subject ? ' for ' : nil}#{subject}: #{invalid * ', '})
      end
      resolved
    end

restore_passthroughs(text, outer = true) click to toggle source

Internal: Restore the passthrough text by reinserting into the placeholder positions

text - The String text into which to restore the passthrough text outer - A Boolean indicating whether we are in the outer call (default: true)

returns The String text with the passthrough text restored

    # File lib/asciidoctor/substitutors.rb, line 330
    def restore_passthroughs text, outer = true
      if outer && (@passthroughs.empty? || !text.include?(PASS_START))
        return text
      end

      text.gsub(PassSlotRx) {
        # NOTE we can't remove entry from map because placeholder may have been duplicated by other substitutions
        pass = @passthroughs[$1.to_i]
        subbed_text = apply_subs(pass[:text], pass[:subs])
        if (type = pass[:type])
          subbed_text = Inline.new(self, :quoted, subbed_text, :type => type, :attributes => pass[:attributes]).convert
        end
        subbed_text.include?(PASS_START) ? restore_passthroughs(subbed_text, false) : subbed_text
      }
    ensure
      # free memory if in outer call...we don't need these anymore
      @passthroughs.clear if outer
    end

split_simple_csv(str) click to toggle source

Internal: Split text formatted as CSV with support for double-quoted values (in which commas are ignored)

    # File lib/asciidoctor/substitutors.rb, line 1220
    def split_simple_csv str
      if str.empty?
        values = []
      elsif str.include? '"'
        values = []
        current = []
        quote_open = false
        str.each_char do |c|
          case c
          when ','
            if quote_open
              current << c
            else
              values << current.join.strip
              current = []
            end
          when '"'
            quote_open = !quote_open
          else
            current << c
          end
        end

        values << current.join.strip
      else
        values = str.split(',').map {|it| it.strip }
      end

      values
    end

sub_attributes(data, opts = {}) click to toggle source

Public: Substitute attribute references

Attribute references are in the format +{name}+.

If an attribute referenced in the line is missing, the line is dropped.

text - The String text to process

returns The String text with the attribute references replaced with attribute values

    # File lib/asciidoctor/substitutors.rb, line 459
    def sub_attributes data, opts = {}
      # normalizes data type to an array (string becomes single-element array)
      data = [data] if (input_is_string = ::String === data)
      doc_attrs, result = @document.attributes, []
      data.each do |line|
        reject = reject_if_empty = false
        line = line.gsub(AttributeReferenceRx) {
          # escaped attribute, return unescaped
          if $1 == RS || $4 == RS
            %Q({#{$2}})
          elsif $3
            case (args = $2.split ':', 3).shift
            when 'set'
              _, value = Parser.store_attribute args[0], args[1] || '', @document
              # since this is an assignment, only drop-line applies here (skip and drop imply the same result)
              if (doc_attrs.fetch 'attribute-undefined', Compliance.attribute_undefined) == 'drop-line'
                reject = true
                break ''
              end unless value
              reject_if_empty = true
              ''
            when 'counter2'
              @document.counter(*args)
              reject_if_empty = true
              ''
            else # 'counter'
              @document.counter(*args)
            end
          elsif doc_attrs.key?(key = $2.downcase)
            doc_attrs[key]
          elsif INTRINSIC_ATTRIBUTES.key? key
            INTRINSIC_ATTRIBUTES[key]
          else
            case (attribute_missing ||= opts[:attribute_missing] || (doc_attrs.fetch 'attribute-missing', Compliance.attribute_missing))
            when 'drop'
              # QUESTION should we warn in this case?
              reject_if_empty = true
              ''
            when 'drop-line'
              warn %Q(asciidoctor: WARNING: dropping line containing reference to missing attribute: #{key})
              reject = true
              break ''
            when 'warn'
              warn %Q(asciidoctor: WARNING: skipping reference to missing attribute: #{key})
              $&
            else # 'skip'
              $&
            end
          end
        } if line.include? ATTR_REF_HEAD

        result << line unless reject || (reject_if_empty && line.empty?)
      end

      input_is_string ? result * LF : result
    end

sub_callouts(text) click to toggle source

Public: Substitute callout source references

text - The String text to process

Returns the converted String text

    # File lib/asciidoctor/substitutors.rb, line 1062
    def sub_callouts(text)
      # FIXME cache this dynamic regex
      callout_rx = (attr? 'line-comment') ? /(?:#{::Regexp.escape(attr 'line-comment')} )?#{CalloutSourceRxt}/ : CalloutSourceRx
      text.gsub(callout_rx) {
        if $1
          # we have to use sub since we aren't sure it's the first char
          next $&.sub(RS, '')
        end
        Inline.new(self, :callout, $3, :id => @document.callouts.read_next_id).convert
      }
    end

sub_inline_anchors(text, found = nil) click to toggle source

Internal: Substitute normal and bibliographic anchors

    # File lib/asciidoctor/substitutors.rb, line 956
    def sub_inline_anchors(text, found = nil)
      if @context == :list_item && @parent.style == 'bibliography'
        text = text.sub(InlineBiblioAnchorRx) {
          # NOTE target property on :bibref is deprecated
          Inline.new(self, :anchor, %Q([#{$2 || $1}]), :type => :bibref, :id => $1, :target => $1).convert
        }
      end

      if ((!found || found[:square_bracket]) && text.include?('[[')) ||
          ((!found || found[:macroish]) && text.include?('or:'))
        text = text.gsub(InlineAnchorRx) {
          # honor the escape
          next $&.slice 1, $&.length if $1
          # NOTE reftext is only relevant for DocBook output; used as value of xreflabel attribute
          if (id = $2)
            reftext = $3
          else
            id = $4
            if (reftext = $5) && (reftext.include? R_SB)
              reftext = reftext.gsub ESC_R_SB, R_SB
            end
          end
          # NOTE target property on :ref is deprecated
          Inline.new(self, :anchor, reftext, :type => :ref, :id => id, :target => id).convert
        }
      end

      text
    end

sub_inline_xrefs(text, found = nil) click to toggle source

Internal: Substitute cross reference links

    # File lib/asciidoctor/substitutors.rb, line 987
    def sub_inline_xrefs(text, found = nil)
      if ((found ? found[:macroish] : (text.include? '[')) && (text.include? 'xref:')) ||
          ((text.include? '&') && (text.include? '&lt;&lt;'))
        text = text.gsub(InlineXrefMacroRx) {
          # alias match for Ruby 1.8.7 compat
          m = $~
          # honor the escape
          if m[0].start_with? RS
            next m[0][1..-1]
          end
          if (id = m[1])
            id, reftext = id.split ',', 2
            reftext = reftext.lstrip if reftext
          else
            id = m[2]
            if (reftext = m[3]) && (reftext.include? R_SB)
              reftext = reftext.gsub ESC_R_SB, R_SB
            end
          end

          if (hash_idx = id.index '#')
            if hash_idx > 0
              if (fragment_len = id.length - hash_idx - 1) > 0
                path, fragment = (id.slice 0, hash_idx), (id.slice hash_idx + 1, fragment_len)
              else
                path, fragment = (id.slice 0, hash_idx), nil
              end
            else
              target, path, fragment = id, nil, (id.slice 1, id.length)
            end
          else
            path, fragment = nil, id
          end

          # handles: #id
          if target
            refid = fragment
          # handles: path#, path.adoc#, path#id, or path.adoc#id
          elsif path
            if (ext_idx = path.rindex '.') && ASCIIDOC_EXTENSIONS[path.slice ext_idx, path.length]
              path = path.slice 0, ext_idx
            end
            # the referenced path is this document, or its contents has been included in this document
            if @document.attributes['docname'] == path || @document.catalog[:includes].include?(path)
              refid, path, target = fragment, nil, %Q(##{fragment})
            else
              refid = fragment ? %Q(#{path}##{fragment}) : path
              path = %Q(#{@document.attributes['relfileprefix']}#{path}#{@document.attributes.fetch 'outfilesuffix', '.html'})
              target = fragment ? %Q(#{path}##{fragment}) : path
            end
          # handles: id or Section Title
          else
            # resolve fragment as reftext if it's not a known ID and resembles reftext (includes space or has uppercase char)
            unless @document.catalog[:ids].key? fragment
              if ((fragment.include? ' ') || fragment.downcase != fragment) &&
                  (resolved_id = @document.catalog[:ids].key fragment)
                fragment = resolved_id
              elsif $VERBOSE
                warn %Q(asciidoctor: WARNING: invalid reference: #{fragment})
              end
            end
            refid, target = fragment, %Q(##{fragment})
          end
          Inline.new(self, :anchor, reftext, :type => :xref, :target => target, :attributes => {'path' => path, 'fragment' => fragment, 'refid' => refid}).convert
        }
      end

      text
    end

sub_macros(source) click to toggle source

Public: Substitute inline macros (e.g., links, images, etc)

Replace inline macros, which may span multiple lines, in the provided text

source - The String text to process

returns The converted String text

    # File lib/asciidoctor/substitutors.rb, line 523
    def sub_macros(source)
      #return source if source.nil_or_empty?
      # some look ahead assertions to cut unnecessary regex calls
      found = {}
      found_square_bracket = found[:square_bracket] = (source.include? '[')
      found_colon = source.include? ':'
      found_macroish = found[:macroish] = found_square_bracket && found_colon
      found_macroish_short = found_macroish && (source.include? ':[')
      doc_attrs = @document.attributes
      use_link_attrs = doc_attrs.key? 'linkattrs'
      result = source

      if doc_attrs.key? 'experimental'
        if found_macroish_short && ((result.include? 'kbd:') || (result.include? 'btn:'))
          result = result.gsub(InlineKbdBtnMacroRx) {
            # honor the escape
            if $1
              $&.slice 1, $&.length
            elsif $2 == 'kbd'
              if (keys = $3.strip).include? R_SB
                keys = keys.gsub ESC_R_SB, R_SB
              end
              if keys.length > 1 && (delim_idx = (delim_idx = keys.index ',', 1) ?
                  [delim_idx, (keys.index '+', 1)].compact.min : (keys.index '+', 1))
                delim = keys.slice delim_idx, 1
                # NOTE handle special case where keys ends with delimiter (e.g., Ctrl++ or Ctrl,,)
                if keys.end_with? delim
                  keys = (keys.chop.split delim, -1).map {|key| key.strip }
                  keys[-1] = %Q(#{keys[-1]}#{delim})
                else
                  keys = keys.split(delim).map {|key| key.strip }
                end
              else
                keys = [keys]
              end
              (Inline.new self, :kbd, nil, :attributes => { 'keys' => keys }).convert
            else # $2 == 'btn'
              (Inline.new self, :button, (unescape_bracketed_text $3)).convert
            end
          }
        end

        if found_macroish && (result.include? 'menu:')
          result = result.gsub(InlineMenuMacroRx) {
            # alias match for Ruby 1.8.7 compat
            m = $~
            # honor the escape
            if (captured = m[0]).start_with? RS
              next captured[1..-1]
            end

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

            if items
              items = items.gsub ESC_R_SB, R_SB if items.include? R_SB
              if (delim = items.include?('&gt;') ? '&gt;' : (items.include?(',') ? ',' : nil))
                submenus = items.split(delim).map {|it| it.strip }
                menuitem = submenus.pop
              else
                submenus, menuitem = [], items.rstrip
              end
            else
              submenus, menuitem = [], nil
            end

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

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

            input = m[1]

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

      # FIXME this location is somewhat arbitrary, probably need to be able to control ordering
      # TODO this handling needs some cleanup
      if (extensions = @document.extensions) && extensions.inline_macros? # && found_macroish
        extensions.inline_macros.each do |extension|
          result = result.gsub(extension.instance.regexp) {
            # alias match for Ruby 1.8.7 compat
            m = $~
            # honor the escape
            if m[0].start_with? RS
              next m[0][1..-1]
            end

            if (m.names rescue []).empty?
              target, content, extconf = m[1], m[2], extension.config
            else
              target, content, extconf = (m[:target] rescue nil), (m[:content] rescue nil), extension.config
            end
            attributes = (attributes = extconf[:default_attrs]) ? attributes.dup : {}
            if content.nil_or_empty?
              attributes['text'] = content if content && extconf[:content_model] != :attributes
            else
              content = unescape_bracketed_text content
              if extconf[:content_model] == :attributes
                # QUESTION should we store the text in the _text key?
                # QUESTION why is the sub_result option false? why isn't the unescape_input option true?
                parse_attributes content, extconf[:pos_attrs] || [], :sub_result => false, :into => attributes
              else
                attributes['text'] = content
              end
            end
            # NOTE use content if target is not set (short form only); deprecated - remove in 1.6.0
            replacement = extension.process_method[self, target || content, attributes]
            Inline === replacement ? replacement.convert : replacement
          }
        end
      end

      if found_macroish && ((result.include? 'image:') || (result.include? 'icon:'))
        # image:filename.png[Alt Text]
        result = result.gsub(InlineImageMacroRx) {
          # alias match for Ruby 1.8.7 compat
          m = $~
          # honor the escape
          if (captured = $&).start_with? RS
            next captured[1..-1]
          end

          if captured.start_with? 'icon:'
            type, posattrs = 'icon', ['size']
          else
            type, posattrs = 'image', ['alt', 'width', 'height']
          end
          if (target = m[1]).include? ATTR_REF_HEAD
            # TODO remove this special case once titles use normal substitution order
            target = sub_attributes target
          end
          @document.register(:images, target) unless type == 'icon'
          attrs = parse_attributes(m[2], posattrs, :unescape_input => true)
          attrs['alt'] ||= (attrs['default-alt'] = Helpers.basename(target, true).tr('_-', ' '))
          Inline.new(self, :image, nil, :type => type, :target => target, :attributes => attrs).convert
        }
      end

      if ((result.include? '((') && (result.include? '))')) ||
          (found_macroish_short && (result.include? 'indexterm'))
        # (((Tigers,Big cats)))
        # indexterm:[Tigers,Big cats]
        # ((Tigers))
        # indexterm2:[Tigers]
        result = result.gsub(InlineIndextermMacroRx) {
          # alias match for Ruby 1.8.7 compat
          m = $~

          # honor the escape
          if m[0].start_with? RS
            next m[0][1..-1]
          end

          case m[1]
          when 'indexterm'
            # indexterm:[Tigers,Big cats]
            terms = split_simple_csv(normalize_string m[2], true)
            @document.register :indexterms, terms
            (Inline.new self, :indexterm, nil, :attributes => { 'terms' => terms }).convert
          when 'indexterm2'
            # indexterm2:[Tigers]
            term = normalize_string m[2], true
            @document.register :indexterms, [term]
            (Inline.new self, :indexterm, term, :type => :visible).convert
          else
            text, visible, before, after = m[3], true, nil, nil
            if text.start_with? '('
              if text.end_with? ')'
                text, visible = (text.slice 1, text.length - 2), false
              else
                text, before, after = (text.slice 1, text.length - 1), '(', ''
              end
            elsif text.end_with? ')'
              if text.start_with? '('
                text, visible = (text.slice 1, text.length - 2), false
              else
                text, before, after = (text.slice 0, text.length - 1), '', ')'
              end
            end
            if visible
              # ((Tigers))
              term = normalize_string text
              @document.register :indexterms, [term]
              result = (Inline.new self, :indexterm, term, :type => :visible).convert
            else
              # (((Tigers,Big cats)))
              terms = split_simple_csv(normalize_string text)
              @document.register :indexterms, terms
              result = (Inline.new self, :indexterm, nil, :attributes => { 'terms' => terms }).convert
            end
            before ? %Q(#{before}#{result}#{after}) : result
          end
        }
      end

      if found_colon && (result.include? '://')
        # inline urls, target[text] (optionally prefixed with link: and optionally surrounded by <>)
        result = result.gsub(LinkInlineRx) {
          # alias match for Ruby 1.8.7 compat
          m = $~
          # honor the escape
          if m[2].start_with? RS
            next %(#{m[1]}#{m[2][1..-1]}#{m[3]})
          end
          # NOTE if text is non-nil, then we've matched a formal macro (i.e., trailing square brackets)
          prefix, target, text, suffix = m[1], m[2], (macro = m[3]) || '', ''
          if prefix == 'link:'
            if macro
              prefix = ''
            else
              # invalid macro syntax (link: prefix w/o trailing square brackets)
              # we probably shouldn't even get here...our regex is doing too much
              next m[0]
            end
          end
          unless macro || UriTerminatorRx !~ target
            case $&
            when ')'
              # strip trailing )
              target = target.chop
              suffix = ')'
            when ';'
              # strip <> around URI
              if prefix.start_with?('&lt;') && target.end_with?('&gt;')
                prefix = prefix[4..-1]
                target = target[0...-4]
              else
                # strip trailing ;
                # check for trailing );
                if (target = target.chop).end_with?(')')
                  target = target.chop
                  suffix = ');'
                else
                  suffix = ';'
                end
              end
            when ':'
              # strip trailing :
              # check for trailing ):
              if (target = target.chop).end_with?(')')
                target = target.chop
                suffix = '):'
              else
                suffix = ':'
              end
            end
          end

          attrs, link_opts = nil, { :type => :link }
          unless text.empty?
            text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
            if use_link_attrs && ((text.start_with? '"') || ((text.include? ',') && (text.include? '=')))
              attrs = parse_attributes text, []
              link_opts[:id] = attrs.delete 'id' if attrs.key? 'id'
              text = attrs[1] || ''
            end

            # TODO enable in Asciidoctor 1.6.x
            # support pipe-separated text and title
            #unless attrs && (attrs.key? 'title')
            #  if text.include? '|'
            #    attrs ||= {}
            #    text, attrs['title'] = text.split '|', 2
            #  end
            #end

            if text.end_with? '^'
              text = text.chop
              if attrs
                attrs['window'] ||= '_blank'
              else
                attrs = { 'window' => '_blank' }
              end
            end
          end

          if text.empty?
            text = (doc_attrs.key? 'hide-uri-scheme') ? (target.sub UriSniffRx, '') : target
            if attrs
              attrs['role'] = (attrs.key? 'role') ? %Q(bare #{attrs['role']}) : 'bare'
            else
              attrs = { 'role' => 'bare' }
            end
          end

          @document.register :links, (link_opts[:target] = target)
          link_opts[:attributes] = attrs if attrs
          %Q(#{prefix}#{Inline.new(self, :anchor, text, link_opts).convert}#{suffix})
        }
      end

      if found_macroish && ((result.include? 'link:') || (result.include? 'mailto:'))
        # inline link macros, link:target[text]
        result = result.gsub(InlineLinkMacroRx) {
          # alias match for Ruby 1.8.7 compat
          m = $~
          # honor the escape
          if m[0].start_with? RS
            next m[0][1..-1]
          end
          target = (mailto = m[1]) ? %Q(mailto:#{m[2]}) : m[2]
          attrs, link_opts = nil, { :type => :link }
          unless (text = m[3]).empty?
            text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
            if use_link_attrs && ((text.start_with? '"') || ((text.include? ',') && (mailto || (text.include? '='))))
              attrs = parse_attributes text, []
              link_opts[:id] = attrs.delete 'id' if attrs.key? 'id'
              if mailto
                if attrs.key? 2
                  if attrs.key? 3
                    target = %Q(#{target}?subject=#{Helpers.uri_encode attrs[2]}&amp;body=#{Helpers.uri_encode attrs[3]})
                  else
                    target = %Q(#{target}?subject=#{Helpers.uri_encode attrs[2]})
                  end
                end
              end
              text = attrs[1] || ''
            end

            # TODO enable in Asciidoctor 1.6.x
            # support pipe-separated text and title
            #unless attrs && (attrs.key? 'title')
            #  if text.include? '|'
            #    attrs ||= {}
            #    text, attrs['title'] = text.split '|', 2
            #  end
            #end

            if text.end_with? '^'
              text = text.chop
              if attrs
                attrs['window'] ||= '_blank'
              else
                attrs = { 'window' => '_blank' }
              end
            end
          end

          if text.empty?
            # mailto is a special case, already processed
            if mailto
              text = m[2]
            else
              text = (doc_attrs.key? 'hide-uri-scheme') ? (target.sub UriSniffRx, '') : target
              if attrs
                attrs['role'] = (attrs.key? 'role') ? %Q(bare #{attrs['role']}) : 'bare'
              else
                attrs = { 'role' => 'bare' }
              end
            end
          end

          # QUESTION should a mailto be registered as an e-mail address?
          @document.register :links, (link_opts[:target] = target)
          link_opts[:attributes] = attrs if attrs
          Inline.new(self, :anchor, text, link_opts).convert
        }
      end

      if result.include? '@'
        result = result.gsub(EmailInlineRx) {
          address, tip = $&, $1
          if tip
            next (tip == RS ? address[1..-1] : address)
          end

          target = %Q(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).convert
        }
      end

      if found_macroish_short && (result.include? 'footnote')
        result = result.gsub(InlineFootnoteMacroRx) {
          # alias match for Ruby 1.8.7 compat
          m = $~
          # honor the escape
          if m[0].start_with? RS
            next m[0][1..-1]
          end
          if m[1] == 'footnote'
            id = nil
            # REVIEW it's a dirty job, but somebody's gotta do it
            text = restore_passthroughs(sub_inline_xrefs(sub_inline_anchors(normalize_string m[2], true)), false)
            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)
            id = id.strip
            if text
              # REVIEW it's a dirty job, but somebody's gotta do it
              text = restore_passthroughs(sub_inline_xrefs(sub_inline_anchors(normalize_string text, true)), false)
              index = @document.counter('footnote-number')
              @document.register(:footnotes, Document::Footnote.new(index, id, text))
              type = :ref
              target = nil
            else
              if (footnote = @document.footnotes.find {|fn| fn.id == id })
                index = footnote.index
                text = footnote.text
              else
                index = nil
                text = id
              end
              target = id
              id = nil
              type = :xref
            end
          end
          Inline.new(self, :footnote, text, :attributes => {'index' => index}, :id => id, :target => target, :type => type).convert
        }
      end

      sub_inline_xrefs(sub_inline_anchors(result, found), found)
    end

sub_post_replacements(text) click to toggle source

Public: Substitute post replacements

text - The String text to process

Returns the converted String text

    # File lib/asciidoctor/substitutors.rb, line 1079
    def sub_post_replacements(text)
      if (@document.attributes.key? 'hardbreaks') || (@attributes.key? 'hardbreaks-option')
        lines = text.split LF, -1
        return text if lines.size < 2
        last = lines.pop
        (lines.map {|line|
          Inline.new(self, :break, (line.end_with? HARD_LINE_BREAK) ? (line.slice 0, line.length - 2) : line, :type => :line).convert
        } << last) * LF
      elsif (text.include? PLUS) && (text.include? HARD_LINE_BREAK)
        text.gsub(HardLineBreakRx) { Inline.new(self, :break, $1, :type => :line).convert }
      else
        text
      end
    end

sub_quotes(text) click to toggle source

    # File lib/asciidoctor/substitutors.rb, line 351
    def sub_quotes text
      if QuotedTextSniffRx[compat = @document.compat_mode].match? text
        QUOTE_SUBS[compat].each do |type, scope, pattern|
          text = text.gsub(pattern) { convert_quoted_text $~, type, scope }
        end
      end
      text
    end

sub_replacements(text) click to toggle source

    # File lib/asciidoctor/substitutors.rb, line 360
    def sub_replacements text
      if ReplaceableTextRx.match? text
        REPLACEMENTS.each do |pattern, replacement, restore|
          text = text.gsub(pattern) { do_replacement $~, replacement, restore }
        end
      end
      text
    end

sub_source(source, process_callouts) click to toggle source

Public: Apply verbatim substitutions on source (for use when highlighting is disabled).

source - the source code String on which to apply verbatim substitutions process_callouts - a Boolean flag indicating whether callout marks should be substituted

returns the substituted source

    # File lib/asciidoctor/substitutors.rb, line 1507
    def sub_source source, process_callouts
      process_callouts ? sub_callouts(sub_specialchars source) : (sub_specialchars source)
    end

sub_specialcharacters(text)
Alias for: sub_specialchars
sub_specialchars(text) click to toggle source

    # File lib/asciidoctor/substitutors.rb, line 369
    def sub_specialchars text
      (text.include? '<') || (text.include? '&') || (text.include? '>') ? (text.gsub SpecialCharsRx, SpecialCharsTr) : text
    end

Also aliased as: sub_specialcharacters
unescape_bracketed_text(text) click to toggle source

Internal: Strip bounding whitespace, fold endlines and unescaped closing square brackets from text extracted from brackets

    # File lib/asciidoctor/substitutors.rb, line 1193
    def unescape_bracketed_text text
      if (text = text.strip.tr LF, ' ').include? R_SB
        text = text.gsub ESC_R_SB, R_SB
      end unless text.empty?
      text
    end

unescape_brackets(str) click to toggle source

Internal: Unescape closing square brackets. Intended for text extracted from square brackets.

    # File lib/asciidoctor/substitutors.rb, line 1211
    def unescape_brackets str
      if str.include? RS
        str = str.gsub ESC_R_SB, R_SB
      end unless str.empty?
      str
    end