2 # Parser for XML-RPC call and response
4 # Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
6 # $Id: parser.rb 19657 2008-10-01 13:46:53Z mame $
11 require "xmlrpc/base64"
12 require "xmlrpc/datetime"
15 # add some methods to NQXML::Node
20 @children.delete(node)
33 if @entity.instance_of? NQXML::Text then :TEXT
34 elsif @entity.instance_of? NQXML::Comment then :COMMENT
35 #elsif @entity.instance_of? NQXML::Element then :ELEMENT
36 elsif @entity.instance_of? NQXML::Tag then :ELEMENT
42 #TODO: error when wrong Entity-type
46 #TODO: error when wrong Entity-type
54 class FaultException < StandardError
55 attr_reader :faultCode, :faultString
57 alias message faultString
59 def initialize(faultCode, faultString)
60 @faultCode = faultCode
61 @faultString = faultString
66 {"faultCode" => @faultCode, "faultString" => @faultString}
80 raise "RPC-value of type boolean is wrong"
88 def self.dateTime(str)
90 when /^(-?\d\d\d\d)-?(\d\d)-?(\d\d)T(\d\d):(\d\d):(\d\d)(?:Z|([+-])(\d\d):?(\d\d))?$/
91 a = [$1, $2, $3, $4, $5, $6].collect{|i| i.to_i}
93 ofs = $8.to_i*3600 + $9.to_i*60
95 utc = Time.utc(*a) + ofs
96 a = [ utc.year, utc.month, utc.day, utc.hour, utc.min, utc.sec ]
98 XMLRPC::DateTime.new(*a)
99 when /^(-?\d\d)-?(\d\d)-?(\d\d)T(\d\d):(\d\d):(\d\d)(Z|([+-]\d\d):(\d\d))?$/
100 a = [$1, $2, $3, $4, $5, $6].collect{|i| i.to_i}
107 ofs = $8.to_i*3600 + $9.to_i*60
108 ofs = -ofs if $7=='+'
109 utc = Time.utc(*a) + ofs
110 a = [ utc.year, utc.month, utc.day, utc.hour, utc.min, utc.sec ]
112 XMLRPC::DateTime.new(*a)
114 raise "wrong dateTime.iso8601 format " + str
119 XMLRPC::Base64.decode(str)
122 def self.struct(hash)
123 # convert to marhalled object
124 klass = hash["___class___"]
125 if klass.nil? or Config::ENABLE_MARSHALLING == false
130 klass.split("::").each {|const| mod = mod.const_get(const.strip)}
134 hash.delete "___class___"
135 hash.each {|key, value|
136 obj.instance_variable_set("@#{ key }", value) if key =~ /^([\w_][\w_0-9]*)$/
146 if hash.kind_of? Hash and hash.size == 2 and
147 hash.has_key? "faultCode" and hash.has_key? "faultString" and
148 hash["faultCode"].kind_of? Integer and hash["faultString"].kind_of? String
150 XMLRPC::FaultException.new(hash["faultCode"], hash["faultString"])
152 raise "wrong fault-structure: #{hash.inspect}"
160 class AbstractTreeParser
162 def parseMethodResponse(str)
163 methodResponse_document(createCleanedTree(str))
166 def parseMethodCall(str)
167 methodCall_document(createCleanedTree(str))
173 # remove all whitespaces but in the tags i4, int, boolean....
176 def removeWhitespacesAndComments(node)
178 childs = node.childNodes.to_a
183 unless %w(i4 int boolean string double dateTime.iso8601 base64).include? node.nodeName
185 if node.nodeName == "value"
186 if not node.childNodes.to_a.detect {|n| _nodeType(n) == :ELEMENT}.nil?
187 remove << nd if nd.nodeValue.strip == ""
190 remove << nd if nd.nodeValue.strip == ""
196 removeWhitespacesAndComments(nd)
200 remove.each { |i| node.removeChild(i) }
204 def nodeMustBe(node, name)
207 name.include?(node.nodeName)
209 name == node.nodeName
215 raise "wrong xml-rpc (name)"
222 # returns, when successfully the only child-node
224 def hasOnlyOneChild(node, name=nil)
225 if node.childNodes.to_a.size != 1
226 raise "wrong xml-rpc (size)"
229 nodeMustBe(node.firstChild, name)
240 # the node `node` has empty string or string
241 def text_zero_one(node)
242 nodes = node.childNodes.to_a.size
245 text(node.firstChild)
249 raise "wrong xml-rpc (size)"
255 #TODO: check string for float because to_i returnsa
256 # 0 when wrong string
257 nodeMustBe(node, %w(i4 int))
258 hasOnlyOneChild(node)
260 Convert.int(text(node.firstChild))
264 nodeMustBe(node, "boolean")
265 hasOnlyOneChild(node)
267 Convert.boolean(text(node.firstChild))
271 nodeMustBe(node, "nil")
272 assert( node.childNodes.to_a.size == 0 )
277 nodeMustBe(node, "string")
282 #TODO: check string for float because to_f returnsa
283 # 0.0 when wrong string
284 nodeMustBe(node, "double")
285 hasOnlyOneChild(node)
287 Convert.double(text(node.firstChild))
291 nodeMustBe(node, "dateTime.iso8601")
292 hasOnlyOneChild(node)
294 Convert.dateTime( text(node.firstChild) )
298 nodeMustBe(node, "base64")
299 #hasOnlyOneChild(node)
301 Convert.base64(text_zero_one(node))
305 nodeMustBe(node, "member")
306 assert( node.childNodes.to_a.size == 2 )
308 [ name(node[0]), value(node[1]) ]
312 nodeMustBe(node, "name")
313 #hasOnlyOneChild(node)
318 nodeMustBe(node, "array")
319 hasOnlyOneChild(node, "data")
320 data(node.firstChild)
324 nodeMustBe(node, "data")
326 node.childNodes.to_a.collect do |val|
332 nodeMustBe(node, "param")
333 hasOnlyOneChild(node, "value")
334 value(node.firstChild)
337 def methodResponse(node)
338 nodeMustBe(node, "methodResponse")
339 hasOnlyOneChild(node, %w(params fault))
340 child = node.firstChild
344 [ true, params(child,false) ]
346 [ false, fault(child) ]
348 raise "unexpected error"
354 nodeMustBe(node, "methodName")
355 hasOnlyOneChild(node)
356 text(node.firstChild)
359 def params(node, call=true)
360 nodeMustBe(node, "params")
363 node.childNodes.to_a.collect do |n|
366 else # response (only one param)
367 hasOnlyOneChild(node)
368 param(node.firstChild)
373 nodeMustBe(node, "fault")
374 hasOnlyOneChild(node, "value")
375 f = value(node.firstChild)
381 # _nodeType is defined in the subclass
383 assert( _nodeType(node) == :TEXT )
384 assert( node.hasChildNodes == false )
385 assert( node.nodeValue != nil )
391 nodeMustBe(node, "struct")
394 node.childNodes.to_a.each do |me|
404 nodeMustBe(node, "value")
405 nodes = node.childNodes.to_a.size
409 raise "wrong xml-rpc (size)"
412 child = node.firstChild
414 case _nodeType(child)
419 when "i4", "int" then integer(child)
420 when "boolean" then boolean(child)
421 when "string" then string(child)
422 when "double" then double(child)
423 when "dateTime.iso8601" then dateTime(child)
424 when "base64" then base64(child)
425 when "struct" then struct(child)
426 when "array" then array(child)
428 if Config::ENABLE_NIL_PARSER
431 raise "wrong/unknown XML-RPC type 'nil'"
434 raise "wrong/unknown XML-RPC type"
437 raise "wrong type of node"
443 nodeMustBe(node, "methodCall")
444 assert( (1..2).include?( node.childNodes.to_a.size ) )
445 name = methodName(node[0])
447 if node.childNodes.to_a.size == 2 then
449 else # no parameters given
455 end # module TreeParserMixin
457 class AbstractStreamParser
458 def parseMethodResponse(str)
459 parser = @parser_class.new
461 raise "No valid method response!" if parser.method_name != nil
462 if parser.fault != nil
463 # is a fault structure
464 [false, parser.fault]
466 # is a normal return value
467 raise "Missing return value!" if parser.params.size == 0
468 raise "Too many return values. Only one allowed!" if parser.params.size > 1
469 [true, parser.params[0]]
473 def parseMethodCall(str)
474 parser = @parser_class.new
476 raise "No valid method call - missing method name!" if parser.method_name.nil?
477 [parser.method_name, parser.params]
481 module StreamParserMixin
483 attr_reader :method_name
504 def startElement(name, attrs=[])
510 raise "wrong/unknown XML-RPC type 'nil'" unless Config::ENABLE_NIL_PARSER
513 @val_stack << @values
530 @value = Convert.int(@data)
532 @value = Convert.boolean(@data)
534 @value = Convert.double(@data)
535 when "dateTime.iso8601"
536 @value = Convert.dateTime(@data)
538 @value = Convert.base64(@data)
540 @value = @data if @value.nil?
541 @values << (@value == :nil ? nil : @value)
544 @values = @val_stack.pop
546 @value = Convert.struct(@struct)
549 @struct = @structs.pop
553 @struct[@name[0]] = @values.pop
556 @params << @values[0]
560 @fault = Convert.fault(@values[0])
577 end # module StreamParserMixin
579 # ---------------------------------------------------------------------------
580 class XMLStreamParser < AbstractStreamParser
583 @parser_class = Class.new(::XMLParser) {
584 include StreamParserMixin
587 end # class XMLStreamParser
588 # ---------------------------------------------------------------------------
589 class NQXMLStreamParser < AbstractStreamParser
591 require "nqxml/streamingparser"
592 @parser_class = XMLRPCParser
596 include StreamParserMixin
599 parser = NQXML::StreamingParser.new(str)
609 startElement(ele.name, ele.attrs)
614 end # class XMLRPCParser
616 end # class NQXMLStreamParser
617 # ---------------------------------------------------------------------------
618 class XMLTreeParser < AbstractTreeParser
621 require "xmltreebuilder"
623 # The new XMLParser library (0.6.2+) uses a slightly different DOM implementation.
624 # The following code removes the differences between both versions.
625 if defined? XML::DOM::Builder
626 return if defined? XML::DOM::Node::DOCUMENT # code below has been already executed
627 klass = XML::DOM::Node
628 klass.const_set("DOCUMENT", klass::DOCUMENT_NODE)
629 klass.const_set("TEXT", klass::TEXT_NODE)
630 klass.const_set("COMMENT", klass::COMMENT_NODE)
631 klass.const_set("ELEMENT", klass::ELEMENT_NODE)
639 if tp == XML::SimpleTree::Node::TEXT then :TEXT
640 elsif tp == XML::SimpleTree::Node::COMMENT then :COMMENT
641 elsif tp == XML::SimpleTree::Node::ELEMENT then :ELEMENT
647 def methodResponse_document(node)
648 assert( node.nodeType == XML::SimpleTree::Node::DOCUMENT )
649 hasOnlyOneChild(node, "methodResponse")
651 methodResponse(node.firstChild)
654 def methodCall_document(node)
655 assert( node.nodeType == XML::SimpleTree::Node::DOCUMENT )
656 hasOnlyOneChild(node, "methodCall")
658 methodCall(node.firstChild)
661 def createCleanedTree(str)
662 doc = XML::SimpleTreeBuilder.new.parse(str)
663 doc.documentElement.normalize
664 removeWhitespacesAndComments(doc)
668 end # class XMLParser
669 # ---------------------------------------------------------------------------
670 class NQXMLTreeParser < AbstractTreeParser
673 require "nqxml/treeparser"
682 def methodResponse_document(node)
686 def methodCall_document(node)
690 def createCleanedTree(str)
691 doc = ::NQXML::TreeParser.new(str).document.rootNode
692 removeWhitespacesAndComments(doc)
696 end # class NQXMLTreeParser
697 # ---------------------------------------------------------------------------
698 class REXMLStreamParser < AbstractStreamParser
700 require "rexml/document"
701 @parser_class = StreamListener
705 include StreamParserMixin
707 alias :tag_start :startElement
708 alias :tag_end :endElement
709 alias :text :character
710 alias :cdata :character
712 def method_missing(*a)
717 parser = REXML::Document.parse_stream(str, self)
722 # ---------------------------------------------------------------------------
723 class XMLScanStreamParser < AbstractStreamParser
725 require "xmlscan/parser"
726 @parser_class = XMLScanParser
730 include StreamParserMixin
741 parser = XMLScan::XMLParser.new(self)
745 alias :on_stag :startElement
746 alias :on_etag :endElement
748 def on_stag_end(name); end
750 def on_stag_end_empty(name)
763 def on_entityref(ent)
768 raise "unknown entity"
776 def on_charref_hex(code)
780 def method_missing(*a)
783 # TODO: call/implement?
791 # ---------------------------------------------------------------------------
792 XMLParser = XMLTreeParser
793 NQXMLParser = NQXMLTreeParser
795 Classes = [XMLStreamParser, XMLTreeParser,
796 NQXMLStreamParser, NQXMLTreeParser,
797 REXMLStreamParser, XMLScanStreamParser]
799 # yields an instance of each installed parser
800 def self.each_installed_parser
801 XMLRPC::XMLParser::Classes.each do |klass|
809 end # module XMLParser