diff --git a/dub.json b/dub.json
index 38ed091..62e7762 100644
--- a/dub.json
+++ b/dub.json
@@ -4,6 +4,7 @@
],
"copyright": "Copyright © 2024, John Pearcey",
"dependencies": {
+ "console-colors": "~>1.3.1",
"d2sqlite3": "~>1.0.0"
},
"description": "Persist Html/Dom to file with element indexing",
diff --git a/dub.selections.json b/dub.selections.json
index 85d02a1..bc63a03 100644
--- a/dub.selections.json
+++ b/dub.selections.json
@@ -1,6 +1,7 @@
{
"fileVersion": 1,
"versions": {
+ "console-colors": "1.3.1",
"d2sqlite3": "1.0.0"
}
}
diff --git a/source/attributeHandler.d b/source/attributeHandler.d
index 4b5bf41..9bb88ff 100644
--- a/source/attributeHandler.d
+++ b/source/attributeHandler.d
@@ -119,6 +119,8 @@
unittest{
+ writeln( "Testing attribute parsing" );
+
string strAtts = " color=\"red\" font='big font' nowrap v-align='top' border=\"\" ";
AttribParser atts = new AttribParser( strAtts );
diff --git a/source/dom_persist.d b/source/dom_persist.d
index 069f44f..ead40bf 100644
--- a/source/dom_persist.d
+++ b/source/dom_persist.d
@@ -122,6 +122,7 @@
long nnid = 0;
TreeNode[long] all_nodes; //https://dlang.org/spec/hash-map.html
+ TreeNode[long] nodes_to_delete;
long getNextNodeId(){
nnid -= 1;
@@ -214,7 +215,7 @@
* Returns the root node of this tree. This is special node holds the tree name and ID and is not
* usually part of the document but rather the parent container.
*/
- TreeNode getTreeNode(){
+ TreeNode getTreeRoot(){
return all_nodes[tree_id];
}
@@ -300,15 +301,30 @@
nodeToMove.node_data.dirty = true; //dirty data will suffice for storing pid too
}
-/*
- Delete node (branch)
- If the id<=-1, then it was a new node, unsaved, can be removed entirely
- Otherwise, move the node and all children into a delete-map.
- Mark the TreeNode parent as dirty indicating that children need removing. Re-ordering is
- not required but may be advantageous
- flush: Delete entries using the delete-map and clear the map.
-
- */
+ void deleteNode( TreeNode nodeToDelete ){
+ /* Algorithm:
+ If the id<=-1, then it was a new node, unsaved, can be removed entirely
+ Otherwise, move the node and all children into a delete-map.
+ Mark the TreeNode parent as dirty indicating that children need removing. Re-ordering is
+ not required but may be advantageous
+ flush: Delete entries using the delete-map and clear the delete-map.
+ */
+
+ nodeToDelete.parentNode().cutChild( nodeToDelete );
+ if(nodeToDelete.node_data.ID>0){
+ //a persisted node, move to a delete map
+ nodes_to_delete[ nodeToDelete.node_data.ID ] = nodeToDelete;
+ }
+
+ //remove the node (and all its descendents) from the all_nodes map
+ DocOrderIterator it = new DocOrderIterator( nodeToDelete );
+ TreeNode tnode;
+ while( (tnode=it.nextNode) !is null ){
+ long key = tnode.node_data.ID;
+ all_nodes.remove(key);
+ }
+
+ }
/**
@@ -355,6 +371,39 @@
}
}
+ //collect together all nodes and their children specified in the nodes_to_delete map which have a database ID
+ long[] del_all;
+ foreach( key; nodes_to_delete.keys() ){
+
+ if(key>0) del_all ~= key;
+
+ TreeNode nodeToDelete = nodes_to_delete[key];
+
+ DocOrderIterator it2 = new DocOrderIterator( nodeToDelete );
+ TreeNode tnode2;
+ while( (tnode2=it2.nextNode) !is null ){
+ if(tnode2.node_data.ID>0) del_all ~= tnode2.node_data.ID;
+ }
+ }
+
+ { //now delete all children from the DB (all have positive keys)
+ int i=0;
+ string sql = "delete from doctree";
+ foreach( key; del_all ){
+ if(i==0){
+ sql ~= " where";
+ }else{
+ sql ~= " and";
+ }
+ sql ~= " id=" ~ to!(string)(key);
+ i += 1;
+ }
+ if(i>0){
+ db.run( sql );
+ }
+ }
+ nodes_to_delete.clear();
+
//Re-enumerate any children that have shifted positions (or are new) according to their array positions
it.reset();
while( (tnode=it.nextNode) !is null ){
diff --git a/source/dom_persist_tests.d b/source/dom_persist_tests.d
index c3909ee..ffaf9fb 100644
--- a/source/dom_persist_tests.d
+++ b/source/dom_persist_tests.d
@@ -4,6 +4,7 @@
import dom_persist;
import nodecode;
+import consolecolors; //https://code.dlang.org/packages/console-colors/1.1.2
import d2sqlite3; // https://d2sqlite3.dpldocs.info/v1.0.0/d2sqlite3.database.Database.this.html
// https://dlang-community.github.io/d2sqlite3/d2sqlite3.html
@@ -69,7 +70,7 @@
auto db = Database( sqlite_filename );
Tree_Db tree = Tree_Db.createTree( db, "mytree" );
- TreeNode tree_node = tree.getTreeNode();
+ TreeNode tree_node = tree.getTreeRoot();
NodeData nd = tree_node.node_data;
assert( nd.pid == 0);
@@ -124,7 +125,7 @@
TreeNameID[] tree_list = Tree_Db.getTreeList( db );
Tree_Db tree = Tree_Db.loadTree( db, tree_list[0].tree_id );
- TreeNode tree_node = tree.getTreeNode();
+ TreeNode tree_node = tree.getTreeRoot();
DocOrderIterator it = new DocOrderIterator( tree_node );
int i=0;
@@ -171,7 +172,7 @@
Tree_Db tree = Tree_Db.loadTree( db, tree_list[0].tree_id );
- TreeNode tree_node = tree.getTreeNode();
+ TreeNode tree_node = tree.getTreeRoot();
NodeData nd_t = tree_node.node_data;
assert( nd_t.ID == tree_list[0].tree_id );
assert( nd_t.e_data == tree_list[0].name );
@@ -257,11 +258,45 @@
auto result = db.execute( "select id, e_data, p_id, t_id from doctree where id=5" );
assertNDRecord( result.front(), 5, "An edit took place", tn_body.node_data.ID, TreeNodeType.comment );
- writeln("TODO: Test multiple moveNode");
+ cwriteln("TODO: Test multiple moveNode");
}
-
unittest{
+
+ writeln( "Testing tree element deletion" );
+
+
+ auto db = Database( sqlite_filename );
+
+ TreeNameID[] tree_list = Tree_Db.getTreeList( db );
+ assert( tree_list.length==2 );
+
+ Tree_Db tree = Tree_Db.loadTree( db, tree_list[0].tree_id );
+ string html_out = tree.getTreeAsText( );
+ //writeln( html_out );
+ assert( html_out == "This is some text with more text");
+
+ TreeNode tn_head = tree.getTreeRoot().getChildAt(1).getChildAt(0);
+ tn_head.getChildAt(0).deleteNode(); //delete the script node
+
+ html_out = tree.getTreeAsText( );
+ assert( html_out == "This is some text with more text");
+
+ //check that the database record still exists
+ auto result = db.execute( "select id, e_data, p_id, t_id from doctree where id=11" );
+ assertNDRecord( result.front(), 11, "script", tn_head.node_data.ID, TreeNodeType.element );
+
+ tree.flush();
+
+ //reload and check
+ tree = Tree_Db.loadTree( db, tree_list[0].tree_id );
+ html_out = tree.getTreeAsText( );
+ assert( html_out == "This is some text with more text");
+
+
+}
+
+/*unittest{
int[] a1;
@@ -277,5 +312,4 @@
removeAt!int( a1, 0 );
}
}
-
-}
+}*/
diff --git a/source/nodecode.d b/source/nodecode.d
index d6f2a39..022cdb1 100644
--- a/source/nodecode.d
+++ b/source/nodecode.d
@@ -106,6 +106,11 @@
return child_nodes[0];
}
+ TreeNode getChildAt( int pos ){
+ if( child_nodes.length<=pos ) return null;
+ return child_nodes[pos];
+ }
+
/**
* Set the data for this node.
*
@@ -149,10 +154,21 @@
owner_tree.moveNode( this, new_p_node, pos );
}
+ /**
+ * Delete this node from the the tree.
+ */
+ void deleteNode( ){
+ owner_tree.deleteNode( this );
+ }
+
+ /**
+ * Internal use only
+ */
void cutChild( TreeNode c_node ){
foreach( i, child; child_nodes ){
if(child == c_node ){
removeAt!TreeNode( child_nodes, cast(int)(i) );
+ c_node.node_data.pid = 0; //it has no parent now
dirty = true;
return;
}