Module: XML::Mapping
- Defined in:
- lib/xml/mapping/base.rb,
lib/xml/mapping/version.rb,
lib/xml/mapping/standard_nodes.rb
Overview
This is the central interface module of the xml-mapping library.
Including this module in your classes adds XML mapping capabilities to them.
Example
Input document:
:include: company.xml
mapping class declaration:
:include: company.rb
usage:
:include: company_usage.intout
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).
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.
Defined Under Namespace
Modules: ClassMethods Classes: ArrayNode, BooleanNode, ChoiceNode, HashNode, Node, NumericNode, ObjectNode, SingleAttributeNode, SubObjectBaseNode, TextNode
Constant Summary collapse
- Classes_by_rootelt_names =
defined mapping classes for a given root elt name and mapping name (nested map from root element name to mapping name to array of classes)
can’t really use a class variable for this because it must be shared by all class methods mixed into classes by including Mapping. See multi-io.github.io/mydocs-pub/ruby/mixin_class_methods_global_state.txt.html for a more detailed discussion.
{}
- VERSION =
'0.10.1'
Class Method Summary collapse
-
.add_node_class(c) ⇒ Object
Registers the new node class c (must be a descendant of Node) with the xml-mapping framework.
-
.append_features(base) ⇒ Object
:nodoc:.
-
.class_and_mapping_for_root_elt_name(name) ⇒ Object
Finds a mapping class and mapping name corresponding to the given XML root element name.
-
.class_for_root_elt_name(name, options = {:mapping=>:_default}) ⇒ Object
Finds a mapping class corresponding to the given XML root element name and mapping name.
-
.load_object_from_file(filename, options = {:mapping=>nil}) ⇒ Object
Like load_object_from_xml, but loads from the XML file named by filename.
-
.load_object_from_xml(xml, options = {:mapping=>nil}) ⇒ Object
“polymorphic” load function.
Instance Method Summary collapse
-
#fill_from_xml(xml, options = {:mapping=>:_default}) ⇒ Object
“fill” the contents of xml into self.
-
#fill_into_xml(xml, options = {:mapping=>:_default}) ⇒ Object
Fill self’s state into the xml node (REXML::Element) xml.
-
#initialize(*args) ⇒ Object
Initializer.
-
#initialize_xml_mapping(options = {:mapping=>nil}) ⇒ Object
Xml-mapping-specific initializer.
-
#post_load(options = {:mapping=>:_default}) ⇒ Object
This method is called immediately after self has been filled from an xml source.
-
#post_save(xml, options = {:mapping=>:_default}) ⇒ Object
This method is called immediately after self’s state has been filled into an XML element.
-
#pre_load(xml, options = {:mapping=>:_default}) ⇒ Object
This method is called immediately before self is filled from an xml source.
-
#pre_save(options = {:mapping=>:_default}) ⇒ Object
This method is called when self is to be converted to an XML tree.
-
#save_to_file(filename, options = {:mapping=>:_default}) ⇒ Object
Save self’s state as XML into the file named filename.
-
#save_to_xml(options = {:mapping=>:_default}) ⇒ Object
Fill self’s state into a new xml node, return that node.
Class Method Details
.add_node_class(c) ⇒ Object
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).
505 506 507 508 509 510 511 512 |
# File 'lib/xml/mapping/base.rb', line 505 def self.add_node_class(c) meth_name = c.name.split('::')[-1].gsub(/^(.)/){$1.downcase}.gsub(/(.)([A-Z])/){$1+"_"+$2.downcase} ClassMethods.module_eval " def \#{meth_name}(*args)\n \#{c.name}.new(self,*args)\n end\n EOS\nend\n" |
.append_features(base) ⇒ Object
:nodoc:
108 109 110 111 112 113 |
# File 'lib/xml/mapping/base.rb', line 108 def self.append_features(base) #:nodoc: super base.extend(ClassMethods) Classes_by_rootelt_names.create_classes_for(base.default_root_element_name, :_default) << base base.initializing_xml_mapping end |
.class_and_mapping_for_root_elt_name(name) ⇒ Object
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]
134 135 136 137 |
# 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 |
.class_for_root_elt_name(name, options = {:mapping=>:_default}) ⇒ Object
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).
122 123 124 125 126 |
# File 'lib/xml/mapping/base.rb', line 122 def self.class_for_root_elt_name(name, ={:mapping=>:_default}) # TODO: implement Hash read-only instead of this # interface Classes_by_rootelt_names.classes_for(name, [:mapping])[-1] end |
.load_object_from_file(filename, options = {:mapping=>nil}) ⇒ Object
Like load_object_from_xml, but loads from the XML file named by filename.
475 476 477 478 |
# File 'lib/xml/mapping/base.rb', line 475 def self.load_object_from_file(filename,={:mapping=>nil}) xml = REXML::Document.new(File.new(filename)) load_object_from_xml xml.root, end |
.load_object_from_xml(xml, options = {:mapping=>nil}) ⇒ Object
“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)
461 462 463 464 465 466 467 468 469 470 471 |
# File 'lib/xml/mapping/base.rb', line 461 def self.load_object_from_xml(xml,={:mapping=>nil}) if mapping = [: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 |
Instance Method Details
#fill_from_xml(xml, options = {:mapping=>:_default}) ⇒ Object
“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.
181 182 183 184 185 186 187 188 189 |
# File 'lib/xml/mapping/base.rb', line 181 def fill_from_xml(xml, ={:mapping=>:_default}) raise(MappingError, "undefined mapping: #{options[:mapping].inspect}") \ unless self.class.xml_mapping_nodes_hash.has_key?([:mapping]) pre_load xml, :mapping=>[:mapping] self.class.all_xml_mapping_nodes(:mapping=>[:mapping]).each do |node| node.xml_to_obj self, xml end post_load :mapping=>[:mapping] end |
#fill_into_xml(xml, options = {:mapping=>:_default}) ⇒ Object
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.
216 217 218 219 220 |
# File 'lib/xml/mapping/base.rb', line 216 def fill_into_xml(xml, ={:mapping=>:_default}) self.class.all_xml_mapping_nodes(:mapping=>[:mapping]).each do |node| node.obj_to_xml self,xml end end |
#initialize(*args) ⇒ Object
Initializer. Called (by Class#new) after self was created using new.
XML::Mapping’s implementation calls #initialize_xml_mapping.
169 170 171 172 |
# File 'lib/xml/mapping/base.rb', line 169 def initialize(*args) super(*args) initialize_xml_mapping end |
#initialize_xml_mapping(options = {:mapping=>nil}) ⇒ Object
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.
159 160 161 162 163 |
# File 'lib/xml/mapping/base.rb', line 159 def initialize_xml_mapping(={:mapping=>nil}) self.class.all_xml_mapping_nodes(:mapping=>[:mapping]).each do |node| node.obj_initializing(self,[:mapping]) end end |
#post_load(options = {:mapping=>:_default}) ⇒ Object
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.
207 208 |
# File 'lib/xml/mapping/base.rb', line 207 def post_load(={:mapping=>:_default}) end |
#post_save(xml, options = {:mapping=>:_default}) ⇒ Object
This method is called immediately after self’s state has been filled into an XML element.
The default implementation does nothing.
253 254 |
# File 'lib/xml/mapping/base.rb', line 253 def post_save(xml, ={:mapping=>:_default}) end |
#pre_load(xml, options = {:mapping=>:_default}) ⇒ Object
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.
195 196 |
# File 'lib/xml/mapping/base.rb', line 195 def pre_load(xml, ={:mapping=>:_default}) end |
#pre_save(options = {:mapping=>:_default}) ⇒ Object
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”.
245 246 247 |
# File 'lib/xml/mapping/base.rb', line 245 def pre_save(={:mapping=>:_default}) REXML::Element.new(self.class.root_element_name(:mapping=>[:mapping])) end |
#save_to_file(filename, options = {:mapping=>:_default}) ⇒ Object
Save self’s state as XML into the file named filename. The XML is obtained by calling #save_to_xml.
259 260 261 262 263 264 265 |
# File 'lib/xml/mapping/base.rb', line 259 def save_to_file(filename, ={:mapping=>:_default}) xml = save_to_xml :mapping=>[:mapping] formatter = [:formatter] || self.class.mapping_output_formatter File.open(filename,"w") do |f| formatter.write(xml, f) end end |
#save_to_xml(options = {:mapping=>:_default}) ⇒ Object
Fill self’s state into a new xml node, return that node.
This method calls #pre_save, then #fill_into_xml, then #post_save.
227 228 229 230 231 232 |
# File 'lib/xml/mapping/base.rb', line 227 def save_to_xml(={:mapping=>:_default}) xml = pre_save :mapping=>[:mapping] fill_into_xml xml, :mapping=>[:mapping] post_save xml, :mapping=>[:mapping] xml end |