class Prawn::Format::Parser

The Parser class is used by the formatting subsystem to take the raw tokens from the Lexer class and wrap them in “instructions”, which are then used by the LayoutBuilder to determine how each token should be rendered.

The parser also ensures that tags are opened and closed consistently. It is not forgiving at all–if you forget to close a tag, the parser will raise an exception (TagError).

It will also raise an exception if a tag is encountered with no style definition for it.

Attributes

document[R]
state[R]
tags[R]

Public Class Methods

new(document, text, options={}) click to toggle source

Creates a new parser associated with the given document, and which will parse the given text. The options may include either of two optional keys:

  • :tags is used to specify the hash of tags and their associated styles. Any tag not specified here will not be recognized by the parser, and will cause an error if it is encountered in text.

  • :styles is used to specify the mapping of style classes to their definitions. The keys should be symbols, and the values should be hashes. The values have the same format as for the :tags map.

  • :style is the default style for any text not otherwise wrapped by tags.

Example:

parser = Parser.new(@pdf, "<b class='ruby'>hello</b>",
    :tags => { :b => { :font_weight => :bold } },
    :styles => { :ruby => { :color => "red" } },
    :style => { :font_family => "Times-Roman" })

See Format::State for a description of the supported style options.

# File lib/prawn/format/parser.rb, line 53
def initialize(document, text, options={})
  @document = document
  @lexer = Lexer.new(text)
  @tags = options[:tags] || {}
  @styles = options[:styles] || {}

  @state = State.new(document, :style => options[:style])
  @lexer.verbatim = (@state.white_space == :pre)

  @action = :start

  @saved = []
  @tag_stack = []
end

Public Instance Methods

eos?() click to toggle source

Returns true if the end of the stream has been reached. Subsequent calls to peek or next will return nil.

# File lib/prawn/format/parser.rb, line 103
def eos?
  peek.nil?
end
next() click to toggle source

Returns the next instruction from the stream. If there are no more instructions in the stream (e.g., the end has been encountered), this returns nil.

# File lib/prawn/format/parser.rb, line 75
def next
  return @saved.pop if @saved.any?

  case @action
  when :start then start_parse
  when :text  then text_parse
  else raise "BUG: unknown parser action: #{@action.inspect}"
  end
end
peek() click to toggle source

This is identical to next, except it does not consume the instruction. This means that peek returns the instruction that will be returned by the next call to next. It is useful for testing the next instruction in the stream without advancing the stream.

# File lib/prawn/format/parser.rb, line 95
def peek
  save = self.next
  push(save) if save
  return save
end
push(instruction) click to toggle source

“Ungets” the given instruction. This makes it so the next call to next will return instruction. This is useful for backtracking.

# File lib/prawn/format/parser.rb, line 87
def push(instruction)
  @saved.push(instruction)
end
verbatim?() click to toggle source
# File lib/prawn/format/parser.rb, line 68
def verbatim?
  @lexer.verbatim
end

Private Instance Methods

process_open_tag() click to toggle source
# File lib/prawn/format/parser.rb, line 159
def process_open_tag
  @tag_stack << @token
  raise TagError, "undefined tag #{@token[:tag]}" unless @tags[@token[:tag]]
  @token[:style] = @tags[@token[:tag]].dup

  (@token[:options][:class] || "").split(/\s/).each do |name|
    @token[:style].update(@styles[name.to_sym] || {})
  end

  if @token[:style][:meta]
    @token[:style][:meta].each do |key, value|
      @token[:style][value] = @token[:options][key]
    end
  end

  @state = @state.with_style(@token[:style])
  Instructions::TagOpen.new(@state, @token)
end
start_parse() click to toggle source
# File lib/prawn/format/parser.rb, line 109
def start_parse
  instruction = nil
  while (@token = @lexer.next)
    case @token[:type]
    when :text
      @position = 0
      instruction = text_parse
    when :open
      instruction = process_open_tag
    when :close
      raise TagError, "closing #{@token[:tag]}, but no tags are open" if @tag_stack.empty?
      raise TagError, "closing #{@tag_stack.last[:tag]} with #{@token[:tag]}" if @tag_stack.last[:tag] != @token[:tag]

      instruction = Instructions::TagClose.new(@state, @tag_stack.pop)
      @state = @state.previous
    else
      raise ArgumentError, "[BUG] unknown token type #{@token[:type].inspect} (#{@token.inspect})"
    end

    if instruction
      if instruction.start_verbatim?
        @lexer.verbatim = true
      elsif instruction.end_verbatim?
        @lexer.verbatim = false
      end

      return instruction
    end
  end

  return nil
end
text_parse() click to toggle source
# File lib/prawn/format/parser.rb, line 142
def text_parse
  if @token[:text][@position]
    @action = :text
    @position += 1

    text = @token[:text][@position - 1]
    if @state.white_space == :pre && text =~ /(?:\r\n|\r|\n)/
      Instructions::TagClose.new(@state, { :style => { :display => :break }, :options => {} })
    else
      Instructions::Text.new(@state, text)
    end
  else
    @action = :start
    start_parse
  end
end