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 }