+ module Kernel +
+ +unsuppress
+ +Public Instance Methods
+# File lib/xml/rexml_ext.rb, line 155 +def warn(msg) +end+
From 979f77a8c7267233f57834bb3110fe74cec0f04d Mon Sep 17 00:00:00 2001
From: Olaf Klischat
+xxpath — XPath implementation for Ruby, including write access
+
+This is the central interface module of the xml-mapping library.
+
+Including this module in your classes adds XML
+mapping capabilities to them.
+
+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.
+
+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).
+
+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]
+
+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).
+
+Like load_object_from_xml, but loads
+from the XML file named by filename.
+
+"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)
+
+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.
+
+"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.
+
+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.
+
+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.
+
+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.
+
+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.
+
+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".
+
+Save self’s state as XML into the
+file named filename. The XML is obtained
+by calling save_to_xml.
+
+Fill self’s state into a new xml node, return that node.
+
+This method calls pre_save, then fill_into_xml, then post_save.
+
+Node factory function synopsis:
+
+-or-
+
+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:
+
+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.
+
+Node factory function synopsis:
+
+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.
+
+Initializer.
+
+(overridden) true if at least one of our nodes is_present_in? obj.
+
+The instance methods of this module are automatically added as class
+methods to a class that includes XML::Mapping.
+
+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.
+
+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.
+
+return the current default mapping (:_default initially, or the value set
+with the latest call to use_mapping)
+
+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".
+
+Create a new instance of this class from the XML contained in the file named
+filename. Calls load_from_xml internally.
+
+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.
+
+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)
+
+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
+
+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
+
+Node factory function synopsis:
+
+hash_node :attrname, base_path,
+per_hashelement_path, key_path
+
+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.
+
+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.
+
+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.
+
+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.
+
+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.
+
+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.
+
+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).
+
+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.
+
+Node factory function synopsis:
+
+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.
+
+Node factory function synopsis:
+
+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.
+
+Initializer. path (a string denoting an XPath expression) is the
+location of the subtree.
+
+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.
+
+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.
+
+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.
+
+(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).
+
+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.
+
+(overridden) returns true if and only if the value of this node’s
+attribute in obj is non-nil.
+
+(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.
+
+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.
+
+(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.
+
+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.
+
+Node factory function synopsis:
+
+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
+
+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.
+
+create and compile a new XPath. xpathstr is the string
+representation (XPath pattern) of the path
+
+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.
+
+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).
+
+loop over all sub-nodes of node that match this XPath.
+
+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).
+
+the value of the attribute.
+
+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.
+
+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
+
+as the more pleasant-looking variant
+
+with the added bonus that path may not only be an XML::XXPath instance, but also just a String containing the XPath expression.
+
+see XML::XXPath#all
+
+see XML::XXPath#create_new
+
+see XML::XXPath#each
+
+see XML::XXPath#first
+
+2005-12-07 Olaf Klischat
+
+2005/11/30 Olaf Klischat
+
+2005/07/07 Olaf Klischat
+
+2005/07/04 Olaf Klischat
+
+2005/06/29 Olaf Klischat
+
+2005/03/30 Olaf Klischat
+
+2005/03/05 Olaf Klischat
+
+2005/03/03 Olaf Klischat
+
+2005/02/28 Olaf Klischat
+
+2005/02/13 Olaf Klischat
+
+2005/02/12 Olaf Klischat
+
+2005/01/27 Olaf Klischat
+
+2005/01/23 Olaf Klischat
+
+2005/01/10 Olaf Klischat
+
+2005/01/07 Olaf Klischat
+
+2004/12/30 Olaf Klischat
+
+2004/12/30 Olaf Klischat
+
+2004/12/21 Olaf Klischat
+
+2004/12/20 Olaf Klischat
+
+2004/12/08 Olaf Klischat
+
+2004/12/02 Olaf Klischat
+
+2004/11/27 Olaf Klischat
+
+2004/11/25 Olaf Klischat
+
+stone age Olaf Klischat
+
+Xml-mapping is an easy to use, extensible library that allows you to
+semi-automatically map Ruby objects to XML trees and vice versa.
+
+For downloading the latest version, CVS repository access etc. go to:
+
+rubyforge.org/projects/xml-mapping/
+
+(example document stolen + extended from www.castor.org/xml-mapping.html)
+
+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.
+
+For example, in the definition
+
+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.
+
+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:
+
+The semantics of default values are as follows:
+
+(when defining your own initializer, you’ll have to call the
+inherited initialize method in order to get this behaviour)
+
+This implies that:
+
+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
+
+, 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:
+
+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:
+
+, and you want to map all the names to a string array attribute
+names, you could do it like this:
+
+usage:
+
+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.
+
+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:
+
+This can be mapped to Ruby like this:
+
+Usage:
+
+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:
+
+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:
+
+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:
+
+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.
+
+I’ll shed some more light on how single-attribute nodes add mapped
+attributes to Ruby classes. An attribute declaration like
+
+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:
+
+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:
+
+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.
+
+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):
+
+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:
+
+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.
+
+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:
+
+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.:
+
+(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.
+
+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:
+
+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.
+
+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.
+
+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:
+
+(we only save year, month and day to make this example shorter), and the
+mapping class declaration to look like this:
+
+(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:
+
+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.
+
+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.
+
+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):
+
+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.
+
+The xml-mapping core "operates" node types as follows:
+
+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:
+
+, 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:
+
+(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.
+
+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.
+
+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
+
+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).
+
+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) 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.
+
+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.
+
+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
+
+, you’ll get:
+
+XML::XXPath is explained in more
+detail in the reference documentation and the README_XPATH file.
+
+Ruby’s.
+
+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
+
+, you’ll get:
+
+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:
+
+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.
+
+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:
+
+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").
+
+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 created resp. previously existing, matching elements are returned.
+
+Examples:
+
+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:
+
+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.
+
+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:
+
+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.
+
+doc/xpath_impl_notes.txt contains some documentation on the
+implementation of xml-xxpath.
+
+Ruby’s.
+
+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
+
+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:
+
+The all function is then trivially implemented on top of this:
+
+…and first, create_new etc. are even more trivial
+frontends to that.
+
+The implementations of the @creator_procs look like this:
+
+..and the implementation of @reader_proc looks like this:
+
+xml-mapping — bidirectional Ruby-XML mapper
+
+xml-mapping — bidirectional Ruby-XML mapper
+
+xml-mapping — bidirectional Ruby-XML mapper
+
+xml-mapping — bidirectional Ruby-XML mapper
+
+xxpath — XPath implementation for Ruby, including write access
+
+xxpath — XPath implementation for Ruby, including write access
+
+xxpath — XPath implementation for Ruby, including write access
+
+xxpath — XPath implementation for Ruby, including write access
+
-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
+
+
+
+ Class
+ Numeric
+
+
+
+ In:
+
+
+ lib/xml/mapping/core_classes_mapping.rb
+
+
+
+
+
+ Parent:
+
+ Object
+
+ Methods
+
+
+ Public Class methods
+
+
+ # 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
+
+
+ # 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
+
+
+ # File lib/xml/mapping/core_classes_mapping.rb, line 29
+29: def text
+30: self.to_s
+31: end
+
+
+
+
+
+ Module
+ REXML
+
+
+
+ In:
+
+
+ lib/xml/xxpath_methods.rb
+
+
+
+
+
+
+
+ Class
+ REXML::Child
+
+
+
+ In:
+
+
+ lib/xml/xxpath_methods.rb
+
+
+
+
+
+ Parent:
+
+ Object
+
+ Included Modules
+
+
+
+
+
+
+ Class
+ REXML::Parent
+
+
+
+ In:
+
+
+ lib/xml/rexml_ext.rb
+
+
+
+
+
+ Parent:
+
+ Object
+
+ Methods
+
+
+ Public Instance methods
+
+
+ # 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
+
+
+ # 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
+
+
+ # 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
+
+
+
+
+
+ Class
+ REXML::Text
+
+
+
+ In:
+
+
+ lib/xml/xxpath/steps.rb
+
+
+
+
+
+ Parent:
+
+ Object
+
+ External Aliases
+
+
+
+
+
+ value
+ ->
+ text
+
+
+
+
+call-compatibility w/ REXML::Element
+
+
+
+
+ value=
+ ->
+ text=
+
+
+
+
+ Class
+ String
+
+
+
+ In:
+
+
+ lib/xml/mapping/core_classes_mapping.rb
+
+
+
+
+
+ Parent:
+
+ Object
+
+ Methods
+
+
+ Public Class methods
+
+
+ # 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
+
+
+ # 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
+
+
+ # File lib/xml/mapping/core_classes_mapping.rb, line 10
+10: def text
+11: self
+12: end
+
+
+
+
+
+ Module
+ XML
+
+
+
+ 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
+
+
+
+ Copyright (C) 2004-2006 Olaf Klischat
+
+
+ Classes and Modules
+
+ Module XML::Mapping
+Module XML::XXPathMethods
+Class XML::MappingError
+Class XML::XXPath
+Class XML::XXPathError
+
+
+
+
+
+ Module
+ XML::Mapping
+
+
+
+ In:
+
+
+ lib/xml/mapping/version.rb
+
+
+
+
+ lib/xml/mapping/base.rb
+
+
+
+ lib/xml/mapping/standard_nodes.rb
+
+
+ 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>#
+
+Methods
+
+
+ Classes and Modules
+
+ Module XML::Mapping::ClassMethods
+Class XML::Mapping::ArrayNode
+Class XML::Mapping::BooleanNode
+Class XML::Mapping::ChoiceNode
+Class XML::Mapping::HashNode
+Class XML::Mapping::Node
+Class XML::Mapping::NumericNode
+Class XML::Mapping::ObjectNode
+Class XML::Mapping::SingleAttributeNode
+Class XML::Mapping::SubObjectBaseNode
+Class XML::Mapping::TextNode
+
+ Constants
+
+
+
+
+
+ VERSION
+ =
+ '0.9'
+ Public Class methods
+
+
+ # 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
+
+
+ # 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
+
+
+ # 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
+
+
+ # 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
+
+
+ # 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
+
+
+ # File lib/xml/mapping/base.rb, line 162
+162: def initialize(*args)
+163: initialize_xml_mapping
+164: end
+
+ Public Instance methods
+
+
+ # 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
+
+
+ # 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
+
+
+ # 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
+
+
+ # File lib/xml/mapping/base.rb, line 199
+199: def post_load(options={:mapping=>:_default})
+200: end
+
+
+ # File lib/xml/mapping/base.rb, line 187
+187: def pre_load(xml, options={:mapping=>:_default})
+188: end
+
+
+ # 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
+
+
+ # 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
+
+
+ # 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
+
+
+
+
+
+ Class
+ XML::Mapping::ArrayNode
+
+
+
+ In:
+
+
+ lib/xml/mapping/standard_nodes.rb
+
+
+
+
+
+ Parent:
+
+
+ SubObjectBaseNode
+
+
+
+ array_node :_attrname_, _per_arrelement_path_
+ [, :default_value=>_obj_]
+ [, :optional=>true]
+ [, :class=>_c_]
+ [, :marshaller=>_proc_]
+ [, :unmarshaller=>_proc_]
+ [, :mapping=>_m_]
+ [, :sub_mapping=>_sm_]
+
+
+ array_node :_attrname_, _base_path_, _per_arrelement_path_
+ [keyword args the same]
+
+
+ <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
+
+ Public Class methods
+
+
+ # 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
+
+
+
+
+
+ Class
+ XML::Mapping::BooleanNode
+
+
+
+ In:
+
+
+ lib/xml/mapping/standard_nodes.rb
+
+
+
+
+
+ Parent:
+
+
+ SingleAttributeNode
+
+
+
+ boolean_node :_attrname_, _path_,
+ _true_value_, _false_value_ [, :default_value=>_obj_]
+ [, :optional=>true]
+ [, :mapping=>_m_]
+
+Methods
+
+ Public Class methods
+
+
+ # 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
+
+
+
+
+
+ Class
+ XML::Mapping::ChoiceNode
+
+
+
+ In:
+
+
+ lib/xml/mapping/standard_nodes.rb
+
+
+
+
+
+ Parent:
+
+
+ Node
+
+
+ Methods
+
+
+ Public Class methods
+
+
+ # 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
+
+
+ # 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
+
+
+ # 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
+
+
+ # 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
+
+
+ # 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
+
+
+
+
+
+ Module
+ XML::Mapping::ClassMethods
+
+
+
+ In:
+
+
+ lib/xml/mapping/base.rb
+
+
+
+ Methods
+
+
+ Public Instance methods
+
+
+ # 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
+
+
+ # 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
+
+
+ # File lib/xml/mapping/base.rb, line 291
+291: def default_mapping
+292: @default_mapping
+293: end
+
+
+ # 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
+
+
+ # 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
+
+
+ # 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
+
+
+ # 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
+
+
+ # 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
+
+
+ # 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
+
+
+
+
+
+ Class
+ XML::Mapping::HashNode
+
+
+
+ In:
+
+
+ lib/xml/mapping/standard_nodes.rb
+
+
+
+
+
+ Parent:
+
+
+ SubObjectBaseNode
+
+
+
+ hash_node :_attrname_, _per_hashelement_path_, _key_path_
+ [, :default_value=>_obj_]
+ [, :optional=>true]
+ [, :class=>_c_]
+ [, :marshaller=>_proc_]
+ [, :unmarshaller=>_proc_]
+ [, :mapping=>_m_]
+ [, :sub_mapping=>_sm_]
+
+
+
+
+ [keyword args the same]
+
+Methods
+
+ Public Class methods
+
+
+ # 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
+
+
+
+
+
+ Class
+ XML::Mapping::Node
+
+
+
+ In:
+
+
+ lib/xml/mapping/base.rb
+
+
+
+
+
+ Parent:
+
+ Object
+
+ Methods
+
+
+ External Aliases
+
+
+
+
+
+ xml_to_obj
+ ->
+ default_xml_to_obj
+
+
+ obj_to_xml
+ ->
+ default_obj_to_xml
+ Public Class methods
+
+
+ # 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
+
+
+ # 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
+
+
+ # 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
+
+
+ # File lib/xml/mapping/base.rb, line 580
+580: def is_present_in? obj
+581: true
+582: end
+
+
+ # File lib/xml/mapping/base.rb, line 573
+573: def obj_initializing(obj,mapping)
+574: end
+
+
+ # File lib/xml/mapping/base.rb, line 558
+558: def obj_to_xml(obj,xml)
+559: raise "abstract method called"
+560: end
+
+
+ # File lib/xml/mapping/base.rb, line 548
+548: def xml_to_obj(obj,xml)
+549: raise "abstract method called"
+550: end
+
+
+
+
+
+ Class
+ XML::Mapping::NumericNode
+
+
+
+ In:
+
+
+ lib/xml/mapping/standard_nodes.rb
+
+
+
+
+
+ Parent:
+
+
+ SingleAttributeNode
+
+
+
+ numeric_node :_attrname_, _path_ [, :default_value=>_obj_]
+ [, :optional=>true]
+ [, :mapping=>_m_]
+
+Methods
+
+ Public Class methods
+
+
+ # 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
+
+
+
+
+
+ Class
+ XML::Mapping::ObjectNode
+
+
+
+ In:
+
+
+ lib/xml/mapping/standard_nodes.rb
+
+
+
+
+
+ Parent:
+
+
+ SubObjectBaseNode
+
+
+
+ object_node :_attrname_, _path_ [, :default_value=>_obj_]
+ [, :optional=>true]
+ [, :class=>_c_]
+ [, :marshaller=>_proc_]
+ [, :unmarshaller=>_proc_]
+ [, :mapping=>_m_]
+ [, :sub_mapping=>_sm_]
+
+Methods
+
+ Public Class methods
+
+
+ # 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
+
+
+
+
+
+ Class
+ XML::Mapping::SingleAttributeNode
+
+
+
+ In:
+
+
+ lib/xml/mapping/base.rb
+
+
+
+
+
+ Parent:
+
+
+ Node
+
+
+ Methods
+
+
+ Public Class methods
+
+
+ # 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
+
+
+ # 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
+
+
+ # File lib/xml/mapping/base.rb, line 664
+664: def extract_attr_value(xml)
+665: raise "abstract method called"
+666: end
+
+
+ # File lib/xml/mapping/base.rb, line 630
+630: def initialize_impl(*args)
+631: end
+
+
+ # File lib/xml/mapping/base.rb, line 712
+712: def is_present_in? obj
+713: nil != obj.send("#{@attrname}""#{@attrname}")
+714: end
+
+
+ # File lib/xml/mapping/base.rb, line 685
+685: def set_attr_value(xml, value)
+686: raise "abstract method called"
+687: end
+
+
+
+
+
+ Class
+ XML::Mapping::SingleAttributeNode::NoAttrValueSet
+
+
+
+ In:
+
+
+ lib/xml/mapping/base.rb
+
+
+
+
+
+ Parent:
+
+ XXPathError
+
+
+
+
+
+ Class
+ XML::Mapping::SubObjectBaseNode
+
+
+
+ In:
+
+
+ lib/xml/mapping/standard_nodes.rb
+
+
+
+
+
+ Parent:
+
+
+ SingleAttributeNode
+
+
+ Methods
+
+ Public Class methods
+
+
+ # 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
+
+
+
+
+
+ Class
+ XML::Mapping::TextNode
+
+
+
+ In:
+
+
+ lib/xml/mapping/standard_nodes.rb
+
+
+
+
+
+ Parent:
+
+
+ SingleAttributeNode
+
+
+
+ text_node :_attrname_, _path_ [, :default_value=>_obj_]
+ [, :optional=>true]
+ [, :mapping=>_m_]
+
+Methods
+
+ Public Class methods
+
+
+ # 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
+
+
+
+
+
+ Class
+ XML::MappingError
+
+
+
+ In:
+
+
+ lib/xml/mapping/base.rb
+
+
+
+
+
+ Parent:
+
+ RuntimeError
+
+
+
+
+
+ Class
+ XML::XXPath
+
+
+
+ In:
+
+
+ lib/xml/rexml_ext.rb
+
+
+
+
+ lib/xml/xxpath.rb
+
+
+
+ lib/xml/xxpath/steps.rb
+
+
+
+
+ Parent:
+
+ Object
+
+ Methods
+
+
+ Public Class methods
+
+
+ # 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
+
+
+ # 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
+
+
+ # File lib/xml/xxpath.rb, line 111
+111: def create_new(base_node)
+112: first(base_node,:create_new=>true)
+113: end
+
+
+ # File lib/xml/xxpath.rb, line 57
+57: def each(node,options={},&block)
+58: all(node,options).each(&block)
+59: end
+
+
+ # 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
+
+
+
+
+
+ Class
+ XML::XXPath::Accessors::Attribute
+
+
+
+ In:
+
+
+ lib/xml/rexml_ext.rb
+
+
+
+
+ lib/xml/xxpath_methods.rb
+
+
+
+
+ Parent:
+
+ Object
+
+ Included Modules
+
+
+ Attributes
+
+
+
+
+
+ name
+ [W]
+
+
+
+ name
+ [R]
+
+
+
+ parent
+ [R]
+
+ Public Class methods
+
+
+ # 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
+
+
+ # File lib/xml/rexml_ext.rb, line 75
+75: def initialize(parent,name)
+76: @parent,@name = parent,name
+77: end
+
+ Public Instance methods
+
+
+ # 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
+
+
+ # File lib/xml/rexml_ext.rb, line 93
+93: def text
+94: parent.attributes[@name]
+95: end
+
+
+ # File lib/xml/rexml_ext.rb, line 97
+97: def text=(x)
+98: parent.attributes[@name] = x
+99: end
+
+
+
+
+
+ Module
+ XML::XXPath::Accessors::REXML
+
+
+
+ In:
+
+
+
+
+
+
+ Module
+ XML::XXPath::Accessors::UnspecifiednessSupport
+
+
+
+ In:
+
+
+ lib/xml/rexml_ext.rb
+
+
+
+ Methods
+
+
+ Public Class methods
+
+
+ # 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
+
+
+ # File lib/xml/rexml_ext.rb, line 24
+24: def unspecified=(x)
+25: @xml_xpath_unspecified = x
+26: end
+
+
+
+
+
+ Class
+ XML::XXPathError
+
+
+
+ In:
+
+
+ lib/xml/xxpath.rb
+
+
+
+
+
+ Parent:
+
+ RuntimeError
+
+
+
+
+
+ Module
+ XML::XXPathMethods
+
+
+
+ In:
+
+
+ lib/xml/xxpath_methods.rb
+
+
+
+
+ path.first(xml_element)
+
+
+ xml_element.first(path)
+
+Methods
+
+
+ Public Instance methods
+
+
+ # File lib/xml/xxpath_methods.rb, line 34
+34: def all(path,options={})
+35: to_xxpath(path).all self, options
+36: end
+
+
+ # File lib/xml/xxpath_methods.rb, line 39
+39: def create_new(path)
+40: to_xxpath(path).create_new self
+41: end
+
+
+ # 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
+
+
+ # File lib/xml/xxpath_methods.rb, line 29
+29: def first(path,options={})
+30: to_xxpath(path).first self, options
+31: end
+
+ ChangeLog
+
+
+
+
+ Path:
+ ChangeLog
+
+
+
+ Last Update:
+ Fri Mar 31 02:05:33 CEST 2006
+
+ * ChangeLog file
+
+
+ * bugfix: clone default values to avoid external modifications
+
+
+ * release 0.8
+
+
+ * xml/xpath / XML::XPath -> xml/xxpath / XML::XXPath, license ->
+ Ruby's
+
+
+ * when creating elt[@attr='value'] path elements, add a new
+ element if one with @attr='value' already existed
+
+
+ * add_accessor: check for existing accessors.
+
+
+ * better support for inheritance among mapping
+ classes
+
+
+ * "polymorphic" nodes via root element
+ name. SubObjectBaseNode-based nodes es use node polymorphy when
+ no explicit node marshaller/unmarshaller has been sp ecified.
+
+
+ * mapping root elt name => mapping class;
+ XML::Mapping::load_object_from_* implemented
+
+
+ * IntNode renamed & generalized to NumericNode
+
+
+ * renaming *_rexml => *_xml
+
+
+ * special exception NoAttrValueSet for indicating absence of a
+ specific attribute in an XML source
+
+
+ * some more documentation, Node.obj_initializing, setting node
+ values to defaults on initialization
+
+
+ * root_element_name
+
+
+ * 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.
+
+
+ * array node writing, hash node writing
+
+
+ * xpath: create_new flag, + convenience method
+
+
+ * node classes
+
+
+ * hash_node
+
+
+ * xpath: attribute nodes
+
+ * xml_mapping: retargeted from REXML::XPath to XML::XPath
+
+
+ * xpath: write accessors
+
+
+ * xpath: read access seems to work
+
+
+ * array_node
+
+
+ * see http://rubygarden.org/ruby?XmlMapping
+
+
+ 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
+Download
+Contents of this Document
+
+
+
+
+Example
+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'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.
+
+Single-attribute Nodes
+
+ class Address
+ include XML::Mapping
+
+ text_node :city, "City"
+ text_node :state, "State"
+ numeric_node :zip, "ZIP"
+ text_node :street, "Street"
+ end
+
+Default Values
+
+ class Signature
+ include XML::Mapping
+
+ text_node :position, "Position", :default_value=>"Some Employee"
+ end
+
+
+
+
+
+
+
+
+
+Single-attribute Nodes with Sub-objects
+
+ array_node :signatures, "Signed-By", "Signature", :class=>Signature, :default_value=>[]
+
+
+ <Signed-By>
+ <Signature>
+ [marshalled object sig1]
+ </Signature>
+ <Signature>
+ [marshalled object sig2]
+ </Signature>
+ <Signature>
+ [marshalled object sig3]
+ </Signature>
+ </Signed-By>
+
+
+ <?xml version="1.0" encoding="ISO-8859-1"?>
+
+ <people>
+ <names>
+ <name>Jim</name>
+ <name>Susan</name>
+ <name>Herbie</name>
+ <name>Nancy</name>
+ </names>
+ </people>
+
+
+ require 'xml/mapping'
+ class People
+ include XML::Mapping
+ array_node :names, "names", "name", :class=>String
+ end
+
+
+ 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>
+
+Polymorphic Sub-objects, Marshallers/Unmarshallers
+
+ <?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>
+
+
+ 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
+
+
+ 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>
+
+
+ <Signature>
+ <Name>John Doe</Name>
+ <Position>product manager</Position>
+ <signed-on>
+ <day>13</day>
+ <month>2</month>
+ <year>2005</year>
+ </signed-on>
+ </Signature>
+
+
+ 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
+
+
+ array_node :birthdays, "birthdays", "birthday",
+ :unmarshaller=> <as above>,
+ :marshaller=> <as above>
+
+Attribute Handling Details, Augmenting Existing Classes
+
+ text_node :city, "City"
+
+
+ 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>
+
+
+ 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
+choice_node
+
+ 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"]>
+
+
+ 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>
+
+Readers/Writers
+
+ 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'/>
+
+
+ class SomeClass
+ include XML::Mapping
+
+ ....
+
+ node :reader=>proc{|obj,xml| ...},
+ :writer=>proc{|obj,xml| ...}
+ end
+
+Multiple Mappings per Class
+
+ 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
+
+
+ object_node :address, "address", :class=>Address, :sub_mapping=>:other
+
+Defining your own Node Types
+Example
+
+ <Signature>
+ <Name>John Doe</Name>
+ <Position>product manager</Position>
+ <signed-on>
+ <day>13</day>
+ <month>2</month>
+ <year>2005</year>
+ </signed-on>
+ </Signature>
+
+
+ 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
+
+
+ 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
+
+Element order in created XML documents
+Node Types Are Ruby Classes
+
+ Node
+ +-SingleAttributeNode
+ | +-SubObjectBaseNode
+ | | +-ObjectNode
+ | | +-ArrayNode
+ | | +-HashNode
+ | +-TextNode
+ | +-NumericNode
+ | +-BooleanNode
+ +-ChoiceNode
+
+How Node Types Work
+Node Initialization
+
+ class MyMappingClass
+ include XML::Mapping
+
+ my_node :foo, "bar", 42, :hi=>"ho"
+ end
+
+
+ def initialize(*args)
+ myparam1,myparam2,...,myparamx,*args = super(*args)
+
+ .... process the myparam1,myparam2,...,myparamx ....
+
+ # return still unprocessed args
+ args
+ end
+
+Node Operation during Marshalling and Unmarshalling
+Basic Node Types Overview
+Node
+SingleAttributeNode
+
+ extract_attr_value(xml)
+
+ set_attr_value(xml, value)
+
+SubObjectBaseNode
+XPath Interpreter
+
+ <foo>
+ <bar>
+ <baz key="ab">hello</baz>
+ <baz key="xy">goodbye</baz>
+ </bar>
+ </foo>
+
+
+ <foo>
+ <bar>
+ <baz key='ab'>hello</baz>
+ <baz key='xy'>goodbye</baz>
+ </bar>
+ <bar/>
+ <bar>
+ <baz key='hiho'/>
+ </bar>
+ </foo>
+
+License
+README_XPATH
+
+
+
+
+ Path:
+ README_XPATH
+
+
+
+ Last Update:
+ Tue Aug 08 01:33:46 CEST 2006
+ XML-XXPATH
+Overview, Motivation
+
+ <foo>
+ <bar>
+ <baz key='ab'>hello</baz>
+ <baz key='xy'>goodbye</baz>
+ </bar>
+ </foo>
+
+
+ <foo>
+ <bar>
+ <baz key='ab'>hello</baz>
+ <baz key='xy'>goodbye</baz>
+ </bar>
+ <bar/>
+ <bar><baz key='hiho'/></bar>
+ </foo>
+
+
+
+Usage
+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"]
+
+
+ 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> ... </>]
+
+Write Access
+
+
+
+ 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
+
+
+ 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
+
+Pathological Cases
+
+ 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
+
+Implentation notes
+License
+TODO.txt
+
+
+
+
+ Path:
+ TODO.txt
+
+
+
+ Last Update:
+ Wed Apr 19 07:58:12 CEST 2006
+
+
+
+
+
+
+
+
+
+
+
+
+
+xpath_impl_notes.txt
+
+
+
+
+ Path:
+ doc/xpath_impl_notes.txt
+
+
+
+ Last Update:
+ Mon Jul 04 02:00:31 CEST 2005
+ latest design (12/2004)
+
+
+
+ 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
+
+
+ @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)
+ }
+
+
+ @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
+ }
+
+
+ base.rb
+
+
+
+
+ Path:
+ lib/xml/mapping/base.rb
+
+
+
+ Last Update:
+ Thu May 04 05:30:32 CEST 2006
+
+ Copyright (C) 2004-2006 Olaf Klischat
+
+
+ Required files
+
+ core_classes_mapping.rb
+
+
+
+
+ Path:
+ lib/xml/mapping/core_classes_mapping.rb
+
+
+
+ Last Update:
+ Fri Mar 31 01:11:26 CEST 2006
+ standard_nodes.rb
+
+
+
+
+ Path:
+ lib/xml/mapping/standard_nodes.rb
+
+
+
+ Last Update:
+ Mon May 22 06:34:03 CEST 2006
+
+ Copyright (C) 2004,2005 Olaf Klischat
+
+
+ Required files
+
+ version.rb
+
+
+
+
+ Path:
+ lib/xml/mapping/version.rb
+
+
+
+ Last Update:
+ Wed Oct 05 04:28:00 CEST 2005
+
+ Copyright (C) 2004,2005 Olaf Klischat
+
+
+ mapping.rb
+
+
+
+
+ Path:
+ lib/xml/mapping.rb
+
+
+
+ Last Update:
+ Mon Apr 03 02:56:24 CEST 2006
+
+ Copyright (C) 2004-2006 Olaf Klischat
+
+
+ Required files
+
+ rexml_ext.rb
+
+
+
+
+ Path:
+ lib/xml/rexml_ext.rb
+
+
+
+ Last Update:
+ Mon Apr 03 02:57:40 CEST 2006
+
+ Copyright (C) 2004-2006 Olaf Klischat
+
+
+ Required files
+
+ steps.rb
+
+
+
+
+ Path:
+ lib/xml/xxpath/steps.rb
+
+
+
+ Last Update:
+ Mon May 01 01:41:30 CEST 2006
+
+ Copyright (C) 2004-2006 Olaf Klischat
+
+
+ xxpath_methods.rb
+
+
+
+
+ Path:
+ lib/xml/xxpath_methods.rb
+
+
+
+ Last Update:
+ Mon Apr 03 02:57:47 CEST 2006
+
+ Copyright (C) 2004-2006 Olaf Klischat
+
+
+ xxpath.rb
+
+
+
+
+ Path:
+ lib/xml/xxpath.rb
+
+
+
+ Last Update:
+ Mon May 01 01:27:10 CEST 2006
+
+ Copyright (C) 2004-2006 Olaf Klischat
+
+
+ Required files
+
+ Classes
+
+ REXML
+ REXML::Child
+ REXML::Parent
+ REXML::Text
+ String
+ XML
+ XML::Mapping
+ XML::Mapping::ArrayNode
+ XML::Mapping::BooleanNode
+ XML::Mapping::ChoiceNode
+ XML::Mapping::ClassMethods
+ XML::Mapping::HashNode
+ XML::Mapping::Node
+ XML::Mapping::NumericNode
+ XML::Mapping::ObjectNode
+ XML::Mapping::SingleAttributeNode
+ XML::Mapping::SingleAttributeNode::NoAttrValueSet
+ XML::Mapping::SubObjectBaseNode
+ XML::Mapping::TextNode
+ XML::MappingError
+ XML::XXPath
+ XML::XXPath::Accessors::Attribute
+ XML::XXPath::Accessors::REXML
+ XML::XXPath::Accessors::UnspecifiednessSupport
+ XML::XXPathError
+ XML::XXPathMethods
+ Files
+
+ README
+ README_XPATH
+ TODO.txt
+ doc/xpath_impl_notes.txt
+ lib/xml/mapping.rb
+ lib/xml/mapping/base.rb
+ lib/xml/mapping/core_classes_mapping.rb
+ lib/xml/mapping/standard_nodes.rb
+ lib/xml/mapping/version.rb
+ lib/xml/rexml_ext.rb
+ lib/xml/xxpath.rb
+ lib/xml/xxpath/steps.rb
+ lib/xml/xxpath_methods.rb
+ Methods
+
+ 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)
+ XML-MAPPING:
Download
unsuppress
+ +# File lib/xml/rexml_ext.rb, line 155 +def warn(msg) +end+
# File lib/xml/mapping/core_classes_mapping.rb, line 17 +def self.load_from_xml(xml, options={:mapping=>:_default}) + begin + Integer(xml.text) + rescue ArgumentError + Float(xml.text) + end +end+
# File lib/xml/mapping/core_classes_mapping.rb, line 25 +def fill_into_xml(xml, options={:mapping=>:_default}) + xml.text = self.to_s +end+
# File lib/xml/mapping/core_classes_mapping.rb, line 29 +def text + self.to_s +end+
Xml-mapping is an easy to use, extensible library that allows you to +semi-automatically map Ruby objects to XML trees and +vice versa.
+ +<?xml version="1.0" encoding="ISO-8859-1"?> + +<item reference="RF-0001"> + <Description>Stuffed Penguin</Description> + <Quantity>10</Quantity> + <UnitPrice>8.95</UnitPrice> +</item>+ +
class Item + include XML::Mapping + + text_node :ref, "@reference" + text_node :descr, "Description" + numeric_node :quantity, "Quantity" + numeric_node :unit_price, "UnitPrice" + + def total_price + quantity*unit_price + end +end ++ +
i = Item.load_from_file("item.xml") +=> #<Item:0xb7888c90 @ref="RF-0001" @quantity=10, @descr="Stuffed Penguin", @unit_price=8.95> + +i.unit_price = 42.23 +xml=i.save_to_xml #convert to REXML node; there's also o.save_to_file(name) +xml.write($stdout,2) + +<item reference="RF-0001"> + <Description>Stuffed Penguin</Description> + <Quantity>10</Quantity> + <UnitPrice>42.23</UnitPrice> +</item> ++ +
This is the most trivial example – the mapper supports arbitrary array and +hash (map) nodes, object (reference) nodes and arrays/hashes of those, +polymorphic mappings, multiple mappings per class, fully programmable +mappings and arbitrary user-defined node types. Read the project documentation for +more information.
+# File lib/xml/rexml_ext.rb, line 142 +def each_on_axis(axis, &block) + send :"each_on_axis_#{axis}", &block +end+
# File lib/xml/rexml_ext.rb, line 118 +def each_on_axis_child + if respond_to? :attributes + attributes.each_key do |name| + yield XML::XXPath::Accessors::Attribute.new(self, name, false) + end + end + each_child do |c| + yield c + end +end+
# File lib/xml/rexml_ext.rb, line 129 +def each_on_axis_descendant(&block) + each_on_axis_child do |c| + block.call c + if REXML::Parent===c + c.each_on_axis_descendant(&block) + end + end +end+
# File lib/xml/rexml_ext.rb, line 138 +def each_on_axis_self + yield self +end+
# File lib/xml/mapping/core_classes_mapping.rb, line 2 +def self.load_from_xml(xml, options={:mapping=>:_default}) + xml.text +end+
# File lib/xml/mapping/core_classes_mapping.rb, line 6 +def fill_into_xml(xml, options={:mapping=>:_default}) + xml.text = self +end+
# File lib/xml/mapping/core_classes_mapping.rb, line 10 +def text + self +end+
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:
+consider switching to YARD
+reasons: parameter/return type metadata, (maybe) plugin for the code +snippet inclusion stuff
+document/show usage of default_when_xpath_err outside node type +implementations
+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.
+would need support in xxpath
+should probably be built on top of the Ruby 2.0 lazy enumeration stuff
+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
+xml-mapping – bidirectional Ruby-XML mapper
+ +Copyright (C) 2004,2005 Olaf Klischat+ +
xml-mapping – bidirectional Ruby-XML mapper
+ +Copyright (C) 2004-2010 Olaf Klischat+ +
xxpath – XPath implementation for Ruby, including write access
+ +Copyright (C) 2004-2006 Olaf Klischat+ +
This is the central interface module of the xml-mapping library.
+ +Including this module in your classes adds XML +mapping capabilities to them.
+ +<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>+ +
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 ++ +
c = Company.load_from_file('company.xml') +=> #<Company:0x007fb015492e98 @name="ACME inc.", @address=#<Address:0x007fb015492380 @city="Berlin", @zip=10113>, @customers=[#<Customer:0x007fb015490030 @id="jim", @name="James Kirk">, #<Customer:0x007fb01548eeb0 @id="ernie", @name="Ernie">, #<Customer:0x007fb01548da38 @id="bert", @name="Bert">]> +c.name +=> "ACME inc." +c.customers.size +=> 3 +c.customers[1] +=> #<Customer:0x007fb01548eeb0 @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:0x007fb015490030 @id="jim", @name="James Tiberius Kirk">, #<Customer:0x007fb01548eeb0 @id="ernie", @name="Ernie">, #<Customer:0x007fb01548da38 @id="bert", @name="Bert">, #<Customer:0x007fb01547f1b8 @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='jim'> + <name> + James Tiberius Kirk + </name> + </customer> + <customer id='ernie'> + <name> + Ernie + </name> + </customer> + <customer id='bert'> + <name> + Bert + </name> + </customer> + <customer id='cm'> + <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 ::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).
+ +It is recommended that if your class does not have required
+initialize
method arguments. The XML
+loader attempts to create a new object using the new
method.
+If this fails because the initializer expects an argument, then the loader
+calls allocate
instead. allocate
bypasses the
+initializer. If your class must have initializer arguments, then you
+should verify that bypassing the initializer is acceptable.
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.
+ +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).
# File lib/xml/mapping/base.rb, line 493 +def self.add_node_class(c) + meth_name = c.name.split('::')[-1].gsub(/^(.)/){$1.downcase}.gsub(/(.)([A-Z])/){$1+"_"+$2.downcase} + ClassMethods.module_eval <<-EOS + def #{meth_name}(*args) + #{c.name}.new(self,*args) + end + EOS +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]
+ + + + +# File lib/xml/mapping/base.rb, line 134 +def self.class_and_mapping_for_root_elt_name(name) + (Classes_by_rootelt_names[name] || {}).each_pair{|mapping,classes| return [classes[0],mapping] } + nil +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).
+ + + + +# File lib/xml/mapping/base.rb, line 122 +def self.class_for_root_elt_name(name, options={:mapping=>:_default}) + # TODO: implement Hash read-only instead of this + # interface + Classes_by_rootelt_names.classes_for(name, options[:mapping])[-1] +end+
Like ::load_object_from_xml, +but loads from the XML file named by +filename.
+ + + + +# File lib/xml/mapping/base.rb, line 463 +def self.load_object_from_file(filename,options={:mapping=>nil}) + xml = REXML::Document.new(File.new(filename)) + load_object_from_xml xml.root, options +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 ::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)
+ + + + +# File lib/xml/mapping/base.rb, line 449 +def self.load_object_from_xml(xml,options={:mapping=>nil}) + if mapping = options[:mapping] + c = class_for_root_elt_name xml.name, :mapping=>mapping + else + c,mapping = class_and_mapping_for_root_elt_name(xml.name) + end + unless c + raise MappingError, "no mapping class for root element name #{xml.name}, mapping #{mapping.inspect}" + end + c.load_from_xml xml, :mapping=>mapping +end+
Initializer. Called (by Class#new) after self was created using +new.
+ +XML::Mapping's implementation calls initialize_xml_mapping.
+ + +# File lib/xml/mapping/base.rb, line 169 +def initialize(*args) + super(*args) + initialize_xml_mapping +end+
“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.
+ + + + +# File lib/xml/mapping/base.rb, line 181 +def fill_from_xml(xml, options={:mapping=>:_default}) + raise(MappingError, "undefined mapping: #{options[:mapping].inspect}") unless self.class.xml_mapping_nodes_hash.has_key?(options[:mapping]) + pre_load xml, :mapping=>options[:mapping] + self.class.all_xml_mapping_nodes(:mapping=>options[:mapping]).each do |node| + node.xml_to_obj self, xml + end + post_load :mapping=>options[:mapping] +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.
+ + + + +# File lib/xml/mapping/base.rb, line 216 +def fill_into_xml(xml, options={:mapping=>:_default}) + self.class.all_xml_mapping_nodes(:mapping=>options[:mapping]).each do |node| + node.obj_to_xml self,xml + end +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.
# File lib/xml/mapping/base.rb, line 159 +def initialize_xml_mapping(options={:mapping=>nil}) + self.class.all_xml_mapping_nodes(:mapping=>options[:mapping]).each do |node| + node.obj_initializing(self,options[:mapping]) + end +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.
+ + + + +# File lib/xml/mapping/base.rb, line 207 +def post_load(options={:mapping=>:_default}) +end+
This method is called immediately after self's state has been +filled into an XML element.
+ +The default implementation does nothing.
+ + + + +# File lib/xml/mapping/base.rb, line 253 +def post_save(xml, options={:mapping=>:_default}) +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.
+ + + + +# File lib/xml/mapping/base.rb, line 195 +def pre_load(xml, options={:mapping=>:_default}) +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 XML::Mapping::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”.
+ + + + +# File lib/xml/mapping/base.rb, line 245 +def pre_save(options={:mapping=>:_default}) + REXML::Element.new(self.class.root_element_name(:mapping=>options[:mapping])) +end+
Save self's state as XML into the +file named filename. The XML is obtained +by calling save_to_xml.
+ + + + +# File lib/xml/mapping/base.rb, line 259 +def save_to_file(filename, options={:mapping=>:_default}) + xml = save_to_xml :mapping=>options[:mapping] + File.open(filename,"w") do |f| + REXML::Formatters::Transitive.new(2,false).write(xml,f) + end +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.
+ + + + +# File lib/xml/mapping/base.rb, line 227 +def save_to_xml(options={:mapping=>:_default}) + xml = pre_save :mapping=>options[:mapping] + fill_into_xml xml, :mapping=>options[:mapping] + post_save xml, :mapping=>options[:mapping] + xml +end+
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>+ +
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.
+ + +# File lib/xml/mapping/standard_nodes.rb, line 263 +def initialize(*args) + path,path2,*args = super(*args) + base_path,per_arrelement_path = if path2 + [path,path2] + else + [".",path] + end + per_arrelement_path=per_arrelement_path[1..-1] if per_arrelement_path[0]==?/ + @base_path = XML::XXPath.new(base_path) + @per_arrelement_path = XML::XXPath.new(per_arrelement_path) + @reader_path = XML::XXPath.new(base_path+"/"+per_arrelement_path) + args +end+
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.
Initializer.
+ + +# File lib/xml/mapping/standard_nodes.rb, line 190 +def initialize(*args) + path,true_value,false_value,*args = super(*args) + @path = XML::XXPath.new(path) + @true_value = true_value; @false_value = false_value + args +end+
# File lib/xml/mapping/standard_nodes.rb, line 364 +def initialize(*args) + args = super(*args) + @choices = [] + path=nil + args.each do |arg| + next if [:if,:then,:elsif].include? arg + if path.nil? + path = (if [:else,:default,:otherwise].include? arg + :else + else + XML::XXPath.new arg + end) + else + raise XML::MappingError, "node expected, found: #{arg.inspect}" unless Node===arg + @choices << [path,arg] + + # undo what the node factory fcn did -- ugly ugly! would + # need some way to lazy-evaluate arg (a proc would be + # simple but ugly for the user), and then use some + # mechanism (a flag with dynamic scope probably) to tell + # the node factory fcn not to add the node to the + # xml_mapping_nodes + @owner.xml_mapping_nodes(:mapping=>@mapping).delete arg + path=nil + end + end + + raise XML::MappingError, "node missing at end of argument list" unless path.nil? + raise XML::MappingError, "no choices were supplied" if @choices.empty? + + [] +end+
(overridden) true if at least one of our nodes is_present_in? obj.
+ + + + +# File lib/xml/mapping/standard_nodes.rb, line 425 +def is_present_in? obj + # TODO: use Enumerable#any? + @choices.inject(false){|prev,(path,node)| prev or node.is_present_in?(obj)} +end+
# File lib/xml/mapping/standard_nodes.rb, line 419 +def obj_initializing(obj,mapping) + @choices[0][1].obj_initializing(obj,mapping) +end+
# File lib/xml/mapping/standard_nodes.rb, line 407 +def obj_to_xml(obj,xml) + @choices.each do |path,node| + if node.is_present_in? obj + node.obj_to_xml(obj,xml) + path.first(xml, :ensure_created=>true) + return true + end + end + # @choices[0][1].obj_to_xml(obj,xml) + raise XML::MappingError, "obj_to_xml: no choice present in object: #{obj.inspect}" +end+
# File lib/xml/mapping/standard_nodes.rb, line 397 +def xml_to_obj(obj,xml) + @choices.each do |path,node| + if path==:else or not(path.all(xml).empty?) + node.xml_to_obj(obj,xml) + return true + end + end + raise XML::MappingError, "xml_to_obj: no choice matched in: #{xml}" +end+
The instance methods of this module are automatically added as class +methods to a class that includes XML::Mapping.
+ +Add getter and setter methods for a new attribute named name (must +be a symbol or a string) to this class, taking care not to replace existing +getters/setters. This is a convenience method intended to be called from +Node class initializers.
+ + + + +# File lib/xml/mapping/base.rb, line 307 +def add_accessor(name) + # existing methods search. Search for symbols and strings + # to be compatible with Ruby 1.8 and 1.9. + methods = self.instance_methods + if methods[0].kind_of? Symbol + getter = :"#{name}" + setter = :"#{name}=" + else + getter = "#{name}" + setter = "#{name}=" + end + unless methods.include?(getter) + self.module_eval <<-EOS + attr_reader :#{name} + EOS + end + unless methods.include?(setter) + self.module_eval <<-EOS + attr_writer :#{name} + EOS + end +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. keyword +arguments are the same as for xml_mapping_nodes.
+ + + + +# File lib/xml/mapping/base.rb, line 393 +def all_xml_mapping_nodes(options={:mapping=>nil,:create=>true}) + # TODO: we could return a dynamic Enumerable here, or cache + # the array... + result = [] + if superclass and superclass.respond_to?(:all_xml_mapping_nodes) + result += superclass.all_xml_mapping_nodes options + end + result += xml_mapping_nodes options +end+
return the current default mapping (:_default initially, or the value set +with the latest call to #use_mapping)
+ + + + +# File lib/xml/mapping/base.rb, line 299 +def default_mapping + @default_mapping +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”.
+ + + + +# File lib/xml/mapping/base.rb, line 434 +def default_root_element_name + self.name.split('::')[-1].gsub(/^(.)/){$1.downcase}.gsub(/(.)([A-Z])/){$1+"-"+$2.downcase} +end+
Create a new instance of this class from the XML contained in the file named +filename. Calls #load_from_xml +internally.
+ + + + +# File lib/xml/mapping/base.rb, line 332 +def load_from_file(filename, options={:mapping=>:_default}) + xml = REXML::Document.new(File.new(filename)) + load_from_xml xml.root, :mapping=>options[:mapping] +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.
+ + + + +# File lib/xml/mapping/base.rb, line 342 +def load_from_xml(xml, options={:mapping=>:_default}) + raise(MappingError, "undefined mapping: #{options[:mapping].inspect}") unless xml_mapping_nodes_hash.has_key?(options[:mapping]) + # create the new object. It is recommended that the class + # have a no-argument initializer, so try new first. If that + # doesn't work, try allocate, which bypasses the initializer. + begin + obj = self.new + #TODO: this will normally invoke our base XML::Mapping#initialize, which calls + # obj.initialize_xml_mapping, which is called below again (with the correct :mapping parameter). + # obj.initialize_xml_mapping calls obj_initializing on all nodes. + # So obj_initializing may be called on the nodes twice for this initialization. + # Maybe document this for node writers? + rescue ArgumentError # TODO: this may hide real errors. + # how to statically check whether + # self self.new accepts an empty + # argument list? + obj = self.allocate + end + obj.initialize_xml_mapping :mapping=>options[:mapping] + obj.fill_from_xml xml, :mapping=>options[:mapping] + obj +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)
+ + + + +# File lib/xml/mapping/base.rb, line 417 +def root_element_name(name=nil, options={:mapping=>@default_mapping}) + if Hash===name # ugly... + options=name; name=nil + end + @root_element_names ||= {} + if name + Classes_by_rootelt_names.remove_class root_element_name, options[:mapping], self + @root_element_names[options[:mapping]] = name + Classes_by_rootelt_names.create_classes_for(name, options[:mapping]) << self + end + @root_element_names[options[:mapping]] || default_root_element_name +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
+ + + + +# File lib/xml/mapping/base.rb, line 290 +def use_mapping mapping + @default_mapping = mapping + xml_mapping_nodes_hash[mapping] ||= [] # create empty mapping node list if + # there wasn't one before so future calls + # to load/save_xml etc. w/ this mapping don't raise +end+
array of all nodes defined in this class, in the order of their definition. +Option :create specifies whether or not an empty array should be created +and returned if there was none before (if not, an exception is raised). +:mapping specifies the mapping the returned nodes must have been defined +in; nil means return all nodes regardless of their mapping
+ + + + +# File lib/xml/mapping/base.rb, line 373 +def xml_mapping_nodes(options={:mapping=>nil,:create=>true}) + unless options[:mapping] + return xml_mapping_nodes_hash.values.inject([]){|a1,a2|a1+a2} + end + options[:create] = true if options[:create].nil? + if options[:create] + xml_mapping_nodes_hash[options[:mapping]] ||= [] + else + xml_mapping_nodes_hash[options[:mapping]] || + raise(MappingError, "undefined mapping: #{options[:mapping].inspect}") + end +end+
# File lib/xml/mapping/base.rb, line 95 +def classes_for rootelt_name, mapping + (self[rootelt_name] || {})[mapping] || [] +end+
# File lib/xml/mapping/base.rb, line 92 +def create_classes_for rootelt_name, mapping + (self[rootelt_name] ||= {})[mapping] ||= [] +end+
# File lib/xml/mapping/base.rb, line 101 +def ensure_exists rootelt_name, mapping, clazz + clazzes = create_classes_for(rootelt_name, mapping) + clazzes << clazz unless clazzes.include? clazz +end+
# File lib/xml/mapping/base.rb, line 98 +def remove_class rootelt_name, mapping, clazz + classes_for(rootelt_name, mapping).delete clazz +end+
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.
+ +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.
+ + +# File lib/xml/mapping/standard_nodes.rb, line 328 +def initialize(*args) + path1,path2,path3,*args = super(*args) + base_path,per_hashelement_path,key_path = if path3 + [path1,path2,path3] + else + ["",path1,path2] + end + per_hashelement_path=per_hashelement_path[1..-1] if per_hashelement_path[0]==?/ + @base_path = XML::XXPath.new(base_path) + @per_hashelement_path = XML::XXPath.new(per_hashelement_path) + @key_path = XML::XXPath.new(key_path) + @reader_path = XML::XXPath.new(base_path+"/"+per_hashelement_path) + args +end+
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.
+ +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.
+ + + + +# File lib/xml/mapping/base.rb, line 538 +def initialize(owner,*args) + @owner = owner + if Hash===args[-1] + @options = args[-1] + args = args[0..-2] + else + @options={} + end + @mapping = @options[:mapping] || owner.default_mapping + owner.xml_mapping_nodes(:mapping=>@mapping) << self + XML::Mapping::Classes_by_rootelt_names.ensure_exists owner.root_element_name, @mapping, owner + if @options[:reader] + # override xml_to_obj in this instance with invocation of + # @options[:reader] + class << self + alias_method :default_xml_to_obj, :xml_to_obj + def xml_to_obj(obj,xml) + begin + @options[:reader].call(obj,xml,self.method(:default_xml_to_obj)) + rescue ArgumentError # thrown if @options[:reader] is a lambda (i.e. no Proc) with !=3 args (e.g. proc{...} in ruby1.8) + @options[:reader].call(obj,xml) + end + end + end + end + if @options[:writer] + # override obj_to_xml in this instance with invocation of + # @options[:writer] + class << self + alias_method :default_obj_to_xml, :obj_to_xml + def obj_to_xml(obj,xml) + begin + @options[:writer].call(obj,xml,self.method(:default_obj_to_xml)) + rescue ArgumentError # thrown if (see above) + @options[:writer].call(obj,xml) + end + end + end + end + args +end+
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 XML::Mapping::ChoiceNode#obj_to_xml.
+ + + + +# File lib/xml/mapping/base.rb, line 617 +def is_present_in? obj + true +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.
+ + + + +# File lib/xml/mapping/base.rb, line 610 +def obj_initializing(obj,mapping) +end+
# File lib/xml/mapping/base.rb, line 568 +def obj_to_xml(obj,xml) + begin + @options[:writer].call(obj,xml,self.method(:default_obj_to_xml)) + rescue ArgumentError # thrown if (see above) + @options[:writer].call(obj,xml) + end +end+
# File lib/xml/mapping/base.rb, line 554 +def xml_to_obj(obj,xml) + begin + @options[:reader].call(obj,xml,self.method(:default_xml_to_obj)) + rescue ArgumentError # thrown if @options[:reader] is a lambda (i.e. no Proc) with !=3 args (e.g. proc{...} in ruby1.8) + @options[:reader].call(obj,xml) + end +end+
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.
+ +# File lib/xml/mapping/standard_nodes.rb, line 46 +def initialize(*args) + path,*args = super(*args) + @path = XML::XXPath.new(path) + args +end+
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.
Initializer. path (a string denoting an XPath expression) is the +location of the subtree.
+ + +# File lib/xml/mapping/standard_nodes.rb, line 160 +def initialize(*args) + path,*args = super(*args) + @path = XML::XXPath.new(path) + args +end+
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.
+ +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.
+ + +# File lib/xml/mapping/base.rb, line 650 +def initialize(*args) + @attrname,*args = super(*args) + @owner.add_accessor @attrname + if @options[:optional] and not(@options.has_key?(:default_value)) + @options[:default_value] = nil + end + initialize_impl(*args) + args +end+
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.
+ + + + +# File lib/xml/mapping/base.rb, line 740 +def default_when_xpath_err # :yields: + begin + yield + rescue XML::XXPathError => err + raise NoAttrValueSet, "Attribute #{@attrname} not set (XXPathError: #{err})" + end +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).
+ + + + +# File lib/xml/mapping/base.rb, line 701 +def extract_attr_value(xml) + raise "abstract method called" +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.
+ + + + +# File lib/xml/mapping/base.rb, line 667 +def initialize_impl(*args) +end+
(overridden) returns true if and only if the value of this node's +attribute in obj is non-nil.
+ + + + +# File lib/xml/mapping/base.rb, line 749 +def is_present_in? obj + nil != obj.send(:"#{@attrname}") +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.
+ + + + +# File lib/xml/mapping/base.rb, line 722 +def set_attr_value(xml, value) + raise "abstract method called" +end+
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.
+ +(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.
+ +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.
+ + +# File lib/xml/mapping/standard_nodes.rb, line 94 +def initialize(*args) + args = super(*args) + + @sub_mapping = @options[:sub_mapping] || @mapping + @marshaller, @unmarshaller = @options[:marshaller], @options[:unmarshaller] + + if @options[:class] + unless @marshaller + @marshaller = proc {|xml,value| + value.fill_into_xml xml, :mapping=>@sub_mapping + if xml.unspecified? + xml.name = value.class.root_element_name :mapping=>@sub_mapping + xml.unspecified = false + end + } + end + unless @unmarshaller + @unmarshaller = proc {|xml| + @options[:class].load_from_xml xml, :mapping=>@sub_mapping + } + end + end + + unless @marshaller + @marshaller = proc {|xml,value| + value.fill_into_xml xml, :mapping=>@sub_mapping + if xml.unspecified? + xml.name = value.class.root_element_name :mapping=>@sub_mapping + xml.unspecified = false + end + } + end + unless @unmarshaller + @unmarshaller = proc {|xml| + XML::Mapping.load_object_from_xml xml, :mapping=>@sub_mapping + } + end + + args +end+
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
# File lib/xml/mapping/standard_nodes.rb, line 23 +def initialize(*args) + path,*args = super(*args) + @path = XML::XXPath.new(path) + args +end+
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.
create and compile a new XPath. xpathstr is the string +representation (XPath pattern) of the path
+ + + + +# File lib/xml/xxpath.rb, line 21 +def initialize(xpathstr) + @xpathstr = xpathstr # for error messages + + # TODO: write a real XPath parser sometime + + xpathstr='/'+xpathstr if xpathstr[0] != ?/ + + @creator_procs = [ proc{|node,create_new| node} ] + @reader_proc = proc {|nodes| nodes} + + part=nil; part_expected=true + xpathstr.split(/(\/+)/)[1..-1].reverse.each do |x| + if part_expected + part=x + part_expected = false + next + end + part_expected = true + axis = case x + when '/' + :child + when '//' + :descendant + else + raise XXPathError, "XPath (#{xpathstr}): unknown axis: #{x}" + end + axis=:self if axis==:child and (part[0]==?. or part=~/^self::/) # yuck + + step = Step.compile(axis,part) + @creator_procs << step.creator(@creator_procs[-1]) + @reader_proc = step.reader(@reader_proc, @creator_procs[-1]) + end +end+
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.
+ + + + +# File lib/xml/xxpath.rb, line 88 +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+
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)
.
# File lib/xml/xxpath.rb, line 111 +def create_new(base_node) + first(base_node,:create_new=>true) +end+
loop over all sub-nodes of node that match this XPath.
+ + + + +# File lib/xml/xxpath.rb, line 57 +def each(node,options={},&block) + all(node,options).each(&block) +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)
.
# File lib/xml/xxpath.rb, line 69 +def first(node,options={}) + a=all(node,options) + if a.empty? + if options[:allow_nil] + nil + else + raise XXPathError, "path not found: #{@xpathstr}" + end + else + a[0] + end +end+
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.
+ +# File lib/xml/rexml_ext.rb, line 74 +def initialize(parent,name) + @parent,@name = parent,name +end+
# File lib/xml/rexml_ext.rb, line 78 +def self.new(parent,name,create) + if parent.attributes[name] + super(parent,name) + else + if create + parent.attributes[name] = "[unset]" + super(parent,name) + else + nil + end + end +end+
# File lib/xml/rexml_ext.rb, line 100 +def ==(other) + other.kind_of?(Attribute) and other.parent==parent and other.name==name +end+
the value of the attribute.
+ + + + +# File lib/xml/rexml_ext.rb, line 92 +def text + parent.attributes[@name] +end+
# File lib/xml/rexml_ext.rb, line 96 +def text=(x) + parent.attributes[@name] = x +end+
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.
+ +# File lib/xml/rexml_ext.rb, line 28 +def self.append_features(base) + return if base.included_modules.include? self # avoid aliasing methods more than once + # (would lead to infinite recursion) + super + base.module_eval <<-EOS + alias_method :_text_orig, :text + alias_method :_textis_orig, :text= + def text + # we're suffering from the "fragile base class" + # phenomenon here -- we don't know whether the + # implementation of the class we get mixed into always + # calls text (instead of just accessing @text or so) + if unspecified? + "[UNSPECIFIED]" + else + _text_orig + end + end + def text=(x) + _textis_orig(x) + self.unspecified=false + end + + alias_method :_nameis_orig, :name= + def name=(x) + _nameis_orig(x) + self.unspecified=false + end + EOS +end+
# File lib/xml/rexml_ext.rb, line 24 +def unspecified=(x) + @xml_xpath_unspecified = x +end+
# File lib/xml/rexml_ext.rb, line 20 +def unspecified? + @xml_xpath_unspecified ||= false +end+
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_xpath(path)+ +
with the added bonus that path may not only be an XML::XXPath instance, but also just a String containing the XPath expression.
+ +Please note that the names of all the added methods are suffixed with +“_xpath” to avoid name clashes with REXML methods. Please note also that +this was changed recently, so older versions of xml-mapping (version < +0.9) used method names without _xpath appended and thus would be +incompatible with this one here.
+ +As a special convenience, if you're using an older version of REXML +that doesn't have the new methods yet, methods without _xpath in their +names will also (additionally) be added to the REXML classes. This will +enable code that relied on the old names to keep on working as long as +REXML isn't updated, at which point that code will fail and must be +changed to used the methods suffixed with _xpath.
+ +see XML::XXPath#all
+ + + + +# File lib/xml/xxpath_methods.rb, line 47 +def all_xpath(path,options={}) + to_xxpath(path).all self, options +end+
# File lib/xml/xxpath_methods.rb, line 52 +def create_new_xpath(path) + to_xxpath(path).create_new self +end+
see XML::XXPath#each
+ + + + +# File lib/xml/xxpath_methods.rb, line 37 +def each_xpath(path,options={},&block) + to_xxpath(path).each self, options, &block +end+
# File lib/xml/xxpath_methods.rb, line 42 +def first_xpath(path,options={}) + to_xxpath(path).first self, options +end+
# File lib/xml/xxpath_methods.rb, line 75 +def to_xxpath(path) + if String===path + XXPath.new path + else + path + end +end+
Class | -Numeric | -
In: | -
-
- lib/xml/mapping/core_classes_mapping.rb
-
- - |
-
Parent: | -- Object - | -
- # 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 --
- # 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 --
- # File lib/xml/mapping/core_classes_mapping.rb, line 29 -29: def text -30: self.to_s -31: end --
Module | -REXML | -
In: | -
-
- lib/xml/xxpath_methods.rb
-
- - |
-
Class | -REXML::Child | -
In: | -
-
- lib/xml/xxpath_methods.rb
-
- - |
-
Parent: | -- Object - | -
Class | -REXML::Parent | -
In: | -
-
- lib/xml/rexml_ext.rb
-
- - |
-
Parent: | -- Object - | -
- # 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 --
- # 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 --
- # 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 --
Class | -REXML::Text | -
In: | -
-
- lib/xml/xxpath/steps.rb
-
- - |
-
Parent: | -- Object - | -
value | --> | -text | -
- | -call-compatibility w/ REXML::Element - - | -|
value= | --> | -text= | -
Class | -String | -
In: | -
-
- lib/xml/mapping/core_classes_mapping.rb
-
- - |
-
Parent: | -- Object - | -
- # 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 --
- # 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 --
- # File lib/xml/mapping/core_classes_mapping.rb, line 10 -10: def text -11: self -12: end --
Module | -XML | -
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 -- -
Module | -XML::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. -
-- <?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> --
- 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 --
- 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. -
- -VERSION | -= | -'0.9' | -
-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). -
- -- # 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] -
- -- # 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). -
- -- # 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. -
- -- # 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) -
- -- # 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. -
- -- # File lib/xml/mapping/base.rb, line 162 -162: def initialize(*args) -163: initialize_xml_mapping -164: end --
-"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. -
- -- # 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. -
- -- # 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. -
- -- # 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. -
- -- # File lib/xml/mapping/base.rb, line 199 -199: def post_load(options={:mapping=>:_default}) -200: 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. -
- -- # 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". -
- -- # 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. -
- -- # 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. -
- -- # 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 --
Class | -XML::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> -- -
-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. -
- -- # 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 --
Class | -XML::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. -
- --Initializer. -
- -- # 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 --
Class | -XML::Mapping::ChoiceNode | -
In: | -
-
- lib/xml/mapping/standard_nodes.rb
-
- - |
-
Parent: | -- - Node - - | -
- # 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 --
-(overridden) true if at least one of our nodes is_present_in? obj. -
- -- # 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 --
- # 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 --
- # 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 --
- # 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 --
Module | -XML::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. -
- --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. -
- -- # 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. -
- -- # 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) -
- -- # 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". -
- -- # 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. -
- -- # 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. -
- -- # 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) -
- -- # 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 -
- -- # 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 -
- -- # 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 --
Class | -XML::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_] --
-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. -
- --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. -
- -- # 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 --
Class | -XML::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. -
- -xml_to_obj | --> | -default_xml_to_obj | -
obj_to_xml | --> | -default_obj_to_xml | -
-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. -
- -- # 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 --
- # 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 --
- # 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 --
-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. -
- -- # 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. -
- -- # 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). -
- -- # 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. -
- -- # File lib/xml/mapping/base.rb, line 548 -548: def xml_to_obj(obj,xml) -549: raise "abstract method called" -550: end --
Class | -XML::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. -
- -- # 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 --
Class | -XML::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. -
- --Initializer. path (a string denoting an XPath expression) is the -location of the subtree. -
- -- # 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 --
Class | -XML::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. -
- --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. -
- -- # 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 --
-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. -
- -- # 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). -
- -- # 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. -
- -- # 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. -
- -- # 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. -
- -- # File lib/xml/mapping/base.rb, line 685 -685: def set_attr_value(xml, value) -686: raise "abstract method called" -687: end --
Class | -XML::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. -
- -Class | -XML::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. -
- --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. -
- -- # 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 --
Class | -XML::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 -
- -- # 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 --
Class | -XML::MappingError | -
In: | -
-
- lib/xml/mapping/base.rb
-
- - |
-
Parent: | -- RuntimeError - | -
Class | -XML::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. -
- --create and compile a new XPath. xpathstr is the string -representation (XPath pattern) of the path -
- -- # 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 --
-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. -
- -- # 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). -
- -- # 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. -
- -- # 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). -
- -- # 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 --
Class | -XML::XXPath::Accessors::Attribute | -
In: | -
-
- lib/xml/rexml_ext.rb
-
- - - lib/xml/xxpath_methods.rb - - - |
-
Parent: | -- Object - | -
name | -[W] | -- |
name | -[R] | -- |
parent | -[R] | -- |
- # 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 --
- # File lib/xml/rexml_ext.rb, line 75 -75: def initialize(parent,name) -76: @parent,@name = parent,name -77: end --
- # 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. -
- -- # File lib/xml/rexml_ext.rb, line 93 -93: def text -94: parent.attributes[@name] -95: end --
- # File lib/xml/rexml_ext.rb, line 97 -97: def text=(x) -98: parent.attributes[@name] = x -99: end --
Module | -XML::XXPath::Accessors::REXML | -
In: | -- | -
Module | -XML::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. -
- -- # 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 --
- # File lib/xml/rexml_ext.rb, line 24 -24: def unspecified=(x) -25: @xml_xpath_unspecified = x -26: end --
Class | -XML::XXPathError | -
In: | -
-
- lib/xml/xxpath.rb
-
- - |
-
Parent: | -- RuntimeError - | -
Module | -XML::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. -
- --see XML::XXPath#all -
- -- # 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 -
- -- # 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 -
- -- # 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 -
- -- # File lib/xml/xxpath_methods.rb, line 29 -29: def first(path,options={}) -30: to_xxpath(path).first self, options -31: end --
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 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 + } ++
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 -- -
Path: | -README - | -
Last Update: | -Tue Aug 02 23:20:23 +0200 2011 | -
-Xml-mapping is an easy to use, extensible library that allows you to -semi-automatically map Ruby objects to XML trees and vice versa. -
--For downloading the latest version, git repository access etc. go to: -
--https://github.com/multi-io/xml-mapping -
--(example document stolen + extended from www.castor.org/xml-mapping.html) -
-- <?xml version="1.0" encoding="ISO-8859-1"?> - - <Order reference="12343-AHSHE-314159"> - <Client> - <Name>Jean Smith</Name> - <Address where="home"> - <City>San Mateo</City> - <State>CA</State> - <ZIP>94403</ZIP> - <Street>2000, Alameda de las Pulgas</Street> - </Address> - <Address where="work"> - <City>San Francisco</City> - <State>CA</State> - <ZIP>94102</ZIP> - <Street>98765, Fulton Street</Street> - </Address> - </Client> - - <Item reference="RF-0001"> - <Description>Stuffed Penguin</Description> - <Quantity>10</Quantity> - <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> --
- 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 --
- ###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'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. -
--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. -
--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 defining your own initializer, you’ll have to call the -inherited initialize method in order to get this behaviour) -
--This implies that: -
--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. -
--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. -
--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 --
-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. -
--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. -
--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. -
--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 --
-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. -
--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. -
--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. -
--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. -
--The xml-mapping core "operates" node types as follows: -
--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. -
--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. -
--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 -
--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). -
--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. -
--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. -
--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. -
--Ruby’s. -
- -Path: | -README_XPATH - | -
Last Update: | -Tue Aug 08 01:33:46 CEST 2006 | -
-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: -
--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. -
-- 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"). -
--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 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. -
--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. -
--doc/xpath_impl_notes.txt contains some documentation on the -implementation of xml-xxpath. -
--Ruby’s. -
- -Path: | -TODO.txt - | -
Last Update: | -Wed Apr 19 07:58:12 CEST 2006 | -
-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 -
-Path: | -doc/xpath_impl_notes.txt - | -
Last Update: | -Mon Jul 04 02:00:31 CEST 2005 | -
-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: -
--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 - } -- -
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 -- -
Path: | -lib/xml/mapping/core_classes_mapping.rb - | -
Last Update: | -Fri Mar 31 01:11:26 CEST 2006 | -
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 -- -
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 -- -
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 -- -
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 -- -
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 -- -
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 -- -
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 -- -
qp!xF&IS;`K>6NqG)suHSxSv`CFR#Hn8!>vR+tRlMMQliJvfNT5k z?}!6OCyoh#3_unjkGM%bppb-+5**8ipC*;Vdq@?a8qfr20W3gUeSj5!)qtze)*8TC zz&gNszy`q8fQ@K(D_|R7J75RkCcrI#JMo>n@Qu3xy8-tA?giWjxF7HUzV#qr58xrd z!+^bjeSiVLe!v01LBJuv)A-&qc<(6Meiqly175`WF&tmUxBiB{z5{p zP6NKiyBD$bU9c4cIyw#-Iu05-4jMWR8afVoISG0>33@pRdN~PtISCp#4jMQP8aNIb zI1U;(4jMQP8aR$QK8ZO#i8(%rIX;OwK8ZO#i8(%rIX;OwK8ZO#i8(%rIX;QGJ&w6O ziMc(Axjl}#J$Z57z7v%v69KfZ1gHVJ;pLde=P-}YVIH5uJU)kce2%z>>qs6Te|QTi z99}_6aNaSzhI9@eBa?BQk9QZ~-9Eq)oUg!f6^^TM+=%zL0=5CR19kvz0^9<)6Kx&E z@kPMPIR6`-e+O_9@4gFoAMgR-W8C`;a2oI>u3hMLDWutJ=y@r6UJ9wU91?9gB-(3` zX0HW$yaPQxNeYHvK(D=|X!sTMd f4eq~)>ti_o8~XkZ;3UTJF5o@j`+Xcg0Gz`4$N23tz-hpj zxc4>Miw6h%4zqOv9Pm351lgy?Q42oO0}QzD0Jw**2H(C64mbf0coZBk01kMWc<{Uz zFdy$M0jvUCg=f|P)&kZ6)&n*Gt_IwRcb~@bWgP#2HeLlB2b{!v?*iV#H{Zwc1HdVq ze~IJQcs>WcY()=S(YsdA(rY9Jzu57M6OaH%#{V*K%mTQFuO+#7I}hW_A3lm6_oKJF zKof^R51<9Ku>fuK0hZvrAAGqCumaaBaa@Iat8u;&ZEXc?18fKE0Nez)1#m0czYTCZ z;10l@_{LG(e-ZF9&i{t@-vOLNTkiti2Ydkd823H{oCbV}Yb8e0jox&lH{Iw#H@@7B zx4ZFnH=gds)7^Nw`$sZUS)iqBK|gh%pE}Ubwcz%n;P#{7_DImuwczj+aPylYJCoR@i|cJbD)nGKp!uFK3)KQ zyzs4*`2gO35U>aE5a40JUcf%U0AN4h0N^0t5P(tbvp7BvVAOjI$G_nl?*Kjqd ABhs6253nZM&%%S9Ahv}J2=`2NC0F2vH qN0z3@Z3)lx30PF`G02~Ay0vyGA&*Jzz;6 - |?H?-=Ic1m@uc=HUe9;RNR41m@uc=HUe9;RNR41m@uc=HUe9;RJYR6Lj1$%*6@J z1*66j;H7ZzQaE^N(~ktdCh)^6;D=Yh53hh9UI9P60)BWIdg%mc_Z86YE1=z1K)bJi zc3%PQz5?2P1$yNK^vVh7l@rh_CqS34fF55VUFYPgZj5a8LbiJGwW~$h>clYtkUX4= zuU?I>UX2;0n9+F5Xgp>#9u`zFWUd!7*NfSWhs^b2hT|b~y|AFlA$Pst$U1OjKP;#@ z;L18!P*;O1>%f(DkjYF_)`1J_zEw7F#5-F7+W^}EI{-HUZUNkdKHUx24Y&tzFW^4F z{eTD1mj?lR01p8k2J8j=0^i+-;{aek-~iwt;1J+({PqOkFyKi5lj+Z(t)uwXv-tga zz;AH>MO+`l`QOm@cK{=D{$ref1~?7)eq{bdEBMD`1w*&N9##w=8RfHVfqpv)`M3r0 z@d&J!Sa8@8aMr!xr~}}rQ{bpm;HXpJsN2Cwr@%$0z(Ics_!=j{EhhuMh8w
JiA)Bao>_z##{~AqT)A2f!f*z##{~AqT)A2f!hx zz#*r=A*aA0r@$enz#*r=9jCw@r@$Sjz#XT+9jCw@r@#$Izzqk$38%mbr@#THzyT-0 z0VhZK3R_0`3R|GpPJ$y&f+J1_e1#+bA$CkJ<~js(-H*8rfqxnTJ-7mM?ZsTLz+Csk zV%-9%x<#CGX4g1yZ^X9oOIE*q jAy&`L6->=w|A7xa=0dI^EF^@3jd zK`;HFmwwPoKj@_&^wJM{>Hk)H=-d5LFKEdNTJnOHyr3m7Xvqs{y9M;*1wDB|PhQZI z7xd%>J$WH*w?Nu%fwbKMX}bmV)erh&ekrq|yr3~Jr0te~9pweBc|mJl&{_!m(hx}9 z6`-{c_@yDx>nlKOUeKBswB`k^g+K!LBT}z}9nQGU4qMm>NC0F2vH+E^5UK#xfF?i- zpcQ^-2Q=(tzyiG62Ur1E4cLfxwgR>RwgYwmZUWo_cmVG{2-pL72=FjqFJK>F0I(l$ z0B{g+2yhhdJd5M=fERIo49CCW8}9%<27CrM4fx?iGXHbEXFcKqkl6q=paZh1bNDnU z;3RzfBk=8yz<)Rb|KSLHha>RokH8;40)PAn{P833$B)1VKSE4sWe-}}gZRfuw6q6a z%T{ LL=7TYR>ODMn3gCd|J9O?%1I`_QO3-x`pc>EwXaQV>`)dGe0qX$k0UH2U10KNd z4+8c89s)cJ*bCSP7y#@C8~_{y90ELzcb>)ZdB8EiNN>KzeF=KwkFAWvOibv{_e)zp zyixyfw8O>x o^JM ;)FL+Ty5eJJOZG3Mn{jj;<`y1y)O8qXB`opG* z|6CvK;>dn{S?P;&c5%)w)=2zsRkQp!?BI*N{Ql~fF5>e0!{z^dJ j~ax2)FAYu4y6xuD1E3y=|eq2 z|M!$z|Nb#^(S{wnM(BrW!~Xbdu`Z6_|9w5&4?F+z!|vV1Oy76O^j*Z(cfr<=bCvPM zIsPtld=XpU1zSH(j`(3~u#3I>_xFLg$i3f%d;fDIA(Sjf&PPeG#|qY8>)_QJh#eK( zPV8e6k99&RB$Z^4Osq=I#wxR1RO9870@UIakz!Qim6HlmjaA&Wq@Fa8M$$x@$t2Q2 zI &SYtfovpO$X2qAY$rR&P2^T` z8@Zj_fjYjO t_mca_{p3M{ot4PL Qo(R+49p4+F3$1YqP zu15Cl;_(I#3`u8BU)oKa;@_nCy>q6M4DoO7(q)U5l49|1d0;oM^E-R79logeP3-<9 zo^xnT4m<}3?goorq9iGBU5w{SiHa(ylB#GBRZ|VsQXSP(12y6q4?vD*q$G$+ajc^< z{GA9$Ldz>fJX6eO8JSBKV{ZRU{z6_SZ;&_13Gx=+rI;-ae@(=K{$N`(euySvd_=fZ zz@B6@*ni{?k#hREoaeMW$8i##<98DBNCWZKif`dh|B8k>^oE?}q&2?F==HMw^ucIC zZe+jnZwVW4h{l(2;Rk*_zRLa!#~qw;P9o)$>;^9Uzp#r7|6?uu>;HIQ5pzUjz>Pwr z<{;3s<{U*EF fQO93e3gluP)VvpZm0o2)R76`iHYa9q6K`>O4>;KIqsMO{+LF*WIBfD=Y-9` z-c6wXyFvTALHGB7@*e=*KPXcEBjjh~QSx(ezyNq)Kj zYt|EU2Zymy3b~DLBGDQA_ ziTD~kyPAALW`IZ5fh%7GUon1SoWywQF)AVd1y1{%e8ISiN Fn5=eVJf2` zG@X{yX4*xk)63}=?s@J7?lu0YXj61(bYyf)v@<#_IzPHR`XO74%^AbRNMp1yhM3Tp z$e6^K>KJcKckHj6y=OV>#mD|rW43ov2@OW8<7gA@q+WU{T73?!9!IOj=-}w^=xDLk zybD^@k7{+6*eYc^{0)CG{MPW(!%q(XY#3|NhgS|S8(uuzHC*j8`%FIRkmJpl-u&&G z$KHJL&0oFwUvED4=A& z3g5yB;t>ALKNs=)zl&eC&tob?-Aj;rE@(?PbmYbXLV1ps);_9eX!m3ioGW-Qp%RWt zW@8>Xo^V}EZpkGAc4AOS8;MjJR7T`-StF6j)Uxt0PZoQN<7v-#d9A~vF38vG4GxDr zDcqq9PNc RomQ#+Ca?$Ap88P9hCH9oklrWh_wlc{Uos<-9OY&{w?;f0D=eq{)kt{Sh z!u3%F%~|e=1<7%)kR_Kzr6wd~m!#U1n>9RW*1tIwd6 M%n%vMlIX8E5b3-y$c;>m0-%Pd;O}4);`}ljg zc=SaHjgjPuH>%~Fgiu<_K{nDxftT>;7b#b%2vM0;X1x|qDjibRBbkQc`>9lKJAX=L z3QnWf=r{#kHgYCx@crdxz0s(@nL5s&0S~Ppq(Xc<2UgY<1KAY93iOi<`iav7aT1-H z=do2N?NCseET~MY;sin3sFZV3skV&uPfb8HPIiO+CXouA-|$-C!46LZA^CZ^ZhS2# zD #rXyY|Bks8@mAuXYqM!P$wfEtTw0p)GR%nY+h z#%Ic;cme0KSewzteK9p&W1y|RS6hW(eZtfO7(f4~rXi^$?v*x4h(2K&O_-JtMce)= z72*yy{WVfDw3#L}`Cg@u`zrK0nS@^FTVOHJ$JKg4q14fJP8;27H2Ny(p-8#Jx5#Pp ztqe`7r0ZZy+~gaUD5aCZah0$@cX=X0C@+bpN*-jatQ(i0kx(fQ?dPU)A_X>8z-9)K z@RGSis-#LOV=hifx!JHCh ihS&6e@{)YsKYORcqpzP( zR$7pklkL#kL(LAI%91FgP?sA#7b&qCLA{{e63S31i= f(!eYKr8nMh(wJUh!mgSddN;=FF;S&y6m>vb#8|d+Rg>)oL87?wd+1N?nkvWXiaR z)M;%s?x_3)cg|K(t=d8F$Y@zm7}c ^1eVaIdg~7$FVQU>miQ5>Fws1Z6xt16XManH;iDBJwwF278&x zh#;uTm>g_|95hSICpE{##l|?};^IUZ27EBwFnlpymjxu48Cc28DCR^IQqUm%7Y}j| zrZvqkEMCx%lG3oCxOjeJYMs5PF*&)R&~7hmNKS4nvUA;1y{2qXuC|m*{*=}@=IN9= zdm)}~WKa8#lFYVs?a9gQ>)P7acO)lwtZ$#Nu*znuS~y|Cf=ZjMasj AA2r3x^e;df9~)nLJRQbVV;zyL7 l%tq&!~=QiVp~dk8OukedzZ zNSYae0(}w*&?l7&%|N7CD5s>VA}z%gYl|}Ilyc&s9=SG=0&`X=qcW!}1?;VXQm|No zJ2#_14J A%Bq%Ycv(z-TS4KBN>@ue zeKI*)&IJXzxw2xK&|$D@Ure5V`E?U(w=Av7>ReS_Rh2n)jc;SY)TOzVs~2=Rec4i> zbY4xIup^~@PA<<2L8{R5!i3ne$?m4@t-k8&+s3Q4Q=jkh{Y9>MA*;Q_kum++&gwM_ zTEiN4v{qiyo^ke%6P8SHM(5S1(q{x|c>T16Oomee8BS%Q43F}J%Yh`wZ}vkY$CL^P zl5O0qbns(@_IXJ?T0V>IJ?Q&8{X$^-NJxKSkkhzCqsd@}($rEv2fNvYOv(+dxKyS| zn9%Eqi174INKnfzS-~xT)7O1OrPRtom%sMszbsq!+Mi!r9xBr)RZsc4Szl+M?T1IV zO|v)` zO4Sj1E81SkU&-AiyaL}n-IJ0I0q?L!Sqyr$LN3w37X(h=AWRs+451Y`%_iAlp*(0M zJ1fV{oKF{s19Xr|-ByUG6k0?xVJ^sup$e?fc#vCBROW(Ii=+{h_6u^AS^GrD6Pi$i zF+}rZ=wY2vBbQ|f@*tD$iO?ssp?X7z_Hf9PT9aBT >jM#X~U1qvm{%h@jqo!$_te^s251!O=HELjg<7g}0M1;PmvByiTj zGlX{FhQKxA9>>O{A}T%mPiBIawn4ku^|oxAp6jKL_!{edP4s7y^}eF 2m^ zmm8y%OC`#vs1S)%tB6gVWX~u_i=Wg~7p;{m1f@jj%Jw)oE`xn@78lQDbC*F UE;q!0_`F)?V%DrzHW{w{g6-o-7{y5f^fM&b2X)(;MQ4Sp&q z3CR3NPZ&hgh+yY=goOyqsd6UFCB{T*;uwZfD8#v;m_nXY!-WD}Qqr*LvEJT;n -zR(9+V)c2p+i*1&U~L7dg$|8tE+GQ z{Go?W-&$35>uH7&_`*b@5!k+q2_Bac7C7v3h*APms0SSlDuY=?Buz|)O3L+mW2{av zCC1wHS#AnhnM!FUqZ*n`D|p{8l^VSwg5F$1vxi(Yl2tKI-v^4IINE#WFt(nj@TG~w z06b)nVoyN|mGCNjjVl#!2Ezo_21AF`FS9|bpixfEL0l&%CB{2qOh%oWWY7$mUs#I* z0P;E0mSKsYHpY^$=10tTQIOkR#XQ&9c;lkd^p?INUqy6 +1$_7%$IMs044$||St6)ow%tfGEtjU#@1 zccGoiyM8(&QykOqClU+BR81y%n#!pRhLc3F!o7h;l1T-rOaK~TQgP5s0+GP1CyH z&2s-RVYM Cp*FjmeOmmzNw~IAv+h zxJ8Yr6 vTNTv!KL0c9Q?THXZM9?|=2D586LFDM9=0s|P z=!6V{{9%fgX3E%%+W0QYt2OVo4s~mFyn=gvQY*&~-Of#=C$dy(-(0#~txDw|dRp%^ z4YGe~0s9Vp!k>bEOC=spkw68SoW${%5D7C8ctHX$1y(H)cna5&kP+8XBH@}rZf36B z6lZtF*<)lD@S@ai<3%Q99^!f2`!&9w)TA;OynyGzB79_0*`@A<_b<+`U$=Kw!Q83! zk>N_CI( WSX&lIRmb;!>I4w9j#a42q~E zRVEBwA`i2S beUTAA zTIM%CTqC?Enu?K}@q%lveD(t?%9EQGmlu1-#pKQ1F>UIOd3iCV-s1AbO)2Fo9+=&@ z&YM*}_lm;8E9RDGO ((XXJj?E$<(@UGw{Yl9D_5ORYxh*r8z+DvGCEh+)~{|$ z$)CE6jpYXD^&0+pkd2)b)IMs5+iJ!LL=M(6sv2R5Fpm+BBbtQiKYzDlAkHZ&+Y3wp z{aB0OhOrjG*sHPj0bf4WToC)n2l|*ysy)g$XargsLHUxA{=$M`svrFg#BtO-JZU5A zC#*Ea-C}eHcRThcB{ bQx}Qq2 zA !Cyg0w^bZF40=a`D=rAp^IMUO7&sll$J)D-=54Nx4on zjv~86?xiRAzw+If7#Xp7A_G&@fsv23>hyXYOd@92F&pDvzF_cK23%4NT~mWTokgGC z3-n1tSf4~IAO|M7i$Zu%^VuK`mkr;6 z(a|_tSb3v2w{q1KcWQOZ_)IQnoRC)4T$PqkJvFm*dZ|;UmHm7y)d@ji87^x={o=~H zh0P`I+?d^ag!tn8!rB>SIg<*a?1}cEv%O;`RUF&=VS|(xGLdzePG);%8mW?-1Or8x zO9d&kq7;US6lRJX=82r?6{6%hWmk{_?zIZ}G!~c;WHMnQM5wHp$OIYwy(3CG+9pnK zm|j_4n4goCp6ZH?2-9n20;#9 $zU(AvzHB#*&-|5}=_)h%cvgN;_x?9(Od3bSt_tisZ9TTdG z%?-hsF`YNg$uq?jJTawhO0%nca`vP>8(R%}-#sbgBa+ 9j=?+Y~ciTh~eOP4g?^ZBj?`>w363@(}1Omk$i@yl -JT_xM?t*$Hv=c%rXe2tR*V~QJu&r$@0>S$Oi!_tkD2N$KTXJ@y#vt4` zH2z+RNmI|2&Guc-UFLfO(PsQ#8$@sQb*ps3hQXEGul0$+!HEWN*Gu^JW%#xUvEu|% z G;_x0j)Z~3 zzTXNP(x$ARl-tujP9O5nwVl_`%I5AJdT7^{vU#;}wHZx4?(1%ZFhKqNCxR9{nZfiq zY-*@1__E+gB%yiI15ZIfl3+G`6H)YwfwB?NkGye0Of)m36|#WW5DOX;<2UD>E6yKR z1mUrGk6(Ymr lNXNMJ)>}uRMNRTkB1~ zT~T+*tkwiU@sTVswKh9q&S4s~dlyw6>9r<$5|V?=QL$0xj(a}Xvi;QEZKk+ n7<<=z{7RM;?S=xFoM-brY5RQn1N=ZPKF zzN8T@9*b!5Jk%MQRuBb?IY{B}23of3#dg;Xt(?OeeR$H)a(|nQ2FgGKS tHV7=W z0QCqW5Tu+`;NV=b4?$l_rIKd!-BiYcej|wyMg_64xkjS8eaOd1zqPtb ?*^c2)xI;J(B`%KxPO0erLpe~OmURGj z$ViC7Y U|URG%oA@=0Ry31OV=g{Mq>|a%) z(Q3jq3XM!IOPjf4HjjU64w-cLVek)QSY}I0QT)I{kvuH6F2r}yZ3!%O_=tMFE6x?o zd_;ewl|^KkBZHV56ob=+P^0KLX42LcW4P6%;tF(OrskpT#s~xGi@T;d)DkrGfXZNv z4(^&gn>)iRG%BuEryg2ovdE+%rlEFi5I0MuQwc+@=ucoy`2Pl81QHD6GT^PyCYUG? zk(dkKfG7wW&!oBBor7m*x3w{ZiaaK*#%VN(^~At(U}N%=Xb(bggvC?DCT!+xeH_F+ zwOD~jSkeW|^<_~N*xD{?F|%3DfNjNJrd3IiX-kspG*zAa(94=oU5Iw*DBn@3(aX64 zdRut3E=t4yw_18;g=Bwd#GpMilz%-U>}-v&L8>0aKM**Y#*Az{z^lJ2e)O7c78cc^ z>~qd+5Eqi4fgVtaMyKGn@}22c&0r7Tks)l51)a@_442$}W`#-eo5--U9T+W#^-`Y- zPk~QaUSpI@gG~OnVqC&gOulXVYVXXMd%xOtPR3pS`yI{AJAQxph@AW4@Eq`$YWLmX zubw$i`o6e(>#5ysZM#ox-S)xm*4Eu0VC)>DGf6Xg8H6eqM7a ee~#7oPWh&E*e0Cv5oB zucq9#xTGm-^6Hwq9~Hm7()UZ@4DiBy_!f_n#kvqHm2gP6g}`qK1$su5SD-g$#;tmm zRtselH}4yspzsM0FpPDwS!024KO6kZ$f595V#CsEx~SC@9prnd eom+n5)&O7jHfLL$O;=q 1Y3|%2?IDDXSefKz} zHe~2F*#E^ALtS#q@|qo67&_Y!A9@n=QN-+_A_R{kFe4n~4n?4oWsfAVGiHO$McZDq z^2PZ 3Bms{g4YFXQr7Wgfv=jIs;AE}o7Ss$D1OsbpZuAW+& zoRr ^l?cES&b{Ys@8TFRkDA46JxZ4-WOsXWCZ$z z<+OQ}42Z&96g_xGMr2SZ0&ory#v=Zbg+A?UUy!h&?ac3lv^IY`&klbq`2y{P5W6Ru z$t@<4!BBxn8A|1hnjo2!gwPPduU47x2t>-n(@2hcZT7B31+!~5Kfh|#^P6kt6!h&H zUv>GOnf$!LZOeYWqqTL%ub1I``^-I;qxV#_m(SpvIuh!!B2okS39k@x6Ec{of`n*D zUS!rmAQmeN`7&K#*EYg9rYDR+Dg*tiVAM1W9qbY|XoJqqP07khl5AJ$&aCi{Nsqzr zM*A*?8@ojY=|oY$Aj(6ABp4_beNq9l#PJp8+Mbio?3<%5sU0algsbfONfC0|Ema7D zQbw bG=L@2y!Xy=FOUVU)f~eRJl|t_f0{+!p<%;rG$i}m?WE3I&^ZGDJdj4 zS-gS>|4ndMSn$~_-~xS>+>XAYBF_`51p>|?gT>E)jkuV28$4tM^dkB;qDK^d)(J$? zIGr!JTcZ}}&v= VHk}Qf6l>H=-kXHBAMt%f zKNLR1+pu@o$beM1ypY1E6!m|J<5^-1&BIhe^G2AxVq_Xfj-ZdwV!h_rF^%5$mGldT zTKDy}I<*7UZeJlk^z6C(5ah}{w4ov8G115mq58uw&*bqgEw|96kCqFM#GZ*UvWh;4 zc=YLhu}}VJh~J=PigO}<=No}+zCv%b#bB A zvVh6qO@pUiEQj?^NvmWd-2@q|AR!(LuIW4`q)Y@k5UGSH5Y^Ep8d-(|olB*AgM(GK z=LG5He-w6T3?IpqLrka7gB&m8KPN8YhF)!qgcMKaIjIby9P%9b7Umfs?<`}nO;MIx z%UI4ujoKBdbi_+!vES5`ICs1|COSOSYBn%$5z$|zHqnOU)~IZMc9xR~dzSJFWCZkn zKSG04G~9%UNSP+Sv3+(?rne@!`sPm`xMW#%cV&{+q}10xw4mYAiE+M~{OQfr_Mg7E zuEds89sZLzPm(pVczSus{Dw41&!%m0X>o>#`i9JD8=8kM%??Q}c0d!^3!Aeu#;3B1 zWW;cXPxBvw#_;bQk+rpt#=?G3hXm_+i6Ajh m+en}! zq}^zYRHly ggr1 zDzz-sTG>7)cN${Zg-h?7KYd3oKUI*0MTZqFxvRVPuD<-3xEN)0WR}xhvF6DYOP;v8 zwm2Je)rNlG6qu{UfoYKlpmWjmV^bFa!ql8YvQ?ZP(oCgLMPjjEFh(uZX^M(Go>E4>-^-aah z 7fnLIN^dO{2 zc`35t2rKxjaK=oG%SlO6qBG5v27)r`G~l^d8hb9;iLBd5EN1L9S;2rZ8_Juwke2Iu z3)GsS^epeyEwu}4la!dJ;EB`wa%bOv>A0e$_syGdS%+J}U7ysjh(Dd*cUP}PXI%8) z(tKOIjZIdprRwTue==`iO-)r==jHVcKWR?|twn;#778~bS~hLJP75+)C5ARkRV1?v z0%kg!1{6C&%xNJ|0@bMDxW*3%J#>wFH1KnWhl$H55~VlW9I*zQ!UD!|L4aqD%#J@> zjnN^I4TJ3YYa$* KRiMxJd&Vn zASkUR%?gYR^ArfAgh(J67*^zj%6tCtwQqc*!xP@rNXg{R##v3XCe)5Aam0iM>$Qm1 z< OG#R+D3Q^^j`Ew#2yJ4n1NJj3tYX1?hL*qqC4LgrRgKUCy&%a(-yhePG#tJo zJThy_RTEn)6VeK2{$$NhW)>DNyLV3S9rJQ}x-trM(aDij6Eml*rT>-QG;i7ROy{_i z(B!tuo9fm~cl)mM%qq7#DrT0H%&KrWDrWJYM@QH~IF)l+PW7_pl+fVPsKgYrRAxwS zEGu5po}D<}n=^4ql-`qPPDzf_#b4c3y{skW> ?Gf*pq7A(J{rXTuN*y zDr%>cIvk}_P+40l)`L*2VIcgYkPT*%>`5>|3^UeYA{rboY9?!$Ac1g(g@@Z_Vz!&@ zELb)oZX-Ds9bgIxqV}$76+d6tw7Mx_+7)YO++PZjx@l5(){ISULu RwZIND%{ z4%TS=M=k%>U 8mW-*w`5SzI8?lMWh#-3a*{aIcuAVFkJ{ z8dz2(gr$QS%@8BP&W>2oD-^rn_pK~Q7G!@zry>hF3L{c10#(Z+LK^G8p^Inl$Z8)K zt5l^0$=e$xGrpm>l;ueI`!O$(B+Zkop`+tbEI^9+VW @TRs*D%u0ADOg4_{16DvCXpo2o7w5~3o0gpSyl^iD*Qc9dI*@0 zBEqati6Wx3c2eqstE`4`Ei9=B?XA5RFp6JqpTM^ny`p*!Un9-T=> zGP{r=H&Ww2R9h7PV?98`ya)Q>_CGLh-UG{uik3YvuVY ~AFM4}vTN4NyOtIgFTHE#tX)gQS_h8Y<=e@hfUGwY2g{5grbI$R zOj4ME0UTXu>M !<<- z!@=L=E!@>Rdut9Yow=(xZ*vE;7w-1$6v|YZTSknAn(@9+K{jXd(3h>FY=$=U`zG|8 z*{|rgfeO56zQQgl^$UU#lhbbv`LP`chkcX%$ykfocIV6-BogUZGv_96=_B*~M$Via zISLx?yAfti&QHZ|@8;h+bC}b&&-R--^>vFv2B*N%(KJ<$SULed!rH3OQR@yqLR5=b zr5xll0=q~_ai}K})A;9QHy8`Wa=05zg6)pj5waY?aG;jmFTFGLS}IT5xh%OZM$PM` z7F$}>;v16rw>0{fR%|pHN_BFj{F=2xh+ZJ$idg+=&?eTHdh!b)NxwCA1yz2&swqF$ z70c3L-xj;_GtGs;EABa&R%Op?F3jmDv8UC~?3r1gmfCSubMH-sR#YXcokd+`C0>sW zKlS3Llu4J>bzNJ)_h>?*LZY%NoT 7X`F3$Ys z;_MkUNm V7bH+xOH=3VqsOCw1?SW9HLqr z2jgX=o(| po*wl zT*V=#yaI_q(HNz4Z};NsRGJ5asalmx^L)>nV>2QgS }$Xzew3&TQvS4Mv1+ zZS7$aj`P=HA%?>b;M;+i0Q)o(|M;?~N&GPZo1HmyV 2DYNb*TR0>)n z=!2^Hx3r4EkF5!2bE1XUtJG+PBFjyLhxm7qZ)Wi|6AG8b$OFe%1frbjm;_t=NPNv= zH3s5Dh-x54A|^wOtWJ%kApBtomJzXx*BJP-GOPy+l2et;?6SzHOskM6$4qrSq{92 z^~@Sf1G>zj8CoQ30&%vuQE|3_o*x@#qnmyAcCj# zQJBl; 7@mg;2&@S$`{NrZ*;Qm^t z-o#I+Wn(EpOzUIBw5my_CoREm^m3riRCX_#QkZRG$fwi2++sB2;;b3vx4)^(IE` zYz<3xz}RQ*KmptYmKpP>>6z~kD8pl(0Z0dtIe(dBG52Rne7@bDA8#>QjLz!bQny}i zORUO_Fjx#B@!1g(+3_JbjmWG@w8{1E(%xz(9;AnD?s!X)CEjgw_w@AU7WGU>Oi%?y z<~F3~&F}5ajxA1!l1Zadi(<2T=g!YfZ^(@dQY9o#=qbwW?Lk2)mHKwVEBp|)SfJLN zVNZ`#AuCNsk+hvkYiorMzxoA6-|PE|3l?#c?SZi(piB|(p(2B&qzKeG^U8q4iD-hE z#jHNDysR`kGc7qYp5-1eW`+WBAp!>PF9P;^O=77ctI7bfn2Q-G+7KNmGtfs@kDx`T z7(q+0A1%QnXi?}!&|- uvCNk%m`Pqc4q9i;NSx{||5^H~>Xp~s? zjge*sJQ5bok=X-o39pJRO0d>P7q;hS&g-3#=qyOE(h1RpZMj+VdS-C$u(VQ_dunBz zE6p|Pl=(&PVBYS8PF0f#;$+Ih@8?5#qSsI`;v`~eeyBmmLc;#_mCQfQL>Va7SE6#s zKN(OO)XI%4ul#AYYord#mF?TfInOVy9Qu>w@2+eOmR*{#{L+m++&RqiZ%N)nB&C2= zkt09Ih3hyuHBw%l yv=h|zqz z4Ppj`(3yxdA>72u=k@k7qyC<(Ik(QP>u)QzI2CHW+LT<}T3ElVA&$PGSNlq;OOT`B zjlrhDoET3=#0%U_S`&3n*}1GN*3+715wN^BB`j;onu)$Yxm0@J?>bf_N+faG@Nw$6 z{Q4|f$>{A)at1Yv< 9;uIs}sl1%*~xylbBe;j^h)#JK3WdxA-5uIq)j3 Ve0M+!i5d`& z68gi+PnqmyMi-OcOo9)7>3ePv6*icj&BLqtdB|f_V@H>9p5kg~xz2{7U 0!o4t54s?tkivRW4`UeKD=HOE~Mu-k|3c9pezXC_w_ zJBk`RrzCdN2r6|*WJpwYr7NY}os#CRoKoDp((as5G1{JwDQry5tSd-M%c|%ony@l9 zc4ke^^xCBC?4bwa$7LrcB!)zLi;^qdN%1bLb1K7~OR|rP5!ysQJG1stGc0^H%L376 zG11H{c_YqnV @+7PZ jQ=OXnNT*EZNzb5{f@D*fIk~qu1@hF3ll|eL!Fuz2LUSlHy ze>oxyU$Ghyd*r|=M9D1TrUG({fpxnORoK6PiK<}iU*M0%e)o6HfxQpD`zJfF`9Vip zLQ))C-x6zMn;$@T!6yy45~F1$jIV&D1($=Oh#ABufyIG3$rQ AsQgUwk}*6uur>bYHOr)1%L*TGpc zAKQuZf;sy>-L&cSzMh_ar#Ef-bl)6$2iBrR6;zH}e(&sA_g+$}jffAksY0Vdvu9k> z)_KkJ%y` td7pBt6g3{)eb@%u_w&kWLn(yCXv?cIW za#gw25v(@E=D8f>(xbS{x`~z$i`tr&yljSVA8ZHHCZRkx%oT!UFNeKj)^m^XSHrK1 zCGvq-Got>i7Tzc>z2#YjKC*WRx_`s1TR)A|$quL$%Af->U8H8r6^Bx{%d|>ukjnRG zlR|Ft{Ye#sOS BFtlEstp FsulxL_Yn=fqW!JSYn4UZz_VI zGET;_#R5^t1w|K;Bh?|1i)mvqBn`ejANLFJ?RR<&Yx|H$`Hmj~w;I#ZCQcY%l~$Qn z5pOfcB_uhqMFLiUd@C~wOj(JWmW-;76l
%q{V)GiQ3}q2ojT$wEfrZzKu?w$R0&vvg zYhM$;XK<3_O5fb^zJ+wlc)EQscs%2U>HJ!*K-z}AxaN42iI8UgwY2`#Md46EP*W6P zGUzZYfMd&vL>EEp&-ees&x}u*yU93|auppO9VN)0+l&S*x=p6ZqXOJo7G1Yx_}AQG zgO0$OTmRk(tU7|WCe`QJLK1Q#e`|D^s4>{*l*&H{PmeWQU0LD9)h3yV3*sX+tE6qA z>EqMlDoP3>UlN4igkZT&_F;H#USYI7D@I@CHo{W!vJHVS-WmKVt_b5TAvbt5MF;_6 zVV_{8hzX8lHZNRm%c?!3G9qP~*uU-;>v`CQIaHqq1Oe8-s;A!x*N(>re!;-8N ztVg!nSYDHq(310M>vQ%Rb|n!FHYnFfxL1Dn+@39(Xwa-8rbI+dPO5W9R~L?-!2E~8 zMz>8BDUk`-If9oM9R Mr8I4V zJ2En}%%SEuxm+F;7aJaykQHLfiZ!Kk*rO!FrW;pa;w3;MCBKzy A3wSvg)EQcgx(IwV t2j$(a3Jw-_3@K$F8N?XXMlpu}7QAKT$V?jST7n=I z$UG26 _xaQcjzUy(aw-Gqj#CTHMV_s~ G zjmRrjpT+nJxm9sffw2qpQn-6`VGfho8LrcXJIy9Xn2u{WcNH6^(}f`;Ovzf`?es-Z zX#{EYsB~(m$S_o+G6R$Ck7X5Q+*C*wik%BVlP#c0_$Ohgox-DY|KW!Y4|cE!v1&Dl zYDXYx$cm|$?;tMLak(?k(J9NR-XZA2;vzEhLes{%5{e=_ri7)J$Hmu&Dy6=URccj| zGsI>I64rZlg$0pmHiOj|9wg#@8{|*6v mYv zee3xFE +Kb`B~0wAsOX!RFmfDv^U5WQuexgS zk}H7|4to%;M 6|0$g$zk4#kwHJXjzbn}f!z3hM#Rii5D z0hCaEs=45Ly;h-Dsi;$>mI!JUja8v`SnFG__O1ITKLEMdKamKFh0Q2Mj6}>KnN0N5 z1sn7*3T>1qI4u6day~5nW7 ~Pe|PAfc}5pakK0Gh`X$x~-|FKAwJ=L>0N#0VjI|hJGOD_(NVjK( zJ3S2}9MOvWxlxX&MRBw|MjHeQ5%r_WKUgThW^8!H2Frr3rJka~0&E19orxmBQQ8m- z9NB(B%(nQK+VDb*7|$bP_P6TE@kXhcGjUeUs=>R*;OX#r{BSRSn+Ek18?mFFcXrq) z DikCrdx#8CK$*M; zy96m@N@g#^Rgz1))FX5=nW$7kvsMR9g8&bs9*rNNu$|ckWB*o5Fv7>Sr2cnXx}Z =&@ Pyk zSKr1{gPq^SrYNp4$n;dAFlaR^`KSHtcj`QOont@l+3#X;RArkGM)S@d+&`LAIg;Sp z#{Ckp_IOf2kokb1W`$3v0>D}~th_*(8Y(%F6Jqt7V! Pr@n %jfPZ$J`$}cVEx$qweZM7{8ChF47~1jJhjm zCl+JwVC*W9OFLjkuqth~u7cHK$zUa^g>rq2Xd&rY%78f{Z0S7%EJsH2jj~0_GVUz@ z)Zp){xw%8TYPlPRLUV^6dP^NKk*?>Wd*vq8xh0`=zi(y;$iN+%VudlV%6GfCmUklQ z@pPkZK`2BZxRlvC6M!oT+loRaNMw*NQYfU^P(!dH2!&rR1PayQL2=kNhn2FR_ljl& z*7OR9>Duw*Jo&j9X)KT$uzDuaiDI$gD60oWqa!;i{gXw%{#w)XrHRZs;;tWM9yvC? zbyw%VUJNW{Ylpj6TtCVBPmHOh@`X=O?v8I?C@f2a;U7tB$Jkw -;(Zwm(woKhlhD4DDkM1I`NJTp`ce0T0{7uZ za1~P4zW;f(!v8$1L_E*h_up5t`=G*f=p3vY#`+!`tFMBCipn1u0cD f>xWxs{oP;v#6XxI-3f_gI!A7I%gaS45{D_pgQauZun|m;-B~xBBJ>) >1*H1^x0H?+1;! z&v4>@UQN%AdY-lKzi$|GpW(`XU&-!+XKp5+^B=&AQen>xg@FAn6r4 4;>$iX1siGQ@DAZA;g`UAGIpw; z;ZbL#J7c3lG;(ASndQzFBStZ!ovk-SZDF8v2#S}LaH9#axF!lx8bvtbW8d^d$AN?d zR7A3^FakT7AsHoll;XY)fqWIS1Z5V3z&2P+p)AxaWMLI=Byt>B*)Fd9T>5 9P5jumzwie%IU=WH7n!>Fzu0m^z7jzv>rr*;-J^lGgemYsH>fqZWh~ #P_ B$ z-CmG3zGZw`c**SXRja4wOe w{UZ2 mc@x(X2|F-vED|C^9{Sa9yuW zN-82v?$+j zj?OxL$ayQP_biRZvs@8T!xa|juThhY%BQNsPIsadQH+Yv;7OBSZM^X5U|&TDwDm$8 z+9M;C)8ET&xvMsT(-vqYCTd^IkgUxA?(gJ TlMM%wh0DU&89#19?ZR~u2ZqhpS1PVB8|l9t%-%%Rg6s&-iFt%RMl+k30Dq!Mxw z8svVG^grFwe%SXBUbeJwbDFo;&73J~^Y2^u*u1gcUQq)FELs0}YSE7-_g$5fm!{oR z$#-nOitgo!yZ^{DU-{;M$tOEMV((Q(_S8As)}**<7Co0eHNw4Ght`F)XFPW_w}0%` zqk&nY5=VK5+YXVJU%LP4Im1TGd2(^qmsZaRpZ{{z_$lMPQQD&Q)vdW-?Tqz|o3n2E z%m-5jPF$7$pnF2 &&vT&iwz@Scgp*pF7r> zd#`_HYKooBHF9|OSZASY;b+D=-RG6{w?jxx%Ac&}%~l-9mi+D>=D1$EW0*5$%{QNz z*}c_i(4X5AHR;IH1OLqMW_!fa!ZXi>HbTXJcBj;=2f`n`b5JAyYkR!*(f<~8_rL$3 z|9!fCyWXo{{yJ1|{V#O=&+n?Yeio{Ky1PDJj&3#W|C8E}aOPgpAGPzU>i^dr_1i=B zU9GDBgU8wrUH|j$dMzcoe(SOJL-kKvy}b6iu0Mp;dkUNx@}e`cre&mP*z1}zWBMTN z$?` V6d2O<&UU}d&;hqDrFF1q zr!=EaEo~3bWs!Ysc`;diD3lTB`f+5>uVj=g@y2RVbN%-p_;zoPFOaE&cKw0l&uysr z@;vQ Pn(!C zrj!j+*sbSdlV99h`~1uQ`?WRq=AFpdeDrS?5Am PG@R$#@=|!aTktP}@YugX^5wgyvey-lM@p<*z@2WQ)gs%VjUG>&} zsQ#n7>P 7JfCMloWq! &iAHyPjHCwmx}Tr*}=*Q*+ 9osSOejYp3g<+Z+)YbF*aa6QPZ+58T(;=TmzlB#RIa##@Mor@MKG>HMDOQO%de zYW-TVTV(=j?M|CS=P1+1b+6XAlyT^201(>Mw_fA+p4zFx_TBDn&PqbnT3+JlQ9}j~ z8faUbwM%rYYpf-^^C+;@7sXA|v=99DsbhE*<(un#^IHdyc@}uXVu!AreBY)4iBkvn zjk@C~GWXn`DbvPgr3@WAIOgNXPmU#PkBT2SZ0Vd4{* v{O7fep1>9r0E*>=}qIZE6z}3?lIQ-hO4Jr zDI=<9Up0M3X^FC)?Rxu>Jfibq=l8 x`*58VL1{c?@88SmL{&r ziKgnyEhfYJ32w>Sr7e6In;u~7u;?W@YgfI-DR-M(_v~GneWUt~zAe)5gj-@9Fm~$T zo7rK@Zk;%`;S-77vaOjZ6XI^ioIa6-c+nnZ*^}*2o~x4&pU8SFMWZlr6y?^*%pQv} zJsB;|qTCMAY)_=uqcsmj1iQnJ7H+b|Oof}e^J`^B^VOG@ES~qk{0FjTPf508sVy@& z#u%LInyYnzyBUnvI##j*Pd4nb^#ToqKZ#e`4)n;|T!-Bn |%wUZrOT0_;I!_XE``^_xPb=|Tby(=$kWNc4uLC}DU zNoDB3-&;p$4Tu|Hdl|+^CXpU?*+kk1Natxi>Uu3Ix-(#>YQ5Y0AHL}R@MoFPy&|sM z{I@l3omt(-`;hyp==i1J5%TS^MkglP8>y5|&6nrciuN5_BV(P|f!t=A2*udlhRC=! zT!@PPhlfQ#8)m=1E=A`Gul)G8?%%FY?H9Guy Ig)~x8uiq< z6k7IYj;76HS}d_M0>+C|jU|;t5*3AeQ#?k!)!u^i*&mG84{VFkU;Y6H{6WlP@531A z(R|P*Er0e`>gb_U0qqqew|--59=@rQ$MhRF#zMximQMIdZ)R?H=P8SNags&rx(BP~ zH>1*Sb?T3QIOdt12OcW>)xLG>_Wi2tp#wXg851`D;~y+3`_XfOx676;Eqgog+>gqZ zcv3=V4Z0r4{_48PU;L5FdyoLnl=WZDcHMvTp7Pd1Yu6sq$zM8bam|5N##ikTYt;W! zl=-#X`9jvFWS95$99pY_-IHc5Wx2w I4d#;g;1-OuA71$nGt(W^LK~=r?PV`p)0@*7p%x%F@F4t z{IJZ>w06z7#dl9@ZykJ4vFmHQZ*4IBPT8EN?;HQwZKh$*-k!~d#^^dTE23FrkQruu zdiP@J8Nz- up>Yy8ah>pP$u-;$s#!%b ^S8iALqj8nOI6ZHjoh3`0?6SjoLmNKQrn$%68yl?`?CA{%dpy0O=Kkxd z@K<^s2#?gR+ykD-4)J}fU7w?~gvhW^jL>mEqsDbu-n_WP?lZYY+ F%rh?&sL~m+o-8=QVm#OW}Il z#>KJ)sGqB!&6DkMI_=iZ2HOkdN|>|3KWKj3xO=Bexp#cr{K5VW;onT0w?1XeoRne1 zQs#_FSwF8kfAmuLbytC>Ml-=X-Up&Hnn>OG^xKhK1)o0Wz_Z{pCmgt)7s8)Y4B?#G z9AR0niSofp`}iaWMGW?`T%Ca`Xb+2J;wj0%&YH5(l3QtIrxHS^k$*aOD2)xDp|q42 zhUo2bqcbNDwYSXa?~qwp(^9gsQl@2j`X^*e7&K@?MnXdR#DN1RrYBgvK9cLpo!@f> zwQFgJ&3h$J*+Y!tfmuqsQ9^Y0E!ZYCmou=xMleHML&85jf*EjofZ{rRYg{sP(#&B~ zKQk1Izh}n8INx1EFx|u0@E^L9JWuH!mM{Im2uY`(3{!y+M;jq5o@^U=dzygk2o?Ek zg-OhEZ|?=;Qsv(DwtMUyr_H)i+FzkNCFDFK?UU_b5w~}PxvC~ibfxzhGb4Uf`iyCV zlP0?F={t5te0=)!X&xtSazbYEU`6@-le!DijN8dqZrp3+#wkvaH8QjnA@oW+9Zco* z>a|FHyozj+C}*#<3r(}thezEmxnB9PQs$DXl``kbZd7sdX Ii>M ahM~7q$zw1jm8Xs4%I66>= z#Am5#wK%NzsD#lvOWHh+hCHD|6}8#PLay2!pQ1^NwmK=sG%ftOKE01W+&OW}QzKFb z+EHIa2mJj{`t=R_bwaQHA3q-U>dkwe-PWU5VlU5bcWu0Hkna0i?h#>!wM74y^L4)! z*5~8@ ;vCS?;W9t__W7nq~8-2)l0v1o(=Ek8ti%Rj k)gIc@7-=IDkA0a8MAJ;rPrDcR+iW8`%3$(ZT;YLo07eWM|V0r zTCUiTei|hI89o*6vtx1K`SY3opMPBvgKyu*o#N4bNIvdF &t8KCkjF!MT?F zbuoT&BX`PH_tE9J6Orv5_wl*%{lC4B&sD+a--hgEUuR|5Z#;*Ti&>o2Gg=`A_u&4P z#;q~Dou8z~le^bkJ)yKY+>$0j9aQt8h=@TEw?^$l)M@plf_3ZfxV-z4tYP+ilds$S zsjEWYYh3N^GrG^H-tlqqw(6~ps#tKCdhP(diYASB9mVxgcTUu~g!={zxc_@Ad;^9K z8Q{zQ-U9;$b{6>t3>iAW*I694J8r06QSJJ*&(}FDEXF-FZe(ZNO>K8_H)|I237?ak z_p*BGm6-1EvGML`Wdw%!oa|E)ShTmtX3bT4y0m!jwFpZu1t9Ijg-AK2(5oQDd35xk z=v$mmy7e=KpmdQ&*y}&Hf$ksKC`yZsVeZX;^>?9PNILx{j31XcX7s3$Bb0||Vp6oW z*T4z(a#r(dpB0v7+!3P{G7kw&87zu-?Q`XK7JpWFu7ZIB9{65%NQNuEcmIF^Z@b?f z`58gpoHH_Rh=kiY%;$6cTEZO?XTDA$*s0pfny<0IpUV%{UaIa$KrhXPlHIQ1$;+P3 zxJPSN$;)!?$#{BMvU}w{IkxLS|9Z-5pOUxLd(6WBllPd7);s7l2GijqqPIf%QoK4! zuQSmQLTm1ydZEdfyQq!*)IUG<# l0~Fq+t;IB z)brHN(FwihGROH_XKB_V@x!|=m2uhA^=IF9DSOcCF1zgjr{-b)tUGIwO4D(7ij;O>m2*X z^bSWi?@{^8-m$wkiv5|bVQNb`(5|171Fg2~+5Nfs&!0|p{&T6#j3K%-i*%pS{GbQp z_D_-`M%%mhG?S5vP1nzSlDIgcJIPh2yr}QwDnI-IXG7LnJ5wfIM(5W2CA6hnNvu)I zXtk~7zR=e4o~{U)W{(JVfDzHuv@R;jyF#zB?dP5M!2OFBE|`CBravt~r)2dVlbB%M zr;ny}7UAn&aOHgxkvN`FmQp?-RAe)Vwr|}!dNOhJ^lA2RPa8erVC?#rPd}Tr<)P(c zMudB#qWZ^Aox5(<`pPdYiE*!wiMx5;hVP!(n9dQtMO!P^&whAOYQmr%-X3ETR(|n? z1z$Q_wzAj$J#xx>!yYdFaMuF^Cf++?v?sFP@G-;tWxjZF`|#+v$tnF3`ws6Jm;csR zvIdTsFlJzn(aCye#RHW;`&zmVvYI-@WbJkqg)LHSv`pjm(azkgtUkS?y^cp`=tyFE zo0{A8q&@YNS74)XWe@5QQMvhB|8&h8I&|php&Ebo8`U>8Zj5(;Hko!Gq%%4$&5{V? z6T=3`W!u7(-XLV#Fh)-g3womdMQ=0q&-VO0DlR%M>Q|mjzqd!^&pOv!T=;%iTddEO z6BT~*f4F`)v9mZf*7agi=X`f&cvNS(>xFcC2Zqu(Y^ %=L3Kd)3nHe6PEH7gZ w+TFS~=zW?~V7Y;w-`lr5eu4no_ z_~qB0_{v*f$;=cy@!FRk?Auw _N!Ryhe J!HHN7ErfNPeH7W$vPB9vp)6yCe%teYP`=zgP5|KTy-n^%*^ zk4f}gjrQGuW|)$~277#AMNa5_eD`H7UOH)zPPdRFQPfq2YKd;%h#uN};e@&e&Pg9WJblj6i4*UiHEPtX``t$;-0vSX z%76caNw>ao4IVdV&E(0e=Z@1$6&5ZGESoTH?waJ}HTLho!iCQ*oAAKctQC_ct;`xb zV%F-knX6}w(BGM9`a3pj<)ld~vc~G%@UM1$J8Y?Z)_5l=D=|toGfdAYb#n8nQl6d9 zsqwL;!^SBccFg!y3HHthW4AlIXRJjKBQ3ai>zoMICDU9xy|Y&5W+nOud-MX$=;8fS z*Up{Pqqt}0Lodu9b??APg%G3Thh}VEIN2LMdrjf03DLd@dOgL+c~30*)4x9YjrB>I z`|Dh^kqf^hD|S0Sb{!1+A7OiyO+MLqEb9wWVpy!)t74bhT&0DzaYNnV5sA`kgyQPD zUS}EEGKVYDj#t+%%<#yF4a)PjTnqi-_)i`AG-#lUH}s4gK5+7&$$fkK>@7V5Tmy9e zww>dr_v+dx*|tn---9W-&*#>L`BK;R>oe@JnW>LIx;Z&*O}~D_AI(Vp;-e2I|C#!* zq)9_(O_-c8F=5iMnUkjIZ|4Q8N$#$(Z$f?NuoDdX-{D&|j(sMy9nY3z^m-lnUQfis z3KMDBMjLE3%`x8Y*Pn3Da 5=i+WnucrSuuqXIhxt zgT@e}!ZaJ4E(dqm^^4~hZgUOj{HG^f`#Q6>FWTPufotWHo%6yTcSU#p$CB$yI)Cqq zS)wH>)7L&{ncmm;pw{8~J33Wf2b=3{Sx%O2{jPWH*$S;&8w6bz6H#6myT*B)pRLL1 zm43dsoBtY@*sot=oO?`MFTK?7xBC3dwS9JU@2Jk-M`^d3PBi;nkJ#u;*9$kBtn<5- zd9y66=?>anG4F9@A-May{i4o?@1`wBXz>jV;cSPTa-j6$J~15DbhEcF&OJ76Y`=bE z?IwJX{;m6G<2rX{YLMWHjq0hra#60BsNT`DKX!#^ztZ)^u>bTlh*!K_?_OTZkB}PX ze46#%aT-1LaZS@XZapmDO7weJ29|{Iifh*#*RIZI=X9QT-4`V%)g#LFK<67#J;KAi z5$?lbSs$PO_ litLuU-?#yA#JE1MSU9Hv^`y~f?zpMCoWw=c>Xb;tK%VWgqs1>ul}6bmpG zxI%=g?l DAo9r*{^3Lh0DV zgZt=V*(0KtUaS-wJ93KBaiimB+&@FxHnrGgatl*-PKDOgdRVOBc6(otK0o~igv2r1 z1`g=;n}t1Ny`4XgXzt;=93EA#HsoU?QndFXDBm>3(HTY>z^LWBj>{R_eHg;0X1Jdm zmZP}RaQC5J{q VjX>3GXz8T8 zZF>`qcKMt4&`}7Qghr0;xg@H0 ~-UgeF=tFV?=sYUxW)_Zj*V;Y!Hgo|V0Mb9UBt&wp F-8uIiTK8? ze|0Z<>Fx?V=UtZ~ZmQi8_Dm76Fd5-zRsbXuCfySsKYP-UA(Lju$KNw)$n;z5 zSrMD!(~|}cOiGU*Ju7LzfTUTYKUvR8is z2m3H zkII-ieMr&-_sl*Csqyg{GgBj$rA-)}K55X%F(YRt#b;0u&(goF BoBH1y6CEneQ?e{>9{ YJWcnzV_>`8J%B`dQsZ8-YCoDKm9K9bf;GD zqdx4K(KRdTMOy0q(*C&gZaIw*yZ>_)N?avQgZtl{li@q{-j|(DwWrfr<9Wh)+Vw-{ zq 6cp1*POJ->544BxBI3;OrZ^zSC;L-%Uu zKf}K6tn-X>eix>kpUA=bPW2J#x~Buq@A&&$=Xc@#oHgOEIHNs3bl$TzBTqYV;os*o zD7?*iPJDdd{m)(hrN7UJ{y&FlBJW=8tPcA?|DNX@5Boc3t^TeF>vYz-i=E+Nzju~~ z&vN$bzAIh-PuJzJdiZ!dBF))v_4=#{Pu5@i&0uGL*hXia`+X->^)H28b_RKBo&I6U zMNqlF;%!SrH`BdVnW1Z)r$qnjw`8w!jXra`|E{uY!kV40>E04`EX^u+jn2k;+Idyq zA2iuhw^-*U^wzjSI&oJzU(n|fUH?CgcKGj{8ScK$A^p2p^%XkPbB%ug1MMR( zaMjUUQ6=}!`9!95{U6f>dN5sdf24; A`ex=STPTZTmO6Fnx4? zq>n+Sv+z>qqRIX9J`symFI|`(?)*p(@%p^#`gvEYKJDRCoOQSLV7j>TBe`_{`z2&z zatZ0Bzv;|$V!RuD;}zZilk=g`6OFgSlbl)7li&E$@AgN%DgI7{XqcYO*7$4u$hJ(s zpZUAf*)AQ7?EduC-|6~X(&q(zUe;% x0(jAN6@fSs$I^d3bn^GhFu+)F-U_vr2#eQJ op=tYBG$2915OoLv>H0ae?@rI^BFB zD?;j%m>Am op=ygnkUdJ@(bxea^$2915OoLv>H0X6qgI>op z=+(Qw3{8Vx$2915OoLv>H0X6qgI>op=ygnkUdJ@(bxea^$2915OoLwSb+vlapw}@C zdL7fC*D(!x9n+xKF%5bh)1cQe4SF5Zpw}@CdL7fC*D(!7OGov>v0xII0;Ylsx*GNE zLU0k74Xy-V;+k@>0;~k9z-sV2;CH$HD0mD!4xRwt08fIaz|-Iv@GN)^yg+O3QN9R% zK+R>9mDnSWd|0CoYxH4_KCID)HR^qYTw~Vg!y0{9qYrEJVU0el(T6qqutvSiOY-+& zjXtc=hc)`JMjzJb!y0{9qYrEJVU0el(T6qqutp!&=))R)SfdYX^kI!YtkH)x`mjbH z*670;eORLpYxH4_KCID)HTtkdAJ*u@8hu!!4{P*cjXtc=hc)`JMjzJb!y0{9qYrEJ zVU0el(T6qqutp!&=))R)SfdYX^kI!YtkH)x`mjbH*670;eORLpYxH4_KCID)HO4p| z*=mgA1$|&_*LKy6?)tHCESLnQfT>_w*M0id56 `h^|6x19AS^HUkbgHePE2PiIr^|_U79J(Bu#+IaCQ}sYk>*>0R#&GrBel@9Fv% zVJ6?^&`to%2McMT2rLFmz*4Xb+zHmuS}o-u 1?{VloPPxiODx2Qp z(0iO?dXG~-wU$lqagOOdPQBI2ruR7YSu2~~ k8@1#agOOdPQAdsHND5F7Z{q}duz;+ChXDmn$Szx z2gY CfWV0>-v>&12r2dKh$+j zH97FI1uk;I05#i` s;9ewu2pfdqZ?b zt3Uo!*h4ljTD{WBJ@uR&t-fjH7}@z~S)gHWu1NsXx^}9@ue%y8&tt#M==y>1o~~2E zOzN{}XET^XYXLAHEaX>3U@=$%mV#yAPOyfSYbggQUx7*;SPwSRW)pagyKSc20=81q zLHUN-Nf6hog #pVoOoT6pgv{Sj0KaxNjixoLA}Z9Q^B;ZZ>T&|Ehk70uL);$ zT@Yq;Z4+ivvrfGrK_1C42P#{jk_!f?*(Sf5;5-lJQNM$7KHnBl^AZ%x!3wYvtOBdS z?|`pzKfA#_;9jtXySl(NAAmt>uF`%3*a*cYuo-Ltuk&pi*ba86eyqIH =v zfLFl=@H*HAwhI%nx JqWKM650mt4qY{66N1M(yz?w60th1R8wPCmx$FR zVs(j*SzV%IR+lK5|5RvJmnffaXjYe~5t^Y{U7|*4hGun%8lf4Q)g{W?Tgzs3iSqMS zHmgf?%<2*yv${lidu!9IE>UBi9}CUu66Ny^&FT{6=dEm3H(vaHLl~oH*?4hh*qfSR zvYheq>W1;oaA5-Vle&^sULacIH5N5o1TN#s ~Y7)Rgum~&$OTbdF4BQFUg4bxF6}+LEWLfh2LhJR( z^!j9tK&)*2J(>QVEG_&-<%QrPaIw5xvgQR=zY<)do}R3yp|!al+z4)={ai2)EC8)v zCu{6sX#F}_V-G{?*U1`t7+Swh)~Md790iYo$H5ce8{kRs6nGju1D*xXf#v~{HIld1 zE`sI_k~NaInycJZ19%;51KWj@9Z%=agkI1G#%S;4WJxbUsF9j5UQ(SbN&Y}M7EA(D zz*I1;YlFV^%ZE&s&&(5MbloG&q-KG}50fQl!$n{=xDs5~bwKs&X=fwl9PVTb_mB$) zsM)45=VbX+!#wJDP|oMu0%~5OopP`OtOTpTYVbQ?4R`om?%^nS3_K2=0N(&lf~UaK z;2H2Ncn-Wko9|J+2!24#Wy(QlUF8lNz((%53A~1!n<=+|t<+rS$~Lea?9jI 1y7rTr%l1rrr>E)@U$s-+7vu(3Z6CvPn*K?Jq1sjf~QTv)285QQ}DDYc-j;^ zZ3>1y7rTr%l1rrr>E)@U$s-+7vu(3Z6CvPn&|LO~KQq;AvCvv?+Mn6g+JTo;C$f zn}VlJ!PBPTX{U+bmz` `hGqI7yZ^O*8$=!UeMbX|gxNMPN3#5-jBUBCr@N z0ZYL$a3{E*)((IN!9(C-@Cf)i_+45)3LXQGgD1c@z?0x9@HBV^JPV!!YoYrtZN5j% zMes6s4X#_k8@e(TOGw2MQn7?oEFl$3NW~ITv4m7CAr(tV#S&7ngj6gc6-!9P5>l~* zR4gGCOGw2MQn7?oEFl$3NW~ITv4m7CAr(tV#S&7ngj6gc6-!9P5>l~*R4gGCOGw2M zQn7?oEFl$3NW~ITv4m7CAr(uQE?@L7%EKQECV?qnDwrl8K3$`@kAyE#Qw~;um0%TE z4SolFmFstdd%(Tm1-|`2m?k-YLuk+XG I__t^ z#(`;?yFDS?NX L4t^Joj)KR) p4 zVheta0IP%+Tkty;Tkty;Tky-e%uXz};CC#x;FqOYjl~xHifb8KY{BnXY{4%Jd`+=+ zi*C)51->IR3!H@o&XNW0QaM&}%UQBOEBEG_c;!aTlD*mWle!KI=jje-$*x+33&BO; zlCC|f$)>y#T;27rs>y-M7Fx~)%_?TeDh!_o^QhlJ+3aDK?7{A&5V}QRF<1hYf@R=N za6fl-06YjD0uO^nz}La=a&Je$W8iV{1o#Gc5 48z+AL|!YOZr-8`ut-)@Dg-?>Onw%lkr`4W+Bs8`^9ro!L-2 zv!Qfp Hi8%kIIwX)5I($!}TZ8nrH`5W47 zD4p3*I NSQo8%kHNF|^rG zx_XVF&4$v|BaEWWhSHf0rK>Mkjm?JAnGL0@FW9}=Y$#oJZfLWi4B7S|VGlh$Gi2LV zR<@xqMl+fW$#AnUj&g6V879eR$h#Vvoo7hGKM*GHt<9A(Bxgf=?q^8aR<;>a2HrJ8 zGPgDtf{VZ Hnf>n1|Bs7kD7r;&A_8(;88R1s2O yT;19X-E$~O++h8sCZyq&6_G5SW9@k$4FL8~{<}zeOc0X6a2CxxXHG$W#jb_R% zU@JA(xv~vx2Rr!ohUnfSyE-K_`?*K 3~h~jwq#;xYuvNtuMBOC zd$# MYuvNte++Gnd$# QG*`J}ss53S1Gqf0Wru?&^ z#i%ni2VAXleLQ-i&65lbec(FDcAmUnyKp1r9O?t$Ht>0H2bd2wf=ysE*aCJ4=ZnfQ zp%?6_Hs_0$)%fKl=1b bLpg6<;dE`shN&Cu;zqq_*YdJ8UPqq|6Yw_h3EMbfFE(Oo1t8yek3 zlCz=FU94x{QlS_0f%eo}%=2!s#$fhs0Nmz0rtRXr6Gf zMv_bQziDtO8eED7mx>GfwP|oE8eFRTw`)v;OJ%EuropAMQbW_=QgLKx8eA%l3{8Ve z#nC(3Z@fUdctD=Pa1oddt^~ge9tDqq$H5ce8{kRs6nGju1D*xXf$xDA!OOyBaJLNZ zmciXJxLXEy%iwMq+%1E Z^9jWuRtjoDaZHrAMpHD+Ut z*;r#X)|iboX44C@vBqqyF&k^l#u~G+#%!!H8*9wQ8g(w7Y*HJ(LF)x8pt}OPE1 +rt<80`xlVH;t4RPCsJE_@eHtzT zv%!^MA=eav#b60o3YLL8!S8bYQScae96SNO0iFa;fv3SU;92k-SW9c~QN9RX292Y2 z-5lM}HS5LEWue74))S*xuWv7FACdLA4fMDT^tcUr7F&(=xD9&38d{IrpeL-M^|%ds z!WvqS+n^_`q4l^8dcqo7kK3RptfBR|4SKd3T94bHr|Jcr*K2a#h@3Yf=Z(mDBXZuT zyZWuZH92oY&Kq@CRyH|r)C|#nYjWPGh=HNWd7~l*h9>8YiWnH0oHy$J4NcA)b^nGY z=Z(65LzDAH-M^v9d86*%(B!;P_it!&-Y71tY;xWxE(}f18 izO^kdtG4k2O$Y&EHpG}N>HZk(q z#K>n8BcDx-d^R!i*~G|a6C izO^kdtG4k2O$Y&EHpG}N>HZk(a zk&agDDPq~eIU4iK5n8rzjx=TUmTQ_LeOcME`*Osuq2-$9h+jj?HO NE*k43qi{@&5<+=E!Q+h;~%4Fxu!W9?HF3FX^ymS6fM^@N7^^ET+ @ZZ%OSfjhwQ!_viow#?#q$isMR%=YnnrL zUykH&cX$-ET+ @ZZ%aJbZPAu0n zN4hYyT+ Qr#@He6}s};r6ZNvu%-= zHne=UEz*m%VEJrYq!&ZWXWJsZ7+OBt7U{*%^4YeCcPm>y+ZL@aSj(2rwngg;hL+E^ zMLyrq^4Ye?^IO^S*|vyltFe5xE#mZ-PA;70!f7s?=E7+%oaVx*^02tlIL(DqMNh>` zE}Z7VX)c`R!f7s?=E7+%oaVx5E}Z7VX)c`R!f7s?=E7+%oaVx5E}Z7VX)c`R!f7s? z=E7+%oaVx5E}Z7VX)c`R!f7s?ZWX6Lb+(GrkA#*fw3YRvt>V|pmMOGV{2KP=ngq~t zHnxi2pwMzQwz7V-mGz^o;?(LbXJaetM_a|UwPraRTgA1Z1@J`yd{F>j6u=h+@I?W9Q2<{Qz!wGZMFD(K0ACcq z7X|P|0en#aUlhO>1@J`yd{F>j6u=h+@I?W9Q2<{Q(6hkq*?ds|UlhO>1@J`yd{F>j z6u=h+@I?W9Q2<{Qz!wGZMFD(K0ACcq7X|P|0en#aUlhO>1@J`yd{F>j6u=h+@I?W9 z(Kcytv*N$K!30n%6{=BOS(u>{H*A(^ CuoNr 9-b!;&y$DeQM8iQ%*W>8dGhc)d3c^YJWrnP z*4i;2n}_Gg!}H|fdGhc)d3c^YJWn2;ClAk)hv&(|^W@=q^6)%)c%D2wPad8p56_c_ z=gGtK OOZM)g4H62U6XERCgfN9Y}QtQr&@6cOcarNOcEN-GNkhAk`g6 zbq7-2fmHKFYqgV4f6u4C=hNTw>F@dU_k8+$KK(tP{+>^N&!@lV)8F&y@A MpZ=atf6u4C=hNTw z>F@dU_k8+$KK(tP{+>^N&!@lV)8F&y@A>rieENGn{XL)lo=< F@dU_k8+$KK(tP z{+>^N&!@lV)8F&y@A>rieENF<{i1+=Q9!>apkEZwFAC@v1=8j(odWtr0sW$Yeo;Wb zD4<^y&@T#jA{Wpv3g{OF^os)eMFIVyfPPUxzbK$z6wogU=oba_ivs#Z0sW$Yeo;Wb zD4<^y&@T$;7X|c-0{TS({i1+=Q9!>apkEZwFABx!YNt><8d`*~kO*NR5yC>*>PM=v z6`Vp@si8#(3uUWTP6aJOSV)AhP!f1eWs49Nig$TKacwmgAuQBP+0Y_{g+vGoi4Yb_ z8dhTw!a^d1g+vGoi4Yb_O7^Wq2n%JIM%S|43T2swmhDz3%QUoXw?bK_p+yJ_i4YbN zAuJ?9SST6VU0H;%P OORm+GAu!c zCCIP@8I~Z!5@c9{3`>w<2{J4}h9$_b1R0hf!xCgzf(%QLVF@xUL53yBuml;FAj1-5 zSb_{okYNcjEJ21P$gl(%mLS6tWLSa>OORm+GAu!cCCIP@8I~Z!5@e`T2;?zJkYNcj zEJ21P$gl(%mLS6tWLSa>OOatIGAu=grO2=p8I~f$Qe;?)3`>z=DKacYhNZ}`6d9Hx z!%}2eiVRDUVJR{!MTVuwuoM}VBEwQ-Sc(iwkzpw^EJcQ;$gmU{mLkJaWLSy}OOatI zGAu=grO2=p8I~f$Qe;?)3`>z=DKacYhNZ}`6d9Hx!%}2eiVRDUVJR{!MTVuwuoM}V zBEvFdScVMCkfBb>lP=1TVHq;i`>o`A%aCCiGAu)eWyr7$8I~c#GGthW49k#V88R$G zhGodG3>lUo!!l%8h78M)VHq+kLxyF@unZZNA;U6cScVMCkYO1zEJKE6$gm6 lUo!!l&}k|g%H^O86+ zw4D(z>HZCEXT(ct+0b@IyrdQkZD+(wYT )NefoC{W!a%3oF|!Tf5&t zn`Q6P7{<_M*}Ej|pwMR7$_UW>cNeqlU6Q%g+bnw*v+P}xxwU4q>|K(lq0O>)X(VH4 zv+P|O$r#!!dzVHshBnLIC5c(fHp|{6Sy|b3M(kpiy^C4)E=kPVv|08pjb!YeZI-=D zax=79_AbfF$~Ma`hwE~ Yx26Ea=0#s z>vFg*hwE~ vFg*hwE~ vFg*hwE~ vFg*hwBQsu7K+bxUPWf3b?L->k7E8fa?l$T>;k>a9sh{6>wbv*A;MG z0oN69T>;k>a9sh{6>wbv*A;MG0oN69T>;k>a9sh{6>wbv*A;MG0oN69T>;k>a9sh{ z6>wbv*A;MG0oN69T?yBfa9s)4m2h1N*OhQx3D=cyT?yBfa9s)4m2h1N*OhQx3D=cy zT?yBfa9s)4m2h1N*OhQx3D=cyT?yBfa9s)4m2h1N*OhQx3D=cyT?yBfa9s)4m2h1N z*OhQx3D=cyT?yA!a9su0Rd8Je*Hv&`1=m$@tsS4T?JBs|>A33G%BquvSHX1^Tvx$$ z6 $9?eC%eJ+!}v_V>{K9@^hS`+I1A5AE-v{XMk5hxYf-{vO)jL;HJZe=qIt zrTx9MznAv+(*9oB-%I;@X@4*6@1^~{w7-}3_tO4e+TTn2duhLh+`Jl5*`^qW<>u9p zn^z->icjh3yjf_uc{QSI*H~^|4Y_$W O| zbB$t0hL-JIqu7z5Wjog>c4TO|c{Pe18Cq^$jbcZJmYY|j*pZ>-=G7>IWM#|Et5F2W z&~o!?6hShy+`JlPJKL3(n^&W3XG6=)tC2ly6IyOwjYf2KhwpK{<>u8W+u5$M+`Jma zm#pS0*Z^8?UXAi*4J{+3Mj0tqw%oiLS(nvVZeEQ>YlfDaSEC#i`_^*v_TlUH$?85* z*+y>r@OAs}b^By{KUR&6-1f=p>>3-x?8DdX!`JP@*X_gC?ZemY!`JP@*X_gC?ZemY z!`JP@*X@&LtsNV=?UQB=ZREC3nl-eM+dgU5&_-_iBqb}`$Za3KZXdpGAHHrMzHT4B zZlB~}cWWcJefYY4l7rRQ$Zemv{>a%cuHO+_hSh#?YH0a6`^hcYFYC3k<>&0zJsVnn z&VG&R4J|)szql~8{G9!wYiRj7`$f^v@^cPA $(&BmF2m;F1m)6=k~hj8d{#)>!NFDd2UCc zdlb4yp?eg%N1=NZx<{dV6uL*Ddlb4yp?eg%N1=NZx<{dV6uL*Ddlb4yp?eg%I>}7b zb N=pKWvR_|%S_&o;QW6(VY-DA)_2Hj)OJr3RD z&^->n$Dw;1y2qh=9J sg@gsmRtCyTDG$Jt~b@Dq4};iv0j}4NZEYXo1$W9zUxg o?&gRXMADVtYRE}=Z(Ip`{ fKnXDx;SxaQH zmdIo+k;z&jleI)9Yl%$O5}B+eGFhw9n%&g}jk0QqOx9}5W;K=%UrS`NRuVIcw%S!I zi5c2zS1pmrS|XFRL?&w`!`05alHof-^LXz{hKA YIp@y`pyKQ9pfyg>Z(0`boa#6K?(|GYr_ z^CH%D5$n2$bzQ`|E@E95v960)*F~)BBGz>g>$-?_UBtRBVqF)pu8UaLMXc*0)^!o< zx`=gM#Jb)W-EGeMqHE{_W3|imebIedXlIUT*Oz7i?~A(Ctn2z`;YKir>$h-yE*PL@ zn`UtDOL~TR)bF62&$k8C*r{UgOS6Vosc8Tkxuywh23x@EeA@=LgB_~Bgk~?H*-L2l z5}Lh)W-p=HOKA2In!SW(FQM5>X!a7Cy@X~jq1j7l_7a-Cgk~?Joy%zFGTOO}b}plx z%V_5^+PRE&E~6b~`>K~+Mmv|$&SkW78SPv~JD1VUWwdh{?OaAXm(fm;8GVo$eUKTw zy(xE)@&jx}A7n-!WJa&E&?Jo@Gx{Ji`XDoUol~XxPLLUWkQsfD8GVo$eUKS_kQsfD z8GVo$eUKS_kQsfD8GVo$eUKS_kQsfD8GVo$eUKS_kQsfD8GTR^*e$v?qYp|BT5Xja ztj1>aL1y$pX7oX3^g(9yL1y$pX7oX3^g(9yL1y$pX7oX3^g(9yL1y$pX7oX3^g(9y zL1y$pX7q}YBLSPy2bs|;rbyXl^g(9yL1y$pX7oX3^g(9yL1y$pX7oX3^g(9yL1y$p zX7oX3^g(9yL1y$pX7oX3^g(9yL1y$pX7oX3^g(9yL1y$pX7oX3^g(9yL1y$pX7oX3 z^g(9yLFI~U);-&dKFEwd$c#S7j6NveV&B?~KFEwd$c#S7j6TSWKFEwd$c#S7j6TSW zKFEwd$c+Aq?CO+r1^c-o`#Gnw?Y6rj%Q3Xwc2{IGhPK=8imb)ZcH3Q%Z5Y~ayDPGX zdf_@naj!`GhPK=8iu7)1yX~$>r-ruM?us;JXuIvMNK=Nk+wO{<8iuyp?uz7ZE!%Fp zE0VvV?Y6rj`5W49yDO5sq3yQ2BFP)tZo4azyrJ#3yCSJtYqs0&ill02yX~$>o>sQq zc2^`%L)&e4MY1xq-F9`zrw;klA)h+rQ-^%&kWU@*sY5<>$fpkZ)FGcb X1(z z@~J~Ub;zd<`P3nwI^ $fpkZ)FGcb X1(z@~J~U^~k3l`RGJFNwpsN)FYpI z XA=9@~KBY^~k3l`P3tydgN1&eCm-;J@TnXKK00_9{JQGpL*m|k9_KpPd)Of zM?Uq)rylv#BOje;FOAnDpL*m|k9_KpPd)OfM?Uq)rylv#BcFQYQ;&S=kxxDHsYgEb z$fq9p)FYpI Fjvc#{-TN+xH_*HpJ zL(3AsD$iqO%M!n;D4DfvS>jg}B{Q@v@vC}T8CsV3RXww;Y+2%0 M4q1sHYfOzI3B{ilOb@(7pjE+TM*u^$SDWyU{3_7)8sUY?Mq4ZSO{-WMXK0HyR}q zL)*L2s2*f!dp8=@gA8r&Mx%O=q3zvhRNU9@&CUsL)DzRt&IxZMf3i_eOe@>o4Mm @Y-i=1}K||ZS(I~AM+TM*u^+!Y7yU{2OuGaI?G}wd&o6ukr z8f-#?O=z$Q4K|^{CN$V2Z}wCD$~4%72Aj}e6B=wngH33#2@N)(!6r1=ga(_?U=tc_ zl4qNvc1(j!Xs`(lHle{LG}wd&o6ukr8f-#?O=z$Q4K|^{CN$WD2Aj}e6B=wngH33# z2@N)(!6r1=ga(_?U=tc_lFwA^3=KA+!6r1=ga(_?U=tc_LW50cun7$|p}{6J*rXn3 z5;hGs$$y%}OoL5mun7$|p}{6J*n|d~&|ni9Y(j%gXs`(lHle{LG 1maj>7R(^^0?TqDX(ukp*v3yM$F|;$5uSp|@HXFJo znOND*SiZ*0=$a&8?bsR1*W{6`WjkZ}ntYL=ow0mP(y(vsjOA;RhM}FY+>AXpW6#al zb2Ikbj6FAF&&}9#Gxpq!JvU>|&De7@_S}p;H)GGu*mE=X+>AXpW6#alb2Ikbj6FAF z&&}9#Gxpq!JvU>|&De7@_S}p;H)GGu*mE=X+>AXpW6#alb2Ikbj6FAF&&}9#Gxpq! zJvU>|&De7@_S}p;H)GGu*mE=X+>AXpW6#alb2Ikbj6FAF&&}9#Gxpq!JvU>|&De7@ z_S}p;H)GGu*mE=X+>AXpW6#alb2Ikbj6FAF&&}9#Gxpq!JvU>|&De7b8f-y>EoiU> z4Yr`c7Btv`23ycz3mR-egDq&V1r4^K!4@>wf(BdAU<(>-L4z%5umugapurY2*n$RI z&|nK1Y(ax9Xs`thwxGclG}wX$ThL$&8f-y>EoiU>4Yr`c7Btv`23ycz3mR-egDq&V z1r4^K!4@>wf(BdAU<(>-L4z%5umugapurY2*n$RI&|nK1Y(ax9Xs`thwxGclG}wX$ zThL$&8f-y>EoiU>4Yr`c7Btv`23ycz3mR-egDq&V6_3=4M{30*wc?Rl@kp(Bq*gpq zD;`NFTB`52;*nbMNIL&X9;p?N)QU%H#Ur)iky`Oct$3tXJW?wjsTGgZibrb2Bemj@ zTJcD&(uh$pkJO4sYQ-b9;*nbMNUeCJRy tH#E?*Ja!Gt&MxHBZupZ2Cp+3yv}Iw zI-|ksj0UeW8obVE@H(Tx>x>4k%Sx>U8~0w9l^WW(_qwdq(8j&jWu=BT?!C@v@H(Tx z>x>4kGa9_kXz)6t!Rw3$uS@TCpEmBj&S>zuY`|)4+ zaM}i^ZE)HKr)_ZB2B&Rs+6Jd>aM}i^ZE)HKr)_ZB2B&Rs+6Jd>aM}i^ZE)HKr)_ZB z2B&Rs+6Jd>aM}i^ZE)HKr)_ZB2B&Rs+6Jd>aM}i^?Qq%-r|ods4yWyK+774faM}*1 z?Qq%-r|ods4yWyK+774faM}*1?Qq%-r|ods4yWyK+774faM}*1?Qq%-r|ods4yWyK z+774faM}*1?Qq%-r|ods4yWyK+774faN2=4?!X&&;Eg-*#vOR$4!m&(-navA+<`al zz#DhqjXUth9eCpoym1HKxC3w8fj92J8+YK1JMhLGc;gPdaR=VG18>}cH}1e2ci@dX z@Wvf@;|{!W2i~{?Z`^@5?!X&&;Eg-*#vOR$4!m&(-navA+<`alz#DhqjXUth9eCpo zym1HKxC3w8fj92J8+YK1JMhLGc;gPdaR=VG18>}cH}1e2ci@dX@Wvf@;|{!W2i~{? zZ`^@5?!X&&;Eg-*#vOR$4!m&(-navA+<`alz#Dhqjc>?SS35UkrG~bWazlFjrO-~l zzab5NB(xmw8`7Pj?Hjrw4O-du4c(CSenV(0DK})puL*4><%X 6Fgx0RF|vUB^^_6^;TzKo*n8@eHV8QQ*~8`77d z?HjrweHq%mp&P8E++Zc;1}iBy)C=scY$fG}q-SU=DK{iHYv1-7+)yttinfw+L-ucI zD=9Z5L;JQ3w3CHys4wW9iq78d|9|6(cfI8<4T}%^aoBIehlVc+KO5fUiT7l99`XFG z=W;}N#Iq3}N6yguOP#2csM;Rmdc4@ VdBh3Lmt2(80ld2bT`MFt}sL zvLWR|E)V(R(7@20!`#Cj(Er~Y-gkKE@Qx8nM&yk2j@&eA&Zr-Z`a^s|{Or*^M~@x- z?C7Hj@d+0a8pb4#d2Gx#$25;k9=mPq+lf;WFO3f$9~l3Y@h^|B9KV13_r{+~awi>5 zdL!xWq@N_!C4H3i+oV5Eh?uZ)!sZD%6JD55JmH%Yc2B6CxNPFaiBC@4Ht}yKetqKQ zNy{c}ob=?RZIk|X($^ $#}xOJUQ-5789OCq z%Iqm$pXyBQId$OFgsD@eW=!2Rb#F@Fl($oUl2SJ Hj{x zb^3qJ2%ix (=gjPxJ7@l8W~YCk zf42WI|I7Z%vwF{3J?kgw+tT-?|1smI86EeGyl3`3b+c1ve T7=kMnHcJ3o{U!Hq>Zri+`^OEOnocHp)v-5s4KVklp^Y_nx z-` gRk_FIL~|gzN2`@j8+GSy|B6dc2~4+1ouwx=Q)0qVuvoR%htdzd~iVeqG*O z4$~gsSG&vMcU #RBG2Lao^YC`r>;8o+wYxsr85kbh zUGC|mgpcbk>zz>H>$}UnoWY) `k!`}Bkrh|tRhEt*GD )p zMsCYnmYeg1XCE7R-}BFW;kg$_&U^8x#~*v~sYgeC`KfJBj?8}ixyJ+BAAfA*g4}1f zjeOt>TOJ=7f8Q6j<;EwEyzi+;AAj~aef9jak3Ak3x$VivN3K|W-^d5IKK}n!ckQul z9O->l67{m|B 6qE*MfSbmefyM{}0MeT{?D!WU|_8;vP zxV8b?d!gtPEpS(~DB8o_4Rw#^(0il>`ap}KE!v_e`tJ3gq9|}h`fua@eluJ>9Q%?0 z?H{!)&NnmPeDCk$aJiao8SEZ{Bd>d5 OqqMz {{T4?;i2;*-*kWwac$8#NhuUQ@o>_G7pc#%V>XpxTNL$hBp7uT6Qu zzY^3CAKvX)R@$*#(<^I!wAyg*7^a|H_gij*2KM7<`)c^M4r4_AT`$VXs|b&3!;K=p z3jEbDSPjA$K@m9;yko!0Sb*%e{K&5~yu_mI)m1O7!hnvf`?ox8<%So-^-Dq6jEp0E zH q@SW4{S16f 1V@$iNnB%UB40O(1ff;yU3`kW6+gLp4Xr=5xr~BGhTLJY9Ch8%Ee)TxhM|X)wmrZ z6vBXNK+o8%_zgccR$&MS?WpBNQEoYoS5Hn&t*x!)5YsyUbJd_Z^|0|Ic%vzkB(EfS zI`y{7a{Qeu%i)KyIr4u~bY#LXs)qh*9OWXvkqg55)Wx$GjwH5tz?{hA6q{9?2@wik z)WtHsy_R?vs^UpC63?EP7Dw@$?Lo&eRspUA)*YOEhZAn_{P-451(f0fzVTi`FA{gr zo5!m!Jj}4IVq^_%4Ej=B#(0F^P~6fO66b3KphC_sgF+Ld67R7W0LPd)fajW!KIwSC z<63fTYtE!Yd6xC6D>!xV0{R!B6LGiF-cG^6+;!lDpyzA;61wK}Z^GGdHCCb2b?~Id zA^2_Af2;QQ>wXfP9z>Dj#=^%p=CfK)!|}n=P2)KSFJk2l$P+;hYS0pyo;Q4q(Z=+t zu~h(>Lv6WD8HgpJ2kjQ3tpT^G^;*$um-Nhb4d2PbIY|EfBH%&XBk(l-Wb`FmDftp+ z=xwQ?CT4Mx7kZSG{;Ff0i2`4HdDH)GXu=3$R3!16MC}c&Be||2rYmTZSlUjPYrnrX z%#p1TDm71|4P*BTY>~)u0Mv|>CS#0`j_9a92M-u8Fk9#qM!baXj56AR5odI#yo|n9 z&oU7(O|Gm0Vk9t!e2ryRR59KHjRr=mdaeb_NyG;2jgzVH$`$0yXk0D&)we@OJG5S|Px7Mp4p17^KRe(jVYg>5MfKR&A zmR{e~)W}@d^k|JFf+-iR_i)~%6+(Lx8H}O3fFtdNtG((`)f^*?(q4u>ZN+RNXWEJu zjRdQQ(VJscZ7tF_cE5!&(x{+MYi*CwHdZpmnUy92NsT(O9#SJy%eXei4>6ZC|0bwz z%?SFMvAV3=rk0KNN@}vF`PX%y*~B_}1{61WFc(Y=Q4d0tC9!6t*L$ez^uhhPiWBzm z!#UQD6|ALn9<=avo|=lDXk#)u;myjvwJNTG0#|R(!_CM^GMUnmgQ@ML%}8avOrp1j z5hF l(Z^e3g_@eGz^r9;Wgg_fnK8JE`Y?r`HT{#*Ync o? z#2D@%-G%2w X*`MX8F*wI?@KnG(K&&qT|Nt) zkBCX^m>k4N4mxp *>_X(Vb z@-6YRxJ%&2#iztiieDDLA%4{w5FIOp(}aE+-?h9cK8e2)f6W>c{~-PoS0{fHSAG5q z_cQ*A_#*Bt{sZwP CS+?o4ts3d ?5AWa}Rh)|@UKgJi zKO%lud|dn}zE{KF99Tovu=sEBmNjCHioX{BBED Zp{m-r>|>*CY6a`rRg8`eJYhWH(8zxBBF zg!QDAww|({w%%_&V@Ycq*KqBDm# -rWE-_hb1N z1`6#kNX>hV*yYf%6DMAHR9{QNC()+cTUHO fYM}7p;54+?qPf?v8>~(*&l8 z@y23NF3C>Em1=acSjLFt+$f *-9)Hy;3Qw`Wy*50~0PpNaYuc zDrL{9L3=JeK3-B*Iji~|8}i6nH&vOFoLS#t^-Ycg%*&4KfLwPvHPN|LEH9?r%cY`S z!rbXAMNFhAeUDXE4LE8jpIJw-%9rP|Duu2+XG=U)dCpZIuYj2vn9Qm{M^d7`LRDFX zN_k!jF=Pfs#_kReOq6AzW4kn()={Ks2PM