1 /**
2  * Copyright: Copyright (c) 2010-2011 Jacob Carlborg.
3  * Authors: Jacob Carlborg
4  * Version: Initial created: Jun 26, 2010
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
6  */
7 module orange.xml.XmlDocument;
8 
9 import std..string;
10 import std.stdio;
11 
12 import orange.xml.PhobosXml;
13 
14 /// This class represents an exception thrown by XmlDocument.
15 class XMLException : Exception
16 {
17     this (string message, string file = null, size_t line = 0)
18     {
19         super(message, file, line);
20     }
21 }
22 
23 /**
24  * This class represents an XML DOM document. It provides a common interface to the XML
25  * document implementations available in Phobos and Tango.
26  */
27 final class XmlDocument
28 {
29     /// The type of the document implementation.
30     alias Document Doc;
31 
32     /// The type of the node implementation.
33     alias Element InternalNode;
34 
35     /// The type of the query node implementation.
36     alias Element QueryNode;
37 
38     /// The type of the visitor type implementation.
39     alias Element[] VisitorType;
40 
41     /// foreach support for visiting a set of nodes.
42     struct VisitorProxy
43     {
44         private VisitorType nodes;
45 
46         private static VisitorProxy opCall (VisitorType nodes)
47         {
48             VisitorProxy vp;
49             vp.nodes = nodes;
50 
51             return vp;
52         }
53 
54         /**
55          * Returns true if this proxy contains any nodes.
56          *
57          * Examples:
58          * ---
59          * VisitorProxy proxy;
60          * assert(proxy.exist == false);
61          * ---
62          *
63          * Returns: true if this proxy contains any nodes.
64          */
65         bool exist ()
66         {
67             return nodes.length > 0;
68         }
69 
70         /**
71          * Allows to iterate over the set of nodes.
72          *
73          * Examples:
74          * ---
75          * VisitorProxy proxy
76          * foreach (node ; proxy) {}
77          * ---
78          */
79         int opApply (int delegate (ref Node) dg)
80         {
81             int result;
82 
83             foreach (n ; nodes)
84             {
85                 auto p = Node(n);
86                 result = dg(p);
87 
88                 if (result)
89                     break;
90             }
91 
92             return result;
93         }
94     }
95 
96     /// A generic document node
97     struct Node
98     {
99         private InternalNode node;
100         private bool shouldAddToDoc = true;
101         private bool isRoot = true;
102 
103         private static Node opCall (InternalNode node, bool shouldAddToDoc = false, bool isRoot = false)
104            {
105             Node proxy;
106                proxy.node = node;
107                 proxy.shouldAddToDoc = shouldAddToDoc;
108                 proxy.isRoot = isRoot;
109 
110                return proxy;
111            }
112 
113         /**
114          * Returns an invalid node.
115          *
116          * Examples:
117          * ---
118          * auto node = Node.invalid;
119          * assert(node.isValid == false);
120          * ---
121          *
122          * Returns: an invalid node
123          */
124         public static Node invalid ()
125         {
126             return Node(null);
127         }
128 
129         /// Returns the name of the node.
130         string name ()
131         {
132             return node.name;
133         }
134 
135         /// Returns the value of the node.
136         string value ()
137         {
138             return node.value;
139         }
140 
141         /// Returns the parent node.
142         Node parent ()
143         {
144             return Node(node.parent);
145         }
146 
147         /**
148          * Returns true if the receiver is valid.
149          *
150          * auto node = Node.invalid;
151          * assert(node.isValid == false);
152          *
153          * Returns: true if the receiver is valid.
154          *
155          * See_Also: invalid
156          */
157         bool isValid ()
158         {
159             return node !is null;
160         }
161 
162         /// Returns a foreach iterator for node children.
163         VisitorProxy children ()
164         {
165             return VisitorProxy(node.children);
166         }
167 
168         /// Returns a foreach iterator for node attributes.
169         VisitorProxy attributes ()
170         {
171             return VisitorProxy(cast(VisitorType) node.attributes);
172         }
173 
174         /// Return an XPath handle to query the receiver.
175         QueryProxy query ()
176         {
177             return QueryProxy(node.query);
178         }
179 
180         /**
181          * Creates a new element and attaches it to the receiver.
182          *
183          * Params:
184          *     name = the name of the element
185          *     value = the value of the element
186          *
187          * Returns: the newly create element.
188          */
189         Node element (string name, string value = null)
190         {
191             auto element = new Element(name, value);
192 
193             if (isRoot)
194             {
195                 node.tag = element.tag;
196                 node ~= new Text(value);
197 
198                 return Node(node, true, false);
199             }
200 
201             else
202             {
203                 if (shouldAddToDoc)
204                 {
205                     shouldAddToDoc = false;
206                     node ~= element;
207                 }
208 
209                 else
210                     node ~= element;
211 
212                 return Node(element, shouldAddToDoc, false);
213             }
214         }
215 
216         /**
217          * Creates a new attribute and attaches it to the receiver.
218          *
219          * Params:
220          *     name = the name of the attribute
221          *     value = the value of the attribute
222          *
223          * Returns: the newly created attribute
224          */
225         Node attribute (string name, string value)
226         {
227             node.attribute(null, name, value);
228 
229             return this;
230         }
231 
232         /**
233          * Attach an already existing node to the receiver.
234          *
235          * Params:
236          *     node = the node to attach.
237          */
238         void attach (Node node)
239         {
240             if (this.node !is node.node.parent)
241             {
242                 node.node.parent = this.node;
243                 this.node ~= node.node;
244             }
245         }
246     }
247 
248     /// This an XPath query handle used to perform queries on a set of elements.
249     struct QueryProxy
250     {
251         private Node[] nodes_;
252 
253         private static QueryProxy opCall (QueryNode node)
254         {
255             QueryProxy qp;
256 
257             qp.nodes_ = [Node(node)];
258 
259             return qp;
260         }
261 
262         private static QueryProxy opCall (Node[] nodes)
263         {
264             QueryProxy qp;
265             qp.nodes_ = nodes;
266 
267             return qp;
268         }
269 
270         /**
271          * Returns a set containing all attribute nodes of the nodes within this set which pass
272          * the given filtering test.
273          *
274          * Params:
275          *     filter = the filter to be applied on the attributes. Should return true when there
276          *                 is a match for an attribute.
277          *
278          * Returns: the set of nodes that passed the filter test
279          */
280         QueryProxy attribute (bool delegate (Node) filter)
281         {
282             Node[] nodes;
283 
284             foreach (node ; nodes_)
285             {
286                 foreach (attr ; node.attributes.nodes)
287                 {
288                     auto n = Node(attr);
289 
290                     if (filter && filter(n))
291                         nodes ~= n;
292                 }
293             }
294 
295             return QueryProxy(nodes);
296         }
297 
298         /**
299          * Return a set containing all attributes of the nodes within this set, which match
300          * the given name.
301          *
302          * Params:
303          *     name = the name of the attribute to filter on
304          *
305          * Returns: a set of elements that passed the filter test
306          */
307         QueryProxy attribute (string name = null)
308         {
309             bool filter (Node node)
310             {
311                 return node.name == name;
312             }
313 
314             bool always (Node node)
315             {
316                 return true;
317             }
318 
319             if (name.length > 0)
320                 return attribute(&filter);
321 
322             return attribute(&always);
323         }
324 
325         /// Returns an array of all the nodes stored in the receiver.
326         Node[] nodes ()
327         {
328             return nodes_;
329         }
330 
331         /**
332          * Returns a set containing all child elements of the nodes within this set, which
333          * match the given name.
334          *
335          * Params:
336          *     name = the name to filter on.
337          *
338          * Returns: a set of elements that passed the filter test
339          */
340         QueryProxy opIndex (string name)
341         {
342             Node[] proxies;
343 
344             foreach (parent ; nodes_)
345             {
346                 foreach (e ; parent.node.elements)
347                 {
348                     if (e.tag.name == name)
349                         proxies ~= Node(e);
350                 }
351             }
352 
353             return QueryProxy(proxies);
354         }
355 
356         /**
357          * Iterates over the set of nodes.
358          *
359          * Examples:
360          * ---
361          * foreach (node ; nodes) {}
362          * ---
363          */
364         int opApply (int delegate (ref Node) dg)
365         {
366             auto visitor = nodes_;
367 
368             int result;
369 
370             foreach (n ; visitor)
371                 if (dg(n))
372                     break;
373 
374             return result;
375         }
376     }
377 
378     /// Set this to true if there should be strict errro checking.
379     bool strictErrorChecking;
380 
381     /// The number of spaces used for indentation used when printing the document.
382     uint indentation = 4;
383 
384     private Doc doc;
385     InternalNode currentNode;
386 
387     /**
388      * Creates a new instance of this class
389      *
390      * Examples:
391      * ---
392      * auto doc = new XmlDocument!();
393      * ---
394      *
395      * Params:
396      *     strictErrorChecking = true if strict errro checking should be enabled
397      */
398     this (bool strictErrorChecking = true)
399     {
400         doc = new Doc(new Tag("root"));
401         this.strictErrorChecking = strictErrorChecking;
402     }
403 
404     /**
405      * Attaches a header to the document.
406      *
407      * Examples:
408      * ---
409      * auto doc = new XmlDocument!();
410      * doc.header("UTF-8");
411      * // <?xml version="1.0" encoding="UTF-8"?>
412      * ---
413      *
414      * Params:
415      *     encoding = the encoding that should be put in the header
416      *
417      * Returns: the receiver
418      */
419     XmlDocument header (string encoding = null)
420     {
421         string newEncoding = encoding.length > 0 ? encoding : "UTF-8";
422         string header = `<?xml version="1.0" encoding="` ~ newEncoding ~ `"?>`;
423         doc.prolog = header;
424 
425         return this;
426     }
427 
428     /// Rests the reciver. Allows to parse new content.
429     XmlDocument reset ()
430     {
431         doc = new Doc(new Tag("root"));
432 
433         return this;
434     }
435 
436     /// Return the root document node, from which all other nodes are descended.
437     Node tree ()
438     {
439         return Node(doc, true, true);
440     }
441 
442     /**
443      * Parses the given string of XML.
444      *
445      * Params:
446      *     xml = the XML to parse
447      */
448     void parse (string xml)
449     {
450         auto tmp = new Doc(xml);
451         doc = new Doc(new Tag("root"));
452         doc.elements ~= tmp;
453     }
454 
455     /// Return an xpath handle to query this document. This starts at the document root.
456     QueryProxy query ()
457     {
458         return QueryProxy(doc);
459     }
460 
461     /// Pretty prints the document.
462     override string toString ()
463     {
464         return doc.prolog ~ "\n" ~ join(doc.pretty(indentation), "\n");
465     }
466 
467     /**
468      * Attaches a new node to the docuement.
469      *
470      * Params:
471      *     name = the name of the node
472      *     value = the vale of the node
473      *
474      * Returns: returns the newly created node
475      */
476     Node createNode (string name, string value = null)
477     {
478         return Node(new Element(name, value), false, false);
479     }
480 }