From e2a847f108fdb8844615671577b600c1c6c76ed9 Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Wed, 23 Dec 2015 12:23:20 +0300 Subject: [PATCH] DOM --- src/dlangui/core/dom.d | 156 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 146 insertions(+), 10 deletions(-) diff --git a/src/dlangui/core/dom.d b/src/dlangui/core/dom.d index 5e7a3fde..b4284c46 100644 --- a/src/dlangui/core/dom.d +++ b/src/dlangui/core/dom.d @@ -4,7 +4,19 @@ import dlangui.core.collections; import std.traits; import std.conv : to; -import std.string; +import std.string : startsWith, endsWith; +import std.array : empty; +import std.algorithm : equal; + +// Namespace, element tag and attribute names are stored as numeric ids for better performance and lesser memory consumption. + +/// id type for interning namespaces +alias ns_id = ushort; +/// id type for interning element names +alias elem_id = uint; +/// id type for interning attribute names +alias attr_id = ushort; + /// Base class for DOM nodes class Node { @@ -17,6 +29,15 @@ public: /// returns document node @property Document document() { return _document; } + /// return element tag id + @property elem_id id() { return 0; } + /// return element namespace id + @property ns_id nsid() { return 0; } + /// return element tag name + @property string name() { return document.tagName(id); } + /// return element namespace name + @property string nsname() { return document.nsName(nsid); } + // node properties /// returns true if node is text @@ -42,6 +63,18 @@ public: /// returns last child node @property Node lastChild() { return null; } + /// find child node, return its index if found, -1 if not found or not child of this node + int childIndex(Node child) { return -1; } + /// return node index in parent's child node collection, -1 if not found + @property int index() { return _parent ? _parent.childIndex(this) : -1; } + + /// append text child + Node appendText(dstring s, int index = -1) { assert(false); } + /// append element child - by namespace and tag names + Node appendElement(string ns, string tag, int index = -1) { assert(false); } + /// append element child - by namespace and tag ids + Node appendElement(ns_id ns, elem_id tag, int index = -1) { assert(false); } + /// node text @property dstring text() { return null; } /// ditto @@ -54,7 +87,10 @@ public: class Text : Node { private: dstring _text; - + this(Document doc, dstring text = null) { + _document = doc; + _text = text; + } public: /// node text override @property dstring text() { return _text; } @@ -66,7 +102,21 @@ public: class Element : Node { private: Collection!Node _children; + elem_id _id; // element tag id + ns_id _ns; // element namespace id + + this(Document doc, ns_id ns, elem_id id) { + _document = doc; + _ns = ns; + _id = id; + } public: + + /// return element tag id + override @property elem_id id() { return _id; } + /// return element namespace id + override @property ns_id nsid() { return _ns; } + // child nodes /// returns child node count @@ -77,15 +127,88 @@ public: override @property Node firstChild() { return _children.length > 0 ? _children[0] : null; } /// returns last child node override @property Node lastChild() { return _children.length > 0 ? _children[_children.length - 1] : null; } + /// find child node, return its index if found, -1 if not found or not child of this node + override int childIndex(Node child) { + for (int i = 0; i < _children.length; i++) + if (child is _children[i]) + return i; + return -1; + } + + /// append text child + override Node appendText(dstring s, int index = -1) { + Node item = document.createText(s); + _children.add(item, index >= 0 ? index : size_t.max); + return item; + } + /// append element child - by namespace and tag names + override Node appendElement(string ns, string tag, int index = -1) { + Node item = document.createElement(ns, tag); + _children.add(item, index >= 0 ? index : size_t.max); + return item; + } + /// append element child - by namespace and tag ids + override Node appendElement(ns_id ns, elem_id tag, int index = -1) { + Node item = document.createElement(ns, tag); + _children.add(item, index >= 0 ? index : size_t.max); + return item; + } } /// Document node class Document : Element { public: this() { + super(null, 0, 0); _elemIds.init!Tag(); _attrIds.init!Attr(); _nsIds.init!Ns(); + _document = this; + } + /// create text node + Text createText(dstring text) { + return new Text(this, text); + } + /// create element node by namespace and tag ids + Element createElement(ns_id ns, elem_id tag) { + return new Element(this, ns, tag); + } + /// create element node by namespace and tag names + Element createElement(string ns, string tag) { + return new Element(this, internNs(ns), internTag(tag)); + } + + // Ids + + /// return name for element tag id + string tagName(elem_id id) { + return _elemIds[id]; + } + /// return name for namespace id + string nsName(ns_id id) { + return _nsIds[id]; + } + /// return name for attribute id + string attrName(ns_id id) { + return _attrIds[id]; + } + /// get id for element tag name + elem_id internTag(string s) { + if (s.empty) + return 0; + return _elemIds.intern(s); + } + /// get id for namespace name + ns_id internNs(string s) { + if (s.empty) + return 0; + return _nsIds.intern(s); + } + /// get id for namespace name + attr_id internAttr(string s) { + if (s.empty) + return 0; + return _attrIds.intern(s); } private: IdentMap!(elem_id) _elemIds; @@ -93,12 +216,7 @@ private: IdentMap!(ns_id) _nsIds; } -/// id type for interning namespaces -alias ns_id = ushort; -/// id type for interning element names -alias elem_id = uint; -/// id type for interning attribute names -alias attr_id = ushort; + /// remove trailing _ from string, e.g. "body_" -> "body" private string removeTrailingUnderscore(string s) { @@ -112,11 +230,14 @@ struct IdentMap(ident_t) { /// initialize with elements of enum void init(E)() if (is(E == enum)) { foreach(member; EnumMembers!E) { - internString(removeTrailingUnderscore(member.to!string), member); + static if (member.to!int) { + //pragma(msg, "interning string '" ~ removeTrailingUnderscore(member.to!string) ~ "' for " ~ E.stringof); + intern(removeTrailingUnderscore(member.to!string), member); + } } } /// intern string - return ID assigned for it - ident_t internString(string s, ident_t id = 0) { + ident_t intern(string s, ident_t id = 0) { if (auto p = s in _stringToId) return *p; ident_t res; @@ -133,12 +254,16 @@ struct IdentMap(ident_t) { } /// lookup id for string, return 0 if string is not found ident_t opIndex(string s) { + if (s.empty) + return 0; if (auto p = s in _stringToId) return *p; return 0; } /// lookup name for id, return null if not found string opIndex(ident_t id) { + if (!id) + return null; if (auto p = id in _idToString) return *p; return null; @@ -187,5 +312,16 @@ unittest { assert(map[Tag.div].equal("div")); Document doc = new Document(); + auto body_ = doc.appendElement(null, "body"); + assert(body_.id == Tag.body_); + assert(body_.name.equal("body")); + auto div = body_.appendElement(null, "div"); + assert(body_.childCount == 1); + assert(div.id == Tag.body_); + assert(div.name.equal("div")); + div.appendText("Some text"d); + assert(div.childCount == 1); + assert(div.child(0).text.equal("Some text"d)); + destroy(doc); }