[shared] use plain remove! and use the new HTTP interface
[opensuse:shared-resources.git] / buildservice / lib / activexml / node.rb
1 require 'xml'
2
3 module ActiveXML
4
5   #basic outline of the xml parser abstraction
6   module XMLAdapters
7     # this adapter defines all available methods. specialized adapters subclass from it.
8     # as much methods as possible should be implemented using more basic methods,
9     # even if the implementations are unoptimized, so that the specialized adapters
10     # can be implemented with low effort.
11     class AbstractAdapter
12       def has_element?( name )
13       end
14
15       def append_node( node )
16       end
17       alias_method '<<', :append_node
18
19       def add_node_after( node, prev_name )
20       end
21
22       def add_node_before( node, succ_name )
23       end
24
25       def remove_node( node )
26       end
27     end
28
29     class RexmlTreeAdapter < AbstractAdapter
30     end
31
32     class REXMLStreamAdapter < AbstractAdapter
33     end
34   end
35
36   class LibXMLNode
37
38     @@elements = {}
39
40     class << self
41
42       def setup
43         @@logger = ActiveXML::Config.logger
44       end
45
46       def get_class(element_name)
47         # FIXME: lines below don't work with relations. the related model has to
48         # be pulled in when the relation is defined
49         #
50         # axbase_subclasses = ActiveXML::Base.subclasses.map {|sc| sc.downcase}
51         # if axbase_subclasses.include?( element_name )
52
53         if @@elements.include? element_name
54           return @@elements[element_name]
55         end
56         return ActiveXML::LibXMLNode
57       end
58
59       #creates an empty xml document
60       # FIXME: works only for projects/packages, or by overwriting it in the model definition
61       # FIXME: could get info somehow from schema, as soon as schema evaluation is built in
62       def make_stub(opt)
63         logger.debug "--> creating stub element for #{self.name}, arguments: #{opt.inspect}"
64         if opt.nil?
65           raise CreationError, "Tried to create document without opt parameter"
66         end
67         root_tag_name = self.name.downcase
68         doc = XML::Document.new
69         doc.root = XML::Node.new root_tag_name
70         doc.root['name'] = opt[:name]
71         doc.root['created'] = opt[:created_at] if opt[:created_at]
72         doc.root['updated'] = opt[:updated_at] if opt[:updated_at]
73         doc.root << XML::Node.new('title')
74         doc.root << XML::Node.new('description')
75         doc.root
76       end
77
78       def logger
79         ActiveXML::Config.logger
80       end
81
82       def handles_xml_element (*elements)
83         elements.each do |elem|
84           @@elements[elem] = self
85         end
86       end
87
88       def xml_attr_reader (*attrs)
89         attrs.each do |attr|
90           class_eval do
91             define_method(attr.to_s) do
92               data.attributes[attr.to_s]
93             end
94           end
95         end
96       end
97
98       def xml_attr_writer (*attrs)
99         attrs.each do |attr|
100           class_eval do
101             define_method(attr.to_s+'=') do |new_value|
102 #              if data.attributes[attr.to_s].nil?
103 #                data.add_attribute attr.to_s, new_value.to_s
104 #              else
105                 data.attributes[attr.to_s] = new_value.to_s
106 #              end
107             end
108           end
109         end
110       end
111
112     end
113
114     #instance methods
115
116     attr_reader :data
117     attr_accessor :throw_on_method_missing
118
119     def initialize( data )
120       if data.kind_of? XML::Node
121         @data = data
122       elsif data.kind_of? String
123         self.raw_data = data
124       elsif data.kind_of? Hash
125         #create new
126         @data = self.class.make_stub(data)
127       else
128         raise "constructor needs either XML::Node, String or Hash"
129       end
130
131       @throw_on_method_missing = true
132       @node_cache = {}
133     end
134
135     def parse(data)
136       raise ParseError.new('Empty XML passed!') if data.empty?
137       begin
138         @data = XML::Parser.string(data.to_str.strip).parse.root
139       rescue => e
140         logger.error "Error parsing XML: #{e}"
141         logger.error "XML content was: #{data}"
142         raise ParseError.new e.message
143       end
144     end
145     private :parse
146
147     def raw_data=( data )
148       if data.kind_of? XML::Node
149         @data = data.clone
150       else
151         if ActiveXML::Config.lazy_evaluation
152           @raw_data = data.clone
153         else
154           parse(data)
155         end
156       end
157     end
158
159     def element_name
160       data.name
161     end
162
163     def data
164       if !@data && @raw_data
165          parse(@raw_data)
166       end
167       @data
168     end
169
170     def text
171       #puts 'text -%s- -%s-' % [data.inner_xml, data.content]
172       data.content
173     end
174
175     def text= (what)
176       data.content = what.to_xs
177     end
178
179     def define_iterator_for_element( elem )
180       logger.debug "2> starting to define iterator for element '#{elem}'"
181
182       eval <<-end_eval
183       def each_#{elem}
184         return nil if not has_element? '#{elem}'
185         result = Array.new
186         data.elements.each('#{elem}') do |e|
187           result << node = create_node_with_relations(e)
188           yield node if block_given?
189         end
190         result
191       end
192       end_eval
193     end
194     #private :define_iterator_for_element
195
196
197     def each
198       result = Array.new
199       data.each_element do |e|
200         result << node = create_node_with_relations(e)
201         yield node if block_given?
202       end
203       return result
204     end
205
206     def each_with_index
207       result = Array.new
208       index = 0
209       data.each_element do |e|
210         result << node = create_node_with_relations(e)
211         yield node, index if block_given?
212         index = index + 1
213       end
214       return result
215     end
216
217
218     def logger
219       self.class.logger
220     end
221
222     def to_s
223       # rexml: data.texts.map {|t| t.value}.to_s or ""
224       ret = ''
225       data.each do |node|
226         if node.node_type == LibXML::XML::Node::TEXT_NODE
227           ret += node.content
228         end
229       end
230       ret
231     end
232
233     def marshal_dump
234       [@throw_on_method_missing, @node_cache, dump_xml]
235     end
236
237     def marshal_load(dumped)
238       @throw_on_method_missing, @node_cache, @raw_data = *dumped.shift(3)
239       @data = nil
240     end
241
242     def dump_xml
243       if @data.nil?
244         @raw_data
245       else
246         data.to_s
247       end
248     end
249
250     def to_param
251       data.attributes['name']
252     end
253
254     def add_node(node)
255       xmlnode = LibXMLNode.new(node).data
256       data << data.doc.import(xmlnode)
257     end
258
259     def add_element ( element, attrs=nil )
260       raise "First argument must be an element name" if element.nil?
261       el = XML::Node.new(element)
262       data << el
263       attrs.each do |key, value|
264         el.attributes[key]=value
265       end if attrs.kind_of? Hash
266       LibXMLNode.new(el)
267     end
268
269     #tests if a child element exists matching the given query.
270     #query can either be an element name, an xpath, or any object
271     #whose to_s method evaluates to an element name or xpath
272     def has_element?( query )
273       not data.find_first(query.to_s).nil?
274     end
275
276     def has_elements?
277       # need to check for actual elements. Just a children can also mean
278       # text node
279       data.each_element { |e| return true }
280       return false
281     end
282
283     def has_attribute?( query )
284       not data.attributes.get_attribute(query).nil?
285     end
286
287     def has_attributes?
288       data.attributes?
289     end
290
291     def delete_attribute( name )
292       data.attributes.get_attribute(name).remove!
293     end
294
295     def delete_element( elem )
296       if elem.kind_of? LibXMLNode
297         elem.data.remove!
298       elsif elem.kind_of? LibXML::XML::Node
299         elem.remove!
300       else
301         e = data.find_first(elem.to_s)
302         e.remove! if e
303       end
304     end
305
306     #removes all elements after the last named from @data and return in list
307     def split_data_after( element_name )
308       return false if not element_name
309
310       element_name = element_name.to_s
311
312       state = :before_element
313       elem_cache = []
314       data.each_element do |elem|
315         case state
316         when :before_element
317           next if elem.name != element_name
318           state = :element
319           redo
320         when :element
321           next if elem.name == element_name
322           state = :after_element
323           redo
324         when :after_element
325           elem_cache << elem
326           elem.remove!
327         end
328       end
329
330       elem_cache
331     end
332
333     def merge_data( elem_list )
334       elem_list.each do |elem|
335         data << data.doc.import(elem)
336       end
337     end
338
339     def create_node_with_relations( element )
340       #FIXME: relation stuff should be taken into an extra module
341       #puts element.name
342       klass = self.class.get_class(element.name)
343       opt = {}
344       node = nil
345       node ||= klass.new(element)
346       #logger.debug "created node: #{node.inspect}"
347       return node
348     end
349
350     def value( symbol) 
351       return nil unless data
352
353       symbols = symbol.to_s
354
355       if data.attributes[symbols]
356         return data.attributes[symbols]
357       end
358
359       elem = data.find_first(symbols)
360       if elem
361         return elem.content
362       end
363
364       return nil
365     end
366
367     def method_missing( symbol, *args, &block )
368       #logger.debug "called method: #{symbol}(#{args.map do |a| a.inspect end.join ', '})"
369
370       symbols = symbol.to_s
371       if( symbols =~ /^each_(.*)$/ )
372         elem = $1
373         query = args[0]
374         if query
375           elem = "#{elem}[#{query}]"
376         end
377         return [] if not has_element? elem
378         result = Array.new
379         data.find(elem).each do |e|
380           result << node = create_node_with_relations(e)
381           block.call(node) if block
382         end
383         return result
384       end
385
386       return nil unless data
387
388       if data.attributes[symbols]
389         return data.attributes[symbols]
390       end
391
392       begin
393         datasym = data.find_first(symbols)
394       rescue LibXML::XML::Error
395         return unless @throw_on_method_missing
396         super( symbol, *args )
397       end
398       unless datasym.nil?
399         xpath = args.shift
400         query = xpath ? "#{symbol}[#{xpath}]" : symbols
401         #logger.debug "method_missing: query is '#{query}'"
402         if @node_cache[query]
403           node = @node_cache[query]
404           #logger.debug "taking from cache: #{node.inspect.to_s.slice(0..100)}"
405         else
406           e = data.find_first(query)
407           return nil if e.nil?
408
409           node = create_node_with_relations(e)
410
411           @node_cache[query] = node
412         end
413         return node
414       end
415
416       return unless @throw_on_method_missing
417       super( symbol, *args )
418     end
419   end
420
421   class XMLNode < LibXMLNode
422   end
423
424 end