class Trivet::Node
Objects of this class represent a single node in a hierarchy.
Constants
- INDEX_WITHIN
Values for positioning a node within its parent. These values are used internally only.
- INDEX_WITHOUT
Values for positioning a node before or after a sibling. These values are used internally only.
Attributes
A Trivet::ChildSet object containing the children. This property can generally be treated like an array, but it has a few other features as well.
Returns the id of the node, or nil if it does not have an id.
A hash of any miscellaneous information you want to attach to the node.
Returns the parent object of the node, or nil if there is no parent.
Public Class Methods
Creates a new Trivet::Node
object. The first param can be a parent node, the id of the new node, or nil.
# File lib/trivet.rb, line 100 def initialize(pod=nil) @id = nil @parent = nil @children = Trivet::Childset.new(self) @misc = {} # if parent object send if pod.is_a?(Trivet::Node) or pod.is_a?(Trivet::Document) self.parent = pod elsif pod.is_a?(String) self.id = pod end end
This method provides a concise way to override Trivet::Node#allow_child?
so that it always returns false. Just add this to your class:
self.no_children()
# File lib/trivet.rb, line 942 def self.no_children() self.define_method('allow_child?') do |child| return false end end
Public Instance Methods
A shortcut for adding children of any arbitrary class. Takes zero or more params, each of which is an object.
# File lib/trivet.rb, line 388 def add(*objs) objs.each do |obj| @children.push obj end end
This method is called when a node or other object is added to a node. By default, always returns true. Override this method to create custom rules.
# File lib/trivet.rb, line 925 def allow_child?(child) return true end
Returns an array of the node's ancestors.
# File lib/trivet.rb, line 631 def ancestors if @parent.is_a?(Trivet::Node) return @parent.heritage() else return [] end end
Returns the class to use for new nodes in Trivet::Node.node()
. By default, this method returns the same class as the calling node. Override this method to create different rules for the class to use.
# File lib/trivet.rb, line 572 def child_class(*opts) return self.class end
Returns the depth of the node. The root note returns 0, its children return 1, etc.
# File lib/trivet.rb, line 995 def depth return ancestors.length end
Returns the Trivet::Document
object that holds the tree. Returns nil if there is no document.
# File lib/trivet.rb, line 958 def document() # $tm.hrm return root.parent end
Returns an array of the node's ancestors plus the node itself.
# File lib/trivet.rb, line 648 def heritage if @parent.is_a?(Trivet::Node) return @parent.heritage() + [self] else return [self] end end
Sets the id property of the node. If any other node already has that id then that node loses its id.
# File lib/trivet.rb, line 404 def id=(new_id) # $tm.hrm # look for another node with this id if new_id root.traverse('self'=>true) do |tag| if (tag.id == new_id) and (tag != self) # raise 'redundant-id: ' + new_id.to_s tag.id = nil end end end # set id @id = new_id end
Returns the index of the node within the parent. Returns nil if the node has no parent.
# File lib/trivet.rb, line 431 def index if @parent return @parent.children.find_index(self) else return nil end end
This method is called for each node in a query. If this method return true then the node is yielded/returned in the query. By default, this method always returns true. See Trivet::Node#query()
for more details.
# File lib/trivet.rb, line 801 def match?(qobj) return true end
Moves the given object from this node to the given node. Note that the object being moved doesn't actually have to be a child of this node, but it will be removed if it is.
# File lib/trivet.rb, line 1069 def move_child(child, tgt) # $tm.hrm @children.remove_object child tgt.add child end
Returns the node's next sibling.
# File lib/trivet.rb, line 1037 def next_sibling # $tm.hrm found_self = false # if parent, search through siblings if parent parent.children.each do |sib| if sib == self found_self = true elsif found_self return sib end end end # no next sibling return nil end
Create a node and positions the new node either within the calling node, before it, after it, or replaces it. By default, the new node is positioned as the last child node of the caller.
In its simplest use, node() creates a new node, yields it if given a block, and returns it. You can build structures by calling node() within node blocks. If a single string is given as a param, that string is used for the `id` for the new node.
food = Trivet::Node.new() food.id = 'food' food.node('spices') do |spices| spices.node 'paprika' spices.node('pepper') do |pepper| pepper.node 'java' pepper.node 'matico' pepper.node 'cubeb' end end
option: index
The index option indicates where the new node should be positioned. This option can have one of the following values:
-
first: The new node becomes the first child of the caller object.
-
last: The new node becomes the last child of the caller object. This is the default behavior.
-
before: The new node is placed before the caller object.
-
after: The new node is placed after the caller object.
-
replace: The node node replaces the caller object and the caller is removed from the tree.
-
[integer]: The new node is placed at the index of the given integer.
option: use
If the `use` option is sent, then that object is used as the new node.
# File lib/trivet.rb, line 318 def node(opts={}) # normalize opts if opts.is_a?(String) opts = {'id'=>opts} elsif opts.is_a?(Trivet::Node) opts = {'use'=>opts} end # $tm.hrm idx = opts['index'] || 'last' # create child object new_node = opts['use'] || child_class(opts).new() # id if sent if opts['id'] new_node.id = opts['id'] end # add to this node if INDEX_WITHIN.include?(idx) or idx.is_a?(Integer) new_node.set_parent self, 'index'=>idx # add to parent elsif INDEX_WITHOUT.include?(idx) if @parent if idx == 'before' new_node.set_parent @parent, 'index'=>self.index elsif idx == 'after' new_node.set_parent @parent, 'index'=>self.index + 1 else raise 'unrecognized-without-index: ' + idx.to_s end else raise 'cannot-set-before-or-after-if-no-parent' end # replace elsif idx == 'replace' new_node.set_parent @parent, 'index'=>self.index @children.to_a.each do |child| child.parent = new_node end unlink() else raise 'node-unknown-index: ' + idx.to_s end # yield if necessary if block_given? yield new_node end # return return new_node end
Searches for a node by its id.
# File lib/trivet.rb, line 897 def node_by_id(qid) if @id == qid return self end # check children @children.each do |child| if child.is_a?(Trivet::Node) if node = child.node_by_id(qid) return node end end end # didn't find the node return nil end
This method is a shortcut for Trivet::Node#set_parent()
without any options.
# File lib/trivet.rb, line 147 def parent=(new_parent) return set_parent(new_parent) end
Returns the node's previous sibling
# File lib/trivet.rb, line 1008 def previous_sibling # $tm.hrm latest = nil # if parent, search through sibling if parent parent.children.each do |sib| if sib == self return latest else latest = sib end end end # no parent return nil end
Runs a query on the tree, yielding and returning nodes that match the given query. This method is really only useful if you subclass Trivet::Node
and override Trivet::Node.match?()
. By default, match? always returns true. The query param can be any kind of object you want it to be. That param will be passed to match? for each node. If match? returns true then the query yields/returns that node.
In these examples, we'll assume a tree like this:
food spices paprika pepper java matico cubeb fruit red cherry apple
For example, you could override match? so that it returns true if a given string is within a node's id.
class MyNode < Trivet::Node def match?(qobj) if @id return @id.match(/#{qobj}/mu) else return false end end end
You could then query for nodes that have 'o' in their id:
food = MyNode.new('food') # ... add a bunch of child nodes food.query('o') do |node| puts node.id end
That query returns one node, 'matico'
option: self
By default, the query does not include the node itself, only its descendents. To include the node itself, use the 'self' option.
food.query('o', 'self'=>true) do |node| puts node.id end
That query will return 'food' and 'matico'.
option: recurse
The recurse option indicates how far into the tree the query should recurse. The default is Trivet::ALWAYS, which means to recurse all the way down.
Trivet::UNTIL_MATCH means to not recurse into nodes that match the query. So if a node matches, its children are not included in the query.
Trivet::STOP_ON_FIRST means that the query is stopped completely after the first match is found. Using this option means that the query always returns either zero or one matches.
# File lib/trivet.rb, line 757 def query(qobj, opts={}) ctl = opts['recurse'] || Trivet::ALWAYS rv = [] # traverse self.traverse(opts) do |node, recurse| if node.match?(qobj) # add to return array rv.push node if ctl == Trivet::UNTIL_MATCH recurse.prune elsif ctl == Trivet::STOP_ON_FIRST recurse.stop end end end # yield if block_given? rv.each do |node| yield node end end # return return rv end
Returns the root node of the tree. Returns self if the node has no parent.
# File lib/trivet.rb, line 586 def root if @parent return @parent.root else return self end end
Sets a new parent for the node. The new parent can be either a Trivet::Node
object or a Trivet::Document
object.
For example, we'll start with the standard tree we've been using:
food spices paprika pepper java matico cubeb fruit red cherry apple
Now we'll set fruit's parent to the pepper node:
fruit = food.node_by_id('fruit') pepper = food.node_by_id('pepper') fruit.set_parent pepper
That moves the fruit node and all its descendents into pepper:
food spices paprika pepper java matico cubeb fruit red cherry apple
option: index
The index option indicates which position within the parent the node should be moved to. This option has no meaning if the new parent is a Trivet::Document
object.
Set index to 'first' to move it to the first position:
fruit = food.node_by_id('fruit') pepper = food.node_by_id('pepper') fruit.set_parent pepper, 'index'=>'first'
Set index to an integer to move the node to that index within the parent.
fruit = food.node_by_id('fruit') pepper = food.node_by_id('pepper') fruit.set_parent pepper, 'index'=>1
nil
If the parent param is nil then the object is unlinked from its parent.
# File lib/trivet.rb, line 217 def set_parent(new_parent, opts={}) # $tm.hrm @id opts = {'recurse'=>true}.merge(opts) index = opts['index'] || 'last' # add to new_parent if new_parent # add to another node if new_parent.is_a?(Trivet::Node) new_parent.trace(self) unlink() @parent = new_parent # add to parent's children if opts['recurse'] if index == 'last' @parent.children.push self, 'recurse'=>false elsif index == 'first' @parent.children.unshift self, 'recurse'=>false elsif index.is_a?(Integer) @parent.children.insert index, self, 'recurse'=>false else raise 'set-parent-unknown-index: ' + index.to_s end end # new parent is a document elsif new_parent.is_a?(Trivet::Document) unlink() @parent = new_parent # set as document's root if opts['recurse'] new_parent.set_root self, 'recurse'=>false end # else raise exception else raise 'unknown-class-for-parent: ' + new_parent.class.to_s end # unlink node because it does not have a parent anymore. else unlink() end end
# File lib/trivet.rb, line 884 def tab return ' ' end
Useful to outputting a string that represents the node for the purpose of debugging. By default, outputs to_s. This method is used in to_tree
, so it's usually a good idea to return a single-line string.
# File lib/trivet.rb, line 821 def to_debug return to_s end
Returns the id if there is one. Otherwise returns Object#to_s.
# File lib/trivet.rb, line 814 def to_s return @id || super() end
This method is for development and debugging. Returns a string consisting of the node and all its descending arranged as an indented tree. Each node's Trivet::Node#to_s
method is used to display the node. Descendents that are not Trivet::Node
objects are not displayed.
# File lib/trivet.rb, line 838 def to_tree(opts={}) opts = {'depth'=>0}.merge(opts) rv = tab() * opts['depth'] + self.to_debug # indent if opts['indent'] rv = opts['indent'] + rv end # send_opts send_opts = opts.clone send_opts['depth'] += 1 # recurse @children.each do |child| # recurse into child node if child.is_a?(Trivet::Node) rv += "\n" + child.to_tree(send_opts) # elsif if there's a custom child_to_tree method elsif respond_to?('child_to_tree') send_opts['tab'] = tab() add = child_to_tree(child, send_opts) if add and add.match(/./mu) rv += "\n" + add end # String elsif child.is_a?(String) rv += "\n" + (tab() * send_opts['depth'] ) + child.lx.collapse end end # return return rv end
Checks if a node is about to become nested within itself. This method is used by Trivet::Node.set_parent
to prevent circular references. Generally you don't need to call this method.
# File lib/trivet.rb, line 667 def trace(new_node) if new_node == self raise 'circular-reference' end # trace to parent if @parent and not(@parent.is_a?(Trivet::Document)) @parent.trace(new_node) end end
Traverses the tree starting with the children of the node. Each node in the tree is yielded to the block if there is one. Returns and array of descendents.
food.traverse() do |node| puts (' ' * node.depth) + node.id end
That gives us output similar to that of the to_tree
() method.
spices paprika pepper java matico cubeb fruit red cherry apple
By default, the node on which you call this method itself is not traversed. You can include that node with the 'self' option:
food.traverse('self'=>true) do |node| puts (' ' * node.depth) + node.id end
The first parameter for the yield block is the node, as in the examples above. The second param is a Trivet::TraverseControl
object which can be used to control the recursion.
Prune recursion
You can indicate that the traversal should not recurse into a node's children with Trivet::TraverseControl.prune
. For example, the following code doesn't traverse into the spices node:
food.traverse('self'=>true) do |node, ctl| puts (' ' * node.depth) + node.id if node.id == 'spices' ctl.prune end end
Giving us this output:
food spices fruit red cherry apple
Stop recursion
You can stop the recursion by calling Trivet::TraverseControl.stop
. For example, suppose you want to stop the traversal completely when you get to the “java” node. You could do that like this:
food.traverse('self'=>true) do |node, ctl| puts (' ' * node.depth) + node.id if node.id == 'java' ctl.stop end end
That gives us output like this:
food spices paprika pepper java
# File lib/trivet.rb, line 522 def traverse(opts={}, &block) ctrl = opts['ctrl'] || Trivet::TraverseControl.new() rv = [] # yield self if opts['self'] # yield if block_given? yield self, ctrl end # add to return array rv.push self # return if stopped ctrl.stopped and return rv end # if pruned, don't recurse into children, but continue to next sibling node if ctrl.pruned ctrl.pruned = false # recurse into children else # puts "--- #{self}" @children.to_a.each do |child| if child.is_a?(Trivet::Node) rv += child.traverse('self'=>true, 'ctrl'=>ctrl, &block) ctrl.stopped and return rv ctrl.pruned = false end end end # return return rv end
Removes the node from the tree.
# File lib/trivet.rb, line 603 def unlink(opts={}) # $tm.hrm opts = {'recurse'=>true}.merge(opts) # remove from parent if @parent and opts['recurse'] if @parent.is_a?(Trivet::Document) @parent.set_root nil, 'recurse'=>false elsif @parent.is_a?(Trivet::Node) @parent.remove_object self else raise 'unlink-unknown-parent-class: ' + @parent.class.to_ end end # set parent to nil @parent = nil end
Moves the child nodes into the node's parent and deletes self.
# File lib/trivet.rb, line 972 def unwrap() # $tm.hrm pchildren = @parent.children # move children @children.to_a.each do |child| pchildren.insert self.index, child end # unlink self unlink() end