diff --git a/detach-link-notes.md b/detach-link-notes.md new file mode 100644 index 0000000..8d0fb58 --- /dev/null +++ b/detach-link-notes.md @@ -0,0 +1,68 @@ +* D E F +* A B C + +`E.insertAfter(B) ->` + +* D F +* A B E C + +* B + * _next_ = C + * prev = A + +* C + * next = null + * _prev_ = B + +* E + * _next_ = F + * _prev_ = D + * _parent_ = ??? + +* D + * _next_ = E + * _prev_ = F + +* F + * next = null + * _prev_ = E + +italic properties must change. + +```D +// first, fix old nodes. +if(E == E.parent.firstChild) // nope + // E.prev must be null if this is true, short circuit logic? + E.parent.firstChild = E.next; +if(E == E.parent.lastChild) // nope + // E.next must be null if this is true, short circuit logic? + E.parent.lastChild = E.prev; +if(E.next) // F + E.next.prev = E.prev; +if(E.prev) // D + E.prev.next = E.next; +// insertBefore is an exact mirror of this beyond this point, and identical before. +// the links on E are free to scram now +E.prev = B; +E.next = B.next; + +// now, set links to E + +if(B.next) // C + B.next.prev = E; +B.next = E; +``` + +in addition to setting, +Any time you set next, you have to check old.next.prev, and old.parent.lastChild +- insertAfter +- also insertBefore for setting dest.next +Any time you set prev, you have to check old.prev.next, and old.parent.firstChild +- insertBefore +- also insertAfter, for setting dest.prev +Any time you set parent, you have to check old.parent.firstChild and old.parent.lastChild +- ??? insertChild? probably never do this. below will both be needed +Any time you set firstChild you have to check old.parent.firstChild (set it to new.next, set it.prev = new) +- appendChild +Any time you set lastChild, you have to check old.parent.lastChild +- prependChild diff --git a/src/html/dom.d b/src/html/dom.d index 068c00a..c97c302 100644 --- a/src/html/dom.d +++ b/src/html/dom.d @@ -12,20 +12,10 @@ import html.parser; import html.alloc; import html.utils; - alias HTMLString = const(char)[]; -enum DOMCreateOptions { - None = 0, - DecodeEntities = 1 << 0, - - Default = DecodeEntities, -} - - enum OnlyElements = "(a) => { return a.isElementNode; }"; - package NodeWrapper!T wrap(T)(T* node) { return NodeWrapper!T(node); } @@ -224,7 +214,6 @@ enum NodeTypes : ubyte { ProcessingInstruction, } - struct Node { package this(Document* document, HTMLString tag) { tag_ = tag; @@ -286,15 +275,12 @@ struct Node { attrs_.remove(name); } - @property void html(size_t options = DOMCreateOptions.Default)(HTMLString html) { + @property void html(HTMLString html) { assert(isElementNode, "cannot add html to non-element nodes"); - enum parserOptions = ((options & DOMCreateOptions.DecodeEntities) ? ParserOptions.DecodeEntities : 0); - destroyChildren(); - - auto builder = DOMBuilder!(Document)(*document_, &this); - parseHTML!(typeof(builder), parserOptions)(html, builder); + auto builder = DOMBuilder!(Document)(document_, &this); + parseHTML!(typeof(builder))(html, builder); } @property auto html() const { @@ -309,44 +295,6 @@ struct Node { return app.data; } - void prependChild(Node* node) { - assert(document_ == node.document_); - assert(isElementNode, "cannot prepend to non-element nodes"); - - if (node.parent_) - node.detach(); - node.parent_ = &this; - if (firstChild_) { - assert(!firstChild_.prev_); - firstChild_.prev_ = node; - node.next_ = firstChild_; - firstChild_ = node; - } else { - assert(!lastChild_); - firstChild_ = node; - lastChild_ = node; - } - } - - void appendChild(Node* node) { - assert(document_ == node.document_); - assert(isElementNode, "cannot append to non-element nodes"); - - if (node.parent_) - node.detach(); - node.parent_ = &this; - if (lastChild_) { - assert(!lastChild_.next_); - lastChild_.next_ = node; - node.prev_ = lastChild_; - lastChild_ = node; - } else { - assert(!firstChild_); - firstChild_ = node; - lastChild_ = node; - } - } - void removeChild(Node* node) { assert(node.parent_ == &this); node.detach(); @@ -364,116 +312,201 @@ struct Node { lastChild_ = null; } - void prependText(HTMLString text) { - auto node = document_.createTextNode(text); - if (firstChild_) { - assert(firstChild_.prev_ == null); - firstChild_.prev_ = node; - node.next_ = firstChild_; - firstChild_ = node; - } else { - assert(lastChild_ == null); - firstChild_ = node; - lastChild_ = node; - } - node.parent_ = &this; - } - - void appendText(HTMLString text) { - auto node = document_.createTextNode(text); - if (lastChild_) { - assert(lastChild_.next_ == null); - lastChild_.next_ = node; - node.prev_ = lastChild_; - lastChild_ = node; - } else { - assert(firstChild_ == null); - firstChild_ = node; - lastChild_ = node; - } - node.parent_ = &this; - } - - void insertBefore(Node* node) { - assert(document_ == node.document_); - - parent_ = node.parent_; - prev_ = node.prev_; - next_ = node; - node.prev_ = &this; - - if (parent_ && (parent_.firstChild_ == node)) - parent_.firstChild_ = &this; - } - - void insertAfter(Node* node) { - assert(document_ == node.document_); - - parent_ = node.parent_; - prev_ = node; - next_ = node.next_; - node.next_ = &this; - } - - void detach() { + void prependChild(Node* node) { + assert(document_ == node.document_); + assert(isElementNode, "cannot prepend to non-element nodes"); + node.detachFast(); + node.parent_ = &this; + if (firstChild_) { + assert(!firstChild_.prev_); + if(document_.mergeTextNodes && + firstChild_.isTextNode && node.isTextNode) { + firstChild_.tag_ = node.tag_ ~ firstChild_.tag_; + node.document_.destroyNode(node); + return; + } + firstChild_.prev_ = node; + node.next_ = firstChild_; + node.prev_ = null; + firstChild_ = node; + } else { + assert(!lastChild_); + node.prev_ = node.next_ = null; + firstChild_ = node; + lastChild_ = node; + } + } + + void appendChild(Node* node) { + assert(document_ == node.document_); + assert(isElementNode, "cannot append to non-element nodes"); + node.detachFast(); + + node.parent_ = &this; + if (lastChild_) { + assert(!lastChild_.next_); + if(document_.mergeTextNodes && + lastChild_.isTextNode && node.isTextNode) { + lastChild_.tag_ = lastChild_.tag_ ~ node.tag_; + node.document_.destroyNode(node); + return; + } + + lastChild_.next_ = node; + node.prev_ = lastChild_; + node.next_ = null; + lastChild_ = node; + } else { + assert(!firstChild_); + node.prev_ = node.next_ = null; + firstChild_ = node; + lastChild_ = node; + } + } + + void prependText(HTMLString text) { + if(isElementNode) { + prependChild(document_.createTextNode(text)); + } else { + tag_ = text ~ tag; + } + } + + void appendText(HTMLString text) { + if(isElementNode) { + appendChild(document_.createTextNode(text)); + } else { + tag_ = tag ~ text; + } + } + + void insertBefore(Node* node) { + assert(document_ == node.document_); + assert(node); + detachFast(); + if(document_.mergeTextNodes && + isTextNode && node.isTextNode) { + node.tag_ = tag_ ~ node.tag_; + document_.destroyNode(&this); + return; + } + + parent_ = node.parent_; + prev_ = node.prev_; + next_ = node; + node.prev_ = &this; + + if (parent_ && (parent_.firstChild_ == node)) { + assert(!prev_); + parent_.firstChild_ = &this; + } else if(prev_) { + prev_.next_ = &this; + } + } + + void insertAfter(Node* node) { + assert(node); + assert(node.parent_); + // can't insert into a parentless node + // since we're assuming in detachFast that !parent implies !prev + assert(document_ == node.document_); + detachFast(); + if(document_.mergeTextNodes && + isTextNode && node.isTextNode) { + node.tag_ = node.tag_ ~ tag_; + document_.destroyNode(&this); + return; + } + parent_ = node.parent_; + next_ = node.next_; + prev_ = node; + node.next_ = &this; + + if(parent_ && (parent_.lastChild_ == node)) { + assert(!next_); + parent_.lastChild_ = &this; + } else if(next_) { + next_.prev_ = &this; + } + } + + /* !careful => detach everything else, but leave our pointers pointing + old places. + careful => set our pointers to null + */ + private void packageDetach(bool careful)() { if (parent_) { if (parent_.firstChild_ == &this) { + assert(prev_ is null); parent_.firstChild_ = next_; if (next_) { next_.prev_ = null; - next_ = null; + static if(careful) { + next_ = null; + } } else { parent_.lastChild_ = null; } - assert(prev_ == null); + static if(careful) { + assert(prev_ == null); + } } else if (parent_.lastChild_ == &this) { + assert(next_ is null); parent_.lastChild_ = prev_; - assert(prev_); - assert(!next_); - prev_.next_ = null; - prev_ = null; - } else { - assert(prev_); - - prev_.next_ = next_; - if (next_) { - next_.prev_ = prev_; - next_ = null; - } - prev_ = null; - } - parent_ = null; - } - } - - package void detachFast() { - if (parent_) { - if (parent_.firstChild_ == &this) { - parent_.firstChild_ = next_; - if (next_) { - next_.prev_ = null; + if (prev_) { + prev_.next_ = null; + static if(careful) { + prev_ = null; + } } else { - parent_.lastChild_ = null; + parent_.firstChild_ = null; } - - assert(prev_ == null); - } else if (parent_.lastChild_ == &this) { - parent_.lastChild_ = prev_; - assert(prev_); - assert(!next_); - prev_.next_ = null; } else { - assert(prev_); + // somewhere in the middle + assert(prev_,"prev_ should be non-null"); + prev_.next_ = next_; - prev_.next_ = next_; - if (next_) { - next_.prev_ = prev_; - } + assert(next_,"next_ should be non-null"); + next_.prev_ = prev_; + + static if(careful) { + prev_ = next_ = null; + } + + } + static if(careful) { + parent_ = null; + } + } else { + // when not being careful, let's just assume a parentless node + // was just created, so shall have no next or prev to check. + static if(careful) { + if(prev_) { + prev_.next_ = next_; } + if(next_) { + next_.prev_ = prev_; + } + static if(careful) { + next_ = prev_ = null; + } + } else { + assert(prev_ is null); + assert(next_ is null); + } } + static if(careful) { + // just making sure no non-nulls slip through when we're being + // careful. + assert(prev_ is null); + assert(next_ is null); + } } + public alias detach = packageDetach!true; + package alias detachFast = packageDetach!false; + void destroy() { detachFast(); destroyChildren(); @@ -500,7 +533,7 @@ struct Node { if (value.length) { app.put("=\""); - writeHTMLEscaped(app, value); + app.put(value); app.put("\""); } } @@ -524,7 +557,7 @@ struct Node { } break; case Text: - writeHTMLEscaped(app, tag_); + app.put(tag_); break; case Comment: app.put("