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 :position, "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&apos;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