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.
@@ -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'