xml_mini 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/.rvmrc +1 -0
- data/Gemfile +20 -0
- data/Guardfile +15 -0
- data/LICENSE +22 -0
- data/README.md +31 -0
- data/Rakefile +11 -0
- data/lib/core_ext/array.rb +45 -0
- data/lib/core_ext/blank.rb +112 -0
- data/lib/core_ext/hash.rb +72 -0
- data/lib/xml_mini.rb +160 -0
- data/lib/xml_mini/libxml.rb +63 -0
- data/lib/xml_mini/libxmlsax.rb +84 -0
- data/lib/xml_mini/node_hash.rb +29 -0
- data/lib/xml_mini/nokogiri.rb +64 -0
- data/lib/xml_mini/nokogirisax.rb +81 -0
- data/lib/xml_mini/rexml.rb +126 -0
- data/lib/xml_mini/version.rb +3 -0
- data/test/lib/xml_mini/libxml_engine_test.rb +202 -0
- data/test/lib/xml_mini/libxmlsax_engine_test.rb +193 -0
- data/test/lib/xml_mini/nokogiri_engine_test.rb +215 -0
- data/test/lib/xml_mini/nokogirisax_engine_test.rb +216 -0
- data/test/lib/xml_mini/rexml_engine_test.rb +28 -0
- data/test/lib/xml_mini_test.rb +89 -0
- data/test/test_helper.rb +7 -0
- data/xml_mini.gemspec +17 -0
- metadata +84 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use ruby-1.9.3-p194@xml_mini --create
|
data/Gemfile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in xml_mini.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'bundler'
|
7
|
+
gem 'rake'
|
8
|
+
|
9
|
+
|
10
|
+
group :development, :test do
|
11
|
+
gem 'minitest'
|
12
|
+
gem 'libxml-ruby'
|
13
|
+
gem 'nokogiri'
|
14
|
+
end
|
15
|
+
|
16
|
+
group :test do
|
17
|
+
gem 'guard'
|
18
|
+
gem 'guard-minitest', github: 'mpouleijn/guard-minitest'
|
19
|
+
gem 'guard-bundler'
|
20
|
+
end
|
data/Guardfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'bundler' do
|
5
|
+
watch('Gemfile')
|
6
|
+
watch(/^.+\.gemspec/)
|
7
|
+
end
|
8
|
+
|
9
|
+
guard 'minitest' do
|
10
|
+
# with Minitest::Unit
|
11
|
+
watch(%r|^test/(.*)\/?(.*)\_test.rb|)
|
12
|
+
watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "test/lib/#{m[1]}#{m[2]}_test.rb" }
|
13
|
+
watch(%r|^lib/core_ext/(.*)([^/]+)\.rb|) { |m| "test/lib/core_ext/#{m[1]}#{m[2]}_test.rb" }
|
14
|
+
watch(%r|^test/test_helper\.rb|) { "test" }
|
15
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Michel Pouleijn
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
[](http://travis-ci.org/Pouleijn/xml_mini) | [](https://codeclimate.com/github/Pouleijn/xml_mini)
|
2
|
+
|
3
|
+
# XmlMini
|
4
|
+
|
5
|
+
TODO: Write a gem description
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'xml_mini'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install xml_mini
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
TODO: Write usage instructions here
|
24
|
+
|
25
|
+
## Contributing
|
26
|
+
|
27
|
+
1. Fork it
|
28
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
30
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
31
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
class Array
|
2
|
+
# Wraps its argument in an array unless it is already an array (or array-like).
|
3
|
+
#
|
4
|
+
# Specifically:
|
5
|
+
#
|
6
|
+
# * If the argument is +nil+ an empty list is returned.
|
7
|
+
# * Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned.
|
8
|
+
# * Otherwise, returns an array with the argument as its single element.
|
9
|
+
#
|
10
|
+
# Array.wrap(nil) # => []
|
11
|
+
# Array.wrap([1, 2, 3]) # => [1, 2, 3]
|
12
|
+
# Array.wrap(0) # => [0]
|
13
|
+
#
|
14
|
+
# This method is similar in purpose to <tt>Kernel#Array</tt>, but there are some differences:
|
15
|
+
#
|
16
|
+
# * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt>
|
17
|
+
# moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns
|
18
|
+
# such a +nil+ right away.
|
19
|
+
# * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt>
|
20
|
+
# raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value.
|
21
|
+
# * It does not call +to_a+ on the argument, though special-cases +nil+ to return an empty array.
|
22
|
+
#
|
23
|
+
# The last point is particularly worth comparing for some enumerables:
|
24
|
+
#
|
25
|
+
# Array(:foo => :bar) # => [[:foo, :bar]]
|
26
|
+
# Array.wrap(:foo => :bar) # => [{:foo => :bar}]
|
27
|
+
#
|
28
|
+
# There's also a related idiom that uses the splat operator:
|
29
|
+
#
|
30
|
+
# [*object]
|
31
|
+
#
|
32
|
+
# which returns <tt>[nil]</tt> for +nil+, and calls to <tt>Array(object)</tt> otherwise.
|
33
|
+
#
|
34
|
+
# Thus, in this case the behavior is different for +nil+, and the differences with
|
35
|
+
# <tt>Kernel#Array</tt> explained above apply to the rest of +object+s.
|
36
|
+
def self.wrap(object)
|
37
|
+
if object.nil?
|
38
|
+
[]
|
39
|
+
elsif object.respond_to?(:to_ary)
|
40
|
+
object.to_ary || [object]
|
41
|
+
else
|
42
|
+
[object]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Object
|
4
|
+
# An object is blank if it's false, empty, or a whitespace string.
|
5
|
+
# For example, '', ' ', +nil+, [], and {} are all blank.
|
6
|
+
#
|
7
|
+
# This simplifies:
|
8
|
+
#
|
9
|
+
# if address.nil? || address.empty?
|
10
|
+
#
|
11
|
+
# ...to:
|
12
|
+
#
|
13
|
+
# if address.blank?
|
14
|
+
def blank?
|
15
|
+
respond_to?(:empty?) ? empty? : !self
|
16
|
+
end
|
17
|
+
|
18
|
+
# An object is present if it's not <tt>blank?</tt>.
|
19
|
+
def present?
|
20
|
+
!blank?
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns object if it's <tt>present?</tt> otherwise returns +nil+.
|
24
|
+
# <tt>object.presence</tt> is equivalent to <tt>object.present? ? object : nil</tt>.
|
25
|
+
#
|
26
|
+
# This is handy for any representation of objects where blank is the same
|
27
|
+
# as not present at all. For example, this simplifies a common check for
|
28
|
+
# HTTP POST/query parameters:
|
29
|
+
#
|
30
|
+
# state = params[:state] if params[:state].present?
|
31
|
+
# country = params[:country] if params[:country].present?
|
32
|
+
# region = state || country || 'US'
|
33
|
+
#
|
34
|
+
# ...becomes:
|
35
|
+
#
|
36
|
+
# region = params[:state].presence || params[:country].presence || 'US'
|
37
|
+
def presence
|
38
|
+
self if present?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class NilClass
|
43
|
+
# +nil+ is blank:
|
44
|
+
#
|
45
|
+
# nil.blank? # => true
|
46
|
+
#
|
47
|
+
def blank?
|
48
|
+
true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class FalseClass
|
53
|
+
# +false+ is blank:
|
54
|
+
#
|
55
|
+
# false.blank? # => true
|
56
|
+
#
|
57
|
+
def blank?
|
58
|
+
true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class TrueClass
|
63
|
+
# +true+ is not blank:
|
64
|
+
#
|
65
|
+
# true.blank? # => false
|
66
|
+
#
|
67
|
+
def blank?
|
68
|
+
false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Array
|
73
|
+
# An array is blank if it's empty:
|
74
|
+
#
|
75
|
+
# [].blank? # => true
|
76
|
+
# [1,2,3].blank? # => false
|
77
|
+
#
|
78
|
+
alias_method :blank?, :empty?
|
79
|
+
end
|
80
|
+
|
81
|
+
class Hash
|
82
|
+
# A hash is blank if it's empty:
|
83
|
+
#
|
84
|
+
# {}.blank? # => true
|
85
|
+
# {:key => 'value'}.blank? # => false
|
86
|
+
#
|
87
|
+
alias_method :blank?, :empty?
|
88
|
+
end
|
89
|
+
|
90
|
+
class String
|
91
|
+
# A string is blank if it's empty or contains whitespaces only:
|
92
|
+
#
|
93
|
+
# ''.blank? # => true
|
94
|
+
# ' '.blank? # => true
|
95
|
+
# ' '.blank? # => true
|
96
|
+
# ' something here '.blank? # => false
|
97
|
+
#
|
98
|
+
def blank?
|
99
|
+
self !~ /[^[:space:]]/
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Numeric #:nodoc:
|
104
|
+
# No number is blank:
|
105
|
+
#
|
106
|
+
# 1.blank? # => false
|
107
|
+
# 0.blank? # => false
|
108
|
+
#
|
109
|
+
def blank?
|
110
|
+
false
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
class Hash
|
2
|
+
class << self
|
3
|
+
def from_minixml(xml)
|
4
|
+
typecast_xml_value(unrename_keys(XmlMini.parse(xml)))
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def typecast_xml_value(value)
|
10
|
+
case value.class.to_s
|
11
|
+
when 'Hash'
|
12
|
+
if value['type'] == 'array'
|
13
|
+
_, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) })
|
14
|
+
if entries.nil? || (c = value['__content__'] && c.blank?)
|
15
|
+
[]
|
16
|
+
else
|
17
|
+
case entries.class.to_s # something weird with classes not matching here. maybe singleton methods breaking is_a?
|
18
|
+
when 'Array'
|
19
|
+
entries.collect { |v| typecast_xml_value(v) }
|
20
|
+
when 'Hash'
|
21
|
+
[typecast_xml_value(entries)]
|
22
|
+
else
|
23
|
+
raise "can't typecast #{entries.inspect}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
elsif value['type'] == 'file' ||
|
27
|
+
(value['__content__'] && (value.keys.size == 1 || !value['__content__'].present? ))
|
28
|
+
content = value['__content__']
|
29
|
+
if parser = XmlMini::PARSING[value['type']]
|
30
|
+
parser.arity == 1 ? parser.call(content) : parser.call(content, value)
|
31
|
+
else
|
32
|
+
content
|
33
|
+
end
|
34
|
+
elsif value['type'] == 'string' && value['nil'] != 'true'
|
35
|
+
''
|
36
|
+
# blank or nil parsed values are represented by nil
|
37
|
+
elsif value.blank? || value['nil'] == 'true'
|
38
|
+
nil
|
39
|
+
# If the type is the only element which makes it then
|
40
|
+
# this still makes the value nil, except if type is
|
41
|
+
# a XML node(where type['value'] is a Hash)
|
42
|
+
elsif value['type'] && value.size == 1 && !value['type'].is_a?(::Hash)
|
43
|
+
nil
|
44
|
+
else
|
45
|
+
xml_value = Hash[value.map { |k,v| [k, typecast_xml_value(v)] }]
|
46
|
+
|
47
|
+
# Turn { :files => { :file => #<StringIO> } } into { :files => #<StringIO> } so it is compatible with
|
48
|
+
# how multipart uploaded files from HTML appear
|
49
|
+
xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
|
50
|
+
end
|
51
|
+
when 'Array'
|
52
|
+
value.map! { |i| typecast_xml_value(i) }
|
53
|
+
value.length > 1 ? value : value.first
|
54
|
+
when 'String'
|
55
|
+
value
|
56
|
+
else
|
57
|
+
raise "can't typecast #{value.class.name} - #{value.inspect}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def unrename_keys(params)
|
62
|
+
case params.class.to_s
|
63
|
+
when 'Hash'
|
64
|
+
Hash[params.map { |k,v| [k.to_s.tr('-', '_'), unrename_keys(v)] } ]
|
65
|
+
when 'Array'
|
66
|
+
params.map { |v| unrename_keys(v) }
|
67
|
+
else
|
68
|
+
params
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/xml_mini.rb
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require "xml_mini/version"
|
3
|
+
require "core_ext/array"
|
4
|
+
require "core_ext/blank"
|
5
|
+
require "core_ext/hash"
|
6
|
+
|
7
|
+
module XmlMini
|
8
|
+
extend Forwardable
|
9
|
+
extend self
|
10
|
+
|
11
|
+
# This module decorates files deserialized using Hash.from_minixml with
|
12
|
+
# the <tt>original_filename</tt> and <tt>content_type</tt> methods.
|
13
|
+
module FileLike #:nodoc:
|
14
|
+
attr_writer :original_filename, :content_type
|
15
|
+
|
16
|
+
def original_filename
|
17
|
+
@original_filename || 'untitled'
|
18
|
+
end
|
19
|
+
|
20
|
+
def content_type
|
21
|
+
@content_type || 'application/octet-stream'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
DEFAULT_ENCODINGS = {
|
26
|
+
"binary" => "base64"
|
27
|
+
} unless defined?(DEFAULT_ENCODINGS)
|
28
|
+
|
29
|
+
TYPE_NAMES = {
|
30
|
+
"Symbol" => "symbol",
|
31
|
+
"Fixnum" => "integer",
|
32
|
+
"Bignum" => "integer",
|
33
|
+
"BigDecimal" => "decimal",
|
34
|
+
"Float" => "float",
|
35
|
+
"TrueClass" => "boolean",
|
36
|
+
"FalseClass" => "boolean",
|
37
|
+
"Date" => "date",
|
38
|
+
"DateTime" => "dateTime",
|
39
|
+
"Time" => "dateTime",
|
40
|
+
"Array" => "array",
|
41
|
+
"Hash" => "hash"
|
42
|
+
} unless defined?(TYPE_NAMES)
|
43
|
+
|
44
|
+
FORMATTING = {
|
45
|
+
"symbol" => Proc.new { |symbol| symbol.to_s },
|
46
|
+
"date" => Proc.new { |date| date.to_s(:db) },
|
47
|
+
"dateTime" => Proc.new { |time| time.xmlschema },
|
48
|
+
"binary" => Proc.new { |binary| ::Base64.encode64(binary) },
|
49
|
+
"yaml" => Proc.new { |yaml| yaml.to_yaml }
|
50
|
+
} unless defined?(FORMATTING)
|
51
|
+
|
52
|
+
# TODO use regexp instead of Date.parse
|
53
|
+
unless defined?(PARSING)
|
54
|
+
PARSING = {
|
55
|
+
"symbol" => Proc.new { |symbol| symbol.to_sym },
|
56
|
+
"date" => Proc.new { |date| ::Date.parse(date) },
|
57
|
+
"datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc },
|
58
|
+
"integer" => Proc.new { |integer| integer.to_i },
|
59
|
+
"float" => Proc.new { |float| float.to_f },
|
60
|
+
"decimal" => Proc.new { |number| BigDecimal(number) },
|
61
|
+
"boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.strip) },
|
62
|
+
"string" => Proc.new { |string| string.to_s },
|
63
|
+
"yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml },
|
64
|
+
"base64Binary" => Proc.new { |bin| ::Base64.decode64(bin) },
|
65
|
+
"binary" => Proc.new { |bin, entity| _parse_binary(bin, entity) },
|
66
|
+
"file" => Proc.new { |file, entity| _parse_file(file, entity) }
|
67
|
+
}
|
68
|
+
|
69
|
+
PARSING.update(
|
70
|
+
"double" => PARSING["float"],
|
71
|
+
"dateTime" => PARSING["datetime"]
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
attr_reader :backend
|
76
|
+
def_delegator :@backend, :parse
|
77
|
+
|
78
|
+
def backend=(name)
|
79
|
+
if name.is_a?(Module)
|
80
|
+
@backend = name
|
81
|
+
else
|
82
|
+
require "xml_mini/#{name.downcase}"
|
83
|
+
@backend = XmlMini.const_get("XmlMini_#{name}")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def with_backend(name)
|
88
|
+
old_backend, self.backend = backend, name
|
89
|
+
yield
|
90
|
+
ensure
|
91
|
+
self.backend = old_backend
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_tag(key, value, options)
|
95
|
+
type_name = options.delete(:type)
|
96
|
+
merged_options = options.merge(:root => key, :skip_instruct => true)
|
97
|
+
|
98
|
+
if value.is_a?(::Method) || value.is_a?(::Proc)
|
99
|
+
if value.arity == 1
|
100
|
+
value.call(merged_options)
|
101
|
+
else
|
102
|
+
value.call(merged_options, key.to_s.singularize)
|
103
|
+
end
|
104
|
+
elsif value.respond_to?(:to_xml)
|
105
|
+
value.to_xml(merged_options)
|
106
|
+
else
|
107
|
+
type_name ||= TYPE_NAMES[value.class.name]
|
108
|
+
type_name ||= value.class.name if value && !value.respond_to?(:to_str)
|
109
|
+
type_name = type_name.to_s if type_name
|
110
|
+
type_name = "dateTime" if type_name == "datetime"
|
111
|
+
|
112
|
+
key = rename_key(key.to_s, options)
|
113
|
+
|
114
|
+
attributes = options[:skip_types] || type_name.nil? ? {} : {:type => type_name}
|
115
|
+
attributes[:nil] = true if value.nil?
|
116
|
+
|
117
|
+
encoding = options[:encoding] || DEFAULT_ENCODINGS[type_name]
|
118
|
+
attributes[:encoding] = encoding if encoding
|
119
|
+
|
120
|
+
formatted_value = FORMATTING[type_name] && !value.nil? ?
|
121
|
+
FORMATTING[type_name].call(value) : value
|
122
|
+
|
123
|
+
options[:builder].tag!(key, formatted_value, attributes)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def rename_key(key, options = {})
|
128
|
+
dasherize = !options.has_key?(:dasherize) || options[:dasherize]
|
129
|
+
key = _dasherize(key) if dasherize
|
130
|
+
key
|
131
|
+
end
|
132
|
+
|
133
|
+
protected
|
134
|
+
|
135
|
+
def _dasherize(key)
|
136
|
+
# $2 must be a non-greedy regex for this to work
|
137
|
+
left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1, 3]
|
138
|
+
"#{left}#{middle.tr('_ ', '--')}#{right}"
|
139
|
+
end
|
140
|
+
|
141
|
+
# TODO: Add support for other encodings
|
142
|
+
def _parse_binary(bin, entity) #:nodoc:
|
143
|
+
case entity['encoding']
|
144
|
+
when 'base64'
|
145
|
+
::Base64.decode64(bin)
|
146
|
+
else
|
147
|
+
bin
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def _parse_file(file, entity)
|
152
|
+
f = StringIO.new(::Base64.decode64(file))
|
153
|
+
f.extend(FileLike)
|
154
|
+
f.original_filename = entity['name']
|
155
|
+
f.content_type = entity['content_type']
|
156
|
+
f
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
XmlMini.backend = 'REXML'
|