1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Merged from REXML main repository:

Fixes ticket:68.
  NOTE that this involves an API change!  Entity declarations in the doctype
  now generate events that carry two, not one, arguments.

Implements ticket:15, using gwrite's suggestion.  This allows Element to be
subclassed.

Two unrelated changes, because subversion is retarded and doesn't do
block-level commits:

  1) Fixed a typo bug in previous change for ticket:15
  2) Fixed namespaces handling in XPath and element.  

    ***** Note that this is an API change!!! *****

    Element.namespaces() now returns a hash of namespace mappings which are
    relevant for that node.

Fixes a bug in multiple decodings

The changeset 1230:1231 was bad.  The default behavior is *not* to use the
native REXML encodings by default, but rather to use ICONV by default.  I know
that this will piss some people off, but defaulting to the pure Ruby version
isn't the correct solution, and it breaks other encodings, so I've reverted it.

* Fixes ticket:61 (xpath_parser)
* Fixes ticket:63 (UTF-16; UNILE decoding was bad)
* Cleans up some tests, removing opportunities for test corruption
* Improves parsing error messages a little
* Adds the ability to override the encoding detection in Source construction
* Fixes an edge case in Functions::string, where document nodes weren't 
  correctly converted
* Fixes Functions::string() for Element and Document nodes
* Fixes some problems in entity handling

Addresses ticket:66

Fixes ticket:71

Addresses ticket:78
  NOTE: that this also fixes what is technically another bug in REXML.  REXML's
  XPath parser used to allow exponential notation in numbers.  The XPath spec
  is specific about what a number is, and scientific notation is not included.
  Therefore, this has been fixed.

Cross-ported a fix for ticket:88 from CVS.

Fixes ticket:80

Documentation cleanup.  Ticket:84

Applied Kou's fix for an un-trac'ed bug.

------------------------------------------------------------------------


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@11548 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
ser 2007-01-20 03:56:02 +00:00
parent f700c1354f
commit fa4bfa6af5
13 changed files with 142 additions and 83 deletions

View file

@ -157,8 +157,9 @@ module REXML
# document will be written. # document will be written.
# indent:: # indent::
# An integer. If -1, no indenting will be used; otherwise, the # An integer. If -1, no indenting will be used; otherwise, the
# indentation will be this number of spaces, and children will be # indentation will be twice this number of spaces, and children will be
# indented an additional amount. Defaults to -1 # indented an additional amount. For a value of 3, every item will be
# indented 3 more levels, or 6 more spaces (2 * 3). Defaults to -1
# transitive:: # transitive::
# If transitive is true and indent is >= 0, then the output will be # If transitive is true and indent is >= 0, then the output will be
# pretty-printed in such a way that the added whitespace does not affect # pretty-printed in such a way that the added whitespace does not affect

View file

@ -94,7 +94,7 @@ module REXML
# new_a = d.root.clone # new_a = d.root.clone
# puts new_a # => "<a/>" # puts new_a # => "<a/>"
def clone def clone
Element.new self self.class.new self
end end
# Evaluates to the root node of the document that this element # Evaluates to the root node of the document that this element
@ -200,9 +200,9 @@ module REXML
end end
def namespaces def namespaces
namespaces = [] namespaces = {}
namespaces = parent.namespaces if parent namespaces = parent.namespaces if parent
namespaces |= attributes.namespaces namespaces = namespaces.merge( attributes.namespaces )
return namespaces return namespaces
end end
@ -494,13 +494,12 @@ module REXML
# doc.root.add_element 'c' #-> '<a><b/>Elliott<c/></a>' # doc.root.add_element 'c' #-> '<a><b/>Elliott<c/></a>'
# doc.root.text = 'Russell' #-> '<a><b/>Russell<c/></a>' # doc.root.text = 'Russell' #-> '<a><b/>Russell<c/></a>'
# doc.root.text = nil #-> '<a><b/><c/></a>' # doc.root.text = nil #-> '<a><b/><c/></a>'
def text=( text ) def text=( text )
if text.kind_of? String if text.kind_of? String
text = Text.new( text, whitespace(), nil, raw() ) text = Text.new( text, whitespace(), nil, raw() )
elsif text and !text.kind_of? Text elsif text and !text.kind_of? Text
text = Text.new( text.to_s, whitespace(), nil, raw() ) text = Text.new( text.to_s, whitespace(), nil, raw() )
end end
old_text = get_text old_text = get_text
if text.nil? if text.nil?
old_text.remove unless old_text.nil? old_text.remove unless old_text.nil?
@ -557,13 +556,9 @@ module REXML
################################################# #################################################
def attribute( name, namespace=nil ) def attribute( name, namespace=nil )
prefix = '' prefix = nil
if namespace prefix = namespaces.index(namespace) if namespace
prefix = attributes.prefixes.each { |prefix| attributes.get_attribute( "#{prefix ? prefix + ':' : ''}#{name}" )
return "#{prefix}:" if namespace( prefix ) == namespace
} || ''
end
attributes.get_attribute( "#{prefix}#{name}" )
end end
# Evaluates to +true+ if this element has any attributes set, false # Evaluates to +true+ if this element has any attributes set, false
@ -1172,16 +1167,16 @@ module REXML
end end
def namespaces def namespaces
namespaces = [] namespaces = {}
each_attribute do |attribute| each_attribute do |attribute|
namespaces << attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns' namespaces[attribute.name] = attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns'
end end
if @element.document and @element.document.doctype if @element.document and @element.document.doctype
expn = @element.expanded_name expn = @element.expanded_name
expn = @element.document.doctype.name if expn.size == 0 expn = @element.document.doctype.name if expn.size == 0
@element.document.doctype.attributes_of(expn).each { @element.document.doctype.attributes_of(expn).each {
|attribute| |attribute|
namespaces << attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns' namespaces[attribute.name] = attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns'
} }
end end
namespaces namespaces

View file

@ -24,20 +24,20 @@ module REXML
old_verbosity = $VERBOSE old_verbosity = $VERBOSE
begin begin
$VERBOSE = false $VERBOSE = false
enc = enc.nil? ? nil : enc.upcase enc = enc.nil? ? nil : enc.upcase
return false if defined? @encoding and enc == @encoding return false if defined? @encoding and enc == @encoding
if enc and enc != UTF_8 if enc and enc != UTF_8
@encoding = enc @encoding = enc
raise ArgumentError, "Bad encoding name #@encoding" unless @encoding =~ /^[\w-]+$/ raise ArgumentError, "Bad encoding name #@encoding" unless @encoding =~ /^[\w-]+$/
@encoding.untaint @encoding.untaint
enc_file = File.join( "rexml", "encodings", "#@encoding.rb" ) begin
begin require 'rexml/encodings/ICONV.rb'
require enc_file Encoding.apply(self, "ICONV")
Encoding.apply(self, @encoding)
rescue LoadError, Exception rescue LoadError, Exception
begin begin
require 'rexml/encodings/ICONV.rb' enc_file = File.join( "rexml", "encodings", "#@encoding.rb" )
Encoding.apply(self, "ICONV") require enc_file
Encoding.apply(self, @encoding)
rescue LoadError => err rescue LoadError => err
puts err.message puts err.message
raise ArgumentError, "No decoder found for encoding #@encoding. Please install iconv." raise ArgumentError, "No decoder found for encoding #@encoding. Please install iconv."
@ -51,14 +51,14 @@ module REXML
ensure ensure
$VERBOSE = old_verbosity $VERBOSE = old_verbosity
end end
true true
end end
def check_encoding str def check_encoding str
# We have to recognize UTF-16, LSB UTF-16, and UTF-8 # We have to recognize UTF-16, LSB UTF-16, and UTF-8
return UTF_16 if /\A\xfe\xff/n =~ str return UTF_16 if /\A\xfe\xff/n =~ str
return UNILE if /\A\xff\xfe/n =~ str return UNILE if /\A\xff\xfe/n =~ str
str =~ /^\s*<?xml\s*version\s*=\s*(['"]).*?\2\s*encoding\s*=\s*(["'])(.*?)\2/um str =~ /^\s*<?xml\s*version=(['"]).*?\2\s*encoding=(["'])(.*?)\2/um
return $1.upcase if $1 return $1.upcase if $1
return UTF_8 return UTF_8
end end

View file

@ -18,7 +18,7 @@ module REXML
def decode_unile(str) def decode_unile(str)
array_enc=str.unpack('C*') array_enc=str.unpack('C*')
array_utf8 = [] array_utf8 = []
2.step(array_enc.size-1, 2){|i| 0.step(array_enc.size-1, 2){|i|
array_utf8 << (array_enc.at(i) + array_enc.at(i+1)*0x100) array_utf8 << (array_enc.at(i) + array_enc.at(i+1)*0x100)
} }
array_utf8.pack('U*') array_utf8.pack('U*')

View file

@ -117,16 +117,30 @@ module REXML
elsif defined? object.node_type elsif defined? object.node_type
if object.node_type == :attribute if object.node_type == :attribute
object.value object.value
elsif object.node_type == :element elsif object.node_type == :element || object.node_type == :document
object.text string_value(object)
else else
object.to_s object.to_s
end end
elsif object.nil?
return ""
else else
object.to_s object.to_s
end end
end end
def Functions::string_value( o )
rv = ""
o.children.each { |e|
if e.node_type == :text
rv << e.to_s
elsif e.node_type == :element
rv << string_value( e )
end
}
rv
end
# UNTESTED # UNTESTED
def Functions::concat( *objects ) def Functions::concat( *objects )
objects.join objects.join
@ -139,7 +153,7 @@ module REXML
# Fixed by Mike Stok # Fixed by Mike Stok
def Functions::contains( string, test ) def Functions::contains( string, test )
string(string).include? string(test) string(string).include?(string(test))
end end
# Kouhei fixed this # Kouhei fixed this
@ -325,8 +339,9 @@ module REXML
object.to_f object.to_f
else else
str = string( object ) str = string( object )
#puts "STRING OF #{object.inspect} = #{str}" # If XPath ever gets scientific notation...
if str =~ /^-?\.?\d/ #if str =~ /^\s*-?(\d*\.?\d+|\d+\.)([Ee]\d*)?\s*$/
if str =~ /^\s*-?(\d*\.?\d+|\d+\.)\s*$/
str.to_f str.to_f
else else
(0.0 / 0.0) (0.0 / 0.0)

View file

@ -55,10 +55,8 @@ module REXML
return nil return nil
end end
# Returns the index that +self+ has in its parent's elements array, so that # Returns the position that +self+ holds in its parent's array, indexed
# the following equation holds true: # from 1.
#
# node == node.parent.elements[node.index_in_parent]
def index_in_parent def index_in_parent
parent.index(self)+1 parent.index(self)+1
end end

View file

@ -146,8 +146,6 @@ module REXML
# Returns true if there are no more events # Returns true if there are no more events
def empty? def empty?
#STDERR.puts "@source.empty? = #{@source.empty?}"
#STDERR.puts "@stack.empty? = #{@stack.empty?}"
return (@source.empty? and @stack.empty?) return (@source.empty? and @stack.empty?)
end end
@ -365,8 +363,6 @@ module REXML
else else
md = @source.match( TEXT_PATTERN, true ) md = @source.match( TEXT_PATTERN, true )
if md[0].length == 0 if md[0].length == 0
puts "EMPTY = #{empty?}"
puts "BUFFER = \"#{@source.buffer}\""
@source.match( /(\s+)/, true ) @source.match( /(\s+)/, true )
end end
#STDERR.puts "GOT #{md[1].inspect}" unless md[0].length == 0 #STDERR.puts "GOT #{md[1].inspect}" unless md[0].length == 0

View file

@ -16,6 +16,10 @@ module REXML
@tag_stack = [] @tag_stack = []
@entities = {} @entities = {}
end end
def source
@parser.source
end
def add_listener( listener ) def add_listener( listener )
@parser.add_listener( listener ) @parser.add_listener( listener )

View file

@ -23,7 +23,8 @@ module REXML
case event[0] case event[0]
when :end_document when :end_document
unless tag_stack.empty? unless tag_stack.empty?
raise ParseException.new("No close tag for #{tag_stack.inspect}") #raise ParseException.new("No close tag for #{tag_stack.inspect}")
raise ParseException.new("No close tag for #{@build_context.xpath}")
end end
return return
when :start_element when :start_element

View file

@ -70,7 +70,7 @@ module REXML
# ["open-hatch", "PUBLIC", "\"-//Textuality//TEXT Standard open-hatch boilerplate//EN\"", "\"http://www.textuality.com/boilerplate/OpenHatch.xml\""] # ["open-hatch", "PUBLIC", "\"-//Textuality//TEXT Standard open-hatch boilerplate//EN\"", "\"http://www.textuality.com/boilerplate/OpenHatch.xml\""]
# <!ENTITY hatch-pic SYSTEM "../grafix/OpenHatch.gif" NDATA gif> # <!ENTITY hatch-pic SYSTEM "../grafix/OpenHatch.gif" NDATA gif>
# ["hatch-pic", "SYSTEM", "\"../grafix/OpenHatch.gif\"", "\n\t\t\t\t\t\t\tNDATA gif", "gif"] # ["hatch-pic", "SYSTEM", "\"../grafix/OpenHatch.gif\"", "\n\t\t\t\t\t\t\tNDATA gif", "gif"]
def entitydecl content def entitydecl name, decl
end end
# <!NOTATION ...> # <!NOTATION ...>
def notationdecl content def notationdecl content

View file

@ -6,7 +6,7 @@ module REXML
# Generates a Source object # Generates a Source object
# @param arg Either a String, or an IO # @param arg Either a String, or an IO
# @return a Source, or nil if a bad argument was given # @return a Source, or nil if a bad argument was given
def SourceFactory::create_from arg#, slurp=true def SourceFactory::create_from(arg)
if arg.kind_of? String if arg.kind_of? String
Source.new(arg) Source.new(arg)
elsif arg.respond_to? :read and elsif arg.respond_to? :read and
@ -35,12 +35,19 @@ module REXML
# Constructor # Constructor
# @param arg must be a String, and should be a valid XML document # @param arg must be a String, and should be a valid XML document
def initialize(arg) # @param encoding if non-null, sets the encoding of the source to this
# value, overriding all encoding detection
def initialize(arg, encoding=nil)
@orig = @buffer = arg @orig = @buffer = arg
self.encoding = check_encoding( @buffer ) if encoding
self.encoding = encoding
else
self.encoding = check_encoding( @buffer )
end
@line = 0 @line = 0
end end
# Inherited from Encoding # Inherited from Encoding
# Overridden to support optimized en/decoding # Overridden to support optimized en/decoding
def encoding=(enc) def encoding=(enc)
@ -124,7 +131,7 @@ module REXML
#attr_reader :block_size #attr_reader :block_size
# block_size has been deprecated # block_size has been deprecated
def initialize(arg, block_size=500) def initialize(arg, block_size=500, encoding=nil)
@er_source = @source = arg @er_source = @source = arg
@to_utf = false @to_utf = false
# Determining the encoding is a deceptively difficult issue to resolve. # Determining the encoding is a deceptively difficult issue to resolve.
@ -134,10 +141,12 @@ module REXML
# if there is one. If there isn't one, the file MUST be UTF-8, as per # if there is one. If there isn't one, the file MUST be UTF-8, as per
# the XML spec. If there is one, we can determine the encoding from # the XML spec. If there is one, we can determine the encoding from
# it. # it.
@buffer = ""
str = @source.read( 2 ) str = @source.read( 2 )
if /\A(?:\xfe\xff|\xff\xfe)/n =~ str if encoding
self.encoding = encoding
elsif /\A(?:\xfe\xff|\xff\xfe)/n =~ str
self.encoding = check_encoding( str ) self.encoding = check_encoding( str )
@line_break = encode( '>' )
else else
@line_break = '>' @line_break = '>'
end end
@ -159,6 +168,8 @@ module REXML
str = @source.readline(@line_break) str = @source.readline(@line_break)
str = decode(str) if @to_utf and str str = decode(str) if @to_utf and str
@buffer << str @buffer << str
rescue Iconv::IllegalSequence
raise
rescue rescue
@source = nil @source = nil
end end

View file

@ -42,6 +42,7 @@ module REXML
# Use this field if you have entities defined for some text, and you don't # Use this field if you have entities defined for some text, and you don't
# want REXML to escape that text in output. # want REXML to escape that text in output.
# Text.new( "<&", false, nil, false ) #-> "&lt;&amp;" # Text.new( "<&", false, nil, false ) #-> "&lt;&amp;"
# Text.new( "&lt;&amp;", false, nil, false ) #-> "&amp;lt;&amp;amp;"
# Text.new( "<&", false, nil, true ) #-> Parse exception # Text.new( "<&", false, nil, true ) #-> Parse exception
# Text.new( "&lt;&amp;", false, nil, true ) #-> "&lt;&amp;" # Text.new( "&lt;&amp;", false, nil, true ) #-> "&lt;&amp;"
# # Assume that the entity "s" is defined to be "sean" # # Assume that the entity "s" is defined to be "sean"
@ -172,17 +173,6 @@ module REXML
end end
@unnormalized = Text::unnormalize( @string, doctype ) @unnormalized = Text::unnormalize( @string, doctype )
end end
def wrap(string, width, addnewline=false)
# Recursivly wrap string at width.
return string if string.length <= width
place = string.rindex(' ', width) # Position in string with last ' ' before cutoff
if addnewline then
return "\n" + string[0,place] + "\n" + wrap(string[place+1..-1], width)
else
return string[0,place] + "\n" + wrap(string[place+1..-1], width)
end
end
# Sets the contents of this text node. This expects the text to be # Sets the contents of this text node. This expects the text to be
# unnormalized. It returns self. # unnormalized. It returns self.
@ -198,17 +188,28 @@ module REXML
@raw = false @raw = false
end end
def indent_text(string, level=1, style="\t", indentfirstline=true) def wrap(string, width, addnewline=false)
return string if level < 0 # Recursivly wrap string at width.
new_string = '' return string if string.length <= width
string.each { |line| place = string.rindex(' ', width) # Position in string with last ' ' before cutoff
indent_string = style * level if addnewline then
new_line = (indent_string + line).sub(/[\s]+$/,'') return "\n" + string[0,place] + "\n" + wrap(string[place+1..-1], width)
new_string << new_line else
} return string[0,place] + "\n" + wrap(string[place+1..-1], width)
new_string.strip! unless indentfirstline end
return new_string
end end
def indent_text(string, level=1, style="\t", indentfirstline=true)
return string if level < 0
new_string = ''
string.each { |line|
indent_string = style * level
new_line = (indent_string + line).sub(/[\s]+$/,'')
new_string << new_line
}
new_string.strip! unless indentfirstline
return new_string
end
def write( writer, indent=-1, transitive=false, ie_hack=false ) def write( writer, indent=-1, transitive=false, ie_hack=false )
s = to_s() s = to_s()
@ -286,9 +287,10 @@ module REXML
def Text::normalize( input, doctype=nil, entity_filter=nil ) def Text::normalize( input, doctype=nil, entity_filter=nil )
copy = input copy = input
# Doing it like this rather than in a loop improves the speed # Doing it like this rather than in a loop improves the speed
#copy = copy.gsub( EREFERENCE, '&amp;' )
copy = copy.gsub( "&", "&amp;" )
if doctype if doctype
# Replace all ampersands that aren't part of an entity # Replace all ampersands that aren't part of an entity
copy = copy.gsub( EREFERENCE, '&amp;' )
doctype.entities.each_value do |entity| doctype.entities.each_value do |entity|
copy = copy.gsub( entity.value, copy = copy.gsub( entity.value,
"&#{entity.name};" ) if entity.value and "&#{entity.name};" ) if entity.value and
@ -296,7 +298,6 @@ module REXML
end end
else else
# Replace all ampersands that aren't part of an entity # Replace all ampersands that aren't part of an entity
copy = copy.gsub( EREFERENCE, '&amp;' )
DocType::DEFAULT_ENTITIES.each_value do |entity| DocType::DEFAULT_ENTITIES.each_value do |entity|
copy = copy.gsub(entity.value, "&#{entity.name};" ) copy = copy.gsub(entity.value, "&#{entity.name};" )
end end

View file

@ -160,8 +160,13 @@ module REXML
node_types = ELEMENTS node_types = ELEMENTS
return nodeset if path_stack.length == 0 || nodeset.length == 0 return nodeset if path_stack.length == 0 || nodeset.length == 0
while path_stack.length > 0 while path_stack.length > 0
#puts "#"*5
#puts "Path stack = #{path_stack.inspect}" #puts "Path stack = #{path_stack.inspect}"
#puts "Nodeset is #{nodeset.inspect}" #puts "Nodeset is #{nodeset.inspect}"
if nodeset.length == 0
path_stack.clear
return []
end
case (op = path_stack.shift) case (op = path_stack.shift)
when :document when :document
nodeset = [ nodeset[0].root_node ] nodeset = [ nodeset[0].root_node ]
@ -235,9 +240,11 @@ module REXML
name = path_stack.shift name = path_stack.shift
for element in nodeset for element in nodeset
if element.node_type == :element if element.node_type == :element
#puts element.name #puts "Element name = #{element.name}"
attr = element.attribute( name, get_namespace(element, prefix) ) #puts "get_namespace( #{element.inspect}, #{prefix} ) = #{get_namespace(element, prefix)}"
new_nodeset << attr if attr attrib = element.attribute( name, get_namespace(element, prefix) )
#puts "attrib = #{attrib.inspect}"
new_nodeset << attrib if attrib
end end
end end
when :any when :any
@ -299,8 +306,10 @@ module REXML
#puts "Adding node #{node.inspect}" if result == (index+1) #puts "Adding node #{node.inspect}" if result == (index+1)
new_nodeset << node if result == (index+1) new_nodeset << node if result == (index+1)
elsif result.instance_of? Array elsif result.instance_of? Array
#puts "Adding node #{node.inspect}" if result.size > 0 if result.size > 0 and result.inject(false) {|k,s| s or k}
new_nodeset << node if result.size > 0 #puts "Adding node #{node.inspect}" if result.size > 0
new_nodeset << node if result.size > 0
end
else else
#puts "Adding node #{node.inspect}" if result #puts "Adding node #{node.inspect}" if result
new_nodeset << node if result new_nodeset << node if result
@ -381,9 +390,25 @@ module REXML
node_types = ELEMENTS node_types = ELEMENTS
when :namespace when :namespace
new_set = [] #puts "In :namespace"
new_nodeset = []
prefix = path_stack.shift
for node in nodeset for node in nodeset
new_nodeset << node.namespace if node.node_type == :element or node.node_type == :attribute if (node.node_type == :element or node.node_type == :attribute)
if @namespaces
namespaces = @namespaces
elsif (node.node_type == :element)
namespaces = node.namespaces
else
namespaces = node.element.namesapces
end
#puts "Namespaces = #{namespaces.inspect}"
#puts "Prefix = #{prefix.inspect}"
#puts "Node.namespace = #{node.namespace}"
if (node.namespace == namespaces[prefix])
new_nodeset << node
end
end
end end
nodeset = new_nodeset nodeset = new_nodeset
@ -404,6 +429,18 @@ module REXML
#puts "RES => #{res.inspect}" #puts "RES => #{res.inspect}"
return res return res
when :and
left = expr( path_stack.shift, nodeset.dup, context )
#puts "LEFT => #{left.inspect} (#{left.class.name})"
if left == false || left.nil? || !left.inject(false) {|a,b| a | b}
return []
end
right = expr( path_stack.shift, nodeset.dup, context )
#puts "RIGHT => #{right.inspect} (#{right.class.name})"
res = equality_relational_compare( left, op, right )
#puts "RES => #{res.inspect}"
return res
when :div when :div
left = Functions::number(expr(path_stack.shift, nodeset, context)).to_f left = Functions::number(expr(path_stack.shift, nodeset, context)).to_f
right = Functions::number(expr(path_stack.shift, nodeset, context)).to_f right = Functions::number(expr(path_stack.shift, nodeset, context)).to_f
@ -477,7 +514,7 @@ module REXML
# The next two methods are BAD MOJO! # The next two methods are BAD MOJO!
# This is my achilles heel. If anybody thinks of a better # This is my achilles heel. If anybody thinks of a better
# way of doing this, be my guest. This really sucks, but # way of doing this, be my guest. This really sucks, but
# it took me three days to get it to work at all. # it is a wonder it works at all.
# ######################################################## # ########################################################
def descendant_or_self( path_stack, nodeset ) def descendant_or_self( path_stack, nodeset )