require "date"
require "xmlrpc/base64"
require "xmlrpc/datetime"
module NQXML
class Node
def removeChild(node)
@children.delete(node)
end
def childNodes
@children
end
def hasChildNodes
not @children.empty?
end
def [] (index)
@children[index]
end
def nodeType
if @entity.instance_of? NQXML::Text then :TEXT
elsif @entity.instance_of? NQXML::Comment then :COMMENT
elsif @entity.instance_of? NQXML::Tag then :ELEMENT
else :ELSE
end
end
def nodeValue
@entity.text
end
def nodeName
@entity.name
end
end end
module XMLRPC
class FaultException < StandardError
attr_reader :faultCode, :faultString
alias message faultString
def initialize(faultCode, faultString)
@faultCode = faultCode
@faultString = faultString
end
def to_h
{"faultCode" => @faultCode, "faultString" => @faultString}
end
end
module Convert
def self.int(str)
str.to_i
end
def self.boolean(str)
case str
when "0" then false
when "1" then true
else
raise "RPC-value of type boolean is wrong"
end
end
def self.double(str)
str.to_f
end
def self.dateTime(str)
case str
when /^(-?\d\d\d\d)-?(\d\d)-?(\d\d)T(\d\d):(\d\d):(\d\d)(?:Z|([+-])(\d\d):?(\d\d))?$/
a = [$1, $2, $3, $4, $5, $6].collect{|i| i.to_i}
if $7
ofs = $8.to_i*3600 + $9.to_i*60
ofs = -ofs if $7=='+'
utc = Time.utc(*a) + ofs
a = [ utc.year, utc.month, utc.day, utc.hour, utc.min, utc.sec ]
end
XMLRPC::DateTime.new(*a)
when /^(-?\d\d)-?(\d\d)-?(\d\d)T(\d\d):(\d\d):(\d\d)(Z|([+-]\d\d):(\d\d))?$/
a = [$1, $2, $3, $4, $5, $6].collect{|i| i.to_i}
if a[0] < 70
a[0] += 2000
else
a[0] += 1900
end
if $7
ofs = $8.to_i*3600 + $9.to_i*60
ofs = -ofs if $7=='+'
utc = Time.utc(*a) + ofs
a = [ utc.year, utc.month, utc.day, utc.hour, utc.min, utc.sec ]
end
XMLRPC::DateTime.new(*a)
else
raise "wrong dateTime.iso8601 format " + str
end
end
def self.base64(str)
XMLRPC::Base64.decode(str)
end
def self.struct(hash)
klass = hash["___class___"]
if klass.nil? or Config::ENABLE_MARSHALLING == false
hash
else
begin
mod = Module
klass.split("::").each {|const| mod = mod.const_get(const.strip)}
obj = mod.allocate
hash.delete "___class___"
hash.each {|key, value|
obj.instance_variable_set("@#{ key }", value) if key =~ /^([\w_][\w_0-9]*)$/
}
obj
rescue
hash
end
end
end
def self.fault(hash)
if hash.kind_of? Hash and hash.size == 2 and
hash.has_key? "faultCode" and hash.has_key? "faultString" and
hash["faultCode"].kind_of? Integer and hash["faultString"].kind_of? String
XMLRPC::FaultException.new(hash["faultCode"], hash["faultString"])
else
raise "wrong fault-structure: #{hash.inspect}"
end
end
end
module XMLParser
class AbstractTreeParser
def parseMethodResponse(str)
methodResponse_document(createCleanedTree(str))
end
def parseMethodCall(str)
methodCall_document(createCleanedTree(str))
end
private
def removeWhitespacesAndComments(node)
remove = []
childs = node.childNodes.to_a
childs.each do |nd|
case _nodeType(nd)
when :TEXT
unless %w(i4 int boolean string double dateTime.iso8601 base64).include? node.nodeName
if node.nodeName == "value"
if not node.childNodes.to_a.detect {|n| _nodeType(n) == :ELEMENT}.nil?
remove << nd if nd.nodeValue.strip == ""
end
else
remove << nd if nd.nodeValue.strip == ""
end
end
when :COMMENT
remove << nd
else
removeWhitespacesAndComments(nd)
end
end
remove.each { |i| node.removeChild(i) }
end
def nodeMustBe(node, name)
cmp = case name
when Array
name.include?(node.nodeName)
when String
name == node.nodeName
else
raise "error"
end
if not cmp then
raise "wrong xml-rpc (name)"
end
node
end
def hasOnlyOneChild(node, name=nil)
if node.childNodes.to_a.size != 1
raise "wrong xml-rpc (size)"
end
if name != nil then
nodeMustBe(node.firstChild, name)
end
end
def assert(b)
if not b then
raise "assert-fail"
end
end
def text_zero_one(node)
nodes = node.childNodes.to_a.size
if nodes == 1
text(node.firstChild)
elsif nodes == 0
""
else
raise "wrong xml-rpc (size)"
end
end
def integer(node)
nodeMustBe(node, %w(i4 int))
hasOnlyOneChild(node)
Convert.int(text(node.firstChild))
end
def boolean(node)
nodeMustBe(node, "boolean")
hasOnlyOneChild(node)
Convert.boolean(text(node.firstChild))
end
def v_nil(node)
nodeMustBe(node, "nil")
assert( node.childNodes.to_a.size == 0 )
nil
end
def string(node)
nodeMustBe(node, "string")
text_zero_one(node)
end
def double(node)
nodeMustBe(node, "double")
hasOnlyOneChild(node)
Convert.double(text(node.firstChild))
end
def dateTime(node)
nodeMustBe(node, "dateTime.iso8601")
hasOnlyOneChild(node)
Convert.dateTime( text(node.firstChild) )
end
def base64(node)
nodeMustBe(node, "base64")
Convert.base64(text_zero_one(node))
end
def member(node)
nodeMustBe(node, "member")
assert( node.childNodes.to_a.size == 2 )
[ name(node[0]), value(node[1]) ]
end
def name(node)
nodeMustBe(node, "name")
text_zero_one(node)
end
def array(node)
nodeMustBe(node, "array")
hasOnlyOneChild(node, "data")
data(node.firstChild)
end
def data(node)
nodeMustBe(node, "data")
node.childNodes.to_a.collect do |val|
value(val)
end
end
def param(node)
nodeMustBe(node, "param")
hasOnlyOneChild(node, "value")
value(node.firstChild)
end
def methodResponse(node)
nodeMustBe(node, "methodResponse")
hasOnlyOneChild(node, %w(params fault))
child = node.firstChild
case child.nodeName
when "params"
[ true, params(child,false) ]
when "fault"
[ false, fault(child) ]
else
raise "unexpected error"
end
end
def methodName(node)
nodeMustBe(node, "methodName")
hasOnlyOneChild(node)
text(node.firstChild)
end
def params(node, call=true)
nodeMustBe(node, "params")
if call
node.childNodes.to_a.collect do |n|
param(n)
end
else hasOnlyOneChild(node)
param(node.firstChild)
end
end
def fault(node)
nodeMustBe(node, "fault")
hasOnlyOneChild(node, "value")
f = value(node.firstChild)
Convert.fault(f)
end
def text(node)
assert( _nodeType(node) == :TEXT )
assert( node.hasChildNodes == false )
assert( node.nodeValue != nil )
node.nodeValue.to_s
end
def struct(node)
nodeMustBe(node, "struct")
hash = {}
node.childNodes.to_a.each do |me|
n, v = member(me)
hash[n] = v
end
Convert.struct(hash)
end
def value(node)
nodeMustBe(node, "value")
nodes = node.childNodes.to_a.size
if nodes == 0
return ""
elsif nodes > 1
raise "wrong xml-rpc (size)"
end
child = node.firstChild
case _nodeType(child)
when :TEXT
text_zero_one(node)
when :ELEMENT
case child.nodeName
when "i4", "int" then integer(child)
when "boolean" then boolean(child)
when "string" then string(child)
when "double" then double(child)
when "dateTime.iso8601" then dateTime(child)
when "base64" then base64(child)
when "struct" then struct(child)
when "array" then array(child)
when "nil"
if Config::ENABLE_NIL_PARSER
v_nil(child)
else
raise "wrong/unknown XML-RPC type 'nil'"
end
else
raise "wrong/unknown XML-RPC type"
end
else
raise "wrong type of node"
end
end
def methodCall(node)
nodeMustBe(node, "methodCall")
assert( (1..2).include?( node.childNodes.to_a.size ) )
name = methodName(node[0])
if node.childNodes.to_a.size == 2 then
pa = params(node[1])
else pa = []
end
[name, pa]
end
end
class AbstractStreamParser
def parseMethodResponse(str)
parser = @parser_class.new
parser.parse(str)
raise "No valid method response!" if parser.method_name != nil
if parser.fault != nil
[false, parser.fault]
else
raise "Missing return value!" if parser.params.size == 0
raise "Too many return values. Only one allowed!" if parser.params.size > 1
[true, parser.params[0]]
end
end
def parseMethodCall(str)
parser = @parser_class.new
parser.parse(str)
raise "No valid method call - missing method name!" if parser.method_name.nil?
[parser.method_name, parser.params]
end
end
module StreamParserMixin
attr_reader :params
attr_reader :method_name
attr_reader :fault
def initialize(*a)
super(*a)
@params = []
@values = []
@val_stack = []
@names = []
@name = []
@structs = []
@struct = {}
@method_name = nil
@fault = nil
@data = nil
end
def startElement(name, attrs=[])
@data = nil
case name
when "value"
@value = nil
when "nil"
raise "wrong/unknown XML-RPC type 'nil'" unless Config::ENABLE_NIL_PARSER
@value = :nil
when "array"
@val_stack << @values
@values = []
when "struct"
@names << @name
@name = []
@structs << @struct
@struct = {}
end
end
def endElement(name)
@data ||= ""
case name
when "string"
@value = @data
when "i4", "int"
@value = Convert.int(@data)
when "boolean"
@value = Convert.boolean(@data)
when "double"
@value = Convert.double(@data)
when "dateTime.iso8601"
@value = Convert.dateTime(@data)
when "base64"
@value = Convert.base64(@data)
when "value"
@value = @data if @value.nil?
@values << (@value == :nil ? nil : @value)
when "array"
@value = @values
@values = @val_stack.pop
when "struct"
@value = Convert.struct(@struct)
@name = @names.pop
@struct = @structs.pop
when "name"
@name[0] = @data
when "member"
@struct[@name[0]] = @values.pop
when "param"
@params << @values[0]
@values = []
when "fault"
@fault = Convert.fault(@values[0])
when "methodName"
@method_name = @data
end
@data = nil
end
def character(data)
if @data
@data << data
else
@data = data
end
end
end
class XMLStreamParser < AbstractStreamParser
def initialize
require "xmlparser"
@parser_class = Class.new(::XMLParser) {
include StreamParserMixin
}
end
end class NQXMLStreamParser < AbstractStreamParser
def initialize
require "nqxml/streamingparser"
@parser_class = XMLRPCParser
end
class XMLRPCParser
include StreamParserMixin
def parse(str)
parser = NQXML::StreamingParser.new(str)
parser.each do |ele|
case ele
when NQXML::Text
@data = ele.text
when NQXML::Tag
if ele.isTagEnd
endElement(ele.name)
else
startElement(ele.name, ele.attrs)
end
end
end end end
end class XMLTreeParser < AbstractTreeParser
def initialize
require "xmltreebuilder"
if defined? XML::DOM::Builder
return if defined? XML::DOM::Node::DOCUMENT klass = XML::DOM::Node
klass.const_set("DOCUMENT", klass::DOCUMENT_NODE)
klass.const_set("TEXT", klass::TEXT_NODE)
klass.const_set("COMMENT", klass::COMMENT_NODE)
klass.const_set("ELEMENT", klass::ELEMENT_NODE)
end
end
private
def _nodeType(node)
tp = node.nodeType
if tp == XML::SimpleTree::Node::TEXT then :TEXT
elsif tp == XML::SimpleTree::Node::COMMENT then :COMMENT
elsif tp == XML::SimpleTree::Node::ELEMENT then :ELEMENT
else :ELSE
end
end
def methodResponse_document(node)
assert( node.nodeType == XML::SimpleTree::Node::DOCUMENT )
hasOnlyOneChild(node, "methodResponse")
methodResponse(node.firstChild)
end
def methodCall_document(node)
assert( node.nodeType == XML::SimpleTree::Node::DOCUMENT )
hasOnlyOneChild(node, "methodCall")
methodCall(node.firstChild)
end
def createCleanedTree(str)
doc = XML::SimpleTreeBuilder.new.parse(str)
doc.documentElement.normalize
removeWhitespacesAndComments(doc)
doc
end
end class NQXMLTreeParser < AbstractTreeParser
def initialize
require "nqxml/treeparser"
end
private
def _nodeType(node)
node.nodeType
end
def methodResponse_document(node)
methodResponse(node)
end
def methodCall_document(node)
methodCall(node)
end
def createCleanedTree(str)
doc = ::NQXML::TreeParser.new(str).document.rootNode
removeWhitespacesAndComments(doc)
doc
end
end class REXMLStreamParser < AbstractStreamParser
def initialize
require "rexml/document"
@parser_class = StreamListener
end
class StreamListener
include StreamParserMixin
alias :tag_start :startElement
alias :tag_end :endElement
alias :text :character
alias :cdata :character
def method_missing(*a)
end
def parse(str)
parser = REXML::Document.parse_stream(str, self)
end
end
end
class XMLScanStreamParser < AbstractStreamParser
def initialize
require "xmlscan/parser"
@parser_class = XMLScanParser
end
class XMLScanParser
include StreamParserMixin
Entities = {
"lt" => "<",
"gt" => ">",
"amp" => "&",
"quot" => '"',
"apos" => "'"
}
def parse(str)
parser = XMLScan::XMLParser.new(self)
parser.parse(str)
end
alias :on_stag :startElement
alias :on_etag :endElement
def on_stag_end(name); end
def on_stag_end_empty(name)
startElement(name)
endElement(name)
end
def on_chardata(str)
character(str)
end
def on_cdata(str)
character(str)
end
def on_entityref(ent)
str = Entities[ent]
if str
character(str)
else
raise "unknown entity"
end
end
def on_charref(code)
character(code.chr)
end
def on_charref_hex(code)
character(code.chr)
end
def method_missing(*a)
end
end
end
XMLParser = XMLTreeParser
NQXMLParser = NQXMLTreeParser
Classes = [XMLStreamParser, XMLTreeParser,
NQXMLStreamParser, NQXMLTreeParser,
REXMLStreamParser, XMLScanStreamParser]
def self.each_installed_parser
XMLRPC::XMLParser::Classes.each do |klass|
begin
yield klass.new
rescue LoadError
end
end
end
end
end