1#
2# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
3#
4# $Id: create.rb 36958 2012-09-13 02:22:10Z zzak $
5#
6
7require "date"
8require "xmlrpc/base64"
9
10module XMLRPC # :nodoc:
11
12  module XMLWriter
13
14    class Abstract
15      def ele(name, *children)
16        element(name, nil, *children)
17      end
18
19      def tag(name, txt)
20        element(name, nil, text(txt))
21      end
22    end
23
24
25    class Simple < Abstract
26
27      def document_to_str(doc)
28        doc
29      end
30
31      def document(*params)
32        params.join("")
33      end
34
35      def pi(name, *params)
36        "<?#{name} " + params.join(" ") + " ?>"
37      end
38
39      def element(name, attrs, *children)
40        raise "attributes not yet implemented" unless attrs.nil?
41        if children.empty?
42          "<#{name}/>"
43        else
44          "<#{name}>" + children.join("") + "</#{name}>"
45        end
46      end
47
48      def text(txt)
49        cleaned = txt.dup
50        cleaned.gsub!(/&/, '&amp;')
51        cleaned.gsub!(/</, '&lt;')
52        cleaned.gsub!(/>/, '&gt;')
53        cleaned
54      end
55
56    end # class Simple
57
58
59    class XMLParser < Abstract
60
61      def initialize
62        require "xmltreebuilder"
63      end
64
65      def document_to_str(doc)
66        doc.to_s
67      end
68
69      def document(*params)
70        XML::SimpleTree::Document.new(*params)
71      end
72
73      def pi(name, *params)
74        XML::SimpleTree::ProcessingInstruction.new(name, *params)
75      end
76
77      def element(name, attrs, *children)
78        XML::SimpleTree::Element.new(name, attrs, *children)
79      end
80
81      def text(txt)
82        XML::SimpleTree::Text.new(txt)
83      end
84
85    end # class XMLParser
86
87    Classes = [Simple, XMLParser]
88
89    # yields an instance of each installed XML writer
90    def self.each_installed_writer
91      XMLRPC::XMLWriter::Classes.each do |klass|
92        begin
93          yield klass.new
94        rescue LoadError
95        end
96      end
97    end
98
99  end # module XMLWriter
100
101  # Creates XML-RPC call/response documents
102  #
103  class Create
104
105    def initialize(xml_writer = nil)
106      @writer = xml_writer || Config::DEFAULT_WRITER.new
107    end
108
109
110    def methodCall(name, *params)
111      name = name.to_s
112
113      if name !~ /[a-zA-Z0-9_.:\/]+/
114        raise ArgumentError, "Wrong XML-RPC method-name"
115      end
116
117      parameter = params.collect do |param|
118        @writer.ele("param", conv2value(param))
119      end
120
121      tree = @writer.document(
122               @writer.pi("xml", 'version="1.0"'),
123               @writer.ele("methodCall",
124                 @writer.tag("methodName", name),
125                 @writer.ele("params", *parameter)
126               )
127             )
128
129      @writer.document_to_str(tree) + "\n"
130    end
131
132
133
134    #
135    # Generates a XML-RPC methodResponse document
136    #
137    # When +is_ret+ is +false+ then the +params+ array must
138    # contain only one element, which is a structure
139    # of a fault return-value.
140    #
141    # When +is_ret+ is +true+ then a normal
142    # return-value of all the given +params+ is created.
143    #
144    def methodResponse(is_ret, *params)
145
146      if is_ret
147        resp = params.collect do |param|
148          @writer.ele("param", conv2value(param))
149        end
150
151        resp = [@writer.ele("params", *resp)]
152      else
153        if params.size != 1 or params[0] === XMLRPC::FaultException
154          raise ArgumentError, "no valid fault-structure given"
155        end
156        resp = @writer.ele("fault", conv2value(params[0].to_h))
157      end
158
159
160      tree = @writer.document(
161               @writer.pi("xml", 'version="1.0"'),
162               @writer.ele("methodResponse", resp)
163             )
164
165      @writer.document_to_str(tree) + "\n"
166    end
167
168
169
170    private
171
172    #
173    # Converts a Ruby object into a XML-RPC <code><value></code> tag
174    #
175    def conv2value(param) # :doc:
176
177        val = case param
178        when Fixnum, Bignum
179          # XML-RPC's int is 32bit int, and Fixnum also may be beyond 32bit
180          if Config::ENABLE_BIGINT
181            @writer.tag("i4", param.to_s)
182          else
183            if param >= -(2**31) and param <= (2**31-1)
184              @writer.tag("i4", param.to_s)
185            else
186              raise "Bignum is too big! Must be signed 32-bit integer!"
187            end
188          end
189        when TrueClass, FalseClass
190          @writer.tag("boolean", param ? "1" : "0")
191
192        when Symbol
193          @writer.tag("string", param.to_s)
194
195        when String
196          @writer.tag("string", param)
197
198        when NilClass
199          if Config::ENABLE_NIL_CREATE
200            @writer.ele("nil")
201          else
202            raise "Wrong type NilClass. Not allowed!"
203          end
204
205        when Float
206          raise "Wrong value #{param}. Not allowed!" unless param.finite?
207          @writer.tag("double", param.to_s)
208
209        when Struct
210          h = param.members.collect do |key|
211            value = param[key]
212            @writer.ele("member",
213              @writer.tag("name", key.to_s),
214              conv2value(value)
215            )
216          end
217
218          @writer.ele("struct", *h)
219
220        when Hash
221          # TODO: can a Hash be empty?
222
223          h = param.collect do |key, value|
224            @writer.ele("member",
225              @writer.tag("name", key.to_s),
226              conv2value(value)
227            )
228          end
229
230          @writer.ele("struct", *h)
231
232        when Array
233          # TODO: can an Array be empty?
234          a = param.collect {|v| conv2value(v) }
235
236          @writer.ele("array",
237            @writer.ele("data", *a)
238          )
239
240        when Time, Date, ::DateTime
241          @writer.tag("dateTime.iso8601", param.strftime("%Y%m%dT%H:%M:%S"))
242
243        when XMLRPC::DateTime
244          @writer.tag("dateTime.iso8601",
245            format("%.4d%02d%02dT%02d:%02d:%02d", *param.to_a))
246
247        when XMLRPC::Base64
248          @writer.tag("base64", param.encoded)
249
250        else
251          if Config::ENABLE_MARSHALLING and param.class.included_modules.include? XMLRPC::Marshallable
252            # convert Ruby object into Hash
253            ret = {"___class___" => param.class.name}
254            param.instance_variables.each {|v|
255              name = v[1..-1]
256              val = param.instance_variable_get(v)
257
258              if val.nil?
259                ret[name] = val if Config::ENABLE_NIL_CREATE
260              else
261                ret[name] = val
262              end
263            }
264            return conv2value(ret)
265          else
266            ok, pa = wrong_type(param)
267            if ok
268              return conv2value(pa)
269            else
270              raise "Wrong type!"
271            end
272          end
273        end
274
275        @writer.ele("value", val)
276    end
277
278    def wrong_type(value)
279      false
280    end
281
282
283  end # class Create
284
285end # module XMLRPC
286
287