This commit is contained in:
Vadim Lopatin 2015-12-23 12:23:20 +03:00
parent d090f8ad49
commit e2a847f108
1 changed files with 146 additions and 10 deletions

View File

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