Skip to content

Commit

Permalink
more documentation; readers/writers: default implementation callable
Browse files Browse the repository at this point in the history
  • Loading branch information
multi-io committed Dec 28, 2005
1 parent 466a236 commit 7feb364
Show file tree
Hide file tree
Showing 8 changed files with 446 additions and 24 deletions.
128 changes: 128 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
2005-12-07 Olaf Klischat

* release 0.8.1

2005-12-07 Olaf Klischat

* ChangeLog file

2005/11/30 Olaf Klischat

* bugfix: clone default values to avoid external modifications

2005/07/07 Olaf Klischat

* release 0.8

2005/07/04 Olaf Klischat

* xml/xpath / XML::XPath -> xml/xxpath / XML::XXPath, license ->
Ruby's

2005/06/29 Olaf Klischat

* when creating elt[@attr='value'] path elements, add a new
element if one with @attr='value' already existed

2005/03/30 Olaf Klischat

* add_accessor: check for existing accessors.

2005/03/05 Olaf Klischat

* better support for inheritance among mapping
classes

2005/03/03 Olaf Klischat

* "polymorphic" nodes via root element
name. SubObjectBaseNode-based nodes es use node polymorphy when
no explicit node marshaller/unmarshaller has been sp ecified.

2005/02/28 Olaf Klischat

* mapping root elt name => mapping class;
XML::Mapping::load_object_from_* implemented

2005/02/13 Olaf Klischat

* IntNode renamed & generalized to NumericNode

2005/02/12 Olaf Klischat

* renaming *_rexml => *_xml

2005/01/27 Olaf Klischat

* special exception NoAttrValueSet for indicating absence of a
specific attribute in an XML source

2005/01/23 Olaf Klischat

* some more documentation, Node.obj_initializing, setting node
values to defaults on initialization

2005/01/10 Olaf Klischat

* root_element_name

2005/01/07 Olaf Klischat

* refactoring:

Made node types (classes) dynamically addable via
XML::Mapping.add_node_class, xml/mapping.rb moved to
xml/mapping/base.rb, node types moved to
xml/mapping/standard_nodes.rb, xml/mapping.rb now requires base
and standard_nodes and adds all standard node types to
XML::Mapping.

* additional node class SingleAttributeNode < Node for nodes that
map to a single attribute in their class (that's true for all
nodes we have so far). Call to add_attribute moved from "core"
to SingleAttributeNode.initialize.

* XML::Mapping: @nodes renamed to @xml_mapping_nodes to minimize
chance of name clashes.

2004/12/30 Olaf Klischat

* array node writing, hash node writing


2004/12/30 Olaf Klischat

* xpath: create_new flag, + convenience method

2004/12/21 Olaf Klischat

* node classes

2004/12/20 Olaf Klischat

* hash_node

2004/12/08 Olaf Klischat

* xpath: attribute nodes

* xml_mapping: retargeted from REXML::XPath to XML::XPath

2004/12/02 Olaf Klischat

* xpath: write accessors

2004/11/27 Olaf Klischat

* xpath: read access seems to work

2004/11/25 Olaf Klischat

* array_node

stone age Olaf Klischat

* see http://rubygarden.org/ruby?XmlMapping



138 changes: 122 additions & 16 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -489,15 +489,16 @@ The rest of the arguments specify the mentioned sequence of XPath
expressions and corresponding nodes.

When reading a +Publication+ object from XML, the XPath expressions
from the choice_node will be matched in sequence against the source
XML tree until a match is found or the end of the argument list is
reached. If the end is reached, an exception is raised. Otherwise, for
the first XPath expression that matched, the corresponding node will
be invoked (i.e. used to read actual data from the XML source into the
+Person+ object). If you specify :else, :default, or :otherwise in
place of an XPath expression, this is treated as an XPath expression
that always matches. So you can use :else (or :default or :otherwise)
for a "fallback" node that will be used if none of the other XPath
from the +choice_node+ (<tt>@author</tt> and +contr+) will be matched
in sequence against the source XML tree until a match is found or the
end of the argument list is reached. If the end is reached, an
exception is raised. Otherwise, for the first XPath expression that
matched, the corresponding node will be invoked (i.e. used to read
actual data from the XML source into the +Person+ object). If you
specify :else, :default, or :otherwise in place of an XPath
expression, this is treated as an XPath expression that always
matches. So you can use :else (or :default or :otherwise) for a
"fallback" node that will be used if none of the other XPath
expressions matched (an example for this follows).

When writing a +Publication+ object back to XML, the first node in the
Expand Down Expand Up @@ -531,17 +532,122 @@ in the choice_node.
=== {Readers/Writers}[a:readerswriters]

Finally, _all_ nodes support keyword arguments :reader and :writer
which allow you to completely override the reading and/or writing
functionality of the node with your own code. The :reader as well as
the :writer argument must be a proc that takes two arguments: the Ruby
object to be read/written (instance of the mapping class the node
belongs to) and the XML tree to be written to/read from. The :reader
proc is for reading (from the XML into the object), the :writer proc
is for writing (from the object into the XML).
which allow you to extend or completely override the reading and/or
writing functionality of the node with your own code. The :reader as
well as the :writer argument must be a proc that takes as its
arguments the Ruby object to be read/written (instance of the mapping
class the node belongs to) and the XML tree to be written to/read
from. An optional third argument may be specified -- it will receive a
proc that wraps the default reader/writer functionality of the
node.

The :reader proc is for reading (from the XML into the object), the
:writer proc is for writing (from the object into the XML).

Here's a (really contrived) example:

:include: reader.intout

So there's a "Foo" class with a text_node that would by default
(without the :reader and :writer proc) map the Ruby attribute "name"
to the XML attribute "name". The :reader proc is invoked when reading
from XML into a +Foo+ object. The +xml+ argument is the XML tree,
+obj+ is the object. +default_reader+ is the proc that wraps the
default reading functionality of the node. We invoke it at the
beginning. For this text_node, the default reading functionality is to
take the text of the "name" attribute of +xml+ and put it into the
+name+ attribute of +obj+. After that, we take the text of the "more"
attribute of +xml+ and append it to the +name+ attribute of +obj+. So
an XML tree <tt><foo name="Jim" more="XYZ"/></tt> is converted to a
+Foo+ object with +name+="JimXYZ".

In our :writer proc, we only take +obj+ (the +Foo+ object to be
written to XML) and +xml+ (the XML tree the stuff is to be written
to). Analogously to the :reader, we could take a proc that wraps the
default writing functionality of the node, but we don't need that here
-- we competely override the writing functionality with our own code,
which just takes the +name+ attribute of the object and writes "hi
<the name> ho" to a +bar+ XML attribute in the XML tree (stupid
example, I know).

As a special convention, if you specify both a :reader and a :writer
for a node, and in both cases you do /not/ call the default behaviour,
then you should use the generic node type +node+, e.g.:

class SomeClass
include XML::Mapping

....

node :reader=>proc{|obj,xml| ...},
:writer=>proc{|obj,xml| ...}
end

(since you're completely replacing both the reading and the writing
functionality, you're effectively replacing all the functionality of
the node, so it would be pointless and confusing to use one of the
more "specific" node types)

As you see, the purpose of readers and writers is to make it possible
to augment or override a node's functionality arbitrarily, so there
shouldn't be anything that's absolutely impossible to achieve with
xml-mapping. However, if you use readers and writers without invoking
the default behaviour, you really do everything manually, so you're
not doing any less work than you would do if you weren't using
xml-mapping at all. So you'll probably use readers and/or writers for
those bits of your mapping semantics that can't be achieved with
xml-mapping's predefined node types (an alternative approach might be
to override the +post_load+ and/or +post_save+ instance methods on the
mapping class -- see the reference documentation).

An advice similar to the one given above for marshallers/unmarshallers
applies here as well: If you find yourself writing lots of readers and
writers that only differ in some easily parameterizable aspects, you
should think about defining your own node types. We talk about that
below[aref:definingnodes], and it generally just involves moving the
(sensibly parameterized) code from your readers/writers to your node
types.


== {Multiple Mappings per Class}[a:mappings]

Sometimes you might want to represent the same Ruby object in multiple
alternative ways in XML. For example, the name of a "Person" object
could be represented either in a "name" element or a "name" attribute.

xml-mapping supports this by allowing you to define multiple disjoint
"mappings" for a mapping class. A mapping is by convention identified
with a symbol, e.g. <tt>:my_mapping</tt>, <tt>:other_mapping</tt>
etc., and each mapping comprises a root element name and a set of node
definitions. In the body of a mapping class definition, you switch to
another mapping with <tt>use_mapping :the_mapping</tt>. All following
node declarations will be added to that mapping *unless* you specify
the option :mapping=>:another_mapping for a node declaration (all node
types support that option). The default mapping (the mapping used if
there was no previous +use_mapping+ in the class body) is named
<tt>:_default</tt>.

All the worker methods like <tt>load_from_xml/file</tt>,
<tt>save_to_xml/file</tt>, <tt>load_object_from_xml/file</tt> support
a <tt>:mapping</tt> keyword argument to specify the mapping, which
again defaults to <tt>:_default</tt>.

In the following example, we define two mappings (the default one and
a mapping named <tt>:other</tt>) for +Person+ objects with a name, an
age and an address:

:include: examples/person_mm.intout

You may have noticed that the <tt>object_node</tt>s in the +Person+
class apply the mapping they were themselves defined in to their
sub-ordinated class (+Address+). This is the case for all
{Single-attribute Nodes with Sub-objects}[aref:subobjnodes]
(+object_node+, +array_node+ and +hash_node+) unless you explicitly
specify a different mapping for the sub-object(s) using the option
:sub_mapping, e.g.

object_node :address, "address", :class=>Address, :sub_mapping=>:other



== {Defining your own Node Types}[a:definingnodes]
Expand Down
2 changes: 2 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ FILES_RDOC_INCLUDES=%w{examples/company.xml
examples/order_signature_enhanced.xml
examples/person.intout
examples/publication.intout
examples/reader.intout
examples/person_mm.intout
}


Expand Down
6 changes: 6 additions & 0 deletions TODO.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
methods). The language could more or less be Ruby itself, but the
write support could need some extra work...

- node types: get rid of initialize_impl. Instead, have initializers
return the remaining arguments, so initializers can call superclass
initializers directly

- XML::XXPath: implement [attrname='attrvalue'] steps

- documentation:

- README:
Expand Down
6 changes: 3 additions & 3 deletions examples/person.intin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ class Person


#:invisible_retval:
require 'test/unit/assertions'
include Test::Unit::Assertions

p1.save_to_xml.write($stdout)#<=

p2.save_to_xml.write($stdout)#<=

p3.save_to_xml.write($stdout)#<=

#:invisible:
require 'test/unit/assertions'
include Test::Unit::Assertions

assert_equal "Jim", p1.name
assert_equal "James", p2.name
assert_equal "Suzy", p3.name
Expand Down
Loading

0 comments on commit 7feb364

Please sign in to comment.