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; }