G
Gavin Kistner
I wrote a little code generation system for my company. It's a small
script that reads in an XML file and an ERB template, feeds the XML file
to the template, and writes out the result to a file.
=20
To help those in the company who don't know XPath, I extended REXML with
a couple convenience methods that let you search for elements by name
and/or attributes.
=20
class REXML::Element
# Find all descendant nodes with a specified tag name and/or
attributes
def find( tag_name=3D'*', attributes_to_match=3D{} )
self.each_element(
".//#{REXML::Element.xpathfor(tag_name,attributes_to_match)}" ){}
end
# Find all child nodes with a specified tag name and/or attributes
def kids( tag_name=3D'*', attributes_to_match=3D{} )
self.each_element(
"./#{REXML::Element.xpathfor(tag_name,attributes_to_match)}" ){}
end
...
end
This worked very nicely. Template writers could simply do something
like:
In this file I see the following classes: <%=3D
root.find( 'class' ).map{ |el| el.attributes[ 'name' ] }.join( ', '
)
%>
All was fine under 1.8.4.
I just upgraded to 1.8.5 and all templates using #find broke. After a
bit of sleuthing, it looks like REXML changed the Document#root method
in a way that caused it to call Elements#[], and that method uses
@element.find, mixed in from Enumerable. My Element#find was overriding
Enumerable#find, and all hell broke loose as a result.
Moral of the story (pick one or more):
[ ] Don't extend someone else's class with common method names
[ ] Don't upgrade your Ruby environment unless you need to
script that reads in an XML file and an ERB template, feeds the XML file
to the template, and writes out the result to a file.
=20
To help those in the company who don't know XPath, I extended REXML with
a couple convenience methods that let you search for elements by name
and/or attributes.
=20
class REXML::Element
# Find all descendant nodes with a specified tag name and/or
attributes
def find( tag_name=3D'*', attributes_to_match=3D{} )
self.each_element(
".//#{REXML::Element.xpathfor(tag_name,attributes_to_match)}" ){}
end
# Find all child nodes with a specified tag name and/or attributes
def kids( tag_name=3D'*', attributes_to_match=3D{} )
self.each_element(
"./#{REXML::Element.xpathfor(tag_name,attributes_to_match)}" ){}
end
...
end
This worked very nicely. Template writers could simply do something
like:
In this file I see the following classes: <%=3D
root.find( 'class' ).map{ |el| el.attributes[ 'name' ] }.join( ', '
)
%>
All was fine under 1.8.4.
I just upgraded to 1.8.5 and all templates using #find broke. After a
bit of sleuthing, it looks like REXML changed the Document#root method
in a way that caused it to call Elements#[], and that method uses
@element.find, mixed in from Enumerable. My Element#find was overriding
Enumerable#find, and all hell broke loose as a result.
Moral of the story (pick one or more):
[ ] Don't extend someone else's class with common method names
[ ] Don't upgrade your Ruby environment unless you need to