Newer
Older
dom-persist / source / nodecode.d
module nodecode;

import std.traits;
import std.format;

import dom_persist;


enum TreeNodeType {
	nulltype=-2, // indicates a type read from the database which is not one of the recognised types
	tree=-1, docType, element, text, comment
}

TreeNodeType getTreeNodeType( int tip ){
	
	auto tnts = [EnumMembers!TreeNodeType];
	foreach( tnt; tnts ){
		if( tnt==tip ) return tnt;
	}
	return TreeNodeType.nulltype;
}


struct NodeData {
	
	long ID;
	string e_data;
	long pid;
	TreeNodeType type;
	bool dirty = false;
	long orig_pid=0;
	
	this( long ID, string e_data, long pid, TreeNodeType type){
		this.ID = ID;
		this.e_data = e_data;
		this.pid = pid;
		this.type = type;
	}
}

/**
 * TreeNode is a class because we need the references to remain valid when we modify containers such as
 * the hashmap which indexes on ID.
 * 
 * If we use a struct, then we need to hold the TreeNode data somewhere in order to use a pointer to the data. If
 * we move the data, such as updating a map, then all the pointers need to change, or we need pointers to pointers.
 * Either is not ideal. If we use references, as provided by a class, then the GC will track the references and ensure
 * that they remain valid. We can move the references knowing that the data remains in place on the heap
 * 
 */
class TreeNode {
	
	private:
	
		Tree_Db owner_tree;
	
	// end private
	
	public:
	
		NodeData 		node_data;
		TreeNode[] 		child_nodes;
		bool				dirty;	// true indicates a change in child_nodes ordering

		this( Tree_Db owner_tree, NodeData node_data){
			this.owner_tree = owner_tree;
			this.node_data = node_data;
		}
		
		/**
		 * Returns the parent node of this node or null if no parent exists (i.e. tree-root)
		 */
		TreeNode parentNode(){
			if(node_data.pid==0) return null;
			return owner_tree.getTreeNodeById( node_data.pid );
		}
		
		/**
		 * Returns the next sibling of this node or null if none exists
		 */
		TreeNode nextSibling(){
			if(node_data.pid==0) return null;
			
			//bDebug_out = true;
			TreeNode p_node = owner_tree.getTreeNodeById( node_data.pid );
			if(p_node is null){
				debug_out("(ID,e_data) = ", node_data.ID, node_data.e_data );
				throw new Exception("oops");
			}
			
			foreach( i, c_node; p_node.child_nodes){
				if(c_node==this){
					if( i == p_node.child_nodes.length-1) return null;
					return p_node.child_nodes[i+1];
				}
			}
			throw new Exception("Damaged tree, possibly incorrect parent id for a child.");
		}

		bool hasChildNodes(){
			return child_nodes.length>0;
		}

		TreeNode firstChild(){
			if( child_nodes.length==0 ) return null;
			return child_nodes[0];
		}

		/**
		 * Set the data for this node.
		 * 
		 * The data is interpreted using the type of node. For example, the text content of a text node is
		 * the data whereas for element types, the data is used to hold the element name.
		 */
		void setData( string nData ){
			node_data.e_data = nData;
			node_data.dirty = true;
		}
		
		/**
		 * Set the node ID of this treenode. Also sets the parent IDs of all child nodes.
		 */
		long setNodeId( long nnid ){
			
			long oldid = node_data.ID;
			
			node_data.ID = nnid;
			foreach(child; child_nodes ){
				child.node_data.pid = nnid;
			}
			return nnid;
		}

		/**
		 * Insert a new child node at the position indicated.
		 */
		TreeNode insertChild( TreeNodeType n_type, string e_data, int pos ){		
			return owner_tree.insertChild( this, n_type, e_data, pos );		
		}

		/**
		 * Append a new child node.
		 */
		TreeNode appendChild( TreeNodeType n_type, string e_data ){
			return owner_tree.insertChild( this, n_type, e_data, cast(int)(child_nodes.length) );
		}
		
		void moveNode( TreeNode new_p_node, int pos ){
			owner_tree.moveNode( this, new_p_node, pos );
		}
	
		void cutChild( TreeNode c_node ){
			foreach( i, child; child_nodes ){
				if(child == c_node ){
					removeAt!TreeNode( child_nodes, cast(int)(i) );
					dirty = true;
					return;
				}
			}
			throw new Exception( format("Node %d is not a child of node %d ", c_node.node_data.ID, node_data.ID) );
		}
				
	// end public
	
}