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 }