#monkeypatch kludge to get Parser to suppress "ignored element" warnings
#generated when mumboe-soap4r consumes wsdl that asp.net serves
#TODO: find a cleaner way to do this
class WSDL::Parser
old_initialize = instance_method(:initialize)
define_method(:initialize) do |*args, &block|
old_initialize.bind(self).call(*args, &block)
#set up QNames to ignore
names = ["binding", "operation", "body", "header", "address"]
namespace = "http://schemas.xmlsoap.org/wsdl/soap12/"
ignored = names.inject({}) do |ignored, name|
elename = XSD::QName.new(namespace, name)
ignored.update(elename => elename)
end
instance_variable_set(:@ignored, ignored)
end
end
how i got there
yesterday i implemented a kludge to suppress these warnings, but it was a bit broad. it would suppress everything printed to stderr during
decode_tag. i refined that method to get to the code above. to make yesterday's kludge, i had to read some of the code in /var/lib/gems/1.9.0/gems/mumboe-soap4r-1.5.8.3/lib/wsdl/parser.rb. i noticed the use of @ignored to prevent duplicate warnings. i figured i could populate it with the things i don't care about before doing any wsdl consumption. turns out that worked.what warnings to suppress
so i needed to print out the contents of
@ignored. at first, i tried monkeypatching decode_tag to print out stuff as it was added to @ignored but that resulted in many duplicates. ironically, avoiding this duplication is exactly the reason for having @ignored. instead i went to the place where decode_tag was first called and monkeypatched that method (start_element).deeper
i found that
@ignored was populated with XSD::QNames. so i went to .../lib/xsd/qname.rb to find out how to create them and how they were compared. QName creation requires a namespace and a name. so i needed to have each key and value in @ignore print its namespace and name. fortunately these were readable attributes, so i just had to modify start_element in wsdl/parser.rbback to wsdl/parser.rb
so i landed on this monkeypatch, which is the slickness:
class WSDL::Parser
def parse(string_or_readable)
@parsestack = []
@lastnode = nil
@textbuf = ''
@parser.do_parse(string_or_readable)
puts
puts "ignored = {"
@ignored.each do |key, value|
puts "XSD::QName.new(\"#{key.namespace}\", \"#{key.name}\") =>"
puts "XSD::QName.new(\"#{value.namespace}\", \"#{value.name}\"),"
end
puts "}"
@lastnode
end
end
that's right, it prints out the information i need formatted as ruby code so i just copy-pasta'd the output back into my code and massaged it a little.
kinda sorta almost
this result was almost what i wanted. here's the code:
class WSDL::Parser
old_initialize = instance_method(:initialize)
define_method(:initialize) do |*args, &block|
old_initialize.bind(self).call(*args, &block)
ignored = {
XSD::QName.new("http://schemas.xmlsoap.org/wsdl/soap12/", "binding") =>
XSD::QName.new("http://schemas.xmlsoap.org/wsdl/soap12/", "binding"),
XSD::QName.new("http://schemas.xmlsoap.org/wsdl/soap12/", "operation") =>
XSD::QName.new("http://schemas.xmlsoap.org/wsdl/soap12/", "operation"),
XSD::QName.new("http://schemas.xmlsoap.org/wsdl/soap12/", "body") =>
XSD::QName.new("http://schemas.xmlsoap.org/wsdl/soap12/", "body"),
XSD::QName.new("http://schemas.xmlsoap.org/wsdl/soap12/", "header") =>
XSD::QName.new("http://schemas.xmlsoap.org/wsdl/soap12/", "header"),
XSD::QName.new("http://schemas.xmlsoap.org/wsdl/soap12/", "address") =>
XSD::QName.new("http://schemas.xmlsoap.org/wsdl/soap12/", "address"),
}
instance_variable_set(:@ignored, ignored)
end
end
this was nearly good enough for me to stop. it suppressed exactly what i knew about. nothing more, nothing less. but that's a lot of text. and a lot of it is repepetetetivive. and every
QName is created twice, so technically it wasn't populating @ignored in exactly the same way as decode_tag does. and what if the namespace needs changing, or i want to add another name? i felt this would be annoying to maintain.another step
so i used a variable to store the namespace and used a variable to store each
QName.here's that code:
class WSDL::Parser
old_initialize = instance_method(:initialize)
define_method(:initialize) do |*args, &block|
old_initialize.bind(self).call(*args, &block)
namespace = "http://schemas.xmlsoap.org/wsdl/soap12/"
binding = XSD::QName.new(namespace, "binding")
operation = XSD::QName.new(namespace, "operation")
body = XSD::QName.new(namespace, "body")
header = XSD::QName.new(namespace, "header")
address = XSD::QName.new(namespace, "address")
ignored = {
binding => binding,
operation => operation,
body => body,
header => header,
address => address,
}
instance_variable_set(:@ignored, ignored)
end
end
all right. better. but ... augh, i can't let it be that way. i mean, come on! for every ignored thing, the
name of the ignored thing shows up 4 times, on 2 different lines that aren't right next to each other. still not DRY enough. i figured i could create an array with symbols and do something with that. turns out that 3 of the 4 times a name showed up, it was just a temporary variable and the true essence could be summed up more generically so that any particular name was only given once! so i messed with Enumerable#each and finally landed on a workable solution using Enumberable#inject.final code again (so you don't have to scroll up):
#monkeypatch kludge to get Parser to suppress "ignored element" warnings
#generated when mumboe-soap4r consumes wsdl that asp.net serves
#TODO: find a cleaner way to do this
class WSDL::Parser
old_initialize = instance_method(:initialize)
define_method(:initialize) do |*args, &block|
old_initialize.bind(self).call(*args, &block)
#set up QNames to ignore
names = ["binding", "operation", "body", "header", "address"]
namespace = "http://schemas.xmlsoap.org/wsdl/soap12/"
ignored = names.inject({}) do |ignored, name|
elename = XSD::QName.new(namespace, name)
ignored.update(elename => elename)
end
instance_variable_set(:@ignored, ignored)
end
end
now when someone wants to add a new ignored name, they just add it to the list of names that get ignored! one place! <comma> <space> <quotation mark> <name> <quotation mark>. the rest of the work is done generically, automatically. and i think reading this is easier than reading the intermediate steps. there's less to read. less dense. easier to update. a good kludge.
so i'm glad that's done. ... or is it?
it's still a kludge
ultimately, this is neat and all, but it's still a kludge. i need to figure out why these warnings are generated at all. i don't want to have to read a wsdl, or read more code involved in the process of parsing a wsdl. a brief look tells me it must be that
parent.parse_element(elename) returns nil in some case. i don't know what type parent is and i don't feel like searching any further right now.
I tried this with
ReplyDeleteruby 1.8.6 (2008-08-08 patchlevel 286) [x86_64-linux] and received an error:
syntax error, unexpected ',', expecting '|' (SyntaxError)
define_method(:initialize) do |*args, &block|
What version(s) of ruby will work with your code here?
zero7% ruby1.9 --version
ReplyDeleteruby 1.9.0 (2008-10-04 revision 19669) [x86_64-linux]
zero7% ruby --version
ruby 1.8.7 (2010-01-10 patchlevel 249) [x86_64-linux]
i don't have a 1.8.6 lying around to try this. are you using redhat/centOS? i've had success compiling later versions of ruby from the sources