class REXML::Parsers::BaseParser

Using the Pull Parser

This API is experimental, and subject to change.

parser = PullParser.new( "<a>text<b att='val'/>txet</a>" )
while parser.has_next?
  res = parser.next
  puts res[1]['att'] if res.start_tag? and res[0] == 'b'
end

See the PullEvent class for information on the content of the results. The data is identical to the arguments passed for the various events to the StreamListener API.

Notice that:

parser = PullParser.new( "<a>BAD DOCUMENT" )
while parser.has_next?
  res = parser.next
  raise res[1] if res.error?
end

Nat Price gave me some good ideas for the API.

Constants

ATTDEF
ATTDEF_RE
ATTLISTDECL_PATTERN
ATTLISTDECL_START
ATTRIBUTE_PATTERN
ATTTYPE
ATTVALUE
CDATA_END
CDATA_PATTERN
CDATA_START
CLOSE_MATCH
COMBININGCHAR
COMMENT_PATTERN
COMMENT_START
DEFAULTDECL
DEFAULT_ENTITIES
DIGIT
DOCTYPE_END
DOCTYPE_START
ELEMENTDECL_PATTERN
ELEMENTDECL_START
ENCODING
ENTITYDECL
ENTITYDEF
ENTITYVALUE
ENTITY_START
ENUMERATEDTYPE
ENUMERATION
EREFERENCE
EXTENDER
EXTERNALID
EXTERNAL_ID_PUBLIC
EXTERNAL_ID_SYSTEM
GEDECL
INSTRUCTION_PATTERN
INSTRUCTION_START
LETTER
NAME
NAMECHAR
NCNAME_STR
NDATADECL
NMTOKEN
NMTOKENS
NOTATIONDECL_START
NOTATIONTYPE
PEDECL
PEDEF
PEREFERENCE
PUBIDCHAR

Entity constants

PUBIDLITERAL
PUBLIC_ID
QNAME
QNAME_STR
REFERENCE
REFERENCE_RE
STANDALONE
SYSTEMENTITY
SYSTEMLITERAL
TAG_MATCH
TEXT_PATTERN
UNAME_STR

Just for backward compatibility. For example, kramdown uses this. It’s not used in REXML.

VERSION
XMLDECL_PATTERN
XMLDECL_START

Attributes

entity_expansion_count[R]
entity_expansion_limit[W]
entity_expansion_text_limit[W]
source[R]

Public Class Methods

new( source ) click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 163
def initialize( source )
  self.stream = source
  @listeners = []
  @prefixes = Set.new
  @entity_expansion_count = 0
  @entity_expansion_limit = Security.entity_expansion_limit
  @entity_expansion_text_limit = Security.entity_expansion_text_limit
  @source.ensure_buffer
end

Public Instance Methods

add_listener( listener ) click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 173
def add_listener( listener )
  @listeners << listener
end
empty?() click to toggle source

Returns true if there are no more events

# File lib/rexml/parsers/baseparser.rb, line 208
def empty?
  return (@source.empty? and @stack.empty?)
end
entity( reference, entities ) click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 540
def entity( reference, entities )
  return unless entities

  value = entities[ reference ]
  return if value.nil?

  record_entity_expansion
  unnormalize( value, entities )
end
has_next?() click to toggle source

Returns true if there are more events. Synonymous with !empty?

# File lib/rexml/parsers/baseparser.rb, line 213
def has_next?
  return !(@source.empty? and @stack.empty?)
end
normalize( input, entities=nil, entity_filter=nil ) click to toggle source

Escapes all possible entities

# File lib/rexml/parsers/baseparser.rb, line 551
def normalize( input, entities=nil, entity_filter=nil )
  copy = input.clone
  # Doing it like this rather than in a loop improves the speed
  copy.gsub!( EREFERENCE, '&amp;' )
  entities.each do |key, value|
    copy.gsub!( value, "&#{key};" ) unless entity_filter and
                                entity_filter.include?(entity)
  end if entities
  copy.gsub!( EREFERENCE, '&amp;' )
  DEFAULT_ENTITIES.each do |key, value|
    copy.gsub!( value[3], value[1] )
  end
  copy
end
peek(depth=0) click to toggle source

Peek at the depth event in the stack. The first element on the stack is at depth 0. If depth is -1, will parse to the end of the input stream and return the last event, which is always :end_document. Be aware that this causes the stream to be parsed up to the depth event, so you can effectively pre-parse the entire document (pull the entire thing into memory) using this method.

# File lib/rexml/parsers/baseparser.rb, line 229
def peek depth=0
  raise %Q[Illegal argument "#{depth}"] if depth < -1
  temp = []
  if depth == -1
    temp.push(pull()) until empty?
  else
    while @stack.size+temp.size < depth+1
      temp.push(pull())
    end
  end
  @stack += temp if temp.size > 0
  @stack[depth]
end
position() click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 198
def position
  if @source.respond_to? :position
    @source.position
  else
    # FIXME
    0
  end
end
pull() click to toggle source

Returns the next event. This is a PullEvent object.

# File lib/rexml/parsers/baseparser.rb, line 244
def pull
  @source.drop_parsed_content

  pull_event.tap do |event|
    @listeners.each do |listener|
      listener.receive event
    end
  end
end
reset() click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 187
def reset
  @closed = nil
  @have_root = false
  @document_status = nil
  @tags = []
  @stack = []
  @entities = []
  @namespaces = {"xml" => Private::XML_PREFIXED_NAMESPACE}
  @namespaces_restore_stack = []
end
stream=( source ) click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 182
def stream=( source )
  @source = SourceFactory.create_from( source )
  reset
end
unnormalize( string, entities=nil, filter=nil ) click to toggle source

Unescapes all possible entities

# File lib/rexml/parsers/baseparser.rb, line 567
def unnormalize( string, entities=nil, filter=nil )
  if string.include?("\r")
    rv = string.gsub( Private::CARRIAGE_RETURN_NEWLINE_PATTERN, "\n" )
  else
    rv = string.dup
  end
  matches = rv.scan( REFERENCE_RE )
  return rv if matches.size == 0
  rv.gsub!( Private::CHARACTER_REFERENCES ) {
    m=$1
    if m.start_with?("x")
      code_point = Integer(m[1..-1], 16)
    else
      code_point = Integer(m, 10)
    end
    [code_point].pack('U*')
  }
  matches.collect!{|x|x[0]}.compact!
  if filter
    matches.reject! do |entity_reference|
      filter.include?(entity_reference)
    end
  end
  if matches.size > 0
    matches.tally.each do |entity_reference, n|
      entity_expansion_count_before = @entity_expansion_count
      entity_value = entity( entity_reference, entities )
      if entity_value
        if n > 1
          entity_expansion_count_delta =
            @entity_expansion_count - entity_expansion_count_before
          record_entity_expansion(entity_expansion_count_delta * (n - 1))
        end
        re = Private::DEFAULT_ENTITIES_PATTERNS[entity_reference] || /&#{entity_reference};/
        rv.gsub!( re, entity_value )
        if rv.bytesize > @entity_expansion_text_limit
          raise "entity expansion has grown too large"
        end
      else
        er = DEFAULT_ENTITIES[entity_reference]
        rv.gsub!( er[0], er[2] ) if er
      end
    end
    rv.gsub!( Private::DEFAULT_ENTITIES_PATTERNS['amp'], '&' )
  end
  rv
end
unshift(token) click to toggle source

Push an event back on the head of the stream. This method has (theoretically) infinite depth.

# File lib/rexml/parsers/baseparser.rb, line 219
def unshift token
  @stack.unshift(token)
end