node.rb   [plain text]


require 'rexml/xmltokens'
require 'rexml/light/node'

# [ :element, parent, name, attributes, children* ]
	# a = Node.new
	# a << "B"		# => <a>B</a>
	# a.b			# => <a>B<b/></a>
	# a.b[1]			# => <a>B<b/><b/><a>
	# a.b[1]["x"] = "y"	# => <a>B<b/><b x="y"/></a>
	# a.b[0].c		# => <a>B<b><c/></b><b x="y"/></a>
	# a.b.c << "D"		# => <a>B<b><c>D</c></b><b x="y"/></a>
module REXML
	module Light
		# Represents a tagged XML element.  Elements are characterized by
		# having children, attributes, and names, and can themselves be
		# children.
		class Node
			NAMESPLIT = /^(?:(#{XMLTokens::NCNAME_STR}):)?(#{XMLTokens::NCNAME_STR})/u
			PARENTS = [ :element, :document, :doctype ]
			# Create a new element.
			def initialize node=nil
				@node = node
				if node.kind_of? String
					node = [ :text, node ]
				elsif node.nil?
					node = [ :document, nil, nil ]
				elsif node[0] == :start_element
					node[0] = :element
				elsif node[0] == :start_doctype
					node[0] = :doctype
				elsif node[0] == :start_document
					node[0] = :document
				end
			end

			def size
				if PARENTS.include? @node[0]
					@node[-1].size
				else
					0
				end
			end

			def each( &block )
				size.times { |x| yield( at(x+4) ) }
			end

			def name
				at(2)
			end

			def name=( name_str, ns=nil )
				pfx = ''
				pfx = "#{prefix(ns)}:" if ns
				_old_put(2, "#{pfx}#{name_str}")
			end

			def parent=( node )
				_old_put(1,node)
			end

			def local_name
				namesplit
				@name
			end

			def local_name=( name_str )
				_old_put( 1, "#@prefix:#{name_str}" )
			end

			def prefix( namespace=nil )
				prefix_of( self, namespace )
			end

			def namespace( prefix=prefix() )
				namespace_of( self, prefix )
			end

			def namespace=( namespace )
				@prefix = prefix( namespace )
				pfx = ''
				pfx = "#@prefix:" if @prefix.size > 0
				_old_put(1, "#{pfx}#@name")
			end

			def []( reference, ns=nil )
				if reference.kind_of? String
					pfx = ''
					pfx = "#{prefix(ns)}:" if ns
					at(3)["#{pfx}#{reference}"]
				elsif reference.kind_of? Range
					_old_get( Range.new(4+reference.begin, reference.end, reference.exclude_end?) )
				else
					_old_get( 4+reference )
				end
			end

			def =~( path )
				XPath.match( self, path )
			end

			# Doesn't handle namespaces yet
			def []=( reference, ns, value=nil )
				if reference.kind_of? String
					value = ns unless value
					at( 3 )[reference] = value
				elsif reference.kind_of? Range
					_old_put( Range.new(3+reference.begin, reference.end, reference.exclude_end?), ns )
				else
					if value
						_old_put( 4+reference, ns, value )
					else
						_old_put( 4+reference, ns )
					end
				end
			end

			# Append a child to this element, optionally under a provided namespace.
			# The namespace argument is ignored if the element argument is an Element
			# object.  Otherwise, the element argument is a string, the namespace (if
			# provided) is the namespace the element is created in.
			def << element
				if node_type() == :text
					at(-1) << element
				else
					newnode = Node.new( element )
					newnode.parent = self
					self.push( newnode )
				end
				at(-1)
			end

			def node_type
				_old_get(0)
			end

			def text=( foo )
				replace = at(4).kind_of?(String)? 1 : 0
				self._old_put(4,replace, normalizefoo)
			end

			def root
				context = self
				context = context.at(1) while context.at(1)
			end

			def has_name?( name, namespace = '' )
				at(3) == name and namespace() == namespace
			end

			def children
				self
			end

			def parent
				at(1)
			end

			def to_s

			end

			private

			def namesplit
				return if @name.defined?
				at(2) =~ NAMESPLIT
				@prefix = '' || $1
				@name = $2
			end

			def namespace_of( node, prefix=nil )
				if not prefix
					name = at(2)
					name =~ NAMESPLIT
					prefix = $1
				end
				to_find = 'xmlns'
				to_find = "xmlns:#{prefix}" if not prefix.nil?
				ns = at(3)[ to_find ]
				ns ? ns : namespace_of( @node[0], prefix )
			end

			def prefix_of( node, namespace=nil )
				if not namespace
					name = node.name
					name =~ NAMESPLIT
					$1
				else
					ns = at(3).find { |k,v| v == namespace }
					ns ? ns : prefix_of( node.parent, namespace )
				end
			end
		end
	end
end