node.rb   [plain text]


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

# Development model
# document = Node.new

# Add an element "foo" to the document
# foo = document << "foo"
# # Set attribute "attr" on foo
# foo["attr"] = "la"
# # Set another attribute in a different namespace
# foo["attr", "namespace"] = "too"
# # Swap foo into another namespace
# foo.namespace = "blah"
# # Add a couple of element nodes to foo
# foo << "a"
# foo << "b"
# # Access the children of foo in various ways
# a = foo[0]
# foo.each { |child|
#         #...
# }
# # Add text to foo
# # Add instruction
# # Add comment
# # Get the root of the document
# document == a.root
# # Write the document out
# puts document.to_s
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 < Array
			alias :_old_get :[]
			alias :_old_put :[]=

			NAMESPLIT = /^(?:(#{XMLTokens::NCNAME_STR}):)?(#{XMLTokens::NCNAME_STR})/u
			# Create a new element.
			def initialize node=nil
				if node.kind_of? String
					node = [ :text, node ]
				elsif node.nil?
					node = [ :document, nil, nil ]
				elsif node[0] == :start_element
					node[0] = :element
				end
				replace( node )
				_old_put( 1, 0, 1 )
				_old_put( 1, nil )
			end

			def size
				el!()
				super-4
			end

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

			def name
				el!()
				at(2)
			end

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

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

			def local_name
				el!()
				namesplit
				@name
			end

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

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

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

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

			def []( reference, ns=nil )
				el!()
				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 )
				el!()
				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 = '' )
				el!()
				at(3) == name and namespace() == namespace
			end

			def children
				el!()
				self
			end

			def parent
				at(1)
			end

			def to_s

			end

			def el!
				if node_type() != :element and node_type() != :document
					_old_put( 0, :element )
					push({})
				end
				self
			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