module TextileParser

Constants

BQ_LEFT
RX_SPACE_CHARS

Ruby s does not match extra unicode space characters.

RX_URL
SYMS

Symbol table, in operator precedence order:

0. Symbol name.
1. Start string for optimized matching.
2. Complete match definition.
SYM_TO_INDEX
SYM_TO_REGEX

Public Instance Methods

find_syms(text) click to toggle source
# File lib/textile/parser.rb, line 13
def find_syms(text)
  # Find possible symbol matches
  syms = SYM_TO_INDEX.map    { |sym, index| [sym, text.index(index)] }
                     .reject { |sym, index| index.nil? }

  # Sort by starting position - closer is better
  syms = syms.sort_by{ |x| x[1] }

  # Get associated regexps and find first
  matchdata = nil
  match = syms.map    { |sym, index| [sym, SYM_TO_REGEX[sym]] }
              .detect { |sym, re| matchdata = re.match(text) }

  # [sym, matchdata]
  [match[0], matchdata] if match
end
operand(ary, text) click to toggle source
# File lib/textile/parser.rb, line 30
def operand(ary, text)
  sym, md = find_syms(text)
  if sym.nil? || md.nil?
    # No match, consume entire string.
    return ary << TextNode.new(text.slice!(0 .. text.length))
  end

  # Consume string before match.
  if md.pre_match.size > 0
    ary << TextNode.new(text.slice!(0 ... md.pre_match.size))
  end

  # Act on match.
  # FIXME: Separate logic for string consumption:
  case sym
  when :raw_bracket
    balanced = balance_markup(text, md.to_s, '[==', '==]').match(SYM_TO_REGEX[:raw_bracket])[1]
    ary << RawTextNode.new(balanced)
  when :bq_author
    balanced = balance_markup(text, md.to_s, BQ_LEFT, '[/bq]').match(SYM_TO_REGEX[:bq_author])[2]
    ary << HTMLNode.new(:blockquote, parse(balanced), title: $1)
  when :bq
    balanced = balance_markup(text, md.to_s, BQ_LEFT, '[/bq]').match(SYM_TO_REGEX[:bq])[1]
    ary << HTMLNode.new(:blockquote, parse(balanced))
  when :spoiler
    balanced = balance_markup(text, md.to_s, '[spoiler]', '[/spoiler]').match(SYM_TO_REGEX[:spoiler])[1]
    ary << HTMLNode.new(:span, parse(balanced), class: 'spoiler')
  else
    text.slice!(0 .. md.to_s.size)
  end

  case sym
  when :raw
    ary << RawTextNode.new(md[1])
  when :link_title_bracket, :link_title
    ary << HTMLNode.new(:a, parse(md[1]), title: md[2], href: md[3])
  when :link_bracket, :link
    ary << HTMLNode.new(:a, parse(md[1]), href: md[2])
  when :image_link_title_bracket, :image_link_title
    ary << HTMLNode.new(:a, ImageNode.new(md[1]), title: md[2], href: md[3])
  when :image_link_bracket, :image_link
    ary << HTMLNode.new(:a, ImageNode.new(md[1]), href: md[2])
  when :image_title_bracket, :image_title
    ary << HTMLNode.new(:span, ImageNode.new(md[1]), title: md[2])
  when :image_bracket, :image
    ary << ImageNode.new(md[1])
  when :dblbold_bracket, :dblbold
    ary << HTMLNode.new(:b, parse(md[1]))
  when :bold_bracket, :bold
    ary << HTMLNode.new(:strong, parse(md[1]))
  when :dblitalic_bracket, :dblitalic
    ary << HTMLNode.new(:i, parse(md[1]))
  when :italic_bracket, :italic
    ary << HTMLNode.new(:em, parse(md[1]))
  when :code_bracket, :code
    ary << HTMLNode.new(:code, parse(md[1]))
  when :ins_bracket, :ins
    ary << HTMLNode.new(:ins, parse(md[1]))
  when :sup_bracket, :sup
    ary << HTMLNode.new(:sup, parse(md[1]))
  when :del_bracket, :del
    ary << HTMLNode.new(:del, parse(md[1]))
  when :sub_bracket, :sub
    ary << HTMLNode.new(:sub, parse(md[1]))
  when :cite_bracket, :cite
    ary << HTMLNode.new(:cite, parse(md[1]))
  end
end
parse(text) click to toggle source
# File lib/textile/parser.rb, line 7
def parse(text)
  ary = []
  operand(ary, text) until text.empty?
  MultiNode.new(ary)
end

Private Instance Methods

balance_markup(text, matched, left, right) click to toggle source

Find the longest substring that contains balanced markup, or the whole string if this is impossible.

# File lib/textile/parser.rb, line 103
def balance_markup(text, matched, left, right)
  both = Regexp.union(left, right)
  left = Regexp.union(left)
  right = Regexp.union(right)

  s = StringScanner.new(matched)
  n, lowest_pos = 0, 0
  i = loop do
    match = s.scan(both)
    case
    when match =~ left
      n += 1
    when match =~ right
      n -= 1
      lowest_pos = s.pos
    else
      m = s.scan_until(both)
      s.pos = s.pos - s.matched.size if m
      s.terminate if m.nil?
    end

    break lowest_pos.pred if n.zero? || s.eos?
  end

  text.slice!(0 .. i)
  matched[0 .. i]
end