126
126
return parent.root
129
# Evaluates to the document to which this element belongs, or nil if this
130
# element doesn't belong to a document.
129
# Evaluates to the document to which this element belongs, or nil if this
130
# element doesn't belong to a document.
136
# Evaluates to +true+ if whitespace is respected for this element. This
138
# 1. Neither :+respect_whitespace+ nor :+compress_whitespace+ has any value
139
# 2. The context has :+respect_whitespace+ set to :+all+ or
140
# an array containing the name of this element, and
136
# Evaluates to +true+ if whitespace is respected for this element. This
138
# 1. Neither :+respect_whitespace+ nor :+compress_whitespace+ has any value
139
# 2. The context has :+respect_whitespace+ set to :+all+ or
140
# an array containing the name of this element, and
141
141
# :+compress_whitespace+ isn't set to :+all+ or an array containing the
142
142
# name of this element.
143
# The evaluation is tested against +expanded_name+, and so is namespace
148
if @context[:respect_whitespace]
149
@whitespace = (@context[:respect_whitespace] == :all or
150
@context[:respect_whitespace].include? expanded_name)
152
@whitespace = false if (@context[:compress_whitespace] and
153
(@context[:compress_whitespace] == :all or
154
@context[:compress_whitespace].include? expanded_name)
157
@whitespace = true unless @whitespace == false
161
def ignore_whitespace_nodes
162
@ignore_whitespace_nodes = false
164
if @context[:ignore_whitespace_nodes]
165
@ignore_whitespace_nodes =
166
(@context[:ignore_whitespace_nodes] == :all or
167
@context[:ignore_whitespace_nodes].include? expanded_name)
172
# Evaluates to +true+ if raw mode is set for this element. This
173
# is the case if the context has :+raw+ set to :+all+ or
174
# an array containing the name of this element.
176
# The evaluation is tested against +expanded_name+, and so is namespace
179
@raw = (@context and @context[:raw] and
180
(@context[:raw] == :all or
181
@context[:raw].include? expanded_name))
185
#once :whitespace, :raw, :ignore_whitespace_nodes
187
#################################################
189
#################################################
191
# Evaluates to an +Array+ containing the prefixes (names) of all defined
192
# namespaces at this context node.
193
# doc = Document.new("<a xmlns:x='1' xmlns:y='2'><b/><c xmlns:z='3'/></a>")
194
# doc.elements['//b'].prefixes # -> ['x', 'y']
197
prefixes = parent.prefixes if parent
198
prefixes |= attributes.prefixes
204
namespaces = parent.namespaces if parent
205
namespaces = namespaces.merge( attributes.namespaces )
209
# Evalutas to the URI for a prefix, or the empty string if no such
210
# namespace is declared for this element. Evaluates recursively for
211
# ancestors. Returns the default namespace, if there is one.
213
# the prefix to search for. If not supplied, returns the default
214
# namespace if one exists
216
# the namespace URI as a String, or nil if no such namespace
217
# exists. If the namespace is undefined, returns an empty string
218
# doc = Document.new("<a xmlns='1' xmlns:y='2'><b/><c xmlns:z='3'/></a>")
219
# b = doc.elements['//b']
220
# b.namespace # -> '1'
221
# b.namespace("y") # -> '2'
222
def namespace(prefix=nil)
229
prefix = "xmlns:#{prefix}" unless prefix[0,5] == 'xmlns'
231
ns = attributes[ prefix ]
232
ns = parent.namespace(prefix) if ns.nil? and parent
233
ns = '' if ns.nil? and prefix == 'xmlns'
237
# Adds a namespace to this element.
239
# the prefix string, or the namespace URI if +uri+ is not
242
# the namespace URI. May be nil, in which +prefix+ is used as
244
# Evaluates to: this Element
245
# a = Element.new("a")
246
# a.add_namespace("xmlns:foo", "bar" )
247
# a.add_namespace("foo", "bar") # shorthand for previous line
248
# a.add_namespace("twiddle")
249
# puts a #-> <a xmlns:foo='bar' xmlns='twiddle'/>
250
def add_namespace( prefix, uri=nil )
252
@attributes["xmlns"] = prefix
254
prefix = "xmlns:#{prefix}" unless prefix =~ /^xmlns:/
255
@attributes[ prefix ] = uri
260
# Removes a namespace from this node. This only works if the namespace is
261
# actually declared in this node. If no argument is passed, deletes the
264
# Evaluates to: this element
265
# doc = Document.new "<a xmlns:foo='bar' xmlns='twiddle'/>"
266
# doc.root.delete_namespace
267
# puts doc # -> <a xmlns:foo='bar'/>
268
# doc.root.delete_namespace 'foo'
270
def delete_namespace namespace="xmlns"
271
namespace = "xmlns:#{namespace}" unless namespace == 'xmlns'
272
attribute = attributes.get_attribute(namespace)
273
attribute.remove unless attribute.nil?
277
#################################################
279
#################################################
281
# Adds a child to this element, optionally setting attributes in
284
# optional. If Element, the element is added.
285
# Otherwise, a new Element is constructed with the argument (see
286
# Element.initialize).
288
# If supplied, must be a Hash containing String name,value
289
# pairs, which will be used to set the attributes of the new Element.
290
# Returns:: the Element that was added
291
# el = doc.add_element 'my-tag'
292
# el = doc.add_element 'my-tag', {'attr1'=>'val1', 'attr2'=>'val2'}
293
# el = Element.new 'my-tag'
295
def add_element element, attrs=nil
143
# The evaluation is tested against +expanded_name+, and so is namespace
148
if @context[:respect_whitespace]
149
@whitespace = (@context[:respect_whitespace] == :all or
150
@context[:respect_whitespace].include? expanded_name)
152
@whitespace = false if (@context[:compress_whitespace] and
153
(@context[:compress_whitespace] == :all or
154
@context[:compress_whitespace].include? expanded_name)
157
@whitespace = true unless @whitespace == false
161
def ignore_whitespace_nodes
162
@ignore_whitespace_nodes = false
164
if @context[:ignore_whitespace_nodes]
165
@ignore_whitespace_nodes =
166
(@context[:ignore_whitespace_nodes] == :all or
167
@context[:ignore_whitespace_nodes].include? expanded_name)
172
# Evaluates to +true+ if raw mode is set for this element. This
173
# is the case if the context has :+raw+ set to :+all+ or
174
# an array containing the name of this element.
176
# The evaluation is tested against +expanded_name+, and so is namespace
179
@raw = (@context and @context[:raw] and
180
(@context[:raw] == :all or
181
@context[:raw].include? expanded_name))
185
#once :whitespace, :raw, :ignore_whitespace_nodes
187
#################################################
189
#################################################
191
# Evaluates to an +Array+ containing the prefixes (names) of all defined
192
# namespaces at this context node.
193
# doc = Document.new("<a xmlns:x='1' xmlns:y='2'><b/><c xmlns:z='3'/></a>")
194
# doc.elements['//b'].prefixes # -> ['x', 'y']
197
prefixes = parent.prefixes if parent
198
prefixes |= attributes.prefixes
204
namespaces = parent.namespaces if parent
205
namespaces = namespaces.merge( attributes.namespaces )
209
# Evalutas to the URI for a prefix, or the empty string if no such
210
# namespace is declared for this element. Evaluates recursively for
211
# ancestors. Returns the default namespace, if there is one.
213
# the prefix to search for. If not supplied, returns the default
214
# namespace if one exists
216
# the namespace URI as a String, or nil if no such namespace
217
# exists. If the namespace is undefined, returns an empty string
218
# doc = Document.new("<a xmlns='1' xmlns:y='2'><b/><c xmlns:z='3'/></a>")
219
# b = doc.elements['//b']
220
# b.namespace # -> '1'
221
# b.namespace("y") # -> '2'
222
def namespace(prefix=nil)
229
prefix = "xmlns:#{prefix}" unless prefix[0,5] == 'xmlns'
231
ns = attributes[ prefix ]
232
ns = parent.namespace(prefix) if ns.nil? and parent
233
ns = '' if ns.nil? and prefix == 'xmlns'
237
# Adds a namespace to this element.
239
# the prefix string, or the namespace URI if +uri+ is not
242
# the namespace URI. May be nil, in which +prefix+ is used as
244
# Evaluates to: this Element
245
# a = Element.new("a")
246
# a.add_namespace("xmlns:foo", "bar" )
247
# a.add_namespace("foo", "bar") # shorthand for previous line
248
# a.add_namespace("twiddle")
249
# puts a #-> <a xmlns:foo='bar' xmlns='twiddle'/>
250
def add_namespace( prefix, uri=nil )
252
@attributes["xmlns"] = prefix
254
prefix = "xmlns:#{prefix}" unless prefix =~ /^xmlns:/
255
@attributes[ prefix ] = uri
260
# Removes a namespace from this node. This only works if the namespace is
261
# actually declared in this node. If no argument is passed, deletes the
264
# Evaluates to: this element
265
# doc = Document.new "<a xmlns:foo='bar' xmlns='twiddle'/>"
266
# doc.root.delete_namespace
267
# puts doc # -> <a xmlns:foo='bar'/>
268
# doc.root.delete_namespace 'foo'
270
def delete_namespace namespace="xmlns"
271
namespace = "xmlns:#{namespace}" unless namespace == 'xmlns'
272
attribute = attributes.get_attribute(namespace)
273
attribute.remove unless attribute.nil?
277
#################################################
279
#################################################
281
# Adds a child to this element, optionally setting attributes in
284
# optional. If Element, the element is added.
285
# Otherwise, a new Element is constructed with the argument (see
286
# Element.initialize).
288
# If supplied, must be a Hash containing String name,value
289
# pairs, which will be used to set the attributes of the new Element.
290
# Returns:: the Element that was added
291
# el = doc.add_element 'my-tag'
292
# el = doc.add_element 'my-tag', {'attr1'=>'val1', 'attr2'=>'val2'}
293
# el = Element.new 'my-tag'
295
def add_element element, attrs=nil
296
296
raise "First argument must be either an element name, or an Element object" if element.nil?
297
el = @elements.add(element)
298
if attrs.kind_of? Hash
299
attrs.each do |key, value|
300
el.attributes[key]=value if key =~ /^xmlns:/
302
attrs.each do |key, value|
303
el.attributes[key]=value if key !~ /^xmlns:/
309
# Deletes a child element.
311
# Must be an +Element+, +String+, or +Integer+. If Element,
312
# the element is removed. If String, the element is found (via XPath)
313
# and removed. <em>This means that any parent can remove any
314
# descendant.<em> If Integer, the Element indexed by that number will be
316
# Returns:: the element that was removed.
317
# doc.delete_element "/a/b/c[@id='4']"
318
# doc.delete_element doc.elements["//k"]
319
# doc.delete_element 1
320
def delete_element element
321
@elements.delete element
324
# Evaluates to +true+ if this element has at least one child Element
325
# doc = Document.new "<a><b/><c>Text</c></a>"
326
# doc.root.has_elements # -> true
327
# doc.elements["/a/b"].has_elements # -> false
328
# doc.elements["/a/c"].has_elements # -> false
333
# Iterates through the child elements, yielding for each Element that
334
# has a particular attribute set.
336
# the name of the attribute to search for
338
# the value of the attribute
340
# (optional) causes this method to return after yielding
341
# for this number of matching children
343
# (optional) if supplied, this is an XPath that filters
344
# the children to check.
346
# doc = Document.new "<a><b @id='1'/><c @id='2'/><d @id='1'/><e/></a>"
348
# doc.root.each_element_with_attribute( 'id' ) {|e| p e}
350
# doc.root.each_element_with_attribute( 'id', '1' ) {|e| p e}
352
# doc.root.each_element_with_attribute( 'id', '1', 1 ) {|e| p e}
354
# doc.root.each_element_with_attribute( 'id', '1', 0, 'd' ) {|e| p e}
355
def each_element_with_attribute( key, value=nil, max=0, name=nil, &block ) # :yields: Element
356
each_with_something( proc {|child|
358
child.attributes[key] != nil
360
child.attributes[key]==value
362
}, max, name, &block )
365
# Iterates through the children, yielding for each Element that
366
# has a particular text set.
368
# the text to search for. If nil, or not supplied, will itterate
369
# over all +Element+ children that contain at least one +Text+ node.
371
# (optional) causes this method to return after yielding
372
# for this number of matching children
374
# (optional) if supplied, this is an XPath that filters
375
# the children to check.
377
# doc = Document.new '<a><b>b</b><c>b</c><d>d</d><e/></a>'
379
# doc.each_element_with_text {|e|p e}
381
# doc.each_element_with_text('b'){|e|p e}
383
# doc.each_element_with_text('b', 1){|e|p e}
385
# doc.each_element_with_text(nil, 0, 'd'){|e|p e}
386
def each_element_with_text( text=nil, max=0, name=nil, &block ) # :yields: Element
387
each_with_something( proc {|child|
393
}, max, name, &block )
396
# Synonym for Element.elements.each
397
def each_element( xpath=nil, &block ) # :yields: Element
398
@elements.each( xpath, &block )
401
# Synonym for Element.to_a
402
# This is a little slower than calling elements.each directly.
403
# xpath:: any XPath by which to search for elements in the tree
404
# Returns:: an array of Elements that match the supplied path
405
def get_elements( xpath )
406
@elements.to_a( xpath )
409
# Returns the next sibling that is an element, or nil if there is
410
# no Element sibling after this one
411
# doc = Document.new '<a><b/>text<c/></a>'
412
# doc.root.elements['b'].next_element #-> <c/>
413
# doc.root.elements['c'].next_element #-> nil
415
element = next_sibling
416
element = element.next_sibling until element.nil? or element.kind_of? Element
420
# Returns the previous sibling that is an element, or nil if there is
421
# no Element sibling prior to this one
422
# doc = Document.new '<a><b/>text<c/></a>'
423
# doc.root.elements['c'].previous_element #-> <b/>
424
# doc.root.elements['b'].previous_element #-> nil
426
element = previous_sibling
427
element = element.previous_sibling until element.nil? or element.kind_of? Element
432
#################################################
434
#################################################
436
# Evaluates to +true+ if this element has at least one Text child
441
# A convenience method which returns the String value of the _first_
442
# child text element, if one exists, and +nil+ otherwise.
444
# <em>Note that an element may have multiple Text elements, perhaps
445
# separated by other children</em>. Be aware that this method only returns
446
# the first Text node.
448
# This method returns the +value+ of the first text child node, which
449
# ignores the +raw+ setting, so always returns normalized text. See
450
# the Text::value documentation.
452
# doc = Document.new "<p>some text <b>this is bold!</b> more text</p>"
453
# # The element 'p' has two text elements, "some text " and " more text".
454
# doc.root.text #-> "some text "
455
def text( path = nil )
457
return rv.value unless rv.nil?
461
# Returns the first child Text node, if any, or +nil+ otherwise.
462
# This method returns the actual +Text+ node, rather than the String content.
463
# doc = Document.new "<p>some text <b>this is bold!</b> more text</p>"
464
# # The element 'p' has two text elements, "some text " and " more text".
465
# doc.root.get_text.value #-> "some text "
466
def get_text path = nil
469
element = @elements[ path ]
470
rv = element.get_text unless element.nil?
472
rv = @children.find { |node| node.kind_of? Text }
477
# Sets the first Text child of this object. See text() for a
478
# discussion about Text children.
480
# If a Text child already exists, the child is replaced by this
481
# content. This means that Text content can be deleted by calling
482
# this method with a nil argument. In this case, the next Text
483
# child becomes the first Text child. In no case is the order of
484
# any siblings disturbed.
486
# If a String, a new Text child is created and added to
487
# this Element as the first Text child. If Text, the text is set
488
# as the first Child element. If nil, then any existing first Text
490
# Returns:: this Element.
491
# doc = Document.new '<a><b/></a>'
492
# doc.root.text = 'Sean' #-> '<a><b/>Sean</a>'
493
# doc.root.text = 'Elliott' #-> '<a><b/>Elliott</a>'
494
# doc.root.add_element 'c' #-> '<a><b/>Elliott<c/></a>'
495
# doc.root.text = 'Russell' #-> '<a><b/>Russell<c/></a>'
496
# doc.root.text = nil #-> '<a><b/><c/></a>'
297
el = @elements.add(element)
298
attrs.each do |key, value|
299
el.attributes[key]=Attribute.new(key,value,self)
300
end if attrs.kind_of? Hash
304
# Deletes a child element.
306
# Must be an +Element+, +String+, or +Integer+. If Element,
307
# the element is removed. If String, the element is found (via XPath)
308
# and removed. <em>This means that any parent can remove any
309
# descendant.<em> If Integer, the Element indexed by that number will be
311
# Returns:: the element that was removed.
312
# doc.delete_element "/a/b/c[@id='4']"
313
# doc.delete_element doc.elements["//k"]
314
# doc.delete_element 1
315
def delete_element element
316
@elements.delete element
319
# Evaluates to +true+ if this element has at least one child Element
320
# doc = Document.new "<a><b/><c>Text</c></a>"
321
# doc.root.has_elements # -> true
322
# doc.elements["/a/b"].has_elements # -> false
323
# doc.elements["/a/c"].has_elements # -> false
328
# Iterates through the child elements, yielding for each Element that
329
# has a particular attribute set.
331
# the name of the attribute to search for
333
# the value of the attribute
335
# (optional) causes this method to return after yielding
336
# for this number of matching children
338
# (optional) if supplied, this is an XPath that filters
339
# the children to check.
341
# doc = Document.new "<a><b @id='1'/><c @id='2'/><d @id='1'/><e/></a>"
343
# doc.root.each_element_with_attribute( 'id' ) {|e| p e}
345
# doc.root.each_element_with_attribute( 'id', '1' ) {|e| p e}
347
# doc.root.each_element_with_attribute( 'id', '1', 1 ) {|e| p e}
349
# doc.root.each_element_with_attribute( 'id', '1', 0, 'd' ) {|e| p e}
350
def each_element_with_attribute( key, value=nil, max=0, name=nil, &block ) # :yields: Element
351
each_with_something( proc {|child|
353
child.attributes[key] != nil
355
child.attributes[key]==value
357
}, max, name, &block )
360
# Iterates through the children, yielding for each Element that
361
# has a particular text set.
363
# the text to search for. If nil, or not supplied, will itterate
364
# over all +Element+ children that contain at least one +Text+ node.
366
# (optional) causes this method to return after yielding
367
# for this number of matching children
369
# (optional) if supplied, this is an XPath that filters
370
# the children to check.
372
# doc = Document.new '<a><b>b</b><c>b</c><d>d</d><e/></a>'
374
# doc.each_element_with_text {|e|p e}
376
# doc.each_element_with_text('b'){|e|p e}
378
# doc.each_element_with_text('b', 1){|e|p e}
380
# doc.each_element_with_text(nil, 0, 'd'){|e|p e}
381
def each_element_with_text( text=nil, max=0, name=nil, &block ) # :yields: Element
382
each_with_something( proc {|child|
388
}, max, name, &block )
391
# Synonym for Element.elements.each
392
def each_element( xpath=nil, &block ) # :yields: Element
393
@elements.each( xpath, &block )
396
# Synonym for Element.to_a
397
# This is a little slower than calling elements.each directly.
398
# xpath:: any XPath by which to search for elements in the tree
399
# Returns:: an array of Elements that match the supplied path
400
def get_elements( xpath )
401
@elements.to_a( xpath )
404
# Returns the next sibling that is an element, or nil if there is
405
# no Element sibling after this one
406
# doc = Document.new '<a><b/>text<c/></a>'
407
# doc.root.elements['b'].next_element #-> <c/>
408
# doc.root.elements['c'].next_element #-> nil
410
element = next_sibling
411
element = element.next_sibling until element.nil? or element.kind_of? Element
415
# Returns the previous sibling that is an element, or nil if there is
416
# no Element sibling prior to this one
417
# doc = Document.new '<a><b/>text<c/></a>'
418
# doc.root.elements['c'].previous_element #-> <b/>
419
# doc.root.elements['b'].previous_element #-> nil
421
element = previous_sibling
422
element = element.previous_sibling until element.nil? or element.kind_of? Element
427
#################################################
429
#################################################
431
# Evaluates to +true+ if this element has at least one Text child
436
# A convenience method which returns the String value of the _first_
437
# child text element, if one exists, and +nil+ otherwise.
439
# <em>Note that an element may have multiple Text elements, perhaps
440
# separated by other children</em>. Be aware that this method only returns
441
# the first Text node.
443
# This method returns the +value+ of the first text child node, which
444
# ignores the +raw+ setting, so always returns normalized text. See
445
# the Text::value documentation.
447
# doc = Document.new "<p>some text <b>this is bold!</b> more text</p>"
448
# # The element 'p' has two text elements, "some text " and " more text".
449
# doc.root.text #-> "some text "
450
def text( path = nil )
452
return rv.value unless rv.nil?
456
# Returns the first child Text node, if any, or +nil+ otherwise.
457
# This method returns the actual +Text+ node, rather than the String content.
458
# doc = Document.new "<p>some text <b>this is bold!</b> more text</p>"
459
# # The element 'p' has two text elements, "some text " and " more text".
460
# doc.root.get_text.value #-> "some text "
461
def get_text path = nil
464
element = @elements[ path ]
465
rv = element.get_text unless element.nil?
467
rv = @children.find { |node| node.kind_of? Text }
472
# Sets the first Text child of this object. See text() for a
473
# discussion about Text children.
475
# If a Text child already exists, the child is replaced by this
476
# content. This means that Text content can be deleted by calling
477
# this method with a nil argument. In this case, the next Text
478
# child becomes the first Text child. In no case is the order of
479
# any siblings disturbed.
481
# If a String, a new Text child is created and added to
482
# this Element as the first Text child. If Text, the text is set
483
# as the first Child element. If nil, then any existing first Text
485
# Returns:: this Element.
486
# doc = Document.new '<a><b/></a>'
487
# doc.root.text = 'Sean' #-> '<a><b/>Sean</a>'
488
# doc.root.text = 'Elliott' #-> '<a><b/>Elliott</a>'
489
# doc.root.add_element 'c' #-> '<a><b/>Elliott<c/></a>'
490
# doc.root.text = 'Russell' #-> '<a><b/>Russell<c/></a>'
491
# doc.root.text = nil #-> '<a><b/><c/></a>'
497
492
def text=( text )
498
493
if text.kind_of? String
499
494
text = Text.new( text, whitespace(), nil, raw() )
500
495
elsif text and !text.kind_of? Text
501
496
text = Text.new( text.to_s, whitespace(), nil, raw() )
505
old_text.remove unless old_text.nil?
510
old_text.replace_with( text )
500
old_text.remove unless old_text.nil?
505
old_text.replace_with( text )
516
# A helper method to add a Text child. Actual Text instances can
517
# be added with regular Parent methods, such as add() and <<()
519
# if a String, a new Text instance is created and added
520
# to the parent. If Text, the object is added directly.
521
# Returns:: this Element
522
# e = Element.new('a') #-> <e/>
523
# e.add_text 'foo' #-> <e>foo</e>
524
# e.add_text Text.new(' bar') #-> <e>foo bar</e>
525
# Note that at the end of this example, the branch has <b>3</b> nodes; the 'e'
526
# element and <b>2</b> Text node children.
528
if text.kind_of? String
529
if @children[-1].kind_of? Text
530
@children[-1] << text
533
text = Text.new( text, whitespace(), nil, raw() )
535
self << text unless text.nil?
511
# A helper method to add a Text child. Actual Text instances can
512
# be added with regular Parent methods, such as add() and <<()
514
# if a String, a new Text instance is created and added
515
# to the parent. If Text, the object is added directly.
516
# Returns:: this Element
517
# e = Element.new('a') #-> <e/>
518
# e.add_text 'foo' #-> <e>foo</e>
519
# e.add_text Text.new(' bar') #-> <e>foo bar</e>
520
# Note that at the end of this example, the branch has <b>3</b> nodes; the 'e'
521
# element and <b>2</b> Text node children.
523
if text.kind_of? String
524
if @children[-1].kind_of? Text
525
@children[-1] << text
528
text = Text.new( text, whitespace(), nil, raw() )
530
self << text unless text.nil?
551
546
return path_elements.reverse.join( "/" )
554
#################################################
556
#################################################
549
#################################################
551
#################################################
558
def attribute( name, namespace=nil )
553
def attribute( name, namespace=nil )
560
555
prefix = namespaces.index(namespace) if namespace
561
attributes.get_attribute( "#{prefix ? prefix + ':' : ''}#{name}" )
564
# Evaluates to +true+ if this element has any attributes set, false
567
return !@attributes.empty?
570
# Adds an attribute to this element, overwriting any existing attribute
573
# can be either an Attribute or a String. If an Attribute,
574
# the attribute is added to the list of Element attributes. If String,
575
# the argument is used as the name of the new attribute, and the value
576
# parameter must be supplied.
578
# Required if +key+ is a String, and ignored if the first argument is
579
# an Attribute. This is a String, and is used as the value
580
# of the new Attribute.
581
# Returns:: the Attribute added
582
# e = Element.new 'e'
583
# e.add_attribute( 'a', 'b' ) #-> <e a='b'/>
584
# e.add_attribute( 'x:a', 'c' ) #-> <e a='b' x:a='c'/>
585
# e.add_attribute Attribute.new('b', 'd') #-> <e a='b' x:a='c' b='d'/>
586
def add_attribute( key, value=nil )
587
if key.kind_of? Attribute
590
@attributes[key] = value
594
# Add multiple attributes to this element.
595
# hash:: is either a hash, or array of arrays
596
# el.add_attributes( {"name1"=>"value1", "name2"=>"value2"} )
597
# el.add_attributes( [ ["name1","value1"], ["name2"=>"value2"] ] )
598
def add_attributes hash
599
if hash.kind_of? Hash
600
hash.each_pair {|key, value| @attributes[key] = value }
601
elsif hash.kind_of? Array
602
hash.each { |value| @attributes[ value[0] ] = value[1] }
606
# Removes an attribute
608
# either an Attribute or a String. In either case, the
609
# attribute is found by matching the attribute name to the argument,
610
# and then removed. If no attribute is found, no action is taken.
612
# the attribute removed, or nil if this Element did not contain
613
# a matching attribute
614
# e = Element.new('E')
615
# e.add_attribute( 'name', 'Sean' ) #-> <E name='Sean'/>
616
# r = e.add_attribute( 'sur:name', 'Russell' ) #-> <E name='Sean' sur:name='Russell'/>
617
# e.delete_attribute( 'name' ) #-> <E sur:name='Russell'/>
618
# e.delete_attribute( r ) #-> <E/>
619
def delete_attribute(key)
620
attr = @attributes.get_attribute(key)
621
attr.remove unless attr.nil?
624
#################################################
626
#################################################
628
# Get an array of all CData children.
631
find_all { |child| child.kind_of? CData }.freeze
634
# Get an array of all Comment children.
637
find_all { |child| child.kind_of? Comment }.freeze
640
# Get an array of all Instruction children.
643
find_all { |child| child.kind_of? Instruction }.freeze
646
# Get an array of all Text children.
649
find_all { |child| child.kind_of? Text }.freeze
652
# Writes out this element, and recursively, all children.
654
# output an object which supports '<< string'; this is where the
655
# document will be written.
657
# An integer. If -1, no indenting will be used; otherwise, the
658
# indentation will be this number of spaces, and children will be
659
# indented an additional amount. Defaults to -1
661
# If transitive is true and indent is >= 0, then the output will be
662
# pretty-printed in such a way that the added whitespace does not affect
663
# the parse tree of the document
665
# Internet Explorer is the worst piece of crap to have ever been
666
# written, with the possible exception of Windows itself. Since IE is
667
# unable to parse proper XML, we have to provide a hack to generate XML
668
# that IE's limited abilities can handle. This hack inserts a space
669
# before the /> on empty tags. Defaults to false
672
# doc.write( out ) #-> doc is written to the string 'out'
673
# doc.write( $stdout ) #-> doc written to the console
674
def write(writer=$stdout, indent=-1, transitive=false, ie_hack=false)
675
#print "ID:#{indent}"
676
writer << "<#@expanded_name"
678
@attributes.each_attribute do |attr|
680
attr.write( writer, indent )
681
end unless @attributes.empty?
684
if transitive and indent>-1
686
indent( writer, indent )
556
attributes.get_attribute( "#{prefix ? prefix + ':' : ''}#{name}" )
559
# Evaluates to +true+ if this element has any attributes set, false
562
return !@attributes.empty?
565
# Adds an attribute to this element, overwriting any existing attribute
568
# can be either an Attribute or a String. If an Attribute,
569
# the attribute is added to the list of Element attributes. If String,
570
# the argument is used as the name of the new attribute, and the value
571
# parameter must be supplied.
573
# Required if +key+ is a String, and ignored if the first argument is
574
# an Attribute. This is a String, and is used as the value
575
# of the new Attribute. This should be the unnormalized value of the
576
# attribute (without entities).
577
# Returns:: the Attribute added
578
# e = Element.new 'e'
579
# e.add_attribute( 'a', 'b' ) #-> <e a='b'/>
580
# e.add_attribute( 'x:a', 'c' ) #-> <e a='b' x:a='c'/>
581
# e.add_attribute Attribute.new('b', 'd') #-> <e a='b' x:a='c' b='d'/>
582
def add_attribute( key, value=nil )
583
if key.kind_of? Attribute
586
@attributes[key] = value
590
# Add multiple attributes to this element.
591
# hash:: is either a hash, or array of arrays
592
# el.add_attributes( {"name1"=>"value1", "name2"=>"value2"} )
593
# el.add_attributes( [ ["name1","value1"], ["name2"=>"value2"] ] )
594
def add_attributes hash
595
if hash.kind_of? Hash
596
hash.each_pair {|key, value| @attributes[key] = value }
597
elsif hash.kind_of? Array
598
hash.each { |value| @attributes[ value[0] ] = value[1] }
602
# Removes an attribute
604
# either an Attribute or a String. In either case, the
605
# attribute is found by matching the attribute name to the argument,
606
# and then removed. If no attribute is found, no action is taken.
608
# the attribute removed, or nil if this Element did not contain
609
# a matching attribute
610
# e = Element.new('E')
611
# e.add_attribute( 'name', 'Sean' ) #-> <E name='Sean'/>
612
# r = e.add_attribute( 'sur:name', 'Russell' ) #-> <E name='Sean' sur:name='Russell'/>
613
# e.delete_attribute( 'name' ) #-> <E sur:name='Russell'/>
614
# e.delete_attribute( r ) #-> <E/>
615
def delete_attribute(key)
616
attr = @attributes.get_attribute(key)
617
attr.remove unless attr.nil?
620
#################################################
622
#################################################
624
# Get an array of all CData children.
627
find_all { |child| child.kind_of? CData }.freeze
630
# Get an array of all Comment children.
633
find_all { |child| child.kind_of? Comment }.freeze
636
# Get an array of all Instruction children.
639
find_all { |child| child.kind_of? Instruction }.freeze
642
# Get an array of all Text children.
645
find_all { |child| child.kind_of? Text }.freeze
649
# See REXML::Formatters
651
# Writes out this element, and recursively, all children.
653
# output an object which supports '<< string'; this is where the
654
# document will be written.
656
# An integer. If -1, no indenting will be used; otherwise, the
657
# indentation will be this number of spaces, and children will be
658
# indented an additional amount. Defaults to -1
660
# If transitive is true and indent is >= 0, then the output will be
661
# pretty-printed in such a way that the added whitespace does not affect
662
# the parse tree of the document
664
# Internet Explorer is the worst piece of crap to have ever been
665
# written, with the possible exception of Windows itself. Since IE is
666
# unable to parse proper XML, we have to provide a hack to generate XML
667
# that IE's limited abilities can handle. This hack inserts a space
668
# before the /> on empty tags. Defaults to false
671
# doc.write( out ) #-> doc is written to the string 'out'
672
# doc.write( $stdout ) #-> doc written to the console
673
def write(writer=$stdout, indent=-1, transitive=false, ie_hack=false)
674
Kernel.warn("#{self.class.name}.write is deprecated. See REXML::Formatters")
675
formatter = if indent > -1
677
REXML::Formatters::Transitive.new( indent, ie_hack )
679
REXML::Formatters::Pretty.new( indent, ie_hack )
682
REXML::Formatters::Default.new( ie_hack )
692
if transitive and indent>-1 and !@children[0].kind_of? Text
694
indent writer, indent+1
697
write_children( writer, indent, transitive, ie_hack )
698
writer << "</#{expanded_name}"
700
if transitive and indent>-1 and !@children.empty?
702
indent -= 1 if next_sibling.nil?
703
indent(writer, indent)
684
formatter.write( self, output )
710
689
def __to_xpath_helper node
711
690
rv = node.expanded_name.clone
724
# A private helper method
725
def each_with_something( test, max=0, name=nil )
728
@elements.each( name ){ |child|
729
yield child if test.call(child) and num += 1
730
return if max>0 and num == max
734
# A private helper method
735
def write_children( writer, indent, transitive, ie_hack )
736
cr = (indent < 0) ? '' : "\n"
738
each { |child| child.write( writer, indent, transitive, ie_hack ) }
740
next_indent = indent+1
743
unless child.kind_of? Text or last_child.kind_of? Text or transitive
745
indent(writer, next_indent)
747
child.write( writer, next_indent, transitive, ie_hack )
750
unless last_child.kind_of? Text or transitive
752
indent( writer, indent )
758
########################################################################
760
########################################################################
762
# A class which provides filtering of children for Elements, and
763
# XPath search support. You are expected to only encounter this class as
764
# the <tt>element.elements</tt> object. Therefore, you are
765
# _not_ expected to instantiate this yourself.
769
# parent:: the parent Element
770
def initialize parent
774
# Fetches a child element. Filters only Element children, regardless of
777
# the search parameter. This is either an Integer, which
778
# will be used to find the index'th child Element, or an XPath,
779
# which will be used to search for the Element. <em>Because
780
# of the nature of XPath searches, any element in the connected XML
781
# document can be fetched through any other element.</em> <b>The
782
# Integer index is 1-based, not 0-based.</b> This means that the first
783
# child element is at index 1, not 0, and the +n+th element is at index
784
# +n+, not <tt>n-1</tt>. This is because XPath indexes element children
785
# starting from 1, not 0, and the indexes should be the same.
787
# optional, and only used in the first argument is an
788
# Integer. In that case, the index'th child Element that has the
789
# supplied name will be returned. Note again that the indexes start at 1.
790
# Returns:: the first matching Element, or nil if no child matched
791
# doc = Document.new '<a><b/><c id="1"/><c id="2"/><d/></a>'
792
# doc.root.elements[1] #-> <b/>
793
# doc.root.elements['c'] #-> <c id="1"/>
794
# doc.root.elements[2,'c'] #-> <c id="2"/>
795
def []( index, name=nil)
796
if index.kind_of? Integer
797
raise "index (#{index}) must be >= 1" if index < 1
798
name = literalize(name) if name
801
@element.find { |child|
802
child.kind_of? Element and
803
(name.nil? ? true : child.has_name?( name )) and
807
return XPath::first( @element, index )
809
# return element if element.kind_of? Element
815
# Sets an element, replacing any previous matching element. If no
816
# existing element is found ,the element is added.
817
# index:: Used to find a matching element to replace. See []().
819
# The element to replace the existing element with
820
# the previous element
821
# Returns:: nil if no previous element was found.
823
# doc = Document.new '<a/>'
824
# doc.root.elements[10] = Element.new('b') #-> <a><b/></a>
825
# doc.root.elements[1] #-> <b/>
826
# doc.root.elements[1] = Element.new('c') #-> <a><c/></a>
827
# doc.root.elements['c'] = Element.new('d') #-> <a><d/></a>
828
def []=( index, element )
829
previous = self[index]
833
previous.replace_with element
838
# Returns +true+ if there are no +Element+ children, +false+ otherwise
840
@element.find{ |child| child.kind_of? Element}.nil?
843
# Returns the index of the supplied child (starting at 1), or -1 if
844
# the element is not a child
845
# element:: an +Element+ child
848
found = @element.find do |child|
849
child.kind_of? Element and
853
return rv if found == element
857
# Deletes a child Element
859
# Either an Element, which is removed directly; an
860
# xpath, where the first matching child is removed; or an Integer,
861
# where the n'th Element is removed.
862
# Returns:: the removed child
863
# doc = Document.new '<a><b/><c/><c id="1"/></a>'
864
# b = doc.root.elements[1]
865
# doc.root.elements.delete b #-> <a><c/><c id="1"/></a>
866
# doc.elements.delete("a/c[@id='1']") #-> <a><c/></a>
867
# doc.root.elements.delete 1 #-> <a/>
869
if element.kind_of? Element
870
@element.delete element
877
# Removes multiple elements. Filters for Element children, regardless of
879
# xpath:: all elements matching this String path are removed.
880
# Returns:: an Array of Elements that have been removed
881
# doc = Document.new '<a><c/><c/><c/><c/></a>'
882
# deleted = doc.elements.delete_all 'a/c' #-> [<c/>, <c/>, <c/>, <c/>]
883
def delete_all( xpath )
885
XPath::each( @element, xpath) {|element|
886
rv << element if element.kind_of? Element
889
@element.delete element
897
# if supplied, is either an Element, String, or
898
# Source (see Element.initialize). If not supplied or nil, a
899
# new, default Element will be constructed
900
# Returns:: the added Element
901
# a = Element.new 'a'
902
# a.elements.add Element.new 'b' #-> <a><b/></a>
903
# a.elements.add 'c' #-> <a><b/><c/></a>
907
Element.new "", self, @element.context
908
elsif not element.kind_of?(Element)
909
Element.new element, self, @element.context
912
element.context = @element.context
919
# Iterates through all of the child Elements, optionally filtering
920
# them by a given XPath
922
# optional. If supplied, this is a String XPath, and is used to
923
# filter the children, so that only matching children are yielded. Note
924
# that XPaths are automatically filtered for Elements, so that
925
# non-Element children will not be yielded
926
# doc = Document.new '<a><b/><c/><d/>sean<b/><c/><d/></a>'
927
# doc.root.each {|e|p e} #-> Yields b, c, d, b, c, d elements
928
# doc.root.each('b') {|e|p e} #-> Yields b, b elements
929
# doc.root.each('child::node()') {|e|p e}
930
# #-> Yields <b/>, <c/>, <d/>, <b/>, <c/>, <d/>
931
# XPath.each(doc.root, 'child::node()', &block)
932
# #-> Yields <b/>, <c/>, <d/>, sean, <b/>, <c/>, <d/>
933
def each( xpath=nil, &block)
934
XPath::each( @element, xpath ) {|e| yield e if e.kind_of? Element }
937
def collect( xpath=nil, &block )
939
XPath::each( @element, xpath ) {|e|
940
collection << yield(e) if e.kind_of?(Element)
945
def inject( xpath=nil, initial=nil, &block )
947
XPath::each( @element, xpath ) {|e|
948
if (e.kind_of? Element)
949
if (first and initial == nil)
953
initial = yield( initial, e ) if e.kind_of? Element
960
# Returns the number of +Element+ children of the parent object.
961
# doc = Document.new '<a>sean<b/>elliott<b/>russell<b/></a>'
962
# doc.root.size #-> 6, 3 element and 3 text nodes
963
# doc.root.elements.size #-> 3
966
@element.each {|child| count+=1 if child.kind_of? Element }
970
# Returns an Array of Element children. An XPath may be supplied to
971
# filter the children. Only Element children are returned, even if the
972
# supplied XPath matches non-Element children.
973
# doc = Document.new '<a>sean<b/>elliott<c/></a>'
974
# doc.root.elements.to_a #-> [ <b/>, <c/> ]
975
# doc.root.elements.to_a("child::node()") #-> [ <b/>, <c/> ]
976
# XPath.match(doc.root, "child::node()") #-> [ sean, <b/>, elliott, <c/> ]
977
def to_a( xpath=nil )
978
rv = XPath.match( @element, xpath )
979
return rv.find_all{|e| e.kind_of? Element} if xpath
984
# Private helper class. Removes quotes from quoted strings
986
name = name[1..-2] if name[0] == ?' or name[0] == ?" #'
991
########################################################################
993
########################################################################
995
# A class that defines the set of Attributes of an Element and provides
996
# operations for accessing elements in that set.
997
class Attributes < Hash
999
# element:: the Element of which this is an Attribute
1000
def initialize element
1004
# Fetches an attribute value. If you want to get the Attribute itself,
1005
# use get_attribute()
1006
# name:: an XPath attribute name. Namespaces are relevant here.
1008
# the String value of the matching attribute, or +nil+ if no
1009
# matching attribute was found.
1011
# doc = Document.new "<a foo:att='1' bar:att='2' att='3'/>"
1012
# doc.root.attributes['att'] #-> '3'
1013
# doc.root.attributes['bar:att'] #-> '2'
1015
attr = get_attribute(name)
1016
return attr.value unless attr.nil?
1024
# Returns the number of attributes the owning Element contains.
1025
# doc = Document "<a x='1' y='2' foo:x='3'/>"
1026
# doc.root.attributes.length #-> 3
1029
each_attribute { c+=1 }
1034
# Itterates over the attributes of an Element. Yields actual Attribute
1035
# nodes, not String values.
1037
# doc = Document.new '<a x="1" y="2"/>'
1038
# doc.root.attributes.each_attribute {|attr|
1039
# p attr.expanded_name+" => "+attr.value
1041
def each_attribute # :yields: attribute
1043
if val.kind_of? Attribute
1046
val.each_value { |atr| yield atr }
1051
# Itterates over each attribute of an Element, yielding the expanded name
1052
# and value as a pair of Strings.
1054
# doc = Document.new '<a x="1" y="2"/>'
1055
# doc.root.attributes.each {|name, value| p name+" => "+value }
1057
each_attribute do |attr|
1058
yield attr.expanded_name, attr.value
1062
# Fetches an attribute
1064
# the name by which to search for the attribute. Can be a
1065
# <tt>prefix:name</tt> namespace name.
1066
# Returns:: The first matching attribute, or nil if there was none. This
1067
# value is an Attribute node, not the String value of the attribute.
1068
# doc = Document.new '<a x:foo="1" foo="2" bar="3"/>'
1069
# doc.root.attributes.get_attribute("foo").value #-> "2"
1070
# doc.root.attributes.get_attribute("x:foo").value #-> "1"
1071
def get_attribute( name )
1072
attr = fetch( name, nil )
1074
return nil if name.nil?
1076
name =~ Namespace::NAMESPLIT
1079
attr = fetch( n, nil )
1082
elsif attr.kind_of? Attribute
1083
return attr if prefix == attr.prefix
1085
attr = attr[ prefix ]
703
# A private helper method
704
def each_with_something( test, max=0, name=nil )
707
@elements.each( name ){ |child|
708
yield child if test.call(child) and num += 1
709
return if max>0 and num == max
714
########################################################################
716
########################################################################
718
# A class which provides filtering of children for Elements, and
719
# XPath search support. You are expected to only encounter this class as
720
# the <tt>element.elements</tt> object. Therefore, you are
721
# _not_ expected to instantiate this yourself.
725
# parent:: the parent Element
726
def initialize parent
730
# Fetches a child element. Filters only Element children, regardless of
733
# the search parameter. This is either an Integer, which
734
# will be used to find the index'th child Element, or an XPath,
735
# which will be used to search for the Element. <em>Because
736
# of the nature of XPath searches, any element in the connected XML
737
# document can be fetched through any other element.</em> <b>The
738
# Integer index is 1-based, not 0-based.</b> This means that the first
739
# child element is at index 1, not 0, and the +n+th element is at index
740
# +n+, not <tt>n-1</tt>. This is because XPath indexes element children
741
# starting from 1, not 0, and the indexes should be the same.
743
# optional, and only used in the first argument is an
744
# Integer. In that case, the index'th child Element that has the
745
# supplied name will be returned. Note again that the indexes start at 1.
746
# Returns:: the first matching Element, or nil if no child matched
747
# doc = Document.new '<a><b/><c id="1"/><c id="2"/><d/></a>'
748
# doc.root.elements[1] #-> <b/>
749
# doc.root.elements['c'] #-> <c id="1"/>
750
# doc.root.elements[2,'c'] #-> <c id="2"/>
751
def []( index, name=nil)
752
if index.kind_of? Integer
753
raise "index (#{index}) must be >= 1" if index < 1
754
name = literalize(name) if name
757
@element.find { |child|
758
child.kind_of? Element and
759
(name.nil? ? true : child.has_name?( name )) and
763
return XPath::first( @element, index )
765
# return element if element.kind_of? Element
771
# Sets an element, replacing any previous matching element. If no
772
# existing element is found ,the element is added.
773
# index:: Used to find a matching element to replace. See []().
775
# The element to replace the existing element with
776
# the previous element
777
# Returns:: nil if no previous element was found.
779
# doc = Document.new '<a/>'
780
# doc.root.elements[10] = Element.new('b') #-> <a><b/></a>
781
# doc.root.elements[1] #-> <b/>
782
# doc.root.elements[1] = Element.new('c') #-> <a><c/></a>
783
# doc.root.elements['c'] = Element.new('d') #-> <a><d/></a>
784
def []=( index, element )
785
previous = self[index]
789
previous.replace_with element
794
# Returns +true+ if there are no +Element+ children, +false+ otherwise
796
@element.find{ |child| child.kind_of? Element}.nil?
799
# Returns the index of the supplied child (starting at 1), or -1 if
800
# the element is not a child
801
# element:: an +Element+ child
804
found = @element.find do |child|
805
child.kind_of? Element and
809
return rv if found == element
813
# Deletes a child Element
815
# Either an Element, which is removed directly; an
816
# xpath, where the first matching child is removed; or an Integer,
817
# where the n'th Element is removed.
818
# Returns:: the removed child
819
# doc = Document.new '<a><b/><c/><c id="1"/></a>'
820
# b = doc.root.elements[1]
821
# doc.root.elements.delete b #-> <a><c/><c id="1"/></a>
822
# doc.elements.delete("a/c[@id='1']") #-> <a><c/></a>
823
# doc.root.elements.delete 1 #-> <a/>
825
if element.kind_of? Element
826
@element.delete element
833
# Removes multiple elements. Filters for Element children, regardless of
835
# xpath:: all elements matching this String path are removed.
836
# Returns:: an Array of Elements that have been removed
837
# doc = Document.new '<a><c/><c/><c/><c/></a>'
838
# deleted = doc.elements.delete_all 'a/c' #-> [<c/>, <c/>, <c/>, <c/>]
839
def delete_all( xpath )
841
XPath::each( @element, xpath) {|element|
842
rv << element if element.kind_of? Element
845
@element.delete element
853
# if supplied, is either an Element, String, or
854
# Source (see Element.initialize). If not supplied or nil, a
855
# new, default Element will be constructed
856
# Returns:: the added Element
857
# a = Element.new 'a'
858
# a.elements.add Element.new 'b' #-> <a><b/></a>
859
# a.elements.add 'c' #-> <a><b/><c/></a>
863
Element.new "", self, @element.context
864
elsif not element.kind_of?(Element)
865
Element.new element, self, @element.context
868
element.context = @element.context
875
# Iterates through all of the child Elements, optionally filtering
876
# them by a given XPath
878
# optional. If supplied, this is a String XPath, and is used to
879
# filter the children, so that only matching children are yielded. Note
880
# that XPaths are automatically filtered for Elements, so that
881
# non-Element children will not be yielded
882
# doc = Document.new '<a><b/><c/><d/>sean<b/><c/><d/></a>'
883
# doc.root.each {|e|p e} #-> Yields b, c, d, b, c, d elements
884
# doc.root.each('b') {|e|p e} #-> Yields b, b elements
885
# doc.root.each('child::node()') {|e|p e}
886
# #-> Yields <b/>, <c/>, <d/>, <b/>, <c/>, <d/>
887
# XPath.each(doc.root, 'child::node()', &block)
888
# #-> Yields <b/>, <c/>, <d/>, sean, <b/>, <c/>, <d/>
889
def each( xpath=nil, &block)
890
XPath::each( @element, xpath ) {|e| yield e if e.kind_of? Element }
893
def collect( xpath=nil, &block )
895
XPath::each( @element, xpath ) {|e|
896
collection << yield(e) if e.kind_of?(Element)
901
def inject( xpath=nil, initial=nil, &block )
903
XPath::each( @element, xpath ) {|e|
904
if (e.kind_of? Element)
905
if (first and initial == nil)
909
initial = yield( initial, e ) if e.kind_of? Element
916
# Returns the number of +Element+ children of the parent object.
917
# doc = Document.new '<a>sean<b/>elliott<b/>russell<b/></a>'
918
# doc.root.size #-> 6, 3 element and 3 text nodes
919
# doc.root.elements.size #-> 3
922
@element.each {|child| count+=1 if child.kind_of? Element }
926
# Returns an Array of Element children. An XPath may be supplied to
927
# filter the children. Only Element children are returned, even if the
928
# supplied XPath matches non-Element children.
929
# doc = Document.new '<a>sean<b/>elliott<c/></a>'
930
# doc.root.elements.to_a #-> [ <b/>, <c/> ]
931
# doc.root.elements.to_a("child::node()") #-> [ <b/>, <c/> ]
932
# XPath.match(doc.root, "child::node()") #-> [ sean, <b/>, elliott, <c/> ]
933
def to_a( xpath=nil )
934
rv = XPath.match( @element, xpath )
935
return rv.find_all{|e| e.kind_of? Element} if xpath
940
# Private helper class. Removes quotes from quoted strings
942
name = name[1..-2] if name[0] == ?' or name[0] == ?" #'
947
########################################################################
949
########################################################################
951
# A class that defines the set of Attributes of an Element and provides
952
# operations for accessing elements in that set.
953
class Attributes < Hash
955
# element:: the Element of which this is an Attribute
956
def initialize element
960
# Fetches an attribute value. If you want to get the Attribute itself,
961
# use get_attribute()
962
# name:: an XPath attribute name. Namespaces are relevant here.
964
# the String value of the matching attribute, or +nil+ if no
965
# matching attribute was found. This is the unnormalized value
966
# (with entities expanded).
968
# doc = Document.new "<a foo:att='1' bar:att='2' att='<'/>"
969
# doc.root.attributes['att'] #-> '<'
970
# doc.root.attributes['bar:att'] #-> '2'
972
attr = get_attribute(name)
973
return attr.value unless attr.nil?
981
# Returns the number of attributes the owning Element contains.
982
# doc = Document "<a x='1' y='2' foo:x='3'/>"
983
# doc.root.attributes.length #-> 3
986
each_attribute { c+=1 }
991
# Itterates over the attributes of an Element. Yields actual Attribute
992
# nodes, not String values.
994
# doc = Document.new '<a x="1" y="2"/>'
995
# doc.root.attributes.each_attribute {|attr|
996
# p attr.expanded_name+" => "+attr.value
998
def each_attribute # :yields: attribute
1000
if val.kind_of? Attribute
1003
val.each_value { |atr| yield atr }
1008
# Itterates over each attribute of an Element, yielding the expanded name
1009
# and value as a pair of Strings.
1011
# doc = Document.new '<a x="1" y="2"/>'
1012
# doc.root.attributes.each {|name, value| p name+" => "+value }
1014
each_attribute do |attr|
1015
yield attr.expanded_name, attr.value
1019
# Fetches an attribute
1021
# the name by which to search for the attribute. Can be a
1022
# <tt>prefix:name</tt> namespace name.
1023
# Returns:: The first matching attribute, or nil if there was none. This
1024
# value is an Attribute node, not the String value of the attribute.
1025
# doc = Document.new '<a x:foo="1" foo="2" bar="3"/>'
1026
# doc.root.attributes.get_attribute("foo").value #-> "2"
1027
# doc.root.attributes.get_attribute("x:foo").value #-> "1"
1028
def get_attribute( name )
1029
attr = fetch( name, nil )
1031
return nil if name.nil?
1033
name =~ Namespace::NAMESPLIT
1036
attr = fetch( n, nil )
1039
elsif attr.kind_of? Attribute
1040
return attr if prefix == attr.prefix
1042
attr = attr[ prefix ]
1089
1046
element_document = @element.document
1090
if element_document and element_document.doctype
1091
expn = @element.expanded_name
1092
expn = element_document.doctype.name if expn.size == 0
1093
attr_val = element_document.doctype.attribute_of(expn, name)
1094
return Attribute.new( name, attr_val ) if attr_val
1098
if attr.kind_of? Hash
1099
attr = attr[ @element.prefix ]
1104
# Sets an attribute, overwriting any existing attribute value by the
1105
# same name. Namespace is significant.
1106
# name:: the name of the attribute
1108
# (optional) If supplied, the value of the attribute. If
1109
# nil, any existing matching attribute is deleted.
1112
# doc = Document.new "<a x:foo='1' foo='3'/>"
1113
# doc.root.attributes['y:foo'] = '2'
1114
# doc.root.attributes['foo'] = '4'
1115
# doc.root.attributes['x:foo'] = nil
1116
def []=( name, value )
1117
if value.nil? # Delete the named attribute
1118
attr = get_attribute(name)
1122
value = Attribute.new(name, value) unless value.kind_of? Attribute
1123
value.element = @element
1124
old_attr = fetch(value.name, nil)
1126
store(value.name, value)
1127
elsif old_attr.kind_of? Hash
1128
old_attr[value.prefix] = value
1129
elsif old_attr.prefix != value.prefix
1130
# Check for conflicting namespaces
1131
raise ParseException.new(
1132
"Namespace conflict in adding attribute \"#{value.name}\": "+
1133
"Prefix \"#{old_attr.prefix}\" = "+
1134
"\"#{@element.namespace(old_attr.prefix)}\" and prefix "+
1135
"\"#{value.prefix}\" = \"#{@element.namespace(value.prefix)}\"") if
1136
value.prefix != "xmlns" and old_attr.prefix != "xmlns" and
1137
@element.namespace( old_attr.prefix ) ==
1138
@element.namespace( value.prefix )
1139
store value.name, { old_attr.prefix => old_attr,
1140
value.prefix => value }
1142
store value.name, value
1147
# Returns an array of Strings containing all of the prefixes declared
1148
# by this set of # attributes. The array does not include the default
1149
# namespace declaration, if one exists.
1150
# doc = Document.new("<a xmlns='foo' xmlns:x='bar' xmlns:y='twee' "+
1151
# "z='glorp' p:k='gru'/>")
1152
# prefixes = doc.root.attributes.prefixes #-> ['x', 'y']
1155
each_attribute do |attribute|
1156
ns << attribute.name if attribute.prefix == 'xmlns'
1158
if @element.document and @element.document.doctype
1159
expn = @element.expanded_name
1160
expn = @element.document.doctype.name if expn.size == 0
1161
@element.document.doctype.attributes_of(expn).each {
1163
ns << attribute.name if attribute.prefix == 'xmlns'
1171
each_attribute do |attribute|
1172
namespaces[attribute.name] = attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns'
1174
if @element.document and @element.document.doctype
1175
expn = @element.expanded_name
1176
expn = @element.document.doctype.name if expn.size == 0
1177
@element.document.doctype.attributes_of(expn).each {
1179
namespaces[attribute.name] = attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns'
1185
# Removes an attribute
1187
# either a String, which is the name of the attribute to remove --
1188
# namespaces are significant here -- or the attribute to remove.
1189
# Returns:: the owning element
1190
# doc = Document.new "<a y:foo='0' x:foo='1' foo='3' z:foo='4'/>"
1191
# doc.root.attributes.delete 'foo' #-> <a y:foo='0' x:foo='1' z:foo='4'/>"
1192
# doc.root.attributes.delete 'x:foo' #-> <a y:foo='0' z:foo='4'/>"
1193
# attr = doc.root.attributes.get_attribute('y:foo')
1194
# doc.root.attributes.delete attr #-> <a z:foo='4'/>"
1195
def delete( attribute )
1198
if attribute.kind_of? Attribute
1199
name = attribute.name
1200
prefix = attribute.prefix
1202
attribute =~ Namespace::NAMESPLIT
1203
prefix, name = $1, $2
1204
prefix = '' unless prefix
1206
old = fetch(name, nil)
1208
if old.kind_of? Hash # the supplied attribute is one of many
1209
attr = old.delete(prefix)
1212
old.each_value{|v| repl = v}
1217
else # the supplied attribute is a top-level one
1224
# Adds an attribute, overriding any existing attribute by the
1225
# same name. Namespaces are significant.
1226
# attribute:: An Attribute
1227
def add( attribute )
1228
self[attribute.name] = attribute
1233
# Deletes all attributes matching a name. Namespaces are significant.
1235
# A String; all attributes that match this path will be removed
1236
# Returns:: an Array of the Attributes that were removed
1237
def delete_all( name )
1239
each_attribute { |attribute|
1240
rv << attribute if attribute.expanded_name == name
1242
rv.each{ |attr| attr.remove }
1047
if element_document and element_document.doctype
1048
expn = @element.expanded_name
1049
expn = element_document.doctype.name if expn.size == 0
1050
attr_val = element_document.doctype.attribute_of(expn, name)
1051
return Attribute.new( name, attr_val ) if attr_val
1055
if attr.kind_of? Hash
1056
attr = attr[ @element.prefix ]
1061
# Sets an attribute, overwriting any existing attribute value by the
1062
# same name. Namespace is significant.
1063
# name:: the name of the attribute
1065
# (optional) If supplied, the value of the attribute. If
1066
# nil, any existing matching attribute is deleted.
1069
# doc = Document.new "<a x:foo='1' foo='3'/>"
1070
# doc.root.attributes['y:foo'] = '2'
1071
# doc.root.attributes['foo'] = '4'
1072
# doc.root.attributes['x:foo'] = nil
1073
def []=( name, value )
1074
if value.nil? # Delete the named attribute
1075
attr = get_attribute(name)
1079
element_document = @element.document
1080
unless value.kind_of? Attribute
1081
if @element.document and @element.document.doctype
1082
value = Text::normalize( value, @element.document.doctype )
1084
value = Text::normalize( value, nil )
1086
value = Attribute.new(name, value)
1088
value.element = @element
1089
old_attr = fetch(value.name, nil)
1091
store(value.name, value)
1092
elsif old_attr.kind_of? Hash
1093
old_attr[value.prefix] = value
1094
elsif old_attr.prefix != value.prefix
1095
# Check for conflicting namespaces
1096
raise ParseException.new(
1097
"Namespace conflict in adding attribute \"#{value.name}\": "+
1098
"Prefix \"#{old_attr.prefix}\" = "+
1099
"\"#{@element.namespace(old_attr.prefix)}\" and prefix "+
1100
"\"#{value.prefix}\" = \"#{@element.namespace(value.prefix)}\"") if
1101
value.prefix != "xmlns" and old_attr.prefix != "xmlns" and
1102
@element.namespace( old_attr.prefix ) ==
1103
@element.namespace( value.prefix )
1104
store value.name, { old_attr.prefix => old_attr,
1105
value.prefix => value }
1107
store value.name, value
1112
# Returns an array of Strings containing all of the prefixes declared
1113
# by this set of # attributes. The array does not include the default
1114
# namespace declaration, if one exists.
1115
# doc = Document.new("<a xmlns='foo' xmlns:x='bar' xmlns:y='twee' "+
1116
# "z='glorp' p:k='gru'/>")
1117
# prefixes = doc.root.attributes.prefixes #-> ['x', 'y']
1120
each_attribute do |attribute|
1121
ns << attribute.name if attribute.prefix == 'xmlns'
1123
if @element.document and @element.document.doctype
1124
expn = @element.expanded_name
1125
expn = @element.document.doctype.name if expn.size == 0
1126
@element.document.doctype.attributes_of(expn).each {
1128
ns << attribute.name if attribute.prefix == 'xmlns'
1136
each_attribute do |attribute|
1137
namespaces[attribute.name] = attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns'
1139
if @element.document and @element.document.doctype
1140
expn = @element.expanded_name
1141
expn = @element.document.doctype.name if expn.size == 0
1142
@element.document.doctype.attributes_of(expn).each {
1144
namespaces[attribute.name] = attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns'
1150
# Removes an attribute
1152
# either a String, which is the name of the attribute to remove --
1153
# namespaces are significant here -- or the attribute to remove.
1154
# Returns:: the owning element
1155
# doc = Document.new "<a y:foo='0' x:foo='1' foo='3' z:foo='4'/>"
1156
# doc.root.attributes.delete 'foo' #-> <a y:foo='0' x:foo='1' z:foo='4'/>"
1157
# doc.root.attributes.delete 'x:foo' #-> <a y:foo='0' z:foo='4'/>"
1158
# attr = doc.root.attributes.get_attribute('y:foo')
1159
# doc.root.attributes.delete attr #-> <a z:foo='4'/>"
1160
def delete( attribute )
1163
if attribute.kind_of? Attribute
1164
name = attribute.name
1165
prefix = attribute.prefix
1167
attribute =~ Namespace::NAMESPLIT
1168
prefix, name = $1, $2
1169
prefix = '' unless prefix
1171
old = fetch(name, nil)
1173
if old.kind_of? Hash # the supplied attribute is one of many
1174
attr = old.delete(prefix)
1177
old.each_value{|v| repl = v}
1182
else # the supplied attribute is a top-level one
1189
# Adds an attribute, overriding any existing attribute by the
1190
# same name. Namespaces are significant.
1191
# attribute:: An Attribute
1192
def add( attribute )
1193
self[attribute.name] = attribute
1198
# Deletes all attributes matching a name. Namespaces are significant.
1200
# A String; all attributes that match this path will be removed
1201
# Returns:: an Array of the Attributes that were removed
1202
def delete_all( name )
1204
each_attribute { |attribute|
1205
rv << attribute if attribute.expanded_name == name
1207
rv.each{ |attr| attr.remove }
1246
1211
# The +get_attribute_ns+ method retrieves a method by its namespace
1247
1212
# and name. Thus it is possible to reliably identify an attribute
1248
1213
# even if an XML processor has changed the prefix.