latest design (12/2004)¶ ↑
At the lowest level, the “Accessors” sub-module contains reader and creator functions that correspond to the various types of path elements (elt_name, @attr_name, elt_name 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 }