Narnach-libxml_rails 0.0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ v0.0.2. Port ActiveSupport Test::Unit tests to RSpec.
2
+
3
+ v0.0.1. Initial release
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Brandon Mitchell
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,40 @@
1
+ = libxml_rails
2
+
3
+ libxml_rails is a gem/rails plugin by Brandon Mitchell. It is designed to replace the default XML processor in Rails (XmlSimple) with the C-based libxml. It requires the libxml-ruby gem.
4
+
5
+ This fork is maintained by Wes 'Narnach' Oldenbeuving.
6
+
7
+ Changes from the Brandon's version are:
8
+ * Don't rely on echoe to build the gem.
9
+ * Added Hash#to_xml_with_libxml and Array#to_xml_with_libxml
10
+ * ActiveResource::Base#to_xml uses libxml.
11
+ * ActiveResource::Formats::XmlFormat#encode uses libxml
12
+
13
+ === Installation
14
+
15
+ libxml_rails can be installed as a rails plugin or as a stand-alone gem.
16
+
17
+ === Gem Installation
18
+
19
+ From PROJECT_ROOT:
20
+
21
+ gem install libxml-ruby
22
+ rake install
23
+
24
+ === Plugin Installation
25
+
26
+ From RAILS_ROOT:
27
+
28
+ script/plugin install git://github.com/Narnach/libxml_rails.git
29
+
30
+ Add the following to the configuration block in config/environment.rb:
31
+
32
+ config.gem 'libxml-ruby'
33
+
34
+ === Gem Installation
35
+
36
+ Add the following to the configuration block in config/environment.rb:
37
+
38
+ config.gem 'Narnach-libxml_rails', :source => 'http://gems.github.com'
39
+
40
+ Copyright (c) 2008 Brandon Mitchell, released under the MIT license
@@ -0,0 +1,28 @@
1
+ require "rake"
2
+ require "rake/clean"
3
+ require "rake/gempackagetask"
4
+ require 'rubygems'
5
+
6
+ ################################################################################
7
+ ### Gem
8
+ ################################################################################
9
+
10
+ begin
11
+ # Parse gemspec using the github safety level.
12
+ file = Dir['*.gemspec'].first
13
+ data = File.read(file)
14
+ spec = nil
15
+ Thread.new { spec = eval("$SAFE = 3\n%s" % data)}.join
16
+
17
+ # Create the gem tasks
18
+ Rake::GemPackageTask.new(spec) do |package|
19
+ package.gem_spec = spec
20
+ end
21
+ rescue Exception => e
22
+ printf "WARNING: Error caught (%s): %s\n%s", e.class.name, e.message, e.backtrace[0...5].map {|l| ' %s' % l}.join("\n")
23
+ end
24
+
25
+ desc 'Package and install the gem for the current version'
26
+ task :install => :gem do
27
+ system "sudo gem install -l pkg/%s-%s.gem" % [spec.name, spec.version]
28
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'libxml_rails'))
@@ -0,0 +1,91 @@
1
+ module Bitbckt #:nodoc:
2
+ module CoreExtensions #:nodoc:
3
+ module Hash #:nodoc:
4
+ module Conversions
5
+
6
+ def self.included(klass)
7
+ klass.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def from_xml(xml)
12
+ result = LibXML::XML::Parser.string(xml).parse
13
+ if result.root.attributes['type'] == 'array'
14
+ { result.root.name.to_s.gsub(/-/,'_') => array_from_node(result.root) }
15
+ else
16
+ { result.root.name.to_s.gsub(/-/,'_') => xml_node_to_hash(result.root) }
17
+ end
18
+ end
19
+
20
+ private
21
+ def xml_node_to_hash(node)
22
+ # If we are at an XML::Node, build the Hash
23
+ if node.element?
24
+ result_hash = {}
25
+ if node.children?
26
+
27
+ node.each_child do |child|
28
+ next if child.empty?
29
+
30
+ if child.attributes['type'] == 'array'
31
+ result = array_from_node(child)
32
+ elsif child.attributes['type'] == 'string' and child.children.empty?
33
+ # NOTE: Odd issue with libxml - XML::Node#empty? returns false and
34
+ # XML::Node#children? returns true when the Node contains just
35
+ # an empty string (#children == []), but has attributes.
36
+ result = ''
37
+ elsif child.attributes['type'] == 'file'
38
+ result = Object::Hash::XML_PARSING['file'].call(child.content.to_s, child.attributes)
39
+ elsif Object::Hash::XML_PARSING.include? child.attributes['type'] and not child.content.to_s.chomp.blank?
40
+ result = Object::Hash::XML_PARSING[child.attributes['type']].call(child.content.to_s)
41
+ else
42
+ result = xml_node_to_hash(child)
43
+ result['type'] = child.attributes['type'] if child.attributes['type'] and result
44
+ end
45
+
46
+ return result if child.name == 'text'
47
+
48
+ key = child.name.gsub(/-/, '_')
49
+ if result_hash[key]
50
+ if result_hash[key].is_a?(Object::Array)
51
+ result_hash[key] << result
52
+ else
53
+ result_hash[key] = [result_hash[key]] << result
54
+ end
55
+ else
56
+ result_hash[key] = result
57
+ end
58
+ end
59
+
60
+ else
61
+ if node.attributes['type'] || node.attributes.length == 0 || node.attributes['nil'] == 'true'
62
+ result_hash = nil
63
+ else
64
+ node.each_attr do |attribute|
65
+ result_hash[attribute.name] = attribute.value.to_s
66
+ end
67
+ end
68
+ end
69
+
70
+ return result_hash
71
+ else
72
+ return node.content.to_s
73
+ end
74
+ end
75
+
76
+ def array_from_node(node)
77
+ returning([]) do |collection|
78
+ node.each_child do |child|
79
+ next if child.empty?
80
+ collection << xml_node_to_hash(child)
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ Hash.class_eval { include Bitbckt::CoreExtensions::Hash::Conversions }
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'libxml'
4
+ require 'bitbckt/core_ext/hash/conversions'
5
+ require 'narnach/core_ext/hash/conversions'
6
+ require 'narnach/core_ext/array/conversions'
@@ -0,0 +1,2 @@
1
+ # Convenience file to make it easy to load 'libxml_rails/active_resource'
2
+ require 'narnach/rails_ext/active_resource'
@@ -0,0 +1,64 @@
1
+ module Narnach #:nodoc:
2
+ module CoreExtensions #:nodoc:
3
+ module Array #:nodoc:
4
+ module Conversions
5
+ # Version of #to_xml copy-adjusted from ActiveSupport to work with libxml.
6
+ #
7
+ # Options not supported:
8
+ # - :indent
9
+ def to_xml_with_libxml(options = {})
10
+ raise "Not all elements respond to to_xml" unless all? { |e| e.respond_to? :to_xml_with_libxml }
11
+ options.reverse_merge!({:to_string => true, :skip_instruct => false })
12
+ dasherize = !options.has_key?(:dasherize) || options[:dasherize]
13
+ options[:root] ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? first.class.to_s.underscore.pluralize : "records"
14
+ options[:root] = options[:root].to_s.dasherize if dasherize
15
+ options[:children] ||= options[:root].singularize
16
+ to_string = options.delete(:to_string)
17
+ skip_instruct = options.delete(:skip_instruct)
18
+ root = options.delete(:root).to_s
19
+ doc = nil
20
+ if skip_instruct
21
+ node = LibXML::XML::Node.new(root)
22
+ doc = node
23
+ case options[:builder]
24
+ when LibXML::XML::Document
25
+ options[:builder].root << node
26
+ when nil
27
+ options[:builder] = node
28
+ else
29
+ options[:builder] << node
30
+ end
31
+ else
32
+ options[:builder] = LibXML::XML::Document.new
33
+ options[:builder].encoding = 'UTF-8'
34
+ options[:builder].root = LibXML::XML::Node.new(root)
35
+ doc = options[:builder].root
36
+ end
37
+ doc['type'] = 'array' unless options[:skip_types]
38
+
39
+
40
+ children = options.delete(:children)
41
+ opts = options.merge({ :root => children })
42
+
43
+ unless empty?
44
+ yield doc if block_given?
45
+ each do |e|
46
+ e.to_xml_with_libxml(opts.merge!({ :skip_instruct => true, :to_string => false, :builder => doc }))
47
+ end
48
+ end
49
+ if to_string
50
+ string = options[:builder].to_s
51
+ string << "\n" if skip_instruct # Emulate Builder behaviour
52
+ return string
53
+ else
54
+ return options[:builder]
55
+ end
56
+ end # to_xml_with_libxml
57
+ end # module Conversions
58
+ end # module Hash
59
+ end # module CoreExtensions
60
+ end # module Narnach
61
+
62
+ class Array
63
+ include Narnach::CoreExtensions::Array::Conversions
64
+ end
@@ -0,0 +1,91 @@
1
+ module Narnach #:nodoc:
2
+ module CoreExtensions #:nodoc:
3
+ module Hash #:nodoc:
4
+ module Conversions
5
+ XML_TYPE_NAMES = ActiveSupport::CoreExtensions::Hash::Conversions::XML_TYPE_NAMES unless defined?(XML_TYPE_NAMES)
6
+ XML_FORMATTING = ActiveSupport::CoreExtensions::Hash::Conversions::XML_FORMATTING unless defined?(XML_FORMATTING)
7
+
8
+ # Version of #to_xml copy-adjusted from ActiveSupport to work with libxml.
9
+ #
10
+ # Options supported in XMLBuilder that are/will be supported here:
11
+ # - :root, name of root node. Default to hash
12
+ # - :children, name of child nodes. Default to singular of root.
13
+ # - :skip_types
14
+ #
15
+ # Options supported in a different way:
16
+ # - :builder. Holds the active LibXML::XML::Document or LibXML::XML::Node.
17
+ # - :skip_instruct. Determine if the document encoding needs to be set. When false, a new Document is created and used as :builder. When true a new Node is created when no :builder is specified.
18
+ # - :dasherize, dasherize the node names or keep them as-is.
19
+ #
20
+ # Options known but not (yet) supported:
21
+ # - :indent, default to level 2
22
+ #
23
+ # Uses Builder's String#to_xs for escaping
24
+ def to_xml_with_libxml(options = {})
25
+ options.reverse_merge!({:root => "hash", :to_string => true })
26
+ dasherize = !options.has_key?(:dasherize) || options[:dasherize]
27
+ root = dasherize ? options[:root].to_s.dasherize : options[:root].to_s
28
+ to_string = options.delete(:to_string)
29
+ skip_instruct = options.delete(:skip_instruct)
30
+ doc = nil
31
+ if skip_instruct
32
+ node = LibXML::XML::Node.new(root)
33
+ doc = node
34
+ case options[:builder]
35
+ when LibXML::XML::Document
36
+ options[:builder].root << node
37
+ when nil
38
+ options[:builder] = node
39
+ else
40
+ options[:builder] << node
41
+ end
42
+ else
43
+ options[:builder] = LibXML::XML::Document.new
44
+ options[:builder].encoding = 'UTF-8'
45
+ options[:builder].root = LibXML::XML::Node.new(root)
46
+ doc = options[:builder].root
47
+ end
48
+ self.each do |key, value|
49
+ case value
50
+ when ::Hash
51
+ value.to_xml_with_libxml(options.merge({ :root => key, :skip_instruct => true, :to_string => false, :builder => doc }))
52
+ when ::Array
53
+ value.to_xml_with_libxml(options.merge({ :root => key, :children => key.to_s.singularize, :skip_instruct => true, :to_string => false, :builder => doc}))
54
+ # when ::Method, ::Proc
55
+ else
56
+ if value.respond_to?(:to_xml_with_libxml)
57
+ value.to_xml_with_libxml(options.merge({ :root => key, :skip_instruct => true, :to_string => false, :builder => doc }))
58
+ else
59
+ type_name = XML_TYPE_NAMES[value.class.name]
60
+ key = dasherize ? key.to_s.dasherize : key.to_s
61
+ attributes = options[:skip_types] || value.nil? || type_name.nil? ? { } : { :type => type_name }
62
+ if value.nil?
63
+ attributes[:nil] = true
64
+ end
65
+
66
+ content = XML_FORMATTING[type_name] ? XML_FORMATTING[type_name].call(value) : value
67
+ child = LibXML::XML::Node.new(key, content.to_s.to_xs)
68
+ attributes.stringify_keys.each do |akey, avalue|
69
+ child[akey] = String(avalue).to_s.to_xs
70
+ end
71
+ doc << child
72
+ end # if
73
+ end # case
74
+ end # each
75
+ yield options[:builder] if block_given?
76
+ if to_string
77
+ string = options[:builder].to_s
78
+ string << "\n" if skip_instruct # Emulate Builder behaviour
79
+ return string
80
+ else
81
+ return options[:builder]
82
+ end
83
+ end # to_xml_with_libxml
84
+ end # module Conversions
85
+ end # module Hash
86
+ end # module CoreExtensions
87
+ end # module Narnach
88
+
89
+ class Hash
90
+ include Narnach::CoreExtensions::Hash::Conversions
91
+ end
@@ -0,0 +1,64 @@
1
+ gem 'activeresource'
2
+ require 'active_resource'
3
+ require 'active_resource/formats/xml_format'
4
+
5
+ module Narnach
6
+ module RailsExt
7
+ module ActiveResource
8
+ module Base
9
+ # A method to convert the the resource to an XML string.
10
+ #
11
+ # This is a copy-adjusted version of ActiveResource's #to_xml, but
12
+ # it uses libxml for serialization instead of builder.
13
+ #
14
+ # ==== Options
15
+ # The +options+ parameter is handed off to the +to_xml_with_libxml+ method on each
16
+ # attribute, so it has the same options as the +to_xml_with_libxml+ methods in
17
+ # Active Support.
18
+ #
19
+ # * <tt>:dasherize</tt> - Boolean option to determine whether or not element names should
20
+ # replace underscores with dashes (default is <tt>false</tt>).
21
+ # * <tt>:skip_instruct</tt> - Toggle skipping the +instruct!+ call on the XML builder
22
+ # that generates the XML declaration (default is <tt>false</tt>).
23
+ #
24
+ # ==== Examples
25
+ # my_group = SubsidiaryGroup.find(:first)
26
+ # my_group.to_xml
27
+ # # => <?xml version="1.0" encoding="UTF-8"?>
28
+ # # <subsidiary_group> [...] </subsidiary_group>
29
+ #
30
+ # my_group.to_xml(:dasherize => true)
31
+ # # => <?xml version="1.0" encoding="UTF-8"?>
32
+ # # <subsidiary-group> [...] </subsidiary-group>
33
+ #
34
+ # my_group.to_xml(:skip_instruct => true)
35
+ # # => <subsidiary_group> [...] </subsidiary_group>
36
+ def to_xml(options={})
37
+ attributes.to_xml_with_libxml({:root => self.class.element_name}.merge(options))
38
+ end
39
+ end
40
+
41
+ module Formats
42
+ module XmlFormat
43
+ def encode(hash, options={})
44
+ hash.to_xml_with_libxml(options)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ module ActiveResource
53
+ module Formats
54
+ XmlFormat.class_eval do
55
+ remove_method :encode
56
+ include Narnach::RailsExt::ActiveResource::Formats::XmlFormat
57
+ end
58
+ end
59
+
60
+ Base.class_eval do
61
+ remove_method :to_xml
62
+ include Narnach::RailsExt::ActiveResource::Base
63
+ end
64
+ end
@@ -0,0 +1,34 @@
1
+ Gem::Specification.new do |s|
2
+ # Project
3
+ s.name = 'libxml_rails'
4
+ s.description = "Libxml_rails replaces ActiveSupport's XmlSimple XML parsing with libxml-ruby. Original gem by Brandon Mitchell, this fork is by Wes 'Narnach' Oldenbeuving."
5
+ s.summary = s.description
6
+ s.version = '0.0.2.5'
7
+ s.date = '2009-01-06'
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Brandon Mitchell", "Wes Oldenbeuving"]
10
+ s.email = "narnach@gmail.com"
11
+ s.homepage = "http://www.github.com/Narnach/libxml_rails"
12
+
13
+ # Files
14
+ root_files = %w[CHANGELOG init.rb libxml_rails.gemspec MIT-LICENSE Rakefile README.rdoc]
15
+ lib_files = %w[bitbckt/core_ext/hash/conversions narnach/core_ext/hash/conversions narnach/core_ext/array/conversions narnach/rails_ext/active_resource libxml_rails libxml_rails/active_resource]
16
+ spec_files = %w[from_xml] + lib_files.select{|f| f[0...7] == 'narnach'}
17
+ other_files = %w[spec/spec_helper.rb]
18
+ s.require_path = "lib"
19
+ s.executables = []
20
+ s.test_files = spec_files.map {|f| 'spec/%s_spec.rb' % f}
21
+ s.files = root_files + s.test_files + other_files + lib_files.map {|f| 'lib/%s.rb' % f}
22
+
23
+ # rdoc
24
+ s.has_rdoc = true
25
+ s.extra_rdoc_files = %w[ README.rdoc MIT-LICENSE]
26
+ s.rdoc_options << '--inline-source' << '--line-numbers' << '--main' << 'README.rdoc'
27
+
28
+ # Dependencies
29
+ s.add_dependency 'activesupport', '>= 2.1.0'
30
+ s.add_dependency 'libxml-ruby', '>= 0.8.3'
31
+
32
+ # Requirements
33
+ s.required_ruby_version = ">= 1.8.0"
34
+ end
@@ -0,0 +1,328 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Hash, '.from_xml' do
4
+
5
+ before(:each) do
6
+ @xml_options = { :root => :person, :skip_instruct => true, :indent => 0 }
7
+ end
8
+
9
+ it 'parses a single record' do
10
+ topic_xml = <<-EOT
11
+ <topic>
12
+ <title>The First Topic</title>
13
+ <author-name>David</author-name>
14
+ <id type="integer">1</id>
15
+ <approved type="boolean"> true </approved>
16
+ <replies-count type="integer">0</replies-count>
17
+ <replies-close-in type="integer">2592000000</replies-close-in>
18
+ <written-on type="date">2003-07-16</written-on>
19
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
20
+ <content type="yaml">--- \n1: should be an integer\n:message: Have a nice day\narray: \n- should-have-dashes: true\n should_have_underscores: true\n</content>
21
+ <author-email-address>david@loudthinking.com</author-email-address>
22
+ <parent-id></parent-id>
23
+ <ad-revenue type="decimal">1.5</ad-revenue>
24
+ <optimum-viewing-angle type="float">135</optimum-viewing-angle>
25
+ <resident type="symbol">yes</resident>
26
+ </topic>
27
+ EOT
28
+
29
+ expected_topic_hash = {
30
+ :title => "The First Topic",
31
+ :author_name => "David",
32
+ :id => 1,
33
+ :approved => true,
34
+ :replies_count => 0,
35
+ :replies_close_in => 2592000000,
36
+ :written_on => Date.new(2003, 7, 16),
37
+ :viewed_at => Time.utc(2003, 7, 16, 9, 28),
38
+ :content => { :message => "Have a nice day", 1 => "should be an integer", "array" => [{ "should-have-dashes" => true, "should_have_underscores" => true }] },
39
+ :author_email_address => "david@loudthinking.com",
40
+ :parent_id => nil,
41
+ :ad_revenue => BigDecimal("1.50"),
42
+ :optimum_viewing_angle => 135.0,
43
+ :resident => :yes
44
+ }.stringify_keys
45
+
46
+ Hash.from_xml(topic_xml)["topic"].should == expected_topic_hash
47
+ end
48
+
49
+ it 'parses a single record with nil values' do
50
+ topic_xml = <<-EOT
51
+ <topic>
52
+ <title></title>
53
+ <id type="integer"></id>
54
+ <approved type="boolean"></approved>
55
+ <written-on type="date"></written-on>
56
+ <viewed-at type="datetime"></viewed-at>
57
+ <content type="yaml"></content>
58
+ <parent-id></parent-id>
59
+ </topic>
60
+ EOT
61
+
62
+ expected_topic_hash = {
63
+ :title => nil,
64
+ :id => nil,
65
+ :approved => nil,
66
+ :written_on => nil,
67
+ :viewed_at => nil,
68
+ :content => nil,
69
+ :parent_id => nil
70
+ }.stringify_keys
71
+
72
+ Hash.from_xml(topic_xml)["topic"].should == expected_topic_hash
73
+ end
74
+
75
+ it 'parses multiple records' do
76
+ topics_xml = <<-EOT
77
+ <topics type="array">
78
+ <topic>
79
+ <title>The First Topic</title>
80
+ <author-name>David</author-name>
81
+ <id type="integer">1</id>
82
+ <approved type="boolean">false</approved>
83
+ <replies-count type="integer">0</replies-count>
84
+ <replies-close-in type="integer">2592000000</replies-close-in>
85
+ <written-on type="date">2003-07-16</written-on>
86
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
87
+ <content>Have a nice day</content>
88
+ <author-email-address>david@loudthinking.com</author-email-address>
89
+ <parent-id nil="true"></parent-id>
90
+ </topic>
91
+ <topic>
92
+ <title>The Second Topic</title>
93
+ <author-name>Jason</author-name>
94
+ <id type="integer">1</id>
95
+ <approved type="boolean">false</approved>
96
+ <replies-count type="integer">0</replies-count>
97
+ <replies-close-in type="integer">2592000000</replies-close-in>
98
+ <written-on type="date">2003-07-16</written-on>
99
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
100
+ <content>Have a nice day</content>
101
+ <author-email-address>david@loudthinking.com</author-email-address>
102
+ <parent-id></parent-id>
103
+ </topic>
104
+ </topics>
105
+ EOT
106
+
107
+ expected_topic_hash = {
108
+ :title => "The First Topic",
109
+ :author_name => "David",
110
+ :id => 1,
111
+ :approved => false,
112
+ :replies_count => 0,
113
+ :replies_close_in => 2592000000,
114
+ :written_on => Date.new(2003, 7, 16),
115
+ :viewed_at => Time.utc(2003, 7, 16, 9, 28),
116
+ :content => "Have a nice day",
117
+ :author_email_address => "david@loudthinking.com",
118
+ :parent_id => nil
119
+ }.stringify_keys
120
+
121
+ Hash.from_xml(topics_xml)["topics"].first.should == expected_topic_hash
122
+ end
123
+
124
+ it 'parses a single record with attributes *other* than "type"' do
125
+ topic_xml = <<-EOT
126
+ <rsp stat="ok">
127
+ <photos page="1" pages="1" perpage="100" total="16">
128
+ <photo id="175756086" owner="55569174@N00" secret="0279bf37a1" server="76" title="Colored Pencil PhotoBooth Fun" ispublic="1" isfriend="0" isfamily="0"/>
129
+ </photos>
130
+ </rsp>
131
+ EOT
132
+
133
+ expected_topic_hash = {
134
+ :id => "175756086",
135
+ :owner => "55569174@N00",
136
+ :secret => "0279bf37a1",
137
+ :server => "76",
138
+ :title => "Colored Pencil PhotoBooth Fun",
139
+ :ispublic => "1",
140
+ :isfriend => "0",
141
+ :isfamily => "0",
142
+ }.stringify_keys
143
+
144
+ Hash.from_xml(topic_xml)["rsp"]["photos"]["photo"].should == expected_topic_hash
145
+ end
146
+
147
+ it 'parses an empty array' do
148
+ blog_xml = <<-XML
149
+ <blog>
150
+ <posts type="array"></posts>
151
+ </blog>
152
+ XML
153
+ expected_blog_hash = {"blog" => {"posts" => []}}
154
+ Hash.from_xml(blog_xml).should == expected_blog_hash
155
+ end
156
+
157
+ it 'parses an empty array with whitespace' do
158
+ blog_xml = <<-XML
159
+ <blog>
160
+ <posts type="array">
161
+ </posts>
162
+ </blog>
163
+ XML
164
+ expected_blog_hash = {"blog" => {"posts" => []}}
165
+ Hash.from_xml(blog_xml).should == expected_blog_hash
166
+ end
167
+
168
+ it 'parses an array with one entry' do
169
+ blog_xml = <<-XML
170
+ <blog>
171
+ <posts type="array">
172
+ <post>a post</post>
173
+ </posts>
174
+ </blog>
175
+ XML
176
+ expected_blog_hash = {"blog" => {"posts" => ["a post"]}}
177
+ Hash.from_xml(blog_xml).should == expected_blog_hash
178
+ end
179
+
180
+ it 'parses an array with multiple entries' do
181
+ blog_xml = <<-XML
182
+ <blog>
183
+ <posts type="array">
184
+ <post>a post</post>
185
+ <post>another post</post>
186
+ </posts>
187
+ </blog>
188
+ XML
189
+ expected_blog_hash = {"blog" => {"posts" => ["a post", "another post"]}}
190
+ Hash.from_xml(blog_xml).should == expected_blog_hash
191
+ end
192
+
193
+ it 'parses a file' do
194
+ blog_xml = <<-XML
195
+ <blog>
196
+ <logo type="file" name="logo.png" content_type="image/png">
197
+ </logo>
198
+ </blog>
199
+ XML
200
+ hash = Hash.from_xml(blog_xml)
201
+ hash.has_key?('blog').should be_true
202
+ hash['blog'].has_key?('logo').should be_true
203
+
204
+ file = hash['blog']['logo']
205
+ file.original_filename.should == 'logo.png'
206
+ file.content_type.should == 'image/png'
207
+ end
208
+
209
+ it 'parses a file with defaults' do
210
+ blog_xml = <<-XML
211
+ <blog>
212
+ <logo type="file">
213
+ </logo>
214
+ </blog>
215
+ XML
216
+ file = Hash.from_xml(blog_xml)['blog']['logo']
217
+ file.original_filename.should == 'untitled'
218
+ file.content_type.should == 'application/octet-stream'
219
+ end
220
+
221
+ it 'parses XSD-like types' do
222
+ bacon_xml = <<-EOT
223
+ <bacon>
224
+ <weight type="double">0.5</weight>
225
+ <price type="decimal">12.50</price>
226
+ <chunky type="boolean"> 1 </chunky>
227
+ <expires-at type="dateTime">2007-12-25T12:34:56+0000</expires-at>
228
+ <notes type="string"></notes>
229
+ <illustration type="base64Binary">YmFiZS5wbmc=</illustration>
230
+ </bacon>
231
+ EOT
232
+
233
+ expected_bacon_hash = {
234
+ :weight => 0.5,
235
+ :chunky => true,
236
+ :price => BigDecimal("12.50"),
237
+ :expires_at => Time.utc(2007,12,25,12,34,56),
238
+ :notes => "",
239
+ :illustration => "babe.png"
240
+ }.stringify_keys
241
+
242
+ Hash.from_xml(bacon_xml)["bacon"].should == expected_bacon_hash
243
+ end
244
+
245
+ it 'trickles the "type" attribute through, when unknown' do
246
+ product_xml = <<-EOT
247
+ <product>
248
+ <weight type="double">0.5</weight>
249
+ <image type="ProductImage"><filename>image.gif</filename></image>
250
+
251
+ </product>
252
+ EOT
253
+
254
+ expected_product_hash = {
255
+ :weight => 0.5,
256
+ :image => {'type' => 'ProductImage', 'filename' => 'image.gif' },
257
+ }.stringify_keys
258
+
259
+ Hash.from_xml(product_xml)["product"].should == expected_product_hash
260
+ end
261
+
262
+ it 'unescapes text nodes' do
263
+ xml_string = '<person><bare-string>First &amp; Last Name</bare-string><pre-escaped-string>First &amp;amp; Last Name</pre-escaped-string></person>'
264
+ expected_hash = {
265
+ :bare_string => 'First & Last Name',
266
+ :pre_escaped_string => 'First &amp; Last Name'
267
+ }.stringify_keys
268
+ Hash.from_xml(xml_string)['person'].should == expected_hash
269
+ end
270
+
271
+ it 'parses Hash.to_xml output' do
272
+ hash = {
273
+ :bare_string => 'First & Last Name',
274
+ :pre_escaped_string => 'First &amp; Last Name'
275
+ }.stringify_keys
276
+
277
+ Hash.from_xml(hash.to_xml(@xml_options))['person'].should == hash
278
+ end
279
+
280
+ it 'parses a datetime with UTC time' do
281
+ alert_xml = <<-XML
282
+ <alert>
283
+ <alert_at type="datetime">2008-02-10T15:30:45Z</alert_at>
284
+ </alert>
285
+ XML
286
+ alert_at = Hash.from_xml(alert_xml)['alert']['alert_at']
287
+ alert_at.utc?.should be_true
288
+ alert_at.should == Time.utc(2008, 2, 10, 15, 30, 45)
289
+ end
290
+
291
+ it 'parses a datetime with non-UTC time' do
292
+ alert_xml = <<-XML
293
+ <alert>
294
+ <alert_at type="datetime">2008-02-10T10:30:45-05:00</alert_at>
295
+ </alert>
296
+ XML
297
+ alert_at = Hash.from_xml(alert_xml)['alert']['alert_at']
298
+ alert_at.utc?.should be_true
299
+ alert_at.should == Time.utc(2008, 2, 10, 15, 30, 45)
300
+ end
301
+
302
+ it 'parses a datetime with a far-future date' do
303
+ alert_xml = <<-XML
304
+ <alert>
305
+ <alert_at type="datetime">2050-02-10T15:30:45Z</alert_at>
306
+ </alert>
307
+ XML
308
+ alert_at = Hash.from_xml(alert_xml)['alert']['alert_at']
309
+ alert_at.utc?.should be_true
310
+ alert_at.year.should == 2050
311
+ alert_at.month.should == 2
312
+ alert_at.day.should == 10
313
+ alert_at.hour.should == 15
314
+ alert_at.min.should == 30
315
+ alert_at.sec.should == 45
316
+ end
317
+
318
+ it 'undasherizes the root node name' do
319
+ xml = <<-EOT
320
+ <?xml version="1.0" encoding="UTF-8"?>
321
+ <with-dash>
322
+ <another-dash>text</another-dash>
323
+ </with-dash>
324
+ EOT
325
+ expected_hash = { "with_dash" => {"another_dash"=>"text"} }
326
+ Hash.from_xml(xml).should == expected_hash
327
+ end
328
+ end
@@ -0,0 +1,73 @@
1
+ require File.dirname(__FILE__) + '/../../../spec_helper'
2
+ require 'narnach/core_ext/array/conversions'
3
+
4
+ describe Array, '#to_xml' do
5
+ describe 'ActiveSupport tests' do
6
+ before(:each) do
7
+ @builder_xml_options = {:skip_instruct => true }
8
+ @libxml_options = {:skip_instruct => true }
9
+ end
10
+
11
+ def compare_with_rails_for(hash, options = {})
12
+ builder_xml = hash.to_xml(@builder_xml_options.merge(options))
13
+ libxml_xml = hash.to_xml_with_libxml(@libxml_options.merge(options))
14
+ libxml_xml.should == builder_xml
15
+ end
16
+
17
+ it 'should pass test_to_xml' do
18
+ compare_with_rails_for([
19
+ { :name => "David", :age => 26, :age_in_millis => 820497600000 },
20
+ { :name => "Jason", :age => 31, :age_in_millis => BigDecimal.new('1.0') }
21
+ ])
22
+ end
23
+
24
+ it 'should pass test_to_xml_with_dedicated_name' do
25
+ compare_with_rails_for([
26
+ { :name => "David", :age => 26, :age_in_millis => 820497600000 }, { :name => "Jason", :age => 31 }
27
+ ],{:root => "people"})
28
+ end
29
+
30
+ it 'should pass test_to_xml_with_options' do
31
+ compare_with_rails_for([
32
+ { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" }
33
+ ], {:skip_types => true})
34
+ end
35
+
36
+ it 'should pass test_to_xml_with_dasherize_false' do
37
+ compare_with_rails_for([
38
+ { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" }
39
+ ], {:skip_types => true, :dasherize => false})
40
+ end
41
+
42
+ it 'should pass test_to_xml_with_dasherize_true' do
43
+ compare_with_rails_for([
44
+ { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" }
45
+ ], {:skip_types => true, :dasherize => true})
46
+ end
47
+
48
+ it 'should pass test_to_with_instruct' do
49
+ compare_with_rails_for([
50
+ { :name => "David", :age => 26, :age_in_millis => 820497600000 },
51
+ { :name => "Jason", :age => 31, :age_in_millis => BigDecimal.new('1.0') }
52
+ ], {:skip_instruct => false})
53
+ end
54
+
55
+ it 'should pass test_to_xml_with_block' do
56
+ ary = [
57
+ { :name => "David", :age => 26, :age_in_millis => 820497600000 },
58
+ { :name => "Jason", :age => 31, :age_in_millis => BigDecimal.new('1.0') }
59
+ ]
60
+ builder_xml = ary.to_xml(@builder_xml_options) do |builder|
61
+ builder.count 2
62
+ end
63
+ libxml_xml = ary.to_xml_with_libxml(@libxml_options) do |libxml|
64
+ libxml << LibXML::XML::Node.new('count', 2)
65
+ end
66
+ libxml_xml.should == builder_xml
67
+ end
68
+
69
+ it 'should pass test_to_xml_with_empty' do
70
+ compare_with_rails_for([])
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,154 @@
1
+ require File.dirname(__FILE__) + '/../../../spec_helper'
2
+
3
+ describe Hash, '#from_xml' do
4
+ it "should undasherize the Hash keys" do
5
+ xml = <<-XML
6
+ <?xml version="1.0" encoding="UTF-8"?>
7
+ <post>
8
+ <post-id type="integer">1</post-id>
9
+ </post>
10
+ XML
11
+ Hash.from_xml(xml).should == {'post' => {'post_id' => 1}}
12
+ end
13
+
14
+ # # libxml will not break, but it takes about 140MB and 60 seconds on a 2.4GHz Core2 Duo Macbook to succeed.
15
+ # # It would be nice if entity expansion could be disabled if you don't plan to use it.
16
+ # it 'should pass test_expansion_count_is_limited' do
17
+ # attack_xml = <<-EOT
18
+ # <?xml version="1.0" encoding="UTF-8"?>
19
+ # <!DOCTYPE member [
20
+ # <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
21
+ # <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
22
+ # <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
23
+ # <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
24
+ # <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;">
25
+ # <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;">
26
+ # <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
27
+ # ]>
28
+ # <member>
29
+ # &a;
30
+ # </member>
31
+ # EOT
32
+ # lambda {
33
+ # Hash.from_xml(attack_xml)
34
+ # }.should_not raise_error
35
+ # end
36
+ end
37
+
38
+ class IWriteMyOwnXML
39
+ def to_xml(options = {})
40
+ options[:indent] ||= 2
41
+ xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
42
+ xml.instruct! unless options[:skip_instruct]
43
+ xml.level_one do
44
+ xml.tag!(:second_level, 'content')
45
+ end
46
+ end
47
+
48
+ def to_xml_with_libxml(options = {})
49
+ {:second_level => 'content'}.to_xml_with_libxml(options.merge(:root => 'level_one', :dasherize => false))
50
+ end
51
+ end
52
+
53
+ describe Hash, "#to_xml_with_libxml" do
54
+ it "should convert a Hash with one element" do
55
+ hsh = {:one => 1}
56
+ libxml_xml = hsh.to_xml_with_libxml.to_s
57
+ builder_xml = hsh.to_xml
58
+ libxml_xml.should == builder_xml
59
+ end
60
+
61
+ it "should convert a Hash with nested Hash" do
62
+ hsh = {:one => 1, 'two' => { :three => 3}}
63
+ libxml_xml = hsh.to_xml_with_libxml.to_s
64
+ builder_xml = hsh.to_xml
65
+ libxml_xml.should == builder_xml
66
+ end
67
+
68
+ # These tests are adapted from the Rails ActiveSupport tests, so test to_xml_with_libxml in the same situations as to_xml
69
+ describe 'ActiveSupport tests' do
70
+ before(:each) do
71
+ @builder_xml_options = { :root => :person, :skip_instruct => true }
72
+ @libxml_options = { :root => :person, :skip_instruct => true }
73
+ end
74
+
75
+ def compare_with_rails_for(hash, custom_builder_options={}, custom_libxml_options={})
76
+ builder_xml = hash.to_xml(@builder_xml_options.merge(custom_builder_options))
77
+ libxml_xml = hash.to_xml_with_libxml(@libxml_options.merge(custom_libxml_options))
78
+ libxml_xml.should == builder_xml
79
+ end
80
+
81
+ it 'should pass test_one_level' do
82
+ compare_with_rails_for({ :name => "David", :street => "Paulina" })
83
+ end
84
+
85
+ it 'should pass test_one_level_dasherize_false' do
86
+ compare_with_rails_for({ :name => "David", :street_name => "Paulina" }, {:dasherize => false}, {:dasherize => false})
87
+ end
88
+
89
+ it 'should pass test_one_level_dasherize_true' do
90
+ compare_with_rails_for({ :name => "David", :street_name => "Paulina" }, {:dasherize => true}, {:dasherize => true})
91
+ end
92
+
93
+ it 'should pass test_one_level_with_types' do
94
+ compare_with_rails_for({ :name => "David", :street => "Paulina", :age => 26, :age_in_millis => 820497600000, :moved_on => Date.new(2005, 11, 15), :resident => :yes })
95
+ end
96
+
97
+ it 'should pass test_one_level_with_nils' do
98
+ compare_with_rails_for({ :name => "David", :street => "Paulina", :age => nil })
99
+ end
100
+
101
+ it 'should pass test_one_level_with_skipping_types' do
102
+ compare_with_rails_for({ :name => "David", :street => "Paulina", :age => nil }, {:skip_types => true}, {:skip_types => true})
103
+ end
104
+
105
+ it 'should pass test_one_level_with_yielding' do
106
+ hash = { :name => "David", :street => "Paulina" }
107
+ builder_xml = hash.to_xml(@builder_xml_options) do |x|
108
+ x.creator("Rails")
109
+ end
110
+ libxml_xml = hash.to_xml_with_libxml(@libxml_options) do |x|
111
+ x << LibXML::XML::Node.new('creator', 'Rails')
112
+ end
113
+ libxml_xml.should == builder_xml
114
+ end
115
+
116
+ it 'should pass test_two_levels' do
117
+ compare_with_rails_for({ :name => "David", :address => { :street => "Paulina" } })
118
+ end
119
+
120
+ it 'should pass test_two_levels_with_second_level_overriding_to_xml' do
121
+ compare_with_rails_for({ :name => "David", :address => { :street => "Paulina" }, :child => IWriteMyOwnXML.new })
122
+ end
123
+
124
+ it 'should pass test_two_levels_with_array' do
125
+ compare_with_rails_for({ :name => "David", :addresses => [{ :street => "Paulina" }, { :street => "Evergreen" }] })
126
+ end
127
+
128
+ it 'should test_three_levels_with_array' do
129
+ compare_with_rails_for({ :name => "David", :addresses => [{ :streets => [ { :name => "Paulina" }, { :name => "Paulina" } ] } ] })
130
+ end
131
+
132
+ # The XML builder seems to fail miserably when trying to tag something
133
+ # with the same name as a Kernel method (throw, test, loop, select ...)
134
+ it 'should pass test_kernel_method_names_to_xml' do
135
+ compare_with_rails_for({ :throw => { :ball => 'red' } })
136
+ end
137
+
138
+ it 'should pass test_escaping_to_xml' do
139
+ compare_with_rails_for({
140
+ :bare_string => 'First & Last Name',
141
+ :pre_escaped_string => 'First &amp; Last Name'
142
+ }.stringify_keys)
143
+ end
144
+
145
+ it 'should pass test_roundtrip_to_xml_from_xml' do
146
+ hash = {
147
+ :bare_string => 'First & Last Name',
148
+ :pre_escaped_string => 'First &amp; Last Name'
149
+ }.stringify_keys
150
+
151
+ Hash.from_xml(hash.to_xml_with_libxml(@libxml_options))['person'].should == hash
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,16 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+ require 'libxml_rails/active_resource'
3
+
4
+ class SomePerson < ActiveResource::Base
5
+ self.site = 'http://example.org'
6
+ end
7
+
8
+ describe ActiveResource, '#to_xml' do
9
+ before(:each) do
10
+ @sp = SomePerson.new(:name => 'Wes', :last_name => 'Oldenbeuving', :age => 22)
11
+ end
12
+
13
+ it "should return the resource as XML" do
14
+ @sp.to_xml(:libxml=>false).should == @sp.to_xml(:libxml=>true)
15
+ end
16
+ end
@@ -0,0 +1,4 @@
1
+ lib_dir = File.expand_path(File.join(File.dirname(__FILE__),'..','lib'))
2
+ $LOAD_PATH.unshift(lib_dir)
3
+
4
+ require 'libxml_rails'
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: Narnach-libxml_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2.5
5
+ platform: ruby
6
+ authors:
7
+ - Brandon Mitchell
8
+ - Wes Oldenbeuving
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-01-06 00:00:00 -08:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: activesupport
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.1.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: libxml-ruby
27
+ version_requirement:
28
+ version_requirements: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.8.3
33
+ version:
34
+ description: Libxml_rails replaces ActiveSupport's XmlSimple XML parsing with libxml-ruby. Original gem by Brandon Mitchell, this fork is by Wes 'Narnach' Oldenbeuving.
35
+ email: narnach@gmail.com
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files:
41
+ - README.rdoc
42
+ - MIT-LICENSE
43
+ files:
44
+ - CHANGELOG
45
+ - init.rb
46
+ - libxml_rails.gemspec
47
+ - MIT-LICENSE
48
+ - Rakefile
49
+ - README.rdoc
50
+ - spec/from_xml_spec.rb
51
+ - spec/narnach/core_ext/hash/conversions_spec.rb
52
+ - spec/narnach/core_ext/array/conversions_spec.rb
53
+ - spec/narnach/rails_ext/active_resource_spec.rb
54
+ - spec/spec_helper.rb
55
+ - lib/bitbckt/core_ext/hash/conversions.rb
56
+ - lib/narnach/core_ext/hash/conversions.rb
57
+ - lib/narnach/core_ext/array/conversions.rb
58
+ - lib/narnach/rails_ext/active_resource.rb
59
+ - lib/libxml_rails.rb
60
+ - lib/libxml_rails/active_resource.rb
61
+ has_rdoc: true
62
+ homepage: http://www.github.com/Narnach/libxml_rails
63
+ post_install_message:
64
+ rdoc_options:
65
+ - --inline-source
66
+ - --line-numbers
67
+ - --main
68
+ - README.rdoc
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 1.8.0
76
+ version:
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ version:
83
+ requirements: []
84
+
85
+ rubyforge_project:
86
+ rubygems_version: 1.2.0
87
+ signing_key:
88
+ specification_version: 2
89
+ summary: Libxml_rails replaces ActiveSupport's XmlSimple XML parsing with libxml-ruby. Original gem by Brandon Mitchell, this fork is by Wes 'Narnach' Oldenbeuving.
90
+ test_files:
91
+ - spec/from_xml_spec.rb
92
+ - spec/narnach/core_ext/hash/conversions_spec.rb
93
+ - spec/narnach/core_ext/array/conversions_spec.rb
94
+ - spec/narnach/rails_ext/active_resource_spec.rb