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.
7 require 'rexml/document'
10 # Easy API to maintain XML (especially configuration files).
16 # A simple cache for XML documents that were already transformed
19 # Creates and initializes a new Cache object.
25 # Saves a data structure into a file.
28 # Data structure to be saved.
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
) }
36 # Restores a data structure from a file. If restoring the data
37 # structure failed for any reason, nil will be returned.
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
46 File
.open(cache_file
) { |f
| data = Marshal
.load(f
) }
50 # Saves a data structure in a shared memory cache.
53 # Data structure to be saved.
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]
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
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)
71 # Copies a data structure to a memory cache.
74 # Data structure to be copied.
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)]
81 # Restores a data structure from a memory cache. If restoring
82 # the data structure failed for any reason, nil will be
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?
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'
101 # Filename to get "cache filename" for.
102 def get_cache_filename(filename
)
103 filename
.sub(/(\.xml)?$/, '.stor')
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.
111 # Name of the file the cache entry belongs to.
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]
121 # Create a "global" cache.
124 # Creates and initializes a new XmlSimple object.
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."
132 @default_options = normalize_option_names(defaults
, (KNOWN_OPTIONS
['in'] + KNOWN_OPTIONS
['out']).uniq
)
137 # Converts an XML document in the same way as the Perl module XML::Simple.
140 # XML source. Could be one of the following:
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.
148 # Options to be used.
149 def xml_in(string
= nil, options
= nil)
150 handle_options('in', options
)
152 # If no XML string or filename was supplied look for scriptname.xml.
154 string
= File
::basename($0)
155 string
.sub
!(/\.[^.]+$/, '')
158 directory
= File
::dirname($0)
159 @options['searchpath'].unshift(directory
) unless directory
.nil?
162 if string
.instance_of
?(String
)
163 if string
=~
/<.*?>/m
166 @doc = parse($stdin.readlines
.to_s
)
168 filename
= find_xml_file(string
, @options['searchpath'])
170 if @options.has_key
?('cache')
171 @options['cache'].each
{ |scheme
|
174 content
= @
@cache.restore_storable(filename
)
176 content
= @
@cache.restore_mem_share(filename
)
178 content
= @
@cache.restore_mem_copy(filename
)
180 raise ArgumentError
, "Unsupported caching scheme: <#{scheme}>."
182 return content
if content
186 @doc = load_xml_file(filename
)
188 elsif string
.kind_of
?(IO
) || string
.kind_of
?(StringIO
)
189 @doc = parse(string
.readlines
.to_s
)
191 raise ArgumentError
, "Could not parse object of type: <#{string.type}>."
194 result
= collapse(@doc.root
)
195 result
= @options['keeproot'] ? merge({}, @doc.root
.name
, result
) : result
196 put_into_cache(result
, filename
)
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
)
206 # Converts a data structure into an XML document.
209 # Reference to data structure to be converted into XML.
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
}
218 if @options['keeproot']
222 @options['rootname'] = keys
[0]
224 elsif @options['rootname'] == ''
225 if ref
.instance_of
?(Hash
)
228 refsave
.each
{ |key
, value
|
232 ref
[key
] = [ value
.to_s
]
239 xml
= value_to_xml(ref
, @options['rootname'], '')
242 if @options['xmldeclaration']
243 xml
= @options['xmldeclaration'] + "\n" + xml
246 if @options.has_key
?('outputfile')
247 if @options['outputfile'].kind_of
?(IO
)
248 return @options['outputfile'].write(xml
)
250 File
.open(@options['outputfile'], "w") { |file
| file
.write(xml
) }
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
)
264 # Declare options that are valid for xml_in and xml_out.
267 keyattr keeproot forcecontent contentkey noattr
268 searchpath forcearray suppressempty anonymoustag
269 cache grouptags normalisespace normalizespace
270 variables varattr keytosymbol
273 keyattr keeproot contentkey noattr rootname
274 xmldeclaration outputfile noescape suppressempty
275 anonymoustag indent grouptags noindent
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
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.
295 # Hash to be normalized.
297 # List of known options.
298 def normalize_option_names(options
, known_options
)
299 return nil if options
.nil?
301 options
.each
{ |key
, value
|
304 if !known_options
.member
?(lkey
)
305 raise ArgumentError
, "Unrecognised option: #{lkey}."
312 # Merges a set of options with the default options.
315 # 'in': If options should be handled for xml_in.
316 # 'out': If options should be handled for xml_out.
318 # Options to be merged with the default options.
319 def handle_options(direction
, options
)
320 @options = options
|| Hash
.new
322 raise ArgumentError
, "Options must be a Hash!" unless @options.instance_of
?(Hash
)
324 unless KNOWN_OPTIONS
.has_key
?(direction
)
325 raise ArgumentError
, "Unknown direction: <#{direction}>."
328 known_options
= KNOWN_OPTIONS
[direction
]
329 @options = normalize_option_names(@options, known_options
)
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
]
341 unless @options.has_key
?('noattr')
342 @options['noattr'] = false
345 if @options.has_key
?('rootname')
346 @options['rootname'] = '' if @options['rootname'].nil?
348 @options['rootname'] = DEF_ROOT_NAME
351 if @options.has_key
?('xmldeclaration') && @options['xmldeclaration'] == true
352 @options['xmldeclaration'] = DEF_XML_DECLARATION
355 @options['keytosymbol'] = DEF_KEY_TO_SYMBOL
unless @options.has_key
?('keytosymbol')
357 if @options.has_key
?('contentkey')
358 if @options['contentkey'] =~
/^-(.*)$/
359 @options['contentkey'] = $1
360 @options['collapseagain'] = true
363 @options['contentkey'] = DEF_CONTENT_KEY
366 unless @options.has_key
?('normalisespace')
367 @options['normalisespace'] = @options['normalizespace']
369 @options['normalisespace'] = 0 if @options['normalisespace'].nil?
371 if @options.has_key
?('searchpath')
372 unless @options['searchpath'].instance_of
?(Array
)
373 @options['searchpath'] = [ @options['searchpath'] ]
376 @options['searchpath'] = []
379 if @options.has_key
?('cache') && scalar(@options['cache'])
380 @options['cache'] = [ @options['cache'] ]
383 @options['anonymoustag'] = DEF_ANONYMOUS_TAG
unless @options.has_key
?('anonymoustag')
385 if !@options.has_key
?('indent') || @options['indent'].nil?
386 @options['indent'] = DEF_INDENTATION
389 @options['indent'] = '' if @options.has_key
?('noindent')
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 : '']
403 elsif !@options['keyattr'].instance_of
?(Array
)
404 raise ArgumentError
, "'keyattr' must be String, Hash, or Array!"
407 @options['keyattr'] = [ @options['keyattr'] ]
410 @options['keyattr'] = DEF_KEY_ATTRIBUTES
413 if @options.has_key
?('forcearray')
414 if @options['forcearray'].instance_of
?(Regexp
)
415 @options['forcearray'] = [ @options['forcearray'] ]
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'] = []
427 @options['forcearray']['_regex'] << tag
429 @options['forcearray'][tag
] = true
433 @options['forcearray'] = false
436 @options['forcearray'] = @options['forcearray'] ? true : false
439 @options['forcearray'] = DEF_FORCE_ARRAY
442 if @options.has_key
?('grouptags') && !@options['grouptags'].instance_of
?(Hash
)
443 raise ArgumentError
, "Illegal value for 'GroupTags' option - expected a Hash."
446 if @options.has_key
?('variables') && !@options['variables'].instance_of
?(Hash
)
447 raise ArgumentError
, "Illegal value for 'Variables' option - expected a Hash."
450 if @options.has_key
?('variables')
451 @_var_values = @options['variables']
452 elsif @options.has_key
?('varattr')
457 # Actually converts an XML document element into a data structure.
460 # The document element to be collapsed.
461 def collapse(element
)
462 result
= @options['noattr'] ? {} : get_attributes(element
)
464 if @options['normalisespace'] == 2
465 result
.each
{ |k
, v
| result
[k
] = normalise_space(v
) }
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
474 result
= merge(result
, child
.name
, value
)
476 if has_mixed_content
?(element
)
478 content
= element
.texts
.map
{ |x
| x
.to_s
}
479 content
= content
[0] if content
.size
== 1
480 result
[@options['contentkey']] = content
482 elsif element
.has_text
? # i.e. it has only text.
483 return collapse_text_node(result
, element
)
486 # Turn Arrays into Hashes if key fields present.
487 count
= fold_arrays(result
)
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
500 # Fold Hashes containing a single anonymous Array up into just the Array.
502 anonymoustag
= @options['anonymoustag']
503 if result
.has_key
?(anonymoustag
) && result
[anonymoustag
].instance_of
?(Array
)
504 return result
[anonymoustag
]
508 if result
.empty
? && @options.has_key
?('suppressempty')
509 return @options['suppressempty'] == '' ? '' : nil
515 # Collapses a text node and merges it with an existing Hash, if
517 # Thanks to Curtis Schofield for reporting a subtle bug.
520 # Hash to merge text node value with, if possible.
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
?
529 if element
.has_attributes
? && !@options['noattr']
530 return merge(hash
, @options['contentkey'], value
)
532 if @options['forcecontent']
533 return merge(hash
, @options['contentkey'], value
)
540 # Folds all arrays in a Hash.
544 def fold_arrays(hash
)
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
)
553 hash
[key
] = fold_array_by_name(key
, value
)
562 # Folds an Array to a Hash, if possible. Folding happens
563 # according to the content of keyattr, which has to be
567 # Array to be folded.
568 def fold_array(array
)
571 return array
unless x
.instance_of
?(Hash
)
573 @options['keyattr'].each
{ |key
|
577 return array
if value
.instance_of
?(Hash
) || value
.instance_of
?(Array
)
578 value
= normalise_space(value
) if @options['normalisespace'] == 1
584 return array
unless key_matched
586 hash
= collapse_content(hash
) if @options['collapseagain']
590 # Folds an Array to a Hash, if possible. Folding happens
591 # according to the content of keyattr, which has to be
595 # Name of the attribute to be folded upon.
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
]
604 if x
.instance_of
?(Hash
) && x
.has_key
?(key
)
606 return array
if value
.instance_of
?(Hash
) || value
.instance_of
?(Array
)
607 value
= normalise_space(value
) if @options['normalisespace'] == 1
609 hash
[value
]["-#{key}"] = hash
[value
][key
] if flag
== '-'
610 hash
[value
].delete(key
) unless flag
== '+'
612 $stderr.puts("Warning: <#{name}> element has no '#{key}' attribute.")
616 hash
= collapse_content(hash
) if @options['collapseagain']
620 # Tries to collapse a Hash even more ;-)
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
] }
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.
639 # Hash to add key/value pair to.
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
648 # do variable substitutions
649 unless @_var_values.nil? || @_var_values.empty
?
650 value
.gsub
!(/\$\{(\w+)\}/) { |x
| get_var($1) }
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
)
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
669 if hash
.has_key
?(key
)
670 if hash
[key
].instance_of
?(Array
)
673 hash
[key
] = [ hash
[key
], value
]
675 elsif value
.instance_of
?(Array
) # Handle anonymous arrays.
676 hash
[key
] = [ value
]
679 hash
[key
] = [ value
]
687 # Checks, if the 'forcearray' option has to be used for
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
}
701 # Converts the attributes array of a document node into a Hash.
702 # Returns an empty Hash, if node has no attributes.
705 # Document node to extract attributes from.
706 def get_attributes(node
)
708 node
.attributes
.each
{ |n
,v
| attributes
[n
] = v
}
712 # Determines, if a document element has mixed content.
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
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
731 # Called during variable substitution to get the value for the
734 if @_var_values.has_key
?(name
)
735 return @_var_values[name
]
741 # Recurses through a data structure building up and returning an
742 # XML representation of that structure as a string.
745 # Reference to the data structure to be encoded.
747 # The XML tag name to be used for this item.
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"
755 if @ancestors.member
?(ref
)
756 raise ArgumentError
, "Circular data structures not supported!"
761 return [indent
, '<', name
, '>', @options['noescape'] ? ref
.to_s
: escape_value(ref
.to_s
), '</', name
, '>', nl
].join('')
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
)
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
}
786 result
<< indent
<< '<' << name
790 ref
.each
{ |key
, value
|
791 next if !key
.nil? && key
[0, 1] == '-'
793 unless @options.has_key
?('suppressempty') && @options['suppressempty'].nil?
794 raise ArgumentError
, "Use of uninitialized value!"
799 if !scalar(value
) || @options['noattr']
800 nested
<< value_to_xml(value
, key
, indent
+ @options['indent'])
803 value
= escape_value(value
) unless @options['noescape']
804 if key
== @options['contentkey']
807 result
<< ' ' << key
<< '="' << value
<< '"'
815 if !nested
.empty
? || !text_content
.nil?
818 if !text_content
.nil?
819 result
<< text_content
820 nested
[0].sub
!(/^\s+/, '') if !nested
.empty
?
825 result
<< nested
<< indent
827 result
<< '</' << name
<< '>' << nl
832 result
<< ' />' << nl
834 elsif ref
.instance_of
?(Array
)
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
)
843 result
<< indent
<< '<' << name
<< '>' << nl
844 result
<< value_to_xml(value
, @options['anonymoustag'], indent
+ @options['indent'])
845 result
<< indent
<< '</' << name
<< '>' << nl
849 # Probably, this is obsolete.
850 raise ArgumentError
, "Can't encode a value of type: #{ref.type}."
852 @ancestors.pop
if !scalar(ref
)
856 # Checks, if a certain value is a "scalar" value. Whatever
857 # that will be in Ruby ... ;-)
860 # Value to be checked.
862 return false if value
.instance_of
?(Hash
) || value
.instance_of
?(Array
)
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
873 # Reference to the hash to be unfolded.
874 def hash_to_array(parent
, hashref
)
876 hashref
.each
{ |key
, value
|
877 return hashref
unless value
.instance_of
?(Hash
)
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
)
883 arrayref
<< { @options['keyattr'][0] => key
}.update(value
)
889 # Replaces XML markup characters by their external entities.
892 # The string to be escaped.
893 def escape_value(data)
894 Text
::normalize(data)
897 # Removes leading and trailing whitespace and sequences of
898 # whitespaces from a string.
901 # String to be normalised.
902 def normalise_space(text
)
903 text
.strip
.gsub(/\s\s+/, ' ')
906 # Checks, if an object is nil, an empty String or an empty Hash.
907 # Thanks to Norbert Gawor for a bugfix.
910 # Value to be checked for emptiness.
916 return value
!~
/\S/m
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.
927 # Document node to be converted.
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)
942 # Parses an XML string and returns the according document.
945 # XML string to be parsed.
947 # The following exception may be raised:
949 # REXML::ParseException::
950 # If the specified file is not wellformed.
951 def parse(xml_string
)
952 Document
.new(xml_string
)
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.
960 # Name of the file to search for.
962 # List of paths to search in.
963 def find_xml_file(file
, searchpath
)
964 filename
= File
::basename(file
)
967 return file
if File
::file?(file
)
969 searchpath
.each
{ |path
|
970 full_path
= File
::join(path
, filename
)
971 return full_path
if File
::file?(full_path
)
976 return file
if File
::file?(file
)
977 raise ArgumentError
, "File does not exist: #{file}."
979 raise ArgumentError
, "Could not find <#{filename}> in <#{searchpath.join(':')}>"
982 # Loads and parses an XML configuration file.
985 # Name of the configuration file to be loaded.
987 # The following exceptions may be raised:
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
)
997 # Caches the data belonging to a certain file.
1000 # Data to be cached.
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
|
1008 @
@cache.save_storable(data, filename
)
1010 @
@cache.save_mem_share(data, filename
)
1012 @
@cache.save_mem_copy(data, filename
)
1014 raise ArgumentError
, "Unsupported caching scheme: <#{scheme}>."