mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
7255981a4d
* Workin' in the coal mine, goin' down, down, down... r1003 | ser | 2004-06-08 22:24:08 -0400 (Tue, 08 Jun 2004) | 7 lines * Entirely rewrote the validation code; the finite state machine, while cool, didn't survive the encounter with Interleave. It was getting sort of hacky, too. The new mechanism is less elegant, but is basically still a FSM, and is more flexible without having to add hacks to extend it. Large chunks of the FSM may be reusable in other validation mechanisms. * Added interleave support r1004 | ser | 2004-06-09 07:24:17 -0400 (Wed, 09 Jun 2004) | 2 lines * Added suppert for mixed r1005 | ser | 2004-06-09 08:01:33 -0400 (Wed, 09 Jun 2004) | 3 lines * Added Kou's patch to normalize attribute values passed through the SAX2 and Stream parsers. r1006 | ser | 2004-06-09 08:12:35 -0400 (Wed, 09 Jun 2004) | 2 lines * Applied Kou's preceding-sibling patch, which fixes the order of the axe results r1009 | ser | 2004-06-20 11:02:55 -0400 (Sun, 20 Jun 2004) | 8 lines * Redesigned and rewrote the RelaxNG code. It isn't elegant, but it works. Particular problems encountered were interleave and ref. Interleave means I can't use a clean FSM design, and ref means the dirty FSM design has to be modified during validation. There's a lot of code that could be cleaned up in here. However, I'm pretty sure that this design is reasonably fast and space efficient. I'm not entirely convinced that it is correct; more tests are required. * This version adds support for defines and refs. r1011 | ser | 2004-06-20 11:20:07 -0400 (Sun, 20 Jun 2004) | 3 lines * Removed debugging output from unit test * Moved ">" in Element.inspect r1014 | ser | 2004-06-20 11:40:30 -0400 (Sun, 20 Jun 2004) | 2 lines * Minor big in missing includes for validation rules r1023 | ser | 2004-07-03 08:57:34 -0400 (Sat, 03 Jul 2004) | 2 lines * Fixed bug #34, typo in xpath_parser. r1024 | ser | 2004-07-03 10:22:08 -0400 (Sat, 03 Jul 2004) | 9 lines * Previous fix, (include? -> includes?) was incorrect. * Added another test for encoding * Started AnyName support in RelaxNG * Added Element#Attributes#to_a, so that it does something intelligent. This was needed by XPath, for '@*' * Fixed XPath so that @* works. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@6577 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
1203 lines
39 KiB
Ruby
1203 lines
39 KiB
Ruby
require "rexml/parent"
|
|
require "rexml/namespace"
|
|
require "rexml/attribute"
|
|
require "rexml/cdata"
|
|
require "rexml/xpath"
|
|
require "rexml/parseexception"
|
|
|
|
module REXML
|
|
# Represents a tagged XML element. Elements are characterized by
|
|
# having children, attributes, and names, and can themselves be
|
|
# children.
|
|
class Element < Parent
|
|
include Namespace
|
|
|
|
UNDEFINED = "UNDEFINED"; # The default name
|
|
|
|
# Mechanisms for accessing attributes and child elements of this
|
|
# element.
|
|
attr_reader :attributes, :elements
|
|
# The context holds information about the processing environment, such as
|
|
# whitespace handling.
|
|
attr_accessor :context
|
|
|
|
# Constructor
|
|
# arg::
|
|
# if not supplied, will be set to the default value.
|
|
# If a String, the name of this object will be set to the argument.
|
|
# If an Element, the object will be shallowly cloned; name,
|
|
# attributes, and namespaces will be copied. Children will +not+ be
|
|
# copied.
|
|
# If a Source, the source will be scanned and parsed for an Element,
|
|
# and all child elements will be recursively parsed as well.
|
|
# parent::
|
|
# if supplied, must be a Parent, and will be used as
|
|
# the parent of this object.
|
|
# context::
|
|
# If supplied, must be a hash containing context items. Context items
|
|
# include:
|
|
# * <tt>:respect_whitespace</tt> the value of this is :+all+ or an array of
|
|
# strings being the names of the elements to respect
|
|
# whitespace for. Defaults to :+all+.
|
|
# * <tt>:compress_whitespace</tt> the value can be :+all+ or an array of
|
|
# strings being the names of the elements to ignore whitespace on.
|
|
# Overrides :+respect_whitespace+.
|
|
# * <tt>:ignore_whitespace_nodes</tt> the value can be :+all+ or an array
|
|
# of strings being the names of the elements in which to ignore
|
|
# whitespace-only nodes. If this is set, Text nodes which contain only
|
|
# whitespace will not be added to the document tree.
|
|
# * <tt>:raw</tt> can be :+all+, or an array of strings being the names of
|
|
# the elements to process in raw mode. In raw mode, special
|
|
# characters in text is not converted to or from entities.
|
|
def initialize( arg = UNDEFINED, parent=nil, context=nil )
|
|
super(parent)
|
|
|
|
@elements = Elements.new(self)
|
|
@attributes = Attributes.new(self)
|
|
@context = context
|
|
|
|
if arg.kind_of? String
|
|
self.name = arg
|
|
elsif arg.kind_of? Element
|
|
self.name = arg.expanded_name
|
|
arg.attributes.each_attribute{ |attribute|
|
|
@attributes << Attribute.new( attribute )
|
|
}
|
|
@context = arg.context
|
|
end
|
|
end
|
|
|
|
def inspect
|
|
rv = "<#@expanded_name"
|
|
|
|
@attributes.each_attribute do |attr|
|
|
rv << " "
|
|
attr.write( rv, 0 )
|
|
end
|
|
|
|
if children.size > 0
|
|
rv << "> ... </>"
|
|
else
|
|
rv << "/>"
|
|
end
|
|
end
|
|
|
|
|
|
# Creates a shallow copy of self.
|
|
# d = Document.new "<a><b/><b/><c><d/></c></a>"
|
|
# new_a = d.root.clone
|
|
# puts new_a # => "<a/>"
|
|
def clone
|
|
Element.new self
|
|
end
|
|
|
|
# Evaluates to the root element of the document that this element
|
|
# belongs to. If this element doesn't belong to a document, but does
|
|
# belong to another Element, the parent's root will be returned, until the
|
|
# earliest ancestor is found.
|
|
# d = Document.new '<a><b><c/></b></a>'
|
|
# a = d[1] ; c = a[1][1]
|
|
# d.root # These all evaluate to the same Element,
|
|
# a.root # namely, <a>
|
|
# c.root #
|
|
def root
|
|
parent.nil? ? self : parent.root
|
|
end
|
|
|
|
# Evaluates to the document to which this element belongs, or nil if this
|
|
# element doesn't belong to a document.
|
|
def document
|
|
root.parent if root
|
|
end
|
|
|
|
# Evaluates to +true+ if whitespace is respected for this element. This
|
|
# is the case if:
|
|
# 1. Neither :+respect_whitespace+ nor :+compress_whitespace+ has any value
|
|
# 2. The context has :+respect_whitespace+ set to :+all+ or
|
|
# an array containing the name of this element, and
|
|
# :+compress_whitespace+ isn't set to :+all+ or an array containing the
|
|
# name of this element.
|
|
# The evaluation is tested against +expanded_name+, and so is namespace
|
|
# sensitive.
|
|
def whitespace
|
|
@whitespace = nil
|
|
if @context
|
|
if @context[:respect_whitespace]
|
|
@whitespace = (@context[:respect_whitespace] == :all or
|
|
@context[:respect_whitespace].include? expanded_name)
|
|
end
|
|
@whitespace = false if (@context[:compress_whitespace] and
|
|
(@context[:compress_whitespace] == :all or
|
|
@context[:compress_whitespace].include? expanded_name)
|
|
)
|
|
end
|
|
@whitespace = true unless @whitespace == false
|
|
@whitespace
|
|
end
|
|
|
|
def ignore_whitespace_nodes
|
|
@ignore_whitespace_nodes = false
|
|
if @context
|
|
if @context[:ignore_whitespace_nodes]
|
|
@ignore_whitespace_nodes =
|
|
(@context[:ignore_whitespace_nodes] == :all or
|
|
@context[:ignore_whitespace_nodes].include? expanded_name)
|
|
end
|
|
end
|
|
end
|
|
|
|
# Evaluates to +true+ if raw mode is set for this element. This
|
|
# is the case if the context has :+raw+ set to :+all+ or
|
|
# an array containing the name of this element.
|
|
#
|
|
# The evaluation is tested against +expanded_name+, and so is namespace
|
|
# sensitive.
|
|
def raw
|
|
@raw = (@context and @context[:raw] and
|
|
(@context[:raw] == :all or
|
|
@context[:raw].include? expanded_name))
|
|
@raw
|
|
end
|
|
|
|
#once :whitespace, :raw, :ignore_whitespace_nodes
|
|
|
|
#################################################
|
|
# Namespaces #
|
|
#################################################
|
|
|
|
# Evaluates to an +Array+ containing the prefixes (names) of all defined
|
|
# namespaces at this context node.
|
|
# doc = Document.new("<a xmlns:x='1' xmlns:y='2'><b/><c xmlns:z='3'/></a>")
|
|
# doc.elements['//b'].prefixes # -> ['x', 'y']
|
|
def prefixes
|
|
prefixes = []
|
|
prefixes = parent.prefixes if parent
|
|
prefixes |= attributes.prefixes
|
|
return prefixes
|
|
end
|
|
|
|
def namespaces
|
|
namespaces = []
|
|
namespaces = parent.namespaces if parent
|
|
namespaces |= attributes.namespaces
|
|
return namespaces
|
|
end
|
|
|
|
# Evalutas to the URI for a prefix, or the empty string if no such
|
|
# namespace is declared for this element. Evaluates recursively for
|
|
# ancestors. Returns the default namespace, if there is one.
|
|
# prefix::
|
|
# the prefix to search for. If not supplied, returns the default
|
|
# namespace if one exists
|
|
# Returns::
|
|
# the namespace URI as a String, or nil if no such namespace
|
|
# exists. If the namespace is undefined, returns an empty string
|
|
# doc = Document.new("<a xmlns='1' xmlns:y='2'><b/><c xmlns:z='3'/></a>")
|
|
# b = doc.elements['//b']
|
|
# b.namespace # -> '1'
|
|
# b.namespace("y") # -> '2'
|
|
def namespace(prefix=nil)
|
|
if prefix.nil?
|
|
prefix = prefix()
|
|
end
|
|
if prefix == ''
|
|
prefix = "xmlns"
|
|
else
|
|
prefix = "xmlns:#{prefix}" unless prefix[0,5] == 'xmlns'
|
|
end
|
|
ns = attributes[ prefix ]
|
|
ns = parent.namespace(prefix) if ns.nil? and parent
|
|
ns = '' if ns.nil? and prefix == 'xmlns'
|
|
return ns
|
|
end
|
|
|
|
# Adds a namespace to this element.
|
|
# prefix::
|
|
# the prefix string, or the namespace URI if +uri+ is not
|
|
# supplied
|
|
# uri::
|
|
# the namespace URI. May be nil, in which +prefix+ is used as
|
|
# the URI
|
|
# Evaluates to: this Element
|
|
# a = Element.new("a")
|
|
# a.add_namespace("xmlns:foo", "bar" )
|
|
# a.add_namespace("foo", "bar") # shorthand for previous line
|
|
# a.add_namespace("twiddle")
|
|
# puts a #-> <a xmlns:foo='bar' xmlns='twiddle'/>
|
|
def add_namespace( prefix, uri=nil )
|
|
unless uri
|
|
@attributes["xmlns"] = prefix
|
|
else
|
|
prefix = "xmlns:#{prefix}" unless prefix =~ /^xmlns:/
|
|
@attributes[ prefix ] = uri
|
|
end
|
|
self
|
|
end
|
|
|
|
# Removes a namespace from this node. This only works if the namespace is
|
|
# actually declared in this node. If no argument is passed, deletes the
|
|
# default namespace.
|
|
#
|
|
# Evaluates to: this element
|
|
# doc = Document.new "<a xmlns:foo='bar' xmlns='twiddle'/>"
|
|
# doc.root.delete_namespace
|
|
# puts doc # -> <a xmlns:foo='bar'/>
|
|
# doc.root.delete_namespace 'foo'
|
|
# puts doc # -> <a/>
|
|
def delete_namespace namespace="xmlns"
|
|
namespace = "xmlns:#{namespace}" unless namespace == 'xmlns'
|
|
attribute = attributes.get_attribute(namespace)
|
|
attribute.remove unless attribute.nil?
|
|
self
|
|
end
|
|
|
|
#################################################
|
|
# Elements #
|
|
#################################################
|
|
|
|
# Adds a child to this element, optionally setting attributes in
|
|
# the element.
|
|
# element::
|
|
# optional. If Element, the element is added.
|
|
# Otherwise, a new Element is constructed with the argument (see
|
|
# Element.initialize).
|
|
# attrs::
|
|
# If supplied, must be a Hash containing String name,value
|
|
# pairs, which will be used to set the attributes of the new Element.
|
|
# Returns:: the Element that was added
|
|
# el = doc.add_element 'my-tag'
|
|
# el = doc.add_element 'my-tag', {'attr1'=>'val1', 'attr2'=>'val2'}
|
|
# el = Element.new 'my-tag'
|
|
# doc.add_element el
|
|
def add_element element=nil, attrs=nil
|
|
el = @elements.add(element)
|
|
if attrs.kind_of? Hash
|
|
attrs.each do |key, value|
|
|
el.attributes[key]=value if key =~ /^xmlns:/
|
|
end
|
|
attrs.each do |key, value|
|
|
el.attributes[key]=value if key !~ /^xmlns:/
|
|
end
|
|
end
|
|
el
|
|
end
|
|
|
|
# Deletes a child element.
|
|
# element::
|
|
# Must be an +Element+, +String+, or +Integer+. If Element,
|
|
# the element is removed. If String, the element is found (via XPath)
|
|
# and removed. <em>This means that any parent can remove any
|
|
# descendant.<em> If Integer, the Element indexed by that number will be
|
|
# removed.
|
|
# Returns:: the element that was removed.
|
|
# doc.delete_element "/a/b/c[@id='4']"
|
|
# doc.delete_element doc.elements["//k"]
|
|
# doc.delete_element 1
|
|
def delete_element element
|
|
@elements.delete element
|
|
end
|
|
|
|
# Evaluates to +true+ if this element has at least one child Element
|
|
# doc = Document.new "<a><b/><c>Text</c></a>"
|
|
# doc.root.has_elements # -> true
|
|
# doc.elements["/a/b"].has_elements # -> false
|
|
# doc.elements["/a/c"].has_elements # -> false
|
|
def has_elements?
|
|
!@elements.empty?
|
|
end
|
|
|
|
# Iterates through the child elements, yielding for each Element that
|
|
# has a particular attribute set.
|
|
# key::
|
|
# the name of the attribute to search for
|
|
# value::
|
|
# the value of the attribute
|
|
# max::
|
|
# (optional) causes this method to return after yielding
|
|
# for this number of matching children
|
|
# name::
|
|
# (optional) if supplied, this is an XPath that filters
|
|
# the children to check.
|
|
#
|
|
# doc = Document.new "<a><b @id='1'/><c @id='2'/><d @id='1'/><e/></a>"
|
|
# # Yields b, c, d
|
|
# doc.root.each_element_with_attribute( 'id' ) {|e| p e}
|
|
# # Yields b, d
|
|
# doc.root.each_element_with_attribute( 'id', '1' ) {|e| p e}
|
|
# # Yields b
|
|
# doc.root.each_element_with_attribute( 'id', '1', 1 ) {|e| p e}
|
|
# # Yields d
|
|
# doc.root.each_element_with_attribute( 'id', '1', 0, 'd' ) {|e| p e}
|
|
def each_element_with_attribute( key, value=nil, max=0, name=nil, &block ) # :yields: Element
|
|
each_with_something( proc {|child|
|
|
if value.nil?
|
|
child.attributes[key] != nil
|
|
else
|
|
child.attributes[key]==value
|
|
end
|
|
}, max, name, &block )
|
|
end
|
|
|
|
# Iterates through the children, yielding for each Element that
|
|
# has a particular text set.
|
|
# text::
|
|
# the text to search for. If nil, or not supplied, will itterate
|
|
# over all +Element+ children that contain at least one +Text+ node.
|
|
# max::
|
|
# (optional) causes this method to return after yielding
|
|
# for this number of matching children
|
|
# name::
|
|
# (optional) if supplied, this is an XPath that filters
|
|
# the children to check.
|
|
#
|
|
# doc = Document.new '<a><b>b</b><c>b</c><d>d</d><e/></a>'
|
|
# # Yields b, c, d
|
|
# doc.each_element_with_text {|e|p e}
|
|
# # Yields b, c
|
|
# doc.each_element_with_text('b'){|e|p e}
|
|
# # Yields b
|
|
# doc.each_element_with_text('b', 1){|e|p e}
|
|
# # Yields d
|
|
# doc.each_element_with_text(nil, 0, 'd'){|e|p e}
|
|
def each_element_with_text( text=nil, max=0, name=nil, &block ) # :yields: Element
|
|
each_with_something( proc {|child|
|
|
if text.nil?
|
|
child.has_text?
|
|
else
|
|
child.text == text
|
|
end
|
|
}, max, name, &block )
|
|
end
|
|
|
|
# Synonym for Element.elements.each
|
|
def each_element( xpath=nil, &block ) # :yields: Element
|
|
@elements.each( xpath, &block )
|
|
end
|
|
|
|
# Synonym for Element.to_a
|
|
# This is a little slower than calling elements.each directly.
|
|
# xpath:: any XPath by which to search for elements in the tree
|
|
# Returns:: an array of Elements that match the supplied path
|
|
def get_elements( xpath )
|
|
@elements.to_a( xpath )
|
|
end
|
|
|
|
# Returns the next sibling that is an element, or nil if there is
|
|
# no Element sibling after this one
|
|
# doc = Document.new '<a><b/>text<c/></a>'
|
|
# doc.root.elements['b'].next_element #-> <c/>
|
|
# doc.root.elements['c'].next_element #-> nil
|
|
def next_element
|
|
element = next_sibling
|
|
element = element.next_sibling until element.nil? or element.kind_of? Element
|
|
return element
|
|
end
|
|
|
|
# Returns the previous sibling that is an element, or nil if there is
|
|
# no Element sibling prior to this one
|
|
# doc = Document.new '<a><b/>text<c/></a>'
|
|
# doc.root.elements['c'].previous_element #-> <b/>
|
|
# doc.root.elements['b'].previous_element #-> nil
|
|
def previous_element
|
|
element = previous_sibling
|
|
element = element.previous_sibling until element.nil? or element.kind_of? Element
|
|
return element
|
|
end
|
|
|
|
|
|
#################################################
|
|
# Text #
|
|
#################################################
|
|
|
|
# Evaluates to +true+ if this element has at least one Text child
|
|
def has_text?
|
|
not text().nil?
|
|
end
|
|
|
|
# A convenience method which returns the String value of the _first_
|
|
# child text element, if one exists, and +nil+ otherwise.
|
|
#
|
|
# <em>Note that an element may have multiple Text elements, perhaps
|
|
# separated by other children</em>. Be aware that this method only returns
|
|
# the first Text node.
|
|
#
|
|
# This method returns the +value+ of the first text child node, which
|
|
# ignores the +raw+ setting, so always returns normalized text. See
|
|
# the Text::value documentation.
|
|
#
|
|
# doc = Document.new "<p>some text <b>this is bold!</b> more text</p>"
|
|
# # The element 'p' has two text elements, "some text " and " more text".
|
|
# doc.root.text #-> "some text "
|
|
def text( path = nil )
|
|
rv = get_text(path)
|
|
return rv.value unless rv.nil?
|
|
nil
|
|
end
|
|
|
|
# Returns the first child Text node, if any, or +nil+ otherwise.
|
|
# This method returns the actual +Text+ node, rather than the String content.
|
|
# doc = Document.new "<p>some text <b>this is bold!</b> more text</p>"
|
|
# # The element 'p' has two text elements, "some text " and " more text".
|
|
# doc.root.get_text.value #-> "some text "
|
|
def get_text path = nil
|
|
rv = nil
|
|
if path
|
|
element = @elements[ path ]
|
|
rv = element.get_text unless element.nil?
|
|
else
|
|
rv = @children.find { |node| node.kind_of? Text }
|
|
end
|
|
return rv
|
|
end
|
|
|
|
# Sets the first Text child of this object. See text() for a
|
|
# discussion about Text children.
|
|
#
|
|
# If a Text child already exists, the child is replaced by this
|
|
# content. This means that Text content can be deleted by calling
|
|
# this method with a nil argument. In this case, the next Text
|
|
# child becomes the first Text child. In no case is the order of
|
|
# any siblings disturbed.
|
|
# text::
|
|
# If a String, a new Text child is created and added to
|
|
# this Element as the first Text child. If Text, the text is set
|
|
# as the first Child element. If nil, then any existing first Text
|
|
# child is removed.
|
|
# Returns:: this Element.
|
|
# doc = Document.new '<a><b/></a>'
|
|
# doc.root.text = 'Sean' #-> '<a><b/>Sean</a>'
|
|
# doc.root.text = 'Elliott' #-> '<a><b/>Elliott</a>'
|
|
# doc.root.add_element 'c' #-> '<a><b/>Elliott<c/></a>'
|
|
# doc.root.text = 'Russell' #-> '<a><b/>Russell<c/></a>'
|
|
# doc.root.text = nil #-> '<a><b/><c/></a>'
|
|
def text=( text )
|
|
if text.kind_of? String
|
|
text = Text.new( text, whitespace(), nil, raw() )
|
|
elsif text and !text.kind_of? Text
|
|
text = Text.new( text.to_s, whitespace(), nil, raw() )
|
|
end
|
|
|
|
old_text = get_text
|
|
if text.nil?
|
|
old_text.remove unless old_text.nil?
|
|
else
|
|
if old_text.nil?
|
|
self << text
|
|
else
|
|
old_text.replace_with( text )
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
# A helper method to add a Text child. Actual Text instances can
|
|
# be added with regular Parent methods, such as add() and <<()
|
|
# text::
|
|
# if a String, a new Text instance is created and added
|
|
# to the parent. If Text, the object is added directly.
|
|
# Returns:: this Element
|
|
# e = Element.new('a') #-> <e/>
|
|
# e.add_text 'foo' #-> <e>foo</e>
|
|
# e.add_text Text.new(' bar') #-> <e>foo bar</e>
|
|
# Note that at the end of this example, the branch has <b>3</b> nodes; the 'e'
|
|
# element and <b>2</b> Text node children.
|
|
def add_text( text )
|
|
if text.kind_of? String
|
|
if @children[-1].kind_of? Text
|
|
@children[-1] << text
|
|
return
|
|
end
|
|
text = Text.new( text, whitespace(), nil, raw() )
|
|
end
|
|
self << text unless text.nil?
|
|
return self
|
|
end
|
|
|
|
def node_type
|
|
:element
|
|
end
|
|
|
|
def xpath
|
|
path_elements = []
|
|
cur = self
|
|
path_elements << __to_xpath_helper( self )
|
|
while cur.parent
|
|
cur = cur.parent
|
|
path_elements << __to_xpath_helper( cur )
|
|
end
|
|
return path_elements.reverse.join( "/" )
|
|
end
|
|
|
|
#################################################
|
|
# Attributes #
|
|
#################################################
|
|
|
|
def attribute( name, namespace=nil )
|
|
prefix = ''
|
|
if namespace
|
|
prefix = attributes.prefixes.each { |prefix|
|
|
return "#{prefix}:" if namespace( prefix ) == namespace
|
|
} || ''
|
|
end
|
|
attributes.get_attribute( "#{prefix}#{name}" )
|
|
end
|
|
|
|
# Evaluates to +true+ if this element has any attributes set, false
|
|
# otherwise.
|
|
def has_attributes?
|
|
return !@attributes.empty?
|
|
end
|
|
|
|
# Adds an attribute to this element, overwriting any existing attribute
|
|
# by the same name.
|
|
# key::
|
|
# can be either an Attribute or a String. If an Attribute,
|
|
# the attribute is added to the list of Element attributes. If String,
|
|
# the argument is used as the name of the new attribute, and the value
|
|
# parameter must be supplied.
|
|
# value::
|
|
# Required if +key+ is a String, and ignored if the first argument is
|
|
# an Attribute. This is a String, and is used as the value
|
|
# of the new Attribute.
|
|
# Returns:: the Attribute added
|
|
# e = Element.new 'e'
|
|
# e.add_attribute( 'a', 'b' ) #-> <e a='b'/>
|
|
# e.add_attribute( 'x:a', 'c' ) #-> <e a='b' x:a='c'/>
|
|
# e.add_attribute Attribute.new('b', 'd') #-> <e a='b' x:a='c' b='d'/>
|
|
def add_attribute( key, value=nil )
|
|
if key.kind_of? Attribute
|
|
@attributes << key
|
|
else
|
|
@attributes[key] = value
|
|
end
|
|
end
|
|
|
|
# Add multiple attributes to this element.
|
|
# hash:: is either a hash, or array of arrays
|
|
# el.add_attributes( {"name1"=>"value1", "name2"=>"value2"} )
|
|
# el.add_attributes( [ ["name1","value1"], ["name2"=>"value2"] ] )
|
|
def add_attributes hash
|
|
if hash.kind_of? Hash
|
|
hash.each_pair {|key, value| @attributes[key] = value }
|
|
elsif hash.kind_of? Array
|
|
hash.each { |value| @attributes[ value[0] ] = value[1] }
|
|
end
|
|
end
|
|
|
|
# Removes an attribute
|
|
# key::
|
|
# either an Attribute or a String. In either case, the
|
|
# attribute is found by matching the attribute name to the argument,
|
|
# and then removed. If no attribute is found, no action is taken.
|
|
# Returns::
|
|
# the attribute removed, or nil if this Element did not contain
|
|
# a matching attribute
|
|
# e = Element.new('E')
|
|
# e.add_attribute( 'name', 'Sean' ) #-> <E name='Sean'/>
|
|
# r = e.add_attribute( 'sur:name', 'Russell' ) #-> <E name='Sean' sur:name='Russell'/>
|
|
# e.delete_attribute( 'name' ) #-> <E sur:name='Russell'/>
|
|
# e.delete_attribute( r ) #-> <E/>
|
|
def delete_attribute(key)
|
|
attr = @attributes.get_attribute(key)
|
|
attr.remove unless attr.nil?
|
|
end
|
|
|
|
#################################################
|
|
# Other Utilities #
|
|
#################################################
|
|
|
|
# Get an array of all CData children.
|
|
# IMMUTABLE
|
|
def cdatas
|
|
find_all { |child| child.kind_of? CData }.freeze
|
|
end
|
|
|
|
# Get an array of all Comment children.
|
|
# IMMUTABLE
|
|
def comments
|
|
find_all { |child| child.kind_of? Comment }.freeze
|
|
end
|
|
|
|
# Get an array of all Instruction children.
|
|
# IMMUTABLE
|
|
def instructions
|
|
find_all { |child| child.kind_of? Instruction }.freeze
|
|
end
|
|
|
|
# Get an array of all Text children.
|
|
# IMMUTABLE
|
|
def texts
|
|
find_all { |child| child.kind_of? Text }.freeze
|
|
end
|
|
|
|
# Writes out this element, and recursively, all children.
|
|
# output::
|
|
# output an object which supports '<< string'; this is where the
|
|
# document will be written.
|
|
# indent::
|
|
# An integer. If -1, no indenting will be used; otherwise, the
|
|
# indentation will be this number of spaces, and children will be
|
|
# indented an additional amount. Defaults to -1
|
|
# transitive::
|
|
# 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
|
|
# the parse tree of the document
|
|
# ie_hack::
|
|
# Internet Explorer is the worst piece of crap to have ever been
|
|
# written, with the possible exception of Windows itself. Since IE is
|
|
# unable to parse proper XML, we have to provide a hack to generate XML
|
|
# that IE's limited abilities can handle. This hack inserts a space
|
|
# before the /> on empty tags. Defaults to false
|
|
#
|
|
# out = ''
|
|
# doc.write( out ) #-> doc is written to the string 'out'
|
|
# doc.write( $stdout ) #-> doc written to the console
|
|
def write(writer=$stdout, indent=-1, transitive=false, ie_hack=false)
|
|
#print "ID:#{indent}"
|
|
writer << "<#@expanded_name"
|
|
|
|
@attributes.each_attribute do |attr|
|
|
writer << " "
|
|
attr.write( writer, indent )
|
|
end unless @attributes.empty?
|
|
|
|
if @children.empty?
|
|
if transitive and indent>-1
|
|
writer << "\n"
|
|
indent( writer, indent )
|
|
elsif ie_hack
|
|
writer << " "
|
|
end
|
|
writer << "/"
|
|
else
|
|
if transitive and indent>-1 and !@children[0].instance_of? Text
|
|
writer << "\n"
|
|
indent writer, indent+1
|
|
end
|
|
writer << ">"
|
|
write_children( writer, indent, transitive, ie_hack )
|
|
writer << "</#{expanded_name}"
|
|
end
|
|
if transitive and indent>-1 and !@children.empty?
|
|
writer << "\n"
|
|
indent -= 1 if next_sibling.nil?
|
|
indent(writer, indent)
|
|
end
|
|
writer << ">"
|
|
end
|
|
|
|
|
|
private
|
|
def __to_xpath_helper node
|
|
rv = node.expanded_name
|
|
if node.parent
|
|
results = node.parent.find_all {|n|
|
|
n.kind_of?(REXML::Element) and n.expanded_name == node.expanded_name
|
|
}
|
|
if results.length > 1
|
|
idx = results.index( node )
|
|
rv << "[#{idx+1}]"
|
|
end
|
|
end
|
|
rv
|
|
end
|
|
|
|
# A private helper method
|
|
def each_with_something( test, max=0, name=nil )
|
|
num = 0
|
|
child=nil
|
|
@elements.each( name ){ |child|
|
|
yield child if test.call(child) and num += 1
|
|
return if max>0 and num == max
|
|
}
|
|
end
|
|
|
|
# A private helper method
|
|
def write_children( writer, indent, transitive, ie_hack )
|
|
cr = (indent < 0) ? '' : "\n"
|
|
if indent == -1
|
|
each { |child| child.write( writer, indent, transitive, ie_hack ) }
|
|
else
|
|
next_indent = indent+1
|
|
last_child=nil
|
|
each { |child|
|
|
unless child.kind_of? Text or last_child.kind_of? Text or transitive
|
|
writer << cr
|
|
indent(writer, next_indent)
|
|
end
|
|
child.write( writer, next_indent, transitive, ie_hack )
|
|
last_child = child
|
|
}
|
|
unless last_child.kind_of? Text or transitive
|
|
writer << cr
|
|
indent( writer, indent )
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
########################################################################
|
|
# ELEMENTS #
|
|
########################################################################
|
|
|
|
# A class which provides filtering of children for Elements, and
|
|
# XPath search support. You are expected to only encounter this class as
|
|
# the <tt>element.elements</tt> object. Therefore, you are
|
|
# _not_ expected to instantiate this yourself.
|
|
class Elements
|
|
include Enumerable
|
|
# Constructor
|
|
# parent:: the parent Element
|
|
def initialize parent
|
|
@element = parent
|
|
end
|
|
|
|
# Fetches a child element. Filters only Element children, regardless of
|
|
# the XPath match.
|
|
# index::
|
|
# the search parameter. This is either an Integer, which
|
|
# will be used to find the index'th child Element, or an XPath,
|
|
# which will be used to search for the Element. <em>Because
|
|
# of the nature of XPath searches, any element in the connected XML
|
|
# document can be fetched through any other element.</em> <b>The
|
|
# Integer index is 1-based, not 0-based.</b> This means that the first
|
|
# child element is at index 1, not 0, and the +n+th element is at index
|
|
# +n+, not <tt>n-1</tt>. This is because XPath indexes element children
|
|
# starting from 1, not 0, and the indexes should be the same.
|
|
# name::
|
|
# optional, and only used in the first argument is an
|
|
# Integer. In that case, the index'th child Element that has the
|
|
# supplied name will be returned. Note again that the indexes start at 1.
|
|
# Returns:: the first matching Element, or nil if no child matched
|
|
# doc = Document.new '<a><b/><c id="1"/><c id="2"/><d/></a>'
|
|
# doc.root.elements[1] #-> <b/>
|
|
# doc.root.elements['c'] #-> <c id="1"/>
|
|
# doc.root.elements[2,'c'] #-> <c id="2"/>
|
|
def []( index, name=nil)
|
|
if index.kind_of? Integer
|
|
raise "index (#{index}) must be >= 1" if index < 1
|
|
name = literalize(name) if name
|
|
num = 0
|
|
child = nil
|
|
@element.find { |child|
|
|
child.kind_of? Element and
|
|
(name.nil? ? true : child.has_name?( name )) and
|
|
(num += 1) == index
|
|
}
|
|
else
|
|
return XPath::first( @element, index )
|
|
#{ |element|
|
|
# return element if element.kind_of? Element
|
|
#}
|
|
#return nil
|
|
end
|
|
end
|
|
|
|
# Sets an element, replacing any previous matching element. If no
|
|
# existing element is found ,the element is added.
|
|
# index:: Used to find a matching element to replace. See []().
|
|
# element::
|
|
# The element to replace the existing element with
|
|
# the previous element
|
|
# Returns:: nil if no previous element was found.
|
|
#
|
|
# doc = Document.new '<a/>'
|
|
# doc.root.elements[10] = Element.new('b') #-> <a><b/></a>
|
|
# doc.root.elements[1] #-> <b/>
|
|
# doc.root.elements[1] = Element.new('c') #-> <a><c/></a>
|
|
# doc.root.elements['c'] = Element.new('d') #-> <a><d/></a>
|
|
def []=( index, element )
|
|
previous = self[index]
|
|
if previous.nil?
|
|
@element.add element
|
|
else
|
|
previous.replace_with element
|
|
end
|
|
return previous
|
|
end
|
|
|
|
# Returns +true+ if there are no +Element+ children, +false+ otherwise
|
|
def empty?
|
|
@element.find{ |child| child.kind_of? Element}.nil?
|
|
end
|
|
|
|
# Returns the index of the supplied child (starting at 1), or -1 if
|
|
# the element is not a child
|
|
# element:: an +Element+ child
|
|
def index element
|
|
rv = 0
|
|
found = @element.find do |child|
|
|
child.kind_of? Element and
|
|
(rv += 1) and
|
|
child == element
|
|
end
|
|
return rv if found == element
|
|
return -1
|
|
end
|
|
|
|
# Deletes a child Element
|
|
# element::
|
|
# Either an Element, which is removed directly; an
|
|
# xpath, where the first matching child is removed; or an Integer,
|
|
# where the n'th Element is removed.
|
|
# Returns:: the removed child
|
|
# doc = Document.new '<a><b/><c/><c id="1"/></a>'
|
|
# b = doc.root.elements[1]
|
|
# doc.root.elements.delete b #-> <a><c/><c id="1"/></a>
|
|
# doc.elements.delete("a/c[@id='1']") #-> <a><c/></a>
|
|
# doc.root.elements.delete 1 #-> <a/>
|
|
def delete element
|
|
if element.kind_of? Element
|
|
@element.delete element
|
|
else
|
|
el = self[element]
|
|
el.remove if el
|
|
end
|
|
end
|
|
|
|
# Removes multiple elements. Filters for Element children, regardless of
|
|
# XPath matching.
|
|
# xpath:: all elements matching this String path are removed.
|
|
# Returns:: an Array of Elements that have been removed
|
|
# doc = Document.new '<a><c/><c/><c/><c/></a>'
|
|
# deleted = doc.elements.delete_all 'a/c' #-> [<c/>, <c/>, <c/>, <c/>]
|
|
def delete_all( xpath )
|
|
rv = []
|
|
XPath::each( @element, xpath) {|element|
|
|
rv << element if element.kind_of? Element
|
|
}
|
|
rv.each do |element|
|
|
@element.delete element
|
|
element.remove
|
|
end
|
|
return rv
|
|
end
|
|
|
|
# Adds an element
|
|
# element::
|
|
# if supplied, is either an Element, String, or
|
|
# Source (see Element.initialize). If not supplied or nil, a
|
|
# new, default Element will be constructed
|
|
# Returns:: the added Element
|
|
# a = Element.new 'a'
|
|
# a.elements.add Element.new 'b' #-> <a><b/></a>
|
|
# a.elements.add 'c' #-> <a><b/><c/></a>
|
|
def add element=nil
|
|
rv = nil
|
|
if element.nil?
|
|
Element.new "", self, @element.context
|
|
elsif not element.kind_of?(Element)
|
|
Element.new element, self, @element.context
|
|
else
|
|
@element << element
|
|
element.context = @element.context
|
|
element
|
|
end
|
|
end
|
|
|
|
alias :<< :add
|
|
|
|
# Iterates through all of the child Elements, optionally filtering
|
|
# them by a given XPath
|
|
# xpath::
|
|
# optional. If supplied, this is a String XPath, and is used to
|
|
# filter the children, so that only matching children are yielded. Note
|
|
# that XPaths are automatically filtered for Elements, so that
|
|
# non-Element children will not be yielded
|
|
# doc = Document.new '<a><b/><c/><d/>sean<b/><c/><d/></a>'
|
|
# doc.root.each {|e|p e} #-> Yields b, c, d, b, c, d elements
|
|
# doc.root.each('b') {|e|p e} #-> Yields b, b elements
|
|
# doc.root.each('child::node()') {|e|p e}
|
|
# #-> Yields <b/>, <c/>, <d/>, <b/>, <c/>, <d/>
|
|
# XPath.each(doc.root, 'child::node()', &block)
|
|
# #-> Yields <b/>, <c/>, <d/>, sean, <b/>, <c/>, <d/>
|
|
def each( xpath=nil, &block)
|
|
XPath::each( @element, xpath ) {|e| yield e if e.kind_of? Element }
|
|
end
|
|
|
|
# Returns the number of +Element+ children of the parent object.
|
|
# doc = Document.new '<a>sean<b/>elliott<b/>russell<b/></a>'
|
|
# doc.root.size #-> 6, 3 element and 3 text nodes
|
|
# doc.root.elements.size #-> 3
|
|
def size
|
|
count = 0
|
|
@element.each {|child| count+=1 if child.kind_of? Element }
|
|
count
|
|
end
|
|
|
|
# Returns an Array of Element children. An XPath may be supplied to
|
|
# filter the children. Only Element children are returned, even if the
|
|
# supplied XPath matches non-Element children.
|
|
# doc = Document.new '<a>sean<b/>elliott<c/></a>'
|
|
# doc.root.elements.to_a #-> [ <b/>, <c/> ]
|
|
# doc.root.elements.to_a("child::node()") #-> [ <b/>, <c/> ]
|
|
# XPath.match(doc.root, "child::node()") #-> [ sean, <b/>, elliott, <c/> ]
|
|
def to_a( xpath=nil )
|
|
rv = XPath.match( @element, xpath )
|
|
return rv.find_all{|e| e.kind_of? Element} if xpath
|
|
rv
|
|
end
|
|
|
|
private
|
|
# Private helper class. Removes quotes from quoted strings
|
|
def literalize name
|
|
name = name[1..-2] if name[0] == ?' or name[0] == ?" #'
|
|
name
|
|
end
|
|
end
|
|
|
|
########################################################################
|
|
# ATTRIBUTES #
|
|
########################################################################
|
|
|
|
# A class that defines the set of Attributes of an Element and provides
|
|
# operations for accessing elements in that set.
|
|
class Attributes < Hash
|
|
# Constructor
|
|
# element:: the Element of which this is an Attribute
|
|
def initialize element
|
|
@element = element
|
|
end
|
|
|
|
# Fetches an attribute value. If you want to get the Attribute itself,
|
|
# use get_attribute()
|
|
# name:: an XPath attribute name. Namespaces are relevant here.
|
|
# Returns::
|
|
# the String value of the matching attribute, or +nil+ if no
|
|
# matching attribute was found.
|
|
#
|
|
# doc = Document.new "<a foo:att='1' bar:att='2' att='3'/>"
|
|
# doc.root.attributes['att'] #-> '3'
|
|
# doc.root.attributes['bar:att'] #-> '2'
|
|
def [](name)
|
|
attr = get_attribute(name)
|
|
return attr.value unless attr.nil?
|
|
return nil
|
|
end
|
|
|
|
def to_a
|
|
values.flatten
|
|
end
|
|
|
|
# Returns the number of attributes the owning Element contains.
|
|
# doc = Document "<a x='1' y='2' foo:x='3'/>"
|
|
# doc.root.attributes.length #-> 3
|
|
def length
|
|
c = 0
|
|
each_attribute { c+=1 }
|
|
c
|
|
end
|
|
alias :size :length
|
|
|
|
# Itterates over the attributes of an Element. Yields actual Attribute
|
|
# nodes, not String values.
|
|
#
|
|
# doc = Document.new '<a x="1" y="2"/>'
|
|
# doc.root.attributes.each_attribute {|attr|
|
|
# p attr.expanded_name+" => "+attr.value
|
|
# }
|
|
def each_attribute # :yields: attribute
|
|
each_value do |val|
|
|
if val.kind_of? Attribute
|
|
yield val
|
|
else
|
|
val.each_value { |atr| yield atr }
|
|
end
|
|
end
|
|
end
|
|
|
|
# Itterates over each attribute of an Element, yielding the expanded name
|
|
# and value as a pair of Strings.
|
|
#
|
|
# doc = Document.new '<a x="1" y="2"/>'
|
|
# doc.root.attributes.each {|name, value| p name+" => "+value }
|
|
def each
|
|
each_attribute do |attr|
|
|
yield attr.expanded_name, attr.value
|
|
end
|
|
end
|
|
|
|
# Fetches an attribute
|
|
# name::
|
|
# the name by which to search for the attribute. Can be a
|
|
# <tt>prefix:name</tt> namespace name.
|
|
# Returns:: The first matching attribute, or nil if there was none. This
|
|
# value is an Attribute node, not the String value of the attribute.
|
|
# doc = Document.new '<a x:foo="1" foo="2" bar="3"/>'
|
|
# doc.root.attributes.get_attribute("foo").value #-> "2"
|
|
# doc.root.attributes.get_attribute("x:foo").value #-> "1"
|
|
def get_attribute( name )
|
|
attr = fetch( name, nil )
|
|
if attr.nil?
|
|
return nil if name.nil?
|
|
# Look for prefix
|
|
name =~ Namespace::NAMESPLIT
|
|
prefix, n = $1, $2
|
|
if prefix
|
|
attr = fetch( n, nil )
|
|
# check prefix
|
|
if attr == nil
|
|
elsif attr.kind_of? Attribute
|
|
return attr if prefix == attr.prefix
|
|
else
|
|
attr = attr[ prefix ]
|
|
return attr
|
|
end
|
|
end
|
|
if @element.document and @element.document.doctype
|
|
expn = @element.expanded_name
|
|
expn = @element.document.doctype.name if expn.size == 0
|
|
attr_val = @element.document.doctype.attribute_of(expn, name)
|
|
return Attribute.new( name, attr_val ) if attr_val
|
|
end
|
|
return nil
|
|
end
|
|
if attr.kind_of? Hash
|
|
attr = attr[ @element.prefix ]
|
|
end
|
|
return attr
|
|
end
|
|
|
|
# Sets an attribute, overwriting any existing attribute value by the
|
|
# same name. Namespace is significant.
|
|
# name:: the name of the attribute
|
|
# value::
|
|
# (optional) If supplied, the value of the attribute. If
|
|
# nil, any existing matching attribute is deleted.
|
|
# Returns::
|
|
# Owning element
|
|
# doc = Document.new "<a x:foo='1' foo='3'/>"
|
|
# doc.root.attributes['y:foo'] = '2'
|
|
# doc.root.attributes['foo'] = '4'
|
|
# doc.root.attributes['x:foo'] = nil
|
|
def []=( name, value )
|
|
if value.nil? # Delete the named attribute
|
|
attr = get_attribute(name)
|
|
delete attr
|
|
return
|
|
end
|
|
value = Attribute.new(name, value) unless value.kind_of? Attribute
|
|
value.element = @element
|
|
old_attr = fetch(value.name, nil)
|
|
if old_attr.nil?
|
|
store(value.name, value)
|
|
elsif old_attr.kind_of? Hash
|
|
old_attr[value.prefix] = value
|
|
elsif old_attr.prefix != value.prefix
|
|
# Check for conflicting namespaces
|
|
raise ParseException.new(
|
|
"Namespace conflict in adding attribute \"#{value.name}\": "+
|
|
"Prefix \"#{old_attr.prefix}\" = "+
|
|
"\"#{@element.namespace(old_attr.prefix)}\" and prefix "+
|
|
"\"#{value.prefix}\" = \"#{@element.namespace(value.prefix)}\"") if
|
|
value.prefix != "xmlns" and old_attr.prefix != "xmlns" and
|
|
@element.namespace( old_attr.prefix ) ==
|
|
@element.namespace( value.prefix )
|
|
store value.name, { old_attr.prefix => old_attr,
|
|
value.prefix => value }
|
|
else
|
|
store value.name, value
|
|
end
|
|
return @element
|
|
end
|
|
|
|
# Returns an array of Strings containing all of the prefixes declared
|
|
# by this set of # attributes. The array does not include the default
|
|
# namespace declaration, if one exists.
|
|
# doc = Document.new("<a xmlns='foo' xmlns:x='bar' xmlns:y='twee' "+
|
|
# "z='glorp' p:k='gru'/>")
|
|
# prefixes = doc.root.attributes.prefixes #-> ['x', 'y']
|
|
def prefixes
|
|
ns = []
|
|
each_attribute do |attribute|
|
|
ns << attribute.name if attribute.prefix == 'xmlns'
|
|
end
|
|
if @element.document and @element.document.doctype
|
|
expn = @element.expanded_name
|
|
expn = @element.document.doctype.name if expn.size == 0
|
|
@element.document.doctype.attributes_of(expn).each {
|
|
|attribute|
|
|
ns << attribute.name if attribute.prefix == 'xmlns'
|
|
}
|
|
end
|
|
ns
|
|
end
|
|
|
|
def namespaces
|
|
namespaces = []
|
|
each_attribute do |attribute|
|
|
namespaces << attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns'
|
|
end
|
|
if @element.document and @element.document.doctype
|
|
expn = @element.expanded_name
|
|
expn = @element.document.doctype.name if expn.size == 0
|
|
@element.document.doctype.attributes_of(expn).each {
|
|
|attribute|
|
|
namespaces << attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns'
|
|
}
|
|
end
|
|
namespaces
|
|
end
|
|
|
|
# Removes an attribute
|
|
# attribute::
|
|
# either a String, which is the name of the attribute to remove --
|
|
# namespaces are significant here -- or the attribute to remove.
|
|
# Returns:: the owning element
|
|
# doc = Document.new "<a y:foo='0' x:foo='1' foo='3' z:foo='4'/>"
|
|
# doc.root.attributes.delete 'foo' #-> <a y:foo='0' x:foo='1' z:foo='4'/>"
|
|
# doc.root.attributes.delete 'x:foo' #-> <a y:foo='0' z:foo='4'/>"
|
|
# attr = doc.root.attributes.get_attribute('y:foo')
|
|
# doc.root.attributes.delete attr #-> <a z:foo='4'/>"
|
|
def delete( attribute )
|
|
name = nil
|
|
prefix = nil
|
|
if attribute.kind_of? Attribute
|
|
name = attribute.name
|
|
prefix = attribute.prefix
|
|
else
|
|
attribute =~ Namespace::NAMESPLIT
|
|
prefix, name = $1, $2
|
|
prefix = '' unless prefix
|
|
end
|
|
old = fetch(name, nil)
|
|
attr = nil
|
|
if old.kind_of? Hash # the supplied attribute is one of many
|
|
attr = old.delete(prefix)
|
|
if old.size == 1
|
|
repl = nil
|
|
old.each_value{|v| repl = v}
|
|
store name, repl
|
|
end
|
|
elsif old.nil?
|
|
return @element
|
|
else # the supplied attribute is a top-level one
|
|
attr = old
|
|
res = super(name)
|
|
end
|
|
@element
|
|
end
|
|
|
|
# Adds an attribute, overriding any existing attribute by the
|
|
# same name. Namespaces are significant.
|
|
# attribute:: An Attribute
|
|
def add( attribute )
|
|
self[attribute.name] = attribute
|
|
end
|
|
|
|
alias :<< :add
|
|
|
|
# Deletes all attributes matching a name. Namespaces are significant.
|
|
# name::
|
|
# A String; all attributes that match this path will be removed
|
|
# Returns:: an Array of the Attributes that were removed
|
|
def delete_all( name )
|
|
rv = []
|
|
each_attribute { |attribute|
|
|
rv << attribute if attribute.expanded_name == name
|
|
}
|
|
rv.each{ |attr| attr.remove }
|
|
return rv
|
|
end
|
|
end
|
|
end
|