From 12c215b1c81a57673f50bfba141d3c3539c88cea Mon Sep 17 00:00:00 2001 From: user Date: Sat, 6 Feb 2016 09:06:27 +0000 Subject: [PATCH 01/15] Node to node cloning document.clone() was horribly annoying when I tried to use it, when I just wanted to clone one node based on another, or just clone a node itself. An even more sophisticated thing would automatically clone nodes that are passed to appendChild, rather than erroring out if they have a different document. --- src/html/dom.d | 81 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/src/html/dom.d b/src/html/dom.d index d6c69d1..88bb5fa 100644 --- a/src/html/dom.d +++ b/src/html/dom.d @@ -791,6 +791,14 @@ struct Node { return c; } + Node* clone(Node* oldnode) { + return document_.clone(oldnode); + } + + Node* clone() { + return document_.clone(&this); + } + package: enum TypeMask = 0x7; enum TypeShift = 0; @@ -1055,6 +1063,39 @@ private: /// unittest { + import core.runtime: Runtime, ModuleInfo; + import std.stdio; + Runtime.moduleUnitTester = function() { + foreach( m; ModuleInfo ) + { + if( m ) + { + auto fp = m.unitTest; + + if( fp ) + { + try + { + fp(); + } catch( Throwable e ) + { + writeln("Dyin",typeof(e) + return false; + } + } + } + } + return true; + } + + void assertEqual(A,B)(A actual, B expected) { + if(a != b) { + writeln("Expected:"); + writeln(expected); + writeln("We got:"); + writeln(actual); + throw new AssertionError("fail"); + //import htmld: createDocument; const(char)[] s = `andsometext`; auto doc = createDocument(s); @@ -1065,8 +1106,22 @@ unittest { auto other = createDocument(); c = other.clone(doc.root().children.front); assert(s == c.outerHTML); + + import std.regex: regex, replaceAll; + auto noformat = regex(`\s*\n\s*`); // can't kill spaces between attrs + void clean(auto s) { + s = s.replaceAll(noformat,""); + } + + c.tag = "kiddo"; - s = `andsometextandsometext`; + s = ` + andsometext + + andsometext + +`; + clean(); c.appendChild(other.clone(c)); assert(s == c.outerHTML); @@ -1074,6 +1129,30 @@ unittest { other.root().appendChild(c); assert(s == other.root().outerHTML()); + + Node* a = doc.root().firstChild; + Node* b = other.root().firstChild; + b.appendChild(b.clone()); + a.appendChild(a.clone(b)); + + s = ` + + andsometext + + andsometext + + andsometext + andsometext + + andsometext + + + + +`; + writeln(s); + writeln(doc.root().outerHTML()); + assert(doc.root().outerHTML() == s); } From ca841eb0c19bdf437130d137085e10b6ca9cb611 Mon Sep 17 00:00:00 2001 From: user Date: Sat, 6 Feb 2016 09:17:41 +0000 Subject: [PATCH 02/15] couldn't read the unittest errors Parsing two pages of text for a single line number wasn't fun, so I just went and made my own unit tester by ripping off dunit, since I didn't know if I should add dunit as a dependency to this package. --- src/html/dom.d | 86 +++++++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/src/html/dom.d b/src/html/dom.d index 88bb5fa..1d92d9c 100644 --- a/src/html/dom.d +++ b/src/html/dom.d @@ -1061,41 +1061,49 @@ private: PageAllocator!(Node, 1024) alloc_; } -/// -unittest { - import core.runtime: Runtime, ModuleInfo; - import std.stdio; - Runtime.moduleUnitTester = function() { - foreach( m; ModuleInfo ) - { - if( m ) - { - auto fp = m.unitTest; - - if( fp ) - { - try - { - fp(); - } catch( Throwable e ) +version(unittest) { + shared static this() { + import core.runtime: Runtime, ModuleInfo; + import std.stdio; + Runtime.moduleUnitTester = function() { + foreach( m; ModuleInfo ) + { + if( m ) + { + auto fp = m.unitTest; + + if( fp ) { - writeln("Dyin",typeof(e) - return false; + try + { + fp(); + } catch( Throwable e ) + { + writeln(e.msg," at ",e.file,":",e.line); + return false; + } } } } - } - return true; + return true; + }; } +} + +/// +unittest { + import std.stdio; void assertEqual(A,B)(A actual, B expected) { - if(a != b) { + if(actual != expected) { writeln("Expected:"); writeln(expected); writeln("We got:"); writeln(actual); - throw new AssertionError("fail"); - + throw new Exception("fail"); + } + } + //import htmld: createDocument; const(char)[] s = `andsometext`; auto doc = createDocument(s); @@ -1109,33 +1117,35 @@ unittest { import std.regex: regex, replaceAll; auto noformat = regex(`\s*\n\s*`); // can't kill spaces between attrs - void clean(auto s) { - s = s.replaceAll(noformat,""); + typeof(s) clean(typeof(s) s) { + return s.replaceAll(noformat,""); } - c.tag = "kiddo"; - - s = ` + c.attr("shoop", "woop"); + + s = clean( + ` andsometext andsometext -`; - clean(); +`); c.appendChild(other.clone(c)); - assert(s == c.outerHTML); + assertEqual(c.outerHTML,s); - s = ""~s~""; other.root().appendChild(c); - assert(s == other.root().outerHTML()); + assertEqual(other.root().outerHTML(), + ""~s~""); Node* a = doc.root().firstChild; Node* b = other.root().firstChild; b.appendChild(b.clone()); a.appendChild(a.clone(b)); - s = ` + assertEqual( + doc.root.outerHTML, + clean(` andsometext @@ -1149,10 +1159,8 @@ unittest { -`; - writeln(s); - writeln(doc.root().outerHTML()); - assert(doc.root().outerHTML() == s); +`)); + } From 8c84c9e24776a7e5065ca819e460b5968c2cb686 Mon Sep 17 00:00:00 2001 From: user Date: Sat, 6 Feb 2016 09:29:22 +0000 Subject: [PATCH 03/15] Replacing assert/== with assertEqual There's a unit testing framework that takes the AST of the source, and rewrites "assert(A==B)" into "assertEqual(A,B)". This is not that unit testing framework. I am not going to do that. --- src/html/dom.d | 61 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/src/html/dom.d b/src/html/dom.d index 1d92d9c..42973cd 100644 --- a/src/html/dom.d +++ b/src/html/dom.d @@ -1062,9 +1062,9 @@ private: } version(unittest) { + import std.stdio; shared static this() { import core.runtime: Runtime, ModuleInfo; - import std.stdio; Runtime.moduleUnitTester = function() { foreach( m; ModuleInfo ) { @@ -1088,32 +1088,42 @@ version(unittest) { return true; }; } + + template assertEqual(A,B) { + void assertEqual(A actual, B expected, + string file = __FILE__, + size_t line = __LINE__) { + if(actual != expected) { + writeln("Expected:"); + writeln(expected); + writeln("We got:"); + writeln(actual); + throw new Exception("fail",file,line); + } + } + } + + } + + /// unittest { import std.stdio; - void assertEqual(A,B)(A actual, B expected) { - if(actual != expected) { - writeln("Expected:"); - writeln(expected); - writeln("We got:"); - writeln(actual); - throw new Exception("fail"); - } - } - //import htmld: createDocument; const(char)[] s = `andsometext`; auto doc = createDocument(s); s = doc.root().html(); // normalize - auto c = doc.clone(doc.root()); - assert(s == c.html); - assert(s == doc.root().html()); - auto other = createDocument(); - c = other.clone(doc.root().children.front); - assert(s == c.outerHTML); + auto me = doc.clone(doc.root()); + assertEqual(me.html,s); + assertEqual(doc.root().html(),s); + + auto other = createDocument(); + auto them = other.clone(doc.root()); + + assertEqual(them.html,s); import std.regex: regex, replaceAll; auto noformat = regex(`\s*\n\s*`); // can't kill spaces between attrs @@ -1121,19 +1131,22 @@ unittest { return s.replaceAll(noformat,""); } - c.attr("shoop", "woop"); + me = me.children.front; + + me.attr("shoop", "woop"); + them.appendChild(other.clone(me)); s = clean( - ` + ` andsometext - + andsometext - + `); - c.appendChild(other.clone(c)); - assertEqual(c.outerHTML,s); + assertEqual(them.outerHTML,s); - other.root().appendChild(c); + other.root().appendChild(other.clone(me)); + me.attr("still","here"); assertEqual(other.root().outerHTML(), ""~s~""); From 3482184d187c3371545957ca46e00709d6ceb913 Mon Sep 17 00:00:00 2001 From: user Date: Sat, 6 Feb 2016 09:42:30 +0000 Subject: [PATCH 04/15] Fixing expected values Now that I can actually read, I can start fixing the values to be what they actually turn out to be. Also, there was an error on line 332 that probably should have had a stack trace, so I made my stupid unit tester use a special exception instead of blanket catching all Throwable. --- src/html/dom.d | 67 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/src/html/dom.d b/src/html/dom.d index 42973cd..55b810b 100644 --- a/src/html/dom.d +++ b/src/html/dom.d @@ -1062,7 +1062,13 @@ private: } version(unittest) { - import std.stdio; + import std.stdio: writeln; + import core.exception: AssertError; + class DUnitIsBetter: AssertError { + this(string msg, string file, size_t line) { + super(msg,file,line); + } + } shared static this() { import core.runtime: Runtime, ModuleInfo; Runtime.moduleUnitTester = function() { @@ -1077,7 +1083,7 @@ version(unittest) { try { fp(); - } catch( Throwable e ) + } catch( DUnitIsBetter e ) { writeln(e.msg," at ",e.file,":",e.line); return false; @@ -1098,7 +1104,7 @@ version(unittest) { writeln(expected); writeln("We got:"); writeln(actual); - throw new Exception("fail",file,line); + throw new DUnitIsBetter("fail",file,line); } } } @@ -1120,10 +1126,11 @@ unittest { assertEqual(me.html,s); assertEqual(doc.root().html(),s); - auto other = createDocument(); - auto them = other.clone(doc.root()); + auto other = createDocument(""); + auto them = other.root().children.front; + them.appendChild(other.clone(them)); - assertEqual(them.html,s); + assertEqual(them.outerHTML,""); import std.regex: regex, replaceAll; auto noformat = regex(`\s*\n\s*`); // can't kill spaces between attrs @@ -1137,42 +1144,48 @@ unittest { them.appendChild(other.clone(me)); s = clean( - ` + ` +andsometext + andsometext - + +`); + s = clean(` + + andsometext -`); +`); assertEqual(them.outerHTML,s); - other.root().appendChild(other.clone(me)); - me.attr("still","here"); - assertEqual(other.root().outerHTML(), ""~s~""); + other.root().appendChild(other.clone(me)); + me.attr("still","here"); + + assertEqual(other.root.outerHTML, + clean(` + + + + andsometext + + + + andsometext + +`)); Node* a = doc.root().firstChild; Node* b = other.root().firstChild; + b.attr("jutsu","henge"); b.appendChild(b.clone()); a.appendChild(a.clone(b)); assertEqual( doc.root.outerHTML, - clean(` - - andsometext - - andsometext - - andsometext - andsometext - - andsometext - - - - -`)); + clean(`andsometextandsometextandsometext +`)); } From 8a685d6ca37b28b82914f19dfa5ec801fd77397b Mon Sep 17 00:00:00 2001 From: user Date: Sat, 6 Feb 2016 09:45:36 +0000 Subject: [PATCH 05/15] fixing the last of the HTML and explaining my madness --- src/html/dom.d | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/html/dom.d b/src/html/dom.d index 55b810b..0efcd38 100644 --- a/src/html/dom.d +++ b/src/html/dom.d @@ -1062,6 +1062,9 @@ private: } version(unittest) { + /* just a quick and dirty unittest thing, so I can actually read the + errors that are going on without a huge useless stack trace + in my face */ import std.stdio: writeln; import core.exception: AssertError; class DUnitIsBetter: AssertError { @@ -1108,12 +1111,8 @@ version(unittest) { } } } - - } - - /// unittest { import std.stdio; @@ -1184,8 +1183,23 @@ unittest { assertEqual( doc.root.outerHTML, - clean(`andsometextandsometextandsometext -`)); + clean(` + + andsometext + + + + andsometext + + + + + andsometext + + + + +`)); } From 199f485f154e1632c57310615f408ab60e923e80 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 8 Feb 2016 05:13:55 +0000 Subject: [PATCH 06/15] Trivial to implement whole document cloning. --- src/html/dom.d | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/html/dom.d b/src/html/dom.d index 0efcd38..9327eb7 100644 --- a/src/html/dom.d +++ b/src/html/dom.d @@ -891,10 +891,17 @@ static auto createDocument() { struct Document { - auto clone(Node* source) { + Node* clone(Node* source) { return source.clone(&this, alloc_); } + Document clone() { + Document other = Document(); + other.init(); + other.root(other.clone(this.root_)); + return other; + } + auto createElement(HTMLString tagName, Node* parent = null) { auto node = alloc_.alloc(); *node = Node(&this, tagName); @@ -1201,10 +1208,11 @@ unittest { `)); + other = doc.clone(); + assertEqual(doc.toString(),other.toString()); + } - - struct DOMBuilder(Document) { this(ref Document document, Node* parent = null) { document_ = &document; From 25a28dbf596c0077159aa63851cf118fb2bcbe7c Mon Sep 17 00:00:00 2001 From: user Date: Wed, 10 Feb 2016 04:52:30 +0000 Subject: [PATCH 07/15] a basic "push" DOM builder It fires events as it builds the DOM, allowing to manipulate the end product, or examine pieces of it, without an extra pass over the document tree for every transformation. --- src/html/dom.d | 2 +- src/html/dombuilder.d | 82 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/html/dombuilder.d diff --git a/src/html/dom.d b/src/html/dom.d index 9327eb7..805fc9f 100644 --- a/src/html/dom.d +++ b/src/html/dom.d @@ -1337,7 +1337,7 @@ struct DOMBuilder(Document) { } } -private: +protected: Document* document_; Node* element_; States state_; diff --git a/src/html/dombuilder.d b/src/html/dombuilder.d new file mode 100644 index 0000000..56bd046 --- /dev/null +++ b/src/html/dombuilder.d @@ -0,0 +1,82 @@ +import dom: Document, Node, DOMBuilder; + +struct Builder; + +struct ElementReceiver { + Builder parent; + this(Builder parent) { + // for changing parent.receiver + this.parent = parent; + } + void onOpenEnd(Node* element) {} + void onClose(Node* element) {} + void onCloseText(HTMLString text) {} + void onSelfClosing(Node* element) { + this.onClose(element); + } + void onDocumentEnd() {} +} + +struct Builder: DOMBuilder!Document { + ElementReceiver receiver; + void onOpenEnd(HTMLString data) { + receiver.onOpenEnd(element_); + super.onOpenEnd(data); + } + void onClose(HTMLString data) { + super.onClose(data); + if(element_) { + receiver.onClose(element_); + } else { + receiver.onCloseText(text_); + } + } + void onSelfClosing() { + super.onSelfClosing(); + receiver.onSelfClosing(element_); + } + void onDocumentEnd() { + super.onDocumentEnd(); + receiver.onDocumentEnd(document_); + } +} + +unittest { + struct ImageCollector: Receiver { + Node*[] images; + Appender!Node*[] a; + this() { + a = Appender!Node*[](images); + } + void onClose(Node* e) { + if(e.tag == "img" && e.hasAttr("src")) { + a.put(e); + } + } + } + + enum parserOptions = ((DOMCreateOptions.Default & DOMCreateOptions.DecodeEntities) ? ParserOptions.DecodeEntities : 0); + + auto document = createDocument(); + Builder b = Builder(document); + ImageCollector c = ImageCollector(b); + b.receiver = c; + HTMLString source = ` + + + whatever + + + + +

+ +

+ + `; + parseHTML!(typeof(builder), parserOptions)(source, builder); + writeln(document.html); + for(img; c.images) { + writeln("image: ",img.getAttr("src")); + } +} From 9305ebda367e9bfe01199aa9e7732391fa8e4cd9 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 10 Feb 2016 04:58:42 +0000 Subject: [PATCH 08/15] Need to use classes for inheritance But that's a big difference from structs... --- src/html/dom.d | 2 +- src/html/dombuilder.d | 32 +++++++++-------- src/html/pushdom.d | 84 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 16 deletions(-) create mode 100644 src/html/pushdom.d diff --git a/src/html/dom.d b/src/html/dom.d index 805fc9f..eee3776 100644 --- a/src/html/dom.d +++ b/src/html/dom.d @@ -1213,7 +1213,7 @@ unittest { } -struct DOMBuilder(Document) { +class DOMBuilder(Document) { this(ref Document document, Node* parent = null) { document_ = &document; element_ = parent ? parent : document.root; diff --git a/src/html/dombuilder.d b/src/html/dombuilder.d index 56bd046..919a22f 100644 --- a/src/html/dombuilder.d +++ b/src/html/dombuilder.d @@ -1,8 +1,7 @@ -import dom: Document, Node, DOMBuilder; +module html.pushdom; +import html.dom: Document, Node, DOMBuilder, HTMLString; -struct Builder; - -struct ElementReceiver { +class NodeReceiver { Builder parent; this(Builder parent) { // for changing parent.receiver @@ -17,13 +16,13 @@ struct ElementReceiver { void onDocumentEnd() {} } -struct Builder: DOMBuilder!Document { - ElementReceiver receiver; - void onOpenEnd(HTMLString data) { +class Builder: DOMBuilder!Document { + NodeReceiver receiver; + override void onOpenEnd(HTMLString data) { receiver.onOpenEnd(element_); super.onOpenEnd(data); } - void onClose(HTMLString data) { + override void onClose(HTMLString data) { super.onClose(data); if(element_) { receiver.onClose(element_); @@ -31,24 +30,27 @@ struct Builder: DOMBuilder!Document { receiver.onCloseText(text_); } } - void onSelfClosing() { + override void onSelfClosing() { super.onSelfClosing(); receiver.onSelfClosing(element_); } - void onDocumentEnd() { + override void onDocumentEnd() { super.onDocumentEnd(); receiver.onDocumentEnd(document_); } } unittest { - struct ImageCollector: Receiver { + import dom: createDocument, DOMCreateOptions, ParserOptions; + import std.array: Appender; + + class ImageCollector: NodeReceiver { Node*[] images; - Appender!Node*[] a; + Appender!(Node*[]) a; this() { - a = Appender!Node*[](images); + a = Appender!(Node*[])(images); } - void onClose(Node* e) { + override void onClose(Node* e) { if(e.tag == "img" && e.hasAttr("src")) { a.put(e); } @@ -76,7 +78,7 @@ unittest { `; parseHTML!(typeof(builder), parserOptions)(source, builder); writeln(document.html); - for(img; c.images) { + foreach(img; c.images) { writeln("image: ",img.getAttr("src")); } } diff --git a/src/html/pushdom.d b/src/html/pushdom.d new file mode 100644 index 0000000..1b07efc --- /dev/null +++ b/src/html/pushdom.d @@ -0,0 +1,84 @@ +module html.pushdom; +import html.dom: Document, Node, DOMBuilder, HTMLString; + +class NodeReceiver { + Builder parent; + this(Builder parent) { + // for changing parent.receiver + this.parent = parent; + } + void onOpenEnd(Node* element) {} + void onClose(Node* element) {} + void onCloseText(HTMLString text) {} + void onSelfClosing(Node* element) { + this.onClose(element); + } + void onDocumentEnd() {} +} + +class Builder: DOMBuilder!Document { + NodeReceiver receiver; + void onOpenEnd(HTMLString data) { + receiver.onOpenEnd(element_); + super.onOpenEnd(data); + } + void onClose(HTMLString data) { + super.onClose(data); + if(element_) { + receiver.onClose(element_); + } else { + receiver.onCloseText(text_); + } + } + void onSelfClosing() { + super.onSelfClosing(); + receiver.onSelfClosing(element_); + } + void onDocumentEnd() { + super.onDocumentEnd(); + receiver.onDocumentEnd(document_); + } +} + +unittest { + import dom: createDocument, DOMCreateOptions, ParserOptions; + import std.array: Appender; + + class ImageCollector: NodeReceiver { + Node*[] images; + Appender!(Node*[]) a; + this() { + a = Appender!(Node*[])(images); + } + void onClose(Node* e) { + if(e.tag == "img" && e.hasAttr("src")) { + a.put(e); + } + } + } + + enum parserOptions = ((DOMCreateOptions.Default & DOMCreateOptions.DecodeEntities) ? ParserOptions.DecodeEntities : 0); + + auto document = createDocument(); + Builder b = Builder(document); + ImageCollector c = ImageCollector(b); + b.receiver = c; + HTMLString source = ` + + + whatever + + + + +

+ +

+ + `; + parseHTML!(typeof(builder), parserOptions)(source, builder); + writeln(document.html); + foreach(img; c.images) { + writeln("image: ",img.getAttr("src")); + } +} From e019f55522023762acbc547a6b1f91e48350781a Mon Sep 17 00:00:00 2001 From: user Date: Wed, 10 Feb 2016 05:00:03 +0000 Subject: [PATCH 09/15] rename to "pushdom" and add override required syntax --- src/html/dombuilder.d | 84 ------------------------------------------- src/html/pushdom.d | 13 ++++--- 2 files changed, 8 insertions(+), 89 deletions(-) delete mode 100644 src/html/dombuilder.d diff --git a/src/html/dombuilder.d b/src/html/dombuilder.d deleted file mode 100644 index 919a22f..0000000 --- a/src/html/dombuilder.d +++ /dev/null @@ -1,84 +0,0 @@ -module html.pushdom; -import html.dom: Document, Node, DOMBuilder, HTMLString; - -class NodeReceiver { - Builder parent; - this(Builder parent) { - // for changing parent.receiver - this.parent = parent; - } - void onOpenEnd(Node* element) {} - void onClose(Node* element) {} - void onCloseText(HTMLString text) {} - void onSelfClosing(Node* element) { - this.onClose(element); - } - void onDocumentEnd() {} -} - -class Builder: DOMBuilder!Document { - NodeReceiver receiver; - override void onOpenEnd(HTMLString data) { - receiver.onOpenEnd(element_); - super.onOpenEnd(data); - } - override void onClose(HTMLString data) { - super.onClose(data); - if(element_) { - receiver.onClose(element_); - } else { - receiver.onCloseText(text_); - } - } - override void onSelfClosing() { - super.onSelfClosing(); - receiver.onSelfClosing(element_); - } - override void onDocumentEnd() { - super.onDocumentEnd(); - receiver.onDocumentEnd(document_); - } -} - -unittest { - import dom: createDocument, DOMCreateOptions, ParserOptions; - import std.array: Appender; - - class ImageCollector: NodeReceiver { - Node*[] images; - Appender!(Node*[]) a; - this() { - a = Appender!(Node*[])(images); - } - override void onClose(Node* e) { - if(e.tag == "img" && e.hasAttr("src")) { - a.put(e); - } - } - } - - enum parserOptions = ((DOMCreateOptions.Default & DOMCreateOptions.DecodeEntities) ? ParserOptions.DecodeEntities : 0); - - auto document = createDocument(); - Builder b = Builder(document); - ImageCollector c = ImageCollector(b); - b.receiver = c; - HTMLString source = ` - - - whatever - - - - -

- -

- - `; - parseHTML!(typeof(builder), parserOptions)(source, builder); - writeln(document.html); - foreach(img; c.images) { - writeln("image: ",img.getAttr("src")); - } -} diff --git a/src/html/pushdom.d b/src/html/pushdom.d index 1b07efc..801f8bd 100644 --- a/src/html/pushdom.d +++ b/src/html/pushdom.d @@ -17,12 +17,15 @@ class NodeReceiver { } class Builder: DOMBuilder!Document { + this(ref Document document, Node* parent = null) { + super(document,parent); + } NodeReceiver receiver; - void onOpenEnd(HTMLString data) { + override void onOpenEnd(HTMLString data) { receiver.onOpenEnd(element_); super.onOpenEnd(data); } - void onClose(HTMLString data) { + override void onClose(HTMLString data) { super.onClose(data); if(element_) { receiver.onClose(element_); @@ -30,11 +33,11 @@ class Builder: DOMBuilder!Document { receiver.onCloseText(text_); } } - void onSelfClosing() { + override void onSelfClosing() { super.onSelfClosing(); receiver.onSelfClosing(element_); } - void onDocumentEnd() { + override void onDocumentEnd() { super.onDocumentEnd(); receiver.onDocumentEnd(document_); } @@ -50,7 +53,7 @@ unittest { this() { a = Appender!(Node*[])(images); } - void onClose(Node* e) { + override void onClose(Node* e) { if(e.tag == "img" && e.hasAttr("src")) { a.put(e); } From f19c965922cab44e3135fcb45c8253aebdaeab06 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 10 Feb 2016 05:11:27 +0000 Subject: [PATCH 10/15] no, classes suck I could do this with way less syntax and keep the DOMBuilder a struct. Just have to implement my own custom object inheritance system! No problem! --- src/html/dom.d | 4 ++-- src/html/pushdom.d | 49 +++++++++++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/html/dom.d b/src/html/dom.d index eee3776..b4a5dba 100644 --- a/src/html/dom.d +++ b/src/html/dom.d @@ -826,7 +826,7 @@ auto createDocument(size_t options = DOMCreateOptions.Default)(HTMLString source enum parserOptions = ((options & DOMCreateOptions.DecodeEntities) ? ParserOptions.DecodeEntities : 0); auto document = createDocument(); - auto builder = DOMBuilder!(Document)(document); + auto builder = new DOMBuilder!(Document)(document); parseHTML!(typeof(builder), parserOptions)(source, builder); return document; @@ -1213,7 +1213,7 @@ unittest { } -class DOMBuilder(Document) { +struct DOMBuilder(Document) { this(ref Document document, Node* parent = null) { document_ = &document; element_ = parent ? parent : document.root; diff --git a/src/html/pushdom.d b/src/html/pushdom.d index 801f8bd..18a7996 100644 --- a/src/html/pushdom.d +++ b/src/html/pushdom.d @@ -1,70 +1,78 @@ module html.pushdom; import html.dom: Document, Node, DOMBuilder, HTMLString; -class NodeReceiver { - Builder parent; - this(Builder parent) { +class NodeReceiver(Document) { + Builder!Document parent; + this(Builder!Document parent) { // for changing parent.receiver this.parent = parent; } + void swap(NodeReceiver!Document other) { + this.parent.receiver = other; + } void onOpenEnd(Node* element) {} void onClose(Node* element) {} void onCloseText(HTMLString text) {} void onSelfClosing(Node* element) { this.onClose(element); } - void onDocumentEnd() {} + void onDocumentEnd(Document* doc) {} } -class Builder: DOMBuilder!Document { +struct Builder(Document) { + DOMBuilder!Document souper; this(ref Document document, Node* parent = null) { - super(document,parent); + souper = DOMBuilder!Document(document,parent); } NodeReceiver receiver; override void onOpenEnd(HTMLString data) { receiver.onOpenEnd(element_); - super.onOpenEnd(data); + souper.onOpenEnd(data); } override void onClose(HTMLString data) { - super.onClose(data); if(element_) { receiver.onClose(element_); } else { receiver.onCloseText(text_); } + souper.onClose(data); } override void onSelfClosing() { - super.onSelfClosing(); receiver.onSelfClosing(element_); + souper.onSelfClosing(); } override void onDocumentEnd() { - super.onDocumentEnd(); + souper.onDocumentEnd(); receiver.onDocumentEnd(document_); } } unittest { - import dom: createDocument, DOMCreateOptions, ParserOptions; + import html.dom: createDocument, DOMCreateOptions, ParserOptions; + import html.parser: parseHTML; import std.array: Appender; - - class ImageCollector: NodeReceiver { + + class ImageCollector(Document): NodeReceiver { Node*[] images; Appender!(Node*[]) a; - this() { - a = Appender!(Node*[])(images); + this(Builder b) { + super(b); } override void onClose(Node* e) { if(e.tag == "img" && e.hasAttr("src")) { a.put(e); } } + override void onDocumentEnd(Document* d) { + images = a.data; + } } enum parserOptions = ((DOMCreateOptions.Default & DOMCreateOptions.DecodeEntities) ? ParserOptions.DecodeEntities : 0); auto document = createDocument(); - Builder b = Builder(document); - ImageCollector c = ImageCollector(b); + auto b = new Builder!Document(document); + ImageCollector c = new ImageCollector(b); b.receiver = c; HTMLString source = ` @@ -79,9 +87,10 @@ unittest {

`; - parseHTML!(typeof(builder), parserOptions)(source, builder); - writeln(document.html); + parseHTML!(typeof(b), parserOptions)(source, b); + import std.stdio: writeln; + writeln(document.root.html); foreach(img; c.images) { - writeln("image: ",img.getAttr("src")); + writeln("image: ",img.attr("src")); } } From 3cf507f8778332aa83f0b16822d9ad7121a9e3e1 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 10 Feb 2016 05:27:23 +0000 Subject: [PATCH 11/15] Tons of stubs There's no way to tell what functions parseHTML requires, without failing to provide those functions and dying with an error. Then reading the errors which can't be machine parsed, and manually adding stubs for the missing functions. No interface or abstract exists for structs, and no inheritance, so parseHTML just has to duplicate its code for every possible builder. Maybe classes would be a good idea after all... --- src/html/dom.d | 4 +-- src/html/pushdom.d | 63 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/html/dom.d b/src/html/dom.d index b4a5dba..0c430d0 100644 --- a/src/html/dom.d +++ b/src/html/dom.d @@ -826,7 +826,7 @@ auto createDocument(size_t options = DOMCreateOptions.Default)(HTMLString source enum parserOptions = ((options & DOMCreateOptions.DecodeEntities) ? ParserOptions.DecodeEntities : 0); auto document = createDocument(); - auto builder = new DOMBuilder!(Document)(document); + auto builder = DOMBuilder!(Document)(document); parseHTML!(typeof(builder), parserOptions)(source, builder); return document; @@ -1337,7 +1337,7 @@ struct DOMBuilder(Document) { } } -protected: + Document* document_; Node* element_; States state_; diff --git a/src/html/pushdom.d b/src/html/pushdom.d index 18a7996..a8eb823 100644 --- a/src/html/pushdom.d +++ b/src/html/pushdom.d @@ -19,32 +19,67 @@ class NodeReceiver(Document) { void onDocumentEnd(Document* doc) {} } +import std.string: replace; +string toIgnore() { + string s = ""; + foreach(name;["onText", + "onOpenStart", + "onAttrName", + "onAttrEnd", + "onAttrValue", + "onComment", + "onCData", + "onDeclaration", + "onProcessingInstruction", + "onNamedEntity", + "onEntity", + "onNumericEntity", + "onHexEntity", + + ]) { + s ~= q{ + void @name@(HTMLString derp) { + } + }.replace("@name@",name); + } + foreach(name;[ + "onAttrEnd"]) { + s ~= q{ + void @name@() { + } + }.replace("@name@",name); + } + return s; +} + + struct Builder(Document) { DOMBuilder!Document souper; this(ref Document document, Node* parent = null) { souper = DOMBuilder!Document(document,parent); } - NodeReceiver receiver; - override void onOpenEnd(HTMLString data) { - receiver.onOpenEnd(element_); + NodeReceiver!Document receiver; + void onOpenEnd(HTMLString data) { + receiver.onOpenEnd(souper.element_); souper.onOpenEnd(data); } - override void onClose(HTMLString data) { - if(element_) { - receiver.onClose(element_); + void onClose(HTMLString data) { + if(souper.element_) { + receiver.onClose(souper.element_); } else { - receiver.onCloseText(text_); + receiver.onCloseText(souper.text_); } souper.onClose(data); } - override void onSelfClosing() { - receiver.onSelfClosing(element_); + void onSelfClosing() { + receiver.onSelfClosing(souper.element_); souper.onSelfClosing(); } - override void onDocumentEnd() { + void onDocumentEnd() { souper.onDocumentEnd(); - receiver.onDocumentEnd(document_); + receiver.onDocumentEnd(souper.document_); } + mixin(toIgnore()); } unittest { @@ -52,7 +87,7 @@ unittest { import html.parser: parseHTML; import std.array: Appender; - class ImageCollector(Document): NodeReceiver { + class ImageCollector(Document): NodeReceiver!Document { Node*[] images; Appender!(Node*[]) a; this(Builder b) { @@ -71,8 +106,8 @@ unittest { enum parserOptions = ((DOMCreateOptions.Default & DOMCreateOptions.DecodeEntities) ? ParserOptions.DecodeEntities : 0); auto document = createDocument(); - auto b = new Builder!Document(document); - ImageCollector c = new ImageCollector(b); + auto b = Builder!Document(document); + ImageCollector!Document c = new ImageCollector!Document(b); b.receiver = c; HTMLString source = ` From ac05a0bf3efb74b7cf2519491fdd3c5f3f65ea18 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 10 Feb 2016 05:51:14 +0000 Subject: [PATCH 12/15] stubs for different arguments parseHTML undeterminably requires some functions to take 0, 1 or 2 HTMLStrings as arguments. So, abstract out stub logic, and add stuff to build argument and type/argument lists. --- src/html/pushdom.d | 64 +++++++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/src/html/pushdom.d b/src/html/pushdom.d index a8eb823..61ba300 100644 --- a/src/html/pushdom.d +++ b/src/html/pushdom.d @@ -1,5 +1,6 @@ module html.pushdom; import html.dom: Document, Node, DOMBuilder, HTMLString; + import std.stdio: writeln; class NodeReceiver(Document) { Builder!Document parent; @@ -14,40 +15,59 @@ class NodeReceiver(Document) { void onClose(Node* element) {} void onCloseText(HTMLString text) {} void onSelfClosing(Node* element) { - this.onClose(element); + if(element) + this.onClose(element); } void onDocumentEnd(Document* doc) {} } import std.string: replace; -string toIgnore() { +import std.format: format; +string makeStubs(string prefix) { string s = ""; + void stub(string name, int args) { + string sargs = ""; + string derpargs = ""; + for(int i=0;i `; parseHTML!(typeof(b), parserOptions)(source, b); - import std.stdio: writeln; writeln(document.root.html); foreach(img; c.images) { writeln("image: ",img.attr("src")); From bd970e9281da927278631dda8172e5f355aa83f2 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 10 Feb 2016 05:58:34 +0000 Subject: [PATCH 13/15] @block@ so we can add code in First take a block and substitute @super@ for the generated super-call to the wrapper. Then substitute that block in where @block@ is for the stub template. Now everything can be stubs, with less room for error! --- src/html/pushdom.d | 55 +++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/html/pushdom.d b/src/html/pushdom.d index 61ba300..cd4172b 100644 --- a/src/html/pushdom.d +++ b/src/html/pushdom.d @@ -23,29 +23,34 @@ class NodeReceiver(Document) { import std.string: replace; import std.format: format; -string makeStubs(string prefix) { - string s = ""; - void stub(string name, int args) { - string sargs = ""; - string derpargs = ""; - for(int i=0;i Date: Wed, 10 Feb 2016 06:11:03 +0000 Subject: [PATCH 14/15] Everything is stubs! This is such a hack. Maybe we should use classes after all. But it works! You don't even have to ensure you typed the right superfunction for a given function. --- src/html/pushdom.d | 55 +++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/html/pushdom.d b/src/html/pushdom.d index cd4172b..e07bf1f 100644 --- a/src/html/pushdom.d +++ b/src/html/pushdom.d @@ -21,10 +21,12 @@ class NodeReceiver(Document) { void onDocumentEnd(Document* doc) {} } -import std.string: replace; +import std.string: replace, strip; import std.format: format; -string stub(string prefix, string name, int nargs, string block = "@super@") { +immutable string prefix = "souper."; + +string stub(string name, int nargs, string block = "@super@") { string arg_signature = ""; string args = ""; for(int i=0;i`; parseHTML!(typeof(b), parserOptions)(source, b); writeln(document.root.html); + string s = ""; foreach(img; c.images) { writeln("image: ",img.attr("src")); + s ~= img.attr("src"); } + assert(s=="one.pngtwo.pngthree.png"); } From 5d795d1dcf0a1ba6d2b33b53d98d9b185446f728 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 10 Feb 2016 06:12:59 +0000 Subject: [PATCH 15/15] Actually these are wrappers now, not stubs... --- src/html/pushdom.d | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/html/pushdom.d b/src/html/pushdom.d index e07bf1f..5659dcb 100644 --- a/src/html/pushdom.d +++ b/src/html/pushdom.d @@ -26,7 +26,7 @@ import std.format: format; immutable string prefix = "souper."; -string stub(string name, int nargs, string block = "@super@") { +string wrapper(string name, int nargs, string block = "@super@") { string arg_signature = ""; string args = ""; for(int i=0;i