1 /** 2 * Copyright: 3 * (C) 2016 Martin Brzenska 4 * 5 * License: 6 * Distributed under the terms of the MIT license. 7 * Consult the provided LICENSE.md file for details 8 */ 9 module libdominator.Node; 10 11 import std.string : toLower; 12 import std.format : format ; 13 import std.conv : to ; 14 15 import libdominator; 16 17 version(unittest) { 18 import libdominator.Filter; 19 import std.file; 20 } 21 22 ///Represents a node in a DOM 23 class Node { 24 private string tag; 25 private Attribute[] arrAttributes; 26 private uint startPos, endPos; 27 private ushort startTagLength, endTagLength; 28 private bool is_comment; 29 private Node* parent; 30 private Node*[] children; 31 32 ///Makes a naked node object 33 this() {} 34 35 ///Makes a node with a given tagname 36 this(string tag) { 37 this.setTag(tag); 38 } 39 40 ///Makes a node with a given tagname and with the information for the position in the Document 41 this(T)(string tag, T startPosition) { 42 this.setTag(tag); 43 this.setStartPosition(startPosition); 44 } 45 46 ///Sets the tagname 47 public Node setTag(string tag) { 48 this.tag = toLower(tag); 49 return this; 50 } 51 52 ///Sets the position in the document where this node begins 53 public Node setStartPosition(T)(T position) { 54 this.startPos = to!uint(position); 55 return this; 56 } 57 58 ///Sets the position in the document where this node ends 59 public Node setEndPosition(T)(T position) { 60 this.endPos = to!uint(position); 61 return this; 62 } 63 64 ///Does what the name says 65 public string getTag() { 66 return this.tag; 67 } 68 /// ditto 69 public Attribute[] getAttributes() { 70 return this.arrAttributes; 71 } 72 73 /// ditto 74 public void addAttribute(Attribute attribute) { 75 this.arrAttributes ~= attribute; 76 } 77 78 /// ditto 79 public uint getStartPosition() { 80 return this.startPos; 81 } 82 83 /// ditto 84 public uint getEndPosition() { 85 return this.endPos; 86 } 87 88 /// ditto 89 public Node setStartTagLength(T)(T length) { 90 this.startTagLength = to!ushort(length); 91 return this; 92 } 93 94 /// ditto 95 public Node setEndTagLength(T)(T length) { 96 this.endTagLength = to!ushort(length); 97 return this; 98 } 99 100 /// ditto 101 public ushort getStartTagLength() { 102 return this.startTagLength; 103 } 104 105 /// ditto 106 public ushort getEndTagLength() { 107 return this.endTagLength; 108 } 109 unittest { 110 const string content = `<ol id="ol-1"> 111 <li id="li-1-ol-1">list Inner</li> 112 <li id="li-2-ol-1">list Inner</li > 113 <li id="li-3-ol-1"> list Inner < /li> 114 <li id="li-4-ol-1"> list Inner < /li > 115 </ol>`; 116 Dominator dom = new Dominator(content); 117 Node[] liNodes = dom.filterDom(DomFilter("li")); 118 assert(liNodes[0].getEndTagLength == 5 ); 119 assert(liNodes[1].getEndTagLength == 6 , to!(string)(liNodes[1].getEndTagLength)); 120 assert(liNodes[2].getEndTagLength == 6 ); 121 assert(liNodes[3].getEndTagLength == 7 ); 122 } 123 124 ///Markes this node to be inside of a comment 125 public Node isComment(bool sw) { 126 this.is_comment = sw; 127 return this; 128 } 129 130 /** 131 * Returns: true if the node is marked to be inside of a comment, otherwise false. 132 */ 133 public bool isComment() { 134 return this.is_comment; 135 } 136 137 ///Sets the given node as the parent node 138 public void setParent(Node* pNode) { 139 this.parent = pNode; 140 } 141 142 ///Does what the name says 143 public Node getParent() { 144 return this.parent is null ? new Node : (*this.parent); 145 } 146 147 ///Adds a node as a child node 148 public void addChild(Node* pNode) { 149 this.children ~= pNode; 150 } 151 152 ///Does what the name says 153 public Node[] getChildren() { 154 Node[] nodes; 155 foreach(Node* pNode ; this.children) { 156 if(pNode !is null) { nodes ~= (*pNode); } 157 } 158 return nodes; 159 } 160 161 /** 162 * Returns: true if the node has children nodes. 163 */ 164 public size_t hasChildren() { 165 return this.children.length; 166 } 167 168 /** 169 * Does what the name says 170 */ 171 public Node[] getSiblings() { 172 import std.algorithm.mutation : remove; 173 return remove!(a => a.getStartPosition() == this.getStartPosition())(this.getParent().getChildren()); 174 } 175 176 /** 177 * Returns: true if the node has a parent node. 178 */ 179 public bool hasParent() { 180 return (parent !is null); 181 } 182 183 private void collectDescendants(Node node , ref Node[] nodes) { 184 foreach(Node childNode ; node.getChildren()) { 185 nodes ~= childNode; 186 collectDescendants(childNode , nodes); 187 } 188 } 189 190 public Node[] getDescendants() { 191 Node[] nodes; 192 collectDescendants(this , nodes); 193 return nodes; 194 } 195 196 private void collectAncestors(Node node , ref Node[] nodes) { 197 if(node.hasParent) { 198 Node parentNode = node.getParent(); 199 nodes ~= parentNode; 200 collectAncestors(parentNode , nodes); 201 } 202 } 203 204 public Node[] getAncestors() { 205 Node[] nodes; 206 collectAncestors(this , nodes); 207 return nodes; 208 } 209 unittest { 210 Node 211 root = new Node("root"), 212 firstChild = new Node("first-child"), 213 secondChild = new Node("second-child"); 214 215 firstChild.setParent(&root); 216 secondChild.setParent(&firstChild); 217 218 assert(secondChild.getAncestors.length == 2); 219 220 } 221 }