module nodecode; import std.file; import std.array; //https://dlang.org/phobos/std_array.html import std.algorithm; //https://dlang.org/phobos/std_algorithm_mutation.html import std.conv; import std.stdio; import std.format; import std.exception : enforce; import std.traits; 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 }