From 979f77a8c7267233f57834bb3110fe74cec0f04d Mon Sep 17 00:00:00 2001 From: Olaf Klischat Date: Fri, 27 Dec 2013 19:22:10 +0100 Subject: [PATCH 1/4] manual documentation commit --- classes/Numeric.html | 200 ++ classes/REXML.html | 113 + classes/REXML/Child.html | 118 + classes/REXML/Parent.html | 230 ++ classes/REXML/Text.html | 136 ++ classes/String.html | 196 ++ classes/XML.html | 148 ++ classes/XML/Mapping.html | 881 +++++++ classes/XML/Mapping/ArrayNode.html | 230 ++ classes/XML/Mapping/BooleanNode.html | 177 ++ classes/XML/Mapping/ChoiceNode.html | 294 +++ classes/XML/Mapping/ClassMethods.html | 440 ++++ classes/XML/Mapping/HashNode.html | 210 ++ classes/XML/Mapping/Node.html | 436 ++++ classes/XML/Mapping/NumericNode.html | 167 ++ classes/XML/Mapping/ObjectNode.html | 182 ++ classes/XML/Mapping/SingleAttributeNode.html | 366 +++ .../SingleAttributeNode/NoAttrValueSet.html | 121 + classes/XML/Mapping/SubObjectBaseNode.html | 221 ++ classes/XML/Mapping/TextNode.html | 172 ++ classes/XML/MappingError.html | 111 + classes/XML/XXPath.html | 351 +++ classes/XML/XXPath/Accessors/Attribute.html | 304 +++ classes/XML/XXPath/Accessors/REXML.html | 101 + .../Accessors/UnspecifiednessSupport.html | 207 ++ classes/XML/XXPathError.html | 111 + classes/XML/XXPathMethods.html | 276 +++ created.rid | 1 + files/ChangeLog.html | 273 +++ files/README.html | 2052 +++++++++++++++++ files/README_XPATH.html | 749 ++++++ files/TODO_txt.html | 210 ++ files/doc/xpath_impl_notes_txt.html | 236 ++ files/lib/xml/mapping/base_rb.html | 119 + .../xml/mapping/core_classes_mapping_rb.html | 101 + files/lib/xml/mapping/standard_nodes_rb.html | 118 + files/lib/xml/mapping/version_rb.html | 111 + files/lib/xml/mapping_rb.html | 119 + files/lib/xml/rexml_ext_rb.html | 117 + files/lib/xml/xxpath/steps_rb.html | 110 + files/lib/xml/xxpath_methods_rb.html | 110 + files/lib/xml/xxpath_rb.html | 119 + fr_class_index.html | 53 + fr_file_index.html | 40 + fr_method_index.html | 103 + index.html | 24 + rdoc-style.css | 208 ++ 47 files changed, 11472 insertions(+) create mode 100644 classes/Numeric.html create mode 100644 classes/REXML.html create mode 100644 classes/REXML/Child.html create mode 100644 classes/REXML/Parent.html create mode 100644 classes/REXML/Text.html create mode 100644 classes/String.html create mode 100644 classes/XML.html create mode 100644 classes/XML/Mapping.html create mode 100644 classes/XML/Mapping/ArrayNode.html create mode 100644 classes/XML/Mapping/BooleanNode.html create mode 100644 classes/XML/Mapping/ChoiceNode.html create mode 100644 classes/XML/Mapping/ClassMethods.html create mode 100644 classes/XML/Mapping/HashNode.html create mode 100644 classes/XML/Mapping/Node.html create mode 100644 classes/XML/Mapping/NumericNode.html create mode 100644 classes/XML/Mapping/ObjectNode.html create mode 100644 classes/XML/Mapping/SingleAttributeNode.html create mode 100644 classes/XML/Mapping/SingleAttributeNode/NoAttrValueSet.html create mode 100644 classes/XML/Mapping/SubObjectBaseNode.html create mode 100644 classes/XML/Mapping/TextNode.html create mode 100644 classes/XML/MappingError.html create mode 100644 classes/XML/XXPath.html create mode 100644 classes/XML/XXPath/Accessors/Attribute.html create mode 100644 classes/XML/XXPath/Accessors/REXML.html create mode 100644 classes/XML/XXPath/Accessors/UnspecifiednessSupport.html create mode 100644 classes/XML/XXPathError.html create mode 100644 classes/XML/XXPathMethods.html create mode 100644 created.rid create mode 100644 files/ChangeLog.html create mode 100644 files/README.html create mode 100644 files/README_XPATH.html create mode 100644 files/TODO_txt.html create mode 100644 files/doc/xpath_impl_notes_txt.html create mode 100644 files/lib/xml/mapping/base_rb.html create mode 100644 files/lib/xml/mapping/core_classes_mapping_rb.html create mode 100644 files/lib/xml/mapping/standard_nodes_rb.html create mode 100644 files/lib/xml/mapping/version_rb.html create mode 100644 files/lib/xml/mapping_rb.html create mode 100644 files/lib/xml/rexml_ext_rb.html create mode 100644 files/lib/xml/xxpath/steps_rb.html create mode 100644 files/lib/xml/xxpath_methods_rb.html create mode 100644 files/lib/xml/xxpath_rb.html create mode 100644 fr_class_index.html create mode 100644 fr_file_index.html create mode 100644 fr_method_index.html create mode 100644 index.html create mode 100644 rdoc-style.css diff --git a/classes/Numeric.html b/classes/Numeric.html new file mode 100644 index 0000000..dfedbfa --- /dev/null +++ b/classes/Numeric.html @@ -0,0 +1,200 @@ + + + + + + Class: Numeric + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassNumeric
In: + + lib/xml/mapping/core_classes_mapping.rb + +
+
Parent: + Object +
+
+ + +
+ + + +
+ + + +
+ +
+

Methods

+ +
+ fill_into_xml   + load_from_xml   + text   +
+
+ +
+ + + + +
+ + + + + + + + + +
+

Public Class methods

+ +
+ + + + +
+

[Source]

+
+
+    # File lib/xml/mapping/core_classes_mapping.rb, line 17
+17:   def self.load_from_xml(xml, options={:mapping=>:_default})
+18:     begin
+19:       Integer(xml.text)
+20:     rescue ArgumentError
+21:       Float(xml.text)
+22:     end
+23:   end
+
+
+
+
+ +

Public Instance methods

+ +
+ + + + +
+

[Source]

+
+
+    # File lib/xml/mapping/core_classes_mapping.rb, line 25
+25:   def fill_into_xml(xml, options={:mapping=>:_default})
+26:     xml.text = self.to_s
+27:   end
+
+
+
+
+ +
+ + + + +
+

[Source]

+
+
+    # File lib/xml/mapping/core_classes_mapping.rb, line 29
+29:   def text
+30:     self.to_s
+31:   end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/REXML.html b/classes/REXML.html new file mode 100644 index 0000000..6db7941 --- /dev/null +++ b/classes/REXML.html @@ -0,0 +1,113 @@ + + + + + + Module: REXML + + + + + + + + + + +
+ + + + + + + + + + +
ModuleREXML
In: + + lib/xml/xxpath_methods.rb + +
+
+
+ + +
+ + + +
+ + + +
+ + +
+ + + + +
+ +
+

Classes and Modules

+ + Class REXML::Child
+Class REXML::Parent
+Class REXML::Text
+ +
+ + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/REXML/Child.html b/classes/REXML/Child.html new file mode 100644 index 0000000..810f8fd --- /dev/null +++ b/classes/REXML/Child.html @@ -0,0 +1,118 @@ + + + + + + Class: REXML::Child + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassREXML::Child
In: + + lib/xml/xxpath_methods.rb + +
+
Parent: + Object +
+
+ + +
+ + + +
+ + + +
+ + +
+ + + +
+

Included Modules

+ + +
+ +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/REXML/Parent.html b/classes/REXML/Parent.html new file mode 100644 index 0000000..d8bdd5d --- /dev/null +++ b/classes/REXML/Parent.html @@ -0,0 +1,230 @@ + + + + + + Class: REXML::Parent + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassREXML::Parent
In: + + lib/xml/rexml_ext.rb + +
+
Parent: + Object +
+
+ + +
+ + + +
+ + + +
+ +
+

Methods

+ + +
+ +
+ + + + +
+ + + + + + + + + +
+

Public Instance methods

+ +
+ + + + +
+

[Source]

+
+
+     # File lib/xml/rexml_ext.rb, line 143
+143:   def each_on_axis(axis, &block)
+144:     send "each_on_axis_#{axis}""each_on_axis_#{axis}", &block
+145:   end
+
+
+
+
+ +
+ + + + +
+

[Source]

+
+
+     # File lib/xml/rexml_ext.rb, line 119
+119:   def each_on_axis_child
+120:     if respond_to? :attributes
+121:       attributes.each_key do |name|
+122:         yield XML::XXPath::Accessors::Attribute.new(self, name, false)
+123:       end
+124:     end
+125:     each_child do |c|
+126:       yield c
+127:     end
+128:   end
+
+
+
+
+ +
+ + + + +
+

[Source]

+
+
+     # File lib/xml/rexml_ext.rb, line 130
+130:   def each_on_axis_descendant(&block)
+131:     each_on_axis_child do |c|
+132:       block.call c
+133:       if REXML::Parent===c
+134:         c.each_on_axis_descendant(&block)
+135:       end
+136:     end
+137:   end
+
+
+
+
+ +
+ + + + +
+

[Source]

+
+
+     # File lib/xml/rexml_ext.rb, line 139
+139:   def each_on_axis_self
+140:     yield self
+141:   end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/REXML/Text.html b/classes/REXML/Text.html new file mode 100644 index 0000000..4fd9eb2 --- /dev/null +++ b/classes/REXML/Text.html @@ -0,0 +1,136 @@ + + + + + + Class: REXML::Text + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassREXML::Text
In: + + lib/xml/xxpath/steps.rb + +
+
Parent: + Object +
+
+ + +
+ + + +
+ + + +
+ + +
+ + + + +
+ + + +
+

External Aliases

+ +
+ + + + + + + + + + + + + + + +
value->text
  +call-compatibility w/ REXML::Element + +
value=->text=
+
+
+ + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/String.html b/classes/String.html new file mode 100644 index 0000000..9f76cb4 --- /dev/null +++ b/classes/String.html @@ -0,0 +1,196 @@ + + + + + + Class: String + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassString
In: + + lib/xml/mapping/core_classes_mapping.rb + +
+
Parent: + Object +
+
+ + +
+ + + +
+ + + +
+ +
+

Methods

+ +
+ fill_into_xml   + load_from_xml   + text   +
+
+ +
+ + + + +
+ + + + + + + + + +
+

Public Class methods

+ +
+ + + + +
+

[Source]

+
+
+   # File lib/xml/mapping/core_classes_mapping.rb, line 2
+2:   def self.load_from_xml(xml, options={:mapping=>:_default})
+3:     xml.text
+4:   end
+
+
+
+
+ +

Public Instance methods

+ +
+ + + + +
+

[Source]

+
+
+   # File lib/xml/mapping/core_classes_mapping.rb, line 6
+6:   def fill_into_xml(xml, options={:mapping=>:_default})
+7:     xml.text = self
+8:   end
+
+
+
+
+ +
+ + + + +
+

[Source]

+
+
+    # File lib/xml/mapping/core_classes_mapping.rb, line 10
+10:   def text
+11:     self
+12:   end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML.html b/classes/XML.html new file mode 100644 index 0000000..43d5ceb --- /dev/null +++ b/classes/XML.html @@ -0,0 +1,148 @@ + + + + + + Module: XML + + + + + + + + + + +
+ + + + + + + + + + +
ModuleXML
In: + + lib/xml/rexml_ext.rb + +
+ + lib/xml/xxpath.rb + +
+ + lib/xml/xxpath_methods.rb + +
+ + lib/xml/mapping/version.rb + +
+ + lib/xml/mapping/base.rb + +
+ + lib/xml/mapping/standard_nodes.rb + +
+ + lib/xml/xxpath/steps.rb + +
+
+
+ + +
+ + + +
+ +
+

+xxpath — XPath implementation for Ruby, including write access +

+
+ Copyright (C) 2004-2006 Olaf Klischat
+
+ +
+ + +
+ + +
+ + + + +
+ +
+

Classes and Modules

+ + Module XML::Mapping
+Module XML::XXPathMethods
+Class XML::MappingError
+Class XML::XXPath
+Class XML::XXPathError
+ +
+ + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/Mapping.html b/classes/XML/Mapping.html new file mode 100644 index 0000000..da1b58c --- /dev/null +++ b/classes/XML/Mapping.html @@ -0,0 +1,881 @@ + + + + + + Module: XML::Mapping + + + + + + + + + + +
+ + + + + + + + + + +
ModuleXML::Mapping
In: + + lib/xml/mapping/version.rb + +
+ + lib/xml/mapping/base.rb + +
+ + lib/xml/mapping/standard_nodes.rb + +
+
+
+ + +
+ + + +
+ +
+

+This is the central interface module of the xml-mapping library. +

+

+Including this module in your classes adds XML +mapping capabilities to them. +

+

Example

+

Input document:

+
+  <?xml version="1.0" encoding="ISO-8859-1"?>
+
+  <company name="ACME inc.">
+
+      <address>
+        <city>Berlin</city>
+        <zip>10113</zip>
+      </address>
+
+      <customers>
+
+        <customer id="jim">
+          <name>James Kirk</name>
+        </customer>
+
+        <customer id="ernie">
+          <name>Ernie</name>
+        </customer>
+
+        <customer id="bert">
+          <name>Bert</name>
+        </customer>
+
+      </customers>
+
+  </company>
+
+

mapping class declaration:

+
+  require 'xml/mapping'
+
+  # forward declarations
+  class Address; end
+  class Customer; end
+
+  class Company
+    include XML::Mapping
+
+    text_node :name, "@name"
+    object_node :address, "address", :class=>Address
+    array_node :customers, "customers", "customer", :class=>Customer
+  end
+
+  class Address
+    include XML::Mapping
+
+    text_node :city, "city"
+    numeric_node :zip, "zip"
+  end
+
+  class Customer
+    include XML::Mapping
+
+    text_node :id, "@id"
+    text_node :name, "name"
+
+    def initialize(id,name)
+      @id,@name = [id,name]
+    end
+  end
+
+

usage:

+
+  c = Company.load_from_file('company.xml')
+  => #<Company:0xb78d0cfc @customers=[#<Customer:0xb78cf8d4 @id="jim", @name="James Kirk">, #<Customer:0xb78cf208 @id="ernie", @name="Ernie">, #<Customer:0xb78ceb3c @id="bert", @name="Bert">], @address=#<Address:0xb78d0680 @city="Berlin", @zip=10113>, @name="ACME inc.">
+  c.name
+  => "ACME inc."
+  c.customers.size
+  => 3
+  c.customers[1]
+  => #<Customer:0xb78cf208 @id="ernie", @name="Ernie">
+  c.customers[1].name
+  => "Ernie"
+  c.customers[0].name
+  => "James Kirk"
+  c.customers[0].name = 'James Tiberius Kirk'
+  => "James Tiberius Kirk"
+  c.customers << Customer.new('cm','Cookie Monster')
+  => [#<Customer:0xb78cf8d4 @id="jim", @name="James Tiberius Kirk">, #<Customer:0xb78cf208 @id="ernie", @name="Ernie">, #<Customer:0xb78ceb3c @id="bert", @name="Bert">, #<Customer:0xb78cd9e4 @id="cm", @name="Cookie Monster">]
+  xml2 = c.save_to_xml
+  => <company name='ACME inc.'> ... </>
+  xml2.write($stdout,2)
+  <company name='ACME inc.'>
+        <address>
+          <city>Berlin</city>
+          <zip>10113</zip>
+        </address>
+        <customers>
+          <customer id='-607749014'>
+            <name>James Tiberius Kirk</name>
+          </customer>
+          <customer id='-607749884'>
+            <name>Ernie</name>
+          </customer>
+          <customer id='-607750754'>
+            <name>Bert</name>
+          </customer>
+          <customer id='-607752974'>
+            <name>Cookie Monster</name>
+          </customer>
+        </customers>
+      </company>#
+
+

+So you have to include XML::Mapping into your +class to turn it into a "mapping class", that is, to add XML mapping capabilities to it. An instance of the +mapping classes is then bidirectionally mapped to an XML node (i.e. an element), where the state (simple +attributes, sub-objects, arrays, hashes etc.) of that instance is mapped to +sub-nodes of that node. In addition to the class and instance methods +defined in XML::Mapping, your mapping class will +get class methods like ‘text_node’, ‘array_node’ +and so on; I call them "node factory methods". More precisely, +there is one node factory method for each registered node type. Node types are classes derived from XML::Mapping::Node; they’re registered +with the xml-mapping library via XML::Mapping.add_node_class. The node types +TextNode, BooleanNode, NumericNode, ObjectNode, ArrayNode, and HashNode are automatically registered by +xml/mapping.rb; you can easily write your own ones. The name of a node +factory method is inferred by ‘underscoring’ the name of the +corresponding node type; e.g. ‘TextNode’ becomes +‘text_node’. Each node factory method creates an instance of +the corresponding node type and adds it to the mapping class (not its +instances). The arguments to a node factory method are automatically turned +into arguments to the corresponding node type’s initializer. So, in +order to learn more about the meaning of a node factory method’s +parameters, you read the documentation of the corresponding node type. All +predefined node types expect as their first argument a symbol that names an +r/w attribute which will be added to the mapping class. The mapping class +is a normal Ruby class; you can add constructors, methods and attributes to +it, derive from it, derive it from another class, include additional +modules etc. +

+

+Including XML::Mapping also adds all methods of +XML::Mapping::ClassMethods to your +class (as class methods). +

+

+As you may have noticed from the example, the node factory methods +generally use XPath expressions to specify locations in the mapped XML document. To make this work, XML::Mapping relies on XML::XXPath, which implements a subset of XPath, but +also provides write access, which is needed by the node types to support +writing data back to XML. Both XML::Mapping and XML::XXPath use REXML +(www.germane-software.com/software/rexml/) +to represent XML elements/documents in memory. +

+ +
+ + +
+ + + +
+ + + + +
+ + + +
+

Constants

+ +
+ + + + + + +
VERSION='0.9'
+
+
+ + + + + + + +
+

Public Class methods

+ +
+ + + + +
+

+Registers the new node class c (must be a descendant of Node) with the xml-mapping framework. +

+

+A new "factory method" will automatically be added to ClassMethods (and therefore to all +classes that include XML::Mapping from now on); +so you can call it from the body of your mapping class definition in order +to create nodes of type c. The name of the factory method is +derived by "underscoring" the (unqualified) name of c; +e.g. c==Foo::Bar::MyNiftyNode will result in the creation +of a factory method named my_nifty_node. The generated factory +method creates and returns a new instance of c. The list of +argument to c.new consists of self (i.e. the mapping +class the factory method was called from) followed by the arguments passed +to the factory method. You should always use the factory methods to create +instances of node classes; you should never need to call a node +class’s constructor directly. +

+

+For a demonstration, see the calls to text_node, +array_node etc. in the examples along with the corresponding node +classes TextNode, ArrayNode etc. (these predefined node +classes are in no way "special"; they’re added using add_node_class in mapping.rb just like any +custom node classes would be). +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 455
+455:     def self.add_node_class(c)
+456:       meth_name = c.name.split('::')[-1].gsub(/^(.)/){$1.downcase}.gsub(/(.)([A-Z])/){$1+"_"+$2.downcase}
+457:       ClassMethods.module_eval "def \#{meth_name}(*args)\n\#{c.name}.new(self,*args)\nend\n"
+458:     end
+
+
+
+
+ +
+ + + + +
+

+Finds a mapping class and mapping name corresponding to the given XML root element name. There may be more than one +(class,mapping) tuple for a given root element name — in that case, +one of them is selected arbitrarily. +

+

+returns [class,mapping] +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 126
+126:     def self.class_and_mapping_for_root_elt_name(name)
+127:       (Classes_by_rootelt_names[name] || {}).each_pair{|mapping,classes| return [classes[0],mapping] }
+128:       nil
+129:     end
+
+
+
+
+ +
+ + + + +
+

+Finds a mapping class corresponding to the given XML root element name and mapping name. There may be +more than one such class — in that case, the most recently defined +one is returned +

+

+This is the inverse operation to <class>.root_element_name (see XML::Mapping::ClassMethods.root_element_name). +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 114
+114:     def self.class_for_root_elt_name(name, options={:mapping=>:_default})
+115:       # TODO: implement Hash read-only instead of this
+116:       # interface
+117:       Classes_by_rootelt_names.classes_for(name, options[:mapping])[-1]
+118:     end
+
+
+
+
+ +
+ + + + +
+

+Like load_object_from_xml, but loads +from the XML file named by filename. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 425
+425:     def self.load_object_from_file(filename,options={:mapping=>nil})
+426:       xml = REXML::Document.new(File.new(filename))
+427:       load_object_from_xml xml.root, options
+428:     end
+
+
+
+
+ +
+ + + + +
+

+"polymorphic" load function. Turns the XML tree xml into an object, which is +returned. The class of the object and the mapping to be used for +unmarshalling are automatically determined from the root element name of +xml using XML::Mapping.class_for_root_elt_name. If +:mapping is non-nil, only root element names defined in that mapping will +be considered (default is to consider all classes) +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 411
+411:     def self.load_object_from_xml(xml,options={:mapping=>nil})
+412:       if mapping = options[:mapping]
+413:         c = class_for_root_elt_name xml.name, :mapping=>mapping
+414:       else
+415:         c,mapping = class_and_mapping_for_root_elt_name(xml.name)
+416:       end
+417:       unless c
+418:         raise MappingError, "no mapping class for root element name #{xml.name}, mapping #{mapping.inspect}"
+419:       end
+420:       c.load_from_xml xml, :mapping=>mapping
+421:     end
+
+
+
+
+ +
+ + + + +
+

+Initializer. Called (by Class#new) after self was created using +new. Not called when self was created from an XML source using load_from_file/load_from_xml (see +initialize_xml_mapping). +

+

+XML::Mapping’s implementation calls initialize_xml_mapping. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 162
+162:     def initialize(*args)
+163:       initialize_xml_mapping
+164:     end
+
+
+
+
+ +

Public Instance methods

+ +
+ + + + +
+

+"fill" the contents of xml into self. +xml is a REXML::Element. +

+

+First, pre_load(xml) is called, +then all the nodes for this object’s class are processed (i.e. have +their xml_to_obj method called) in the order of their definition inside the +class, then post_load is called. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 173
+173:     def fill_from_xml(xml, options={:mapping=>:_default})
+174:       raise(MappingError, "undefined mapping: #{options[:mapping].inspect}") \
+175:         unless self.class.xml_mapping_nodes_hash.has_key?(options[:mapping])
+176:       pre_load xml, :mapping=>options[:mapping]
+177:       self.class.all_xml_mapping_nodes(:mapping=>options[:mapping]).each do |node|
+178:         node.xml_to_obj self, xml
+179:       end
+180:       post_load :mapping=>options[:mapping]
+181:     end
+
+
+
+
+ +
+ + + + +
+

+Fill self’s state into the xml node (REXML::Element) +xml. All the nodes for this object’s class are processed +(i.e. have their obj_to_xml method called) in the order of their definition +inside the class. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 208
+208:     def fill_into_xml(xml, options={:mapping=>:_default})
+209:       self.class.all_xml_mapping_nodes(:mapping=>options[:mapping]).each do |node|
+210:         node.obj_to_xml self,xml
+211:       end
+212:     end
+
+
+
+
+ +
+ + + + +
+

+Xml-mapping-specific initializer. +

+

+This will be called when a new instance is being initialized from an XML source, as well as after calling +class.new(args) (for the latter case to work, +you’ll have to make sure you call the inherited initialize +method) +

+

+The :mapping keyword argument gives the mapping the instance is being +initialized with. This is non-nil only when the instance is being +initialized from an XML source (:mapping will +contain the :mapping argument passed (explicitly or implicitly) to the +load_from_… method). +

+

+When the instance is being initialized because class.new +was called, the :mapping argument is set to nil to show that the object is +being initialized with respect to no specific mapping. +

+

+The default implementation of this method calls obj_initializing on all +nodes. You may overwrite this method to do your own initialization stuff; +make sure to call super in that case. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 151
+151:     def initialize_xml_mapping(options={:mapping=>nil})
+152:       self.class.all_xml_mapping_nodes(:mapping=>options[:mapping]).each do |node|
+153:         node.obj_initializing(self,options[:mapping])
+154:       end
+155:     end
+
+
+
+
+ +
+ + + + +
+

+This method is called immediately after self has been filled from +an xml source. If you have things to do after the object has been +succefully loaded from the xml (reorganising the loaded data in some way, +setting up additional views on the data etc.), this is the place where you +put them. You can also raise an exception to abandon the whole loading +process. +

+

+The default implementation of this method is empty. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 199
+199:     def post_load(options={:mapping=>:_default})
+200:     end
+
+
+
+
+ +
+ + + + +
+

+This method is called immediately after self’s state has +been filled into an XML element. +

+

+The default implementation does nothing. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 245
+245:     def post_save(xml, options={:mapping=>:_default})
+246:     end
+
+
+
+
+ +
+ + + + +
+

+This method is called immediately before self is filled from an +xml source. xml is the source REXML::Element. +

+

+The default implementation of this method is empty. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 187
+187:     def pre_load(xml, options={:mapping=>:_default})
+188:     end
+
+
+
+
+ +
+ + + + +
+

+This method is called when self is to be converted to an XML tree. It must create and return an XML element (as a REXML::Element); that element will +then be passed to fill_into_xml. +

+

+The default implementation of this method creates a new empty element whose +name is the root_element_name of self’s class (see ClassMethods.root_element_name). +By default, this is the class name, with capital letters converted to +lowercase and preceded by a dash, e.g. "MySampleClass" becomes +"my-sample-class". +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 237
+237:     def pre_save(options={:mapping=>:_default})
+238:       REXML::Element.new(self.class.root_element_name(:mapping=>options[:mapping]))
+239:     end
+
+
+
+
+ +
+ + + + +
+

+Save self’s state as XML into the +file named filename. The XML is obtained +by calling save_to_xml. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 251
+251:     def save_to_file(filename, options={:mapping=>:_default})
+252:       xml = save_to_xml :mapping=>options[:mapping]
+253:       File.open(filename,"w") do |f|
+254:         xml.write(f,2)
+255:       end
+256:     end
+
+
+
+
+ +
+ + + + +
+

+Fill self’s state into a new xml node, return that node. +

+

+This method calls pre_save, then fill_into_xml, then post_save. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 219
+219:     def save_to_xml(options={:mapping=>:_default})
+220:       xml = pre_save :mapping=>options[:mapping]
+221:       fill_into_xml xml, :mapping=>options[:mapping]
+222:       post_save xml, :mapping=>options[:mapping]
+223:       xml
+224:     end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/Mapping/ArrayNode.html b/classes/XML/Mapping/ArrayNode.html new file mode 100644 index 0000000..227f2e8 --- /dev/null +++ b/classes/XML/Mapping/ArrayNode.html @@ -0,0 +1,230 @@ + + + + + + Class: XML::Mapping::ArrayNode + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassXML::Mapping::ArrayNode
In: + + lib/xml/mapping/standard_nodes.rb + +
+
Parent: + + SubObjectBaseNode + +
+
+ + +
+ + + +
+ +
+

+Node factory function synopsis: +

+
+  array_node :_attrname_, _per_arrelement_path_
+                    [, :default_value=>_obj_]
+                    [, :optional=>true]
+                    [, :class=>_c_]
+                    [, :marshaller=>_proc_]
+                    [, :unmarshaller=>_proc_]
+                    [, :mapping=>_m_]
+                    [, :sub_mapping=>_sm_]
+
+

+-or- +

+
+  array_node :_attrname_, _base_path_, _per_arrelement_path_
+                    [keyword args the same]
+
+

+Node that maps a sequence of sub-nodes of the XML tree to an attribute containing an array of +Ruby objects, with each array element mapping to a corresponding member of +the sequence of sub-nodes. +

+

+If base_path is not supplied, it is assumed to be "". +base_path+"/"+per_arrelement_path is +an XPath expression that must "yield" the sequence of XML nodes that is to be mapped to the array. The +difference between base_path and per_arrelement_path +becomes important when marshalling the array attribute back to XML. When that happens, base_path names +the most specific common parent node of all the mapped sub-nodes, and +per_arrelement_path names (relative to base_path) the +part of the path that is duplicated for each array element. For example, +with base_path=="foo/bar" and +per_arrelement_path=="hi/ho", an array +[x,y,z] will be written to an XML +structure that looks like this: +

+
+  <foo>
+   <bar>
+    <hi>
+     <ho>
+      [marshalled object x]
+     </ho>
+    </hi>
+    <hi>
+     <ho>
+      [marshalled object y]
+     </ho>
+    </hi>
+    <hi>
+     <ho>
+      [marshalled object z]
+     </ho>
+    </hi>
+   </bar>
+  </foo>
+
+ +
+ + +
+ +
+

Methods

+ +
+ new   +
+
+ +
+ + + + +
+ + + + + + + + + +
+

Public Class methods

+ +
+ + + + +
+

+Initializer. Called with keyword arguments and either 1 or 2 paths; the +hindmost path argument passed is delegated to per_arrelement_path; +the preceding path argument (if present, "" by default) is +delegated to base_path. +

+

[Source]

+
+
+     # File lib/xml/mapping/standard_nodes.rb, line 263
+263:       def initialize(*args)
+264:         path,path2,*args = super(*args)
+265:         base_path,per_arrelement_path = if path2
+266:                                           [path,path2]
+267:                                         else
+268:                                           [".",path]
+269:                                         end
+270:         per_arrelement_path=per_arrelement_path[1..-1] if per_arrelement_path[0]==?/
+271:         @base_path = XML::XXPath.new(base_path)
+272:         @per_arrelement_path = XML::XXPath.new(per_arrelement_path)
+273:         @reader_path = XML::XXPath.new(base_path+"/"+per_arrelement_path)
+274:         args
+275:       end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/Mapping/BooleanNode.html b/classes/XML/Mapping/BooleanNode.html new file mode 100644 index 0000000..8b50544 --- /dev/null +++ b/classes/XML/Mapping/BooleanNode.html @@ -0,0 +1,177 @@ + + + + + + Class: XML::Mapping::BooleanNode + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassXML::Mapping::BooleanNode
In: + + lib/xml/mapping/standard_nodes.rb + +
+
Parent: + + SingleAttributeNode + +
+
+ + +
+ + + +
+ +
+

+Node factory function synopsis: +

+
+  boolean_node :_attrname_, _path_,
+               _true_value_, _false_value_ [, :default_value=>_obj_]
+                                           [, :optional=>true]
+                                           [, :mapping=>_m_]
+
+

+Node that maps an XML +node’s text (the element name resp. the attribute value) to a boolean +attribute of the mapped object. The attribute named by :attrname +is mapped to/from the XML subnode named by the +XPath expression path. true_value is the text the node +must have in order to represent the true boolean value, +false_value (actually, any value other than true_value) +is the text the node must have in order to represent the false +boolean value. +

+ +
+ + +
+ +
+

Methods

+ +
+ new   +
+
+ +
+ + + + +
+ + + + + + + + + +
+

Public Class methods

+ +
+ + + + +
+

+Initializer. +

+

[Source]

+
+
+     # File lib/xml/mapping/standard_nodes.rb, line 190
+190:       def initialize(*args)
+191:         path,true_value,false_value,*args = super(*args)
+192:         @path = XML::XXPath.new(path)
+193:         @true_value = true_value; @false_value = false_value
+194:         args
+195:       end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/Mapping/ChoiceNode.html b/classes/XML/Mapping/ChoiceNode.html new file mode 100644 index 0000000..4bec724 --- /dev/null +++ b/classes/XML/Mapping/ChoiceNode.html @@ -0,0 +1,294 @@ + + + + + + Class: XML::Mapping::ChoiceNode + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassXML::Mapping::ChoiceNode
In: + + lib/xml/mapping/standard_nodes.rb + +
+
Parent: + + Node + +
+
+ + +
+ + + +
+ + + +
+ +
+

Methods

+ +
+ is_present_in?   + new   + obj_initializing   + obj_to_xml   + xml_to_obj   +
+
+ +
+ + + + +
+ + + + + + + + + +
+

Public Class methods

+ +
+ + + + +
+

[Source]

+
+
+     # File lib/xml/mapping/standard_nodes.rb, line 364
+364:       def initialize(*args)
+365:         args = super(*args)
+366:         @choices = []
+367:         path=nil
+368:         args.each do |arg|
+369:           next if [:if,:then,:elsif].include? arg
+370:           if path.nil?
+371:             path = (if [:else,:default,:otherwise].include? arg
+372:                       :else
+373:                     else
+374:                       XML::XXPath.new arg
+375:                     end)
+376:           else
+377:             raise XML::MappingError, "node expected, found: #{arg.inspect}" unless Node===arg
+378:             @choices << [path,arg]
+379: 
+380:             # undo what the node factory fcn did -- ugly ugly!  would
+381:             # need some way to lazy-evaluate arg (a proc would be
+382:             # simple but ugly for the user), and then use some
+383:             # mechanism (a flag with dynamic scope probably) to tell
+384:             # the node factory fcn not to add the node to the
+385:             # xml_mapping_nodes
+386:             @owner.xml_mapping_nodes(:mapping=>@mapping).delete arg 
+387:             path=nil
+388:           end
+389:         end
+390: 
+391:         raise XML::MappingError, "node missing at end of argument list" unless path.nil?
+392:         raise XML::MappingError, "no choices were supplied" if @choices.empty?
+393:         
+394:         []
+395:       end
+
+
+
+
+ +

Public Instance methods

+ +
+ + + + +
+

+(overridden) true if at least one of our nodes is_present_in? obj. +

+

[Source]

+
+
+     # File lib/xml/mapping/standard_nodes.rb, line 425
+425:       def is_present_in? obj
+426:         # TODO: use Enumerable#any?
+427:         @choices.inject(false){|prev,(path,node)| prev or node.is_present_in?(obj)}
+428:       end
+
+
+
+
+ +
+ + + + +
+

[Source]

+
+
+     # File lib/xml/mapping/standard_nodes.rb, line 419
+419:       def obj_initializing(obj,mapping)
+420:         @choices[0][1].obj_initializing(obj,mapping)
+421:       end
+
+
+
+
+ +
+ + + + +
+

[Source]

+
+
+     # File lib/xml/mapping/standard_nodes.rb, line 407
+407:       def obj_to_xml(obj,xml)
+408:         @choices.each do |path,node|
+409:           if node.is_present_in? obj
+410:             node.obj_to_xml(obj,xml)
+411:             path.first(xml, :ensure_created=>true)
+412:             return true
+413:           end
+414:         end
+415:         # @choices[0][1].obj_to_xml(obj,xml)
+416:         raise XML::MappingError, "obj_to_xml: no choice present in object: #{obj.inspect}"
+417:       end
+
+
+
+
+ +
+ + + + +
+

[Source]

+
+
+     # File lib/xml/mapping/standard_nodes.rb, line 397
+397:       def xml_to_obj(obj,xml)
+398:         @choices.each do |path,node|
+399:           if path==:else or not(path.all(xml).empty?)
+400:             node.xml_to_obj(obj,xml)
+401:             return true
+402:           end
+403:         end
+404:         raise XML::MappingError, "xml_to_obj: no choice matched in: #{xml}"
+405:       end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/Mapping/ClassMethods.html b/classes/XML/Mapping/ClassMethods.html new file mode 100644 index 0000000..847062f --- /dev/null +++ b/classes/XML/Mapping/ClassMethods.html @@ -0,0 +1,440 @@ + + + + + + Module: XML::Mapping::ClassMethods + + + + + + + + + + +
+ + + + + + + + + + +
ModuleXML::Mapping::ClassMethods
In: + + lib/xml/mapping/base.rb + +
+
+
+ + +
+ + + +
+ +
+

+The instance methods of this module are automatically added as class +methods to a class that includes XML::Mapping. +

+ +
+ + +
+ + + +
+ + + + +
+ + + + + + + + + +
+

Public Instance methods

+ +
+ + + + +
+

+Add getter and setter methods for a new attribute named name to +this class. This is a convenience method intended to be called from Node class initializers. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 298
+298:       def add_accessor(name)
+299:         name = name.id2name if name.kind_of? Symbol
+300:         unless self.instance_methods.include?(name)
+301:           self.module_eval "attr_reader :\#{name}\n"
+302:         end
+303:         unless self.instance_methods.include?("#{name}=")
+304:           self.module_eval "attr_writer :\#{name}\n"
+305:         end
+306:       end
+
+
+
+
+ +
+ + + + +
+

+enumeration of all nodes in effect when marshalling/unmarshalling this +class, that is, nodes defined for this class as well as for its +superclasses. The nodes are returned in the order of their definition, +starting with the topmost superclass that has nodes defined. Option +arguments are the same as for xml_mapping_nodes. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 355
+355:       def all_xml_mapping_nodes(options={:mapping=>nil})
+356:         # TODO: we could return a dynamic Enumerable here, or cache
+357:         # the array...
+358:         result = []
+359:         if superclass and superclass.respond_to?(:all_xml_mapping_nodes)
+360:           result += superclass.all_xml_mapping_nodes options
+361:         end
+362:         result += xml_mapping_nodes options
+363:       end
+
+
+
+
+ +
+ + + + +
+

+return the current default mapping (:_default initially, or the value set +with the latest call to use_mapping) +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 291
+291:       def default_mapping
+292:         @default_mapping
+293:       end
+
+
+
+
+ +
+ + + + +
+

+The default root element name for this class. Equals the class name, with +all parent module names stripped, and with capital letters converted to +lowercase and preceded by a dash; e.g. "Foo::Bar::MySampleClass" +becomes "my-sample-class". +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 396
+396:       def default_root_element_name
+397:         self.name.split('::')[-1].gsub(/^(.)/){$1.downcase}.gsub(/(.)([A-Z])/){$1+"-"+$2.downcase}
+398:       end
+
+
+
+
+ +
+ + + + +
+

+Create a new instance of this class from the XML contained in the file named +filename. Calls load_from_xml internally. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 316
+316:       def load_from_file(filename, options={:mapping=>:_default})
+317:         xml = REXML::Document.new(File.new(filename))
+318:         load_from_xml xml.root, :mapping=>options[:mapping]
+319:       end
+
+
+
+
+ +
+ + + + +
+

+Create a new instance of this class from the XML contained in xml (a REXML::Element). +

+

+Allocates a new object, then calls fill_from_xml(xml) on it. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 326
+326:       def load_from_xml(xml, options={:mapping=>:_default})
+327:         raise(MappingError, "undefined mapping: #{options[:mapping].inspect}") \
+328:           unless xml_mapping_nodes_hash.has_key?(options[:mapping])
+329:         obj = self.allocate
+330:         obj.initialize_xml_mapping :mapping=>options[:mapping]
+331:         obj.fill_from_xml xml, :mapping=>options[:mapping]
+332:         obj
+333:       end
+
+
+
+
+ +
+ + + + +
+

+The "root element name" of this class (combined getter/setter +method). +

+

+The root element name is the name of the root element of the XML tree returned by <this +class>.save_to_xml (or, more specifically, <this class>.pre_save). +By default, this method returns the default_root_element_name; you may +call this method with an argument to set the root element name to something +other than the default. The option argument :mapping specifies the mapping +the root element is/will be defined in, it defaults to the current default +mapping (:_default initially, or the value set with the latest call to use_mapping) +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 379
+379:       def root_element_name(name=nil, options={:mapping=>@default_mapping})
+380:         if Hash===name    # ugly...
+381:           options=name; name=nil
+382:         end
+383:         @root_element_names ||= {}
+384:         if name
+385:           Classes_by_rootelt_names.remove_class root_element_name, options[:mapping], self
+386:           @root_element_names[options[:mapping]] = name
+387:           Classes_by_rootelt_names.create_classes_for(name, options[:mapping]) << self
+388:         end
+389:         @root_element_names[options[:mapping]] || default_root_element_name
+390:       end
+
+
+
+
+ +
+ + + + +
+

+Make mapping the mapping to be used by default in future node +declarations in this class. The default can be overwritten on a per-node +basis by passing a :mapping option parameter to the node factory method +

+

+The initial default mapping in a mapping class is :_default +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 282
+282:       def use_mapping mapping
+283:         @default_mapping = mapping
+284:         xml_mapping_nodes_hash[mapping] ||= []  # create empty mapping node list if
+285:                                                 # there wasn't one before so future calls
+286:                                                 # to load/save_xml etc. w/ this mapping don't raise
+287:       end
+
+
+
+
+ +
+ + + + +
+

+array of all nodes defined in this class, in the order of their definition. +Option :mapping specifies the mapping the returned nodes must have been +defined in; nil means return all nodes regardless of their mapping +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 340
+340:       def xml_mapping_nodes(options={:mapping=>nil})
+341:         if nil==options[:mapping]
+342:           return xml_mapping_nodes_hash.values.flatten
+343:         end
+344:         mapping = options[:mapping] || @default_mapping
+345:         xml_mapping_nodes_hash[mapping] ||= []
+346:       end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/Mapping/HashNode.html b/classes/XML/Mapping/HashNode.html new file mode 100644 index 0000000..b39db20 --- /dev/null +++ b/classes/XML/Mapping/HashNode.html @@ -0,0 +1,210 @@ + + + + + + Class: XML::Mapping::HashNode + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassXML::Mapping::HashNode
In: + + lib/xml/mapping/standard_nodes.rb + +
+
Parent: + + SubObjectBaseNode + +
+
+ + +
+ + + +
+ +
+

+Node factory function synopsis: +

+
+  hash_node :_attrname_, _per_hashelement_path_, _key_path_
+                    [, :default_value=>_obj_]
+                    [, :optional=>true]
+                    [, :class=>_c_]
+                    [, :marshaller=>_proc_]
+                    [, :unmarshaller=>_proc_]
+                    [, :mapping=>_m_]
+                    [, :sub_mapping=>_sm_]
+
+
    +
  • or - + +

    +hash_node :attrname, base_path, +per_hashelement_path, key_path +

    +
    +                  [keyword args the same]
    +
    +
  • +
+

+Node that maps a sequence of sub-nodes of the XML tree to an attribute containing a hash of +Ruby objects, with each hash value mapping to a corresponding member of the +sequence of sub-nodes. The (string-valued) hash key associated with a hash +value v is mapped to the text of a specific sub-node of +v’s sub-node. +

+

+Analogously to ArrayNode, base_path +and per_arrelement_path define the XPath expression that +"yields" the sequence of XML nodes, +each of which maps to a value in the hash table. Relative to such a node, +key_path_ names the node whose text becomes the associated hash key. +

+ +
+ + +
+ +
+

Methods

+ +
+ new   +
+
+ +
+ + + + +
+ + + + + + + + + +
+

Public Class methods

+ +
+ + + + +
+

+Initializer. Called with keyword arguments and either 2 or 3 paths; the +hindmost path argument passed is delegated to key_path, the +preceding path argument is delegated to per_arrelement_path, the +path preceding that argument (if present, "" by default) is +delegated to base_path. The meaning of the keyword arguments is +the same as for ObjectNode. +

+

[Source]

+
+
+     # File lib/xml/mapping/standard_nodes.rb, line 328
+328:       def initialize(*args)
+329:         path1,path2,path3,*args = super(*args)
+330:         base_path,per_hashelement_path,key_path = if path3
+331:                                                     [path1,path2,path3]
+332:                                                   else
+333:                                                     ["",path1,path2]
+334:                                                   end
+335:         per_hashelement_path=per_hashelement_path[1..-1] if per_hashelement_path[0]==?/
+336:         @base_path = XML::XXPath.new(base_path)
+337:         @per_hashelement_path = XML::XXPath.new(per_hashelement_path)
+338:         @key_path = XML::XXPath.new(key_path)
+339:         @reader_path = XML::XXPath.new(base_path+"/"+per_hashelement_path)
+340:         args
+341:       end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/Mapping/Node.html b/classes/XML/Mapping/Node.html new file mode 100644 index 0000000..bce582b --- /dev/null +++ b/classes/XML/Mapping/Node.html @@ -0,0 +1,436 @@ + + + + + + Class: XML::Mapping::Node + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassXML::Mapping::Node
In: + + lib/xml/mapping/base.rb + +
+
Parent: + Object +
+
+ + +
+ + + +
+ +
+

+Abstract base class for all node types. As mentioned in the documentation +for XML::Mapping, node types must be +registered using XML::Mapping.add_node_class, and a +corresponding "node factory method" (e.g. "text_node") +will then be added as a class method to your mapping classes. The node +factory method is called from the body of the mapping classes as +demonstrated in the examples. It creates an instance of its corresponding +node type (the list of parameters to the node factory method, preceded by +the owning mapping class, will be passed to the constructor of the node +type) and adds it to its owning mapping class, so there is one node object +per node definition per mapping class. That node object will handle all XML marshalling/unmarshalling for this node, for +all instances of the mapping class. For this purpose, the marshalling and +unmarshalling methods of a mapping class instance (fill_into_xml and +fill_from_xml, respectively) will call obj_to_xml resp. xml_to_obj on all nodes of the mapping class, +in the order of their definition, passing the REXML element the data is to be marshalled +to/unmarshalled from as well as the object the data is to be read +from/filled into. +

+

+Node types that map some XML data to a single attribute of their mapping +class (that should be most of them) shouldn’t be directly derived +from this class, but rather from SingleAttributeNode. +

+ +
+ + +
+ +
+

Methods

+ +
+ is_present_in?   + new   + obj_initializing   + obj_to_xml   + obj_to_xml   + xml_to_obj   + xml_to_obj   +
+
+ +
+ + + + +
+ + + +
+

External Aliases

+ +
+ + + + + + + + + + + +
xml_to_obj->default_xml_to_obj
obj_to_xml->default_obj_to_xml
+
+
+ + + + + + +
+

Public Class methods

+ +
+ + + + +
+

+Intializer, to be called from descendant classes. owner is the +mapping class this node is being defined in. It‘ll be stored in +_@owner_. @options will be set to a (possibly empty) hash containing the +option arguments passed to initialize. Options :mapping, :reader +and :writer will be handled, subclasses may handle additional options. See +the section on defining nodes in the README for details. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 501
+501:       def initialize(owner,*args)
+502:         @owner = owner
+503:         if Hash===args[-1]
+504:           @options = args[-1]
+505:           args = args[0..-2]
+506:         else
+507:           @options={}
+508:         end
+509:         @mapping = @options[:mapping] || owner.default_mapping
+510:         owner.xml_mapping_nodes(:mapping=>@mapping) << self
+511:         XML::Mapping::Classes_by_rootelt_names.ensure_exists owner.root_element_name, @mapping, owner
+512:         if @options[:reader]
+513:           # override xml_to_obj in this instance with invocation of
+514:           # @options[:reader]
+515:           class << self
+516:             alias_method :default_xml_to_obj, :xml_to_obj
+517:             def xml_to_obj(obj,xml)
+518:               begin
+519:                 @options[:reader].call(obj,xml)
+520:               rescue ArgumentError
+521:                 @options[:reader].call(obj,xml,self.method(:default_xml_to_obj))
+522:               end
+523:             end
+524:           end
+525:         end
+526:         if @options[:writer]
+527:           # override obj_to_xml in this instance with invocation of
+528:           # @options[:writer]
+529:           class << self
+530:             alias_method :default_obj_to_xml, :obj_to_xml
+531:             def obj_to_xml(obj,xml)
+532:               begin
+533:                 @options[:writer].call(obj,xml)
+534:               rescue ArgumentError
+535:                 @options[:writer].call(obj,xml,self.method(:default_obj_to_xml))
+536:               end
+537:             end
+538:           end
+539:         end
+540:         args
+541:       end
+
+
+
+
+ +
+ + + + +
+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 531
+531:             def obj_to_xml(obj,xml)
+532:               begin
+533:                 @options[:writer].call(obj,xml)
+534:               rescue ArgumentError
+535:                 @options[:writer].call(obj,xml,self.method(:default_obj_to_xml))
+536:               end
+537:             end
+
+
+
+
+ +
+ + + + +
+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 517
+517:             def xml_to_obj(obj,xml)
+518:               begin
+519:                 @options[:reader].call(obj,xml)
+520:               rescue ArgumentError
+521:                 @options[:reader].call(obj,xml,self.method(:default_xml_to_obj))
+522:               end
+523:             end
+
+
+
+
+ +

Public Instance methods

+ +
+ + + + +
+

+tell whether this node’s data is present in obj (when this +method is called, obj will be an instance of the mapping class +this node was defined in). This method is currently used only by ChoiceNode when writing data back to XML. See ChoiceNode#obj_to_xml. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 580
+580:       def is_present_in? obj
+581:         true
+582:       end
+
+
+
+
+ +
+ + + + +
+

+Called when a new instance of the mapping class this node belongs to is +being initialized. obj is the instance. mapping is the +mapping the initialization is happening with, if any: If the instance is +being initialized as part of e.g. Class.load_from_file(name, +:mapping=>:some_mapping or any other call that specifies a mapping, +that mapping will be passed to this method. If the instance is being +initialized normally with Class.new, mapping is nil here. +

+

+You may set up initial values for the attributes this node is responsible +for here. Default implementation is empty. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 573
+573:       def obj_initializing(obj,mapping)
+574:       end
+
+
+
+
+ +
+ + + + +
+

+This is called by the XML unmarshalling +machinery when the state of an instance of this node’s @owner is to +be stored into an XML tree. obj is +the instance, xml is the tree (a REXML::Element). The node must +extract "its" data from obj and store it to the +corresponding parts (sub-elements, attributes etc.) of xml (using +XML::XXPath or any other means). +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 558
+558:       def obj_to_xml(obj,xml)
+559:         raise "abstract method called"
+560:       end
+
+
+
+
+ +
+ + + + +
+

+This is called by the XML unmarshalling +machinery when the state of an instance of this node’s @owner is to +be read from an XML tree. obj is the +instance, xml is the tree (a REXML::Element). The node must read +"its" data from xml (using XML::XXPath or any other means) and store it to +the corresponding parts (attributes etc.) of obj’s state. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 548
+548:       def xml_to_obj(obj,xml)
+549:         raise "abstract method called"
+550:       end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/Mapping/NumericNode.html b/classes/XML/Mapping/NumericNode.html new file mode 100644 index 0000000..2124767 --- /dev/null +++ b/classes/XML/Mapping/NumericNode.html @@ -0,0 +1,167 @@ + + + + + + Class: XML::Mapping::NumericNode + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassXML::Mapping::NumericNode
In: + + lib/xml/mapping/standard_nodes.rb + +
+
Parent: + + SingleAttributeNode + +
+
+ + +
+ + + +
+ +
+

+Node factory function synopsis: +

+
+  numeric_node :_attrname_, _path_ [, :default_value=>_obj_]
+                                   [, :optional=>true]
+                                   [, :mapping=>_m_]
+
+

+Like TextNode, but interprets the XML node’s text as a number (Integer or +Float, depending on the nodes’s text) and maps it to an Integer or +Float attribute. +

+ +
+ + +
+ +
+

Methods

+ +
+ new   +
+
+ +
+ + + + +
+ + + + + + + + + +
+

Public Class methods

+ +
+ + + + +
+

[Source]

+
+
+    # File lib/xml/mapping/standard_nodes.rb, line 46
+46:       def initialize(*args)
+47:         path,*args = super(*args)
+48:         @path = XML::XXPath.new(path)
+49:         args
+50:       end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/Mapping/ObjectNode.html b/classes/XML/Mapping/ObjectNode.html new file mode 100644 index 0000000..86b21f8 --- /dev/null +++ b/classes/XML/Mapping/ObjectNode.html @@ -0,0 +1,182 @@ + + + + + + Class: XML::Mapping::ObjectNode + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassXML::Mapping::ObjectNode
In: + + lib/xml/mapping/standard_nodes.rb + +
+
Parent: + + SubObjectBaseNode + +
+
+ + +
+ + + +
+ +
+

+Node factory function synopsis: +

+
+  object_node :_attrname_, _path_ [, :default_value=>_obj_]
+                                  [, :optional=>true]
+                                  [, :class=>_c_]
+                                  [, :marshaller=>_proc_]
+                                  [, :unmarshaller=>_proc_]
+                                  [, :mapping=>_m_]
+                                  [, :sub_mapping=>_sm_]
+
+

+Node that maps a subtree in the source XML to a Ruby object. :attrname and +path are again the attribute name resp. XPath expression of the +mapped attribute; the keyword arguments :default_value and +:optional are handled by the SingleAttributeNode superclass. The XML subnode named by path is mapped to +the attribute named by :attrname according to the keyword +arguments :class, :marshaller, and +:unmarshaller, which are handled by the SubObjectBaseNode superclass. +

+ +
+ + +
+ +
+

Methods

+ +
+ new   +
+
+ +
+ + + + +
+ + + + + + + + + +
+

Public Class methods

+ +
+ + + + +
+

+Initializer. path (a string denoting an XPath expression) is the +location of the subtree. +

+

[Source]

+
+
+     # File lib/xml/mapping/standard_nodes.rb, line 160
+160:       def initialize(*args)
+161:         path,*args = super(*args)
+162:         @path = XML::XXPath.new(path)
+163:         args
+164:       end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/Mapping/SingleAttributeNode.html b/classes/XML/Mapping/SingleAttributeNode.html new file mode 100644 index 0000000..520f787 --- /dev/null +++ b/classes/XML/Mapping/SingleAttributeNode.html @@ -0,0 +1,366 @@ + + + + + + Class: XML::Mapping::SingleAttributeNode + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassXML::Mapping::SingleAttributeNode
In: + + lib/xml/mapping/base.rb + +
+
Parent: + + Node + +
+
+ + +
+ + + +
+ +
+

+Base class for node types that map some XML +data to a single attribute of their mapping class. +

+

+All node types that come with xml-mapping except one (ChoiceNode) inherit from SingleAttributeNode. +

+ +
+ + +
+ +
+

Methods

+ + +
+ +
+ + + + +
+ +
+

Classes and Modules

+ + Class XML::Mapping::SingleAttributeNode::NoAttrValueSet
+ +
+ + + + + + + + +
+

Public Class methods

+ +
+ + + + +
+

+Initializer. owner is the owning mapping class (gets passed to the +superclass initializer and therefore put into @owner). The second parameter +(and hence the first parameter to the node factory method), +attrname, is a symbol that names the mapping class attribute this +node should map to. It gets stored into @attrname, and the attribute (an +r/w attribute of name attrname) is added to the mapping class (using +attr_accessor). +

+

+In the initializer, two option arguments — :optional and +:default_value — are processed in SingleAttributeNode: +

+

+Supplying :default_value=>obj makes obj the _default +value_ for this attribute. When unmarshalling (loading) an object from an +XML source, the attribute will be set to this +value if nothing was provided in the XML; when +marshalling (saving), the attribute won’t be saved if it is set to +the default value. +

+

+Providing just :optional=>true is equivalent to providing +:default_value=>nil. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 613
+613:       def initialize(*args)
+614:         @attrname,*args = super(*args)
+615:         @owner.add_accessor @attrname
+616:         if @options[:optional] and not(@options.has_key?(:default_value))
+617:           @options[:default_value] = nil
+618:         end
+619:         initialize_impl(*args)
+620:         args
+621:       end
+
+
+
+
+ +

Public Instance methods

+ +
+ + + + +
+

+utility method to be used by implementations of extract_attr_value. Calls the +supplied block, catching XML::XXPathError +and mapping it to NoAttrValueSet. This is +for the common case that an implementation considers an attribute value not +to be present in the XML if some specific +sub-path does not exist. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 703
+703:       def default_when_xpath_err # :yields:
+704:         begin
+705:           yield
+706:         rescue XML::XXPathError => err
+707:           raise NoAttrValueSet, "Attribute #{@attrname} not set (XXPathError: #{err})"
+708:         end
+709:       end
+
+
+
+
+ +
+ + + + +
+

+(to be overridden by subclasses) Extract and return the value of the +attribute this node is responsible for (@attrname) from xml. If +the implementation decides that the attribute value is "unset" in +xml, it should raise NoAttrValueSet in order +to initiate proper handling of possibly supplied :optional and +:default_value options (you may use default_when_xpath_err for this +purpose). +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 664
+664:       def extract_attr_value(xml)
+665:         raise "abstract method called"
+666:       end
+
+
+
+
+ +
+ + + + +
+

+this method was retained for compatibility with xml-mapping 0.8. +

+

+It used to be the initializer to be implemented by subclasses. The +arguments (args) are those still unprocessed by SingleAttributeNode’s +initializer. +

+

+In xml-mapping 0.9 and up, you should just override initialize() and call +super.initialize. The returned array is the same args array. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 630
+630:       def initialize_impl(*args)
+631:       end
+
+
+
+
+ +
+ + + + +
+

+(overridden) returns true if and only if the value of this node’s +attribute in obj is non-nil. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 712
+712:       def is_present_in? obj
+713:         nil != obj.send("#{@attrname}""#{@attrname}")
+714:       end
+
+
+
+
+ +
+ + + + +
+

+(to be overridden by subclasses) Write value, which is the current +value of the attribute this node is responsible for (@attrname), into (the +correct sub-nodes, attributes, whatever) of xml. +

+

[Source]

+
+
+     # File lib/xml/mapping/base.rb, line 685
+685:       def set_attr_value(xml, value)
+686:         raise "abstract method called"
+687:       end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/Mapping/SingleAttributeNode/NoAttrValueSet.html b/classes/XML/Mapping/SingleAttributeNode/NoAttrValueSet.html new file mode 100644 index 0000000..715764d --- /dev/null +++ b/classes/XML/Mapping/SingleAttributeNode/NoAttrValueSet.html @@ -0,0 +1,121 @@ + + + + + + Class: XML::Mapping::SingleAttributeNode::NoAttrValueSet + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassXML::Mapping::SingleAttributeNode::NoAttrValueSet
In: + + lib/xml/mapping/base.rb + +
+
Parent: + XXPathError +
+
+ + +
+ + + +
+ +
+

+Exception that may be used by implementations of extract_attr_value to +announce that the attribute value is not set in the XML and, consequently, the default value +should be set in the object being created, or an Exception be raised if no +default value was specified. +

+ +
+ + +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/Mapping/SubObjectBaseNode.html b/classes/XML/Mapping/SubObjectBaseNode.html new file mode 100644 index 0000000..87f21a5 --- /dev/null +++ b/classes/XML/Mapping/SubObjectBaseNode.html @@ -0,0 +1,221 @@ + + + + + + Class: XML::Mapping::SubObjectBaseNode + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassXML::Mapping::SubObjectBaseNode
In: + + lib/xml/mapping/standard_nodes.rb + +
+
Parent: + + SingleAttributeNode + +
+
+ + +
+ + + +
+ +
+

+(does somebody have a better name for this class?) base node class that +provides an initializer which lets the user specify a means to +marshal/unmarshal a Ruby object to/from XML. +Used as the base class for nodes that map some sub-nodes of their XML tree to (Ruby-)sub-objects of their +attribute. +

+ +
+ + +
+ +
+

Methods

+ +
+ new   +
+
+ +
+ + + + +
+ + + + + + + + + +
+

Public Class methods

+ +
+ + + + +
+

+processes the keyword arguments :class, :marshaller, and :unmarshaller +(args is ignored). When this initiaizer returns, @marshaller and +@unmarshaller are set to procs that marshal/unmarshal a Ruby object to/from +an XML tree according to the keyword arguments +that were passed to the initializer: +

+

+You either supply a :class argument with a class implementing XML::Mapping — in that case, the subtree +will be mapped to an instance of that class (using load_from_xml resp. +fill_into_xml). Or, you supply :marshaller and :unmarshaller arguments +specifying explicit unmarshaller/marshaller procs. The :marshaller proc +takes arguments xml,value and must fill value +(the object to be marshalled) into xml; the :unmarshaller proc +takes xml and must extract and return the object value from it. +Or, you specify none of those arguments, in which case the name of the +class to create will be automatically deduced from the root element name of +the XML node (see +XML::Mapping::load_object_from_xml, XML::Mapping::class_for_root_elt_name). +

+

+If both :class and :marshaller/:unmarshaller arguments are supplied, the +latter take precedence. +

+

[Source]

+
+
+     # File lib/xml/mapping/standard_nodes.rb, line 94
+ 94:       def initialize(*args)
+ 95:         args = super(*args)
+ 96: 
+ 97:         @sub_mapping = @options[:sub_mapping] || @mapping
+ 98:         @marshaller, @unmarshaller = @options[:marshaller], @options[:unmarshaller]
+ 99: 
+100:         if @options[:class]
+101:           unless @marshaller
+102:             @marshaller = proc {|xml,value|
+103:               value.fill_into_xml xml, :mapping=>@sub_mapping
+104:               if xml.unspecified?
+105:                 xml.name = value.class.root_element_name :mapping=>@sub_mapping
+106:                 xml.unspecified = false
+107:               end
+108:             }
+109:           end
+110:           unless @unmarshaller
+111:             @unmarshaller = proc {|xml|
+112:               @options[:class].load_from_xml xml, :mapping=>@sub_mapping
+113:             }
+114:           end
+115:         end
+116: 
+117:         unless @marshaller
+118:           @marshaller = proc {|xml,value|
+119:             value.fill_into_xml xml, :mapping=>@sub_mapping
+120:             if xml.unspecified?
+121:               xml.name = value.class.root_element_name :mapping=>@sub_mapping
+122:               xml.unspecified = false
+123:             end
+124:           }
+125:         end
+126:         unless @unmarshaller
+127:           @unmarshaller = proc {|xml|
+128:             XML::Mapping.load_object_from_xml xml, :mapping=>@sub_mapping
+129:           }
+130:         end
+131: 
+132:         args
+133:       end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/Mapping/TextNode.html b/classes/XML/Mapping/TextNode.html new file mode 100644 index 0000000..31e4a2c --- /dev/null +++ b/classes/XML/Mapping/TextNode.html @@ -0,0 +1,172 @@ + + + + + + Class: XML::Mapping::TextNode + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassXML::Mapping::TextNode
In: + + lib/xml/mapping/standard_nodes.rb + +
+
Parent: + + SingleAttributeNode + +
+
+ + +
+ + + +
+ +
+

+Node factory function synopsis: +

+
+  text_node :_attrname_, _path_ [, :default_value=>_obj_]
+                                [, :optional=>true]
+                                [, :mapping=>_m_]
+
+

+Node that maps an XML +node’s text (the element’s first child text node resp. the +attribute’s value) to a (string) attribute of the mapped object. +path (an XPath expression) locates the XML node, attrname (a symbol) names the +attribute. :default_value is the default value, :optional=>true +is equivalent to :default_value=>nil (see superclass documentation for +details). m is the mapping; it defaults to the current +default mapping +

+ +
+ + +
+ +
+

Methods

+ +
+ new   +
+
+ +
+ + + + +
+ + + + + + + + + +
+

Public Class methods

+ +
+ + + + +
+

[Source]

+
+
+    # File lib/xml/mapping/standard_nodes.rb, line 23
+23:       def initialize(*args)
+24:         path,*args = super(*args)
+25:         @path = XML::XXPath.new(path)
+26:         args
+27:       end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/MappingError.html b/classes/XML/MappingError.html new file mode 100644 index 0000000..a3a2d97 --- /dev/null +++ b/classes/XML/MappingError.html @@ -0,0 +1,111 @@ + + + + + + Class: XML::MappingError + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassXML::MappingError
In: + + lib/xml/mapping/base.rb + +
+
Parent: + RuntimeError +
+
+ + +
+ + + +
+ + + +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/XXPath.html b/classes/XML/XXPath.html new file mode 100644 index 0000000..72b368d --- /dev/null +++ b/classes/XML/XXPath.html @@ -0,0 +1,351 @@ + + + + + + Class: XML::XXPath + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassXML::XXPath
In: + + lib/xml/rexml_ext.rb + +
+ + lib/xml/xxpath.rb + +
+ + lib/xml/xxpath/steps.rb + +
+
Parent: + Object +
+
+ + +
+ + + +
+ +
+

+Instances of this class hold (in a pre-compiled form) an XPath pattern. You +call instance methods like each, first, all, +create_new on instances of this +class to apply the pattern to REXML elements. +

+ +
+ + +
+ +
+

Methods

+ +
+ all   + create_new   + each   + first   + new   +
+
+ +
+ + + + +
+ + + + + + + + + +
+

Public Class methods

+ +
+ + + + +
+

+create and compile a new XPath. xpathstr is the string +representation (XPath pattern) of the path +

+

[Source]

+
+
+    # File lib/xml/xxpath.rb, line 21
+21:     def initialize(xpathstr)
+22:       @xpathstr = xpathstr  # for error messages
+23: 
+24:       # TODO: write a real XPath parser sometime
+25: 
+26:       xpathstr='/'+xpathstr if xpathstr[0] != ?/
+27: 
+28:       @creator_procs = [ proc{|node,create_new| node} ]
+29:       @reader_proc = proc {|nodes| nodes}
+30:       
+31:       part=nil; part_expected=true
+32:       xpathstr.split(/(\/+)/)[1..-1].reverse.each do |x|
+33:         if part_expected
+34:           part=x
+35:           part_expected = false
+36:           next
+37:         end
+38:         part_expected = true
+39:         axis = case x
+40:                when '/'
+41:                  :child
+42:                when '//'
+43:                  :descendant
+44:                else
+45:                  raise XXPathError, "XPath (#{xpathstr}): unknown axis: #{x}"
+46:                end
+47:         axis=:self if axis==:child and (part[0]==?. or part=~/^self::/)  # yuck
+48: 
+49:         step = Step.compile(axis,part)
+50:         @creator_procs << step.creator(@creator_procs[-1])
+51:         @reader_proc = step.reader(@reader_proc, @creator_procs[-1])
+52:       end
+53:     end
+
+
+
+
+ +

Public Instance methods

+ +
+ + + + +
+

+Return an Enumerable with all sub-nodes of node that match this +XPath. Returns an empty Enumerable if no match was found. +

+

+If :ensure_created=>true is provided, all() ensures that a match exists +in node, creating one (and returning it as the sole element of the +returned enumerable) if none existed before. +

+

[Source]

+
+
+     # File lib/xml/xxpath.rb, line 88
+ 88:     def all(node,options={})
+ 89:       raise "options not a hash" unless Hash===options
+ 90:       if options[:create_new]
+ 91:         return [ @creator_procs[-1].call(node,true) ]
+ 92:       else
+ 93:         last_nodes,rest_creator = catch(:not_found) do
+ 94:           return @reader_proc.call([node])
+ 95:         end
+ 96:         if options[:ensure_created]
+ 97:           [ rest_creator.call(last_nodes[0],false) ]
+ 98:         else
+ 99:           []
+100:         end
+101:       end
+102:     end
+
+
+
+
+ +
+ + + + +
+

+create a completely new match of this XPath in base_node. +"Completely new" means that a new node will be created for each +path element, even if a matching node already existed in +base_node. +

+

+path.create_new(node) is equivalent to +path.first(node,:create_new=>true). +

+

[Source]

+
+
+     # File lib/xml/xxpath.rb, line 111
+111:     def create_new(base_node)
+112:       first(base_node,:create_new=>true)
+113:     end
+
+
+
+
+ +
+ + + + +
+

+loop over all sub-nodes of node that match this XPath. +

+

[Source]

+
+
+    # File lib/xml/xxpath.rb, line 57
+57:     def each(node,options={},&block)
+58:       all(node,options).each(&block)
+59:     end
+
+
+
+
+ +
+ + + + +
+

+the first sub-node of node that matches this XPath. If nothing +matches, raise XXPathError unless +:allow_nil=>true was provided. +

+

+If :ensure_created=>true is provided, first() ensures that a match +exists in node, creating one if none existed before. +

+

+path.first(node,:create_new=>true) is equivalent to +path.create_new(node). +

+

[Source]

+
+
+    # File lib/xml/xxpath.rb, line 69
+69:     def first(node,options={})
+70:       a=all(node,options)
+71:       if a.empty?
+72:         if options[:allow_nil]
+73:           nil
+74:         else
+75:           raise XXPathError, "path not found: #{@xpathstr}"
+76:         end
+77:       else
+78:         a[0]
+79:       end
+80:     end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/XXPath/Accessors/Attribute.html b/classes/XML/XXPath/Accessors/Attribute.html new file mode 100644 index 0000000..d275993 --- /dev/null +++ b/classes/XML/XXPath/Accessors/Attribute.html @@ -0,0 +1,304 @@ + + + + + + Class: XML::XXPath::Accessors::Attribute + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassXML::XXPath::Accessors::Attribute
In: + + lib/xml/rexml_ext.rb + +
+ + lib/xml/xxpath_methods.rb + +
+
Parent: + Object +
+
+ + +
+ + + +
+ +
+

+attribute node, more or less call-compatible with REXML’s Element. REXML’s Attribute +class doesn’t provide this… +

+

+The all/first calls return instances of this class if they matched an +attribute node. +

+ +
+ + +
+ +
+

Methods

+ +
+ ==   + new   + new   + text   + text=   +
+
+ +
+ + + +
+

Included Modules

+ + +
+ +
+ + + + + +
+

Attributes

+ +
+ + + + + + + + + + + + + + + + +
name [W] 
name [R] 
parent [R] 
+
+
+ + + + +
+

Public Class methods

+ +
+ + + + +
+

[Source]

+
+
+    # File lib/xml/rexml_ext.rb, line 79
+79:         def self.new(parent,name,create)
+80:           if parent.attributes[name]
+81:             super(parent,name)
+82:           else
+83:             if create
+84:               parent.attributes[name] = "[unset]"
+85:               super(parent,name)
+86:             else
+87:               nil
+88:             end
+89:           end
+90:         end
+
+
+
+
+ +
+ + + + +
+

[Source]

+
+
+    # File lib/xml/rexml_ext.rb, line 75
+75:         def initialize(parent,name)
+76:           @parent,@name = parent,name
+77:         end
+
+
+
+
+ +

Public Instance methods

+ +
+ + + + +
+

[Source]

+
+
+     # File lib/xml/rexml_ext.rb, line 101
+101:         def ==(other)
+102:           other.kind_of?(Attribute) and other.parent==parent and other.name==name
+103:         end
+
+
+
+
+ +
+ + + + +
+

+the value of the attribute. +

+

[Source]

+
+
+    # File lib/xml/rexml_ext.rb, line 93
+93:         def text
+94:           parent.attributes[@name]
+95:         end
+
+
+
+
+ +
+ + + + +
+

[Source]

+
+
+    # File lib/xml/rexml_ext.rb, line 97
+97:         def text=(x)
+98:           parent.attributes[@name] = x
+99:         end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/XXPath/Accessors/REXML.html b/classes/XML/XXPath/Accessors/REXML.html new file mode 100644 index 0000000..0689379 --- /dev/null +++ b/classes/XML/XXPath/Accessors/REXML.html @@ -0,0 +1,101 @@ + + + + + + Module: XML::XXPath::Accessors::REXML + + + + + + + + + + +
+ + + + + + + + + + +
ModuleXML::XXPath::Accessors::REXML
In: +
+
+ + +
+ + + +
+ + + +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/XXPath/Accessors/UnspecifiednessSupport.html b/classes/XML/XXPath/Accessors/UnspecifiednessSupport.html new file mode 100644 index 0000000..74a6ed3 --- /dev/null +++ b/classes/XML/XXPath/Accessors/UnspecifiednessSupport.html @@ -0,0 +1,207 @@ + + + + + + Module: XML::XXPath::Accessors::UnspecifiednessSupport + + + + + + + + + + +
+ + + + + + + + + + +
ModuleXML::XXPath::Accessors::UnspecifiednessSupport
In: + + lib/xml/rexml_ext.rb + +
+
+
+ + +
+ + + +
+ +
+

+we need a boolean "unspecified?" attribute for XML nodes — paths like "*" +oder (somewhen) "foo|bar" create "unspecified" nodes +that the user must then "specify" by setting their text etc. (or +manually setting unspecified=false) +

+

+This is mixed into the REXML::Element and XML::XXPath::Accessors::Attribute classes. +

+ +
+ + +
+ +
+

Methods

+ + +
+ +
+ + + + +
+ + + + + + + + + +
+

Public Class methods

+ +
+ + + + +
+

[Source]

+
+
+    # File lib/xml/rexml_ext.rb, line 28
+28:         def self.append_features(base)
+29:           return if base.included_modules.include? self # avoid aliasing methods more than once
+30:                                                         # (would lead to infinite recursion)
+31:           super
+32:           base.module_eval "alias_method :_text_orig, :text\nalias_method :_textis_orig, :text=\ndef text\n# we're suffering from the \"fragile base class\"\n# phenomenon here -- we don't know whether the\n# implementation of the class we get mixed into always\n# calls text (instead of just accessing @text or so)\nif unspecified?\n\"[UNSPECIFIED]\"\nelse\n_text_orig\nend\nend\ndef text=(x)\n_textis_orig(x)\nself.unspecified=false\nend\n\nalias_method :_nameis_orig, :name=\ndef name=(x)\n_nameis_orig(x)\nself.unspecified=false\nend\n"
+33:         end
+
+
+
+
+ +

Public Instance methods

+ +
+ + + + +
+

[Source]

+
+
+    # File lib/xml/rexml_ext.rb, line 24
+24:         def unspecified=(x)
+25:           @xml_xpath_unspecified = x
+26:         end
+
+
+
+
+ +
+ + + + +
+

[Source]

+
+
+    # File lib/xml/rexml_ext.rb, line 20
+20:         def unspecified?
+21:           @xml_xpath_unspecified ||= false
+22:         end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/XXPathError.html b/classes/XML/XXPathError.html new file mode 100644 index 0000000..031d598 --- /dev/null +++ b/classes/XML/XXPathError.html @@ -0,0 +1,111 @@ + + + + + + Class: XML::XXPathError + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassXML::XXPathError
In: + + lib/xml/xxpath.rb + +
+
Parent: + RuntimeError +
+
+ + +
+ + + +
+ + + +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/classes/XML/XXPathMethods.html b/classes/XML/XXPathMethods.html new file mode 100644 index 0000000..3ab15d9 --- /dev/null +++ b/classes/XML/XXPathMethods.html @@ -0,0 +1,276 @@ + + + + + + Module: XML::XXPathMethods + + + + + + + + + + +
+ + + + + + + + + + +
ModuleXML::XXPathMethods
In: + + lib/xml/xxpath_methods.rb + +
+
+
+ + +
+ + + +
+ +
+

+set of convenience wrappers around XML::XPath’s instance methods, for +people who frequently use XML::XPath directly. This module is included into +the REXML node classes and adds methods to them +that enable you to write a call like +

+
+  path.first(xml_element)
+
+

+as the more pleasant-looking variant +

+
+  xml_element.first(path)
+
+

+with the added bonus that path may not only be an XML::XXPath instance, but also just a String containing the XPath expression. +

+ +
+ + +
+ +
+

Methods

+ +
+ all   + create_new   + each_xpath   + first   + to_xxpath   +
+
+ +
+ + + + +
+ + + + + + + + + +
+

Public Instance methods

+ +
+ + + + +
+

+see XML::XXPath#all +

+

[Source]

+
+
+    # File lib/xml/xxpath_methods.rb, line 34
+34:     def all(path,options={})
+35:       to_xxpath(path).all self, options
+36:     end
+
+
+
+
+ +
+ + + + +
+

+see XML::XXPath#create_new +

+

[Source]

+
+
+    # File lib/xml/xxpath_methods.rb, line 39
+39:     def create_new(path)
+40:       to_xxpath(path).create_new self
+41:     end
+
+
+
+
+ +
+ + + + +
+

+see XML::XXPath#each +

+

[Source]

+
+
+    # File lib/xml/xxpath_methods.rb, line 23
+23:     def each_xpath(path,options={},&block)
+24:       # ^^^^ name "each" would clash with REXML::Element#each etc.
+25:       to_xxpath(path).each self, options, &block
+26:     end
+
+
+
+
+ +
+ + + + +
+

+see XML::XXPath#first +

+

[Source]

+
+
+    # File lib/xml/xxpath_methods.rb, line 29
+29:     def first(path,options={})
+30:       to_xxpath(path).first self, options
+31:     end
+
+
+
+
+ +
+ + + + +
+

[Source]

+
+
+    # File lib/xml/xxpath_methods.rb, line 44
+44:     def to_xxpath(path)
+45:       if String===path
+46:         XXPath.new path
+47:       else
+48:         path
+49:       end
+50:     end
+
+
+
+
+ + +
+ + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/created.rid b/created.rid new file mode 100644 index 0000000..5648c11 --- /dev/null +++ b/created.rid @@ -0,0 +1 @@ +Tue Aug 08 01:33:54 CEST 2006 diff --git a/files/ChangeLog.html b/files/ChangeLog.html new file mode 100644 index 0000000..a03b708 --- /dev/null +++ b/files/ChangeLog.html @@ -0,0 +1,273 @@ + + + + + + File: ChangeLog + + + + + + + + + + +
+

ChangeLog

+ + + + + + + + + +
Path:ChangeLog +
Last Update:Fri Mar 31 02:05:33 CEST 2006
+
+ + +
+ + + +
+ +
+

+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
+
+ +
+ + +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/files/README.html b/files/README.html new file mode 100644 index 0000000..890d744 --- /dev/null +++ b/files/README.html @@ -0,0 +1,2052 @@ + + + + + + File: README + + + + + + + + + + +
+

README

+ + + + + + + + + +
Path:README +
Last Update:Tue Aug 02 23:20:23 +0200 2011
+
+ + +
+ + + +
+ +
+

XML-MAPPING: XML-to-object (and back) Mapper for Ruby, including XPath Interpreter

+

+Xml-mapping is an easy to use, extensible library that allows you to +semi-automatically map Ruby objects to XML trees and vice versa. +

+

Download

+

+For downloading the latest version, CVS repository access etc. go to: +

+

+rubyforge.org/projects/xml-mapping/ +

+

Contents of this Document

+ +

Example

+

+(example document stolen + extended from www.castor.org/xml-mapping.html) +

+

Input Document:

+
+  <?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>
+      <UnitPrice>8.95</UnitPrice>
+    </Item>
+
+    <Item reference="RF-0034">
+      <Description>Chocolate</Description>
+      <Quantity>5</Quantity>
+      <UnitPrice>28.50</UnitPrice>
+    </Item>
+
+    <Item reference="RF-3341">
+      <Description>Cookie</Description>
+      <Quantity>30</Quantity>
+      <UnitPrice>0.85</UnitPrice>
+    </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 Declaration:

+
+  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, "UnitPrice"
+
+    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:0xb788f194 @client=#<Client:0xb788e9d8 @work_address=#<Address:0xb788c87c @state="CA", @city="San Francisco", @street="98765, Fulton Street", @zip=94102>, @home_address=#<Address:0xb788de48 @state="CA", @city="San Mateo", @street="2000, Alameda de las Pulgas", @zip=94403>, @name="Jean Smith">, @items={"RF-3341"=>#<Item:0xb7888c90 @quantity=30, @descr="Cookie", @unit_price=0.85>, "RF-0034"=>#<Item:0xb7889e4c @quantity=5, @descr="Chocolate", @unit_price=28.5>, "RF-0001"=>#<Item:0xb788b008 @quantity=10, @descr="Stuffed Penguin", @unit_price=8.95>}, @reference="12343-AHSHE-314159", @signatures=[#<Signature:0xb78875e8 @name="John Doe", @position="product manager">, #<Signature:0xb7886d78 @name="Jill Smith", @position="clerk">, #<Signature:0xb7886508 @name="Miles O'Brien", @position="Some Employee">]>
+  o.reference
+  => "12343-AHSHE-314159"
+  o.client
+  => #<Client:0xb788e9d8 @work_address=#<Address:0xb788c87c @state="CA", @city="San Francisco", @street="98765, Fulton Street", @zip=94102>, @home_address=#<Address:0xb788de48 @state="CA", @city="San Mateo", @street="2000, Alameda de las Pulgas", @zip=94403>, @name="Jean Smith">
+  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:0xb78875e8 @name="John Doe", @position="product manager">, #<Signature:0xb7886d78 @name="Jill Smith", @position="clerk">, #<Signature:0xb7886508 @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>
+          <UnitPrice>0.85</UnitPrice>
+        </Item>
+        <Item reference='RF-0034'>
+          <Description>Chocolate</Description>
+          <Quantity>5</Quantity>
+          <UnitPrice>28.5</UnitPrice>
+        </Item>
+        <Item reference='RF-0001'>
+          <Description>Stuffed Penguin</Description>
+          <Quantity>10</Quantity>
+          <UnitPrice>8.95</UnitPrice>
+        </Item>
+        <Item reference='RF-4711'>
+          <Description>power transfer grid</Description>
+          <Quantity>2</Quantity>
+          <UnitPrice>29.95</UnitPrice>
+        </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:0xb786ee58 @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:672:in `obj_to_xml'
+        from ../lib/xml/../xml/mapping/base.rb:210:in `fill_into_xml'
+        from ../lib/xml/../xml/mapping/base.rb:209:in `fill_into_xml'
+        from ../lib/xml/../xml/mapping/base.rb:221: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
+
+  xml=o.save_to_xml
+  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>
+          <UnitPrice>299.95</UnitPrice>
+        </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_name for details.
+
+

+As shown in the example, you have to include XML::Mapping into a class to turn it +into a "mapping class". There are no other restrictions imposed +on mapping classes; you can add attributes and methods to them, include +additional modules in them, derive them from other classes, derive other +classes from them etc.pp. +

+

+An instance of a mapping class can be created from/converted into an XML node with methods like XML::Mapping::ClassMethods.load_from_xml, +XML::Mapping#save_to_xml, +XML::Mapping::ClassMethods.load_from_file, +XML::Mapping#save_to_file. +Special class methods like "text_node", "array_node" +etc., called node factory methods, may be called from +the body of the class definition to define instance attributes that are +automatically and bidirectionally mapped to subtrees of the XML element an instance of the class is +mapped to. +

+

Single-attribute Nodes

+

+For example, in the definition +

+
+  class Address
+    include XML::Mapping
+
+    text_node :city, "City"
+    text_node :state, "State"
+    numeric_node :zip, "ZIP"
+    text_node :street, "Street"
+  end
+
+

+the first call to text_node creates an attribute named "city" +which is mapped to the text of the XML +child element defined by the XPath expression "City" (xml-mapping +includes an XPath interpreter that can also be used seperately; see below). When you create an instance of Address +from an XML element (using +Address.load_from_file(file_name) or Address.load_from_xml(rexml_element)), +that instance’s "city" attribute will be set to the text of +the XML element’s "City" +child element. When you convert an instance of Address into an XML element, a sub-element "City" +is added and its text is set to the current value of the city +attribute. The other node types (numeric_node, array_node etc.) work +analogously. Generally said, when an instance of the above Address +class is created from or converted to an XML tree, each of the four nodes in the +class maps some parts of that XML tree to +a single, specific attribute of the Adress instance. The name of +that attribute is given in the first argument to the node factory method. +Such a node is called a "single-attribute node". All node types +that come with xml-mapping except one (choice_node, which +I’ll talk about below) are single-attribute nodes. +

+

Default Values

+

+For each single-attribute node you may define a default value +which will be set if there was no value defined for the attribute in the XML source. +

+

+From the example: +

+
+  class Signature
+    include XML::Mapping
+
+    text_node :position, "Position", :default_value=>"Some Employee"
+  end
+
+

+The semantics of default values are as follows: +

+
    +
  • when creating a new instance from scratch: + +
      +
    • attributes with default values are set to their default values + +
    • +
    • attributes without default values are left unset + +
    • +
    +

    +(when defining your own initializer, you’ll have to call the +inherited initialize method in order to get this behaviour) +

    +
  • +
  • when loading an instance from an XML +document: + +
      +
    • attributes without default values that are not represented in the XML raise an error + +
    • +
    • attributes with default values that are not represented in the XML are set to their default values + +
    • +
    • all other attributes are set to their respective values as present in the +XML + +
    • +
    +
  • +
  • when saving an instance to an XML +document: + +
      +
    • unset attributes without default values raise an error + +
    • +
    • attributes with default values that are set to their default values are not +saved + +
    • +
    • all other attributes are saved + +
    • +
    +
  • +
+

+This implies that: +

+
    +
  • attributes that are set to their respective default values are not +represented in the XML + +
  • +
  • attributes without default values must be set explicitly before saving + +
  • +
+

Single-attribute Nodes with Sub-objects

+

+Single-attribute nodes of type array_node, hash_node, and +object_node recursively map one or more subtrees of their XML to sub-objects (e.g. array elements or +hash values) of their attribute. For example, with the line +

+
+  array_node :signatures, "Signed-By", "Signature", :class=>Signature, :default_value=>[]
+
+

+, an attribute named "signatures" is added to the surrounding +class (here: Order); the attribute will be an array whose elements +correspond to the XML sub-trees yielded +by the XPath expression "Signed-By/Signature" (relative to the +tree corresponding to the Order instance). Each element will be of +class Signature (internally, each element is created from its +corresponding XML subtree by just calling +Signature.load_from_xml(the_subtree)). The reason why the path +"Signed-By/Signature" is provided in two arguments instead of +just one combined one becomes apparent when marshalling the array (along +with the surrounding Order object) back into a sequence of XML elements. When that happens, +"Signed-By" names the common base element for all those elements, +and "Signature" is the path that will be duplicated for each +element. For example, when the signatures attribute contains an +array with 3 Signature instances (let’s call them +sig1, sig2, and sig3) in it, it will be +marshalled to an XML tree that looks like +this: +

+
+  <Signed-By>
+    <Signature>
+      [marshalled object sig1]
+    </Signature>
+    <Signature>
+      [marshalled object sig2]
+    </Signature>
+    <Signature>
+      [marshalled object sig3]
+    </Signature>
+  </Signed-By>
+
+

+Internally, each Signature instance is stored into its +<Signature> sub-element by calling +the_signature_instance.fill_into_xml(the_sub_element). The input +document in the example above shows how this ends up looking. +

+

+hash_nodes work similarly, but they define hash-valued attributes +instead of array-valued ones. +

+

+object_nodes are the simplest of the three types of +single-attribute nodes with sub-objects. They just map a single given +subtree directly to their attribute value. See the example for examples :) +

+

+The mentioned methods load_from_xml and fill_into_xml are +the only methods classes must implement in order to be usable in the +:class=> keyword arguments to node factory methods. Mapping +classes (i.e. classes that include XML::Mapping) automatically +inherit those functions and can thus be readily used in +:class=> arguments, as shown for the Signature class +in the array_node call above. In addition to that, xml-mapping +adds those methods to some of Ruby’s core classes, namely String and Numeric (and thus Float, +Integer, and BigInt). So you can also use strings or +numbers as sub-objects of attributes of array_node, +hash_node, or object_node nodes. For example, say you +have an XML document like this one: +

+
+  <?xml version="1.0" encoding="ISO-8859-1"?>
+
+  <people>
+    <names>
+      <name>Jim</name>
+      <name>Susan</name>
+      <name>Herbie</name>
+      <name>Nancy</name>
+    </names>
+  </people>
+
+

+, and you want to map all the names to a string array attribute +names, you could do it like this: +

+
+  require 'xml/mapping'
+  class People
+    include XML::Mapping
+    array_node :names, "names", "name", :class=>String
+  end
+
+

+usage: +

+
+  ppl=People.load_from_file("stringarray.xml")
+  => #<People:0xb7bd8174 @names=["Jim", "Susan", "Herbie", "Nancy"]>
+  ppl.names
+  => ["Jim", "Susan", "Herbie", "Nancy"]
+
+  ppl.names.concat ["Mary","Arnold"]
+  => ["Jim", "Susan", "Herbie", "Nancy", "Mary", "Arnold"]
+  ppl.save_to_xml.write $stdout,2
+
+  <people>
+        <names>
+          <name>Jim</name>
+          <name>Susan</name>
+          <name>Herbie</name>
+          <name>Nancy</name>
+          <name>Mary</name>
+          <name>Arnold</name>
+        </names>
+      </people>
+
+

+As a side node, this feature actually makes text_node and +numeric_node special cases of object_node. For example, +text_node :attr, "path" is the same as object_node +:attr, "path", :class=>String. +

+

Polymorphic Sub-objects, Marshallers/Unmarshallers

+

+Besides the :class keyword argument, there are alternative ways +for a single-attribute node with sub-objects to specify the way the +sub-objects are created from/marshalled into their subtrees. +

+

+First, it’s possible not to specify anything at all — in that +case, the class of a sub-object will be automatically deduced from the root +element name of its subtree. This allows you to achieve a kind of +"polymorphic", late-bound way to decide about the +sub-object’s class. The following example document contains a +hierarchical, recursive set of named "documents" and +"folders", where folders hold a set of entries, each of which may +again be either a document or a folder: +

+
+  <?xml version="1.0" encoding="ISO-8859-1"?>
+
+  <folder name="home">
+    <document name="plan">
+      <contents> inhale, exhale</contents>
+    </document>
+
+    <folder name="work">
+      <folder name="xml-mapping">
+        <document name="README">
+          <contents>foo bar baz</contents>
+        </document>
+      </folder>
+    </folder>
+
+  </folder>
+
+

+This can be mapped to Ruby like this: +

+
+  require 'xml/mapping'
+
+  class Entry
+    include XML::Mapping
+
+    text_node :name, "@name"
+  end
+
+  class Document <Entry
+    include XML::Mapping
+
+    text_node :contents, "contents"
+  end
+
+  class Folder <Entry
+    include XML::Mapping
+
+    array_node :entries, "document|folder", :default_value=>[]
+
+    def [](name)
+      entries.select{|e|e.name==name}[0]
+    end
+
+    def append(name,entry)
+      entries << entry
+      entry.name = name
+      entry
+    end
+  end
+
+

+Usage: +

+
+  root = XML::Mapping.load_object_from_file "documents_folders.xml"
+  => #<Folder:0xb7a2b584 @entries=[#<Document:0xb7a2ab5c @contents=" inhale, exhale", @name="plan">, #<Folder:0xb7a2a238 @entries=[#<Folder:0xb7a29900 @entries=[#<Document:0xb7a28ff0 @contents="foo bar baz", @name="README">], @name="xml-mapping">], @name="work">], @name="home">
+  root.name
+  => "home"
+  root.entries
+  => [#<Document:0xb7a2ab5c @contents=" inhale, exhale", @name="plan">, #<Folder:0xb7a2a238 @entries=[#<Folder:0xb7a29900 @entries=[#<Document:0xb7a28ff0 @contents="foo bar baz", @name="README">], @name="xml-mapping">], @name="work">]
+
+  root.append "etc", Folder.new
+  root["etc"].append "passwd", Document.new
+  root["etc"]["passwd"].contents = "foo:x:2:2:/bin/sh"
+  root["etc"].append "hosts", Document.new
+  root["etc"]["hosts"].contents = "127.0.0.1 localhost"
+
+  xml = root.save_to_xml
+  => <folder name='home'> ... </>
+  xml.write $stdout,2
+
+  <folder name='home'>
+        <document name='plan'>
+          <contents> inhale, exhale</contents>
+        </document>
+        <folder name='work'>
+          <folder name='xml-mapping'>
+            <document name='README'>
+              <contents>foo bar baz</contents>
+            </document>
+          </folder>
+        </folder>
+        <folder name='etc'>
+          <document name='passwd'>
+            <contents>foo:x:2:2:/bin/sh</contents>
+          </document>
+          <document name='hosts'>
+            <contents>127.0.0.1 localhost</contents>
+          </document>
+        </folder>
+      </folder>
+
+

+As you see, the Folder#entries attribute is mapped via an +array_node that does not specify a :class or anything else to +govern the instantiation of the array’s elements. This causes +xml-mapping to deduce the class of each array element from the root element +name of the corresponding XML tree. In +this example, the root element name is either "document" or +"folder". The mapping between root element names and class names +is the one briefly described in example at the +beginning of this document — the unqualified class name is just +converted to lower case and "dashed", e.g. Foo::Bar::MyClass +becomes "my-class"; and you may overwrite this on a per-class +basis by calling root_element_name "the-new-name" in the +class body. In our example, the root element name "document" +leads to an instantiation of class Document, and the root element +name "folder" leads to an instantiation of class Folder. +

+

+Incidentally, the last example shows that you can readily derive mapping +classes from one another (as said before, you can also derive mapping +classes from other classes, include other modules into them etc. at will). +This works just like intuition thinks it should — when deriving one +mapping class from another one, the list of nodes in effect when +loading/saving instances of the derived class will consist of all nodes of +that class and all superclasses, starting with the topmost superclass that +has nodes defined. There is one thing to take care of though: When deriving +mapping classes from one another, you have to make sure to include XML::Mapping in each class. +This requirement exists purely due to ease-of-implementation +considerations; there are probably ways to do away with it, but the +inconvenience seemed not severe enough for me to bother (as yet). Still, +you might get "strange" errors if you forget to do it for a +class. +

+

+Besides the :class keyword argument and no argument, there is a +third way to specify the way the sub-objects are created from/marshalled +into their subtrees: :marshaller and/or :unmarshaller +keyword arguments. Here you pass procs in which you just do all the work +manually. So this is basically a "catch-all" for cases where the +other two alternatives are not appropriate for the problem at hand. +(TODO: Use other example?) Let’s say we want to extend the +Signature class from the initial example to include the date on +which the signature was created. We want the new XML representation of such a signature to +look like this: +

+
+  <Signature>
+    <Name>John Doe</Name>
+    <Position>product manager</Position>
+    <signed-on>
+      <day>13</day>
+      <month>2</month>
+      <year>2005</year>
+    </signed-on>
+  </Signature>
+
+

+So, a new "signed-on" element was added that holds the day, +month, and year. In the Signature instance in Ruby, we want the +date to be stored in an attribute named signed_on of type +Time (that’s Ruby’s built-in Time class). +

+

+One could think of using object_node, but something like +object_node :signed_on, "signed-on", :class=>Time +won’t work because Time isn’t a mapping class and +doesn’t define methods load_from_xml and +fill_into_xml (we could easily define those though; we’ll +talk about that possibility here and here). The fastest, most ad-hoc way to achieve +what we want are :marshaller and :unmarshaller keyword arguments, like +this: +

+
+  require 'xml/mapping'
+  require 'xml/xxpath_methods'
+
+  class Signature
+    include XML::Mapping
+
+    text_node :name, "Name"
+    text_node :position, "Position", :default_value=>"Some Employee"
+    object_node :signed_on, "signed-on",
+                :unmarshaller=>proc{|xml|
+                                 y,m,d = [xml.first("year").text.to_i,
+                                          xml.first("month").text.to_i,
+                                          xml.first("day").text.to_i]
+                                 Time.local(y,m,d)
+                               },
+                :marshaller=>proc{|xml,value|
+                               e = xml.elements.add; e.name = "year"; e.text = value.year
+                               e = xml.elements.add; e.name = "month"; e.text = value.month
+                               e = xml.elements.add; e.name = "day"; e.text = value.day
+
+                               # xml.first("year",:ensure_created=>true).text = value.year
+                               # xml.first("month",:ensure_created=>true).text = value.month
+                               # xml.first("day",:ensure_created=>true).text = value.day
+                             }
+  end
+
+

+The :unmarshaller proc will be called whenever a +Signature instance is being read in from an XML source. The xml argument passed +to the proc contains (as a REXML::Element instance) the XML subtree corresponding to the +node’s attribute’s sub-object currently being read. In the case +of our object_node, the sub-object is just the node’s +attribute (signed_on) itself, and the subtree is the one rooted at +the <signed-on> element (if this were e.g. an array_node, +the :unmarshaller proc would be called once for each array +element, and xml would hold the subtree corresponding to the +"current" array element). The proc is expected to extract the +sub-object’s data from xml and return the sub-object. So we +have to read the "year", "month", and "day" +elements, construct a Time instance from them and return that. One +could just use the REXML API to do +that, but I’ve decided here to use the XPath interpreter that comes +with xml-mapping (xml/xxpath), and specifically the +‘xml/xxpath_methods’ utility library that adds methods like +first to REMXML::Element. We call first on xml +three times, passing XPath expressions to extract the +"year"/"month"/"day" sub-elements, construct +the Time instance from that and return it. The XPath library is +explained in more detail below. +

+

+The :marshaller proc will be called whenever a Signature +instance is being written into an XML +tree. xml is again the XML +subtree rooted at the <signed-on> element (it will still be empty +when this proc is called), and value is the current value of the +sub-object (again, since this is an object_node, value is +the node’s attribute, i.e. the Time instance). We have to +fill xml with the data from value here. So we add three +elements "year", "month" and "day" and set +their texts to the corresponding values from value. The +commented-out code shows an alternative implementation of the same thing +using the XPath interpreter. +

+

+It should be mentioned again that :marshaller/:unmarshaller procs are +possible with all single-attribute nodes with sub-objects, i.e. with +object_node, array_node, and hash_node. So, if +you wanted to map a whole array of date values, you could use +array_node with the same :marshaller/:unmarshaller procs as above, +for example: +

+
+  array_node :birthdays, "birthdays", "birthday",
+             :unmarshaller=> <as above>,
+             :marshaller=> <as above>
+
+

+You can see that :marshaller/:unmarshaller procs give you more flexibility, +but they also impose more work because you essentially have to do all the +work of marshalling/unmarshalling the sub-objects yourself. If you find +yourself copying and pasting marshaller/unmarshaller procs all over the +place, you should instead define your own node type or mix the +marshalling/unmarshalling capabilities into the Time class itself. +This is explained here and here, and you’ll see that it’s not +really much more work than writing :marshaller and :unmarshaller procs (you +essentially just move the code from those procs into your own node type +resp. into the Time class), so you should not hesitate to do this. +

+

+Another thing worth mentioning is that you don’t have to specify +both a :marshaller and an :unmarshaller simultaneously. You can as +well give only one of them, and in addition to that pass a :class +argument or no argument. When you do that, the specified marshaller (or +unmarshaller) will be used when marshalling (resp. unmarshalling) the +sub-objects, and the other passed argument (:class or none) will +be employed when unmarshalling (resp. marshalling) the sub-objects. So, in +effect, you can deactivate or "short-cut" some part of the +marshalling/unmarshalling functionality of a node type while retaining +another part. +

+

Attribute Handling Details, Augmenting Existing Classes

+

+I’ll shed some more light on how single-attribute nodes add mapped +attributes to Ruby classes. An attribute declaration like +

+
+  text_node :city, "City"
+
+

+maps some portion of the XML tree (here: +the "City" sub-element) to an attribute (here: "city") +of the class whose body the declaration appears in. When writing +(marshalling) instances of the surrounding class into an XML document, xml-mapping will read the +attribute value from the instance using the function named city; +when reading (unmarshalling) an instance from an XML document, xml-mapping will use the +one-parameter function city= to set the attribute in the instance +to the value read from the XML document. +

+

+If these functions don’t exist at the time the node declaration is +executed, xml-mapping adds default implementations that simply read/write +the attribute value to instance variables that have the same name as the +attribute. For example, the city attribute declaration in the +Address class in the example added functions city and +city= that read/write from/to the instance variable +@city. +

+

+If, however, these functions already exist prior to defining the +attributes, xml-mapping will leave them untouched, so your precious +self-written accessor methods that do whatever complicated internal +processing of the data won’t be overwritten. +

+

+This means that you can not only create new mapping classes from scratch, +you can also take existing classes that contain some "business +logic" and "augment" them with xml-mapping capabilities. As +a simple example, let’s augment Ruby’s "Time" class +with node declarations that declare XML +mappings for the day, month etc. fields: +

+
+  class Time
+    include XML::Mapping
+
+    numeric_node :year, "year"
+    numeric_node :month, "month"
+    numeric_node :day, "mday"
+    numeric_node :hour, "hours"
+    numeric_node :min, "minutes"
+    numeric_node :sec, "seconds"
+  end
+
+  nowxml=Time.now.save_to_xml
+  => <time> ... </>
+  nowxml.write($stdout,2)
+  <time>
+        <year>2006</year>
+        <month>8</month>
+        <mday>8</mday>
+        <hours>0</hours>
+        <minutes>36</minutes>
+        <seconds>27</seconds>
+      </time>
+
+

+Here XML mappings are defined for the +existing fields year, month etc. Xml-mapping noticed that +the getter methods for those attributes existed, so it didn’t +overwrite them. When calling save_to_xml on a Time +object, these methods are called and return the object’s values for +those fields, which then get written to the output XML. +

+

+So you can convert Time objects into XML trees. What about reading them back in +from XML? All XML reading operations go through +<Class>.load_from_xml. The load_from_xml class +method inherited from XML::Mapping (see XML::Mapping::ClassMethods#load_from_xml) +allocates a new instance of the class (Time), then calls +fill_from_xml (i.e. XML::Mapping#fill_from_xml) +on it. fill_from_xml iterates over all our nodes in the order of +their definition. For each node, its data (the <year>, or +<month>, or <day> etc. element) is read from the XML source and then written to the +Time instance via the respective setter method (year=, +month=, day= etc.). These methods didn’t exist in +Time before (Time objects are immutable), so xml-mapping +defined its own, default setter methods that just set @year, +@month etc. This is of course pretty useless because Time +objects don’t hold their time in these variables, so the setter +methods don’t really change the time of the Time object. So +we have to redefine load_from_xml for the Time class: +

+
+  def Time.load_from_xml(xml, options={:mapping=>:_default})
+    year,month,day,hour,min,sec =
+      [xml.first("year").text.to_i,
+       xml.first("month").text.to_i,
+       xml.first("mday").text.to_i,
+       xml.first("hours").text.to_i,
+       xml.first("minutes").text.to_i,
+       xml.first("seconds").text.to_i]
+    Time.local(year,month,day,hour,min,sec)
+  end
+
+

Other Nodes

+

+All nodes I’ve shown so far (node types text_node, numeric_node, +boolean_node, object_node, array_node, and hash_node) were single-attribute +nodes: The first parameter to the node factory method of such a node is an +attribute name, and the attribute of that name is the only piece of the +state of instances of the node’s mapping class that gets read/written +by the node. +

+

choice_node

+

+There is one node type distributed with xml-mapping that is not a +single-attribute node: choice_node. A choice_node allows +you to specify a sequence of pairs, each consisting of an XPath expression +and another node (any node is supported here, including other +choice_nodes). When reading in an XML +source, the choice_node will delegate the work to the first node in the +sequence whose corresponding XPath expression was matched in the XML. When writing an object back to XML, the choice_node will delegate the work +to the first node whose data was "present" in the object (for +single-attribute nodes, the data is considered "present" if the +node’s attribute is non-nil; for choice_nodes, the data is considered +"present" if at least one of the node’s sub-nodes is +"present"). +

+

+As a (somewhat contrived) example, here’s a mapping for +Publication objects that have either a single author (contained in +an "author" XML attribute) or +several "contributors" (contained in a sequence of +"contr" XML elements): +

+
+  class Publication
+    include XML::Mapping
+
+    choice_node :if,    '@author', :then, (text_node :author, '@author'),
+                :elsif, 'contr',   :then, (array_node :contributors, 'contr', :class=>String)
+  end
+
+  ## usage
+
+  p1 = Publication.load_from_xml(REXML::Document.new('<publication author="Jim"/>').root)
+  => #<Publication:0xb78b0088 @author="Jim">
+
+  p2 = Publication.load_from_xml(REXML::Document.new('
+  <publication>
+    <contr>Chris</contr>
+    <contr>Mel</contr>
+    <contr>Toby</contr>
+  </publication>').root)
+  => #<Publication:0xb78ae38c @contributors=["Chris", "Mel", "Toby"]>
+
+

+The symbols :if, :then, and :elsif (but not :else — see below) in the +choice_node’s node factory method call are ignored; they may +be sprinkled across the argument list at will (preferably the way shown +above of course) to increase readability. +

+

+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 (@author 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 sequence whose +data is "present" in the source object will be invoked to write +data from the object into the target XML +tree (and the corresponding XPath expression will be created in the XML tree if it doesn’t exist already). +If there is no such node in the sequence, an exception is raised. As said +above, for single-attribute nodes, the node’s data is considered +"present" if the node’s attribute is non-nil. So, if you +write a Publication object to XML, and either the author or the +contributors attribute of the object is set, it will be written; +if both attributes are nil, an exception will be raised. +

+

+A frequent use case for choice_nodes will probably be object attributes +that may be represented in multiple alternative ways in XML. As an example, consider +"Person" objects where the name of the person should be stored +alternatively in a sub-element named name, or an attribute named +name, or in the text of the person element itself. You +can achieve this with choice_node like this: +

+
+  class Person
+    include XML::Mapping
+
+    choice_node :if,    'name',  :then, (text_node :name, 'name'),
+                :elsif, '@name', :then, (text_node :name, '@name'),
+                :else,  (text_node :name, '.')
+  end
+
+  ## usage
+
+  p1 = Person.load_from_xml(REXML::Document.new('<person name="Jim"/>').root)
+  => #<Person:0xb78bd940 @name="Jim">
+
+  p2 = Person.load_from_xml(REXML::Document.new('<person><name>James</name></person>').root)
+  => #<Person:0xb78bc4a0 @name="James">
+
+  p3 = Person.load_from_xml(REXML::Document.new('<person>Suzy</person>').root)
+  => #<Person:0xb78bb320 @name="Suzy">
+
+  p1.save_to_xml.write($stdout)
+  <person><name>Jim</name></person>
+  p2.save_to_xml.write($stdout)
+  <person><name>James</name></person>
+  p3.save_to_xml.write($stdout)
+  <person><name>Suzy</name></person>
+
+

+Here all sub-nodes of the choice_nodes are single-attribute nodes +(text_nodes) with the same attribute (name). As you see, when +writing persons to XML, the name is +always stored in a <name> sub-element. Of course, this is because +that alternative appears first in the choice_node. +

+

Readers/Writers

+

+Finally, all nodes support keyword arguments :reader and :writer +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: +

+
+  class Foo
+    include XML::Mapping
+
+    text_node :name, "@name", :reader=>proc{|obj,xml,default_reader|
+                                         default_reader.call(obj,xml)
+                                         obj.name += xml.attributes['more']
+                                       },
+                              :writer=>proc{|obj,xml|
+                                         xml.attributes['bar'] = "hi #{obj.name} ho"
+                                       }
+  end
+
+  f = Foo.load_from_xml(REXML::Document.new('<foo name="Jim" more="XYZ"/>').root)
+  => #<Foo:0xb789731c @name="JimXYZ">
+
+  xml = f.save_to_xml
+  xml.write $stdout,2
+  <foo bar='hi JimXYZ ho'/>
+
+

+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 the XML tree <foo name="Jim" +more="XYZ"/> 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 do that here—we +completely 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, and it generally just means that you move +the (sensibly parameterized) code from your readers/writers to your node +types. +

+

Multiple Mappings per Class

+

+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. :my_mapping, +:other_mapping 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 use_mapping +:the_mapping. 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 :_default. +

+

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

+

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

+
+  require 'xml/mapping'
+
+  class Address; end
+
+  class Person
+    include XML::Mapping
+
+    # the default mapping. Stores the name and age in XML attributes,
+    # and the address in a sub-element "address".
+
+    text_node :name, "@name"
+    numeric_node :age, "@age"
+    object_node :address, "address", :class=>Address
+
+    use_mapping :other
+
+    # the ":other" mapping. Non-default root element name; name and age
+    # stored in XML elements; address stored in the person's element
+    # itself
+
+    root_element_name "individual"
+    text_node :name, "name"
+    numeric_node :age, "age"
+    object_node :address, ".", :class=>Address
+
+    # you could also specify the mapping on a per-node basis with the
+    # :mapping option, e.g.:
+    #
+    # numeric_node :age, "age", :mapping=>:other
+  end
+
+  class Address
+    include XML::Mapping
+
+    # the default mapping.
+
+    text_node :street, "street"
+    numeric_node :number, "number"
+    text_node :city, "city"
+    numeric_node :zip, "zip"
+
+    use_mapping :other
+
+    # the ":other" mapping.
+
+    text_node :street, "street-name"
+    numeric_node :number, "street-name/@number"
+    text_node :city, "city-name"
+    numeric_node :zip, "city-name/@zip-code"
+  end
+
+  ## usage
+
+  # XML representation of a person in the default mapping
+  xml = REXML::Document.new('
+  <person name="Suzy" age="28">
+    <address>
+      <street>Abbey Road</street>
+      <number>72</number>
+      <city>London</city>
+      <zip>18827</zip>
+    </address>
+  </person>').root
+
+  # load using the default mapping
+  p = Person.load_from_xml xml
+  => #<Person:0xb787c10c @address=#<Address:0xb787b89c @city="London", @number=72, @street="Abbey Road", @zip=18827>, @name="Suzy", @age=28>
+
+  # save using the default mapping
+  xml2 = p.save_to_xml
+  xml2.write $stdout,2
+  <person name='Suzy' age='28'>
+        <address>
+          <street>Abbey Road</street>
+          <number>72</number>
+          <city>London</city>
+          <zip>18827</zip>
+        </address>
+      </person>
+  # xml2 identical to xml
+
+  # now, save the same person to XML using the :other mapping...
+  other_xml = p.save_to_xml :mapping=>:other
+  other_xml.write $stdout,2
+  <individual>
+        <name>Suzy</name>
+        <age>28</age>
+        <street-name number='72'>Abbey Road</street-name>
+        <city-name zip-code='18827'>London</city-name>
+      </individual>
+  # load it again using the :other mapping
+  p2 = Person.load_from_xml other_xml, :mapping=>:other
+  => #<Person:0xb7873f98 @address=#<Address:0xb7873610 @city="London", @number=72, @street="Abbey Road", @zip=18827>, @name="Suzy", @age=28>
+
+  # p2 identical to p
+
+

+In this example, each of the two mappings contains nodes that map the same +set of Ruby attributes (name, age and address). This is probably what you +want most of the time (since you’re normally defining multiple XML mappings for the same Ruby data), but +it’s not a necessity at all. When a mapping class is defined, +xml-mapping will add all Ruby attributes from all mappings to it. +

+

+You may have noticed that the object_nodes 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 +(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

+

+It’s easy to write additional node types and register them with the +xml-mapping library (the following node types come with xml-mapping: +node, text_node, numeric_node, +boolean_node, object_node, array_node, +hash_node, choice_node). +

+

+I’ll first show an example, then some more theoretical insight. +

+

Example

+

+Let’s say we want to extend the Signature class from the +example to include the time at which the signature was created. We want the +new XML representation of such a +signature to look like this: +

+
+  <Signature>
+    <Name>John Doe</Name>
+    <Position>product manager</Position>
+    <signed-on>
+      <day>13</day>
+      <month>2</month>
+      <year>2005</year>
+    </signed-on>
+  </Signature>
+
+

+(we only save year, month and day to make this example shorter), and the +mapping class declaration to look like this: +

+
+  class Signature
+    include XML::Mapping
+
+    text_node :name, "Name"
+    text_node :position, "Position", :default_value=>"Some Employee"
+    time_node :signed_on, "signed-on", :default_value=>Time.now
+  end
+
+

+(i.e. a new "time_node" declaration was added). +

+

+We want this time_node call to define an attribute named +signed_on which holds the date value from the XML document in an instance of class +Time. +

+

+This node type can be defined with this piece of code: +

+
+  require 'xml/mapping/base'
+
+  class TimeNode < XML::Mapping::SingleAttributeNode
+    def initialize(*args)
+      path,*args = super(*args)
+      @y_path = XML::XXPath.new(path+"/year")
+      @m_path = XML::XXPath.new(path+"/month")
+      @d_path = XML::XXPath.new(path+"/day")
+      args
+    end
+
+    def extract_attr_value(xml)
+      y,m,d = default_when_xpath_err{ [@y_path.first(xml).text.to_i,
+                                       @m_path.first(xml).text.to_i,
+                                       @d_path.first(xml).text.to_i]
+                                    }
+      Time.local(y,m,d)
+    end
+
+    def set_attr_value(xml, value)
+      @y_path.first(xml,:ensure_created=>true).text = value.year
+      @m_path.first(xml,:ensure_created=>true).text = value.month
+      @d_path.first(xml,:ensure_created=>true).text = value.day
+    end
+  end
+
+  XML::Mapping.add_node_class TimeNode
+
+

+The last line registers the new node type with the xml-mapping library. The +name of the node factory method ("time_node") is automatically +derived from the class name of the node type ("TimeNode"). +

+

+There will be one instance of the node type TimeNode per +time_node declaration per mapping class (not per mapping class +instance). That instance (the "node" for short) will be created +by the node factory method (time_node); there’s no need to +instantiate the node type directly. The time_node method places +the node into the mapping class; the @owner attribute of the node is set to +reference the mapping class. The node factory method passes the mapping +class the node appears in (Signature), followed by its own +arguments, to the node’s constructor. In the example, the +time_node method calls TimeNode.new(Signature, :signed_on, +"signed-on", :default_value=>Time.now)). new of +course creates the node and then delegates the arguments to our initializer +initialize. We first call the superclass’s initializer, +which strips off from the argument list those arguments it handles itself, +and returns the remaining ones. In this case, the superclass XML::Mapping::SingleAttributeNode +handles the Signature, :signed_on and +:default_value=>Time.now arguments — Signature +is stored into @owner, :signed_on is stored into +@attrname, and {:default_value=>Time.now} is stored +into @options. The remaining argument list +["signed-on"] is returned; we capture the +"signed-on" string in path (the rest of the +argument list (an empty array) we capture in args for returning it +at the end of the initializer. This isn’t strictly necessary, +it’s just a convention that a node class initializer should always +return those arguments it didn’t handle itself). We‘ll +interpret path as an XPath expression that locates the time value +relative to the parent mapping object’s XML tree (in this case, this would be the XML tree rooted at the +<Signature> element, i.e. the tree the Signature +instance was read from). We‘ll later have to read/store the year, +month, and day values from path+"/year", +path+"/month", and path+"/day", +respectively, so we create (and precompile) three corresponding XPath +expressions using XML::XXPath.new and store +them into member variables of the node. XML::XXPath is an XPath +implementation that is bundled with xml-mapping. It is very incomplete, but +it supports writing (not just reading) of XML nodes, which is needed to support +writing data back to XML. The XML::XXPath library is explained in +more detail below. +

+

+The extract_attr_value method is called whenever an instance of +the mapping class the node belongs to (Signature in the example) +is being created from an XML tree. The +parameter xml is that tree (again, this is the tree rooted at the +<Signature> element in this example). The method +implementation is expected to extract the single attribute’s value +from xml and return it, or raise XML::Mapping::SingleAttributeNode::NoAttrValueSet +if the attribute was "unset" in the XML (this exception tells the framework that +the default value should be put in place if it was defined), or raise any +other exception to signal an error and abort the whole process. Our +superclass XML::Mapping::SingleAttributeNode +will store the returned single attribute’s value into the +signed_on attribute of the Signature instance being read +in. In our implementation, we apply the xpath expressions created during +initialization to xml (e.g. @y_path.first(xml)). An +expression xpath_expr.first(xml) returns (as a REXML element) the first sub-element of +xml that matches xpath_expr, or raises XML::XXPathError if there was no +such element. We apply REXML’s +text method to the returned element to get out the element’s +text, convert it to integer, and supply it to the constructor of the +Time object to be returned. As a side note, if an XPath expression +matches XML attributes, XML::XXPath methods like +first will return XML::XXPath::Accessors::Attribute +nodes that behave similarly to REXML::Element nodes, including support for +messages like name and text, so this would’ve +worked also if our XPath expressions had referred to XML attributes, not elements. The +default_when_xpath_err thing calls the supplied block and returns +its value, but maps the exception XML::XXPathError to the +mentioned XML::Mapping::SingleAttributeNode::NoAttrValueSet +(any other exceptions fall through unchanged). As said above, XML::Mapping::SingleAttributeNode::NoAttrValueSet +is caught by the framework (more precisely, by our superclass XML::Mapping::SingleAttributeNode), +and the default value is set if it was provided. So you should just wrap +default_when_xpath_err around any applications of XPath +expressions whose non-presence in the XML +you want to be considered a non-presence of the attribute you’re +trying to extract. (XML::XXPath is +designed to know knothing about XML::Mapping, so it doesn’t +raise XML::Mapping::SingleAttributeNode::NoAttrValueSet +directly) +

+

+The set_attr_value method is called whenever an instance of the +mapping class the node belongs to (Signature in the example) is +being stored into an XML tree. The +xml parameter is the XML tree (a +REXML element node; here this is again +the tree rooted at the <Signature> element); value +is the current value of the single attribute (in this example, the +signed_on attribute of the Signature instance being +stored). xml will most probably be "half-populated" by +the time this method is called — the framework calls the +set_attr_value methods of all nodes of a mapping class in the +order of their definition, letting each node fill its "bit" into +xml. The method implementation is expected to write value +into (the correct sub-elements of) xml, or raise an exception to +signal an error and abort the whole process. No default value handling is +done here; set_attr_value won’t be called at all if the +attribute had been set to its default value. In our implementation we grab +the year, month and day values from value (which must be a +Time), and store it into the sub-elements of xml +identified by XPath expressions @y_path, @m_path and +@d_path, respectively. We do this by calling XML::XXPath#first with an +additional parameter :ensure_created=>true. An expression +xpath_expr.first(xml,:ensure_created=>true) works just +like xpath_expr.first(xml) if xpath_expr was +already present in xml. If it was not, it is created (preferably +at the end of xml’s list of sub-nodes), and returned. See below for a more detailed documentation of the XPath +interpreter. +

+

Element order in created XML documents

+

+As just said, XML::XXPath, when +used to create new XML nodes, generally +appends those nodes to the end of the list of subnodes of the node the +xpath expression was applied to. All xml-mapping nodes that come with +xml-mapping use XML::XXPath when +writing data to XML, and therefore also +append their data to the XML data written +by preceding nodes (the nodes are invoked in the order of their +definition). This means that, generally, your output data will appear in +the XML document in the same order in +which the corresponding xml-mapping node definitions appeared in the +mapping class (unless you used XPath expressions like foo[number] which +explicitly dictate a fixed position in the sequence of XML nodes). For instance, in the +Order class from the example at the beginning of this document, if +we put the :signatures node before the :items +node, the <Signed-By> element will appear before +the sequence of <Item> elements in the output XML. +

+

+The following is a more systematic overview of the basic node types. The +description is self-contained, so some information from the previous +section will be repeated. +

+

Node Types Are Ruby Classes

+

+A node type is implemented as a Ruby class derived from XML::Mapping::Node or one of +its subclasses. +

+

+The following node types (node classes) come with xml-mapping (they all +live in the XML::Mapping +namespace, which I’ve left out here for brevity): +

+
+  Node
+   +-SingleAttributeNode
+   |  +-SubObjectBaseNode
+   |  |  +-ObjectNode
+   |  |  +-ArrayNode
+   |  |  +-HashNode
+   |  +-TextNode
+   |  +-NumericNode
+   |  +-BooleanNode
+   +-ChoiceNode
+
+

+XML::Mapping::Node is the +base class for all nodes, XML::Mapping::SingleAttributeNode +is the base class for single-attribute nodes, and XML::Mapping::SubObjectBaseNode +is the base class for single-attribute nodes with +sub-objects. XML::Mapping::TextNode, XML::Mapping::ArrayNode +etc. are of course the text_node, array_node etc. +we’ve talked about in this document. When you’ve written a new +node class, you register it with xml-mapping by calling XML::Mapping.add_node_class +MyNode. When you do that, xml-mapping automatically defines the node +factory method for your class — the method’s name (e.g. +my_node) is derived from the node’s class name (e.g. +Foo::Bar::MyNode) by stripping all parent module names, and then converting +capital letters to lowercase and preceding them with an underscore. In +fact, this is just how all the predefined node types are defined — +those node types are not "special"; they’re defined in the +source file +xml/mapping/standard_nodes.rb+ and then registered normally in ++xml/mapping.rb+. The source code of the built-in nodes is not very long or +complicated; you may consider reading it in addition to this text to gain a +better understanding. +

+

How Node Types Work

+

+The xml-mapping core "operates" node types as follows: +

+

Node Initialization

+

+As said above, when a node class is registered with xml-mapping by calling +XML::Mapping.add_node_class +TheNodeClass, xml-mapping automatically generates the node factory +method for that type. The node factory method will effectively be defined +as a class method of the XML::Mapping module, which is why +one can call it from the body of a mapping class definition. The generated +method will create a new instance of the node class (a node for +short) by calling new on the node class. The list of parameters to +new will consist of the mapping class, followed by all +arguments that were passed to the node factory method. For example, +when you have this node declaration: +

+
+  class MyMappingClass
+    include XML::Mapping
+
+    my_node :foo, "bar", 42, :hi=>"ho"
+  end
+
+

+, then the node factory method (my_node) calls +MyNode.new(MyMappingClass, :foo, "bar", 42, +:hi=>"ho"). +

+

+new of course creates the instance and calls initialize +on it. The initialize implementation will generally store the +parameters into some instance variables for later usage. As a convention, +initialize should always extract from the parameter list those +parameters it processes itself, process them, and return an array +containing the remaining (still unprocessed) parameters. Thus, an +implementation of initialize follows this pattern: +

+
+  def initialize(*args)
+    myparam1,myparam2,...,myparamx,*args = super(*args)
+
+    .... process the myparam1,myparam2,...,myparamx ....
+
+    # return still unprocessed args
+    args
+  end
+
+

+(since the called superclass initializer is written the same way, the +parameter array returned by it will already be stripped of all parameters +that the superclass initializer (or any of its superclasses’s +initializers) processed) +

+

+This technique is a simple way to "chain" the initializers of all +superclasses of a node class, starting with the topmost one (Node), so that +each initializer can easily find out and process the parameters it is +responsible for. +

+

+The base node class XML::Mapping::Node provides an +initialize implementation that, among other things (described +below), adds self (i.e. the created node) to the internal list of +nodes held by the mapping class, and sets the @owner attribute of +self to reference the mapping class. +

+

+So, effectively there will be one instance of a node class (a node) per +node definition, and that instance lives in the mapping class the node was +defined in. +

+

Node Operation during Marshalling and Unmarshalling

+

+When an instance of a mapping class is created or filled from an XML tree, xml-mapping will call +xml_to_obj on all nodes defined in that mapping class in the mapping the node is defined in, in the order of their +definition. Two parameters will be passed: the mapping class instance being +created/filled, and the XML tree the +instance is being created/filled from. The implementation of +xml_to_obj is expected to read whatever pieces of data it is +responsible for from the XML tree and put +it into the appropriate variables/attributes etc. of the instance. +

+

+When an instance of a mapping class is stored or filled into an XML tree, xml-mapping will call +obj_to_xml on all nodes defined in that mapping class in the mapping the node is defined in, in the order of their +definition, again passing as parameters the mapping class instance being +stored, and the XML tree the instance is +being stored/filled into. The implementation of obj_to_xml is +expected to read whatever pieces of data it is responsible for from the +instance and put it into the appropriate XML elements/XML attr etc. of the XML tree. +

+

Basic Node Types Overview

+

+The following is an overview of how initialization and +marshalling/unmarshalling is implemented in the node base classes (Node, +SingleAttributeNode, and SubObjectBaseNode). +

+

+TODO: summary table: member var name; introduced in class; meaning +

+

Node

+

+In initialize, the mapping class and the option arguments are +stripped from the argument list. The mapping class is stored in @owner, the +option arguments are stored (as a hash) in @options (the hash will be empty +if no options were given). The mapping the node is +defined in is determined (:mapping option, last use_mapping or +:_default) and stored in @mapping. The node then stores itself in +the list of nodes of the mapping class belonging to the mapping +(@owner.xml_mapping_nodes(:mapping=>@mapping); see XML::Mapping::ClassMethods#xml_mapping_nodes). +This list is the list of nodes later used when marshalling/unmarshalling an +instance of the mapping class with respect to a given mapping. This means +that node implementors will not normally "see" anything of the +mapping (they don’t need to access the @mapping variable) because the +marshalling/unmarshalling methods (obj_to_xml/xml_to_obj) +simply won’t be called if the node’s mapping is not the same as +the mapping the marshalling/unmarshalling is happening with. +

+

+Furthermore, if :reader and/or :writer options were given, +xml_to_obj resp. obj_to_xml are transparently overwritten +on the node to delegate to the supplied :reader/:writer procs. +

+

+The marshalling/unmarshalling methods +(obj_to_xml/xml_to_obj) are not implemented in +Node (they just raise an exception). +

+

SingleAttributeNode

+

+In initialize, the attribute name is stripped from the argument +list and stored in @attrname, and an attribute of that name is added to the +mapping class the node belongs to. +

+

+During marshalling/unmarshalling of an object to/from XML, single-attribute nodes only read/write +a single piece of the object’s state: the single attribute +(@attrname) the node handles. Because of this, the +obj_to_xml/xml_to_obj implementations in +SingleAttributeNode call two new methods introduced by SingleAttributeNode, +which must be overwritten by subclasses: +

+
+  extract_attr_value(xml)
+
+  set_attr_value(xml, value)
+
+

+extract_attr_value(xml) is called by xml_to_obj during +unmarshalling. xml is the XML +tree being read. The method must read the attribute’s value from +xml and return it. xml_to_obj will set the attribute to +that value. +

+

+set_attr_value(xml, value) is called by obj_to_xml during +marshalling. xml is the XML tree +being written, value is the current value of the attribute. The +method must write value into (the correct sub-elements/attributes) +of xml. +

+

+SingleAttributeNode also handles the default value, if it was specified +(via the :default_value option): When writing data to XML, set_attr_value(xml, value) +won’t be called if the attribute was set to the default value. When +reading data from XML, the +extract_attr_value(xml) implementation must raise a special +exception, XML::Mapping::SingleAttributeNode::NoAttrValueSet, +if it wants to indicate that the data was not present in the XML. SingleAttributeNode will catch this +exception and put the default value, if it was defined, into the attribute. +

+

SubObjectBaseNode

+

+The initializer will set up additional member variables @sub_mapping, +@marshaller, and @unmarshaller. +

+

+@sub_mapping contains the mapping to be used when reading/writing the +sub-objects (either specified with :sub_mapping, or, by default, the +mapping the node itself was defined in). +

+

+@marshaller and @unmarshaller contain procs that encapsulate +writing/reading of sub-objects to/from XML, as specified by the user with +:class/:marshaller/:unmarshaller etc. options (the meaning of those +different options was described above). The +procs are there to be called from extract_attr_value or +set_attr_value whenever the need arises. +

+

XPath Interpreter

+

+XML::XXPath is an XPath parser. It +is used in xml-mapping node type definitions, but can just as well be +utilized stand-alone (it does not depend on xml-mapping). XML::XXPath is very incomplete and +probably will always be, but it should be reasonably efficient (XPath +expressions are precompiled), and, most importantly, it supports write +access, which is needed for writing objects to XML. For example, if you create the path +"/foo/bar[3]/baz[@key=’hiho’]" in the XML document +

+
+  <foo>
+    <bar>
+      <baz key="ab">hello</baz>
+      <baz key="xy">goodbye</baz>
+    </bar>
+  </foo>
+
+

+, you’ll get: +

+
+  <foo>
+    <bar>
+      <baz key='ab'>hello</baz>
+      <baz key='xy'>goodbye</baz>
+    </bar>
+    <bar/>
+    <bar>
+      <baz key='hiho'/>
+    </bar>
+  </foo>
+
+

+XML::XXPath is explained in more +detail in the reference documentation and the README_XPATH file. +

+

License

+

+Ruby’s. +

+ +
+ + +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + diff --git a/files/README_XPATH.html b/files/README_XPATH.html new file mode 100644 index 0000000..faaa577 --- /dev/null +++ b/files/README_XPATH.html @@ -0,0 +1,749 @@ + + + + + + File: README_XPATH + + + + + + + + + + +
+

README_XPATH

+ + + + + + + + + +
Path:README_XPATH +
Last Update:Tue Aug 08 01:33:46 CEST 2006
+
+ + +
+ + + +
+ +
+

XML-XXPATH

+

Overview, Motivation

+

+Xml-xxpath is an (incomplete) XPath interpreter that is at the moment +bundled with xml-mapping. It is built on top of REXML. xml-mapping uses xml-xxpath +extensively for implementing its node types — see the README file and +the reference documentation (and the source code) for details. xml-xxpath, +however, does not depend on xml-mapping at all, and is useful in its own +right — maybe I’ll later distribute it as a seperate library +instead of bundling it. For the time being, if you want to use this XPath +implementation stand-alone, you can just rip the files +lib/xml/xxpath.rb, lib/xml/xxpath/steps.rb, and +lib/xml/xxpath_methods.rb out of the xml-mapping distribution and +use them on their own (they do not depend on anything else). +

+

+xml-xxpath’s XPath support is vastly incomplete (see below), but, in +addition to the normal reading/matching functionality found in other XPath +implementations (i.e. "find all elements in a given XML document matching a given XPath +expression"), xml-xxpath supports write access. For example, +when writing the XPath expression +"/foo/bar[3]/baz[@key=’hiho’]" to the XML document +

+
+  <foo>
+    <bar>
+      <baz key='ab'>hello</baz>
+      <baz key='xy'>goodbye</baz>
+    </bar>
+  </foo>
+
+

+, you’ll get: +

+
+  <foo>
+    <bar>
+      <baz key='ab'>hello</baz>
+      <baz key='xy'>goodbye</baz>
+    </bar>
+    <bar/>
+    <bar><baz key='hiho'/></bar>
+  </foo>
+
+

+This feature is used by xml-mapping when writing (marshalling) Ruby objects +to XML, and is actually the reason why I +couldn’t just use any of the existing XPath implementations, e.g. the +one that comes with REXML. Also, the +whole xml-xxpath implementation is just 300 lines of Ruby code, it is quite +fast (paths are precompiled), and xml-xxpath returns matched elements in +the order they appeared in the source document — I’ve heard +REXML::XPath doesn’t do that :) +

+

+Some basic knowledge of XPath is helpful for reading this document (I +don’t know very much either). +

+

+At the moment, xml-xxpath understands XPath expressions of the form +[/]pathelement/[/]pathelement/[/]…, +where each pathelement must be one of these: +

+
    +
  • a simple element name name, e.g. signature + +
  • +
  • an attribute name, @attr_name, e.g. @key + +
  • +
  • a combination of an element name and an attribute name and -value, in the +form +elt_name[@attr_name=’attr_value’] + +
  • +
  • an element name and an index, elt_name[index] + +
  • +
  • the "match-all" path element, * + +
  • +
+

Usage

+

+Xml-xxpath defines the class XML::XXPath. An instance of that +class wraps an XPath expression, the string representation of which must be +supplied when constructing the instance. You then call instance methods +like first, all or create_new on the instance, +supplying the REXML Element the XPath +expression should be applied to, and get the results, or, in the case of +write access, the element is updated in-place. +

+

Read Access

+
+  require 'xml/xxpath'
+
+  d=REXML::Document.new <<EOS
+    <foo>
+      <bar>
+        <baz key="work">Java</baz>
+        <baz key="play">Ruby</baz>
+      </bar>
+      <bar>
+        <baz key="ab">hello</baz>
+        <baz key="play">scrabble</baz>
+        <baz key="xy">goodbye</baz>
+      </bar>
+      <more>
+        <baz key="play">poker</baz>
+      </more>
+    </foo>
+  EOS
+
+  ###read access
+  path=XML::XXPath.new("/foo/bar[2]/baz")
+
+  # path.all(document) gives all elements matching path in document
+  path.all(d)
+  => [<baz key='ab'> ... </>, <baz key='play'> ... </>, <baz key='xy'> ... </>]
+
+  # loop over them
+  path.each(d){|elt| puts elt.text}
+  hello
+  scrabble
+  goodbye
+  => [<baz key='ab'> ... </>, <baz key='play'> ... </>, <baz key='xy'> ... </>]
+
+  # the first of those
+  path.first(d)
+  => <baz key='ab'> ... </>
+
+  # no match here (only three "baz" elements)
+  path2=XML::XXPath.new("/foo/bar[2]/baz[4]")
+  path2.all(d)
+  => []
+
+  # "first" raises XML::XXPathError in such cases...
+  path2.first(d)
+  XML::XXPathError: path not found: /foo/bar[2]/baz[4]
+        from ../lib/xml/xxpath.rb:75:in `first'
+
+  #...unless we allow nil returns
+  path2.first(d,:allow_nil=>true)
+  => nil
+
+  #attribute nodes can also be returned
+  keysPath=XML::XXPath.new("/foo/*/*/@key")
+
+  keysPath.all(d).map{|attr|attr.text}
+  => ["work", "play", "ab", "play", "xy", "play"]
+
+

+The objects supplied to the all(), first(), and +each() calls must be REXML +element nodes, i.e. they must support messages like elements, +attributes etc (instances of REXML::Element and its subclasses do +this). The calls return the found elements as instances of REXML::Element +or XML::XXPath::Accessors::Attribute. +The latter is a wrapper around attribute nodes that is largely +call-compatible to REXML::Element. This is so you can write things like +path.each{|node|puts node.text} without having to special-case +anything even if the path matches attributes, not just elements. +

+

+As you can see, you can re-use path objects, applying them to different XML elements at will. You should do this +because the XPath pattern is stored inside the XPath object in a +pre-compiled form, which makes it more efficient. +

+

+The path elements of the XPath pattern are applied to the +.elements collection of the passed XML element and its sub-elements, starting +with the first one. This is shown by the following code: +

+
+  require 'xml/xxpath'
+
+  d=REXML::Document.new <<EOS
+    <foo>
+      <bar x="hello">
+        <first>
+          <second>pingpong</second>
+        </first>
+      </bar>
+      <bar x="goodbye"/>
+    </foo>
+  EOS
+
+  XML::XXPath.new("/foo/bar").all(d)
+  => [<bar x='hello'> ... </>, <bar x='goodbye'/>]
+
+  XML::XXPath.new("/bar").all(d)
+  => []
+
+  XML::XXPath.new("/foo/bar").all(d.root)
+  => []
+
+  XML::XXPath.new("/bar").all(d.root)
+  => [<bar x='hello'> ... </>, <bar x='goodbye'/>]
+
+  firstelt = XML::XXPath.new("/foo/bar/first").first(d)
+  => <first> ... </>
+
+  XML::XXPath.new("/first/second").all(firstelt)
+  => []
+
+  XML::XXPath.new("/second").all(firstelt)
+  => [<second> ... </>]
+
+

+A REXML Document object is a +REXML Element object whose +elements collection consists only of a single member — the +document’s root node. The first path element of the XPath — +"foo" in the example — is matched against that. That is why +the path "/bar" in the example doesn’t match anything when +matched against the document d itself. +

+

+An ordinary REXML Element +object that represents a node somewhere inside an XML tree has an elements collection +that consists of all the element’s direct sub-elements. That is why +XPath patterns matched against the firstelt element in the example +*must not* start with "/first" (unless there is a child node that +is also named "first"). +

+

Write Access

+

+You may pass an :ensure_created=>true option argument to +path.first(elt)/path.all(elt) calls to +make sure that path exists inside the passed XML element elt. If it existed +before, nothing changes, and the call behaves just as it would without the +option argument. If the path didn’t exist before, the XML element is modified such that +

+
    +
  • the path exists afterwards + +
  • +
  • all paths that existed before still exist afterwards + +
  • +
  • the modification is as small as possible (i.e. as few elements as possible +are added, additional attributes are added to existing elements if possible +etc.) + +
  • +
+

+The created resp. previously existing, matching elements are returned. +

+

+Examples: +

+
+  require 'xml/xxpath'
+
+  d=REXML::Document.new <<EOS
+    <foo>
+      <bar>
+        <baz key="work">Java</baz>
+        <baz key="play">Ruby</baz>
+      </bar>
+    </foo>
+  EOS
+
+  rootelt=d.root
+
+  ### ensuring that a specific path exists inside the document
+
+  XML::XXPath.new("/bar/baz[@key='work']").first(rootelt,:ensure_created=>true)
+  => <baz key='work'> ... </>
+  d.write($stdout,2)
+      <foo>
+      <bar>
+        <baz key='work'>Java</baz>
+        <baz key='play'>Ruby</baz>
+      </bar>
+    </foo>
+
+  ## no change (path existed before)
+
+  XML::XXPath.new("/bar/baz[@key='42']").first(rootelt,:ensure_created=>true)
+  => <baz key='42'/>
+  d.write($stdout,2)
+      <foo>
+      <bar>
+        <baz key='work'>Java</baz>
+        <baz key='play'>Ruby</baz>
+      <baz key='42'/>
+        </bar>
+    </foo>
+
+  ## path was added
+
+  XML::XXPath.new("/bar/baz[@key='42']").first(rootelt,:ensure_created=>true)
+  => <baz key='42'/>
+  d.write($stdout,2)
+      <foo>
+      <bar>
+        <baz key='work'>Java</baz>
+        <baz key='play'>Ruby</baz>
+      <baz key='42'/>
+        </bar>
+    </foo>
+
+  ## no change this time
+
+  XML::XXPath.new("/bar/baz[@key2='hello']").first(rootelt,:ensure_created=>true)
+  => <baz key2='hello' key='work'> ... </>
+  d.write($stdout,2)
+      <foo>
+      <bar>
+        <baz key2='hello' key='work'>Java</baz>
+        <baz key='play'>Ruby</baz>
+      <baz key='42'/>
+        </bar>
+    </foo>
+
+  ## this fit in the 1st "baz" element since
+  ## there was no "key2" attribute there before.
+
+  XML::XXPath.new("/bar/baz[2]").first(rootelt,:ensure_created=>true)
+  => <baz key='play'> ... </>
+  d.write($stdout,2)
+      <foo>
+      <bar>
+        <baz key2='hello' key='work'>Java</baz>
+        <baz key='play'>Ruby</baz>
+      <baz key='42'/>
+        </bar>
+    </foo>
+
+  ## no change
+
+  XML::XXPath.new("/bar/baz[6]/@haha").first(rootelt,:ensure_created=>true)
+  => #<XML::XXPath::Accessors::Attribute:0xb794b178 @name="haha", @parent=<baz haha='[unset]'/>>
+  d.write($stdout,2)
+      <foo>
+      <bar>
+        <baz key2='hello' key='work'>Java</baz>
+        <baz key='play'>Ruby</baz>
+      <baz key='42'/>
+          <baz/>
+          <baz/>
+          <baz haha='[unset]'/>
+        </bar>
+    </foo>
+
+  ## for there to be a 6th "baz" element, there must be 1st..5th "baz" elements
+
+  XML::XXPath.new("/bar/baz[6]/@haha").first(rootelt,:ensure_created=>true)
+  => #<XML::XXPath::Accessors::Attribute:0xb7942708 @name="haha", @parent=<baz haha='[unset]'/>>
+  d.write($stdout,2)
+      <foo>
+      <bar>
+        <baz key2='hello' key='work'>Java</baz>
+        <baz key='play'>Ruby</baz>
+      <baz key='42'/>
+          <baz/>
+          <baz/>
+          <baz haha='[unset]'/>
+        </bar>
+    </foo>
+
+  ## no change this time
+
+

+Alternatively, you may pass a :create_new=>true option argument +or call create_new (path.create_new(elt) is +equivalent to path.first(elt,:create_new=>true)). In +that case, a new node is created in elt for each path element of +path (or an exception raised if that wasn’t possible for any +path element). +

+

+Examples: +

+
+  require 'xml/xxpath'
+
+  d=REXML::Document.new <<EOS
+    <foo>
+      <bar>
+        <baz key="work">Java</baz>
+        <baz key="play">Ruby</baz>
+      </bar>
+    </foo>
+  EOS
+
+  rootelt=d.root
+
+  path1=XML::XXPath.new("/bar/baz[@key='work']")
+
+  path1.create_new(rootelt)
+  => <baz key='work'/>
+  d.write($stdout,2)
+      <foo>
+      <bar>
+        <baz key='work'>Java</baz>
+        <baz key='play'>Ruby</baz>
+      </bar>
+    <bar>
+          <baz key='work'/>
+        </bar>
+      </foo>
+
+  ## a new element is created for *each* path element, regardless of
+  ## what existed before. So a new "bar" element was added, with a new
+  ## "baz" element inside it
+
+  ## same call again...
+  path1.create_new(rootelt)
+  => <baz key='work'/>
+  d.write($stdout,2)
+      <foo>
+      <bar>
+        <baz key='work'>Java</baz>
+        <baz key='play'>Ruby</baz>
+      </bar>
+    <bar>
+          <baz key='work'/>
+        </bar>
+        <bar>
+          <baz key='work'/>
+        </bar>
+      </foo>
+
+  ## same procedure -- new elements added for each path element
+
+  # get reference to 1st "baz" element
+  firstbazelt=XML::XXPath.new("/bar/baz").first(rootelt)
+  => <baz key='work'> ... </>
+
+  path2=XML::XXPath.new("@key2")
+
+  path2.create_new(firstbazelt)
+  => #<XML::XXPath::Accessors::Attribute:0xb79116d0 @name="key2", @parent=<baz key2='[unset]' key='work'> ... </>>
+  d.write($stdout,2)
+      <foo>
+      <bar>
+        <baz key2='[unset]' key='work'>Java</baz>
+        <baz key='play'>Ruby</baz>
+      </bar>
+    <bar>
+          <baz key='work'/>
+        </bar>
+        <bar>
+          <baz key='work'/>
+        </bar>
+      </foo>
+
+  ## ok, new attribute node added
+
+  ## same call again...
+  path2.create_new(firstbazelt)
+  XML::XXPathError: XPath (@key2): create_new and attribute already exists
+        from ../lib/xml/../xml/xxpath/steps.rb:210:in `create_on'
+        from ../lib/xml/../xml/xxpath/steps.rb:80:in `creator'
+        from ../lib/xml/xxpath.rb:91:in `all'
+        from ../lib/xml/xxpath.rb:70:in `first'
+        from ../lib/xml/xxpath.rb:112:in `create_new'
+  ## can't create that path anew again -- an element can't have more
+  ## than one attribute with the same name
+
+  ## the document hasn't changed
+  d.write($stdout,2)
+      <foo>
+      <bar>
+        <baz key2='[unset]' key='work'>Java</baz>
+        <baz key='play'>Ruby</baz>
+      </bar>
+    <bar>
+          <baz key='work'/>
+        </bar>
+        <bar>
+          <baz key='work'/>
+        </bar>
+      </foo>
+
+  ## create_new the same path as in the ensure_created example
+  baz6elt=XML::XXPath.new("/bar/baz[6]").create_new(rootelt)
+  => <baz/>
+  d.write($stdout,2)
+      <foo>
+      <bar>
+        <baz key2='[unset]' key='work'>Java</baz>
+        <baz key='play'>Ruby</baz>
+      </bar>
+    <bar>
+          <baz key='work'/>
+        </bar>
+        <bar>
+          <baz key='work'/>
+        </bar>
+        <bar>
+          <baz/>
+          <baz/>
+          <baz/>
+          <baz/>
+          <baz/>
+          <baz/>
+        </bar>
+      </foo>
+
+  ## ok, new "bar" element and 6th "baz" element inside it created
+
+  XML::XXPath.new("baz[6]").create_new(baz6elt.parent)
+  XML::XXPathError: XPath: baz[6]: create_new and element already exists
+        from ../lib/xml/../xml/xxpath/steps.rb:162:in `create_on'
+        from ../lib/xml/../xml/xxpath/steps.rb:80:in `creator'
+        from ../lib/xml/xxpath.rb:91:in `all'
+        from ../lib/xml/xxpath.rb:70:in `first'
+        from ../lib/xml/xxpath.rb:112:in `create_new'
+  ## yep, baz[6] already existed and thus couldn't be created once
+  ## again
+
+  ## but of course...
+  XML::XXPath.new("/bar/baz[6]").create_new(rootelt)
+  => <baz/>
+  d.write($stdout,2)
+      <foo>
+      <bar>
+        <baz key2='[unset]' key='work'>Java</baz>
+        <baz key='play'>Ruby</baz>
+      </bar>
+    <bar>
+          <baz key='work'/>
+        </bar>
+        <bar>
+          <baz key='work'/>
+        </bar>
+        <bar>
+          <baz/>
+          <baz/>
+          <baz/>
+          <baz/>
+          <baz/>
+          <baz/>
+        </bar>
+        <bar>
+          <baz/>
+          <baz/>
+          <baz/>
+          <baz/>
+          <baz/>
+          <baz/>
+        </bar>
+      </foo>
+
+  ## this works because *all* path elements are newly created
+
+

+This feature is used in xml-mapping by node types like XML::Mapping::ArrayNode, +which must create a new instance of the "per-array element path" +for each element of the array to be stored in an XML tree. +

+

Pathological Cases

+

+What is created when the Path "*" is to be created inside an +empty XML element? The name of the +element to be created isn’t known, but still some element must be +created. The answer is that xml-xxpath creates a special +"unspecified" element whose name must be set by the caller +afterwards: +

+
+  require 'xml/xxpath'
+
+  d=REXML::Document.new <<EOS
+    <foo>
+      <bar/>
+      <bar/>
+    </foo>
+  EOS
+
+  rootelt=d.root
+
+  XML::XXPath.new("*").all(rootelt)
+  => [<bar/>, <bar/>]
+  ## ok
+
+  XML::XXPath.new("bar/*").first(rootelt, :allow_nil=>true)
+  => nil
+  ## ok, nothing there
+
+  ## the same call with :ensure_created=>true
+  newelt = XML::XXPath.new("bar/*").first(rootelt, :ensure_created=>true)
+  => </>
+
+  d.write($stdout,2)
+      <foo>
+      <bar>
+          </>
+        </bar>
+      <bar/>
+    </foo>
+
+  ## a new "unspecified" element was created
+  newelt.unspecified?
+  => true
+
+  ## we must modify it to "specify" it
+  newelt.name="new-one"
+  newelt.text="hello!"
+  newelt.unspecified?
+  => false
+
+  d.write($stdout,2)
+      <foo>
+      <bar>
+          <new-one>hello!</new-one>
+        </bar>
+      <bar/>
+    </foo>
+
+  ## you could also set unspecified to false explicitly, as in:
+  newelt.unspecified=true
+
+

+The "newelt" object in the last example is an ordinary +REXML::Element. xml-xxpath mixes the "unspecified" attribute into +that class, as well as into the XML::XXPath::Accessors::Attribute +class mentioned above. +

+

Implentation notes

+

+doc/xpath_impl_notes.txt contains some documentation on the +implementation of xml-xxpath. +

+

License

+

+Ruby’s. +

+ +
+ + +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/files/TODO_txt.html b/files/TODO_txt.html new file mode 100644 index 0000000..c0cf310 --- /dev/null +++ b/files/TODO_txt.html @@ -0,0 +1,210 @@ + + + + + + File: TODO.txt + + + + + + + + + + +
+

TODO.txt

+ + + + + + + + + +
Path:TODO.txt +
Last Update:Wed Apr 19 07:58:12 CEST 2006
+
+ + +
+ + + +
+ +
+
    +
  • XML::XXPath: Write a real XPath +parser eventually + +
  • +
  • XML::XXPath: avoid duplicates in +path.all(node) result arrays when using the descendants ("//") +axis + +
  • +
  • invent an XPath-like language for Ruby object graphs (i.e. a language that +is to Ruby object graphs what XPath is to XML trees). Use expressions in that language +as a generalization of "attribute names" (e.g. the 1st parameter +to single attribute node factory methods). The language could more or less +be Ruby itself, but the write support would need some extra work… + +
  • +
  • XML::XXPath: + +
      +
    • implement .[@attrname] steps + +
        +
      • returns the context node iff it contains an attrname attribute + +
      • +
      • doesn’t work properly in REXML::XPath? + +
      • +
      +
    • +
    • implement *[@attrname] steps + +
    • +
    • implement *[@attrname=’attrvalue’] steps + +
    • +
    • id/idref support (write support possible?) + +
    • +
    +
  • +
  • XML::Mapping: make +SubObjectBaseNode a mixin instead of a subclass of SingleAttributeNode +("mapping sub-objects" and "mapping to a single +attribute" are orthogonal concepts; inheritance is bad design here) + +
  • +
  • documentation: + +
      +
    • README: + +
        +
      • document/show usage of default_when_xpath_err outside node type +implementations + +
      • +
      +
    • +
    • README_XPATH: + +
        +
      • mention new step types, new axes, xml/xpath_methods + +
      • +
      +
    • +
    +
  • +
  • XML::XXPath/XML::Mapping: support for XML namespaces in XML::XXPath (match nodes with +specific namespaces only) and XML::Mapping (use_namespace etc.) + +
  • +
  • add streaming input/output to XML::Mapping, i.e. SAX-based input +in addition to the current REXML/DOM - +based one. Probably won’t be implementable for some more complicated +XPaths — raise meaningful exceptions in those cases. + +
  • +
  • XML::XXPath/XML::Mapping: add XML text nodes (the sub-node of an element +node that contains that element’s text) first-class to XML::XXPath. Use it for things like +text_node :contents, "text()". + +

    +Along those lines: promote XPath node "unspecifiedness" from an +attribute to a REXML node object of +"unspecified" class that’s turned into an +attribute/element/text node when necessary +

    +
  • +
  • (eventually, maybe) provide a "scaffolding" feature to +automatically turn a dtd/schema into a set of node type definitions or even +a set of mapping classes + +
  • +
+ +
+ + +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/files/doc/xpath_impl_notes_txt.html b/files/doc/xpath_impl_notes_txt.html new file mode 100644 index 0000000..c3805fb --- /dev/null +++ b/files/doc/xpath_impl_notes_txt.html @@ -0,0 +1,236 @@ + + + + + + File: xpath_impl_notes.txt + + + + + + + + + + +
+

xpath_impl_notes.txt

+ + + + + + + + + +
Path:doc/xpath_impl_notes.txt +
Last Update:Mon Jul 04 02:00:31 CEST 2005
+
+ + +
+ + + +
+ +
+

latest design (12/2004)

+

+At the lowest level, the "Accessors" sub-module contains reader +and creator functions that correspond to the various types of path elements +(elt_name, @attr_name, +elt_name[@attr_name=’attr_value’] +etc.) that xml-xxpath supports. A reader function gets an array of nodes +and the search parameters corresponding to its path element type (e.g. +elt_name, attr_name, attr_value) and returns an +array with all matching direct sub-nodes of any of the supplied nodes. A +creator function gets one node and the search parameters and returns the +created sub-node. +

+

+An XPath expression +<things1>/<things2>/…/<thingsx> is +compiled into a bunch of nested closures, each of which is responsible for +a specific path element and calls the corresponding accessor function: +

+
    +
  • @creator_procs — an array of "creator" functions. +@creator_procs[i] gets passed a base node (XML element) and a create_new flag, and +it creates the path +<things[x-i+1]>/<things[x-i+2]>/…/<thingsx> +inside the base node and returns the hindmost element created (i.e. the one +corresponding to <thingsx>). + +
  • +
  • @reader_proc — a "reader" function that gets +passed an array of nodes and returns an array of all nodes that matched the +path in any of the supplied nodes, or, if no match was found, throws +:not_found along with the last non-empty set of nodes that was found, and +the element of @creator_procs that could be used to create the +remaining part of the path. + +
  • +
+

+The all function is then trivially implemented on top of this: +

+
+    def all(node,options={})
+      raise "options not a hash" unless Hash===options
+      if options[:create_new]
+        return [ @creator_procs[-1].call(node,true) ]
+      else
+        last_nodes,rest_creator = catch(:not_found) do
+          return @reader_proc.call([node])
+        end
+        if options[:ensure_created]
+          [ rest_creator.call(last_nodes[0],false) ]
+        else
+          []
+        end
+      end
+    end
+
+

+…and first, create_new etc. are even more trivial +frontends to that. +

+

+The implementations of the @creator_procs look like this: +

+
+  @creator_procs[0] =
+    proc{|node,create_new| node}
+
+  @creator_procs[1] =
+    proc {|node,create_new|
+      @creator_procs[0].call(Accessors.create_subnode_by_<thingsx>(node,create_new,<thingsx>),
+                             create_new)
+    }
+
+  @creator_procs[2] =
+    proc {|node,create_new|
+      @creator_procs[1].call(Accessors.create_subnode_by_<thingsx-1>(node,create_new,<thingsx-1>),
+                             create_new)
+    }
+
+  ...
+
+  @creator_procs[n] =
+    proc {|node,create_new|
+      @creator_procs[n-1].call(Accessors.create_subnode_by_<things[x+1-n]>(node,create_new,<things[x+1-n]>),
+                               create_new)
+    }
+
+  ...
+  @creator_procs[x] =
+    proc {|node,create_new|
+      @creator_procs[x-1].call(Accessors.create_subnode_by_<things1>(node,create_new,<things1>),
+                               create_new)
+    }
+
+

+..and the implementation of @reader_proc looks like this: +

+
+  @reader_proc = rpx where
+
+  rp0 = proc {|nodes| nodes}
+
+  rp1 = proc {|nodes|
+                next_nodes = Accessors.subnodes_by_<thingsx>(nodes,<thingsx>)
+                if (next_nodes == [])
+                  throw :not_found, [nodes,@creator_procs[1]]
+                else
+                  rp0.call(next_nodes)
+                end
+              }
+
+  rp2 = proc {|nodes|
+                next_nodes = Accessors.subnodes_by_<thingsx-1>(nodes,<thingsx-1>)
+                if (next_nodes == [])
+                  throw :not_found, [nodes,@creator_procs[2]]
+                else
+                  rp1.call(next_nodes)
+                end
+              }
+  ...
+
+  rpx = proc {|nodes|
+                next_nodes = Accessors.subnodes_by_<things1>(nodes,<things1>)
+                if (next_nodes == [])
+                  throw :not_found, [nodes,@creator_procs[x]]
+                else
+                  rpx-1.call(next_nodes)
+                end
+              }
+
+ +
+ + +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/files/lib/xml/mapping/base_rb.html b/files/lib/xml/mapping/base_rb.html new file mode 100644 index 0000000..4a0b60b --- /dev/null +++ b/files/lib/xml/mapping/base_rb.html @@ -0,0 +1,119 @@ + + + + + + File: base.rb + + + + + + + + + + +
+

base.rb

+ + + + + + + + + +
Path:lib/xml/mapping/base.rb +
Last Update:Thu May 04 05:30:32 CEST 2006
+
+ + +
+ + + +
+ +
+

+xml-mapping — bidirectional Ruby-XML mapper +

+
+ Copyright (C) 2004-2006 Olaf Klischat
+
+ +
+ +
+

Required files

+ +
+ rexml/document   + xml/xxpath   +
+
+ +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/files/lib/xml/mapping/core_classes_mapping_rb.html b/files/lib/xml/mapping/core_classes_mapping_rb.html new file mode 100644 index 0000000..0c2de62 --- /dev/null +++ b/files/lib/xml/mapping/core_classes_mapping_rb.html @@ -0,0 +1,101 @@ + + + + + + File: core_classes_mapping.rb + + + + + + + + + + +
+

core_classes_mapping.rb

+ + + + + + + + + +
Path:lib/xml/mapping/core_classes_mapping.rb +
Last Update:Fri Mar 31 01:11:26 CEST 2006
+
+ + +
+ + + +
+ + + +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/files/lib/xml/mapping/standard_nodes_rb.html b/files/lib/xml/mapping/standard_nodes_rb.html new file mode 100644 index 0000000..689e31e --- /dev/null +++ b/files/lib/xml/mapping/standard_nodes_rb.html @@ -0,0 +1,118 @@ + + + + + + File: standard_nodes.rb + + + + + + + + + + +
+

standard_nodes.rb

+ + + + + + + + + +
Path:lib/xml/mapping/standard_nodes.rb +
Last Update:Mon May 22 06:34:03 CEST 2006
+
+ + +
+ + + +
+ +
+

+xml-mapping — bidirectional Ruby-XML mapper +

+
+ Copyright (C) 2004,2005 Olaf Klischat
+
+ +
+ +
+

Required files

+ +
+ xml/mapping/core_classes_mapping   +
+
+ +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/files/lib/xml/mapping/version_rb.html b/files/lib/xml/mapping/version_rb.html new file mode 100644 index 0000000..eb213dc --- /dev/null +++ b/files/lib/xml/mapping/version_rb.html @@ -0,0 +1,111 @@ + + + + + + File: version.rb + + + + + + + + + + +
+

version.rb

+ + + + + + + + + +
Path:lib/xml/mapping/version.rb +
Last Update:Wed Oct 05 04:28:00 CEST 2005
+
+ + +
+ + + +
+ +
+

+xml-mapping — bidirectional Ruby-XML mapper +

+
+ Copyright (C) 2004,2005 Olaf Klischat
+
+ +
+ + +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/files/lib/xml/mapping_rb.html b/files/lib/xml/mapping_rb.html new file mode 100644 index 0000000..63601d5 --- /dev/null +++ b/files/lib/xml/mapping_rb.html @@ -0,0 +1,119 @@ + + + + + + File: mapping.rb + + + + + + + + + + +
+

mapping.rb

+ + + + + + + + + +
Path:lib/xml/mapping.rb +
Last Update:Mon Apr 03 02:56:24 CEST 2006
+
+ + +
+ + + +
+ +
+

+xml-mapping — bidirectional Ruby-XML mapper +

+
+ Copyright (C) 2004-2006 Olaf Klischat
+
+ +
+ +
+

Required files

+ +
+ xml/mapping/base   + xml/mapping/standard_nodes   +
+
+ +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/files/lib/xml/rexml_ext_rb.html b/files/lib/xml/rexml_ext_rb.html new file mode 100644 index 0000000..8a97e9b --- /dev/null +++ b/files/lib/xml/rexml_ext_rb.html @@ -0,0 +1,117 @@ + + + + + + File: rexml_ext.rb + + + + + + + + + + +
+

rexml_ext.rb

+ + + + + + + + + +
Path:lib/xml/rexml_ext.rb +
Last Update:Mon Apr 03 02:57:40 CEST 2006
+
+ + +
+ + + +
+ +
+

+xxpath — XPath implementation for Ruby, including write access +

+
+ Copyright (C) 2004-2006 Olaf Klischat
+
+ +
+ +
+

Required files

+ +
+ rexml/document   +
+
+ +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/files/lib/xml/xxpath/steps_rb.html b/files/lib/xml/xxpath/steps_rb.html new file mode 100644 index 0000000..18601e8 --- /dev/null +++ b/files/lib/xml/xxpath/steps_rb.html @@ -0,0 +1,110 @@ + + + + + + File: steps.rb + + + + + + + + + + +
+

steps.rb

+ + + + + + + + + +
Path:lib/xml/xxpath/steps.rb +
Last Update:Mon May 01 01:41:30 CEST 2006
+
+ + +
+ + + +
+ +
+

+xxpath — XPath implementation for Ruby, including write access +

+
+ Copyright (C) 2004-2006 Olaf Klischat
+
+ +
+ + +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/files/lib/xml/xxpath_methods_rb.html b/files/lib/xml/xxpath_methods_rb.html new file mode 100644 index 0000000..ecc047d --- /dev/null +++ b/files/lib/xml/xxpath_methods_rb.html @@ -0,0 +1,110 @@ + + + + + + File: xxpath_methods.rb + + + + + + + + + + +
+

xxpath_methods.rb

+ + + + + + + + + +
Path:lib/xml/xxpath_methods.rb +
Last Update:Mon Apr 03 02:57:47 CEST 2006
+
+ + +
+ + + +
+ +
+

+xxpath — XPath implementation for Ruby, including write access +

+
+ Copyright (C) 2004-2006 Olaf Klischat
+
+ +
+ + +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/files/lib/xml/xxpath_rb.html b/files/lib/xml/xxpath_rb.html new file mode 100644 index 0000000..52a393c --- /dev/null +++ b/files/lib/xml/xxpath_rb.html @@ -0,0 +1,119 @@ + + + + + + File: xxpath.rb + + + + + + + + + + +
+

xxpath.rb

+ + + + + + + + + +
Path:lib/xml/xxpath.rb +
Last Update:Mon May 01 01:27:10 CEST 2006
+
+ + +
+ + + +
+ +
+

+xxpath — XPath implementation for Ruby, including write access +

+
+ Copyright (C) 2004-2006 Olaf Klischat
+
+ +
+ +
+

Required files

+ +
+ rexml/document   + xml/rexml_ext   + xml/xxpath/steps   +
+
+ +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/fr_class_index.html b/fr_class_index.html new file mode 100644 index 0000000..227bdfc --- /dev/null +++ b/fr_class_index.html @@ -0,0 +1,53 @@ + + + + + + + + Classes + + + + + +
+

Classes

+ +
+ + \ No newline at end of file diff --git a/fr_file_index.html b/fr_file_index.html new file mode 100644 index 0000000..f0a2e48 --- /dev/null +++ b/fr_file_index.html @@ -0,0 +1,40 @@ + + + + + + + + Files + + + + + +
+

Files

+ +
+ + \ No newline at end of file diff --git a/fr_method_index.html b/fr_method_index.html new file mode 100644 index 0000000..5eb0429 --- /dev/null +++ b/fr_method_index.html @@ -0,0 +1,103 @@ + + + + + + + + Methods + + + + + +
+

Methods

+
+ == (XML::XXPath::Accessors::Attribute)
+ add_accessor (XML::Mapping::ClassMethods)
+ add_node_class (XML::Mapping)
+ all (XML::XXPath)
+ all (XML::XXPathMethods)
+ all_xml_mapping_nodes (XML::Mapping::ClassMethods)
+ append_features (XML::XXPath::Accessors::UnspecifiednessSupport)
+ class_and_mapping_for_root_elt_name (XML::Mapping)
+ class_for_root_elt_name (XML::Mapping)
+ create_new (XML::XXPathMethods)
+ create_new (XML::XXPath)
+ default_mapping (XML::Mapping::ClassMethods)
+ default_root_element_name (XML::Mapping::ClassMethods)
+ default_when_xpath_err (XML::Mapping::SingleAttributeNode)
+ each (XML::XXPath)
+ each_on_axis (REXML::Parent)
+ each_on_axis_child (REXML::Parent)
+ each_on_axis_descendant (REXML::Parent)
+ each_on_axis_self (REXML::Parent)
+ each_xpath (XML::XXPathMethods)
+ extract_attr_value (XML::Mapping::SingleAttributeNode)
+ fill_from_xml (XML::Mapping)
+ fill_into_xml (XML::Mapping)
+ fill_into_xml (String)
+ fill_into_xml (Numeric)
+ first (XML::XXPath)
+ first (XML::XXPathMethods)
+ initialize_impl (XML::Mapping::SingleAttributeNode)
+ initialize_xml_mapping (XML::Mapping)
+ is_present_in? (XML::Mapping::Node)
+ is_present_in? (XML::Mapping::ChoiceNode)
+ is_present_in? (XML::Mapping::SingleAttributeNode)
+ load_from_file (XML::Mapping::ClassMethods)
+ load_from_xml (XML::Mapping::ClassMethods)
+ load_from_xml (String)
+ load_from_xml (Numeric)
+ load_object_from_file (XML::Mapping)
+ load_object_from_xml (XML::Mapping)
+ new (XML::Mapping::BooleanNode)
+ new (XML::XXPath::Accessors::Attribute)
+ new (XML::XXPath)
+ new (XML::Mapping::ObjectNode)
+ new (XML::Mapping::Node)
+ new (XML::XXPath::Accessors::Attribute)
+ new (XML::Mapping::HashNode)
+ new (XML::Mapping::TextNode)
+ new (XML::Mapping::SingleAttributeNode)
+ new (XML::Mapping::SubObjectBaseNode)
+ new (XML::Mapping)
+ new (XML::Mapping::ChoiceNode)
+ new (XML::Mapping::ArrayNode)
+ new (XML::Mapping::NumericNode)
+ obj_initializing (XML::Mapping::ChoiceNode)
+ obj_initializing (XML::Mapping::Node)
+ obj_to_xml (XML::Mapping::Node)
+ obj_to_xml (XML::Mapping::ChoiceNode)
+ obj_to_xml (XML::Mapping::Node)
+ post_load (XML::Mapping)
+ post_save (XML::Mapping)
+ pre_load (XML::Mapping)
+ pre_save (XML::Mapping)
+ root_element_name (XML::Mapping::ClassMethods)
+ save_to_file (XML::Mapping)
+ save_to_xml (XML::Mapping)
+ set_attr_value (XML::Mapping::SingleAttributeNode)
+ text (XML::XXPath::Accessors::Attribute)
+ text (Numeric)
+ text (String)
+ text= (XML::XXPath::Accessors::Attribute)
+ to_xxpath (XML::XXPathMethods)
+ unspecified= (XML::XXPath::Accessors::UnspecifiednessSupport)
+ unspecified? (XML::XXPath::Accessors::UnspecifiednessSupport)
+ use_mapping (XML::Mapping::ClassMethods)
+ xml_mapping_nodes (XML::Mapping::ClassMethods)
+ xml_to_obj (XML::Mapping::ChoiceNode)
+ xml_to_obj (XML::Mapping::Node)
+ xml_to_obj (XML::Mapping::Node)
+
+
+ + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..1538890 --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + + + + + + XML::Mapping -- Simple, extensible Ruby-to-XML (and back) mapper + + + + + + + + + + + \ No newline at end of file diff --git a/rdoc-style.css b/rdoc-style.css new file mode 100644 index 0000000..44c7b3d --- /dev/null +++ b/rdoc-style.css @@ -0,0 +1,208 @@ + +body { + font-family: Verdana,Arial,Helvetica,sans-serif; + font-size: 90%; + margin: 0; + margin-left: 40px; + padding: 0; + background: white; +} + +h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; } +h1 { font-size: 150%; } +h2,h3,h4 { margin-top: 1em; } + +a { background: #eef; color: #039; text-decoration: none; } +a:hover { background: #039; color: #eef; } + +/* Override the base stylesheet's Anchor inside a table cell */ +td > a { + background: transparent; + color: #039; + text-decoration: none; +} + +/* and inside a section title */ +.section-title > a { + background: transparent; + color: #eee; + text-decoration: none; +} + +/* === Structural elements =================================== */ + +div#index { + margin: 0; + margin-left: -40px; + padding: 0; + font-size: 90%; +} + + +div#index a { + margin-left: 0.7em; +} + +div#index .section-bar { + margin-left: 0px; + padding-left: 0.7em; + background: #ccc; + font-size: small; +} + + +div#classHeader, div#fileHeader { + width: auto; + color: white; + padding: 0.5em 1.5em 0.5em 1.5em; + margin: 0; + margin-left: -40px; + border-bottom: 3px solid #006; +} + +div#classHeader a, div#fileHeader a { + background: inherit; + color: white; +} + +div#classHeader td, div#fileHeader td { + background: inherit; + color: white; +} + + +div#fileHeader { + background: #057; +} + +div#classHeader { + background: #048; +} + + +.class-name-in-header { + font-size: 180%; + font-weight: bold; +} + + +div#bodyContent { + padding: 0 1.5em 0 1.5em; +} + +div#description { + padding: 0.5em 1.5em; + background: #efefef; + border: 1px dotted #999; +} + +div#description h1,h2,h3,h4,h5,h6 { + color: #125;; + background: transparent; +} + +div#validator-badges { + text-align: center; +} +div#validator-badges img { border: 0; } + +div#copyright { + color: #333; + background: #efefef; + font: 0.75em sans-serif; + margin-top: 5em; + margin-bottom: 0; + padding: 0.5em 2em; +} + + +/* === Classes =================================== */ + +table.header-table { + color: white; + font-size: small; +} + +.type-note { + font-size: small; + color: #DEDEDE; +} + +.xxsection-bar { + background: #eee; + color: #333; + padding: 3px; +} + +.section-bar { + color: #333; + border-bottom: 1px solid #999; + margin-left: -20px; +} + + +.section-title { + background: #79a; + color: #eee; + padding: 3px; + margin-top: 2em; + margin-left: -30px; + border: 1px solid #999; +} + +.top-aligned-row { vertical-align: top } +.bottom-aligned-row { vertical-align: bottom } + +/* --- Context section classes ----------------------- */ + +.context-row { } +.context-item-name { font-family: monospace; font-weight: bold; color: black; } +.context-item-value { font-size: small; color: #448; } +.context-item-desc { color: #333; padding-left: 2em; } + +/* --- Method classes -------------------------- */ +.method-detail { + background: #efefef; + padding: 0; + margin-top: 0.5em; + margin-bottom: 1em; + border: 1px dotted #ccc; +} +.method-heading { + color: black; + background: #ccc; + border-bottom: 1px solid #666; + padding: 0.2em 0.5em 0 0.5em; +} +.method-signature { color: black; background: inherit; } +.method-name { font-weight: bold; } +.method-args { font-style: italic; } +.method-description { padding: 0 0.5em 0 0.5em; } + +/* --- Source code sections -------------------- */ + +a.source-toggle { font-size: 90%; } +div.method-source-code { + background: #262626; + color: #ffdead; + margin: 1em; + padding: 0.5em; + border: 1px dashed #999; + overflow: hidden; +} + +div.method-source-code pre { color: #ffdead; overflow: hidden; } + +/* --- Ruby keyword styles --------------------- */ + +.standalone-code { background: #221111; color: #ffdead; overflow: hidden; } + +.ruby-constant { color: #7fffd4; background: transparent; } +.ruby-keyword { color: #00ffff; background: transparent; } +.ruby-ivar { color: #eedd82; background: transparent; } +.ruby-operator { color: #00ffee; background: transparent; } +.ruby-identifier { color: #ffdead; background: transparent; } +.ruby-node { color: #ffa07a; background: transparent; } +.ruby-comment { color: #b22222; font-weight: bold; background: transparent; } +.ruby-regexp { color: #ffa07a; background: transparent; } +.ruby-value { color: #7fffd4; background: transparent; } \ No newline at end of file From a69806890b21a784fc653a7d5c602af769dedfbd Mon Sep 17 00:00:00 2001 From: Olaf Klischat Date: Wed, 27 Aug 2014 23:19:43 +0200 Subject: [PATCH 2/4] manual documentation update --- files/README.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/README.html b/files/README.html index 890d744..58a15c1 100644 --- a/files/README.html +++ b/files/README.html @@ -77,11 +77,11 @@

XML-MAPPING:

Download

-For downloading the latest version, CVS repository access etc. go to: +For downloading the latest version, git repository access etc. go to:

rubyforge.org/projects/xml-mapping/ +href="https://github.com/multi-io/xml-mapping">https://github.com/multi-io/xml-mapping

Contents of this Document