Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Adds an xml encoder / decoder

  • Loading branch information...
commit 94e6de051d1f6a434989a4ea0ea8f217ae4cac7d 1 parent a98ef66
@hassox hassox authored
View
20 merb-rest-formats/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008 YOUR NAME
+
+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:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+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.
View
4 merb-rest-formats/README
@@ -0,0 +1,4 @@
+merb_dm_rest_formats
+====================
+
+A plugin for the Merb framework that provides ...
View
67 merb-rest-formats/Rakefile
@@ -0,0 +1,67 @@
+require 'rubygems'
+require 'rake/gempackagetask'
+require 'rubygems/specification'
+require 'spec'
+require 'spec/rake/spectask'
+require 'date'
+
+NAME = "merb_dm_rest_formats"
+GEM_VERSION = "0.0.1"
+AUTHOR = "Your Name"
+EMAIL = "Your Email"
+HOMEPAGE = "http://merbivore.com/"
+SUMMARY = "Merb plugin that provides ..."
+
+spec = Gem::Specification.new do |s|
+ s.rubyforge_project = ''
+ s.name = NAME
+ s.version = GEM_VERSION
+ s.platform = Gem::Platform::RUBY
+ s.has_rdoc = true
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
+ s.summary = SUMMARY
+ s.description = s.summary
+ s.author = AUTHOR
+ s.email = EMAIL
+ s.homepage = HOMEPAGE
+ s.require_path = 'lib'
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
+
+end
+
+Rake::GemPackageTask.new(spec) do |pkg|
+ pkg.gem_spec = spec
+end
+
+desc "install the plugin locally"
+task :install => [:package] do
+ sh %{#{sudo} gem install #{install_home} pkg/#{NAME}-#{GEM_VERSION} --no-update-sources}
+end
+
+desc "create a gemspec file"
+task :make_spec do
+ File.open("#{NAME}.gemspec", "w") do |file|
+ file.puts spec.to_ruby
+ end
+end
+
+
+desc "Run specs, run a specific spec with TASK=spec/path_to_spec.rb"
+task :spec => [ "spec:default" ]
+namespace :spec do
+ OPTS_FILENAME = "./spec/spec.opts"
+ if File.exist?(OPTS_FILENAME)
+ SPEC_OPTS = ["--options", OPTS_FILENAME]
+ else
+ SPEC_OPTS = ["--color", "--format", "specdoc"]
+ end
+
+ Spec::Rake::SpecTask.new('default') do |t|
+ t.spec_opts = SPEC_OPTS
+ if(ENV['TASK'])
+ t.spec_files = [ENV['TASK']]
+ else
+ t.spec_files = Dir['spec/**/*_spec.rb'].sort
+ end
+ end
+end
View
5 merb-rest-formats/TODO
@@ -0,0 +1,5 @@
+TODO:
+Fix LICENSE with your name
+Fix Rakefile with your name and contact info
+Add your code to lib/merb_dm_rest_formats.rb
+Add your Merb rake tasks to lib/merb_dm_rest_formats/merbtasks.rb
View
218 merb-rest-formats/lib/formats/xml.rb
@@ -0,0 +1,218 @@
+require 'rexml/document'
+require 'rexml/element'
+
+module Merb
+ module Rest
+ module Formats
+ module Xml
+
+ def self.encode(hash)
+ Encode.encode(hash)
+ end
+
+ def self.decode(xml_string)
+ Decode.decode(xml_string)
+ end
+
+ module Encode
+ class << self
+ include REXML
+
+ def encode(args)
+ raise ArgumentError, "Must provide a hash to encode to XML" unless args.kind_of?(Hash) or args.kind_of?(Struct)
+ el = Element.new('args')
+ args.each do |k,v|
+ el << encode_pair(k,v)
+ end
+ el.root.to_s
+ end
+
+ def encode_pair(name, value)
+
+ case value
+ when Fixnum
+ el = Element.new("i4")
+ el.attributes['name'] = name.to_s unless name.nil?
+ el.text = value
+ el
+
+ when Bignum
+ if value >= -(2**31) and value <= (2**31-1)
+ el = Element.new("i4")
+ el.attributes['name'] = name.to_s unless name.nil?
+ el.text = value
+ el
+ else
+ raise "Bignum is too big! Must be signed 32-bit integer!"
+ end
+
+ when Array
+ el = Element.new("list")
+ el.attributes['name'] = name.to_s unless name.nil?
+ value.each{|v| el << encode_pair(nil, v)}
+ el
+
+ when TrueClass, FalseClass
+ el = Element.new("boolean")
+ el.attributes['name'] = name.to_s unless name.nil?
+ el.text = value ? 1 : 0
+ el
+
+ when String, Symbol
+ el = Element.new("string")
+ el.attributes['name'] = name.to_s unless name.nil?
+ el.text = value
+ el
+
+ when Float
+ el = Element.new("double")
+ el.attributes['name'] = name.to_s unless name.nil?
+ el.text = value
+ el
+
+ when Struct
+ el = Element.new("struct")
+ el.attributes['name'] = name.to_s unless name.nil?
+ value.members.collect do |key|
+ val = value[key]
+ el << encode_pair(key, val)
+ end
+ el
+
+ when Hash
+ el = Element.new("struct")
+ el.attributes['name'] = name.to_s unless name.nil?
+ value.collect do |key, val|
+ el << encode_pair(key, val)
+ end
+ el
+
+ when Time, Date, ::DateTime
+ el = Element.new("dateTime.iso8601")
+ el.attributes['name'] = name.to_s unless name.nil?
+ el.text = value.strftime("%Y%m%dT%H:%M:%S%Z")
+ el
+
+ when DateTime
+ el = Element.new("dateTime.iso8601")
+ el.attributes['name'] = name.to_s unless name.nil?
+ el.text = format("%.4d%02d%02dT%02d:%02d:%02d", *value.to_a)
+ el
+
+ when NilClass
+ el = Element.new("nil")
+ el.attributes['name'] = name.to_s unless name.nil?
+ el
+
+ when Base64
+ el = Element.new("base64")
+ el.attributes['name'] = name.to_s unless name.nil?
+ el.text = value.encoded
+ el
+ else
+ raise "Param Not encodable! #{value}"
+
+ end
+ end
+ end # class << self
+ end # Encode
+
+ module Decode
+ class Node
+ attr_accessor :name, :attributes, :children, :type
+ cattr_accessor :typecasts, :available_typecasts
+
+ def initialize(type, attributes = {})
+ @children = []
+ @attributes = attributes
+ @type = type
+ @name = attributes["name"]
+ end
+
+ def add_node(node)
+ @text = true if node.is_a? String
+ @children << node
+ end
+
+ def munge
+ result = case type
+ when "args"
+ out = {}
+ if children.size == 1
+ out[:args] = children.first.munge
+ end
+ return out
+ when "list"
+ children.map{|c| c.munge}
+ when "struct"
+ result = {}
+ children.each do |n|
+ raise "children of struct must have a name attribute" unless n.name
+ result.merge!(n.munge)
+ end
+ result
+ else
+ typecast_value(self)
+ end
+
+ if @name
+ {@name.to_sym => result}
+ else
+ result
+ end
+ end
+
+ def typecast_value(value)
+ return value unless @type
+ proc = self.class.typecasts[@type]
+ proc.call(value)
+ end
+
+ end
+
+ Node.typecasts = {}
+ Node.typecasts["nil"] = lambda{|n| nil}
+ Node.typecasts["boolean"] = lambda{|n| n.children.first == "1"}
+ Node.typecasts["string"] = lambda{|n| n.children.join}
+ Node.typecasts["i4"] = lambda{|n| n.children.first.to_i}
+ Node.typecasts["double"] = lambda{|n| n.children.first.to_f}
+ Node.typecasts["dateTime.iso8601"] = lambda do |n|
+ str = n.children.first
+ begin
+ DateTime.parse(str)
+ rescue e => ArgumentError
+ raise "wronge dateTime.iso8601 format " + str
+ end
+ end
+
+
+ def self.decode(xml)
+ stack = []
+ parser = REXML::Parsers::BaseParser.new(xml)
+ while true
+ event = parser.pull
+ case event[0]
+ when :end_document
+ break
+ when :end_doctype, :start_doctype
+ # do nothing
+ when :start_element
+ stack.push Node.new(event[1], event[2])
+ when :end_element
+ if stack.size > 1
+ temp = stack.pop
+ stack.last.add_node(temp)
+ end
+ when :text, :cdata
+ stack.last.add_node(event[1]) unless event[1].strip.length == 0
+ end
+ end
+ stack.pop.munge
+ end # self.decode
+ end # Decode
+
+
+ end # XML
+ end #Formats
+ end # DmRest
+end # Merb
View
4 merb-rest-formats/lib/merb-rest-formats.rb
@@ -0,0 +1,4 @@
+
+path = File.dirname(__FILE__)
+
+require File.join( path, "formats", "xml")
View
6 merb-rest-formats/lib/merb-rest-formats/merbtasks.rb
@@ -0,0 +1,6 @@
+namespace :"merb-rest-formats" do
+ desc "Do something for merb-rest-formats"
+ task :default do
+ puts "merb-rest-formats doesn't do anything"
+ end
+end
View
407 merb-rest-formats/spec/formats/xml_spec.rb
@@ -0,0 +1,407 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+
+describe "Merb::DmRest::Fomats::Xml format marshalling" do
+
+ before(:all) do
+ Formats = Merb::Rest::Formats unless defined?(Formats)
+ end
+
+ describe "encoding" do
+
+ it{Formats::Xml.should respond_to(:encode)}
+
+ it "should not raise an error if it does receive a hash" do
+ lambda do
+ Formats::Xml.encode(:name => "String")
+ end.should_not raise_error(ArgumentError)
+ end
+
+ it "should encode a number" do
+ result = Formats::Xml.encode(:age => 23)
+ result.should have_tag(:i4, :name => 'age')
+ result.should contain("23")
+ end
+
+ it "should encode an Array of strings" do
+ result = Formats::Xml.encode(:my_array => ["A", "B", "C"])
+ result.should have_tag(:list, :name => "my_array") do |list|
+ list.to_s.should have_tag(:string)
+ end
+ end
+
+ it "should encode a string value" do
+ result = Formats::Xml.encode(:name => "String")
+ result.should have_tag(:string, :name => "name")
+ result.should contain("String")
+ end
+
+ it "should encode a symbol with a name" do
+ result = Formats::Xml.encode(:name => :symbol)
+ result.should have_tag(:string, :name => "name")
+ result.should contain("symbol")
+ end
+
+ it "should encode a symbol with a name" do
+ result = Formats::Xml.encode(:list => [:symbol])
+ result.should have_tag(:string)
+ result.should contain("symbol")
+ end
+
+ it "should encode a boolean true without a name" do
+ result = Formats::Xml.encode(:list => [true])
+ result.should have_tag(:boolean)
+ result.should contain("1")
+ end
+
+ it "shoudl encode a blooean true with a name" do
+ result = Formats::Xml.encode(:true => true)
+ result.should have_tag(:boolean, :name => "true")
+ result.should contain("1")
+ end
+
+ it "should encode a boolean false without a name" do
+ result = Formats::Xml.encode(:list => [false])
+ result.should have_tag(:boolean)
+ result.should contain("0")
+ end
+
+ it "should encode a boolean false with a name" do
+ result = Formats::Xml.encode(:false => false)
+ result.should have_tag(:boolean)
+ result.should contain("0")
+ end
+
+ it "should encode a float with a name" do
+ result = Formats::Xml.encode(:a_double => 2.3)
+ result.should have_tag(:double, :name => "a_double")
+ result.should contain("2.3")
+ end
+
+ it "should encode a float without a name" do
+ result = Formats::Xml.encode(:list => [2.3])
+ result.should have_tag(:double)
+ result.should contain("2.3")
+ end
+
+ it "should encode a struct with a name" do
+ struct = Struct.new(:name, :age)
+ result = Formats::Xml.encode(:a_struct => struct.new("phil", 24))
+ result.should have_tag(:struct, :name => "a_struct") do |the_struct|
+ the_struct = the_struct.to_s
+ the_struct.should have_tag(:string, :name => "name")
+ the_struct.should have_tag(:i4, :name => "age")
+ end
+ end
+
+ it "should encode a struct without a name" do
+ struct = Struct.new(:name, :age)
+ result = Formats::Xml.encode(:list => [struct.new("fred", 21)])
+ result.should have_tag(:struct) do |the_struct|
+ the_struct = the_struct.to_s
+ the_struct.should have_tag(:string, :name => "name")
+ the_struct.should have_tag(:i4, :name => "age")
+ end
+ end
+
+ it "should encode a hash without a name" do
+ result = Formats::Xml.encode(:list => [{:name => "fred", :age => 21}])
+ result.should have_tag(:struct) do |the_struct|
+ the_struct = the_struct.to_s
+ the_struct.should have_tag(:string, :name => "name")
+ the_struct.should have_tag(:i4, :name => "age")
+ end
+ end
+
+ it "should encode a hash with a name" do
+ result = Formats::Xml.encode(:hash => {:name => :value})
+ result.should have_tag(:struct, :name => "hash") do |ts|
+ ts = ts.to_s
+ ts.should have_tag(:string, :name => "name") do |string|
+ string.to_s.should contain("value")
+ end
+ end
+ end
+
+ it "should encode a Time without a name" do
+ time = Time.now
+ time_str = time.strftime("%Y%m%dT%H:%M:%S%Z")
+ result = Formats::Xml.encode(:list => [time])
+ result.should match(%r[<dateTime\.iso8601>#{time_str}</dateTime\.iso8601>])
+ end
+
+ it "should encode a Time with a name" do
+ time = Time.now
+ time_str = time.strftime("%Y%m%dT%H:%M:%S%Z")
+ result = Formats::Xml.encode(:time => time)
+ result.should include("<dateTime.iso8601 name='time'>#{time_str}</dateTime.iso8601>")
+ end
+
+ it "should encode a Date with a name" do
+ date = Date.today
+ date_str = date.strftime("%Y%m%dT%H:%M:%S%Z")
+ result = Formats::Xml.encode(:date => date)
+ result.should include("<dateTime.iso8601 name='date'>#{date_str}</dateTime.iso8601>")
+ end
+
+ it "should encode a Date witout a name" do
+ date = Date.today
+ date_str = date.strftime("%Y%m%dT%H:%M:%S%Z")
+ result = Formats::Xml.encode(:list => [date])
+ result.should include("<dateTime.iso8601>#{date_str}</dateTime.iso8601>")
+ end
+
+ it "should encode a DateTime without a name" do
+ datetime = DateTime.now
+ date_str = datetime.strftime("%Y%m%dT%H:%M:%S%Z")
+ result = Formats::Xml.encode(:list => [datetime])
+ result.should include("<dateTime.iso8601>#{date_str}</dateTime.iso8601>")
+ end
+
+ it "should encode a DateTime with a name" do
+ datetime = DateTime.now
+ date_str = datetime.strftime("%Y%m%dT%H:%M:%S%Z")
+ result = Formats::Xml.encode(:datetime => datetime)
+ result.should include("<dateTime.iso8601 name='datetime'>#{date_str}</dateTime.iso8601>")
+ end
+
+ it "should encode a nil with a name" do
+ result = Formats::Xml.encode(:the_nil => nil)
+ result.should include("<nil name='the_nil'/>")
+ end
+
+ it "should encode a nil without a name" do
+ result = Formats::Xml.encode(:list => [nil])
+ result.should include("<nil/>")
+ end
+ end
+
+ describe "decoding" do
+ it{Formats::Xml.should respond_to(:decode)}
+
+ it "should decode a string" do
+ string = "<args><string>Fred</string></args>"
+ Formats::Xml.decode(string).should == {:args => "Fred"}
+ end
+
+ it "should decode a string with a name" do
+ string = "<args><string name='name'>Fred</string></args>"
+ Formats::Xml.decode(string).should == {:args => {:name => "Fred"}}
+ end
+
+ it "should decode an integer" do
+ string = "<args><i4>42</i4></args>"
+ Formats::Xml.decode(string).should == {:args => 42}
+ end
+
+ it "should decode an integer with a name" do
+ string = "<args><i4 name='answer'>42</i4></args>"
+ Formats::Xml.decode(string).should == {:args => {:answer => 42}}
+ end
+
+ it "should decode a float" do
+ string = "<args><double>4.2</double></args>"
+ Formats::Xml.decode(string).should == {:args => 4.2}
+ end
+
+ it "should decode a float with a name" do
+ string = "<args><double name='wrong_answer'>4.2</double></args>"
+ Formats::Xml.decode(string).should == {:args => {:wrong_answer => 4.2}}
+ end
+
+ it "should decode a DateTime" do
+ date_time = DateTime.now
+ ds = date_time.strftime("%Y%m%dT%H:%M:%S%Z")
+ string = "<args><dateTime.iso8601>#{ds}</dateTime.iso8601></args>"
+ result = Formats::Xml.decode(string)
+ result[:args].to_s.should == date_time.to_s
+ end
+
+ it "should decode a DateTime with a name" do
+ date_time = DateTime.now
+ ds = date_time.strftime("%Y%m%dT%H:%M:%S%Z")
+ string = "<args><dateTime.iso8601 name='dob'>#{ds}</dateTime.iso8601></args>"
+ result = Formats::Xml.decode(string)
+ result[:args][:dob].to_s.should == date_time.to_s
+ end
+
+ it "should decode an Array of Strings" do
+ string = "<args><list><string>a</string><string>b</string><string>c</string></list></args>"
+ result = Formats::Xml.decode(string)
+ result[:args].should == %w(a b c)
+ end
+
+ it "should decode an Array of integers" do
+ string =<<-XML
+ <args>
+ <list>
+ <i4>1</i4>
+ <i4>2</i4>
+ <i4>3</i4>
+ </list>
+ </args>
+ XML
+ result = Formats::Xml.decode(string)
+ result[:args].should == [1,2,3]
+ end
+
+ it "should decode a mixed array" do
+ string =<<-XML
+ <args>
+ <list>
+ <i4>1</i4>
+ <string>Fred</string>
+ <boolean>1</boolean>
+ <nil/>
+ </list>
+ </args>
+ XML
+ result = Formats::Xml.decode(string)
+ result[:args].should == [1,"Fred",true,nil]
+ end
+
+ it "should decode a named list" do
+ string =<<-XML
+ <args>
+ <list name='my_list'>
+ <i4>1</i4>
+ <i4>2</i4>
+ </list>
+ </args>
+ XML
+ result = Formats::Xml.decode(string)
+ result[:args][:my_list].should == [1,2]
+ end
+
+ it "should decode a nested array" do
+ string =<<-XML
+ <args>
+ <list>
+ <i4>1</i4>
+ <list>
+ <string>nested</string>
+ </list>
+ </list>
+ </args>
+ XML
+ result = Formats::Xml.decode(string)
+ result[:args].should == [1,["nested"]]
+ end
+
+ it "should decode a struct to a hash" do
+ string =<<-XML
+ <args>
+ <struct>
+ <string name='name'>Fred</string>
+ <list name='my_list'>
+ <i4>1</i4>
+ <string>five</string>
+ </list>
+ </struct>
+ </args>
+ XML
+ result = Formats::Xml.decode(string)
+ result[:args].should == {:name => "Fred", :my_list => [1,"five"]}
+ end
+
+ it "should raise an error for any children of a struct that do not have a name" do
+ string =<<-XML
+ <args>
+ <struct>
+ <string name='name'>Fred</string>
+ <list>
+ <i4>1</i4>
+ <string>five</string>
+ </list>
+ </struct>
+ </args>
+ XML
+ lambda do
+ result = Formats::Xml.decode(string)
+ end.should raise_error(Exception, "children of struct must have a name attribute")
+ end
+
+ it "should deal with a collection of structs" do
+ string =<<-XML
+ <args>
+ <list>
+ <struct>
+ <string name="name">Fred</string>
+ <i4 name="age">23</i4>
+ </struct>
+ <struct>
+ <string name="name">Graeme</string>
+ <i4 name="age">45</i4>
+ </struct>
+ <struct>
+ <string name="name">Wilma</string>
+ <i4 name="age">21</i4>
+ </struct>
+ </list>
+ </args>
+ XML
+ result = Formats::Xml.decode(string)
+ result.should == {:args => [
+ {:name => "Fred", :age => 23},
+ {:name => "Graeme", :age => 45},
+ {:name => "Wilma", :age => 21}
+ ]}
+ end
+
+ it "should deal with a collection of structs in a named list" do
+ string =<<-XML
+ <args>
+ <list name="users">
+ <struct>
+ <string name="name">Fred</string>
+ <i4 name="age">23</i4>
+ </struct>
+ <struct>
+ <string name="name">Graeme</string>
+ <i4 name="age">45</i4>
+ </struct>
+ <struct>
+ <string name="name">Wilma</string>
+ <i4 name="age">21</i4>
+ </struct>
+ </list>
+ </args>
+ XML
+ result = Formats::Xml.decode(string)
+ result.should == {:args => {:users => [
+ {:name => "Fred", :age => 23},
+ {:name => "Graeme", :age => 45},
+ {:name => "Wilma", :age => 21}
+ ]}}
+ end
+
+ end
+
+ describe "integration" do
+
+ def reversible?(params)
+ Formats::Xml.decode(Formats::Xml.encode(params))[:args].should == params
+ end
+
+ it "should be reversible " do
+ reversible? :name => "fred"
+ end
+
+ it "shoudl go back and forth with a list" do
+ reversible? :users => ["fred", "barney", "wilma", "betty"]
+ end
+
+ it "should go back and forth with a hash" do
+ reversible? :a_hash => {:numbers => 1}
+ end
+
+ it "shodul go back and forth with a nested list" do
+ reversible? :a_list => [1,2,3,"Fred", {:stuff => ["in", "a", "hash"]}]
+ end
+
+ it "should go back and forth with a nested hash" do
+ reversible? :a_hash => {:stuff => [1,2,3,{:complex => "thing"}, 2.3]}
+ end
+
+ end
+
+end
View
7 merb-rest-formats/spec/merb-rest-formats_spec.rb
@@ -0,0 +1,7 @@
+require File.dirname(__FILE__) + '/spec_helper'
+
+describe "merb-rest-formats" do
+ it "should do nothing" do
+ true.should == true
+ end
+end
View
3  merb-rest-formats/spec/spec.opts
@@ -0,0 +1,3 @@
+--colour
+--loadby random
+--format progress
View
23 merb-rest-formats/spec/spec_helper.rb
@@ -0,0 +1,23 @@
+$TESTING=true
+
+require 'rubygems'
+require 'spec'
+require 'hpricot'
+require 'merb-core'
+
+module Merb
+ module Test
+ module Rspec
+ end
+ end
+end
+
+require 'merb-core'
+require 'merb-core/test/matchers/view_matchers'
+
+
+require File.join(File.dirname(__FILE__), "..", "lib", "merb-rest-formats")
+
+Spec::Runner.configure do |config|
+ config.include(Merb::Test::Rspec::ViewMatchers)
+end
Please sign in to comment.
Something went wrong with that request. Please try again.