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

children[R]

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.

id[R]

Returns the id of the node, or nil if it does not have an id.

misc[R]

A hash of any miscellaneous information you want to attach to the node.

parent[R]

Returns the parent object of the node, or nil if there is no parent.

Public Class Methods

new(pod=nil) click to toggle source

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
no_children() click to toggle source

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

add(*objs) click to toggle source

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
allow_child?(child) click to toggle source

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
ancestors() click to toggle source

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
child_class(*opts) click to toggle source

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
depth() click to toggle source

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
document() click to toggle source

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
heritage() click to toggle source

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
id=(new_id) click to toggle source

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
index() click to toggle source

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
match?(qobj) click to toggle source

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
move_child(child, tgt) click to toggle source

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
next_sibling() click to toggle source

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
node(opts={}) { |new_node| ... } click to toggle source

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
node_by_id(qid) click to toggle source

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
parent=(new_parent) click to toggle source

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
previous_sibling() click to toggle source

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
query(qobj, opts={}) { |node| ... } click to toggle source

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
root() click to toggle source

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
set_parent(new_parent, opts={}) click to toggle source

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
tab() click to toggle source
# File lib/trivet.rb, line 884
def tab
        return '   '
end
to_debug() click to toggle source

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
to_s() click to toggle source

Returns the id if there is one. Otherwise returns Object#to_s.

Calls superclass method
# File lib/trivet.rb, line 814
def to_s
        return @id || super()
end
to_tree(opts={}) click to toggle source

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
trace(new_node) click to toggle source

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
traverse(opts={}) { |self, ctrl| ... } click to toggle source

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
unwrap() click to toggle source

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