source: extensions/gsdl-video/trunk/installed/cmdline/lib/ruby/1.8/xmlrpc/parser.rb@ 18425

Last change on this file since 18425 was 18425, checked in by davidb, 15 years ago

Video extension to Greenstone

File size: 18.2 KB
Line 
1#
2# Parser for XML-RPC call and response
3#
4# Copyright (C) 2001, 2002, 2003 by Michael Neumann ([email protected])
5#
6# $Id: parser.rb 11708 2007-02-12 23:01:19Z shyouhei $
7#
8
9
10require "date"
11require "xmlrpc/base64"
12require "xmlrpc/datetime"
13
14
15# add some methods to NQXML::Node
16module NQXML
17 class Node
18
19 def removeChild(node)
20 @children.delete(node)
21 end
22 def childNodes
23 @children
24 end
25 def hasChildNodes
26 not @children.empty?
27 end
28 def [] (index)
29 @children[index]
30 end
31
32 def nodeType
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
37 else :ELSE
38 end
39 end
40
41 def nodeValue
42 #TODO: error when wrong Entity-type
43 @entity.text
44 end
45 def nodeName
46 #TODO: error when wrong Entity-type
47 @entity.name
48 end
49 end # class Node
50end # module NQXML
51
52module XMLRPC
53
54 class FaultException < StandardError
55 attr_reader :faultCode, :faultString
56
57 alias message faultString
58
59 def initialize(faultCode, faultString)
60 @faultCode = faultCode
61 @faultString = faultString
62 end
63
64 # returns a hash
65 def to_h
66 {"faultCode" => @faultCode, "faultString" => @faultString}
67 end
68 end
69
70 module Convert
71 def self.int(str)
72 str.to_i
73 end
74
75 def self.boolean(str)
76 case str
77 when "0" then false
78 when "1" then true
79 else
80 raise "RPC-value of type boolean is wrong"
81 end
82 end
83
84 def self.double(str)
85 str.to_f
86 end
87
88 def self.dateTime(str)
89 case 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}
92 if $7
93 ofs = $8.to_i*3600 + $9.to_i*60
94 ofs = -ofs if $7=='+'
95 utc = Time.utc(a.reverse) + ofs
96 a = [ utc.year, utc.month, utc.day, utc.hour, utc.min, utc.sec ]
97 end
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}
101 if a[0] < 70
102 a[0] += 2000
103 else
104 a[0] += 1900
105 end
106 if $7
107 ofs = $8.to_i*3600 + $9.to_i*60
108 ofs = -ofs if $7=='+'
109 utc = Time.utc(a.reverse) + ofs
110 a = [ utc.year, utc.month, utc.day, utc.hour, utc.min, utc.sec ]
111 end
112 XMLRPC::DateTime.new(*a)
113 else
114 raise "wrong dateTime.iso8601 format " + str
115 end
116 end
117
118 def self.base64(str)
119 XMLRPC::Base64.decode(str)
120 end
121
122 def self.struct(hash)
123 # convert to marhalled object
124 klass = hash["___class___"]
125 if klass.nil? or Config::ENABLE_MARSHALLING == false
126 hash
127 else
128 begin
129 mod = Module
130 klass.split("::").each {|const| mod = mod.const_get(const.strip)}
131
132 obj = mod.allocate
133
134 hash.delete "___class___"
135 hash.each {|key, value|
136 obj.instance_variable_set("@#{ key }", value) if key =~ /^([\w_][\w_0-9]*)$/
137 }
138 obj
139 rescue
140 hash
141 end
142 end
143 end
144
145 def self.fault(hash)
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
149
150 XMLRPC::FaultException.new(hash["faultCode"], hash["faultString"])
151 else
152 raise "wrong fault-structure: #{hash.inspect}"
153 end
154 end
155
156 end # module Convert
157
158 module XMLParser
159
160 class AbstractTreeParser
161
162 def parseMethodResponse(str)
163 methodResponse_document(createCleanedTree(str))
164 end
165
166 def parseMethodCall(str)
167 methodCall_document(createCleanedTree(str))
168 end
169
170 private
171
172 #
173 # remove all whitespaces but in the tags i4, int, boolean....
174 # and all comments
175 #
176 def removeWhitespacesAndComments(node)
177 remove = []
178 childs = node.childNodes.to_a
179 childs.each do |nd|
180 case _nodeType(nd)
181 when :TEXT
182 # TODO: add nil?
183 unless %w(i4 int boolean string double dateTime.iso8601 base64).include? node.nodeName
184
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 == ""
188 end
189 else
190 remove << nd if nd.nodeValue.strip == ""
191 end
192 end
193 when :COMMENT
194 remove << nd
195 else
196 removeWhitespacesAndComments(nd)
197 end
198 end
199
200 remove.each { |i| node.removeChild(i) }
201 end
202
203
204 def nodeMustBe(node, name)
205 cmp = case name
206 when Array
207 name.include?(node.nodeName)
208 when String
209 name == node.nodeName
210 else
211 raise "error"
212 end
213
214 if not cmp then
215 raise "wrong xml-rpc (name)"
216 end
217
218 node
219 end
220
221 #
222 # returns, when successfully the only child-node
223 #
224 def hasOnlyOneChild(node, name=nil)
225 if node.childNodes.to_a.size != 1
226 raise "wrong xml-rpc (size)"
227 end
228 if name != nil then
229 nodeMustBe(node.firstChild, name)
230 end
231 end
232
233
234 def assert(b)
235 if not b then
236 raise "assert-fail"
237 end
238 end
239
240 # the node `node` has empty string or string
241 def text_zero_one(node)
242 nodes = node.childNodes.to_a.size
243
244 if nodes == 1
245 text(node.firstChild)
246 elsif nodes == 0
247 ""
248 else
249 raise "wrong xml-rpc (size)"
250 end
251 end
252
253
254 def integer(node)
255 #TODO: check string for float because to_i returnsa
256 # 0 when wrong string
257 nodeMustBe(node, %w(i4 int))
258 hasOnlyOneChild(node)
259
260 Convert.int(text(node.firstChild))
261 end
262
263 def boolean(node)
264 nodeMustBe(node, "boolean")
265 hasOnlyOneChild(node)
266
267 Convert.boolean(text(node.firstChild))
268 end
269
270 def v_nil(node)
271 nodeMustBe(node, "nil")
272 assert( node.childNodes.to_a.size == 0 )
273 nil
274 end
275
276 def string(node)
277 nodeMustBe(node, "string")
278 text_zero_one(node)
279 end
280
281 def double(node)
282 #TODO: check string for float because to_f returnsa
283 # 0.0 when wrong string
284 nodeMustBe(node, "double")
285 hasOnlyOneChild(node)
286
287 Convert.double(text(node.firstChild))
288 end
289
290 def dateTime(node)
291 nodeMustBe(node, "dateTime.iso8601")
292 hasOnlyOneChild(node)
293
294 Convert.dateTime( text(node.firstChild) )
295 end
296
297 def base64(node)
298 nodeMustBe(node, "base64")
299 #hasOnlyOneChild(node)
300
301 Convert.base64(text_zero_one(node))
302 end
303
304 def member(node)
305 nodeMustBe(node, "member")
306 assert( node.childNodes.to_a.size == 2 )
307
308 [ name(node[0]), value(node[1]) ]
309 end
310
311 def name(node)
312 nodeMustBe(node, "name")
313 #hasOnlyOneChild(node)
314 text_zero_one(node)
315 end
316
317 def array(node)
318 nodeMustBe(node, "array")
319 hasOnlyOneChild(node, "data")
320 data(node.firstChild)
321 end
322
323 def data(node)
324 nodeMustBe(node, "data")
325
326 node.childNodes.to_a.collect do |val|
327 value(val)
328 end
329 end
330
331 def param(node)
332 nodeMustBe(node, "param")
333 hasOnlyOneChild(node, "value")
334 value(node.firstChild)
335 end
336
337 def methodResponse(node)
338 nodeMustBe(node, "methodResponse")
339 hasOnlyOneChild(node, %w(params fault))
340 child = node.firstChild
341
342 case child.nodeName
343 when "params"
344 [ true, params(child,false) ]
345 when "fault"
346 [ false, fault(child) ]
347 else
348 raise "unexpected error"
349 end
350
351 end
352
353 def methodName(node)
354 nodeMustBe(node, "methodName")
355 hasOnlyOneChild(node)
356 text(node.firstChild)
357 end
358
359 def params(node, call=true)
360 nodeMustBe(node, "params")
361
362 if call
363 node.childNodes.to_a.collect do |n|
364 param(n)
365 end
366 else # response (only one param)
367 hasOnlyOneChild(node)
368 param(node.firstChild)
369 end
370 end
371
372 def fault(node)
373 nodeMustBe(node, "fault")
374 hasOnlyOneChild(node, "value")
375 f = value(node.firstChild)
376 Convert.fault(f)
377 end
378
379
380
381 # _nodeType is defined in the subclass
382 def text(node)
383 assert( _nodeType(node) == :TEXT )
384 assert( node.hasChildNodes == false )
385 assert( node.nodeValue != nil )
386
387 node.nodeValue.to_s
388 end
389
390 def struct(node)
391 nodeMustBe(node, "struct")
392
393 hash = {}
394 node.childNodes.to_a.each do |me|
395 n, v = member(me)
396 hash[n] = v
397 end
398
399 Convert.struct(hash)
400 end
401
402
403 def value(node)
404 nodeMustBe(node, "value")
405 nodes = node.childNodes.to_a.size
406 if nodes == 0
407 return ""
408 elsif nodes > 1
409 raise "wrong xml-rpc (size)"
410 end
411
412 child = node.firstChild
413
414 case _nodeType(child)
415 when :TEXT
416 text_zero_one(node)
417 when :ELEMENT
418 case child.nodeName
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)
427 when "nil"
428 if Config::ENABLE_NIL_PARSER
429 v_nil(child)
430 else
431 raise "wrong/unknown XML-RPC type 'nil'"
432 end
433 else
434 raise "wrong/unknown XML-RPC type"
435 end
436 else
437 raise "wrong type of node"
438 end
439
440 end
441
442 def methodCall(node)
443 nodeMustBe(node, "methodCall")
444 assert( (1..2).include?( node.childNodes.to_a.size ) )
445 name = methodName(node[0])
446
447 if node.childNodes.to_a.size == 2 then
448 pa = params(node[1])
449 else # no parameters given
450 pa = []
451 end
452 [name, pa]
453 end
454
455 end # module TreeParserMixin
456
457 class AbstractStreamParser
458 def parseMethodResponse(str)
459 parser = @parser_class.new
460 parser.parse(str)
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]
465 else
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]]
470 end
471 end
472
473 def parseMethodCall(str)
474 parser = @parser_class.new
475 parser.parse(str)
476 raise "No valid method call - missing method name!" if parser.method_name.nil?
477 [parser.method_name, parser.params]
478 end
479 end
480
481 module StreamParserMixin
482 attr_reader :params
483 attr_reader :method_name
484 attr_reader :fault
485
486 def initialize(*a)
487 super(*a)
488 @params = []
489 @values = []
490 @val_stack = []
491
492 @names = []
493 @name = []
494
495 @structs = []
496 @struct = {}
497
498 @method_name = nil
499 @fault = nil
500
501 @data = nil
502 end
503
504 def startElement(name, attrs=[])
505 @data = nil
506 case name
507 when "value"
508 @value = nil
509 when "nil"
510 raise "wrong/unknown XML-RPC type 'nil'" unless Config::ENABLE_NIL_PARSER
511 @value = :nil
512 when "array"
513 @val_stack << @values
514 @values = []
515 when "struct"
516 @names << @name
517 @name = []
518
519 @structs << @struct
520 @struct = {}
521 end
522 end
523
524 def endElement(name)
525 @data ||= ""
526 case name
527 when "string"
528 @value = @data
529 when "i4", "int"
530 @value = Convert.int(@data)
531 when "boolean"
532 @value = Convert.boolean(@data)
533 when "double"
534 @value = Convert.double(@data)
535 when "dateTime.iso8601"
536 @value = Convert.dateTime(@data)
537 when "base64"
538 @value = Convert.base64(@data)
539 when "value"
540 @value = @data if @value.nil?
541 @values << (@value == :nil ? nil : @value)
542 when "array"
543 @value = @values
544 @values = @val_stack.pop
545 when "struct"
546 @value = Convert.struct(@struct)
547
548 @name = @names.pop
549 @struct = @structs.pop
550 when "name"
551 @name[0] = @data
552 when "member"
553 @struct[@name[0]] = @values.pop
554
555 when "param"
556 @params << @values[0]
557 @values = []
558
559 when "fault"
560 @fault = Convert.fault(@values[0])
561
562 when "methodName"
563 @method_name = @data
564 end
565
566 @data = nil
567 end
568
569 def character(data)
570 if @data
571 @data << data
572 else
573 @data = data
574 end
575 end
576
577 end # module StreamParserMixin
578
579 # ---------------------------------------------------------------------------
580 class XMLStreamParser < AbstractStreamParser
581 def initialize
582 require "xmlparser"
583 @parser_class = Class.new(::XMLParser) {
584 include StreamParserMixin
585 }
586 end
587 end # class XMLStreamParser
588 # ---------------------------------------------------------------------------
589 class NQXMLStreamParser < AbstractStreamParser
590 def initialize
591 require "nqxml/streamingparser"
592 @parser_class = XMLRPCParser
593 end
594
595 class XMLRPCParser
596 include StreamParserMixin
597
598 def parse(str)
599 parser = NQXML::StreamingParser.new(str)
600 parser.each do |ele|
601 case ele
602 when NQXML::Text
603 @data = ele.text
604 #character(ele.text)
605 when NQXML::Tag
606 if ele.isTagEnd
607 endElement(ele.name)
608 else
609 startElement(ele.name, ele.attrs)
610 end
611 end
612 end # do
613 end # method parse
614 end # class XMLRPCParser
615
616 end # class NQXMLStreamParser
617 # ---------------------------------------------------------------------------
618 class XMLTreeParser < AbstractTreeParser
619
620 def initialize
621 require "xmltreebuilder"
622
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)
632 end
633 end
634
635 private
636
637 def _nodeType(node)
638 tp = node.nodeType
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
642 else :ELSE
643 end
644 end
645
646
647 def methodResponse_document(node)
648 assert( node.nodeType == XML::SimpleTree::Node::DOCUMENT )
649 hasOnlyOneChild(node, "methodResponse")
650
651 methodResponse(node.firstChild)
652 end
653
654 def methodCall_document(node)
655 assert( node.nodeType == XML::SimpleTree::Node::DOCUMENT )
656 hasOnlyOneChild(node, "methodCall")
657
658 methodCall(node.firstChild)
659 end
660
661 def createCleanedTree(str)
662 doc = XML::SimpleTreeBuilder.new.parse(str)
663 doc.documentElement.normalize
664 removeWhitespacesAndComments(doc)
665 doc
666 end
667
668 end # class XMLParser
669 # ---------------------------------------------------------------------------
670 class NQXMLTreeParser < AbstractTreeParser
671
672 def initialize
673 require "nqxml/treeparser"
674 end
675
676 private
677
678 def _nodeType(node)
679 node.nodeType
680 end
681
682 def methodResponse_document(node)
683 methodResponse(node)
684 end
685
686 def methodCall_document(node)
687 methodCall(node)
688 end
689
690 def createCleanedTree(str)
691 doc = ::NQXML::TreeParser.new(str).document.rootNode
692 removeWhitespacesAndComments(doc)
693 doc
694 end
695
696 end # class NQXMLTreeParser
697 # ---------------------------------------------------------------------------
698 class REXMLStreamParser < AbstractStreamParser
699 def initialize
700 require "rexml/document"
701 @parser_class = StreamListener
702 end
703
704 class StreamListener
705 include StreamParserMixin
706
707 alias :tag_start :startElement
708 alias :tag_end :endElement
709 alias :text :character
710 alias :cdata :character
711
712 def method_missing(*a)
713 # ignore
714 end
715
716 def parse(str)
717 parser = REXML::Document.parse_stream(str, self)
718 end
719 end
720
721 end
722 # ---------------------------------------------------------------------------
723 class XMLScanStreamParser < AbstractStreamParser
724 def initialize
725 require "xmlscan/parser"
726 @parser_class = XMLScanParser
727 end
728
729 class XMLScanParser
730 include StreamParserMixin
731
732 Entities = {
733 "lt" => "<",
734 "gt" => ">",
735 "amp" => "&",
736 "quot" => '"',
737 "apos" => "'"
738 }
739
740 def parse(str)
741 parser = XMLScan::XMLParser.new(self)
742 parser.parse(str)
743 end
744
745 alias :on_stag :startElement
746 alias :on_etag :endElement
747
748 def on_stag_end(name); end
749
750 def on_stag_end_empty(name)
751 startElement(name)
752 endElement(name)
753 end
754
755 def on_chardata(str)
756 character(str)
757 end
758
759 def on_cdata(str)
760 character(str)
761 end
762
763 def on_entityref(ent)
764 str = Entities[ent]
765 if str
766 character(str)
767 else
768 raise "unknown entity"
769 end
770 end
771
772 def on_charref(code)
773 character(code.chr)
774 end
775
776 def on_charref_hex(code)
777 character(code.chr)
778 end
779
780 def method_missing(*a)
781 end
782
783 # TODO: call/implement?
784 # valid_name?
785 # valid_chardata?
786 # valid_char?
787 # parse_error
788
789 end
790 end
791 # ---------------------------------------------------------------------------
792 XMLParser = XMLTreeParser
793 NQXMLParser = NQXMLTreeParser
794
795 Classes = [XMLStreamParser, XMLTreeParser,
796 NQXMLStreamParser, NQXMLTreeParser,
797 REXMLStreamParser, XMLScanStreamParser]
798
799 # yields an instance of each installed parser
800 def self.each_installed_parser
801 XMLRPC::XMLParser::Classes.each do |klass|
802 begin
803 yield klass.new
804 rescue LoadError
805 end
806 end
807 end
808
809 end # module XMLParser
810
811
812end # module XMLRPC
813
Note: See TracBrowser for help on using the repository browser.