Nodes¶
Nodes are the primary objects in an XML document tree. The XML::Node class represents elements, text, CDATA, comments, and other node types.
Node Types¶
XML::Node::ELEMENT_NODE # <element>
XML::Node::ATTRIBUTE_NODE # attribute="value"
XML::Node::TEXT_NODE # text content
XML::Node::CDATA_SECTION_NODE # <![CDATA[...]]>
XML::Node::COMMENT_NODE # <!-- comment -->
XML::Node::DOCUMENT_NODE # the document itself
XML::Node::PI_NODE # <?target data?>
Creating Nodes¶
# Element
node = XML::Node.new('book')
# Element with text content
node = XML::Node.new('title', 'Ruby Programming')
# Special node types
cdata = XML::Node.new_cdata('raw content')
comment = XML::Node.new_comment('a comment')
pi = XML::Node.new_pi('xml-stylesheet', 'type="text/xsl" href="style.xsl"')
Reading Node Properties¶
node.name # element name
node.content # text content (recursively)
node.node_type # node type constant
node.empty? # no children?
node.doc # owning document
node.line_num # line number in source
node.path # XPath path to this node
node.lang # xml:lang value
node.base_uri # base URI
Traversal¶
node.parent # parent node
node.child # first child (alias: first)
node.last # last child
node.next # next sibling
node.prev # previous sibling
# Iterate over direct children
node.each { |child| puts child.name }
Example: Walk a Document¶
doc = XML::Parser.string(<<~XML).parse
<catalog>
<book id="1"><title>Ruby</title><price>29.99</price></book>
<book id="2"><title>Rails</title><price>39.99</price></book>
</catalog>
XML
doc.root.each do |book|
next unless book.element?
title = book.find_first('title').content
price = book.find_first('price').content
puts "#{title}: $#{price}"
end
Modifying Content¶
Changing Text¶
Note
Setting content= replaces all child nodes with a single text node. The value is automatically XML-escaped.
Renaming Elements¶
Setting Attributes¶
Adding Nodes¶
Append a Child¶
parent << XML::Node.new('child')
parent << XML::Node.new('item', 'text content')
parent << 'plain text' # adds a text node
Insert Before/After¶
# Insert after a node
node.sibling = XML::Node.new('after_me')
# Insert using next=/prev=
reference_node.next = XML::Node.new('after_reference')
reference_node.prev = XML::Node.new('before_reference')
Example: Build a Document from Scratch¶
doc = XML::Document.new
doc.root = XML::Node.new('catalog')
['Ruby', 'Rails', 'Sinatra'].each_with_index do |title, i|
book = XML::Node.new('book')
book['id'] = (i + 1).to_s
book << XML::Node.new('title', title)
book << XML::Node.new('in_stock', 'true')
doc.root << book
end
puts doc.to_s(indent: true)
Example: Add Elements from Data¶
data = [
{ name: 'Alice', role: 'admin' },
{ name: 'Bob', role: 'user' },
]
doc = XML::Document.new
doc.root = XML::Node.new('users')
data.each do |user|
node = XML::Node.new('user')
node['role'] = user[:role]
node << XML::Node.new('name', user[:name])
doc.root << node
end
Removing Nodes¶
remove! detaches the node from its parent and document. The node becomes a detached root — you can reattach it elsewhere or let it be garbage collected.
Example: Remove Matching Nodes¶
Moving Nodes¶
To move a node, remove it and reattach it:
Copying Nodes¶
shallow = node.copy(false) # element only, no children
deep = node.copy(true) # element and all descendants
Example: Duplicate a Subtree¶
template = doc.find_first('//item[@id="template"]')
copy = template.copy(true)
copy['id'] = 'new_item'
template.parent << copy
Detached Nodes¶
A newly created node is not attached to any document — it is a detached root node owned by Ruby. When you insert it into a document tree, ownership transfers to the document. Removing it transfers ownership back to Ruby.
node = XML::Node.new('item') # detached, Ruby owns it
doc.root << node # attached, document owns it
node.remove! # detached again, Ruby owns it
If a detached node goes out of scope and is garbage collected, Ruby frees it (and all its children). Once attached, the document handles cleanup.
Comparing Nodes¶
Note
Multiple Ruby objects can wrap the same underlying XML node. Use eql? or == to compare nodes, not equal?.