Skip to content
This repository
Browse code

Update bundled xml-simple to 1.0.11 and prefer installed gems

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@7829 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
commit bf724d4acc600106e04b1ecd7085949fbbbb24d5 1 parent e7040b0
Jeremy Kemper authored October 10, 2007
1,021  activesupport/lib/active_support/vendor/xml_simple.rb
... ...
@@ -1,1021 +0,0 @@
1  
-# = XmlSimple
2  
-#
3  
-# Author::    Maik Schmidt <contact@maik-schmidt.de>
4  
-# Copyright:: Copyright (c) 2003-2006 Maik Schmidt
5  
-# License::   Distributes under the same terms as Ruby.
6  
-#
7  
-require 'rexml/document'
8  
-require 'stringio'
9  
-
10  
-# Easy API to maintain XML (especially configuration files).
11  
-class XmlSimple
12  
-  include REXML
13  
-
14  
-  @@VERSION = '1.0.9'
15  
-
16  
-  # A simple cache for XML documents that were already transformed
17  
-  # by xml_in.
18  
-  class Cache #:nodoc:
19  
-    # Creates and initializes a new Cache object.
20  
-    def initialize
21  
-      @mem_share_cache = {}
22  
-      @mem_copy_cache  = {}
23  
-    end
24  
-
25  
-    # Saves a data structure into a file.
26  
-    # 
27  
-    # data::
28  
-    #   Data structure to be saved.
29  
-    # filename::
30  
-    #   Name of the file belonging to the data structure.
31  
-    def save_storable(data, filename)
32  
-      cache_file = get_cache_filename(filename)
33  
-      File.open(cache_file, "w+") { |f| Marshal.dump(data, f) }
34  
-    end
35  
-
36  
-    # Restores a data structure from a file. If restoring the data
37  
-    # structure failed for any reason, nil will be returned.
38  
-    #
39  
-    # filename::
40  
-    #   Name of the file belonging to the data structure.
41  
-    def restore_storable(filename)
42  
-      cache_file = get_cache_filename(filename)
43  
-      return nil unless File::exist?(cache_file)
44  
-      return nil unless File::mtime(cache_file).to_i > File::mtime(filename).to_i
45  
-      data = nil
46  
-      File.open(cache_file) { |f| data = Marshal.load(f) }
47  
-      data
48  
-    end
49  
-
50  
-    # Saves a data structure in a shared memory cache.
51  
-    #
52  
-    # data::
53  
-    #   Data structure to be saved.
54  
-    # filename::
55  
-    #   Name of the file belonging to the data structure.
56  
-    def save_mem_share(data, filename)
57  
-      @mem_share_cache[filename] = [Time::now.to_i, data]
58  
-    end
59  
-
60  
-    # Restores a data structure from a shared memory cache. You
61  
-    # should consider these elements as "read only". If restoring
62  
-    # the data structure failed for any reason, nil will be
63  
-    # returned.
64  
-    #
65  
-    # filename::
66  
-    #   Name of the file belonging to the data structure.
67  
-    def restore_mem_share(filename)
68  
-      get_from_memory_cache(filename, @mem_share_cache)
69  
-    end
70  
-
71  
-    # Copies a data structure to a memory cache.
72  
-    #
73  
-    # data::
74  
-    #   Data structure to be copied.
75  
-    # filename::
76  
-    #   Name of the file belonging to the data structure.
77  
-    def save_mem_copy(data, filename)
78  
-      @mem_share_cache[filename] = [Time::now.to_i, Marshal.dump(data)]
79  
-    end
80  
-
81  
-    # Restores a data structure from a memory cache. If restoring
82  
-    # the data structure failed for any reason, nil will be
83  
-    # returned.
84  
-    #
85  
-    # filename::
86  
-    #   Name of the file belonging to the data structure.
87  
-    def restore_mem_copy(filename)
88  
-      data = get_from_memory_cache(filename, @mem_share_cache)
89  
-      data = Marshal.load(data) unless data.nil?
90  
-      data
91  
-    end
92  
-
93  
-    private
94  
-
95  
-    # Returns the "cache filename" belonging to a filename, i.e.
96  
-    # the extension '.xml' in the original filename will be replaced
97  
-    # by '.stor'. If filename does not have this extension, '.stor'
98  
-    # will be appended.
99  
-    #
100  
-    # filename::
101  
-    #   Filename to get "cache filename" for.
102  
-    def get_cache_filename(filename)
103  
-      filename.sub(/(\.xml)?$/, '.stor')
104  
-    end
105  
-
106  
-    # Returns a cache entry from a memory cache belonging to a
107  
-    # certain filename. If no entry could be found for any reason,
108  
-    # nil will be returned.
109  
-    #
110  
-    # filename::
111  
-    #   Name of the file the cache entry belongs to.
112  
-    # cache::
113  
-    #   Memory cache to get entry from.
114  
-    def get_from_memory_cache(filename, cache)
115  
-      return nil unless cache[filename]
116  
-      return nil unless cache[filename][0] > File::mtime(filename).to_i
117  
-      return cache[filename][1]
118  
-    end
119  
-  end
120  
-
121  
-  # Create a "global" cache.
122  
-  @@cache = Cache.new
123  
-
124  
-  # Creates and initializes a new XmlSimple object.
125  
-  # 
126  
-  # defaults::
127  
-  #   Default values for options.
128  
-  def initialize(defaults = nil)
129  
-    unless defaults.nil? || defaults.instance_of?(Hash)
130  
-      raise ArgumentError, "Options have to be a Hash."
131  
-    end
132  
-    @default_options = normalize_option_names(defaults, KNOWN_OPTIONS['in'] & KNOWN_OPTIONS['out'])
133  
-    @options = Hash.new
134  
-    @_var_values = nil
135  
-  end
136  
-
137  
-  # Converts an XML document in the same way as the Perl module XML::Simple.
138  
-  #
139  
-  # string::
140  
-  #   XML source. Could be one of the following:
141  
-  #
142  
-  #   - nil: Tries to load and parse '<scriptname>.xml'.
143  
-  #   - filename: Tries to load and parse filename.
144  
-  #   - IO object: Reads from object until EOF is detected and parses result.
145  
-  #   - XML string: Parses string.
146  
-  #   
147  
-  # options::
148  
-  #   Options to be used.
149  
-  def xml_in(string = nil, options = nil)
150  
-    handle_options('in', options)
151  
-
152  
-    # If no XML string or filename was supplied look for scriptname.xml.
153  
-    if string.nil?
154  
-      string = File::basename($0)
155  
-      string.sub!(/\.[^.]+$/, '')
156  
-      string += '.xml'
157  
-
158  
-      directory = File::dirname($0)
159  
-      @options['searchpath'].unshift(directory) unless directory.nil?
160  
-    end
161  
-
162  
-    if string.instance_of?(String)
163  
-      if string =~ /<.*?>/m
164  
-        @doc = parse(string)
165  
-      elsif string == '-'
166  
-        @doc = parse($stdin.readlines.to_s)
167  
-      else
168  
-        filename = find_xml_file(string, @options['searchpath'])
169  
-
170  
-        if @options.has_key?('cache')
171  
-          @options['cache'].each { |scheme|
172  
-            case(scheme)
173  
-            when 'storable'
174  
-              content = @@cache.restore_storable(filename)
175  
-            when 'mem_share'
176  
-              content = @@cache.restore_mem_share(filename)
177  
-            when 'mem_copy'
178  
-              content = @@cache.restore_mem_copy(filename)
179  
-            else
180  
-              raise ArgumentError, "Unsupported caching scheme: <#{scheme}>."
181  
-            end
182  
-            return content if content
183  
-          }
184  
-        end
185  
-        
186  
-        @doc = load_xml_file(filename)
187  
-      end
188  
-    elsif string.kind_of?(IO) || string.kind_of?(StringIO)
189  
-      @doc = parse(string.readlines.to_s)
190  
-    else
191  
-      raise ArgumentError, "Could not parse object of type: <#{string.type}>."
192  
-    end
193  
-
194  
-    result = collapse(@doc.root)
195  
-    result = @options['keeproot'] ? merge({}, @doc.root.name, result) : result
196  
-    put_into_cache(result, filename)
197  
-    result
198  
-  end
199  
-
200  
-  # This is the functional version of the instance method xml_in.
201  
-  def XmlSimple.xml_in(string = nil, options = nil)
202  
-    xml_simple = XmlSimple.new
203  
-    xml_simple.xml_in(string, options)
204  
-  end
205  
-  
206  
-  # Converts a data structure into an XML document.
207  
-  #
208  
-  # ref::
209  
-  #   Reference to data structure to be converted into XML.
210  
-  # options::
211  
-  #   Options to be used.
212  
-  def xml_out(ref, options = nil)
213  
-    handle_options('out', options)
214  
-    if ref.instance_of?(Array)
215  
-      ref = { @options['anonymoustag'] => ref }
216  
-    end
217  
-
218  
-    if @options['keeproot']
219  
-      keys = ref.keys
220  
-      if keys.size == 1
221  
-        ref = ref[keys[0]]
222  
-        @options['rootname'] = keys[0]
223  
-      end
224  
-    elsif @options['rootname'] == ''
225  
-      if ref.instance_of?(Hash)
226  
-        refsave = ref
227  
-        ref = {}
228  
-        refsave.each { |key, value|
229  
-          if !scalar(value)
230  
-            ref[key] = value
231  
-          else
232  
-            ref[key] = [ value.to_s ]
233  
-          end
234  
-        }
235  
-      end
236  
-    end
237  
-
238  
-    @ancestors = []
239  
-    xml = value_to_xml(ref, @options['rootname'], '')
240  
-    @ancestors = nil
241  
-
242  
-    if @options['xmldeclaration']
243  
-      xml = @options['xmldeclaration'] + "\n" + xml
244  
-    end
245  
-
246  
-    if @options.has_key?('outputfile')
247  
-      if @options['outputfile'].kind_of?(IO)
248  
-        return @options['outputfile'].write(xml)
249  
-      else
250  
-        File.open(@options['outputfile'], "w") { |file| file.write(xml) }
251  
-      end
252  
-    end
253  
-    xml
254  
-  end
255  
-
256  
-  # This is the functional version of the instance method xml_out.
257  
-  def XmlSimple.xml_out(hash, options = nil)
258  
-    xml_simple = XmlSimple.new
259  
-    xml_simple.xml_out(hash, options)
260  
-  end
261  
-  
262  
-  private
263  
-
264  
-  # Declare options that are valid for xml_in and xml_out.
265  
-  KNOWN_OPTIONS = {
266  
-    'in'  => %w(
267  
-      keyattr keeproot forcecontent contentkey noattr
268  
-      searchpath forcearray suppressempty anonymoustag
269  
-      cache grouptags normalisespace normalizespace
270  
-      variables varattr keytosymbol
271  
-    ),
272  
-    'out' => %w(
273  
-      keyattr keeproot contentkey noattr rootname
274  
-      xmldeclaration outputfile noescape suppressempty
275  
-      anonymoustag indent grouptags noindent
276  
-    )
277  
-  }
278  
-
279  
-  # Define some reasonable defaults.
280  
-  DEF_KEY_ATTRIBUTES  = []
281  
-  DEF_ROOT_NAME       = 'opt'
282  
-  DEF_CONTENT_KEY     = 'content'
283  
-  DEF_XML_DECLARATION = "<?xml version='1.0' standalone='yes'?>"
284  
-  DEF_ANONYMOUS_TAG   = 'anon'
285  
-  DEF_FORCE_ARRAY     = true
286  
-  DEF_INDENTATION     = '  '
287  
-  DEF_KEY_TO_SYMBOL   = false
288  
-  
289  
-  # Normalizes option names in a hash, i.e., turns all
290  
-  # characters to lower case and removes all underscores.
291  
-  # Additionally, this method checks, if an unknown option
292  
-  # was used and raises an according exception.
293  
-  #
294  
-  # options::
295  
-  #   Hash to be normalized.
296  
-  # known_options::
297  
-  #   List of known options.
298  
-  def normalize_option_names(options, known_options)
299  
-    return nil if options.nil?
300  
-    result = Hash.new
301  
-    options.each { |key, value|
302  
-      lkey = key.downcase
303  
-      lkey.gsub!(/_/, '')
304  
-      if !known_options.member?(lkey)
305  
-        raise ArgumentError, "Unrecognised option: #{lkey}."
306  
-      end
307  
-      result[lkey] = value
308  
-    }
309  
-    result
310  
-  end
311  
-  
312  
-  # Merges a set of options with the default options.
313  
-  # 
314  
-  # direction::
315  
-  #  'in':  If options should be handled for xml_in.
316  
-  #  'out': If options should be handled for xml_out.
317  
-  # options::
318  
-  #   Options to be merged with the default options.
319  
-  def handle_options(direction, options)
320  
-    @options = options || Hash.new
321  
-
322  
-    raise ArgumentError, "Options must be a Hash!" unless @options.instance_of?(Hash)
323  
-
324  
-    unless KNOWN_OPTIONS.has_key?(direction)
325  
-      raise ArgumentError, "Unknown direction: <#{direction}>."
326  
-    end
327  
-
328  
-    known_options = KNOWN_OPTIONS[direction]
329  
-    @options = normalize_option_names(@options, known_options)
330  
-
331  
-    unless @default_options.nil?
332  
-      known_options.each { |option|
333  
-        unless @options.has_key?(option)
334  
-          if @default_options.has_key?(option)
335  
-            @options[option] = @default_options[option]
336  
-          end
337  
-        end
338  
-      }
339  
-    end
340  
-
341  
-    unless @options.has_key?('noattr')
342  
-        @options['noattr'] = false
343  
-    end
344  
-
345  
-    if @options.has_key?('rootname')
346  
-      @options['rootname'] = '' if @options['rootname'].nil?
347  
-    else
348  
-      @options['rootname'] = DEF_ROOT_NAME
349  
-    end
350  
-
351  
-    if @options.has_key?('xmldeclaration') && @options['xmldeclaration'] == true
352  
-      @options['xmldeclaration'] = DEF_XML_DECLARATION
353  
-    end
354  
-
355  
-    @options['keytosymbol'] = DEF_KEY_TO_SYMBOL unless @options.has_key?('keytosymbol')
356  
-
357  
-    if @options.has_key?('contentkey')
358  
-      if @options['contentkey'] =~ /^-(.*)$/
359  
-        @options['contentkey']    = $1
360  
-        @options['collapseagain'] = true
361  
-      end
362  
-    else
363  
-      @options['contentkey'] = DEF_CONTENT_KEY
364  
-    end
365  
-
366  
-    unless @options.has_key?('normalisespace')
367  
-      @options['normalisespace'] = @options['normalizespace']
368  
-    end
369  
-    @options['normalisespace'] = 0 if @options['normalisespace'].nil?
370  
-
371  
-    if @options.has_key?('searchpath')
372  
-      unless @options['searchpath'].instance_of?(Array)
373  
-        @options['searchpath'] = [ @options['searchpath'] ]
374  
-      end
375  
-    else
376  
-      @options['searchpath'] = []
377  
-    end
378  
-
379  
-    if @options.has_key?('cache') && scalar(@options['cache'])
380  
-      @options['cache'] = [ @options['cache'] ]
381  
-    end
382  
-
383  
-    @options['anonymoustag'] = DEF_ANONYMOUS_TAG unless @options.has_key?('anonymoustag')
384  
-
385  
-    if !@options.has_key?('indent') || @options['indent'].nil?
386  
-      @options['indent'] = DEF_INDENTATION
387  
-    end
388  
-
389  
-    @options['indent'] = '' if @options.has_key?('noindent')
390  
-
391  
-    # Special cleanup for 'keyattr' which could be an array or
392  
-    # a hash or left to default to array.
393  
-    if @options.has_key?('keyattr')
394  
-      if !scalar(@options['keyattr'])
395  
-        # Convert keyattr => { elem => '+attr' }
396  
-        #      to keyattr => { elem => ['attr', '+'] }
397  
-        if @options['keyattr'].instance_of?(Hash)
398  
-          @options['keyattr'].each { |key, value|
399  
-            if value =~ /^([-+])?(.*)$/
400  
-              @options['keyattr'][key] = [$2, $1 ? $1 : '']
401  
-            end
402  
-          }
403  
-        elsif !@options['keyattr'].instance_of?(Array)
404  
-          raise ArgumentError, "'keyattr' must be String, Hash, or Array!"
405  
-        end
406  
-      else
407  
-        @options['keyattr'] = [ @options['keyattr'] ]
408  
-      end
409  
-    else
410  
-      @options['keyattr'] = DEF_KEY_ATTRIBUTES
411  
-    end
412  
-
413  
-    if @options.has_key?('forcearray')
414  
-      if @options['forcearray'].instance_of?(Regexp)
415  
-        @options['forcearray'] = [ @options['forcearray'] ]
416  
-      end
417  
-
418  
-      if @options['forcearray'].instance_of?(Array)
419  
-        force_list = @options['forcearray']
420  
-        unless force_list.empty?
421  
-          @options['forcearray'] = {}
422  
-          force_list.each { |tag|
423  
-            if tag.instance_of?(Regexp)
424  
-              unless @options['forcearray']['_regex'].instance_of?(Array)
425  
-                @options['forcearray']['_regex'] = []
426  
-              end
427  
-              @options['forcearray']['_regex'] << tag
428  
-            else
429  
-              @options['forcearray'][tag] = true
430  
-            end
431  
-          }
432  
-        else
433  
-          @options['forcearray'] = false
434  
-        end
435  
-      else
436  
-        @options['forcearray'] = @options['forcearray'] ? true : false
437  
-      end
438  
-    else
439  
-      @options['forcearray'] = DEF_FORCE_ARRAY
440  
-    end
441  
-
442  
-    if @options.has_key?('grouptags') && !@options['grouptags'].instance_of?(Hash)
443  
-      raise ArgumentError, "Illegal value for 'GroupTags' option - expected a Hash."
444  
-    end
445  
-
446  
-    if @options.has_key?('variables') && !@options['variables'].instance_of?(Hash)
447  
-      raise ArgumentError, "Illegal value for 'Variables' option - expected a Hash."
448  
-    end
449  
-
450  
-    if @options.has_key?('variables')
451  
-      @_var_values = @options['variables']
452  
-    elsif @options.has_key?('varattr')
453  
-      @_var_values = {}
454  
-    end
455  
-  end
456  
-
457  
-  # Actually converts an XML document element into a data structure.
458  
-  #
459  
-  # element::
460  
-  #   The document element to be collapsed.
461  
-  def collapse(element)
462  
-    result = @options['noattr'] ? {} : get_attributes(element)
463  
-
464  
-    if @options['normalisespace'] == 2
465  
-      result.each { |k, v| result[k] = normalise_space(v) }
466  
-    end
467  
-
468  
-    if element.has_elements?
469  
-      element.each_element { |child|
470  
-        value = collapse(child)
471  
-        if empty(value) && (element.attributes.empty? || @options['noattr'])
472  
-          next if @options.has_key?('suppressempty') && @options['suppressempty'] == true
473  
-        end
474  
-        result = merge(result, child.name, value)
475  
-      }
476  
-      if has_mixed_content?(element)
477  
-        # normalisespace?
478  
-        content = element.texts.map { |x| x.to_s }
479  
-        content = content[0] if content.size == 1
480  
-        result[@options['contentkey']] = content
481  
-      end
482  
-    elsif element.has_text? # i.e. it has only text.
483  
-      return collapse_text_node(result, element)
484  
-    end
485  
-
486  
-    # Turn Arrays into Hashes if key fields present.
487  
-    count = fold_arrays(result)
488  
-
489  
-    # Disintermediate grouped tags.
490  
-    if @options.has_key?('grouptags')
491  
-      result.each { |key, value|
492  
-        next unless (value.instance_of?(Hash) && (value.size == 1))
493  
-        child_key, child_value = value.to_a[0]
494  
-        if @options['grouptags'][key] == child_key
495  
-          result[key] = child_value
496  
-        end
497  
-      }
498  
-    end
499  
-    
500  
-    # Fold Hases containing a single anonymous Array up into just the Array.
501  
-    if count == 1 
502  
-      anonymoustag = @options['anonymoustag']
503  
-      if result.has_key?(anonymoustag) && result[anonymoustag].instance_of?(Array)
504  
-        return result[anonymoustag]
505  
-      end
506  
-    end
507  
-
508  
-    if result.empty? && @options.has_key?('suppressempty')
509  
-      return @options['suppressempty'] == '' ? '' : nil
510  
-    end
511  
-
512  
-    result
513  
-  end
514  
-
515  
-  # Collapses a text node and merges it with an existing Hash, if
516  
-  # possible.
517  
-  # Thanks to Curtis Schofield for reporting a subtle bug.
518  
-  #
519  
-  # hash::
520  
-  #   Hash to merge text node value with, if possible.
521  
-  # element::
522  
-  #   Text node to be collapsed.
523  
-  def collapse_text_node(hash, element)
524  
-    value = node_to_text(element)
525  
-    if empty(value) && !element.has_attributes?
526  
-      return {}
527  
-    end
528  
-
529  
-    if element.has_attributes? && !@options['noattr']
530  
-      return merge(hash, @options['contentkey'], value)
531  
-    else
532  
-      if @options['forcecontent']
533  
-        return merge(hash, @options['contentkey'], value)
534  
-      else
535  
-        return value
536  
-      end
537  
-    end
538  
-  end
539  
-
540  
-  # Folds all arrays in a Hash.
541  
-  # 
542  
-  # hash::
543  
-  #   Hash to be folded.
544  
-  def fold_arrays(hash)
545  
-    fold_amount = 0
546  
-    keyattr = @options['keyattr']
547  
-    if (keyattr.instance_of?(Array) || keyattr.instance_of?(Hash))
548  
-      hash.each { |key, value|
549  
-        if value.instance_of?(Array)
550  
-          if keyattr.instance_of?(Array)
551  
-            hash[key] = fold_array(value)
552  
-          else
553  
-            hash[key] = fold_array_by_name(key, value)
554  
-          end
555  
-          fold_amount += 1
556  
-        end
557  
-      }
558  
-    end
559  
-    fold_amount
560  
-  end
561  
-
562  
-  # Folds an Array to a Hash, if possible. Folding happens
563  
-  # according to the content of keyattr, which has to be
564  
-  # an array.
565  
-  #
566  
-  # array::
567  
-  #   Array to be folded.
568  
-  def fold_array(array)
569  
-    hash = Hash.new
570  
-    array.each { |x|
571  
-      return array unless x.instance_of?(Hash)
572  
-      key_matched = false
573  
-      @options['keyattr'].each { |key|
574  
-        if x.has_key?(key)
575  
-          key_matched = true
576  
-          value = x[key]
577  
-          return array if value.instance_of?(Hash) || value.instance_of?(Array)
578  
-          value = normalise_space(value) if @options['normalisespace'] == 1
579  
-          x.delete(key)
580  
-          hash[value] = x
581  
-          break
582  
-        end
583  
-      }
584  
-      return array unless key_matched
585  
-    }
586  
-    hash = collapse_content(hash) if @options['collapseagain']
587  
-    hash
588  
-  end
589  
-  
590  
-  # Folds an Array to a Hash, if possible. Folding happens
591  
-  # according to the content of keyattr, which has to be
592  
-  # a Hash.
593  
-  #
594  
-  # name::
595  
-  #   Name of the attribute to be folded upon.
596  
-  # array::
597  
-  #   Array to be folded.
598  
-  def fold_array_by_name(name, array)
599  
-    return array unless @options['keyattr'].has_key?(name)
600  
-    key, flag = @options['keyattr'][name]
601  
-
602  
-    hash = Hash.new
603  
-    array.each { |x|
604  
-      if x.instance_of?(Hash) && x.has_key?(key)
605  
-        value = x[key]
606  
-        return array if value.instance_of?(Hash) || value.instance_of?(Array)
607  
-        value = normalise_space(value) if @options['normalisespace'] == 1
608  
-        hash[value] = x
609  
-        hash[value]["-#{key}"] = hash[value][key] if flag == '-'
610  
-        hash[value].delete(key) unless flag == '+'
611  
-      else
612  
-        $stderr.puts("Warning: <#{name}> element has no '#{key}' attribute.")
613  
-        return array
614  
-      end
615  
-    }
616  
-    hash = collapse_content(hash) if @options['collapseagain']
617  
-    hash
618  
-  end
619  
-
620  
-  # Tries to collapse a Hash even more ;-)
621  
-  #
622  
-  # hash::
623  
-  #   Hash to be collapsed again.
624  
-  def collapse_content(hash)
625  
-    content_key = @options['contentkey']
626  
-    hash.each_value { |value|
627  
-      return hash unless value.instance_of?(Hash) && value.size == 1 && value.has_key?(content_key)
628  
-      hash.each_key { |key| hash[key] = hash[key][content_key] }
629  
-    }
630  
-    hash
631  
-  end
632  
-  
633  
-  # Adds a new key/value pair to an existing Hash. If the key to be added
634  
-  # does already exist and the existing value associated with key is not
635  
-  # an Array, it will be converted into an Array. Then the new value is
636  
-  # appended to that Array.
637  
-  #
638  
-  # hash::
639  
-  #   Hash to add key/value pair to.
640  
-  # key::
641  
-  #   Key to be added.
642  
-  # value::
643  
-  #   Value to be associated with key.
644  
-  def merge(hash, key, value)
645  
-    if value.instance_of?(String)
646  
-      value = normalise_space(value) if @options['normalisespace'] == 2
647  
-
648  
-      # do variable substitutions
649  
-      unless @_var_values.nil? || @_var_values.empty?
650  
-        value.gsub!(/\$\{(\w+)\}/) { |x| get_var($1) }
651  
-      end
652  
-      
653  
-      # look for variable definitions
654  
-      if @options.has_key?('varattr')
655  
-        varattr = @options['varattr']
656  
-        if hash.has_key?(varattr)
657  
-          set_var(hash[varattr], value)
658  
-        end
659  
-      end
660  
-    end
661  
-    
662  
-    #patch for converting keys to symbols
663  
-    if @options.has_key?('keytosymbol')
664  
-      if @options['keytosymbol'] == true
665  
-        key = key.to_s.downcase.to_sym
666  
-      end
667  
-    end
668  
-    
669  
-    if hash.has_key?(key)
670  
-      if hash[key].instance_of?(Array)
671  
-        hash[key] << value
672  
-      else
673  
-        hash[key] = [ hash[key], value ]
674  
-      end
675  
-    elsif value.instance_of?(Array) # Handle anonymous arrays.
676  
-      hash[key] = [ value ]
677  
-    else
678  
-      if force_array?(key)
679  
-        hash[key] = [ value ]
680  
-      else
681  
-        hash[key] = value
682  
-      end
683  
-    end
684  
-    hash
685  
-  end
686  
-  
687  
-  # Checks, if the 'forcearray' option has to be used for
688  
-  # a certain key.
689  
-  def force_array?(key)
690  
-    return false if key == @options['contentkey']
691  
-    return true if @options['forcearray'] == true
692  
-    forcearray = @options['forcearray']
693  
-    if forcearray.instance_of?(Hash)
694  
-      return true if forcearray.has_key?(key) 
695  
-      return false unless forcearray.has_key?('_regex')
696  
-      forcearray['_regex'].each { |x| return true if key =~ x }
697  
-    end
698  
-    return false
699  
-  end
700  
-  
701  
-  # Converts the attributes array of a document node into a Hash.
702  
-  # Returns an empty Hash, if node has no attributes.
703  
-  #
704  
-  # node::
705  
-  #   Document node to extract attributes from.
706  
-  def get_attributes(node)
707  
-    attributes = {}
708  
-    node.attributes.each { |n,v| attributes[n] = v }
709  
-    attributes
710  
-  end
711  
-  
712  
-  # Determines, if a document element has mixed content.
713  
-  #
714  
-  # element::
715  
-  #   Document element to be checked.
716  
-  def has_mixed_content?(element)
717  
-    if element.has_text? && element.has_elements?
718  
-      return true if element.texts.join('') !~ /^\s*$/s
719  
-    end
720  
-    false
721  
-  end
722  
-  
723  
-  # Called when a variable definition is encountered in the XML.
724  
-  # A variable definition looks like
725  
-  #    <element attrname="name">value</element>
726  
-  # where attrname matches the varattr setting.
727  
-  def set_var(name, value)
728  
-    @_var_values[name] = value
729  
-  end
730  
-
731  
-  # Called during variable substitution to get the value for the
732  
-  # named variable.
733  
-  def get_var(name)
734  
-    if @_var_values.has_key?(name)
735  
-      return @_var_values[name]
736  
-    else
737  
-      return "${#{name}}"
738  
-    end
739  
-  end
740  
-  
741  
-  # Recurses through a data structure building up and returning an
742  
-  # XML representation of that structure as a string.
743  
-  #
744  
-  # ref::
745  
-  #   Reference to the data structure to be encoded.
746  
-  # name::
747  
-  #   The XML tag name to be used for this item.
748  
-  # indent::
749  
-  #   A string of spaces for use as the current indent level.
750  
-  def value_to_xml(ref, name, indent)
751  
-    named = !name.nil? && name != ''
752  
-    nl    = @options.has_key?('noindent') ? '' : "\n"
753  
-
754  
-    if !scalar(ref)
755  
-      if @ancestors.member?(ref)
756  
-        raise ArgumentError, "Circular data structures not supported!"
757  
-      end
758  
-      @ancestors << ref
759  
-    else
760  
-      if named
761  
-        return [indent, '<', name, '>', @options['noescape'] ? ref.to_s : escape_value(ref.to_s), '</', name, '>', nl].join('')
762  
-      else
763  
-        return ref.to_s + nl
764  
-      end
765  
-    end
766  
-
767  
-    # Unfold hash to array if possible.
768  
-    if ref.instance_of?(Hash) && !ref.empty? && !@options['keyattr'].empty? && indent != ''
769  
-      ref = hash_to_array(name, ref)
770  
-    end
771  
-
772  
-    result = []
773  
-    if ref.instance_of?(Hash)
774  
-      # Reintermediate grouped values if applicable.
775  
-      if @options.has_key?('grouptags')
776  
-        ref.each { |key, value|
777  
-          if @options['grouptags'].has_key?(key)
778  
-            ref[key] = { @options['grouptags'][key] => value }
779  
-          end
780  
-        }
781  
-      end
782  
-      
783  
-      nested = []
784  
-      text_content = nil
785  
-      if named
786  
-        result << indent << '<' << name
787  
-      end
788  
-
789  
-      if !ref.empty?
790  
-        ref.each { |key, value|
791  
-          next if !key.nil? && key[0, 1] == '-'
792  
-          if value.nil?
793  
-            unless @options.has_key?('suppressempty') && @options['suppressempty'].nil?
794  
-              raise ArgumentError, "Use of uninitialized value!"
795  
-            end
796  
-            value = {}
797  
-          end
798  
-
799  
-          if !scalar(value) || @options['noattr']
800  
-            nested << value_to_xml(value, key, indent + @options['indent'])
801  
-          else
802  
-            value = value.to_s
803  
-            value = escape_value(value) unless @options['noescape']
804  
-            if key == @options['contentkey']
805  
-              text_content = value
806  
-            else
807  
-              result << ' ' << key << '="' << value << '"'
808  
-            end
809  
-          end
810  
-        }
811  
-      else
812  
-        text_content = ''
813  
-      end
814  
-
815  
-      if !nested.empty? || !text_content.nil?
816  
-        if named
817  
-          result << '>'
818  
-          if !text_content.nil?
819  
-            result << text_content
820  
-            nested[0].sub!(/^\s+/, '') if !nested.empty?
821  
-          else
822  
-            result << nl
823  
-          end
824  
-          if !nested.empty?
825  
-            result << nested << indent
826  
-          end
827  
-          result << '</' << name << '>' << nl
828  
-        else
829  
-          result << nested
830  
-        end
831  
-      else
832  
-        result << ' />' << nl
833  
-      end
834  
-    elsif ref.instance_of?(Array)
835  
-      ref.each { |value|
836  
-        if scalar(value)
837  
-          result << indent << '<' << name << '>'
838  
-          result << (@options['noescape'] ? value.to_s : escape_value(value.to_s))
839  
-          result << '</' << name << '>' << nl
840  
-        elsif value.instance_of?(Hash)
841  
-          result << value_to_xml(value, name, indent)
842  
-        else
843  
-          result << indent << '<' << name << '>' << nl
844  
-          result << value_to_xml(value, @options['anonymoustag'], indent + @options['indent'])
845  
-          result << indent << '</' << name << '>' << nl
846  
-        end
847  
-      }
848  
-    else
849  
-      # Probably, this is obsolete.
850  
-      raise ArgumentError, "Can't encode a value of type: #{ref.type}."
851  
-    end
852  
-    @ancestors.pop if !scalar(ref)
853  
-    result.join('')
854  
-  end
855  
-  
856  
-  # Checks, if a certain value is a "scalar" value. Whatever
857  
-  # that will be in Ruby ... ;-)
858  
-  # 
859  
-  # value::
860  
-  #   Value to be checked.
861  
-  def scalar(value)
862  
-    return false if value.instance_of?(Hash) || value.instance_of?(Array)
863  
-    return true
864  
-  end
865  
-
866  
-  # Attempts to unfold a hash of hashes into an array of hashes. Returns
867  
-  # a reference to th array on success or the original hash, if unfolding
868  
-  # is not possible.
869  
-  # 
870  
-  # parent::
871  
-  #   
872  
-  # hashref::
873  
-  #   Reference to the hash to be unfolded.
874  
-  def hash_to_array(parent, hashref)
875  
-    arrayref = []
876  
-    hashref.each { |key, value|
877  
-      return hashref unless value.instance_of?(Hash)
878  
-
879  
-      if @options['keyattr'].instance_of?(Hash)
880  
-        return hashref unless @options['keyattr'].has_key?(parent)
881  
-        arrayref << { @options['keyattr'][parent][0] => key }.update(value)
882  
-      else
883  
-        arrayref << { @options['keyattr'][0] => key }.update(value)
884  
-      end
885  
-    }
886  
-    arrayref
887  
-  end
888  
-  
889  
-  # Replaces XML markup characters by their external entities.
890  
-  #
891  
-  # data::
892  
-  #   The string to be escaped.
893  
-  def escape_value(data)
894  
-    Text::normalize(data)
895  
-  end
896  
-  
897  
-  # Removes leading and trailing whitespace and sequences of
898  
-  # whitespaces from a string.
899  
-  #
900  
-  # text::
901  
-  #   String to be normalised.
902  
-  def normalise_space(text)
903  
-    text.strip.gsub(/\s\s+/, ' ')
904  
-  end
905  
-
906  
-  # Checks, if an object is nil, an empty String or an empty Hash.
907  
-  # Thanks to Norbert Gawor for a bugfix.
908  
-  #
909  
-  # value::
910  
-  #   Value to be checked for emptyness.
911  
-  def empty(value)
912  
-    case value
913  
-      when Hash
914  
-        return value.empty?
915  
-      when String
916  
-        return value !~ /\S/m
917  
-      else
918  
-        return value.nil?
919  
-    end
920  
-  end
921  
-  
922  
-  # Converts a document node into a String.
923  
-  # If the node could not be converted into a String
924  
-  # for any reason, default will be returned.
925  
-  #
926  
-  # node::
927  
-  #   Document node to be converted.
928  
-  # default::
929  
-  #   Value to be returned, if node could not be converted.
930  
-  def node_to_text(node, default = nil)
931  
-    if node.instance_of?(REXML::Element) 
932  
-      node.texts.map { |t| t.value }.join('')
933  
-    elsif node.instance_of?(REXML::Attribute)
934  
-      node.value.nil? ? default : node.value.strip
935  
-    elsif node.instance_of?(REXML::Text)
936  
-      node.value.strip
937  
-    else
938  
-      default
939  
-    end
940  
-  end
941  
-
942  
-  # Parses an XML string and returns the according document.
943  
-  #
944  
-  # xml_string::
945  
-  #   XML string to be parsed.
946  
-  #
947  
-  # The following exception may be raised:
948  
-  #
949  
-  # REXML::ParseException::
950  
-  #   If the specified file is not wellformed.
951  
-  def parse(xml_string)
952  
-    Document.new(xml_string)
953  
-  end
954  
-  
955  
-  # Searches in a list of paths for a certain file. Returns
956  
-  # the full path to the file, if it could be found. Otherwise,
957  
-  # an exception will be raised.
958  
-  #
959  
-  # filename::
960  
-  #   Name of the file to search for.
961  
-  # searchpath::
962  
-  #   List of paths to search in.
963  
-  def find_xml_file(file, searchpath)
964  
-    filename = File::basename(file)
965  
-
966  
-    if filename != file
967  
-      return file if File::file?(file)
968  
-    else
969  
-      searchpath.each { |path|
970  
-        full_path = File::join(path, filename)
971  
-        return full_path if File::file?(full_path)
972  
-      }
973  
-    end
974  
-
975  
-    if searchpath.empty?
976  
-      return file if File::file?(file)
977  
-      raise ArgumentError, "File does not exist: #{file}."
978  
-    end
979  
-    raise ArgumentError, "Could not find <#{filename}> in <#{searchpath.join(':')}>"
980  
-  end
981  
-  
982  
-  # Loads and parses an XML configuration file.
983  
-  #
984  
-  # filename::
985  
-  #   Name of the configuration file to be loaded.
986  
-  #
987  
-  # The following exceptions may be raised:
988  
-  # 
989  
-  # Errno::ENOENT::
990  
-  #   If the specified file does not exist.
991  
-  # REXML::ParseException::
992  
-  #   If the specified file is not wellformed.
993  
-  def load_xml_file(filename)
994  
-    parse(File.readlines(filename).to_s)
995  
-  end
996  
-
997  
-  # Caches the data belonging to a certain file.
998  
-  #
999  
-  # data::
1000  
-  #   Data to be cached.
1001  
-  # filename::
1002  
-  #   Name of file the data was read from.
1003  
-  def put_into_cache(data, filename)
1004  
-    if @options.has_key?('cache')
1005  
-      @options['cache'].each { |scheme|
1006  
-        case(scheme)
1007  
-        when 'storable'
1008  
-          @@cache.save_storable(data, filename)
1009  
-        when 'mem_share'
1010  
-          @@cache.save_mem_share(data, filename)
1011  
-        when 'mem_copy'
1012  
-          @@cache.save_mem_copy(data, filename)
1013  
-        else
1014  
-          raise ArgumentError, "Unsupported caching scheme: <#{scheme}>."
1015  
-        end
1016  
-      }
1017  
-    end
1018  
-  end
1019  
-end
1020  
-
1021  
-# vim:sw=2

0 notes on commit bf724d4

Please sign in to comment.
Something went wrong with that request. Please try again.