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 164
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
  @version = nil
end

Public Instance Methods

add_listener( listener ) click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 175
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 210
def empty?
  (@source.empty? and @stack.empty?)
end
entity( reference, entities ) click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 537
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 215
def has_next?
  !(@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 548
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 231
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 200
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 246
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 189
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 184
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 564
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 221
def unshift token
  @stack.unshift(token)
end

Private Instance Methods

add_namespace(prefix, uri) click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 613
def add_namespace(prefix, uri)
  @namespaces_restore_stack.last[prefix] = @namespaces[prefix]
  if uri.nil?
    @namespaces.delete(prefix)
  else
    @namespaces[prefix] = uri
  end
end
need_source_encoding_update?(xml_declaration_encoding) click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 646
def need_source_encoding_update?(xml_declaration_encoding)
  return false if xml_declaration_encoding.nil?
  return false if /\AUTF-16\z/i =~ xml_declaration_encoding
  true
end
normalize_xml_declaration_encoding(xml_declaration_encoding) click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 652
def normalize_xml_declaration_encoding(xml_declaration_encoding)
  /\AUTF-16(?:BE|LE)\z/i.match?(xml_declaration_encoding) ? "UTF-16" : nil
end
parse_attribute_value_with_equal(name) click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 849
def parse_attribute_value_with_equal(name)
  unless @source.match?(Private::EQUAL_PATTERN, true)
    message = "Missing attribute equal: <#{name}>"
    raise REXML::ParseException.new(message, @source)
  end
  unless quote = scan_quote
    message = "Missing attribute value start quote: <#{name}>"
    raise REXML::ParseException.new(message, @source)
  end
  start_position = @source.position
  value = @source.read_until(quote)
  unless value.chomp!(quote)
    @source.position = start_position
    message = "Missing attribute value end quote: <#{name}>: <#{quote}>"
    raise REXML::ParseException.new(message, @source)
  end
  value
end
parse_attributes(prefixes) click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 868
def parse_attributes(prefixes)
  attributes = {}
  expanded_names = {}
  closed = false
  while true
    if @source.match?(">", true)
      return attributes, closed
    elsif @source.match?("/>", true)
      closed = true
      return attributes, closed
    elsif match = @source.match(QNAME, true)
      name = match[1]
      prefix = match[2]
      local_part = match[3]
      value = parse_attribute_value_with_equal(name)
      @source.skip_spaces
      if prefix == "xmlns"
        if local_part == "xml"
          if value != Private::XML_PREFIXED_NAMESPACE
            msg = "The 'xml' prefix must not be bound to any other namespace "+
              "(http://www.w3.org/TR/REC-xml-names/#ns-decl)"
            raise REXML::ParseException.new( msg, @source, self )
          end
        elsif local_part == "xmlns"
          msg = "The 'xmlns' prefix must not be declared "+
            "(http://www.w3.org/TR/REC-xml-names/#ns-decl)"
          raise REXML::ParseException.new( msg, @source, self)
        end
        add_namespace(local_part, value)
      elsif prefix
        prefixes << prefix unless prefix == "xml"
      end

      if attributes[name]
        msg = "Duplicate attribute #{name.inspect}"
        raise REXML::ParseException.new(msg, @source, self)
      end

      unless prefix == "xmlns"
        uri = @namespaces[prefix]
        expanded_name = [uri, local_part]
        existing_prefix = expanded_names[expanded_name]
        if existing_prefix
          message = "Namespace conflict in adding attribute " +
                    "\"#{local_part}\": " +
                    "Prefix \"#{existing_prefix}\" = \"#{uri}\" and " +
                    "prefix \"#{prefix}\" = \"#{uri}\""
          raise REXML::ParseException.new(message, @source, self)
        end
        expanded_names[expanded_name] = prefix
      end

      attributes[name] = value
    else
      message = "Invalid attribute name: <#{@source.buffer.split(%r{[/>\s]}).first}>"
      raise REXML::ParseException.new(message, @source)
    end
  end
end
parse_id(base_error_message, accept_external_id:, accept_public_id:) click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 669
def parse_id(base_error_message,
             accept_external_id:,
             accept_public_id:)
  if accept_external_id and (md = @source.match(EXTERNAL_ID_PUBLIC, true))
    pubid = system = nil
    pubid_literal = md[1]
    pubid = pubid_literal[1..-2] if pubid_literal # Remove quote
    system_literal = md[2]
    system = system_literal[1..-2] if system_literal # Remove quote
    ["PUBLIC", pubid, system]
  elsif accept_public_id and (md = @source.match(PUBLIC_ID, true))
    pubid = system = nil
    pubid_literal = md[1]
    pubid = pubid_literal[1..-2] if pubid_literal # Remove quote
    ["PUBLIC", pubid, nil]
  elsif accept_external_id and (md = @source.match(EXTERNAL_ID_SYSTEM, true))
    system = nil
    system_literal = md[1]
    system = system_literal[1..-2] if system_literal # Remove quote
    ["SYSTEM", nil, system]
  else
    details = parse_id_invalid_details(accept_external_id: accept_external_id,
                                       accept_public_id: accept_public_id)
    message = "#{base_error_message}: #{details}"
    raise REXML::ParseException.new(message, @source)
  end
end
parse_id_invalid_details(accept_external_id:, accept_public_id:) click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 697
def parse_id_invalid_details(accept_external_id:,
                             accept_public_id:)
  public = /\A\s*PUBLIC/um
  system = /\A\s*SYSTEM/um
  if (accept_external_id or accept_public_id) and @source.match?(/#{public}/um)
    if @source.match?(/#{public}(?:\s+[^'"]|\s*[\[>])/um)
      return "public ID literal is missing"
    end
    unless @source.match?(/#{public}\s+#{PUBIDLITERAL}/um)
      return "invalid public ID literal"
    end
    if accept_public_id
      if @source.match?(/#{public}\s+#{PUBIDLITERAL}\s+[^'"]/um)
        return "system ID literal is missing"
      end
      unless @source.match?(/#{public}\s+#{PUBIDLITERAL}\s+#{SYSTEMLITERAL}/um)
        return "invalid system literal"
      end
      "garbage after system literal"
    else
      "garbage after public ID literal"
    end
  elsif accept_external_id and @source.match?(/#{system}/um)
    if @source.match?(/#{system}(?:\s+[^'"]|\s*[\[>])/um)
      return "system literal is missing"
    end
    unless @source.match?(/#{system}\s+#{SYSTEMLITERAL}/um)
      return "invalid system literal"
    end
    "garbage after system literal"
  else
    unless @source.match?(/\A\s*(?:PUBLIC|SYSTEM)\s/um)
      return "invalid ID type"
    end
    "ID type is missing"
  end
end
parse_name(base_error_message) click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 656
def parse_name(base_error_message)
  md = @source.match(Private::NAME_PATTERN, true)
  unless md
    if @source.match?(/\S/um)
      message = "#{base_error_message}: invalid name"
    else
      message = "#{base_error_message}: name is missing"
    end
    raise REXML::ParseException.new(message, @source)
  end
  md[0]
end
pop_namespaces_restore() click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 628
def pop_namespaces_restore
  namespaces_restore = @namespaces_restore_stack.pop
  namespaces_restore.each do |prefix, uri|
    if uri.nil?
      @namespaces.delete(prefix)
    else
      @namespaces[prefix] = uri
    end
  end
end
process_comment() click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 735
def process_comment
  text = @source.read_until("-->")
  unless text.chomp!("-->")
    raise REXML::ParseException.new("Unclosed comment: Missing end '-->'", @source)
  end

  if text.include? "--" or text.end_with?("-")
    raise REXML::ParseException.new("Malformed comment", @source)
  end
  text
end
process_instruction() click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 747
def process_instruction
  name = parse_name("Malformed XML: Invalid processing instruction node")
  if name == "xml"
    xml_declaration
  else # PITarget
    if @source.skip_spaces # e.g. <?name content?>
      start_position = @source.position
      content = @source.read_until("?>")
      unless content.chomp!("?>")
        @source.position = start_position
        raise ParseException.new("Malformed XML: Unclosed processing instruction: <#{name}>", @source)
      end
    else # e.g. <?name?>
      content = nil
      unless @source.match?("?>", true)
        raise ParseException.new("Malformed XML: Unclosed processing instruction: <#{name}>", @source)
      end
    end
    [:processing_instruction, name, content]
  end
end
pull_event() click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 256
def pull_event
  if @closed
    x, @closed = @closed, nil
    return [ :end_element, x ]
  end
  if empty?
    if @document_status == :in_doctype
      raise ParseException.new("Malformed DOCTYPE: unclosed", @source)
    end
    unless @tags.empty?
      path = "/" + @tags.join("/")
      raise ParseException.new("Missing end tag for '#{path}'", @source)
    end

    unless @document_status == :in_element
      raise ParseException.new("Malformed XML: No root element", @source)
    end

    return [ :end_document ]
  end
  return @stack.shift if @stack.size > 0
  #STDERR.puts @source.encoding
  #STDERR.puts "BUFFER = #{@source.buffer.inspect}"

  @source.ensure_buffer
  if @document_status == nil
    start_position = @source.position
    if @source.match?("<?", true)
      return process_instruction
    elsif @source.match?("<!", true)
      if @source.match?("--", true)
        return [ :comment, process_comment ]
      elsif @source.match?("DOCTYPE", true)
        base_error_message = "Malformed DOCTYPE"
        unless @source.skip_spaces
          if @source.match?(">")
            message = "#{base_error_message}: name is missing"
          else
            message = "#{base_error_message}: invalid name"
          end
          @source.position = start_position
          raise REXML::ParseException.new(message, @source)
        end
        name = parse_name(base_error_message)
        @source.skip_spaces
        if @source.match?("[", true)
          id = [nil, nil, nil]
          @document_status = :in_doctype
        elsif @source.match?(">", true)
          id = [nil, nil, nil]
          @document_status = :after_doctype
          @source.ensure_buffer
        else
          id = parse_id(base_error_message,
                        accept_external_id: true,
                        accept_public_id: false)
          if id[0] == "SYSTEM"
            # For backward compatibility
            id[1], id[2] = id[2], nil
          end
          @source.skip_spaces
          if @source.match?("[", true)
            @document_status = :in_doctype
          elsif @source.match?(">", true)
            @document_status = :after_doctype
            @source.ensure_buffer
          else
            message = "#{base_error_message}: garbage after external ID"
            raise REXML::ParseException.new(message, @source)
          end
        end
        args = [:start_doctype, name, *id]
        if @document_status == :after_doctype
          @source.skip_spaces
          @stack << [ :end_doctype ]
        end
        return args
      else
        message = "Invalid XML"
        raise REXML::ParseException.new(message, @source)
      end
    end
  end
  if @document_status == :in_doctype
    @source.skip_spaces
    start_position = @source.position
    if @source.match?("<!", true)
      if @source.match?("ELEMENT", true)
        md = @source.match(/(.*?)>/um, true)
        raise REXML::ParseException.new( "Bad ELEMENT declaration!", @source ) if md.nil?
        return [ :elementdecl, "<!ELEMENT" + md[1] ]
      elsif @source.match?("ENTITY", true)
        match_data = @source.match(Private::ENTITYDECL_PATTERN, true)
        unless match_data
          raise REXML::ParseException.new("Malformed entity declaration", @source)
        end
        match = [:entitydecl, *match_data.captures.compact]
        ref = false
        if match[1] == '%'
          ref = true
          match.delete_at 1
        end
        # Now we have to sort out what kind of entity reference this is
        if match[2] == 'SYSTEM'
          # External reference
          match[3] = match[3][1..-2] # PUBID
          match.delete_at(4) if match.size > 4 # Chop out NDATA decl
          # match is [ :entity, name, SYSTEM, pubid(, ndata)? ]
        elsif match[2] == 'PUBLIC'
          # External reference
          match[3] = match[3][1..-2] # PUBID
          match[4] = match[4][1..-2] # HREF
          match.delete_at(5) if match.size > 5 # Chop out NDATA decl
          # match is [ :entity, name, PUBLIC, pubid, href(, ndata)? ]
        elsif Private::PEREFERENCE_PATTERN.match?(match[2])
          raise REXML::ParseException.new("Parameter entity references forbidden in internal subset: #{match[2]}", @source)
        else
          match[2] = match[2][1..-2]
          match.pop if match.size == 4
          # match is [ :entity, name, value ]
        end
        match << '%' if ref
        return match
      elsif @source.match?("ATTLIST", true)
        md = @source.match(Private::ATTLISTDECL_END, true)
        raise REXML::ParseException.new( "Bad ATTLIST declaration!", @source ) if md.nil?
        element = md[1]
        contents = "<!ATTLIST" + md[0]

        pairs = {}
        values = md[0].strip.scan( ATTDEF_RE )
        values.each do |attdef|
          unless attdef[3] == "#IMPLIED"
            attdef.compact!
            val = attdef[3]
            val = attdef[4] if val == "#FIXED "
            pairs[attdef[0]] = val
            if attdef[0] =~ /^xmlns:(.*)/
              @namespaces[$1] = val
            end
          end
        end
        return [ :attlistdecl, element, pairs, contents ]
      elsif @source.match?("NOTATION", true)
        base_error_message = "Malformed notation declaration"
        unless @source.skip_spaces
          if @source.match?(">")
            message = "#{base_error_message}: name is missing"
          else
            message = "#{base_error_message}: invalid name"
          end
          @source.position = start_position
          raise REXML::ParseException.new(message, @source)
        end
        name = parse_name(base_error_message)
        id = parse_id(base_error_message,
                      accept_external_id: true,
                      accept_public_id: true)
        @source.skip_spaces
        unless @source.match?(">", true)
          message = "#{base_error_message}: garbage before end >"
          raise REXML::ParseException.new(message, @source)
        end
        return [:notationdecl, name, *id]
      elsif @source.match?("--", true)
        return [ :comment, process_comment ]
      else
        raise REXML::ParseException.new("Malformed node: Started with '<!' but not a comment nor ELEMENT,ENTITY,ATTLIST,NOTATION", @source)
      end
    elsif match = @source.match(/(%.*?;)\s*/um, true)
      return [ :externalentity, match[1] ]
    elsif @source.match?(/\]\s*>/um, true)
      @document_status = :after_doctype
      return [ :end_doctype ]
    else
      raise ParseException.new("Malformed DOCTYPE: invalid declaration", @source)
    end
  end
  if @document_status == :after_doctype
    @source.skip_spaces
  end
  begin
    start_position = @source.position
    if @source.match?("<", true)
      # :text's read_until may remain only "<" in buffer. In the
      # case, buffer is empty here. So we need to fill buffer
      # here explicitly.
      @source.ensure_buffer
      if @source.match?("/", true)
        @namespaces_restore_stack.pop
        last_tag = @tags.pop
        md = @source.match(Private::CLOSE_PATTERN, true)
        if md and !last_tag
          message = "Unexpected top-level end tag (got '#{md[1]}')"
          raise REXML::ParseException.new(message, @source)
        end
        if md.nil? or last_tag != md[1]
          message = "Missing end tag for '#{last_tag}'"
          message += " (got '#{md[1]}')" if md
          @source.position = start_position if md.nil?
          raise REXML::ParseException.new(message, @source)
        end
        return [ :end_element, last_tag ]
      elsif @source.match?("!", true)
        #STDERR.puts "SOURCE BUFFER = #{source.buffer}, #{source.buffer.size}"
        if @source.match?("--", true)
          return [ :comment, process_comment ]
        elsif @source.match?("[CDATA[", true)
          text = @source.read_until("]]>")
          if text.chomp!("]]>")
            return [ :cdata, text ]
          else
            raise REXML::ParseException.new("Malformed CDATA: Missing end ']]>'", @source)
          end
        else
          raise REXML::ParseException.new("Malformed node: Started with '<!' but not a comment nor CDATA", @source)
        end
      elsif @source.match?("?", true)
        return process_instruction
      else
        # Get the next tag
        md = @source.match(Private::TAG_PATTERN, true)
        unless md
          @source.position = start_position
          raise REXML::ParseException.new("malformed XML: missing tag start", @source)
        end
        tag = md[1]
        @document_status = :in_element
        @prefixes.clear
        @prefixes << md[2] if md[2]
        push_namespaces_restore
        attributes, closed = parse_attributes(@prefixes)
        # Verify that all of the prefixes have been defined
        for prefix in @prefixes
          unless @namespaces.key?(prefix)
            raise UndefinedNamespaceException.new(prefix,@source,self)
          end
        end

        if closed
          @closed = tag
          pop_namespaces_restore
        else
          if @tags.empty? and @have_root
            raise ParseException.new("Malformed XML: Extra tag at the end of the document (got '<#{tag}')", @source)
          end
          @tags.push( tag )
        end
        @have_root = true
        return [ :start_element, tag, attributes ]
      end
    else
      text = @source.read_until("<")
      if text.chomp!("<")
        @source.position -= "<".bytesize
      end
      if @tags.empty?
        unless /\A\s*\z/.match?(text)
          if @have_root
            raise ParseException.new("Malformed XML: Extra content at the end of the document (got '#{text}')", @source)
          else
            raise ParseException.new("Malformed XML: Content at the start of the document (got '#{text}')", @source)
          end
        end
        return pull_event if @have_root
      end
      return [ :text, text ]
    end
  rescue REXML::UndefinedNamespaceException
    raise
  rescue REXML::ParseException
    raise
  rescue => error
    raise REXML::ParseException.new( "Exception parsing",
      @source, self, (error ? error : $!) )
  end
  # NOTE: The end of the method never runs, because it is unreachable.
  #       All branches of code above have explicit unconditional return or raise statements.
end
push_namespaces_restore() click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 622
def push_namespaces_restore
  namespaces_restore = {}
  @namespaces_restore_stack.push(namespaces_restore)
  namespaces_restore
end
record_entity_expansion(delta=1) click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 639
def record_entity_expansion(delta=1)
  @entity_expansion_count += delta
  if @entity_expansion_count > @entity_expansion_limit
    raise "number of entity expansions exceeded, processing aborted."
  end
end
scan_quote() click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 831
def scan_quote
  @source.match(/(['"])/, true)&.[](1)
end
xml_declaration() click to toggle source
# File lib/rexml/parsers/baseparser.rb, line 769
def xml_declaration
  unless @version.nil?
    raise ParseException.new("Malformed XML: XML declaration is duplicated", @source)
  end
  if @document_status
    raise ParseException.new("Malformed XML: XML declaration is not at the start", @source)
  end
  unless @source.skip_spaces
    raise ParseException.new("Malformed XML: XML declaration misses spaces before version", @source)
  end
  unless @source.match?("version", true)
    raise ParseException.new("Malformed XML: XML declaration misses version", @source)
  end
  @version = parse_attribute_value_with_equal("xml")
  unless @source.skip_spaces
    unless @source.match?("?>", true)
      raise ParseException.new("Malformed XML: Unclosed XML declaration", @source)
    end
    encoding = normalize_xml_declaration_encoding(@source.encoding)
    return [ :xmldecl, @version, encoding, nil ] # e.g. <?xml version="1.0"?>
  end

  if @source.match?("encoding", true)
    encoding = parse_attribute_value_with_equal("xml")
    unless @source.skip_spaces
      unless @source.match?("?>", true)
        raise ParseException.new("Malformed XML: Unclosed XML declaration", @source)
      end
      if need_source_encoding_update?(encoding)
        @source.encoding = encoding
      end
      encoding ||= normalize_xml_declaration_encoding(@source.encoding)
      return [ :xmldecl, @version, encoding, nil ] # e.g. <?xml version="1.1" encoding="UTF-8"?>
    end
  end

  if @source.match?("standalone", true)
    standalone = parse_attribute_value_with_equal("xml")
    case standalone
    when "yes", "no"
    else
      raise ParseException.new("Malformed XML: XML declaration standalone is not yes or no : <#{standalone}>", @source)
    end
  end
  @source.skip_spaces
  unless @source.match?("?>", true)
    raise ParseException.new("Malformed XML: Unclosed XML declaration", @source)
  end

  if need_source_encoding_update?(encoding)
    @source.encoding = encoding
  end
  encoding ||= normalize_xml_declaration_encoding(@source.encoding)

  # e.g. <?xml version="1.0" ?>
  #      <?xml version="1.1" encoding="UTF-8" ?>
  #      <?xml version="1.1" standalone="yes"?>
  #      <?xml version="1.1" encoding="UTF-8" standalone="yes" ?>
  [ :xmldecl, @version, encoding, standalone ]
end