O
Olaf Klischat
Project Homepage: http://xml-mapping.rubyforge.org/
Download, CVS etc.: http://rubyforge.org/projects/xml-mapping
xml-mapping is an easy to use, extensible library for mapping Ruby
objects to XML trees and back. It includes an XPath interpreter (see
below).
===========================
Example:
----------------------------
Input document:
----------------------------
(example document stolen + extended from
www.castor.org/xml-mapping.html)
<?xml version="1.0" encoding="ISO-8859-1"?>
<order reference="12343-AHSHE-314159">
<client>
<name>Jean Smith</name>
<address where="home">
<city>San Mateo</city>
<state>CA</state>
<zip>94403</zip>
<street>2000, Alameda de las Pulgas</street>
</address>
<address where="work">
<city>San Francisco</city>
<state>CA</state>
<zip>94102</zip>
<street>98765, Fulton street</street>
</address>
</client>
<item reference="RF-0001">
<description>Stuffed Penguin</description>
<quantity>10</quantity>
<unit-price>8.95</unit-price>
</item>
<item reference="RF-0034">
<description>Chocolate</description>
<quantity>5</quantity>
<unit-price>28.50</unit-price>
</item>
<item reference="RF-3341">
<description>Cookie</description>
<quantity>30</quantity>
<unit-price>0.85</unit-price>
</item>
<signed-by>
<signature>
<name>John Doe</name>
<position>product manager</position>
</signature>
<signature>
<name>Jill Smith</name>
<position>clerk</position>
</signature>
<signature>
<name>Miles O'Brien</name>
</signature>
</signed-by>
</order>
----------------------------
Mapping class declarations:
----------------------------
require 'xml/mapping'
## forward declarations
class Client; end
class Address; end
class Item; end
class Signature; end
class Order
include XML::Mapping
text_node :reference, "@reference"
object_node :client, "client", :class=>Client
hash_node :items, "item", "@reference", :class=>Item
array_node :signatures, "signed-by", "signature", :class=>Signature, :default_value=>[]
def total_price
items.values.map{|i| i.total_price}.inject(0){|x,y|x+y}
end
end
class Client
include XML::Mapping
text_node :name, "name"
object_node :home_address, "address[@where='home']", :class=>Address
object_node :work_address, "address[@where='work']", :class=>Address, :default_value=>nil
end
class Address
include XML::Mapping
text_node :city, "city"
text_node :state, "state"
numeric_node :zip, "zip"
text_node :street, "street"
end
class Item
include XML::Mapping
text_node :descr, "description"
numeric_node :quantity, "quantity"
numeric_node :unit_price, "unit-price"
def total_price
quantity*unit_price
end
end
class Signature
include XML::Mapping
text_node :name, "name"
text_node osition, "position", :default_value=>"Some Employee"
end
----------------------------
Usage
----------------------------
####read access
o=Order.load_from_file("order.xml")
=> #<Order:0x40461390 @signatures=[#<Signature[....] @reference="12343-AHSHE-314159">
o.reference
=> "12343-AHSHE-314159"
o.client
=> #<Client:0x404609e0 @name="Jean Smith", @work_address=#<Address:0x4045de5c @city="San Francisco", @street="98765, Fulton street", @zip=94102, @state="CA">, @home_address=#<Address:0x4045fd10 @city="San Mateo", @street="2000, Alameda de las Pulgas", @zip=94403, @state="CA">>
o.items.keys
=> ["RF-3341", "RF-0034", "RF-0001"]
o.items["RF-0034"].descr
=> "Chocolate"
o.items["RF-0034"].total_price
=> 142.5
o.signatures
=> [#<Signature:0x40456f30 @name="John Doe", @position="product manager">, #<Signature:0x40456314 @name="Jill Smith", @position="clerk">, #<Signature:0x404556f8 @name="Miles O'Brien", @position="Some Employee">]
o.signatures[2].name
=> "Miles O'Brien"
o.signatures[2].position
=> "Some Employee"
## default value was set
o.total_price
=> 257.5
####write access
o.client.name="James T. Kirk"
o.items['RF-4711'] = Item.new
o.items['RF-4711'].descr = 'power transfer grid'
o.items['RF-4711'].quantity = 2
o.items['RF-4711'].unit_price = 29.95
s=Signature.new
s.name='Harry Smith'
s.position='general manager'
o.signatures << s
xml=o.save_to_xml #convert to REXML node; there's also o.save_to_file(name)
=> <order reference='12343-AHSHE-314159'> ... </>
xml.write($stdout,2)
<order reference='12343-AHSHE-314159'>
<client>
<name>James T. Kirk</name>
<address where='home'>
<city>San Mateo</city>
<state>CA</state>
<zip>94403</zip>
<street>2000, Alameda de las Pulgas</street>
</address>
<address where='work'>
<city>San Francisco</city>
<state>CA</state>
<zip>94102</zip>
<street>98765, Fulton street</street>
</address>
</client>
<item reference='RF-3341'>
<description>Cookie</description>
<quantity>30</quantity>
<unit-price>0.85</unit-price>
</item>
<item reference='RF-0034'>
<description>Chocolate</description>
<quantity>5</quantity>
<unit-price>28.5</unit-price>
</item>
<item reference='RF-0001'>
<description>Stuffed Penguin</description>
<quantity>10</quantity>
<unit-price>8.95</unit-price>
</item>
<item reference='RF-4711'>
<description>power transfer grid</description>
<quantity>2</quantity>
<unit-price>29.95</unit-price>
</item>
<signed-by>
<signature>
<name>John Doe</name>
<position>product manager</position>
</signature>
<signature>
<name>Jill Smith</name>
<position>clerk</position>
</signature>
<signature>
<name>Miles O'Brien</name>
</signature>
<signature>
<name>Harry Smith</name>
<position>general manager</position>
</signature>
</signed-by>
</order>
####Starting a new order from scratch
o = Order.new
=> #<Order:0x4043c9f0 @signatures=[]>
## attributes with default values (here: signatures) are set
## automatically
xml=o.save_to_xml
XML::MappingError: no value, and no default value, for attribute: reference
from ../../lib/xml/../xml/mapping/base.rb:377:in `obj_to_xml'
from ../../lib/xml/../xml/mapping/base.rb:157:in `fill_into_xml'
from ../../lib/xml/../xml/mapping/base.rb:156:in `each'
from ../../lib/xml/../xml/mapping/base.rb:156:in `fill_into_xml'
from ../../lib/xml/../xml/mapping/base.rb:168:in `save_to_xml'
## can't save as long as there are still unset attributes without
## default values
o.reference = "FOOBAR-1234"
o.client = Client.new
o.client.name = 'Ford Prefect'
o.client.home_address = Address.new
o.client.home_address.street = '42 Park Av.'
o.client.home_address.city = 'small planet'
o.client.home_address.zip = 17263
o.client.home_address.state = 'Betelgeuse system'
o.items={'XY-42' => Item.new}
o.items['XY-42'].descr = 'improbability drive'
o.items['XY-42'].quantity = 3
o.items['XY-42'].unit_price = 299.95
o.save_to_xml.write($stdout,2)
<order reference='FOOBAR-1234'>
<client>
<name>Ford Prefect</name>
<address where='home'>
<city>small planet</city>
<state>Betelgeuse system</state>
<zip>17263</zip>
<street>42 Park Av.</street>
</address>
</client>
<item reference='XY-42'>
<description>improbability drive</description>
<quantity>3</quantity>
<unit-price>299.95</unit-price>
</item>
</order>
## the root element name when saving an object to XML will by default
## be derived from the class name (in this example, "Order" became
## "order"). This can be overridden on a per-class basis; see
## XML::Mapping::ClassMethods#root_element_namefor details.
=======
As shown in the example, you generally include XML::Mapping in a class
to turn it into a "mapping class", and then map instance attributes to
XML sub-trees at will. XPath expressions like "@reference",
"signed-by", or "address[@where='home']" in the mapping class
definition locate the sub-trees; an XPath interpreter is bundled with
xml-mapping for reading and writing data from/to them.
The XPath interpreter can also be used on its own; it does not depend
on the rest of the library.
Several "node types" like text_node, numeric_node, array_node etc. are
provided; it is easy to write your own ones and register them with the
xml-mapping library. There is even documentation on all this =
Olaf
Download, CVS etc.: http://rubyforge.org/projects/xml-mapping
xml-mapping is an easy to use, extensible library for mapping Ruby
objects to XML trees and back. It includes an XPath interpreter (see
below).
===========================
Example:
----------------------------
Input document:
----------------------------
(example document stolen + extended from
www.castor.org/xml-mapping.html)
<?xml version="1.0" encoding="ISO-8859-1"?>
<order reference="12343-AHSHE-314159">
<client>
<name>Jean Smith</name>
<address where="home">
<city>San Mateo</city>
<state>CA</state>
<zip>94403</zip>
<street>2000, Alameda de las Pulgas</street>
</address>
<address where="work">
<city>San Francisco</city>
<state>CA</state>
<zip>94102</zip>
<street>98765, Fulton street</street>
</address>
</client>
<item reference="RF-0001">
<description>Stuffed Penguin</description>
<quantity>10</quantity>
<unit-price>8.95</unit-price>
</item>
<item reference="RF-0034">
<description>Chocolate</description>
<quantity>5</quantity>
<unit-price>28.50</unit-price>
</item>
<item reference="RF-3341">
<description>Cookie</description>
<quantity>30</quantity>
<unit-price>0.85</unit-price>
</item>
<signed-by>
<signature>
<name>John Doe</name>
<position>product manager</position>
</signature>
<signature>
<name>Jill Smith</name>
<position>clerk</position>
</signature>
<signature>
<name>Miles O'Brien</name>
</signature>
</signed-by>
</order>
----------------------------
Mapping class declarations:
----------------------------
require 'xml/mapping'
## forward declarations
class Client; end
class Address; end
class Item; end
class Signature; end
class Order
include XML::Mapping
text_node :reference, "@reference"
object_node :client, "client", :class=>Client
hash_node :items, "item", "@reference", :class=>Item
array_node :signatures, "signed-by", "signature", :class=>Signature, :default_value=>[]
def total_price
items.values.map{|i| i.total_price}.inject(0){|x,y|x+y}
end
end
class Client
include XML::Mapping
text_node :name, "name"
object_node :home_address, "address[@where='home']", :class=>Address
object_node :work_address, "address[@where='work']", :class=>Address, :default_value=>nil
end
class Address
include XML::Mapping
text_node :city, "city"
text_node :state, "state"
numeric_node :zip, "zip"
text_node :street, "street"
end
class Item
include XML::Mapping
text_node :descr, "description"
numeric_node :quantity, "quantity"
numeric_node :unit_price, "unit-price"
def total_price
quantity*unit_price
end
end
class Signature
include XML::Mapping
text_node :name, "name"
text_node osition, "position", :default_value=>"Some Employee"
end
----------------------------
Usage
----------------------------
####read access
o=Order.load_from_file("order.xml")
=> #<Order:0x40461390 @signatures=[#<Signature[....] @reference="12343-AHSHE-314159">
o.reference
=> "12343-AHSHE-314159"
o.client
=> #<Client:0x404609e0 @name="Jean Smith", @work_address=#<Address:0x4045de5c @city="San Francisco", @street="98765, Fulton street", @zip=94102, @state="CA">, @home_address=#<Address:0x4045fd10 @city="San Mateo", @street="2000, Alameda de las Pulgas", @zip=94403, @state="CA">>
o.items.keys
=> ["RF-3341", "RF-0034", "RF-0001"]
o.items["RF-0034"].descr
=> "Chocolate"
o.items["RF-0034"].total_price
=> 142.5
o.signatures
=> [#<Signature:0x40456f30 @name="John Doe", @position="product manager">, #<Signature:0x40456314 @name="Jill Smith", @position="clerk">, #<Signature:0x404556f8 @name="Miles O'Brien", @position="Some Employee">]
o.signatures[2].name
=> "Miles O'Brien"
o.signatures[2].position
=> "Some Employee"
## default value was set
o.total_price
=> 257.5
####write access
o.client.name="James T. Kirk"
o.items['RF-4711'] = Item.new
o.items['RF-4711'].descr = 'power transfer grid'
o.items['RF-4711'].quantity = 2
o.items['RF-4711'].unit_price = 29.95
s=Signature.new
s.name='Harry Smith'
s.position='general manager'
o.signatures << s
xml=o.save_to_xml #convert to REXML node; there's also o.save_to_file(name)
=> <order reference='12343-AHSHE-314159'> ... </>
xml.write($stdout,2)
<order reference='12343-AHSHE-314159'>
<client>
<name>James T. Kirk</name>
<address where='home'>
<city>San Mateo</city>
<state>CA</state>
<zip>94403</zip>
<street>2000, Alameda de las Pulgas</street>
</address>
<address where='work'>
<city>San Francisco</city>
<state>CA</state>
<zip>94102</zip>
<street>98765, Fulton street</street>
</address>
</client>
<item reference='RF-3341'>
<description>Cookie</description>
<quantity>30</quantity>
<unit-price>0.85</unit-price>
</item>
<item reference='RF-0034'>
<description>Chocolate</description>
<quantity>5</quantity>
<unit-price>28.5</unit-price>
</item>
<item reference='RF-0001'>
<description>Stuffed Penguin</description>
<quantity>10</quantity>
<unit-price>8.95</unit-price>
</item>
<item reference='RF-4711'>
<description>power transfer grid</description>
<quantity>2</quantity>
<unit-price>29.95</unit-price>
</item>
<signed-by>
<signature>
<name>John Doe</name>
<position>product manager</position>
</signature>
<signature>
<name>Jill Smith</name>
<position>clerk</position>
</signature>
<signature>
<name>Miles O'Brien</name>
</signature>
<signature>
<name>Harry Smith</name>
<position>general manager</position>
</signature>
</signed-by>
</order>
####Starting a new order from scratch
o = Order.new
=> #<Order:0x4043c9f0 @signatures=[]>
## attributes with default values (here: signatures) are set
## automatically
xml=o.save_to_xml
XML::MappingError: no value, and no default value, for attribute: reference
from ../../lib/xml/../xml/mapping/base.rb:377:in `obj_to_xml'
from ../../lib/xml/../xml/mapping/base.rb:157:in `fill_into_xml'
from ../../lib/xml/../xml/mapping/base.rb:156:in `each'
from ../../lib/xml/../xml/mapping/base.rb:156:in `fill_into_xml'
from ../../lib/xml/../xml/mapping/base.rb:168:in `save_to_xml'
## can't save as long as there are still unset attributes without
## default values
o.reference = "FOOBAR-1234"
o.client = Client.new
o.client.name = 'Ford Prefect'
o.client.home_address = Address.new
o.client.home_address.street = '42 Park Av.'
o.client.home_address.city = 'small planet'
o.client.home_address.zip = 17263
o.client.home_address.state = 'Betelgeuse system'
o.items={'XY-42' => Item.new}
o.items['XY-42'].descr = 'improbability drive'
o.items['XY-42'].quantity = 3
o.items['XY-42'].unit_price = 299.95
o.save_to_xml.write($stdout,2)
<order reference='FOOBAR-1234'>
<client>
<name>Ford Prefect</name>
<address where='home'>
<city>small planet</city>
<state>Betelgeuse system</state>
<zip>17263</zip>
<street>42 Park Av.</street>
</address>
</client>
<item reference='XY-42'>
<description>improbability drive</description>
<quantity>3</quantity>
<unit-price>299.95</unit-price>
</item>
</order>
## the root element name when saving an object to XML will by default
## be derived from the class name (in this example, "Order" became
## "order"). This can be overridden on a per-class basis; see
## XML::Mapping::ClassMethods#root_element_namefor details.
=======
As shown in the example, you generally include XML::Mapping in a class
to turn it into a "mapping class", and then map instance attributes to
XML sub-trees at will. XPath expressions like "@reference",
"signed-by", or "address[@where='home']" in the mapping class
definition locate the sub-trees; an XPath interpreter is bundled with
xml-mapping for reading and writing data from/to them.
The XPath interpreter can also be used on its own; it does not depend
on the rest of the library.
Several "node types" like text_node, numeric_node, array_node etc. are
provided; it is easy to write your own ones and register them with the
xml-mapping library. There is even documentation on all this =
Olaf