xml_mini 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://secure.travis-ci.org/Pouleijn/xml_mini.png?branch=master)](http://travis-ci.org/Pouleijn/xml_mini) | [![Code Climate](https://codeclimate.com/badge.png)](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'
|