summaryrefslogtreecommitdiff
path: root/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers
diff options
context:
space:
mode:
authorBen Sanders <ben@sanders.life>2026-08-02 09:49:25 -0400
committerBen Sanders <ben@sanders.life>2026-08-02 09:49:25 -0400
commitb41479c91e6685511c1f8ba8586106b322073b62 (patch)
treec9f1345999d754ae0a533849966427e792d9e68d /vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers
parentcfaceeb9a6d1295f5754be211858155cd309acc2 (diff)
added statscounter code
Diffstat (limited to 'vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers')
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/baseparser.rb949
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/lightparser.rb59
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/pullparser.rb213
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/sax2parser.rb270
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/streamparser.rb67
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/treeparser.rb89
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/ultralightparser.rb57
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/xpathparser.rb739
8 files changed, 2443 insertions, 0 deletions
diff --git a/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/baseparser.rb b/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/baseparser.rb
new file mode 100644
index 0000000..8fe287a
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/baseparser.rb
@@ -0,0 +1,949 @@
+# frozen_string_literal: true
+require_relative '../parseexception'
+require_relative '../undefinednamespaceexception'
+require_relative '../security'
+require_relative '../source'
+require 'set'
+require "strscan"
+
+module REXML
+ module Parsers
+ unless [].respond_to?(:tally)
+ module EnumerableTally
+ refine Enumerable do
+ def tally
+ counts = {}
+ each do |item|
+ counts[item] ||= 0
+ counts[item] += 1
+ end
+ counts
+ end
+ end
+ end
+ using EnumerableTally
+ end
+
+ if StringScanner::Version < "3.0.8"
+ module StringScannerCaptures
+ refine StringScanner do
+ def captures
+ values_at(*(1...size))
+ end
+ end
+ end
+ using StringScannerCaptures
+ end
+
+ # = Using the Pull Parser
+ # <em>This API is experimental, and subject to change.</em>
+ # 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.
+ class BaseParser
+ LETTER = '[:alpha:]'
+ DIGIT = '[:digit:]'
+
+ COMBININGCHAR = '' # TODO
+ EXTENDER = '' # TODO
+
+ NCNAME_STR= "[#{LETTER}_][-[:alnum:]._#{COMBININGCHAR}#{EXTENDER}]*"
+ QNAME_STR= "(?:(#{NCNAME_STR}):)?(#{NCNAME_STR})"
+ QNAME = /(#{QNAME_STR})/
+
+ # Just for backward compatibility. For example, kramdown uses this.
+ # It's not used in REXML.
+ UNAME_STR= "(?:#{NCNAME_STR}:)?#{NCNAME_STR}"
+
+ NAMECHAR = '[\-\w\.:]'
+ NAME = "([\\w:]#{NAMECHAR}*)"
+ NMTOKEN = "(?:#{NAMECHAR})+"
+ NMTOKENS = "#{NMTOKEN}(\\s+#{NMTOKEN})*"
+ REFERENCE = "&(?:#{NAME};|#\\d+;|#x[0-9a-fA-F]+;)"
+ REFERENCE_RE = /#{REFERENCE}/
+
+ DOCTYPE_START = /\A\s*<!DOCTYPE\s/um
+ DOCTYPE_END = /\A\s*\]\s*>/um
+ ATTRIBUTE_PATTERN = /\s*(#{QNAME_STR})\s*=\s*(["'])(.*?)\4/um
+ COMMENT_START = /\A<!--/u
+ COMMENT_PATTERN = /<!--(.*?)-->/um
+ CDATA_START = /\A<!\[CDATA\[/u
+ CDATA_END = /\A\s*\]\s*>/um
+ CDATA_PATTERN = /<!\[CDATA\[(.*?)\]\]>/um
+ XMLDECL_START = /\A<\?xml\s/u;
+ XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>/um
+ INSTRUCTION_START = /\A<\?/u
+ INSTRUCTION_PATTERN = /<\?#{NAME}(\s+.*?)?\?>/um
+ TAG_MATCH = /\A<((?>#{QNAME_STR}))/um
+ CLOSE_MATCH = /\A\s*<\/(#{QNAME_STR})\s*>/um
+
+ VERSION = /\bversion\s*=\s*["'](.*?)['"]/um
+ ENCODING = /\bencoding\s*=\s*["'](.*?)['"]/um
+ STANDALONE = /\bstandalone\s*=\s*["'](.*?)['"]/um
+
+ ENTITY_START = /\A\s*<!ENTITY/
+ ELEMENTDECL_START = /\A\s*<!ELEMENT/um
+ ELEMENTDECL_PATTERN = /\A\s*(<!ELEMENT.*?)>/um
+ SYSTEMENTITY = /\A\s*(%.*?;)\s*$/um
+ ENUMERATION = "\\(\\s*#{NMTOKEN}(?:\\s*\\|\\s*#{NMTOKEN})*\\s*\\)"
+ NOTATIONTYPE = "NOTATION\\s+\\(\\s*#{NAME}(?:\\s*\\|\\s*#{NAME})*\\s*\\)"
+ ENUMERATEDTYPE = "(?:(?:#{NOTATIONTYPE})|(?:#{ENUMERATION}))"
+ ATTTYPE = "(CDATA|ID|IDREF|IDREFS|ENTITY|ENTITIES|NMTOKEN|NMTOKENS|#{ENUMERATEDTYPE})"
+ ATTVALUE = "(?:\"((?:[^<&\"]|#{REFERENCE})*)\")|(?:'((?:[^<&']|#{REFERENCE})*)')"
+ DEFAULTDECL = "(#REQUIRED|#IMPLIED|(?:(#FIXED\\s+)?#{ATTVALUE}))"
+ ATTDEF = "\\s+#{NAME}\\s+#{ATTTYPE}\\s+#{DEFAULTDECL}"
+ ATTDEF_RE = /#{ATTDEF}/
+ ATTLISTDECL_START = /\A\s*<!ATTLIST/um
+ ATTLISTDECL_PATTERN = /\A\s*<!ATTLIST\s+#{NAME}(?:#{ATTDEF})*\s*>/um
+
+ TEXT_PATTERN = /\A([^<]*)/um
+
+ # Entity constants
+ PUBIDCHAR = "\x20\x0D\x0Aa-zA-Z0-9\\-()+,./:=?;!*@$_%#"
+ SYSTEMLITERAL = %Q{((?:"[^"]*")|(?:'[^']*'))}
+ PUBIDLITERAL = %Q{("[#{PUBIDCHAR}']*"|'[#{PUBIDCHAR}]*')}
+ EXTERNALID = "(?:(?:(SYSTEM)\\s+#{SYSTEMLITERAL})|(?:(PUBLIC)\\s+#{PUBIDLITERAL}\\s+#{SYSTEMLITERAL}))"
+ NDATADECL = "\\s+NDATA\\s+#{NAME}"
+ PEREFERENCE = "%#{NAME};"
+ ENTITYVALUE = %Q{((?:"(?:[^%&"]|#{PEREFERENCE}|#{REFERENCE})*")|(?:'([^%&']|#{PEREFERENCE}|#{REFERENCE})*'))}
+ PEDEF = "(?:#{ENTITYVALUE}|#{EXTERNALID})"
+ ENTITYDEF = "(?:#{ENTITYVALUE}|(?:#{EXTERNALID}(#{NDATADECL})?))"
+ PEDECL = "<!ENTITY\\s+(%)\\s+#{NAME}\\s+#{PEDEF}\\s*>"
+ GEDECL = "<!ENTITY\\s+#{NAME}\\s+#{ENTITYDEF}\\s*>"
+ ENTITYDECL = /\s*(?:#{GEDECL})|\s*(?:#{PEDECL})/um
+
+ NOTATIONDECL_START = /\A\s*<!NOTATION/um
+ EXTERNAL_ID_PUBLIC = /\A\s*PUBLIC\s+#{PUBIDLITERAL}\s+#{SYSTEMLITERAL}\s*/um
+ EXTERNAL_ID_SYSTEM = /\A\s*SYSTEM\s+#{SYSTEMLITERAL}\s*/um
+ PUBLIC_ID = /\A\s*PUBLIC\s+#{PUBIDLITERAL}\s*/um
+
+ EREFERENCE = /&(?!#{NAME};)/
+
+ DEFAULT_ENTITIES = {
+ 'gt' => [/&gt;/, '&gt;', '>', />/],
+ 'lt' => [/&lt;/, '&lt;', '<', /</],
+ 'quot' => [/&quot;/, '&quot;', '"', /"/],
+ "apos" => [/&apos;/, "&apos;", "'", /'/]
+ }
+
+ module Private
+ PEREFERENCE_PATTERN = /#{PEREFERENCE}/um
+ TAG_PATTERN = /((?>#{QNAME_STR}))\s*/um
+ CLOSE_PATTERN = /(#{QNAME_STR})\s*>/um
+ EQUAL_PATTERN = /\s*=\s*/um
+ ATTLISTDECL_END = /\s+#{NAME}(?:#{ATTDEF})*\s*>/um
+ NAME_PATTERN = /#{NAME}/um
+ GEDECL_PATTERN = "\\s+#{NAME}\\s+#{ENTITYDEF}\\s*>"
+ PEDECL_PATTERN = "\\s+(%)\\s+#{NAME}\\s+#{PEDEF}\\s*>"
+ ENTITYDECL_PATTERN = /(?:#{GEDECL_PATTERN})|(?:#{PEDECL_PATTERN})/um
+ CARRIAGE_RETURN_NEWLINE_PATTERN = /\r\n?/
+ CHARACTER_REFERENCES = /&#((?:\d+)|(?:x[a-fA-F0-9]+));/
+ DEFAULT_ENTITIES_PATTERNS = {}
+ default_entities = ['gt', 'lt', 'quot', 'apos', 'amp']
+ default_entities.each do |term|
+ DEFAULT_ENTITIES_PATTERNS[term] = /&#{term};/
+ end
+ XML_PREFIXED_NAMESPACE = "http://www.w3.org/XML/1998/namespace"
+ end
+ private_constant :Private
+
+ 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
+
+ def add_listener( listener )
+ @listeners << listener
+ end
+
+ attr_reader :source
+ attr_reader :entity_expansion_count
+ attr_writer :entity_expansion_limit
+ attr_writer :entity_expansion_text_limit
+
+ def stream=( source )
+ @source = SourceFactory.create_from( source )
+ reset
+ end
+
+ def reset
+ @closed = nil
+ @have_root = false
+ @document_status = nil
+ @tags = []
+ @stack = []
+ @entities = []
+ @namespaces = {"xml" => Private::XML_PREFIXED_NAMESPACE}
+ @namespaces_restore_stack = []
+ end
+
+ def position
+ if @source.respond_to? :position
+ @source.position
+ else
+ # FIXME
+ 0
+ end
+ end
+
+ # Returns true if there are no more events
+ def empty?
+ (@source.empty? and @stack.empty?)
+ end
+
+ # Returns true if there are more events. Synonymous with !empty?
+ def has_next?
+ !(@source.empty? and @stack.empty?)
+ end
+
+ # Push an event back on the head of the stream. This method
+ # has (theoretically) infinite depth.
+ def unshift token
+ @stack.unshift(token)
+ end
+
+ # 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.
+ 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
+
+ # Returns the next event. This is a +PullEvent+ object.
+ def pull
+ @source.drop_parsed_content
+
+ pull_event.tap do |event|
+ @listeners.each do |listener|
+ listener.receive event
+ end
+ end
+ end
+
+ 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
+ private :pull_event
+
+ def entity( reference, entities )
+ return unless entities
+
+ value = entities[ reference ]
+ return if value.nil?
+
+ record_entity_expansion
+ unnormalize( value, entities )
+ end
+
+ # Escapes all possible entities
+ 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
+
+ # Unescapes all possible entities
+ 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
+
+ private
+ def add_namespace(prefix, uri)
+ @namespaces_restore_stack.last[prefix] = @namespaces[prefix]
+ if uri.nil?
+ @namespaces.delete(prefix)
+ else
+ @namespaces[prefix] = uri
+ end
+ end
+
+ def push_namespaces_restore
+ namespaces_restore = {}
+ @namespaces_restore_stack.push(namespaces_restore)
+ namespaces_restore
+ end
+
+ 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
+
+ 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
+
+ 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
+
+ def normalize_xml_declaration_encoding(xml_declaration_encoding)
+ /\AUTF-16(?:BE|LE)\z/i.match?(xml_declaration_encoding) ? "UTF-16" : nil
+ end
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ if StringScanner::Version < "3.1.1"
+ def scan_quote
+ @source.match(/(['"])/, true)&.[](1)
+ end
+ else
+ def scan_quote
+ case @source.peek_byte
+ when 34 # '"'.ord
+ @source.scan_byte
+ '"'
+ when 39 # "'".ord
+ @source.scan_byte
+ "'"
+ else
+ nil
+ end
+ end
+ end
+
+ 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
+
+ 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
+ end
+ end
+end
+
+=begin
+ case event[0]
+ when :start_element
+ when :text
+ when :end_element
+ when :processing_instruction
+ when :cdata
+ when :comment
+ when :xmldecl
+ when :start_doctype
+ when :end_doctype
+ when :externalentity
+ when :elementdecl
+ when :entity
+ when :attlistdecl
+ when :notationdecl
+ when :end_doctype
+ end
+=end
diff --git a/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/lightparser.rb b/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/lightparser.rb
new file mode 100644
index 0000000..bdc0827
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/lightparser.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: false
+require_relative 'streamparser'
+require_relative 'baseparser'
+require_relative '../light/node'
+
+module REXML
+ module Parsers
+ class LightParser
+ def initialize stream
+ @stream = stream
+ @parser = REXML::Parsers::BaseParser.new( stream )
+ end
+
+ def add_listener( listener )
+ @parser.add_listener( listener )
+ end
+
+ def rewind
+ @stream.rewind
+ @parser.stream = @stream
+ end
+
+ def parse
+ root = context = [ :document ]
+ while true
+ event = @parser.pull
+ case event[0]
+ when :end_document
+ break
+ when :start_element, :start_doctype
+ new_node = event
+ context << new_node
+ new_node[1,0] = [context]
+ context = new_node
+ when :end_element, :end_doctype
+ context = context[1]
+ else
+ new_node = event
+ context << new_node
+ new_node[1,0] = [context]
+ end
+ end
+ root
+ end
+ end
+
+ # An element is an array. The array contains:
+ # 0 The parent element
+ # 1 The tag name
+ # 2 A hash of attributes
+ # 3..-1 The child elements
+ # An element is an array of size > 3
+ # Text is a String
+ # PIs are [ :processing_instruction, target, data ]
+ # Comments are [ :comment, data ]
+ # DocTypes are DocType structs
+ # The root is an array with XMLDecls, Text, DocType, Array, Text
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/pullparser.rb b/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/pullparser.rb
new file mode 100644
index 0000000..e0b1e94
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/pullparser.rb
@@ -0,0 +1,213 @@
+# frozen_string_literal: false
+require 'forwardable'
+
+require_relative '../parseexception'
+require_relative 'baseparser'
+require_relative '../xmltokens'
+
+module REXML
+ module Parsers
+ # = Using the Pull Parser
+ # <em>This API is experimental, and subject to change.</em>
+ # 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.
+ class PullParser
+ include XMLTokens
+ extend Forwardable
+
+ def_delegators( :@parser, :has_next? )
+ def_delegators( :@parser, :entity )
+ def_delegators( :@parser, :empty? )
+ def_delegators( :@parser, :source )
+
+ def initialize stream
+ @entities = {}
+ @listeners = nil
+ @parser = BaseParser.new( stream )
+ @my_stack = []
+ end
+
+ def add_listener( listener )
+ @listeners = [] unless @listeners
+ @listeners << listener
+ end
+
+ def entity_expansion_count
+ @parser.entity_expansion_count
+ end
+
+ def entity_expansion_limit=( limit )
+ @parser.entity_expansion_limit = limit
+ end
+
+ def entity_expansion_text_limit=( limit )
+ @parser.entity_expansion_text_limit = limit
+ end
+
+ def each
+ while has_next?
+ yield self.pull
+ end
+ end
+
+ def peek depth=0
+ if @my_stack.length <= depth
+ (depth - @my_stack.length + 1).times {
+ e = PullEvent.new(@parser.pull)
+ @my_stack.push(e)
+ }
+ end
+ @my_stack[depth]
+ end
+
+ def pull
+ return @my_stack.shift if @my_stack.length > 0
+
+ event = @parser.pull
+ case event[0]
+ when :entitydecl
+ @entities[ event[1] ] =
+ event[2] unless event[2] =~ /PUBLIC|SYSTEM/
+ when :text
+ unnormalized = @parser.unnormalize( event[1], @entities )
+ event << unnormalized
+ end
+ PullEvent.new( event )
+ end
+
+ def unshift token
+ @my_stack.unshift token
+ end
+
+ def reset
+ @parser.reset
+ end
+ end
+
+ # A parsing event. The contents of the event are accessed as an +Array?,
+ # and the type is given either by the ...? methods, or by accessing the
+ # +type+ accessor. The contents of this object vary from event to event,
+ # but are identical to the arguments passed to +StreamListener+s for each
+ # event.
+ class PullEvent
+ # The type of this event. Will be one of :tag_start, :tag_end, :text,
+ # :processing_instruction, :comment, :doctype, :attlistdecl, :entitydecl,
+ # :notationdecl, :entity, :cdata, :xmldecl, or :error.
+ def initialize(arg)
+ @contents = arg
+ end
+
+ def []( start, endd=nil)
+ if start.kind_of? Range
+ @contents.slice( start.begin+1 .. start.end )
+ elsif start.kind_of? Numeric
+ if endd.nil?
+ @contents.slice( start+1 )
+ else
+ @contents.slice( start+1, endd )
+ end
+ else
+ raise "Illegal argument #{start.inspect} (#{start.class})"
+ end
+ end
+
+ def event_type
+ @contents[0]
+ end
+
+ # Content: [ String tag_name, Hash attributes ]
+ def start_element?
+ @contents[0] == :start_element
+ end
+
+ # Content: [ String tag_name ]
+ def end_element?
+ @contents[0] == :end_element
+ end
+
+ # Content: [ String raw_text, String unnormalized_text ]
+ def text?
+ @contents[0] == :text
+ end
+
+ # Content: [ String text ]
+ def instruction?
+ @contents[0] == :processing_instruction
+ end
+
+ # Content: [ String text ]
+ def comment?
+ @contents[0] == :comment
+ end
+
+ # Content: [ String name, String pub_sys, String long_name, String uri ]
+ def doctype?
+ @contents[0] == :start_doctype
+ end
+
+ # Content: [ String text ]
+ def attlistdecl?
+ @contents[0] == :attlistdecl
+ end
+
+ # Content: [ String text ]
+ def elementdecl?
+ @contents[0] == :elementdecl
+ end
+
+ # Due to the wonders of DTDs, an entity declaration can be just about
+ # anything. There's no way to normalize it; you'll have to interpret the
+ # content yourself. However, the following is true:
+ #
+ # * If the entity declaration is an internal entity:
+ # [ String name, String value ]
+ # Content: [ String text ]
+ def entitydecl?
+ @contents[0] == :entitydecl
+ end
+
+ # Content: [ String text ]
+ def notationdecl?
+ @contents[0] == :notationdecl
+ end
+
+ # Content: [ String text ]
+ def entity?
+ @contents[0] == :entity
+ end
+
+ # Content: [ String text ]
+ def cdata?
+ @contents[0] == :cdata
+ end
+
+ # Content: [ String version, String encoding, String standalone ]
+ def xmldecl?
+ @contents[0] == :xmldecl
+ end
+
+ def error?
+ @contents[0] == :error
+ end
+
+ def inspect
+ @contents[0].to_s + ": " + @contents[1..-1].inspect
+ end
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/sax2parser.rb b/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/sax2parser.rb
new file mode 100644
index 0000000..a51477d
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/sax2parser.rb
@@ -0,0 +1,270 @@
+# frozen_string_literal: false
+require_relative 'baseparser'
+require_relative '../parseexception'
+require_relative '../namespace'
+require_relative '../text'
+
+module REXML
+ module Parsers
+ # SAX2Parser
+ class SAX2Parser
+ def initialize source
+ @parser = BaseParser.new(source)
+ @listeners = []
+ @procs = []
+ @namespace_stack = []
+ @has_listeners = false
+ @tag_stack = []
+ @entities = {}
+ end
+
+ def source
+ @parser.source
+ end
+
+ def entity_expansion_count
+ @parser.entity_expansion_count
+ end
+
+ def entity_expansion_limit=( limit )
+ @parser.entity_expansion_limit = limit
+ end
+
+ def entity_expansion_text_limit=( limit )
+ @parser.entity_expansion_text_limit = limit
+ end
+
+ def add_listener( listener )
+ @parser.add_listener( listener )
+ end
+
+ # Listen arguments:
+ #
+ # Symbol, Array, Block
+ # Listen to Symbol events on Array elements
+ # Symbol, Block
+ # Listen to Symbol events
+ # Array, Listener
+ # Listen to all events on Array elements
+ # Array, Block
+ # Listen to :start_element events on Array elements
+ # Listener
+ # Listen to All events
+ #
+ # Symbol can be one of: :start_element, :end_element,
+ # :start_prefix_mapping, :end_prefix_mapping, :characters,
+ # :processing_instruction, :doctype, :attlistdecl, :elementdecl,
+ # :entitydecl, :notationdecl, :cdata, :xmldecl, :comment
+ #
+ # There is an additional symbol that can be listened for: :progress.
+ # This will be called for every event generated, passing in the current
+ # stream position.
+ #
+ # Array contains regular expressions or strings which will be matched
+ # against fully qualified element names.
+ #
+ # Listener must implement the methods in SAX2Listener
+ #
+ # Block will be passed the same arguments as a SAX2Listener method would
+ # be, where the method name is the same as the matched Symbol.
+ # See the SAX2Listener for more information.
+ def listen( *args, &blok )
+ if args[0].kind_of? Symbol
+ if args.size == 2
+ args[1].each { |match| @procs << [args[0], match, blok] }
+ else
+ add( [args[0], nil, blok] )
+ end
+ elsif args[0].kind_of? Array
+ if args.size == 2
+ args[0].each { |match| add( [nil, match, args[1]] ) }
+ else
+ args[0].each { |match| add( [ :start_element, match, blok ] ) }
+ end
+ else
+ add([nil, nil, args[0]])
+ end
+ end
+
+ def deafen( listener=nil, &blok )
+ if listener
+ @listeners.delete_if {|item| item[-1] == listener }
+ @has_listeners = false if @listeners.size == 0
+ else
+ @procs.delete_if {|item| item[-1] == blok }
+ end
+ end
+
+ def parse
+ @procs.each { |sym,match,block| block.call if sym == :start_document }
+ @listeners.each { |sym,match,block|
+ block.start_document if sym == :start_document or sym.nil?
+ }
+ context = []
+ while true
+ event = @parser.pull
+ case event[0]
+ when :end_document
+ handle( :end_document )
+ break
+ when :start_doctype
+ handle( :doctype, *event[1..-1])
+ when :end_doctype
+ context = context[1]
+ when :start_element
+ @tag_stack.push(event[1])
+ # find the observers for namespaces
+ procs = get_procs( :start_prefix_mapping, event[1] )
+ listeners = get_listeners( :start_prefix_mapping, event[1] )
+ if procs or listeners
+ # break out the namespace declarations
+ # The attributes live in event[2]
+ event[2].each {|n, v| event[2][n] = @parser.normalize(v)}
+ nsdecl = event[2].find_all { |n, value| n =~ /^xmlns(:|$)/ }
+ nsdecl.collect! { |n, value| [ n[6..-1], value ] }
+ @namespace_stack.push({})
+ nsdecl.each do |n,v|
+ @namespace_stack[-1][n] = v
+ # notify observers of namespaces
+ procs.each { |ob| ob.call( n, v ) } if procs
+ listeners.each { |ob| ob.start_prefix_mapping(n, v) } if listeners
+ end
+ end
+ event[1] =~ Namespace::NAMESPLIT
+ prefix = $1
+ local = $2
+ uri = get_namespace(prefix)
+ # find the observers for start_element
+ procs = get_procs( :start_element, event[1] )
+ listeners = get_listeners( :start_element, event[1] )
+ # notify observers
+ procs.each { |ob| ob.call( uri, local, event[1], event[2] ) } if procs
+ listeners.each { |ob|
+ ob.start_element( uri, local, event[1], event[2] )
+ } if listeners
+ when :end_element
+ @tag_stack.pop
+ event[1] =~ Namespace::NAMESPLIT
+ prefix = $1
+ local = $2
+ uri = get_namespace(prefix)
+ # find the observers for start_element
+ procs = get_procs( :end_element, event[1] )
+ listeners = get_listeners( :end_element, event[1] )
+ # notify observers
+ procs.each { |ob| ob.call( uri, local, event[1] ) } if procs
+ listeners.each { |ob|
+ ob.end_element( uri, local, event[1] )
+ } if listeners
+
+ namespace_mapping = @namespace_stack.pop
+ # find the observers for namespaces
+ procs = get_procs( :end_prefix_mapping, event[1] )
+ listeners = get_listeners( :end_prefix_mapping, event[1] )
+ if procs or listeners
+ namespace_mapping.each do |ns_prefix, ns_uri|
+ # notify observers of namespaces
+ procs.each { |ob| ob.call( ns_prefix ) } if procs
+ listeners.each { |ob| ob.end_prefix_mapping(ns_prefix) } if listeners
+ end
+ end
+ when :text
+ unnormalized = @parser.unnormalize( event[1], @entities )
+ handle( :characters, unnormalized )
+ when :entitydecl
+ handle_entitydecl( event )
+ when :processing_instruction, :comment, :attlistdecl,
+ :elementdecl, :cdata, :notationdecl, :xmldecl
+ handle( *event )
+ end
+ handle( :progress, @parser.position )
+ end
+ end
+
+ private
+ def handle( symbol, *arguments )
+ tag = @tag_stack[-1]
+ procs = get_procs( symbol, tag )
+ listeners = get_listeners( symbol, tag )
+ # notify observers
+ procs.each { |ob| ob.call( *arguments ) } if procs
+ listeners.each { |l|
+ l.send( symbol.to_s, *arguments )
+ } if listeners
+ end
+
+ def handle_entitydecl( event )
+ @entities[ event[1] ] = event[2] if event.size == 3
+ parameter_reference_p = false
+ case event[2]
+ when "SYSTEM"
+ if event.size == 5
+ if event.last == "%"
+ parameter_reference_p = true
+ else
+ event[4, 0] = "NDATA"
+ end
+ end
+ when "PUBLIC"
+ if event.size == 6
+ if event.last == "%"
+ parameter_reference_p = true
+ else
+ event[5, 0] = "NDATA"
+ end
+ end
+ else
+ parameter_reference_p = (event.size == 4)
+ end
+ event[1, 0] = event.pop if parameter_reference_p
+ handle( event[0], event[1..-1] )
+ end
+
+ # The following methods are duplicates, but it is faster than using
+ # a helper
+ def get_procs( symbol, name )
+ return nil if @procs.size == 0
+ @procs.find_all do |sym, match, block|
+ (
+ (sym.nil? or symbol == sym) and
+ ((name.nil? and match.nil?) or match.nil? or (
+ (name == match) or
+ (match.kind_of? Regexp and name =~ match)
+ )
+ )
+ )
+ end.collect{|x| x[-1]}
+ end
+ def get_listeners( symbol, name )
+ return nil if @listeners.size == 0
+ @listeners.find_all do |sym, match, block|
+ (
+ (sym.nil? or symbol == sym) and
+ ((name.nil? and match.nil?) or match.nil? or (
+ (name == match) or
+ (match.kind_of? Regexp and name =~ match)
+ )
+ )
+ )
+ end.collect{|x| x[-1]}
+ end
+
+ def add( pair )
+ if pair[-1].respond_to? :call
+ @procs << pair unless @procs.include? pair
+ else
+ @listeners << pair unless @listeners.include? pair
+ @has_listeners = true
+ end
+ end
+
+ def get_namespace( prefix )
+ return nil if @namespace_stack.empty?
+
+ uris = (@namespace_stack.find_all { |ns| not ns[prefix].nil? }) ||
+ (@namespace_stack.find { |ns| not ns[nil].nil? })
+ uris[-1][prefix] unless uris.nil? or 0 == uris.size
+ end
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/streamparser.rb b/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/streamparser.rb
new file mode 100644
index 0000000..6c64d97
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/streamparser.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: false
+require_relative "baseparser"
+
+module REXML
+ module Parsers
+ class StreamParser
+ def initialize source, listener
+ @listener = listener
+ @parser = BaseParser.new( source )
+ @entities = {}
+ end
+
+ def add_listener( listener )
+ @parser.add_listener( listener )
+ end
+
+ def entity_expansion_count
+ @parser.entity_expansion_count
+ end
+
+ def entity_expansion_limit=( limit )
+ @parser.entity_expansion_limit = limit
+ end
+
+ def entity_expansion_text_limit=( limit )
+ @parser.entity_expansion_text_limit = limit
+ end
+
+ def parse
+ # entity string
+ while true
+ event = @parser.pull
+ case event[0]
+ when :end_document
+ return
+ when :start_element
+ attrs = event[2].each do |n, v|
+ event[2][n] = @parser.unnormalize( v )
+ end
+ @listener.tag_start( event[1], attrs )
+ when :end_element
+ @listener.tag_end( event[1] )
+ when :text
+ unnormalized = @parser.unnormalize( event[1], @entities )
+ @listener.text( unnormalized )
+ when :processing_instruction
+ @listener.instruction( *event[1,2] )
+ when :start_doctype
+ @listener.doctype( *event[1..-1] )
+ when :end_doctype
+ # FIXME: remove this condition for milestone:3.2
+ @listener.doctype_end if @listener.respond_to? :doctype_end
+ when :comment, :attlistdecl, :cdata, :xmldecl, :elementdecl
+ @listener.send( event[0].to_s, *event[1..-1] )
+ when :entitydecl, :notationdecl
+ @entities[ event[1] ] = event[2] if event.size == 3
+ @listener.send( event[0].to_s, event[1..-1] )
+ when :externalentity
+ entity_reference = event[1]
+ content = entity_reference.gsub(/\A%|;\z/, "")
+ @listener.entity(content)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/treeparser.rb b/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/treeparser.rb
new file mode 100644
index 0000000..4565a40
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/treeparser.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: false
+require_relative '../validation/validationexception'
+require_relative '../undefinednamespaceexception'
+
+module REXML
+ module Parsers
+ class TreeParser
+ def initialize( source, build_context = Document.new )
+ @build_context = build_context
+ @parser = Parsers::BaseParser.new( source )
+ end
+
+ def add_listener( listener )
+ @parser.add_listener( listener )
+ end
+
+ def parse
+ entities = nil
+ begin
+ while true
+ event = @parser.pull
+ #STDERR.puts "TREEPARSER GOT #{event.inspect}"
+ case event[0]
+ when :end_document
+ return
+ when :start_element
+ el = @build_context = @build_context.add_element( event[1] )
+ event[2].each do |key, value|
+ el.attributes[key]=Attribute.new(key,value,self)
+ end
+ when :end_element
+ @build_context = @build_context.parent
+ when :text
+ if @build_context[-1].instance_of? Text
+ @build_context[-1] << event[1]
+ else
+ @build_context.add(
+ Text.new(event[1], @build_context.whitespace, nil, true)
+ ) unless (
+ @build_context.ignore_whitespace_nodes and
+ event[1].strip.size==0
+ )
+ end
+ when :comment
+ c = Comment.new( event[1] )
+ @build_context.add( c )
+ when :cdata
+ c = CData.new( event[1] )
+ @build_context.add( c )
+ when :processing_instruction
+ @build_context.add( Instruction.new( event[1], event[2] ) )
+ when :end_doctype
+ entities.each { |k,v| entities[k] = @build_context.entities[k].value }
+ @build_context = @build_context.parent
+ when :start_doctype
+ doctype = DocType.new( event[1..-1], @build_context )
+ @build_context = doctype
+ entities = {}
+ when :attlistdecl
+ n = AttlistDecl.new( event[1..-1] )
+ @build_context.add( n )
+ when :externalentity
+ n = ExternalEntity.new( event[1] )
+ @build_context.add( n )
+ when :elementdecl
+ n = ElementDecl.new( event[1] )
+ @build_context.add(n)
+ when :entitydecl
+ entities[ event[1] ] = event[2] unless event[2] =~ /PUBLIC|SYSTEM/
+ @build_context.add(Entity.new(event))
+ when :notationdecl
+ n = NotationDecl.new( *event[1..-1] )
+ @build_context.add( n )
+ when :xmldecl
+ x = XMLDecl.new( event[1], event[2], event[3] )
+ @build_context.add( x )
+ end
+ end
+ rescue REXML::Validation::ValidationException
+ raise
+ rescue REXML::ParseException
+ raise
+ rescue
+ raise ParseException.new( $!.message, @parser.source, @parser, $! )
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/ultralightparser.rb b/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/ultralightparser.rb
new file mode 100644
index 0000000..e0029f4
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/ultralightparser.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: false
+require_relative 'streamparser'
+require_relative 'baseparser'
+
+module REXML
+ module Parsers
+ class UltraLightParser
+ def initialize stream
+ @stream = stream
+ @parser = REXML::Parsers::BaseParser.new( stream )
+ end
+
+ def add_listener( listener )
+ @parser.add_listener( listener )
+ end
+
+ def rewind
+ @stream.rewind
+ @parser.stream = @stream
+ end
+
+ def parse
+ root = context = []
+ while true
+ event = @parser.pull
+ case event[0]
+ when :end_document
+ break
+ when :end_doctype
+ context = context[1]
+ when :start_element, :start_doctype
+ context << event
+ event[1,0] = [context]
+ context = event
+ when :end_element
+ context = context[1]
+ else
+ context << event
+ end
+ end
+ root
+ end
+ end
+
+ # An element is an array. The array contains:
+ # 0 The parent element
+ # 1 The tag name
+ # 2 A hash of attributes
+ # 3..-1 The child elements
+ # An element is an array of size > 3
+ # Text is a String
+ # PIs are [ :processing_instruction, target, data ]
+ # Comments are [ :comment, data ]
+ # DocTypes are DocType structs
+ # The root is an array with XMLDecls, Text, DocType, Array, Text
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/xpathparser.rb b/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/xpathparser.rb
new file mode 100644
index 0000000..a6d76fd
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/rexml-3.4.4/lib/rexml/parsers/xpathparser.rb
@@ -0,0 +1,739 @@
+# frozen_string_literal: false
+
+require_relative '../namespace'
+require_relative '../xmltokens'
+
+module REXML
+ module Parsers
+ # You don't want to use this class. Really. Use XPath, which is a wrapper
+ # for this class. Believe me. You don't want to poke around in here.
+ # There is strange, dark magic at work in this code. Beware. Go back! Go
+ # back while you still can!
+ class XPathParser
+ include XMLTokens
+ LITERAL = /^'([^']*)'|^"([^"]*)"/u
+
+ def namespaces=( namespaces )
+ Functions::namespace_context = namespaces
+ @namespaces = namespaces
+ end
+
+ def parse path
+ path = path.dup
+ path.gsub!(/([\(\[])\s+/, '\1') # Strip ignorable spaces
+ path.gsub!( /\s+([\]\)])/, '\1')
+ parsed = []
+ rest = OrExpr(path, parsed)
+ if rest
+ unless rest.strip.empty?
+ raise ParseException.new("Garbage component exists at the end: " +
+ "<#{rest}>: <#{path}>")
+ end
+ end
+ parsed
+ end
+
+ def predicate path
+ parsed = []
+ Predicate( "[#{path}]", parsed )
+ parsed
+ end
+
+ def abbreviate(path_or_parsed)
+ if path_or_parsed.kind_of?(String)
+ parsed = parse(path_or_parsed)
+ else
+ parsed = path_or_parsed
+ end
+ components = []
+ component = nil
+ while parsed.size > 0
+ op = parsed.shift
+ case op
+ when :node
+ component << "node()"
+ when :attribute
+ component = "@"
+ components << component
+ when :child
+ component = ""
+ components << component
+ when :descendant_or_self
+ next_op = parsed[0]
+ if next_op == :node
+ parsed.shift
+ component = ""
+ components << component
+ else
+ component = "descendant-or-self::"
+ components << component
+ end
+ when :self
+ next_op = parsed[0]
+ if next_op == :node
+ parsed.shift
+ components << "."
+ else
+ component = "self::"
+ components << component
+ end
+ when :parent
+ next_op = parsed[0]
+ if next_op == :node
+ parsed.shift
+ components << ".."
+ else
+ component = "parent::"
+ components << component
+ end
+ when :any
+ component << "*"
+ when :text
+ component << "text()"
+ when :following, :following_sibling,
+ :ancestor, :ancestor_or_self, :descendant,
+ :namespace, :preceding, :preceding_sibling
+ component = op.to_s.tr("_", "-") << "::"
+ components << component
+ when :qname
+ prefix = parsed.shift
+ name = parsed.shift
+ component << prefix+":" if prefix.size > 0
+ component << name
+ when :predicate
+ component << '['
+ component << predicate_to_path(parsed.shift) {|x| abbreviate(x)}
+ component << ']'
+ when :document
+ components << ""
+ when :function
+ component << parsed.shift
+ component << "( "
+ component << predicate_to_path(parsed.shift[0]) {|x| abbreviate(x)}
+ component << " )"
+ when :literal
+ component << quote_literal(parsed.shift)
+ else
+ component << "UNKNOWN("
+ component << op.inspect
+ component << ")"
+ end
+ end
+ case components
+ when [""]
+ "/"
+ when ["", ""]
+ "//"
+ else
+ components.join("/")
+ end
+ end
+
+ def expand(path_or_parsed)
+ if path_or_parsed.kind_of?(String)
+ parsed = parse(path_or_parsed)
+ else
+ parsed = path_or_parsed
+ end
+ path = ""
+ document = false
+ while parsed.size > 0
+ op = parsed.shift
+ case op
+ when :node
+ path << "node()"
+ when :attribute, :child, :following, :following_sibling,
+ :ancestor, :ancestor_or_self, :descendant, :descendant_or_self,
+ :namespace, :preceding, :preceding_sibling, :self, :parent
+ path << "/" unless path.size == 0
+ path << op.to_s.tr("_", "-")
+ path << "::"
+ when :any
+ path << "*"
+ when :qname
+ prefix = parsed.shift
+ name = parsed.shift
+ path << prefix+":" if prefix.size > 0
+ path << name
+ when :predicate
+ path << '['
+ path << predicate_to_path( parsed.shift ) { |x| expand(x) }
+ path << ']'
+ when :document
+ document = true
+ else
+ path << "UNKNOWN("
+ path << op.inspect
+ path << ")"
+ end
+ end
+ path = "/"+path if document
+ path
+ end
+
+ def predicate_to_path(parsed, &block)
+ path = ""
+ case parsed[0]
+ when :and, :or, :mult, :plus, :minus, :neq, :eq, :lt, :gt, :lteq, :gteq, :div, :mod, :union
+ op = parsed.shift
+ case op
+ when :eq
+ op = "="
+ when :lt
+ op = "<"
+ when :gt
+ op = ">"
+ when :lteq
+ op = "<="
+ when :gteq
+ op = ">="
+ when :neq
+ op = "!="
+ when :union
+ op = "|"
+ end
+ left = predicate_to_path( parsed.shift, &block )
+ right = predicate_to_path( parsed.shift, &block )
+ path << left
+ path << " "
+ path << op.to_s
+ path << " "
+ path << right
+ when :function
+ parsed.shift
+ name = parsed.shift
+ path << name
+ path << "("
+ parsed.shift.each_with_index do |argument, i|
+ path << ", " if i > 0
+ path << predicate_to_path(argument, &block)
+ end
+ path << ")"
+ when :literal
+ parsed.shift
+ path << quote_literal(parsed.shift)
+ else
+ path << yield( parsed )
+ end
+ path.squeeze(" ")
+ end
+ # For backward compatibility
+ alias_method :preciate_to_string, :predicate_to_path
+
+ private
+ def quote_literal( literal )
+ case literal
+ when String
+ # XPath 1.0 does not support escape characters.
+ # Assumes literal does not contain both single and double quotes.
+ if literal.include?("'")
+ "\"#{literal}\""
+ else
+ "'#{literal}'"
+ end
+ else
+ literal.inspect
+ end
+ end
+
+ #LocationPath
+ # | RelativeLocationPath
+ # | '/' RelativeLocationPath?
+ # | '//' RelativeLocationPath
+ def LocationPath path, parsed
+ path = path.lstrip
+ if path[0] == ?/
+ parsed << :document
+ if path[1] == ?/
+ parsed << :descendant_or_self
+ parsed << :node
+ path = path[2..-1]
+ else
+ path = path[1..-1]
+ end
+ end
+ RelativeLocationPath( path, parsed ) if path.size > 0
+ end
+
+ #RelativeLocationPath
+ # | Step
+ # | (AXIS_NAME '::' | '@' | '') AxisSpecifier
+ # NodeTest
+ # Predicate
+ # | '.' | '..' AbbreviatedStep
+ # | RelativeLocationPath '/' Step
+ # | RelativeLocationPath '//' Step
+ AXIS = /^(ancestor|ancestor-or-self|attribute|child|descendant|descendant-or-self|following|following-sibling|namespace|parent|preceding|preceding-sibling|self)::/
+ def RelativeLocationPath path, parsed
+ loop do
+ original_path = path
+ path = path.lstrip
+
+ return original_path if path.empty?
+
+ # (axis or @ or <child::>) nodetest predicate >
+ # OR > / Step
+ # (. or ..) >
+ if path[0] == ?.
+ if path[1] == ?.
+ parsed << :parent
+ parsed << :node
+ path = path[2..-1]
+ else
+ parsed << :self
+ parsed << :node
+ path = path[1..-1]
+ end
+ else
+ path_before_axis_specifier = path
+ parsed_not_abberviated = []
+ if path[0] == ?@
+ parsed_not_abberviated << :attribute
+ path = path[1..-1]
+ # Goto Nodetest
+ elsif path =~ AXIS
+ parsed_not_abberviated << $1.tr('-','_').intern
+ path = $'
+ # Goto Nodetest
+ else
+ parsed_not_abberviated << :child
+ end
+
+ path_before_node_test = path
+ path = NodeTest(path, parsed_not_abberviated)
+ if path == path_before_node_test
+ return path_before_axis_specifier
+ end
+ path = Predicate(path, parsed_not_abberviated)
+
+ parsed.concat(parsed_not_abberviated)
+ end
+
+ original_path = path
+ path = path.lstrip
+ return original_path if path.empty?
+
+ return original_path if path[0] != ?/
+
+ if path[1] == ?/
+ parsed << :descendant_or_self
+ parsed << :node
+ path = path[2..-1]
+ else
+ path = path[1..-1]
+ end
+ end
+ end
+
+ # Returns a 1-1 map of the nodeset
+ # The contents of the resulting array are either:
+ # true/false, if a positive match
+ # String, if a name match
+ #NodeTest
+ # | ('*' | NCNAME ':' '*' | QNAME) NameTest
+ # | '*' ':' NCNAME NameTest since XPath 2.0
+ # | NODE_TYPE '(' ')' NodeType
+ # | PI '(' LITERAL ')' PI
+ # | '[' expr ']' Predicate
+ PREFIX_WILDCARD = /^\*:(#{NCNAME_STR})/u
+ LOCAL_NAME_WILDCARD = /^(#{NCNAME_STR}):\*/u
+ QNAME = Namespace::NAMESPLIT
+ NODE_TYPE = /^(comment|text|node)\(\s*\)/m
+ PI = /^processing-instruction\(/
+ def NodeTest path, parsed
+ original_path = path
+ path = path.lstrip
+ case path
+ when PREFIX_WILDCARD
+ prefix = nil
+ name = $1
+ path = $'
+ parsed << :qname
+ parsed << prefix
+ parsed << name
+ when /^\*/
+ path = $'
+ parsed << :any
+ when NODE_TYPE
+ type = $1
+ path = $'
+ parsed << type.tr('-', '_').intern
+ when PI
+ path = $'
+ literal = nil
+ if path =~ /^\s*\)/
+ path = $'
+ else
+ path =~ LITERAL
+ literal = $1
+ path = $'
+ raise ParseException.new("Missing ')' after processing instruction") if path[0] != ?)
+ path = path[1..-1]
+ end
+ parsed << :processing_instruction
+ parsed << (literal || '')
+ when LOCAL_NAME_WILDCARD
+ prefix = $1
+ path = $'
+ parsed << :namespace
+ parsed << prefix
+ when QNAME
+ prefix = $1
+ name = $2
+ path = $'
+ prefix = "" unless prefix
+ parsed << :qname
+ parsed << prefix
+ parsed << name
+ else
+ path = original_path
+ end
+ path
+ end
+
+ # Filters the supplied nodeset on the predicate(s)
+ def Predicate path, parsed
+ original_path = path
+ path = path.lstrip
+ return original_path unless path[0] == ?[
+ predicates = []
+ while path[0] == ?[
+ path, expr = get_group(path)
+ predicates << expr[1..-2] if expr
+ end
+ predicates.each{ |pred|
+ preds = []
+ parsed << :predicate
+ parsed << preds
+ OrExpr(pred, preds)
+ }
+ path
+ end
+
+ # The following return arrays of true/false, a 1-1 mapping of the
+ # supplied nodeset, except for axe(), which returns a filtered
+ # nodeset
+
+ #| OrExpr S 'or' S AndExpr
+ #| AndExpr
+ def OrExpr path, parsed
+ n = []
+ rest = AndExpr( path, n )
+ if rest != path
+ while rest =~ /^\s*( or )/
+ n = [ :or, n, [] ]
+ rest = AndExpr( $', n[-1] )
+ end
+ end
+ if parsed.size == 0 and n.size != 0
+ parsed.replace(n)
+ elsif n.size > 0
+ parsed << n
+ end
+ rest
+ end
+
+ #| AndExpr S 'and' S EqualityExpr
+ #| EqualityExpr
+ def AndExpr path, parsed
+ n = []
+ rest = EqualityExpr( path, n )
+ if rest != path
+ while rest =~ /^\s*( and )/
+ n = [ :and, n, [] ]
+ rest = EqualityExpr( $', n[-1] )
+ end
+ end
+ if parsed.size == 0 and n.size != 0
+ parsed.replace(n)
+ elsif n.size > 0
+ parsed << n
+ end
+ rest
+ end
+
+ #| EqualityExpr ('=' | '!=') RelationalExpr
+ #| RelationalExpr
+ def EqualityExpr path, parsed
+ n = []
+ rest = RelationalExpr( path, n )
+ if rest != path
+ while rest =~ /^\s*(!?=)\s*/
+ if $1[0] == ?!
+ n = [ :neq, n, [] ]
+ else
+ n = [ :eq, n, [] ]
+ end
+ rest = RelationalExpr( $', n[-1] )
+ end
+ end
+ if parsed.size == 0 and n.size != 0
+ parsed.replace(n)
+ elsif n.size > 0
+ parsed << n
+ end
+ rest
+ end
+
+ #| RelationalExpr ('<' | '>' | '<=' | '>=') AdditiveExpr
+ #| AdditiveExpr
+ def RelationalExpr path, parsed
+ n = []
+ rest = AdditiveExpr( path, n )
+ if rest != path
+ while rest =~ /^\s*([<>]=?)\s*/
+ if $1[0] == ?<
+ sym = "lt"
+ else
+ sym = "gt"
+ end
+ sym << "eq" if $1[-1] == ?=
+ n = [ sym.intern, n, [] ]
+ rest = AdditiveExpr( $', n[-1] )
+ end
+ end
+ if parsed.size == 0 and n.size != 0
+ parsed.replace(n)
+ elsif n.size > 0
+ parsed << n
+ end
+ rest
+ end
+
+ #| AdditiveExpr ('+' | '-') MultiplicativeExpr
+ #| MultiplicativeExpr
+ def AdditiveExpr path, parsed
+ n = []
+ rest = MultiplicativeExpr( path, n )
+ if rest != path
+ while rest =~ /^\s*(\+|-)\s*/
+ if $1[0] == ?+
+ n = [ :plus, n, [] ]
+ else
+ n = [ :minus, n, [] ]
+ end
+ rest = MultiplicativeExpr( $', n[-1] )
+ end
+ end
+ if parsed.size == 0 and n.size != 0
+ parsed.replace(n)
+ elsif n.size > 0
+ parsed << n
+ end
+ rest
+ end
+
+ #| MultiplicativeExpr ('*' | S ('div' | 'mod') S) UnaryExpr
+ #| UnaryExpr
+ def MultiplicativeExpr path, parsed
+ n = []
+ rest = UnaryExpr( path, n )
+ if rest != path
+ while rest =~ /^\s*(\*| div | mod )\s*/
+ if $1[0] == ?*
+ n = [ :mult, n, [] ]
+ elsif $1.include?( "div" )
+ n = [ :div, n, [] ]
+ else
+ n = [ :mod, n, [] ]
+ end
+ rest = UnaryExpr( $', n[-1] )
+ end
+ end
+ if parsed.size == 0 and n.size != 0
+ parsed.replace(n)
+ elsif n.size > 0
+ parsed << n
+ end
+ rest
+ end
+
+ #| '-' UnaryExpr
+ #| UnionExpr
+ def UnaryExpr path, parsed
+ path =~ /^(\-*)/
+ path = $'
+ if $1 and (($1.size % 2) != 0)
+ mult = -1
+ else
+ mult = 1
+ end
+ parsed << :neg if mult < 0
+
+ n = []
+ path = UnionExpr( path, n )
+ parsed.concat( n )
+ path
+ end
+
+ #| UnionExpr '|' PathExpr
+ #| PathExpr
+ def UnionExpr path, parsed
+ n = []
+ rest = PathExpr( path, n )
+ if rest != path
+ while rest =~ /^\s*(\|)\s*/
+ n = [ :union, n, [] ]
+ rest = PathExpr( $', n[-1] )
+ end
+ end
+ if parsed.size == 0 and n.size != 0
+ parsed.replace( n )
+ elsif n.size > 0
+ parsed << n
+ end
+ rest
+ end
+
+ #| LocationPath
+ #| FilterExpr ('/' | '//') RelativeLocationPath
+ def PathExpr path, parsed
+ path = path.lstrip
+ n = []
+ rest = FilterExpr( path, n )
+ if rest != path
+ if rest and rest[0] == ?/
+ rest = RelativeLocationPath(rest, n)
+ parsed.concat(n)
+ return rest
+ end
+ end
+ rest = LocationPath(rest, n) if rest =~ /\A[\/\.\@\[\w*]/
+ parsed.concat(n)
+ rest
+ end
+
+ #| FilterExpr Predicate
+ #| PrimaryExpr
+ def FilterExpr path, parsed
+ n = []
+ path_before_primary_expr = path
+ path = PrimaryExpr(path, n)
+ return path_before_primary_expr if path == path_before_primary_expr
+ path = Predicate(path, n)
+ parsed.concat(n)
+ path
+ end
+
+ #| VARIABLE_REFERENCE
+ #| '(' expr ')'
+ #| LITERAL
+ #| NUMBER
+ #| FunctionCall
+ VARIABLE_REFERENCE = /^\$(#{NAME_STR})/u
+ NUMBER = /^(\d*\.?\d+)/
+ NT = /^comment|text|processing-instruction|node$/
+ def PrimaryExpr path, parsed
+ case path
+ when VARIABLE_REFERENCE
+ varname = $1
+ path = $'
+ parsed << :variable
+ parsed << varname
+ #arry << @variables[ varname ]
+ when /^(\w[-\w]*)(?:\()/
+ fname = $1
+ tmp = $'
+ return path if fname =~ NT
+ path = tmp
+ parsed << :function
+ parsed << fname
+ path = FunctionCall(path, parsed)
+ when NUMBER
+ varname = $1.nil? ? $2 : $1
+ path = $'
+ parsed << :literal
+ parsed << (varname.include?('.') ? varname.to_f : varname.to_i)
+ when LITERAL
+ varname = $1.nil? ? $2 : $1
+ path = $'
+ parsed << :literal
+ parsed << varname
+ when /^\(/ #/
+ path, contents = get_group(path)
+ contents = contents[1..-2]
+ n = []
+ OrExpr( contents, n )
+ parsed.concat(n)
+ end
+ path
+ end
+
+ #| FUNCTION_NAME '(' ( expr ( ',' expr )* )? ')'
+ def FunctionCall rest, parsed
+ path, arguments = parse_args(rest)
+ argset = []
+ for argument in arguments
+ args = []
+ OrExpr( argument, args )
+ argset << args
+ end
+ parsed << argset
+ path
+ end
+
+ # get_group( '[foo]bar' ) -> ['bar', '[foo]']
+ def get_group string
+ ind = 0
+ depth = 0
+ st = string[0,1]
+ en = (st == "(" ? ")" : "]")
+ begin
+ case string[ind,1]
+ when st
+ depth += 1
+ when en
+ depth -= 1
+ end
+ ind += 1
+ end while depth > 0 and ind < string.length
+ return nil unless depth==0
+ [string[ind..-1], string[0..ind-1]]
+ end
+
+ def parse_args( string )
+ arguments = []
+ ind = 0
+ inquot = false
+ inapos = false
+ depth = 1
+ begin
+ case string[ind]
+ when ?"
+ inquot = !inquot unless inapos
+ when ?'
+ inapos = !inapos unless inquot
+ else
+ unless inquot or inapos
+ case string[ind]
+ when ?(
+ depth += 1
+ if depth == 1
+ string = string[1..-1]
+ ind -= 1
+ end
+ when ?)
+ depth -= 1
+ if depth == 0
+ s = string[0,ind].strip
+ arguments << s unless s == ""
+ string = string[ind+1..-1]
+ end
+ when ?,
+ if depth == 1
+ s = string[0,ind].strip
+ arguments << s unless s == ""
+ string = string[ind+1..-1]
+ ind = -1
+ end
+ end
+ end
+ end
+ ind += 1
+ end while depth > 0 and ind < string.length
+ return nil unless depth==0
+ [string,arguments]
+ end
+ end
+ end
+end