xml_mini 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .idea
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
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
@@ -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.
@@ -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
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.pattern = '**/*_test.rb'
8
+ end
9
+
10
+ desc "Default Task"
11
+ task :default => "test"
@@ -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
@@ -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'