~ubuntu-branches/ubuntu/intrepid/ruby1.9/intrepid-updates

« back to all changes in this revision

Viewing changes to lib/rexml/element.rb

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2007-09-04 16:01:17 UTC
  • mfrom: (1.1.8 upstream)
  • Revision ID: james.westby@ubuntu.com-20070904160117-i15zckg2nhxe9fyw
Tags: 1.9.0+20070830-2ubuntu1
* Sync from Debian; remaining changes:
  - Add -g to CFLAGS.
* Fixes build failure on ia64.
* Fixes build failure with gcc-4.2 on lpia.
* Robustify check for target_os, fixing build failure on lpia.
* Set Ubuntu maintainer address.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
  # context node and convert it back when we write it.
15
15
  @@namespaces = {}
16
16
 
17
 
        # Represents a tagged XML element.  Elements are characterized by
18
 
        # having children, attributes, and names, and can themselves be
19
 
        # children.
20
 
        class Element < Parent
21
 
                include Namespace
22
 
 
23
 
                UNDEFINED = "UNDEFINED";                # The default name
24
 
 
25
 
                # Mechanisms for accessing attributes and child elements of this
26
 
                # element.
27
 
                attr_reader :attributes, :elements
28
 
                # The context holds information about the processing environment, such as
29
 
                # whitespace handling.
30
 
                attr_accessor :context
31
 
 
32
 
                # Constructor
33
 
                # arg:: 
34
 
                #       if not supplied, will be set to the default value.
35
 
                #       If a String, the name of this object will be set to the argument.
36
 
                #       If an Element, the object will be shallowly cloned; name, 
37
 
                #       attributes, and namespaces will be copied.  Children will +not+ be
38
 
                #       copied.
39
 
                # parent:: 
40
 
                #       if supplied, must be a Parent, and will be used as
41
 
                #       the parent of this object.
42
 
                # context::
43
 
                #       If supplied, must be a hash containing context items.  Context items
44
 
                #       include:
45
 
                # * <tt>:respect_whitespace</tt> the value of this is :+all+ or an array of
46
 
                #   strings being the names of the elements to respect
47
 
                #   whitespace for.  Defaults to :+all+.
48
 
                # * <tt>:compress_whitespace</tt> the value can be :+all+ or an array of
49
 
                #   strings being the names of the elements to ignore whitespace on.
50
 
                #   Overrides :+respect_whitespace+.
51
 
                # * <tt>:ignore_whitespace_nodes</tt> the value can be :+all+ or an array
52
 
                #   of strings being the names of the elements in which to ignore
53
 
                #   whitespace-only nodes.  If this is set, Text nodes which contain only
54
 
                #   whitespace will not be added to the document tree.
55
 
                # * <tt>:raw</tt> can be :+all+, or an array of strings being the names of
56
 
                #   the elements to process in raw mode.  In raw mode, special
57
 
                #   characters in text is not converted to or from entities.
58
 
                def initialize( arg = UNDEFINED, parent=nil, context=nil )
59
 
                        super(parent)
60
 
 
61
 
                        @elements = Elements.new(self)
62
 
                        @attributes = Attributes.new(self)
63
 
                        @context = context
64
 
 
65
 
                        if arg.kind_of? String
66
 
                                self.name = arg
67
 
                        elsif arg.kind_of? Element
68
 
                                self.name = arg.expanded_name
69
 
                                arg.attributes.each_attribute{ |attribute|
70
 
                                        @attributes << Attribute.new( attribute )
71
 
                                }
72
 
                                @context = arg.context
73
 
                        end
74
 
                end
 
17
  # Represents a tagged XML element.  Elements are characterized by
 
18
  # having children, attributes, and names, and can themselves be
 
19
  # children.
 
20
  class Element < Parent
 
21
    include Namespace
 
22
 
 
23
    UNDEFINED = "UNDEFINED";            # The default name
 
24
 
 
25
    # Mechanisms for accessing attributes and child elements of this
 
26
    # element.
 
27
    attr_reader :attributes, :elements
 
28
    # The context holds information about the processing environment, such as
 
29
    # whitespace handling.
 
30
    attr_accessor :context
 
31
 
 
32
    # Constructor
 
33
    # arg:: 
 
34
    #   if not supplied, will be set to the default value.
 
35
    #   If a String, the name of this object will be set to the argument.
 
36
    #   If an Element, the object will be shallowly cloned; name, 
 
37
    #   attributes, and namespaces will be copied.  Children will +not+ be
 
38
    #   copied.
 
39
    # parent:: 
 
40
    #   if supplied, must be a Parent, and will be used as
 
41
    #   the parent of this object.
 
42
    # context::
 
43
    #   If supplied, must be a hash containing context items.  Context items
 
44
    #   include:
 
45
    # * <tt>:respect_whitespace</tt> the value of this is :+all+ or an array of
 
46
    #   strings being the names of the elements to respect
 
47
    #   whitespace for.  Defaults to :+all+.
 
48
    # * <tt>:compress_whitespace</tt> the value can be :+all+ or an array of
 
49
    #   strings being the names of the elements to ignore whitespace on.
 
50
    #   Overrides :+respect_whitespace+.
 
51
    # * <tt>:ignore_whitespace_nodes</tt> the value can be :+all+ or an array
 
52
    #   of strings being the names of the elements in which to ignore
 
53
    #   whitespace-only nodes.  If this is set, Text nodes which contain only
 
54
    #   whitespace will not be added to the document tree.
 
55
    # * <tt>:raw</tt> can be :+all+, or an array of strings being the names of
 
56
    #   the elements to process in raw mode.  In raw mode, special
 
57
    #   characters in text is not converted to or from entities.
 
58
    def initialize( arg = UNDEFINED, parent=nil, context=nil )
 
59
      super(parent)
 
60
 
 
61
      @elements = Elements.new(self)
 
62
      @attributes = Attributes.new(self)
 
63
      @context = context
 
64
 
 
65
      if arg.kind_of? String
 
66
        self.name = arg
 
67
      elsif arg.kind_of? Element
 
68
        self.name = arg.expanded_name
 
69
        arg.attributes.each_attribute{ |attribute|
 
70
          @attributes << Attribute.new( attribute )
 
71
        }
 
72
        @context = arg.context
 
73
      end
 
74
    end
75
75
 
76
76
    def inspect
77
77
      rv = "<#@expanded_name"
89
89
    end
90
90
 
91
91
 
92
 
                # Creates a shallow copy of self.
93
 
                #   d = Document.new "<a><b/><b/><c><d/></c></a>"
94
 
                #   new_a = d.root.clone
95
 
                #   puts new_a  # => "<a/>"
96
 
                def clone
97
 
                        self.class.new self
98
 
                end
 
92
    # Creates a shallow copy of self.
 
93
    #   d = Document.new "<a><b/><b/><c><d/></c></a>"
 
94
    #   new_a = d.root.clone
 
95
    #   puts new_a  # => "<a/>"
 
96
    def clone
 
97
      self.class.new self
 
98
    end
99
99
 
100
 
                # Evaluates to the root node of the document that this element 
101
 
                # belongs to. If this element doesn't belong to a document, but does
102
 
                # belong to another Element, the parent's root will be returned, until the
103
 
                # earliest ancestor is found.
 
100
    # Evaluates to the root node of the document that this element 
 
101
    # belongs to. If this element doesn't belong to a document, but does
 
102
    # belong to another Element, the parent's root will be returned, until the
 
103
    # earliest ancestor is found.
104
104
    #
105
105
    # Note that this is not the same as the document element.
106
106
    # In the following example, <a> is the document element, and the root
111
111
    # The only time this isn't true is when an Element is created that is
112
112
    # not part of any Document.  In this case, the ancestor that has no
113
113
    # parent acts as the root node.
114
 
                #  d = Document.new '<a><b><c/></b></a>'
115
 
                #  a = d[1] ; c = a[1][1]
116
 
                #  d.root_node == d   # TRUE
117
 
                #  a.root_node        # namely, d
118
 
                #  c.root_node        # again, d
119
 
                def root_node
120
 
                        parent.nil? ? self : parent.root_node
121
 
                end
 
114
    #  d = Document.new '<a><b><c/></b></a>'
 
115
    #  a = d[1] ; c = a[1][1]
 
116
    #  d.root_node == d   # TRUE
 
117
    #  a.root_node        # namely, d
 
118
    #  c.root_node        # again, d
 
119
    def root_node
 
120
      parent.nil? ? self : parent.root_node
 
121
    end
122
122
 
123
123
    def root
124
124
      return elements[1] if self.kind_of? Document
126
126
      return parent.root
127
127
    end
128
128
 
129
 
                # Evaluates to the document to which this element belongs, or nil if this
130
 
                # element doesn't belong to a document.
131
 
                def document
 
129
    # Evaluates to the document to which this element belongs, or nil if this
 
130
    # element doesn't belong to a document.
 
131
    def document
132
132
      rt = root
133
 
                        rt.parent if rt
134
 
                end
 
133
      rt.parent if rt
 
134
    end
135
135
 
136
 
                # Evaluates to +true+ if whitespace is respected for this element.  This
137
 
                # is the case if:
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
 
137
    # is the case if:
 
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
144
 
                # sensitive.
145
 
                def whitespace
146
 
                        @whitespace = nil
147
 
                        if @context
148
 
                                if @context[:respect_whitespace]
149
 
                                        @whitespace = (@context[:respect_whitespace] == :all or
150
 
                                                                                                 @context[:respect_whitespace].include? expanded_name)
151
 
                                end
152
 
                                @whitespace = false if (@context[:compress_whitespace] and
153
 
                                        (@context[:compress_whitespace] == :all or
154
 
                                         @context[:compress_whitespace].include? expanded_name)
155
 
                                )
156
 
                        end
157
 
                        @whitespace = true unless @whitespace == false
158
 
                        @whitespace
159
 
                end
160
 
 
161
 
                def ignore_whitespace_nodes
162
 
                        @ignore_whitespace_nodes = false
163
 
                        if @context
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)
168
 
                                end
169
 
                        end
170
 
                end
171
 
 
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.
175
 
                #
176
 
                # The evaluation is tested against +expanded_name+, and so is namespace
177
 
                # sensitive.
178
 
                def raw
179
 
                        @raw = (@context and @context[:raw] and
180
 
                        (@context[:raw] == :all or
181
 
                        @context[:raw].include? expanded_name))
182
 
                        @raw
183
 
                end
184
 
 
185
 
                #once :whitespace, :raw, :ignore_whitespace_nodes
186
 
 
187
 
                #################################################
188
 
                # Namespaces                                    #
189
 
                #################################################
190
 
 
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']
195
 
                def prefixes
196
 
                        prefixes = []
197
 
                        prefixes = parent.prefixes if parent
198
 
                        prefixes |= attributes.prefixes
199
 
                        return prefixes
200
 
                end
201
 
 
202
 
                def namespaces
203
 
                        namespaces = {}
204
 
                        namespaces = parent.namespaces if parent
205
 
                        namespaces = namespaces.merge( attributes.namespaces )
206
 
                        return namespaces
207
 
                end
208
 
 
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.
212
 
                # prefix:: 
213
 
                #   the prefix to search for.  If not supplied, returns the default
214
 
                #   namespace if one exists
215
 
                # Returns:: 
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)
223
 
                        if prefix.nil?
224
 
                                prefix = prefix()
225
 
                        end
226
 
                        if prefix == ''
227
 
                                prefix = "xmlns"
228
 
                        else
229
 
                                prefix = "xmlns:#{prefix}" unless prefix[0,5] == 'xmlns'
230
 
                        end
231
 
                        ns = attributes[ prefix ]
232
 
                        ns = parent.namespace(prefix) if ns.nil? and parent
233
 
                        ns = '' if ns.nil? and prefix == 'xmlns'
234
 
                        return ns
235
 
                end
236
 
 
237
 
                # Adds a namespace to this element.
238
 
                # prefix:: 
239
 
                #   the prefix string, or the namespace URI if +uri+ is not
240
 
                #   supplied
241
 
                # uri::    
242
 
                #   the namespace URI.  May be nil, in which +prefix+ is used as
243
 
                #   the URI
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 )
251
 
                        unless uri
252
 
                                @attributes["xmlns"] = prefix
253
 
                        else
254
 
                                prefix = "xmlns:#{prefix}" unless prefix =~ /^xmlns:/
255
 
                                @attributes[ prefix ] = uri
256
 
                        end
257
 
                        self
258
 
                end
259
 
 
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
262
 
                # default namespace.
263
 
                #
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'
269
 
                #  puts doc     # -> <a/>
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?
274
 
                        self
275
 
                end
276
 
 
277
 
                #################################################
278
 
                # Elements                                      #
279
 
                #################################################
280
 
 
281
 
                # Adds a child to this element, optionally setting attributes in
282
 
                # the element.
283
 
                # element:: 
284
 
                #   optional.  If Element, the element is added.
285
 
                #   Otherwise, a new Element is constructed with the argument (see
286
 
                #   Element.initialize).
287
 
                # attrs:: 
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'
294
 
                #  doc.add_element el
295
 
                def add_element element, attrs=nil
 
143
    # The evaluation is tested against +expanded_name+, and so is namespace
 
144
    # sensitive.
 
145
    def whitespace
 
146
      @whitespace = nil
 
147
      if @context
 
148
        if @context[:respect_whitespace]
 
149
          @whitespace = (@context[:respect_whitespace] == :all or
 
150
                         @context[:respect_whitespace].include? expanded_name)
 
151
        end
 
152
        @whitespace = false if (@context[:compress_whitespace] and
 
153
                                (@context[:compress_whitespace] == :all or
 
154
                                 @context[:compress_whitespace].include? expanded_name)
 
155
                               )
 
156
      end
 
157
      @whitespace = true unless @whitespace == false
 
158
      @whitespace
 
159
    end
 
160
 
 
161
    def ignore_whitespace_nodes
 
162
      @ignore_whitespace_nodes = false
 
163
      if @context
 
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)
 
168
        end
 
169
      end
 
170
    end
 
171
 
 
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.
 
175
    #
 
176
    # The evaluation is tested against +expanded_name+, and so is namespace
 
177
    # sensitive.
 
178
    def raw
 
179
      @raw = (@context and @context[:raw] and
 
180
              (@context[:raw] == :all or
 
181
               @context[:raw].include? expanded_name))
 
182
               @raw
 
183
    end
 
184
 
 
185
    #once :whitespace, :raw, :ignore_whitespace_nodes
 
186
 
 
187
    #################################################
 
188
    # Namespaces                                    #
 
189
    #################################################
 
190
 
 
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']
 
195
    def prefixes
 
196
      prefixes = []
 
197
      prefixes = parent.prefixes if parent
 
198
      prefixes |= attributes.prefixes
 
199
      return prefixes
 
200
    end
 
201
 
 
202
    def namespaces
 
203
      namespaces = {}
 
204
      namespaces = parent.namespaces if parent
 
205
      namespaces = namespaces.merge( attributes.namespaces )
 
206
      return namespaces
 
207
    end
 
208
 
 
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.
 
212
    # prefix:: 
 
213
    #   the prefix to search for.  If not supplied, returns the default
 
214
    #   namespace if one exists
 
215
    # Returns:: 
 
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)
 
223
      if prefix.nil?
 
224
        prefix = prefix()
 
225
      end
 
226
      if prefix == ''
 
227
        prefix = "xmlns"
 
228
      else
 
229
        prefix = "xmlns:#{prefix}" unless prefix[0,5] == 'xmlns'
 
230
      end
 
231
      ns = attributes[ prefix ]
 
232
      ns = parent.namespace(prefix) if ns.nil? and parent
 
233
      ns = '' if ns.nil? and prefix == 'xmlns'
 
234
      return ns
 
235
    end
 
236
 
 
237
    # Adds a namespace to this element.
 
238
    # prefix:: 
 
239
    #   the prefix string, or the namespace URI if +uri+ is not
 
240
    #   supplied
 
241
    # uri::    
 
242
    #   the namespace URI.  May be nil, in which +prefix+ is used as
 
243
    #   the URI
 
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 )
 
251
      unless uri
 
252
        @attributes["xmlns"] = prefix
 
253
      else
 
254
        prefix = "xmlns:#{prefix}" unless prefix =~ /^xmlns:/
 
255
        @attributes[ prefix ] = uri
 
256
      end
 
257
      self
 
258
    end
 
259
 
 
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
 
262
    # default namespace.
 
263
    #
 
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'
 
269
    #  puts doc     # -> <a/>
 
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?
 
274
      self
 
275
    end
 
276
 
 
277
    #################################################
 
278
    # Elements                                      #
 
279
    #################################################
 
280
 
 
281
    # Adds a child to this element, optionally setting attributes in
 
282
    # the element.
 
283
    # element:: 
 
284
    #   optional.  If Element, the element is added.
 
285
    #   Otherwise, a new Element is constructed with the argument (see
 
286
    #   Element.initialize).
 
287
    # attrs:: 
 
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'
 
294
    #  doc.add_element el
 
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:/
301
 
                                end
302
 
                                attrs.each do |key, value|
303
 
                                        el.attributes[key]=value if key !~ /^xmlns:/
304
 
                                end
305
 
                        end
306
 
                        el
307
 
                end
308
 
 
309
 
                # Deletes a child element.
310
 
                # 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
315
 
                #   removed.
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
322
 
                end
323
 
 
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
329
 
                def has_elements?
330
 
                        !@elements.empty?
331
 
                end
332
 
 
333
 
                # Iterates through the child elements, yielding for each Element that
334
 
                # has a particular attribute set.
335
 
                # key:: 
336
 
                #   the name of the attribute to search for
337
 
                # value:: 
338
 
                #   the value of the attribute
339
 
                # max:: 
340
 
                #   (optional) causes this method to return after yielding 
341
 
                #   for this number of matching children
342
 
                # name:: 
343
 
                #   (optional) if supplied, this is an XPath that filters
344
 
                #   the children to check.
345
 
                #
346
 
                #  doc = Document.new "<a><b @id='1'/><c @id='2'/><d @id='1'/><e/></a>"
347
 
                #  # Yields b, c, d
348
 
                #  doc.root.each_element_with_attribute( 'id' ) {|e| p e}
349
 
                #  # Yields b, d
350
 
                #  doc.root.each_element_with_attribute( 'id', '1' ) {|e| p e}
351
 
                #  # Yields b
352
 
                #  doc.root.each_element_with_attribute( 'id', '1', 1 ) {|e| p e}
353
 
                #  # Yields d
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| 
357
 
                                if value.nil?
358
 
                                        child.attributes[key] != nil
359
 
                                else
360
 
                                        child.attributes[key]==value
361
 
                                end
362
 
                        }, max, name, &block )
363
 
                end
364
 
 
365
 
                # Iterates through the children, yielding for each Element that
366
 
                # has a particular text set.
367
 
                # text:: 
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.
370
 
                # max:: 
371
 
                #   (optional) causes this method to return after yielding
372
 
                #   for this number of matching children
373
 
                # name:: 
374
 
                #   (optional) if supplied, this is an XPath that filters
375
 
                #   the children to check.
376
 
                #
377
 
                #  doc = Document.new '<a><b>b</b><c>b</c><d>d</d><e/></a>'
378
 
                #  # Yields b, c, d
379
 
                #  doc.each_element_with_text {|e|p e}
380
 
                #  # Yields b, c
381
 
                #  doc.each_element_with_text('b'){|e|p e}
382
 
                #  # Yields b
383
 
                #  doc.each_element_with_text('b', 1){|e|p e}
384
 
                #  # Yields d
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| 
388
 
                                if text.nil?
389
 
                                        child.has_text?
390
 
                                else
391
 
                                        child.text == text
392
 
                                end
393
 
                        }, max, name, &block )
394
 
                end
395
 
 
396
 
                # Synonym for Element.elements.each
397
 
                def each_element( xpath=nil, &block ) # :yields: Element
398
 
                        @elements.each( xpath, &block )
399
 
                end
400
 
 
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 )
407
 
                end
408
 
 
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
414
 
                def next_element
415
 
                        element = next_sibling
416
 
                        element = element.next_sibling until element.nil? or element.kind_of? Element 
417
 
                        return element
418
 
                end
419
 
 
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
425
 
                def previous_element
426
 
                        element = previous_sibling
427
 
                        element = element.previous_sibling until element.nil? or element.kind_of? Element
428
 
                        return element
429
 
                end
430
 
 
431
 
 
432
 
                #################################################
433
 
                # Text                                          #
434
 
                #################################################
435
 
 
436
 
                # Evaluates to +true+ if this element has at least one Text child
437
 
                def has_text?
438
 
                        not text().nil?
439
 
                end
440
 
 
441
 
                # A convenience method which returns the String value of the _first_
442
 
                # child text element, if one exists, and +nil+ otherwise.
443
 
                #
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.
447
 
                #
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.
451
 
                #
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 )
456
 
                        rv = get_text(path)
457
 
                        return rv.value unless rv.nil?
458
 
                        nil
459
 
                end
460
 
 
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
467
 
                        rv = nil
468
 
                        if path
469
 
                                element = @elements[ path ]
470
 
                                rv = element.get_text unless element.nil?
471
 
                        else
472
 
                                rv = @children.find { |node| node.kind_of? Text }
473
 
                        end
474
 
                        return rv
475
 
                end
476
 
 
477
 
                # Sets the first Text child of this object.  See text() for a
478
 
                # discussion about Text children.
479
 
                #
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.
485
 
                # text:: 
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
489
 
                #   child is removed.
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
 
301
      el
 
302
    end
 
303
 
 
304
    # Deletes a child element.
 
305
    # 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
 
310
    #   removed.
 
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
 
317
    end
 
318
 
 
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
 
324
    def has_elements?
 
325
      !@elements.empty?
 
326
    end
 
327
 
 
328
    # Iterates through the child elements, yielding for each Element that
 
329
    # has a particular attribute set.
 
330
    # key:: 
 
331
    #   the name of the attribute to search for
 
332
    # value:: 
 
333
    #   the value of the attribute
 
334
    # max:: 
 
335
    #   (optional) causes this method to return after yielding 
 
336
    #   for this number of matching children
 
337
    # name:: 
 
338
    #   (optional) if supplied, this is an XPath that filters
 
339
    #   the children to check.
 
340
    #
 
341
    #  doc = Document.new "<a><b @id='1'/><c @id='2'/><d @id='1'/><e/></a>"
 
342
    #  # Yields b, c, d
 
343
    #  doc.root.each_element_with_attribute( 'id' ) {|e| p e}
 
344
    #  # Yields b, d
 
345
    #  doc.root.each_element_with_attribute( 'id', '1' ) {|e| p e}
 
346
    #  # Yields b
 
347
    #  doc.root.each_element_with_attribute( 'id', '1', 1 ) {|e| p e}
 
348
    #  # Yields d
 
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| 
 
352
        if value.nil?
 
353
          child.attributes[key] != nil
 
354
        else
 
355
          child.attributes[key]==value
 
356
        end
 
357
      }, max, name, &block )
 
358
    end
 
359
 
 
360
    # Iterates through the children, yielding for each Element that
 
361
    # has a particular text set.
 
362
    # text:: 
 
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.
 
365
    # max:: 
 
366
    #   (optional) causes this method to return after yielding
 
367
    #   for this number of matching children
 
368
    # name:: 
 
369
    #   (optional) if supplied, this is an XPath that filters
 
370
    #   the children to check.
 
371
    #
 
372
    #  doc = Document.new '<a><b>b</b><c>b</c><d>d</d><e/></a>'
 
373
    #  # Yields b, c, d
 
374
    #  doc.each_element_with_text {|e|p e}
 
375
    #  # Yields b, c
 
376
    #  doc.each_element_with_text('b'){|e|p e}
 
377
    #  # Yields b
 
378
    #  doc.each_element_with_text('b', 1){|e|p e}
 
379
    #  # Yields d
 
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| 
 
383
        if text.nil?
 
384
          child.has_text?
 
385
        else
 
386
          child.text == text
 
387
        end
 
388
      }, max, name, &block )
 
389
    end
 
390
 
 
391
    # Synonym for Element.elements.each
 
392
    def each_element( xpath=nil, &block ) # :yields: Element
 
393
      @elements.each( xpath, &block )
 
394
    end
 
395
 
 
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 )
 
402
    end
 
403
 
 
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
 
409
    def next_element
 
410
      element = next_sibling
 
411
      element = element.next_sibling until element.nil? or element.kind_of? Element 
 
412
      return element
 
413
    end
 
414
 
 
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
 
420
    def previous_element
 
421
      element = previous_sibling
 
422
      element = element.previous_sibling until element.nil? or element.kind_of? Element
 
423
      return element
 
424
    end
 
425
 
 
426
 
 
427
    #################################################
 
428
    # Text                                          #
 
429
    #################################################
 
430
 
 
431
    # Evaluates to +true+ if this element has at least one Text child
 
432
    def has_text?
 
433
      not text().nil?
 
434
    end
 
435
 
 
436
    # A convenience method which returns the String value of the _first_
 
437
    # child text element, if one exists, and +nil+ otherwise.
 
438
    #
 
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.
 
442
    #
 
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.
 
446
    #
 
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 )
 
451
      rv = get_text(path)
 
452
      return rv.value unless rv.nil?
 
453
      nil
 
454
    end
 
455
 
 
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
 
462
      rv = nil
 
463
      if path
 
464
        element = @elements[ path ]
 
465
        rv = element.get_text unless element.nil?
 
466
      else
 
467
        rv = @children.find { |node| node.kind_of? Text }
 
468
      end
 
469
      return rv
 
470
    end
 
471
 
 
472
    # Sets the first Text child of this object.  See text() for a
 
473
    # discussion about Text children.
 
474
    #
 
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.
 
480
    # text:: 
 
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
 
484
    #   child is removed.
 
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() )
502
497
      end
503
 
                        old_text = get_text
504
 
                        if text.nil?
505
 
                                old_text.remove unless old_text.nil?
506
 
                        else
507
 
                                if old_text.nil?
508
 
                                        self << text
509
 
                                else
510
 
                                        old_text.replace_with( text )
511
 
                                end
512
 
                        end
513
 
                        return self
514
 
                end
 
498
      old_text = get_text
 
499
      if text.nil?
 
500
        old_text.remove unless old_text.nil?
 
501
      else
 
502
        if old_text.nil?
 
503
          self << text
 
504
        else
 
505
          old_text.replace_with( text )
 
506
        end
 
507
      end
 
508
      return self
 
509
    end
515
510
 
516
 
                # A helper method to add a Text child.  Actual Text instances can
517
 
                # be added with regular Parent methods, such as add() and <<()
518
 
                # text::
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.
527
 
                def add_text( text )
528
 
                        if text.kind_of? String 
529
 
                                if @children[-1].kind_of? Text
530
 
                                        @children[-1] << text
531
 
                                        return
532
 
                                end
533
 
                                text = Text.new( text, whitespace(), nil, raw() )
534
 
                        end
535
 
                        self << text unless text.nil?
536
 
                        return self
537
 
                end
 
511
    # A helper method to add a Text child.  Actual Text instances can
 
512
    # be added with regular Parent methods, such as add() and <<()
 
513
    # text::
 
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.
 
522
    def add_text( text )
 
523
      if text.kind_of? String 
 
524
        if @children[-1].kind_of? Text
 
525
          @children[-1] << text
 
526
          return
 
527
        end
 
528
        text = Text.new( text, whitespace(), nil, raw() )
 
529
      end
 
530
      self << text unless text.nil?
 
531
      return self
 
532
    end
538
533
 
539
534
    def node_type
540
535
      :element
551
546
      return path_elements.reverse.join( "/" )
552
547
    end
553
548
 
554
 
                #################################################
555
 
                # Attributes                                    #
556
 
                #################################################
 
549
    #################################################
 
550
    # Attributes                                    #
 
551
    #################################################
557
552
 
558
 
                def attribute( name, namespace=nil )
559
 
                        prefix = nil
 
553
    def attribute( name, namespace=nil )
 
554
      prefix = nil
560
555
      prefix = namespaces.index(namespace) if namespace
561
 
                        attributes.get_attribute( "#{prefix ? prefix + ':' : ''}#{name}" )
562
 
                end
563
 
 
564
 
                # Evaluates to +true+ if this element has any attributes set, false
565
 
                # otherwise.
566
 
                def has_attributes?
567
 
                        return !@attributes.empty?
568
 
                end
569
 
 
570
 
                # Adds an attribute to this element, overwriting any existing attribute
571
 
                # by the same name.
572
 
                # key::
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.
577
 
                # value:: 
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
588
 
                                @attributes << key
589
 
                        else
590
 
                                @attributes[key] = value
591
 
                        end
592
 
                end
593
 
 
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] }
603
 
                        end
604
 
                end
605
 
 
606
 
                # Removes an attribute
607
 
                # key::
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.
611
 
                # Returns:: 
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?
622
 
                end
623
 
 
624
 
                #################################################
625
 
                # Other Utilities                               #
626
 
                #################################################
627
 
 
628
 
                # Get an array of all CData children.  
629
 
                # IMMUTABLE
630
 
                def cdatas
631
 
                        find_all { |child| child.kind_of? CData }.freeze
632
 
                end
633
 
 
634
 
                # Get an array of all Comment children.
635
 
                # IMMUTABLE
636
 
                def comments
637
 
                        find_all { |child| child.kind_of? Comment }.freeze
638
 
                end
639
 
 
640
 
                # Get an array of all Instruction children.
641
 
                # IMMUTABLE
642
 
                def instructions
643
 
                        find_all { |child| child.kind_of? Instruction }.freeze
644
 
                end
645
 
 
646
 
                # Get an array of all Text children.
647
 
                # IMMUTABLE
648
 
                def texts
649
 
                        find_all { |child| child.kind_of? Text }.freeze
650
 
                end
651
 
 
652
 
                # Writes out this element, and recursively, all children.
653
 
                # output::
654
 
                #         output an object which supports '<< string'; this is where the
655
 
                #   document will be written.
656
 
                # indent::
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
660
 
                # transitive::
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
664
 
                # ie_hack::
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
670
 
                #
671
 
                #  out = ''
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"
677
 
 
678
 
                        @attributes.each_attribute do |attr|
679
 
                                writer << " "
680
 
                                attr.write( writer, indent )
681
 
                        end unless @attributes.empty?
682
 
 
683
 
                        if @children.empty?
684
 
        if transitive and indent>-1
685
 
          writer << "\n"
686
 
          indent( writer, indent )
687
 
        elsif ie_hack
688
 
          writer << " " 
 
556
      attributes.get_attribute( "#{prefix ? prefix + ':' : ''}#{name}" )
 
557
    end
 
558
 
 
559
    # Evaluates to +true+ if this element has any attributes set, false
 
560
    # otherwise.
 
561
    def has_attributes?
 
562
      return !@attributes.empty?
 
563
    end
 
564
 
 
565
    # Adds an attribute to this element, overwriting any existing attribute
 
566
    # by the same name.
 
567
    # key::
 
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.
 
572
    # value:: 
 
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
 
584
        @attributes << key
 
585
      else
 
586
        @attributes[key] = value
 
587
      end
 
588
    end
 
589
 
 
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] }
 
599
      end
 
600
    end
 
601
 
 
602
    # Removes an attribute
 
603
    # key::
 
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.
 
607
    # Returns:: 
 
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?
 
618
    end
 
619
 
 
620
    #################################################
 
621
    # Other Utilities                               #
 
622
    #################################################
 
623
 
 
624
    # Get an array of all CData children.  
 
625
    # IMMUTABLE
 
626
    def cdatas
 
627
      find_all { |child| child.kind_of? CData }.freeze
 
628
    end
 
629
 
 
630
    # Get an array of all Comment children.
 
631
    # IMMUTABLE
 
632
    def comments
 
633
      find_all { |child| child.kind_of? Comment }.freeze
 
634
    end
 
635
 
 
636
    # Get an array of all Instruction children.
 
637
    # IMMUTABLE
 
638
    def instructions
 
639
      find_all { |child| child.kind_of? Instruction }.freeze
 
640
    end
 
641
 
 
642
    # Get an array of all Text children.
 
643
    # IMMUTABLE
 
644
    def texts
 
645
      find_all { |child| child.kind_of? Text }.freeze
 
646
    end
 
647
 
 
648
    # == DEPRECATED
 
649
    # See REXML::Formatters
 
650
    #
 
651
    # Writes out this element, and recursively, all children.
 
652
    # output::
 
653
    #     output an object which supports '<< string'; this is where the
 
654
    #   document will be written.
 
655
    # indent::
 
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
 
659
    # transitive::
 
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
 
663
    # ie_hack::
 
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
 
669
    #
 
670
    #  out = ''
 
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
 
676
          if transitive
 
677
            REXML::Formatters::Transitive.new( indent, ie_hack )
 
678
          else
 
679
            REXML::Formatters::Pretty.new( indent, ie_hack )
 
680
          end
 
681
        else
 
682
          REXML::Formatters::Default.new( ie_hack )
689
683
        end
690
 
                                writer << "/" 
691
 
                        else
692
 
                                if transitive and indent>-1 and !@children[0].kind_of? Text
693
 
                                        writer << "\n"
694
 
                                        indent writer, indent+1
695
 
                                end
696
 
                                writer << ">"
697
 
                                write_children( writer, indent, transitive, ie_hack )
698
 
                                writer << "</#{expanded_name}"
699
 
                        end
700
 
                        if transitive and indent>-1 and !@children.empty?
701
 
                                writer << "\n"
702
 
                                indent -= 1 if next_sibling.nil?
703
 
                                indent(writer, indent)
704
 
                        end
705
 
                        writer << ">"
706
 
                end
707
 
 
708
 
 
709
 
                private
 
684
      formatter.write( self, output )
 
685
    end
 
686
 
 
687
 
 
688
    private
710
689
    def __to_xpath_helper node
711
690
      rv = node.expanded_name.clone
712
691
      if node.parent
721
700
      rv
722
701
    end
723
702
 
724
 
                # A private helper method
725
 
                def each_with_something( test, max=0, name=nil )
726
 
                        num = 0
727
 
                        child=nil
728
 
                        @elements.each( name ){ |child|
729
 
                                yield child if test.call(child) and num += 1
730
 
                                return if max>0 and num == max
731
 
                        }
732
 
                end
733
 
 
734
 
                # A private helper method
735
 
                def write_children( writer, indent, transitive, ie_hack )
736
 
                        cr = (indent < 0) ? '' : "\n"
737
 
                        if indent == -1
738
 
                                each { |child| child.write( writer, indent, transitive, ie_hack ) }
739
 
                        else
740
 
                                next_indent = indent+1
741
 
                                last_child=nil
742
 
                                each { |child|
743
 
                                        unless child.kind_of? Text or last_child.kind_of? Text or transitive
744
 
                                                writer << cr
745
 
                                                indent(writer, next_indent)
746
 
                                        end
747
 
                                        child.write( writer, next_indent, transitive, ie_hack )
748
 
                                        last_child = child
749
 
                                }
750
 
                                unless last_child.kind_of? Text or transitive
751
 
                                        writer << cr
752
 
                                        indent( writer, indent )
753
 
                                end
754
 
                        end
755
 
                end
756
 
        end
757
 
 
758
 
        ########################################################################
759
 
        # ELEMENTS                                                             #
760
 
        ########################################################################
761
 
 
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.
766
 
        class Elements
767
 
                include Enumerable
768
 
                # Constructor
769
 
                # parent:: the parent Element
770
 
                def initialize parent
771
 
                        @element = parent
772
 
                end
773
 
 
774
 
                # Fetches a child element.  Filters only Element children, regardless of
775
 
                # the XPath match.
776
 
                # index:: 
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.
786
 
                # name:: 
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
799
 
                                num = 0
800
 
                                child = nil
801
 
                                @element.find { |child|
802
 
                                        child.kind_of? Element and
803
 
                                        (name.nil? ? true : child.has_name?( name )) and 
804
 
                                        (num += 1) == index
805
 
                                }
806
 
                        else
807
 
                                return XPath::first( @element, index )
808
 
                                #{ |element| 
809
 
                                #       return element if element.kind_of? Element
810
 
                                #}
811
 
                                #return nil
812
 
                        end
813
 
                end
814
 
 
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 []().
818
 
                # element:: 
819
 
                #   The element to replace the existing element with
820
 
                #   the previous element
821
 
                # Returns:: nil if no previous element was found.
822
 
                #
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]
830
 
                        if previous.nil?
831
 
                                @element.add element
832
 
                        else
833
 
                                previous.replace_with element
834
 
                        end
835
 
                        return previous
836
 
                end
837
 
 
838
 
                # Returns +true+ if there are no +Element+ children, +false+ otherwise
839
 
                def empty?
840
 
                        @element.find{ |child| child.kind_of? Element}.nil?
841
 
                end
842
 
 
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
846
 
                def index element
847
 
                        rv = 0
848
 
                        found = @element.find do |child| 
849
 
                                child.kind_of? Element and
850
 
                                (rv += 1) and
851
 
                                child == element
852
 
                        end
853
 
                        return rv if found == element
854
 
                        return -1
855
 
                end
856
 
 
857
 
                # Deletes a child Element
858
 
                # 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/>
868
 
                def delete element
869
 
                        if element.kind_of? Element
870
 
                                @element.delete element
871
 
                        else
872
 
                                el = self[element]
873
 
                                el.remove if el
874
 
                        end
875
 
                end
876
 
 
877
 
                # Removes multiple elements.  Filters for Element children, regardless of
878
 
                # XPath matching.
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 )
884
 
                        rv = []
885
 
                        XPath::each( @element, xpath) {|element| 
886
 
                                rv << element if element.kind_of? Element
887
 
                        }
888
 
                        rv.each do |element|
889
 
                                @element.delete element
890
 
                                element.remove
891
 
                        end
892
 
                        return rv
893
 
                end
894
 
 
895
 
                # Adds an element
896
 
                # 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>
904
 
                def add element=nil
905
 
                        rv = nil
906
 
                        if element.nil?
907
 
                                Element.new "", self, @element.context
908
 
                        elsif not element.kind_of?(Element)
909
 
                                Element.new element, self, @element.context
910
 
                        else
911
 
                                @element << element
912
 
                                element.context = @element.context
913
 
                                element
914
 
                        end
915
 
                end
916
 
 
917
 
                alias :<< :add
918
 
 
919
 
                # Iterates through all of the child Elements, optionally filtering
920
 
                # them by a given XPath
921
 
                # 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 }
935
 
                end
936
 
                
937
 
                def collect( xpath=nil, &block )
938
 
                        collection = []
939
 
                        XPath::each( @element, xpath ) {|e| 
940
 
                                collection << yield(e)  if e.kind_of?(Element) 
941
 
                        }
942
 
                        collection
943
 
                end
944
 
                        
945
 
                def inject( xpath=nil, initial=nil, &block )
946
 
                        first = true
947
 
                        XPath::each( @element, xpath ) {|e|
948
 
                                if (e.kind_of? Element)
949
 
                                        if (first and initial == nil)
950
 
                                                initial = e
951
 
                                                first = false
952
 
                                        else
953
 
                                                initial = yield( initial, e ) if e.kind_of? Element
954
 
                                        end
955
 
                                end
956
 
                        }
957
 
                        initial
958
 
                end
959
 
 
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
964
 
                def size
965
 
                        count = 0
966
 
                        @element.each {|child| count+=1 if child.kind_of? Element }
967
 
                        count
968
 
                end
969
 
 
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
980
 
                        rv
981
 
                end
982
 
 
983
 
                private
984
 
                # Private helper class.  Removes quotes from quoted strings
985
 
                def literalize name
986
 
                        name = name[1..-2] if name[0] == ?' or name[0] == ?"               #'
987
 
                        name
988
 
                end
989
 
        end
990
 
 
991
 
        ########################################################################
992
 
        # ATTRIBUTES                                                           #
993
 
        ########################################################################
994
 
 
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
998
 
                # Constructor
999
 
                # element:: the Element of which this is an Attribute
1000
 
                def initialize element
1001
 
                        @element = element
1002
 
                end
1003
 
 
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.
1007
 
                # Returns:: 
1008
 
                #   the String value of the matching attribute, or +nil+ if no
1009
 
                #   matching attribute was found.
1010
 
                # 
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'
1014
 
                def [](name)
1015
 
                        attr = get_attribute(name)
1016
 
                        return attr.value unless attr.nil?
1017
 
                        return nil
1018
 
                end
1019
 
 
1020
 
                def to_a
1021
 
                        values.flatten
1022
 
                end
1023
 
 
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
1027
 
                def length
1028
 
                        c = 0
1029
 
                        each_attribute { c+=1 }
1030
 
                        c
1031
 
                end
1032
 
                alias :size :length
1033
 
 
1034
 
                # Itterates over the attributes of an Element.  Yields actual Attribute
1035
 
                # nodes, not String values.
1036
 
                # 
1037
 
                #  doc = Document.new '<a x="1" y="2"/>'
1038
 
                #  doc.root.attributes.each_attribute {|attr|
1039
 
                #    p attr.expanded_name+" => "+attr.value
1040
 
                #  }
1041
 
                def each_attribute # :yields: attribute
1042
 
                        each_value do |val|
1043
 
                                if val.kind_of? Attribute
1044
 
                                        yield val
1045
 
                                else
1046
 
                                        val.each_value { |atr| yield atr }
1047
 
                                end
1048
 
                        end
1049
 
                end
1050
 
 
1051
 
                # Itterates over each attribute of an Element, yielding the expanded name
1052
 
                # and value as a pair of Strings.
1053
 
                #
1054
 
                #  doc = Document.new '<a x="1" y="2"/>'
1055
 
                #  doc.root.attributes.each {|name, value| p name+" => "+value }
1056
 
                def each
1057
 
                        each_attribute do |attr|
1058
 
                                yield attr.expanded_name, attr.value
1059
 
                        end
1060
 
                end
1061
 
 
1062
 
                # Fetches an attribute
1063
 
                # name:: 
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 )
1073
 
                        if attr.nil?
1074
 
                                return nil if name.nil?
1075
 
                                # Look for prefix
1076
 
                                name =~ Namespace::NAMESPLIT
1077
 
                                prefix, n = $1, $2
1078
 
                                if prefix
1079
 
                                        attr = fetch( n, nil )
1080
 
                                        # check prefix
1081
 
                                        if attr == nil
1082
 
                                        elsif attr.kind_of? Attribute
1083
 
                                                return attr if prefix == attr.prefix
1084
 
                                        else
1085
 
                                                attr = attr[ prefix ]
1086
 
                                                return attr
1087
 
                                        end
1088
 
                                end
 
703
    # A private helper method
 
704
    def each_with_something( test, max=0, name=nil )
 
705
      num = 0
 
706
      child=nil
 
707
      @elements.each( name ){ |child|
 
708
        yield child if test.call(child) and num += 1
 
709
        return if max>0 and num == max
 
710
      }
 
711
    end
 
712
  end
 
713
 
 
714
  ########################################################################
 
715
  # ELEMENTS                                                             #
 
716
  ########################################################################
 
717
 
 
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.
 
722
  class Elements
 
723
    include Enumerable
 
724
    # Constructor
 
725
    # parent:: the parent Element
 
726
    def initialize parent
 
727
      @element = parent
 
728
    end
 
729
 
 
730
    # Fetches a child element.  Filters only Element children, regardless of
 
731
    # the XPath match.
 
732
    # index:: 
 
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.
 
742
    # name:: 
 
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
 
755
        num = 0
 
756
        child = nil
 
757
        @element.find { |child|
 
758
          child.kind_of? Element and
 
759
          (name.nil? ? true : child.has_name?( name )) and 
 
760
          (num += 1) == index
 
761
        }
 
762
      else
 
763
        return XPath::first( @element, index )
 
764
        #{ |element| 
 
765
        #       return element if element.kind_of? Element
 
766
        #}
 
767
        #return nil
 
768
      end
 
769
    end
 
770
 
 
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 []().
 
774
    # element:: 
 
775
    #   The element to replace the existing element with
 
776
    #   the previous element
 
777
    # Returns:: nil if no previous element was found.
 
778
    #
 
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]
 
786
      if previous.nil?
 
787
        @element.add element
 
788
      else
 
789
        previous.replace_with element
 
790
      end
 
791
      return previous
 
792
    end
 
793
 
 
794
    # Returns +true+ if there are no +Element+ children, +false+ otherwise
 
795
    def empty?
 
796
      @element.find{ |child| child.kind_of? Element}.nil?
 
797
    end
 
798
 
 
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
 
802
    def index element
 
803
      rv = 0
 
804
      found = @element.find do |child| 
 
805
        child.kind_of? Element and
 
806
        (rv += 1) and
 
807
        child == element
 
808
      end
 
809
      return rv if found == element
 
810
      return -1
 
811
    end
 
812
 
 
813
    # Deletes a child Element
 
814
    # 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/>
 
824
    def delete element
 
825
      if element.kind_of? Element
 
826
        @element.delete element
 
827
      else
 
828
        el = self[element]
 
829
        el.remove if el
 
830
      end
 
831
    end
 
832
 
 
833
    # Removes multiple elements.  Filters for Element children, regardless of
 
834
    # XPath matching.
 
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 )
 
840
      rv = []
 
841
      XPath::each( @element, xpath) {|element| 
 
842
        rv << element if element.kind_of? Element
 
843
      }
 
844
      rv.each do |element|
 
845
        @element.delete element
 
846
        element.remove
 
847
      end
 
848
      return rv
 
849
    end
 
850
 
 
851
    # Adds an element
 
852
    # 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>
 
860
    def add element=nil
 
861
      rv = nil
 
862
      if element.nil?
 
863
        Element.new "", self, @element.context
 
864
      elsif not element.kind_of?(Element)
 
865
        Element.new element, self, @element.context
 
866
      else
 
867
        @element << element
 
868
        element.context = @element.context
 
869
        element
 
870
      end
 
871
    end
 
872
 
 
873
    alias :<< :add
 
874
 
 
875
    # Iterates through all of the child Elements, optionally filtering
 
876
    # them by a given XPath
 
877
    # 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 }
 
891
    end
 
892
 
 
893
    def collect( xpath=nil, &block )
 
894
      collection = []
 
895
      XPath::each( @element, xpath ) {|e| 
 
896
        collection << yield(e)  if e.kind_of?(Element) 
 
897
      }
 
898
      collection
 
899
    end
 
900
 
 
901
    def inject( xpath=nil, initial=nil, &block )
 
902
      first = true
 
903
      XPath::each( @element, xpath ) {|e|
 
904
        if (e.kind_of? Element)
 
905
          if (first and initial == nil)
 
906
            initial = e
 
907
            first = false
 
908
          else
 
909
            initial = yield( initial, e ) if e.kind_of? Element
 
910
          end
 
911
        end
 
912
      }
 
913
      initial
 
914
    end
 
915
 
 
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
 
920
    def size
 
921
      count = 0
 
922
      @element.each {|child| count+=1 if child.kind_of? Element }
 
923
      count
 
924
    end
 
925
 
 
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
 
936
      rv
 
937
    end
 
938
 
 
939
    private
 
940
    # Private helper class.  Removes quotes from quoted strings
 
941
    def literalize name
 
942
      name = name[1..-2] if name[0] == ?' or name[0] == ?"               #'
 
943
      name
 
944
    end
 
945
  end
 
946
 
 
947
  ########################################################################
 
948
  # ATTRIBUTES                                                           #
 
949
  ########################################################################
 
950
 
 
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
 
954
    # Constructor
 
955
    # element:: the Element of which this is an Attribute
 
956
    def initialize element
 
957
      @element = element
 
958
    end
 
959
 
 
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.
 
963
    # Returns:: 
 
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).
 
967
    # 
 
968
    #  doc = Document.new "<a foo:att='1' bar:att='2' att='&lt;'/>"
 
969
    #  doc.root.attributes['att']         #-> '<'
 
970
    #  doc.root.attributes['bar:att']     #-> '2'
 
971
    def [](name)
 
972
      attr = get_attribute(name)
 
973
      return attr.value unless attr.nil?
 
974
      return nil
 
975
    end
 
976
 
 
977
    def to_a
 
978
      values.flatten
 
979
    end
 
980
 
 
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
 
984
    def length
 
985
      c = 0
 
986
      each_attribute { c+=1 }
 
987
      c
 
988
    end
 
989
    alias :size :length
 
990
 
 
991
    # Itterates over the attributes of an Element.  Yields actual Attribute
 
992
    # nodes, not String values.
 
993
    # 
 
994
    #  doc = Document.new '<a x="1" y="2"/>'
 
995
    #  doc.root.attributes.each_attribute {|attr|
 
996
    #    p attr.expanded_name+" => "+attr.value
 
997
    #  }
 
998
    def each_attribute # :yields: attribute
 
999
      each_value do |val|
 
1000
        if val.kind_of? Attribute
 
1001
          yield val
 
1002
        else
 
1003
          val.each_value { |atr| yield atr }
 
1004
        end
 
1005
      end
 
1006
    end
 
1007
 
 
1008
    # Itterates over each attribute of an Element, yielding the expanded name
 
1009
    # and value as a pair of Strings.
 
1010
    #
 
1011
    #  doc = Document.new '<a x="1" y="2"/>'
 
1012
    #  doc.root.attributes.each {|name, value| p name+" => "+value }
 
1013
    def each
 
1014
      each_attribute do |attr|
 
1015
        yield attr.expanded_name, attr.value
 
1016
      end
 
1017
    end
 
1018
 
 
1019
    # Fetches an attribute
 
1020
    # name:: 
 
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 )
 
1030
      if attr.nil?
 
1031
        return nil if name.nil?
 
1032
        # Look for prefix
 
1033
        name =~ Namespace::NAMESPLIT
 
1034
        prefix, n = $1, $2
 
1035
        if prefix
 
1036
          attr = fetch( n, nil )
 
1037
          # check prefix
 
1038
          if attr == nil
 
1039
          elsif attr.kind_of? Attribute
 
1040
            return attr if prefix == attr.prefix
 
1041
          else
 
1042
            attr = attr[ prefix ]
 
1043
            return attr
 
1044
          end
 
1045
        end
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
1095
 
                                end
1096
 
                                return nil
1097
 
                        end
1098
 
                        if attr.kind_of? Hash
1099
 
                                attr = attr[ @element.prefix ]
1100
 
                        end
1101
 
                        return attr
1102
 
                end
1103
 
 
1104
 
                # Sets an attribute, overwriting any existing attribute value by the
1105
 
                # same name.  Namespace is significant.
1106
 
                # name:: the name of the attribute
1107
 
                # value:: 
1108
 
                #   (optional) If supplied, the value of the attribute.  If
1109
 
                #   nil, any existing matching attribute is deleted.
1110
 
                # Returns:: 
1111
 
                #   Owning element
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)
1119
 
                                delete attr
1120
 
                                return
1121
 
                        end
1122
 
                        value = Attribute.new(name, value) unless value.kind_of? Attribute
1123
 
                        value.element = @element
1124
 
                        old_attr = fetch(value.name, nil)
1125
 
                        if old_attr.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 }
1141
 
                        else
1142
 
                                store value.name, value
1143
 
                        end
1144
 
                        return @element
1145
 
                end
1146
 
 
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']
1153
 
                def prefixes
1154
 
                        ns = []
1155
 
                        each_attribute do |attribute|
1156
 
                                ns << attribute.name if attribute.prefix == 'xmlns'
1157
 
                        end
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 {
1162
 
                                        |attribute|
1163
 
                                        ns << attribute.name if attribute.prefix == 'xmlns'
1164
 
                                }
1165
 
                        end
1166
 
                        ns
1167
 
                end
1168
 
 
1169
 
                def namespaces
1170
 
                        namespaces = {}
1171
 
                        each_attribute do |attribute|
1172
 
                                namespaces[attribute.name] = attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns'
1173
 
                        end
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 {
1178
 
                                        |attribute|
1179
 
                                        namespaces[attribute.name] = attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns'
1180
 
                                }
1181
 
                        end
1182
 
                        namespaces
1183
 
                end
1184
 
 
1185
 
                # Removes an attribute
1186
 
                # 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 )
1196
 
                        name = nil
1197
 
                        prefix = nil
1198
 
                        if attribute.kind_of? Attribute
1199
 
                                name = attribute.name
1200
 
                                prefix = attribute.prefix
1201
 
                        else
1202
 
                                attribute =~ Namespace::NAMESPLIT
1203
 
                                prefix, name = $1, $2
1204
 
                                prefix = '' unless prefix
1205
 
                        end
1206
 
                        old = fetch(name, nil)
1207
 
                        attr = nil
1208
 
                        if old.kind_of? Hash # the supplied attribute is one of many
1209
 
                                attr = old.delete(prefix)
1210
 
                                if old.size == 1
1211
 
                                        repl = nil
1212
 
                                        old.each_value{|v| repl = v}
1213
 
                                        store name, repl
1214
 
                                end
1215
 
                        elsif old.nil?
1216
 
                                return @element
1217
 
                        else # the supplied attribute is a top-level one
1218
 
                                attr = old
1219
 
                                res = super(name)
1220
 
                        end
1221
 
                        @element
1222
 
                end
1223
 
 
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
1229
 
                end
1230
 
 
1231
 
                alias :<< :add
1232
 
 
1233
 
                # Deletes all attributes matching a name.  Namespaces are significant.
1234
 
                # name:: 
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 )
1238
 
                        rv = []
1239
 
                        each_attribute { |attribute| 
1240
 
                                rv << attribute if attribute.expanded_name == name
1241
 
                        }
1242
 
                        rv.each{ |attr| attr.remove }
1243
 
                        return rv
1244
 
                end
1245
 
    
 
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
 
1052
        end
 
1053
        return nil
 
1054
      end
 
1055
      if attr.kind_of? Hash
 
1056
        attr = attr[ @element.prefix ]
 
1057
      end
 
1058
      return attr
 
1059
    end
 
1060
 
 
1061
    # Sets an attribute, overwriting any existing attribute value by the
 
1062
    # same name.  Namespace is significant.
 
1063
    # name:: the name of the attribute
 
1064
    # value:: 
 
1065
    #   (optional) If supplied, the value of the attribute.  If
 
1066
    #   nil, any existing matching attribute is deleted.
 
1067
    # Returns:: 
 
1068
    #   Owning element
 
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)
 
1076
        delete attr
 
1077
        return
 
1078
      end
 
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 )
 
1083
        else
 
1084
          value = Text::normalize( value, nil )
 
1085
        end
 
1086
        value = Attribute.new(name, value)
 
1087
      end
 
1088
      value.element = @element
 
1089
      old_attr = fetch(value.name, nil)
 
1090
      if old_attr.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 }
 
1106
      else
 
1107
        store value.name, value
 
1108
      end
 
1109
      return @element
 
1110
    end
 
1111
 
 
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']
 
1118
    def prefixes
 
1119
      ns = []
 
1120
      each_attribute do |attribute|
 
1121
        ns << attribute.name if attribute.prefix == 'xmlns'
 
1122
      end
 
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 {
 
1127
          |attribute|
 
1128
          ns << attribute.name if attribute.prefix == 'xmlns'
 
1129
        }
 
1130
      end
 
1131
      ns
 
1132
    end
 
1133
 
 
1134
    def namespaces
 
1135
      namespaces = {}
 
1136
      each_attribute do |attribute|
 
1137
        namespaces[attribute.name] = attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns'
 
1138
      end
 
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 {
 
1143
          |attribute|
 
1144
          namespaces[attribute.name] = attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns'
 
1145
        }
 
1146
      end
 
1147
      namespaces
 
1148
    end
 
1149
 
 
1150
    # Removes an attribute
 
1151
    # 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 )
 
1161
      name = nil
 
1162
      prefix = nil
 
1163
      if attribute.kind_of? Attribute
 
1164
        name = attribute.name
 
1165
        prefix = attribute.prefix
 
1166
      else
 
1167
        attribute =~ Namespace::NAMESPLIT
 
1168
        prefix, name = $1, $2
 
1169
        prefix = '' unless prefix
 
1170
      end
 
1171
      old = fetch(name, nil)
 
1172
      attr = nil
 
1173
      if old.kind_of? Hash # the supplied attribute is one of many
 
1174
        attr = old.delete(prefix)
 
1175
        if old.size == 1
 
1176
          repl = nil
 
1177
          old.each_value{|v| repl = v}
 
1178
          store name, repl
 
1179
        end
 
1180
      elsif old.nil?
 
1181
        return @element
 
1182
      else # the supplied attribute is a top-level one
 
1183
        attr = old
 
1184
        res = super(name)
 
1185
      end
 
1186
      @element
 
1187
    end
 
1188
 
 
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
 
1194
    end
 
1195
 
 
1196
    alias :<< :add
 
1197
 
 
1198
    # Deletes all attributes matching a name.  Namespaces are significant.
 
1199
    # name:: 
 
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 )
 
1203
      rv = []
 
1204
      each_attribute { |attribute| 
 
1205
        rv << attribute if attribute.expanded_name == name
 
1206
      }
 
1207
      rv.each{ |attr| attr.remove }
 
1208
      return rv
 
1209
    end
 
1210
 
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.
1251
1216
    def get_attribute_ns(namespace, name)
1252
1217
      each_attribute() { |attribute|
1253
1218
        if name == attribute.name &&
1254
 
           namespace == attribute.namespace()
 
1219
          namespace == attribute.namespace()
1255
1220
          return attribute
1256
1221
        end
1257
1222
      }
1258
1223
      nil
1259
1224
    end
1260
 
        end
 
1225
  end
1261
1226
end