@@ -2240,15 +2240,10 @@ def length
22402240 # [REXML::Attribute, bar:att='2']
22412241 # [REXML::Attribute, att='<']
22422242 #
2243- def each_attribute # :yields: attribute
2244- return to_enum ( __method__ ) unless block_given?
2245- each_value do |val |
2246- if val . kind_of? Attribute
2247- yield val
2248- else
2249- val . each_value { |atr | yield atr }
2250- end
2251- end
2243+ # This method doesn't iterate attributes in the DTD. This may be a bug.
2244+ #
2245+ def each_attribute ( &block ) # :yields: attribute
2246+ each_own_attribute ( &block )
22522247 end
22532248
22542249 # :call-seq:
@@ -2273,6 +2268,8 @@ def each_attribute # :yields: attribute
22732268 # ["bar:att", "2"]
22742269 # ["att", "<"]
22752270 #
2271+ # This method doesn't iterate attributes in the DTD. This may be a bug.
2272+ #
22762273 def each
22772274 return to_enum ( __method__ ) unless block_given?
22782275 each_attribute do |attr |
@@ -2300,36 +2297,7 @@ def each
23002297 # attrs.get_attribute('nosuch') # => nil
23012298 #
23022299 def get_attribute ( name )
2303- attr = fetch ( name , nil )
2304- if attr . nil?
2305- return nil if name . nil?
2306- # Look for prefix
2307- name =~ Namespace ::NAMESPLIT
2308- prefix , n = $1, $2
2309- if prefix
2310- attr = fetch ( n , nil )
2311- # check prefix
2312- if attr == nil
2313- elsif attr . kind_of? Attribute
2314- return attr if prefix == attr . prefix
2315- else
2316- attr = attr [ prefix ]
2317- return attr
2318- end
2319- end
2320- doctype = @element . document &.doctype
2321- if doctype
2322- expn = @element . expanded_name
2323- expn = doctype . name if expn . size == 0
2324- attr_val = doctype . attribute_of ( expn , name )
2325- return Attribute . new ( name , attr_val ) if attr_val
2326- end
2327- return nil
2328- end
2329- if attr . kind_of? Hash
2330- attr = attr [ @element . prefix ]
2331- end
2332- attr
2300+ fetch ( name , nil ) || attlist_attributes &.[]( name )
23332301 end
23342302
23352303 # :call-seq:
@@ -2357,8 +2325,7 @@ def get_attribute( name )
23572325 #
23582326 def []=( name , value )
23592327 if value . nil? # Delete the named attribute
2360- attr = get_attribute ( name )
2361- delete attr
2328+ delete name
23622329 return
23632330 end
23642331
@@ -2372,17 +2339,7 @@ def []=( name, value )
23722339 value = Attribute . new ( name , value )
23732340 end
23742341 value . element = @element
2375- old_attr = fetch ( value . name , nil )
2376- if old_attr . nil?
2377- store ( value . name , value )
2378- elsif old_attr . kind_of? Hash
2379- old_attr [ value . prefix ] = value
2380- elsif old_attr . prefix != value . prefix
2381- store value . name , { old_attr . prefix => old_attr ,
2382- value . prefix => value }
2383- else
2384- store value . name , value
2385- end
2342+ store name , value
23862343 @element
23872344 end
23882345
@@ -2398,20 +2355,7 @@ def []=( name, value )
23982355 # d.root.attributes.prefixes # => ["x", "y"]
23992356 #
24002357 def prefixes
2401- ns = [ ]
2402- each_attribute do |attribute |
2403- ns << attribute . name if attribute . prefix == 'xmlns'
2404- end
2405- doctype = @element . document &.doctype
2406- if doctype
2407- expn = @element . expanded_name
2408- expn = doctype . name if expn . size == 0
2409- doctype . attributes_of ( expn ) . each {
2410- |attribute |
2411- ns << attribute . name if attribute . prefix == 'xmlns'
2412- }
2413- end
2414- ns
2358+ namespaces . keys - [ 'xmlns' ]
24152359 end
24162360
24172361 # :call-seq:
@@ -2425,17 +2369,8 @@ def prefixes
24252369 #
24262370 def namespaces
24272371 namespaces = { }
2428- each_attribute do |attribute |
2429- namespaces [ attribute . name ] = attribute . value if attribute . prefix == 'xmlns' or attribute . name == 'xmlns'
2430- end
2431- doctype = @element . document &.doctype
2432- if doctype
2433- expn = @element . expanded_name
2434- expn = doctype . name if expn . size == 0
2435- doctype . attributes_of ( expn ) . each {
2436- |attribute |
2437- namespaces [ attribute . name ] = attribute . value if attribute . prefix == 'xmlns' or attribute . name == 'xmlns'
2438- }
2372+ each_effective_attribute do |attribute |
2373+ namespaces [ attribute . name ] = attribute . value if attribute . namespace_declaration?
24392374 end
24402375 namespaces
24412376 end
@@ -2468,28 +2403,11 @@ def namespaces
24682403 # attrs.delete(attr) # => <ele att='<'/> # => <ele att='<'/>
24692404 # attrs.delete(attr) # => <ele att='<'/> # => <ele/>
24702405 #
2471- def delete ( attribute )
2472- name = nil
2473- prefix = nil
2474- if attribute . kind_of? Attribute
2475- name = attribute . name
2476- prefix = attribute . prefix
2477- else
2478- attribute =~ Namespace ::NAMESPLIT
2479- prefix , name = $1, $2
2480- prefix = '' unless prefix
2481- end
2482- old = fetch ( name , nil )
2483- if old . kind_of? Hash # the supplied attribute is one of many
2484- old . delete ( prefix )
2485- if old . size == 1
2486- repl = nil
2487- old . each_value { |v | repl = v }
2488- store name , repl
2489- end
2490- elsif old # the supplied attribute is a top-level one
2491- super ( name )
2492- end
2406+ def delete ( attribute_or_name )
2407+ key = attribute_or_name
2408+ key = attribute_or_name . expanded_name if attribute_or_name . kind_of? Attribute
2409+ super ( key )
2410+
24932411 @element
24942412 end
24952413
@@ -2514,7 +2432,7 @@ def delete( attribute )
25142432 # attrs.include?('baz') # => true
25152433 #
25162434 def add ( attribute )
2517- self [ attribute . name ] = attribute
2435+ self [ attribute . expanded_name ] = attribute
25182436 end
25192437
25202438 alias :<< :add
@@ -2527,21 +2445,26 @@ def add( attribute )
25272445 #
25282446 # xml_string = <<-EOT
25292447 # <root xmlns:foo="http://foo" xmlns:bar="http://bar">
2530- # <ele foo:att='1' bar: att='2' att='< '/>
2448+ # <ele foo:att='1' att='<' foo:other=' 2' other='3 '/>
25312449 # </root>
25322450 # EOT
25332451 # d = REXML::Document.new(xml_string)
2534- # ele = d.root.elements['//ele'] # => <a foo:att='1' bar: att='2' att='< '/>
2452+ # ele = d.root.elements['//ele'] # => <a foo:att='1' att='<' foo:other=' 2' other='3 '/>
25352453 # attrs = ele.attributes
2536- # attrs.delete_all('att') # => [att='<']
2454+ # attrs.delete_all('att') # => [foo:att='1', att='<']
2455+ # attrs.each_attribute.map(&:expanded_name) #=> ['foo:other', 'other']
25372456 #
25382457 def delete_all ( name )
2539- rv = [ ]
2540- each_attribute { |attribute |
2541- rv << attribute if attribute . expanded_name == name
2542- }
2543- rv . each { |attr | attr . remove }
2544- rv
2458+ attributes = each_attribute . select do |attribute |
2459+ # For <element xmlns:foo="url" ns:foo="value"/>
2460+ # delete_all('foo') should not delete xmlns:foo="url"
2461+ # because it is a namespace declaration, not a normal attribute.
2462+ ( !attribute . namespace_declaration? && attribute . name == name ) || attribute . expanded_name == name
2463+ end
2464+ attributes . each do |attribute |
2465+ delete attribute . expanded_name
2466+ end
2467+ attributes
25452468 end
25462469
25472470 # :call-seq:
@@ -2562,17 +2485,51 @@ def delete_all( name )
25622485 # attrs.get_attribute_ns('http://foo', 'nosuch') # => nil
25632486 #
25642487 def get_attribute_ns ( namespace , name )
2565- result = nil
2566- each_attribute ( ) { |attribute |
2567- if name == attribute . name &&
2568- namespace == attribute . namespace ( ) &&
2569- ( !namespace . empty? || !attribute . fully_expanded_name . index ( ':' ) )
2570- # foo will match xmlns:foo, but only if foo isn't also an attribute
2571- result = attribute if !result or !namespace . empty? or
2572- !attribute . fully_expanded_name . index ( ':' )
2488+ each_effective_attribute . find do |attribute |
2489+ if attribute . namespace_declaration?
2490+ # namespace declarations are not considered as attributes in this method.
2491+ # For example: <elem1 xmlns="" xmlns:foo="url" /><elem2 xmlns="bar" xmlns:foo="url" />
2492+ # Both elem1.get_attribute_ns('', 'foo') and elem2.get_attribute_ns('bar', 'foo')
2493+ # should not match.
2494+ false
2495+ elsif namespace . empty? && !attribute . prefix . empty?
2496+ # If prefix is present, namespace url shouldn't be empty, so it never matches.
2497+ # For example, <elem foo:att="value" />, even if attribute.namespace returns '',
2498+ # it just means that namespace lookup failed, it shouldn't match.
2499+ false
2500+ else
2501+ name == attribute . name && namespace == attribute . namespace ( )
25732502 end
2574- }
2575- result
2503+ end
2504+ end
2505+
2506+ private
2507+
2508+ # Iterates over the attributes of the element and those in the DTD.
2509+ # If the element has an attribute with the same name as one in the DTD,
2510+ # the element's attribute takes precedence.
2511+ def each_effective_attribute
2512+ return to_enum ( __method__ ) unless block_given?
2513+ attr = attlist_attributes
2514+ attr = attr ? attr . merge ( self ) : self
2515+ attr . each_value do |attribute |
2516+ yield attribute
2517+ end
2518+ end
2519+
2520+ alias each_own_attribute each_value
2521+ private :each_own_attribute
2522+
2523+ def attlist_attributes
2524+ doctype = @element . document &.doctype
2525+ if doctype
2526+ expn = @element . is_a? ( Document ) ? doctype . name : @element . expanded_name
2527+ doctype . attribute_declarations_of ( expn ) . map do |key , value |
2528+ attr = Attribute . new ( key , value )
2529+ attr . element = @element
2530+ [ key , attr ]
2531+ end . to_h
2532+ end
25762533 end
25772534 end
25782535end
0 commit comments