[shared] avoid relying on XML parsers in make_stub functions
[opensuse:shared-resources.git] / buildservice / lib / activexml / node.rb
1 require 'xml'
2
3 module ActiveXML
4
5   class LibXMLNode
6
7     @@elements = {}
8
9     class << self
10
11       def get_class(element_name)
12         # FIXME: lines below don't work with relations. the related model has to
13         # be pulled in when the relation is defined
14         #
15         # axbase_subclasses = ActiveXML::Base.subclasses.map {|sc| sc.downcase}
16         # if axbase_subclasses.include?( element_name )
17
18         if @@elements.include? element_name
19           return @@elements[element_name]
20         end
21         return ActiveXML::LibXMLNode
22       end
23
24       #creates an empty xml document
25       # FIXME: works only for projects/packages, or by overwriting it in the model definition
26       # FIXME: could get info somehow from schema, as soon as schema evaluation is built in
27       def make_stub(opt)
28         logger.debug "--> creating stub element for #{self.name}, arguments: #{opt.inspect}"
29         if opt.nil?
30           raise CreationError, "Tried to create document without opt parameter"
31         end
32         root_tag_name = self.name.downcase
33         doc = ActiveXML::Base.new("<#{root_tag_name}/>")
34         doc.set_attriute('name', opt[:name])
35         doc.set_attribute('created', opt[:created_at]) if opt[:created_at]
36         doc.set_attribute('updated', opt[:updated_at]) if opt[:updated_at]
37         doc.add_element 'title'
38         doc.add_element 'description'
39         doc
40       end
41
42       def logger
43         ActiveXML::Config.logger
44       end
45
46       def handles_xml_element (*elements)
47         elements.each do |elem|
48           @@elements[elem] = self
49         end
50       end
51
52     end
53
54     #instance methods
55
56     attr_reader :data
57     attr_accessor :throw_on_method_missing
58
59     def initialize( data )
60       if data.kind_of? XML::Node
61         @data = data
62       elsif data.kind_of? String
63         self.raw_data = data
64       elsif data.kind_of? Hash
65         #create new
66         stub = self.class.make_stub(data)
67         if stub.kind_of? String
68           self.raw_data = stub
69         elsif data.kind_of? LibXMLNode
70           self.raw_data = data.dump_xml
71         else
72           raise RuntimeError "make_stub should return LibXMLNode or String" 
73         end
74       elsif data.kind_of? LibXMLNode
75         self.raw_data = data.dump_xml
76       else
77         raise "constructor needs either XML::Node, String or Hash"
78       end
79
80       @throw_on_method_missing = true
81       @node_cache = {}
82     end
83
84     def parse(data)
85       raise ParseError.new('Empty XML passed!') if data.empty?
86       begin
87         @data = XML::Parser.string(data.to_str.strip).parse.root
88       rescue => e
89         logger.error "Error parsing XML: #{e}"
90         logger.error "XML content was: #{data}"
91         raise ParseError.new e.message
92       end
93     end
94     private :parse
95
96     def raw_data=( data )
97       if data.kind_of? XML::Node
98         @data = data.clone
99       else
100         if ActiveXML::Config.lazy_evaluation
101           @raw_data = data.clone
102           @data = nil
103         else
104           parse(data)
105         end
106       end
107     end
108
109     def element_name
110       data.name
111     end
112
113     def element_name=(name)
114       data.name = name
115     end
116
117     def data
118       if !@data && @raw_data
119         parse(@raw_data)
120         # save memory
121         @raw_data = nil
122       end
123       @data
124     end
125     private :data
126
127     def text
128       #puts 'text -%s- -%s-' % [data.inner_xml, data.content]
129       data.content
130     end
131
132     def text= (what)
133       data.content = what.to_xs
134     end
135
136     def each(symbol = nil)
137       result = Array.new
138       each_with_index(symbol) do |node, index|
139         result << node
140         yield node if block_given?
141       end
142       return result
143     end
144
145     def each_with_index(symbol = nil)
146       unless block_given?
147         raise RuntimeError "use each instead"
148       end
149       index = 0
150       nodes = Array.new
151       if symbol.nil?
152         data.each_element { |e| nodes << e }
153       else
154         data.find(symbol.to_s).each { |e| nodes << e }
155       end
156       nodes.each do |e|
157         yield create_node_with_relations(e), index
158         index = index + 1
159       end
160     end
161
162     def find_first(symbol)
163       data.find(symbol.to_s).each do |e|
164         return create_node_with_relations(e)
165       end
166       return nil
167     end
168
169     def logger
170       self.class.logger
171     end
172
173     def to_s
174       ret = ''
175       data.each do |node|
176         if node.node_type == LibXML::XML::Node::TEXT_NODE
177           ret += node.content
178         end
179       end
180       ret
181     end
182
183     def marshal_dump
184       [@throw_on_method_missing, @node_cache, dump_xml]
185     end
186
187     def marshal_load(dumped)
188       @throw_on_method_missing, @node_cache, @raw_data = *dumped.shift(3)
189       @data = nil
190     end
191
192     def dump_xml
193       if @data.nil?
194         @raw_data
195       else
196         data.to_s
197       end
198     end
199
200     def to_param
201       data.attributes['name']
202     end
203
204     def add_node(node)
205       raise ArgumentError, "argument must be a string" unless node.kind_of? String
206       xmlnode = data.doc.import(XML::Parser.string(node.to_s).parse.root)
207       data << xmlnode
208       xmlnode
209     end
210
211     def add_element ( element, attrs=nil )
212       raise "First argument must be an element name" if element.nil?
213       el = XML::Node.new(element)
214       data << el
215       attrs.each do |key, value|
216         el.attributes[key]=value
217       end if attrs.kind_of? Hash
218       LibXMLNode.new(el)
219     end
220
221     #tests if a child element exists matching the given query.
222     #query can either be an element name, an xpath, or any object
223     #whose to_s method evaluates to an element name or xpath
224     def has_element?( query )
225       not data.find_first(query.to_s).nil?
226     end
227
228     def has_elements?
229       # need to check for actual elements. Just a children can also mean
230       # text node
231       data.each_element { |e| return true }
232       return false
233     end
234
235     def has_attribute?( query )
236       not data.attributes.get_attribute(query).nil?
237     end
238
239     def has_attributes?
240       data.attributes?
241     end
242
243     def delete_attribute( name )
244       data.attributes.get_attribute(name).remove!
245     end
246
247     def delete_element( elem )
248       if elem.kind_of? LibXMLNode
249         raise RuntimeError, "NO GOOD IDEA!" unless self.internal_data.doc == elem.internal_data.doc
250         elem.internal_data.remove!
251       elsif elem.kind_of? LibXML::XML::Node
252         raise RuntimeError, "this should be obsolete!!!"
253         elem.remove!
254       else
255         e = data.find_first(elem.to_s)
256         e.remove! if e
257       end
258     end
259
260     def set_attribute( name, value)
261        data.attributes[name] = value
262     end
263
264     def create_node_with_relations( element )
265       #FIXME: relation stuff should be taken into an extra module
266       #puts element.name
267       klass = self.class.get_class(element.name)
268       opt = {}
269       node = nil
270       node ||= klass.new(element)
271       #logger.debug "created node: #{node.inspect}"
272       return node
273     end
274
275     def value( symbol) 
276       return nil unless data
277
278       symbols = symbol.to_s
279
280       if data.attributes[symbols]
281         return data.attributes[symbols]
282       end
283
284       elem = data.find_first(symbols)
285       if elem
286         return elem.content
287       end
288
289       return nil
290     end
291
292     def method_missing( symbol, *args, &block )
293       logger.debug "called method: #{symbol}(#{args.map do |a| a.inspect end.join ', '})"
294
295       symbols = symbol.to_s
296       if( symbols =~ /^each_(.*)$/ )
297         elem = $1
298         query = args[0]
299         if query
300           elem = "#{elem}[#{query}]"
301         end
302         return [] if not has_element? elem
303         result = Array.new
304         data.find(elem).each do |e|
305           result << node = create_node_with_relations(e)
306           block.call(node) if block
307         end
308         return result
309       end
310
311       return nil unless data
312
313       if data.attributes[symbols]
314         return data.attributes[symbols]
315       end
316
317       begin
318         datasym = data.find_first(symbols)
319       rescue LibXML::XML::Error
320         return unless @throw_on_method_missing
321         super( symbol, *args )
322       end
323       unless datasym.nil?
324         xpath = args.shift
325         query = xpath ? "#{symbol}[#{xpath}]" : symbols
326         #logger.debug "method_missing: query is '#{query}'"
327         if @node_cache[query]
328           node = @node_cache[query]
329           #logger.debug "taking from cache: #{node.inspect.to_s.slice(0..100)}"
330         else
331           e = data.find_first(query)
332           return nil if e.nil?
333
334           node = create_node_with_relations(e)
335
336           @node_cache[query] = node
337         end
338         return node
339       end
340
341       return unless @throw_on_method_missing
342       super( symbol, *args )
343     end
344
345     # stay away from this
346     def internal_data #nodoc
347       data
348     end
349     protected :internal_data
350   end
351
352   class XMLNode < LibXMLNode
353   end
354
355 end
356
357 LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER)