XMLable provides an ability to convert XML to Ruby object and back.
Add this line to your application's Gemfile:
gem 'xmlable'
# Describe XML structure
class Catalog
include XMLable::Document
root :catalog do
attribute :date, Date
element :size, Integer
elements :items, tag: 'item' do
attribute :position, Integer
element :title
element :desc, tag: 'description'
element :amount, Integer
end
end
end
xml = <<-XML
<?xml version="1.0"?>
<catalog date="2014-12-20">
<size>10</size>
<item position="1">
<title>Sumac Shoes</title>
<description>Capture the beauty</description>
<amount>3</amount>
</item>
<item position="2">
<title>Channing Oxford Shoes</title>
<description>Hand finished with a wash of color</description>
</item>
</catalog>
XML
doc = Catalog.from_xml(xml)
doc.catalog.date # => #<Date: 2014-12-20 ((2457012j,0s,0n),+0s,2299161j)>
doc.catalog.size # => 10
doc.catalog.items.size # => 2
doc.catalog.items[0].title # => "Sumac Shoes"
doc.catalog.items[0].desc # => "Capture the beauty"
doc.catalog.items[0].amount # => 3
doc.catalog.items[0].position # => 1
doc.to_json
#{
# "catalog": {
# "date": "2014-12-20",
# "size": 10,
# "items": [
# { "title": "Sumac Shoes", "desc": "Capture the beauty", "amount": 3, "position": 1 },
# { "title": "Channing Oxford Shoes", "desc": "Hand finished with a wash of color", "position": 2 }
# ]
# }
#}
json = {
catalog: {
date: "2014-12-20",
size: 10,
items: [
{ title: "Sumac Shoes", desc: "Capture the beauty", amount: 3, position: 1 },
{ title: "Channing Oxford Shoes", desc: "Hand finished with a wash of color", position: 2 }
]
}
}
# Initialize with JSON
doc = Catalog.new(json)
doc.catalog.date # => #<Date: 2014-12-20 ((2457012j,0s,0n),+0s,2299161j)>
doc.catalog.size # => 10
doc.catalog.items.size # => 2
doc.catalog.items[0].title # => "Sumac Shoes"
doc.catalog.items[0].desc # => "Capture the beauty"
doc.catalog.items[0].amount # => 3
doc.catalog.items[0].position # => 1
# By default all elements and attribute values are proxied. If you for some
# reason need to get real value just use the exclamation mark.
doc.catalog.date! # => #<Date: 2014-12-20 ((2457012j,0s,0n),+0s,2299161j)>
doc.to_xml
#<?xml version="1.0"?>
#<catalog date="2014-12-20">
# <size>10</size>
# <item position="1">
# <title>Sumac Shoes</title>
# <description>Capture the beauty</description>
# <amount>3</amount>
# </item>
# <item position="2">
# <title>Channing Oxford Shoes</title>
# <description>Hand finished with a wash of color</description>
# </item>
#</catalog>
xml = <<-XML
<?xml version="1.0"?>
<d:student xmlns="http://example.com" xmlns:d="http://foo.com/student" d:id="11">
<d:name d:position="2">Jeff Smith</d:name>
<info>
<city number="7">Beijing</city>
</info>
</d:student>
XML
class Student
include XMLable::Document
root :student, namespace: 'd' do
namespace nil, 'http://example.com'
# the last defined namespace will be set as default for the all following
# and nested elements and attributes. If you need to overwrite namespace
# just pass namespace: 'prefix' (see the root definition above).
namespace :d, 'http://foo.com/student'
attribute :id, :int
element :name do
attribute :position, :int
# You can describe content method of element which has also attributes
# besides the content. By default content if present will be exported into JSON with
# '__content' key. You can pase 'false' instead of name to ignore it.
content :fullname
end
# If namespace setting is passed it will be set as default for the all
# nested element and attributes.
element :info, namespace: nil do
element :city do
attribute :number, Integer
content :name
end
end
end
end
student = Student.from_xml(xml)
student.to_json
#{
# "student": {
# "id": 11,
# "name": {
# "fullname": "Jeff Smith",
# "position": 2
# },
# "info": {
# "city": {
# "name": "Beijing",
# "number": 7
# }
# }
# }
#}
# Back direction
other = Student.new(student.to_h)
other.to_xml
#<?xml version="1.0"?>
#<d:student xmlns="http://example.com" xmlns:d="http://foo.com/student" d:id="11">
# <d:name d:position="2">Jeff Smith</d:name>
# <info>
# <city number="7">Beijing</city>
# </info>
#</d:student>
It's also possible to load undescribed XML. In this case each XML element(not attribute) will be represented as an array, so you have to use indexes to access element values.
xml = <<-XML
<?xml version="1.0"?>
<item id="11">
<name position="2">Bolt</name>
<info>
<color>Silver</color>
</info>
</item>
XML
class Item
include XMLable::Document
end
item = Item.from_xml(xml)
item.root.id # => "11"
item.root.name[0].position # => "2"
item.root.name[0] # => "Bolt"
item.root.info[0].color[0] # => "Silver"
item.to_h
#{
# "item": {
# "id": "11"
# "name": [{"position": 2", "__content": "Bolt"}]
# "info": [{ "color": ["Silver"]}]
# }
#}
item.to_xml
#<?xml version="1.0"?>
#<item id="11">
# <name position="2">Bolt</name>
# <info>
# <color>Silver</color>
# </info>
#</item>
Bug reports and pull requests are welcome on GitHub at https://github.com/zoer/xmlable.
The gem is available as open source under the terms of the MIT License.