crackoid 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ *.gem
data/History ADDED
@@ -0,0 +1,25 @@
1
+ == 0.1.7 2010-02-19
2
+ * 1 minor patch
3
+ * Added patch from @purp for ISO 8601 date/time format
4
+ == 0.1.6 2010-01-31
5
+ * 1 minor patch
6
+ * Added Crack::VERSION constant - http://weblog.rubyonrails.org/2009/9/1/gem-packaging-best-practices
7
+ == 0.1.5 2010-01-27
8
+ * 1 minor patch
9
+ * Strings that begin with dates shouldn't be parsed as such (sandro)
10
+
11
+ == 0.1.3 2009-06-22
12
+ * 1 minor patch
13
+ * Parsing a text node with attributes stores them in the attributes method (tamalw)
14
+
15
+ == 0.1.2 2009-04-21
16
+ * 2 minor patches
17
+ * Correct unnormalization of attribute values (der-flo)
18
+ * Fix error in parsing YAML in the case where a hash value ends with backslashes, and there are subsequent values in the hash (deadprogrammer)
19
+
20
+ == 0.1.1 2009-03-31
21
+ * 1 minor patch
22
+ * Parsing empty or blank xml now returns empty hash instead of raising error.
23
+
24
+ == 0.1.0 2009-03-28
25
+ * Initial release.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 John Nunemaker
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.
data/README.rdoc ADDED
@@ -0,0 +1,42 @@
1
+ = crack
2
+
3
+ Really simple JSON and XML parsing, ripped from Merb and Rails. The XML parser is ripped from Merb and the JSON parser is ripped from Rails. I take no credit, just packaged them for all to enjoy and easily use.
4
+
5
+ == note on releases
6
+
7
+ Releases are tagged on github and also released as gems on github and rubyforge. Master is pushed to whenever I add a patch or a new feature. To build from master, you can clone the code, generate the updated gemspec, build the gem and install.
8
+
9
+ * rake gemspec
10
+ * gem build httparty.gemspec
11
+ * gem install the gem that was built
12
+
13
+ == note on patches/pull requests
14
+
15
+ * Fork the project.
16
+ * Make your feature addition or bug fix.
17
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
18
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself in another branch so I can ignore when I pull)
19
+ * Send me a pull request. Bonus points for topic branches.
20
+
21
+ == usage
22
+
23
+ gem 'crack'
24
+ require 'crack' # for xml and json
25
+ require 'crack/json' # for just json
26
+ require 'crack/xml' # for just xml
27
+
28
+ == examples
29
+
30
+ Crack::XML.parse("<tag>This is the contents</tag>")
31
+ # => {'tag' => 'This is the contents'}
32
+
33
+ Crack::JSON.parse('{"tag":"This is the contents"}')
34
+ # => {'tag' => 'This is the contents'}
35
+
36
+ == Copyright
37
+
38
+ Copyright (c) 2009 John Nunemaker. See LICENSE for details.
39
+
40
+ == Docs
41
+
42
+ http://rdoc.info/projects/jnunemaker/crack
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require File.expand_path('../lib/crack', __FILE__)
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "crack"
9
+ gem.summary = %Q{Really simple JSON and XML parsing, ripped from Merb and Rails.}
10
+ gem.email = "nunemaker@gmail.com"
11
+ gem.homepage = "http://github.com/jnunemaker/crack"
12
+ gem.authors = ["John Nunemaker", "Wynn Netherland"]
13
+ gem.rubyforge_project = 'crack'
14
+ gem.version = Crack::VERSION
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
20
+ end
21
+
22
+ require 'rake/rdoctask'
23
+ Rake::RDocTask.new do |rdoc|
24
+ rdoc.rdoc_dir = 'rdoc'
25
+ rdoc.title = 'crack'
26
+ rdoc.options << '--line-numbers' << '--inline-source'
27
+ rdoc.rdoc_files.include('README*')
28
+ rdoc.rdoc_files.include('lib/**/*.rb')
29
+ end
30
+
31
+ require 'rake/testtask'
32
+ Rake::TestTask.new(:test) do |test|
33
+ test.libs << 'lib' << 'test'
34
+ test.pattern = 'test/**/*_test.rb'
35
+ test.verbose = false
36
+ end
37
+
38
+ begin
39
+ require 'rcov/rcovtask'
40
+ Rcov::RcovTask.new do |test|
41
+ test.libs << 'test'
42
+ test.pattern = 'test/**/*_test.rb'
43
+ test.verbose = true
44
+ end
45
+ rescue LoadError
46
+ task :rcov do
47
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
48
+ end
49
+ end
50
+
51
+
52
+ task :default => :test
data/crack.gemspec ADDED
@@ -0,0 +1,63 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{crackoid}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["sdomino"]
12
+ s.date = %q{2011-07-05}
13
+ s.email = %q{sdomino@pagodabox.com}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "History",
21
+ "LICENSE",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "crack.gemspec",
25
+ "lib/crack.rb",
26
+ "lib/crack/core_extensions.rb",
27
+ "lib/crack/json.rb",
28
+ "lib/crack/xml.rb",
29
+ "test/crack_test.rb",
30
+ "test/data/twittersearch-firefox.json",
31
+ "test/data/twittersearch-ie.json",
32
+ "test/hash_test.rb",
33
+ "test/json_test.rb",
34
+ "test/string_test.rb",
35
+ "test/test_helper.rb",
36
+ "test/xml_test.rb"
37
+ ]
38
+ s.homepage = %q{http://www.pagodabox.com}
39
+ s.rdoc_options = ["--charset=UTF-8"]
40
+ s.require_paths = ["lib"]
41
+ s.rubyforge_project = %q{crackoid}
42
+ s.rubygems_version = %q{1.3.6}
43
+ s.summary = %q{This gem is jnunemaker's 'Crack', with a fix so that XML parsing now works with mongoid!}
44
+ s.test_files = [
45
+ "test/crack_test.rb",
46
+ "test/hash_test.rb",
47
+ "test/json_test.rb",
48
+ "test/string_test.rb",
49
+ "test/test_helper.rb",
50
+ "test/xml_test.rb"
51
+ ]
52
+
53
+ if s.respond_to? :specification_version then
54
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
58
+ else
59
+ end
60
+ else
61
+ end
62
+ end
63
+
@@ -0,0 +1,128 @@
1
+ require 'uri'
2
+
3
+ class Object #:nodoc:
4
+ # @return <TrueClass, FalseClass>
5
+ #
6
+ # @example [].blank? #=> true
7
+ # @example [1].blank? #=> false
8
+ # @example [nil].blank? #=> false
9
+ #
10
+ # Returns true if the object is nil or empty (if applicable)
11
+ def blank?
12
+ nil? || (respond_to?(:empty?) && empty?)
13
+ end unless method_defined?(:blank?)
14
+ end # class Object
15
+
16
+ class Numeric #:nodoc:
17
+ # @return <TrueClass, FalseClass>
18
+ #
19
+ # Numerics can't be blank
20
+ def blank?
21
+ false
22
+ end unless method_defined?(:blank?)
23
+ end # class Numeric
24
+
25
+ class NilClass #:nodoc:
26
+ # @return <TrueClass, FalseClass>
27
+ #
28
+ # Nils are always blank
29
+ def blank?
30
+ true
31
+ end unless method_defined?(:blank?)
32
+ end # class NilClass
33
+
34
+ class TrueClass #:nodoc:
35
+ # @return <TrueClass, FalseClass>
36
+ #
37
+ # True is not blank.
38
+ def blank?
39
+ false
40
+ end unless method_defined?(:blank?)
41
+ end # class TrueClass
42
+
43
+ class FalseClass #:nodoc:
44
+ # False is always blank.
45
+ def blank?
46
+ true
47
+ end unless method_defined?(:blank?)
48
+ end # class FalseClass
49
+
50
+ class String #:nodoc:
51
+ # @example "".blank? #=> true
52
+ # @example " ".blank? #=> true
53
+ # @example " hey ho ".blank? #=> false
54
+ #
55
+ # @return <TrueClass, FalseClass>
56
+ #
57
+ # Strips out whitespace then tests if the string is empty.
58
+ def blank?
59
+ strip.empty?
60
+ end unless method_defined?(:blank?)
61
+
62
+ def snake_case
63
+ return self.downcase if self =~ /^[A-Z]+$/
64
+ self.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/, '_\&') =~ /_*(.*)/
65
+ return $+.downcase
66
+ end unless method_defined?(:snake_case)
67
+ end # class String
68
+
69
+ class Hash #:nodoc:
70
+ # @return <String> This hash as a query string
71
+ #
72
+ # @example
73
+ # { :name => "Bob",
74
+ # :address => {
75
+ # :street => '111 Ruby Ave.',
76
+ # :city => 'Ruby Central',
77
+ # :phones => ['111-111-1111', '222-222-2222']
78
+ # }
79
+ # }.to_params
80
+ # #=> "name=Bob&address[city]=Ruby Central&address[phones][]=111-111-1111&address[phones][]=222-222-2222&address[street]=111 Ruby Ave."
81
+ def to_params
82
+ params = self.map { |k,v| normalize_param(k,v) }.join
83
+ params.chop! # trailing &
84
+ params
85
+ end
86
+
87
+ # @param key<Object> The key for the param.
88
+ # @param value<Object> The value for the param.
89
+ #
90
+ # @return <String> This key value pair as a param
91
+ #
92
+ # @example normalize_param(:name, "Bob Jones") #=> "name=Bob%20Jones&"
93
+ def normalize_param(key, value)
94
+ param = ''
95
+ stack = []
96
+
97
+ if value.is_a?(Array)
98
+ param << value.map { |element| normalize_param("#{key}[]", element) }.join
99
+ elsif value.is_a?(Hash)
100
+ stack << [key,value]
101
+ else
102
+ param << "#{key}=#{URI.encode(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))}&"
103
+ end
104
+
105
+ stack.each do |parent, hash|
106
+ hash.each do |key, value|
107
+ if value.is_a?(Hash)
108
+ stack << ["#{parent}[#{key}]", value]
109
+ else
110
+ param << normalize_param("#{parent}[#{key}]", value)
111
+ end
112
+ end
113
+ end
114
+
115
+ param
116
+ end
117
+
118
+ # @return <String> The hash as attributes for an XML tag.
119
+ #
120
+ # @example
121
+ # { :one => 1, "two"=>"TWO" }.to_xml_attributes
122
+ # #=> 'one="1" two="TWO"'
123
+ def to_xml_attributes
124
+ map do |k,v|
125
+ %{#{k.to_s.snake_case.sub(/^(.{1,1})/) { |m| m.downcase }}="#{v}"}
126
+ end.join(' ')
127
+ end
128
+ end
data/lib/crack/json.rb ADDED
@@ -0,0 +1,68 @@
1
+ # Copyright (c) 2004-2008 David Heinemeier Hansson
2
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
3
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
4
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5
+
6
+ require 'yaml'
7
+ require 'strscan'
8
+
9
+ module Crack
10
+ class JSON
11
+ def self.parse(json)
12
+ YAML.load(unescape(convert_json_to_yaml(json)))
13
+ rescue ArgumentError => e
14
+ raise ParseError, "Invalid JSON string"
15
+ end
16
+
17
+ protected
18
+ def self.unescape(str)
19
+ str.gsub(/\\u([0-9a-f]{4})/) { [$1.hex].pack("U") }
20
+ end
21
+
22
+ # matches YAML-formatted dates
23
+ DATE_REGEX = /^\d{4}-\d{2}-\d{2}$|^\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?$/
24
+
25
+ # Ensure that ":" and "," are always followed by a space
26
+ def self.convert_json_to_yaml(json) #:nodoc:
27
+ scanner, quoting, marks, pos, times = StringScanner.new(json), false, [], nil, []
28
+ while scanner.scan_until(/(\\['"]|['":,\\]|\\.)/)
29
+ case char = scanner[1]
30
+ when '"', "'"
31
+ if !quoting
32
+ quoting = char
33
+ pos = scanner.pos
34
+ elsif quoting == char
35
+ if json[pos..scanner.pos-2] =~ DATE_REGEX
36
+ # found a date, track the exact positions of the quotes so we can remove them later.
37
+ # oh, and increment them for each current mark, each one is an extra padded space that bumps
38
+ # the position in the final YAML output
39
+ total_marks = marks.size
40
+ times << pos+total_marks << scanner.pos+total_marks
41
+ end
42
+ quoting = false
43
+ end
44
+ when ":",","
45
+ marks << scanner.pos - 1 unless quoting
46
+ when "\\"
47
+ scanner.skip(/\\/)
48
+ end
49
+ end
50
+
51
+ if marks.empty?
52
+ json.gsub(/\\\//, '/')
53
+ else
54
+ left_pos = [-1].push(*marks)
55
+ right_pos = marks << json.length
56
+ output = []
57
+ left_pos.each_with_index do |left, i|
58
+ output << json[left.succ..right_pos[i]]
59
+ end
60
+ output = output * " "
61
+
62
+ times.each { |i| output[i-1] = ' ' }
63
+ output.gsub!(/\\\//, '/')
64
+ output
65
+ end
66
+ end
67
+ end
68
+ end
data/lib/crack/xml.rb ADDED
@@ -0,0 +1,214 @@
1
+ require 'rexml/parsers/streamparser'
2
+ require 'rexml/parsers/baseparser'
3
+ require 'rexml/light/node'
4
+ require 'rexml/text'
5
+ require 'date'
6
+ require 'time'
7
+ require 'yaml'
8
+ require 'bigdecimal'
9
+
10
+ # This is a slighly modified version of the XMLUtilityNode from
11
+ # http://merb.devjavu.com/projects/merb/ticket/95 (has.sox@gmail.com)
12
+ # It's mainly just adding vowels, as I ht cd wth n vwls :)
13
+ # This represents the hard part of the work, all I did was change the
14
+ # underlying parser.
15
+ class REXMLUtilityNode #:nodoc:
16
+ attr_accessor :name, :attributez, :children, :type
17
+
18
+ def self.typecasts
19
+ @@typecasts
20
+ end
21
+
22
+ def self.typecasts=(obj)
23
+ @@typecasts = obj
24
+ end
25
+
26
+ def self.available_typecasts
27
+ @@available_typecasts
28
+ end
29
+
30
+ def self.available_typecasts=(obj)
31
+ @@available_typecasts = obj
32
+ end
33
+
34
+ self.typecasts = {}
35
+ self.typecasts["integer"] = lambda{|v| v.nil? ? nil : v.to_i}
36
+ self.typecasts["boolean"] = lambda{|v| v.nil? ? nil : (v.strip != "false")}
37
+ self.typecasts["datetime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
38
+ self.typecasts["date"] = lambda{|v| v.nil? ? nil : Date.parse(v)}
39
+ self.typecasts["dateTime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
40
+ self.typecasts["decimal"] = lambda{|v| v.nil? ? nil : BigDecimal(v.to_s)}
41
+ self.typecasts["double"] = lambda{|v| v.nil? ? nil : v.to_f}
42
+ self.typecasts["float"] = lambda{|v| v.nil? ? nil : v.to_f}
43
+ self.typecasts["symbol"] = lambda{|v| v.nil? ? nil : v.to_sym}
44
+ self.typecasts["string"] = lambda{|v| v.to_s}
45
+ self.typecasts["yaml"] = lambda{|v| v.nil? ? nil : YAML.load(v)}
46
+ self.typecasts["base64Binary"] = lambda{|v| v.unpack('m').first }
47
+
48
+ self.available_typecasts = self.typecasts.keys
49
+
50
+ def initialize(name, normalized_attributez = {})
51
+
52
+ # unnormalize attribute values
53
+ attributez = Hash[* normalized_attributez.map { |key, value|
54
+ [ key, unnormalize_xml_entities(value) ]
55
+ }.flatten]
56
+
57
+ @name = name.tr("-", "_")
58
+ # leave the type alone if we don't know what it is
59
+ @type = self.class.available_typecasts.include?(attributez["type"]) ? attributez.delete("type") : attributez["type"]
60
+
61
+ @nil_element = attributez.delete("nil") == "true"
62
+ @attributez = undasherize_keys(attributez)
63
+ @children = []
64
+ @text = false
65
+ end
66
+
67
+ def add_node(node)
68
+ @text = true if node.is_a? String
69
+ @children << node
70
+ end
71
+
72
+ def to_hash
73
+ if @type == "file"
74
+ f = StringIO.new((@children.first || '').unpack('m').first)
75
+ class << f
76
+ attr_accessor :original_filename, :content_type
77
+ end
78
+ f.original_filename = attributez['name'] || 'untitled'
79
+ f.content_type = attributez['content_type'] || 'application/octet-stream'
80
+ return {name => f}
81
+ end
82
+
83
+ if @text
84
+ t = typecast_value( unnormalize_xml_entities( inner_html ) )
85
+ t.class.send(:attr_accessor, :attributez)
86
+ t.attributez = attributez
87
+ return { name => t }
88
+ else
89
+ #change repeating groups into an array
90
+ groups = @children.inject({}) { |s,e| (s[e.name] ||= []) << e; s }
91
+
92
+ out = nil
93
+ if @type == "array"
94
+ out = []
95
+ groups.each do |k, v|
96
+ if v.size == 1
97
+ out << v.first.to_hash.entries.first.last
98
+ else
99
+ out << v.map{|e| e.to_hash[k]}
100
+ end
101
+ end
102
+ out = out.flatten
103
+
104
+ else # If Hash
105
+ out = {}
106
+ groups.each do |k,v|
107
+ if v.size == 1
108
+ out.merge!(v.first)
109
+ else
110
+ out.merge!( k => v.map{|e| e.to_hash[k]})
111
+ end
112
+ end
113
+ out.merge! attributez unless attributez.empty?
114
+ out = out.empty? ? nil : out
115
+ end
116
+
117
+ if @type && out.nil?
118
+ { name => typecast_value(out) }
119
+ else
120
+ { name => out }
121
+ end
122
+ end
123
+ end
124
+
125
+ # Typecasts a value based upon its type. For instance, if
126
+ # +node+ has #type == "integer",
127
+ # {{[node.typecast_value("12") #=> 12]}}
128
+ #
129
+ # @param value<String> The value that is being typecast.
130
+ #
131
+ # @details [:type options]
132
+ # "integer"::
133
+ # converts +value+ to an integer with #to_i
134
+ # "boolean"::
135
+ # checks whether +value+, after removing spaces, is the literal
136
+ # "true"
137
+ # "datetime"::
138
+ # Parses +value+ using Time.parse, and returns a UTC Time
139
+ # "date"::
140
+ # Parses +value+ using Date.parse
141
+ #
142
+ # @return <Integer, TrueClass, FalseClass, Time, Date, Object>
143
+ # The result of typecasting +value+.
144
+ #
145
+ # @note
146
+ # If +self+ does not have a "type" key, or if it's not one of the
147
+ # options specified above, the raw +value+ will be returned.
148
+ def typecast_value(value)
149
+ return value unless @type
150
+ proc = self.class.typecasts[@type]
151
+ proc.nil? ? value : proc.call(value)
152
+ end
153
+
154
+ # Take keys of the form foo-bar and convert them to foo_bar
155
+ def undasherize_keys(params)
156
+ params.keys.each do |key, value|
157
+ params[key.tr("-", "_")] = params.delete(key)
158
+ end
159
+ params
160
+ end
161
+
162
+ # Get the inner_html of the REXML node.
163
+ def inner_html
164
+ @children.join
165
+ end
166
+
167
+ # Converts the node into a readable HTML node.
168
+ #
169
+ # @return <String> The HTML node in text form.
170
+ def to_html
171
+ attributez.merge!(:type => @type ) if @type
172
+ "<#{name}#{attributez.to_xml_attributez}>#{@nil_element ? '' : inner_html}</#{name}>"
173
+ end
174
+
175
+ # @alias #to_html #to_s
176
+ def to_s
177
+ to_html
178
+ end
179
+
180
+ private
181
+
182
+ def unnormalize_xml_entities value
183
+ REXML::Text.unnormalize(value)
184
+ end
185
+ end
186
+
187
+ module Crack
188
+ class XML
189
+ def self.parse(xml)
190
+ stack = []
191
+ parser = REXML::Parsers::BaseParser.new(xml)
192
+
193
+ while true
194
+ event = parser.pull
195
+ case event[0]
196
+ when :end_document
197
+ break
198
+ when :end_doctype, :start_doctype
199
+ # do nothing
200
+ when :start_element
201
+ stack.push REXMLUtilityNode.new(event[1], event[2])
202
+ when :end_element
203
+ if stack.size > 1
204
+ temp = stack.pop
205
+ stack.last.add_node(temp)
206
+ end
207
+ when :text, :cdata
208
+ stack.last.add_node(event[1]) unless event[1].strip.length == 0 || stack.empty?
209
+ end
210
+ end
211
+ stack.length > 0 ? stack.pop.to_hash : {}
212
+ end
213
+ end
214
+ end
data/lib/crack.rb ADDED
@@ -0,0 +1,8 @@
1
+ module Crack
2
+ VERSION = "0.1.8".freeze
3
+ class ParseError < StandardError; end
4
+ end
5
+
6
+ require 'crack/core_extensions'
7
+ require 'crack/json'
8
+ require 'crack/xml'
@@ -0,0 +1,4 @@
1
+ require 'test_helper'
2
+
3
+ # class CrackTest < Test::Unit::TestCase
4
+ # end