1 /******************************************************************************* 2 3 Copyright: 4 Copyright (C) 2008 Kris Bell. 5 Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH. 6 All rights reserved. 7 8 License: 9 Tango Dual License: 3-Clause BSD License / Academic Free License v3.0. 10 See LICENSE_TANGO.txt for details. 11 12 Version: Initial release: March 2008 13 14 Authors: Kris 15 16 *******************************************************************************/ 17 18 module ocean.text.xml.DocPrinter; 19 20 import ocean.meta.types.Qualifiers; 21 22 import ocean.io.model.IConduit; 23 24 import ocean.text.xml.Document; 25 26 import ocean.core.ExceptionDefinitions : XmlException; 27 28 version (unittest) 29 { 30 import ocean.text.xml.Document; 31 import ocean.core.Test; 32 } 33 34 35 /******************************************************************************* 36 37 Simple Document printer, with support for serialization caching 38 where the latter avoids having to generate unchanged sub-trees 39 40 *******************************************************************************/ 41 42 class DocPrinter(T) 43 { 44 public alias Document!(T) Doc; /// the typed document 45 public alias Doc.Node Node; /// generic document node 46 47 private bool quick = true; 48 private uint indentation = 2; 49 50 private static immutable Eol = "\n"; 51 52 /*********************************************************************** 53 54 Sets the number of spaces used when increasing indentation 55 levels. Use a value of zero to disable explicit formatting 56 57 ***********************************************************************/ 58 59 final DocPrinter indent (uint indentation) 60 { 61 this.indentation = indentation; 62 return this; 63 } 64 65 /*********************************************************************** 66 67 Enable or disable use of cached document snippets. These 68 represent document branches that remain unaltered, and 69 can be emitted verbatim instead of traversing the tree 70 71 ***********************************************************************/ 72 73 final DocPrinter cache (bool yes) 74 { 75 this.quick = yes; 76 return this; 77 } 78 79 /*********************************************************************** 80 81 Generate a text representation of the document tree 82 83 ***********************************************************************/ 84 85 final const(T)[] print (Doc doc, T[] content=null) 86 { 87 if(content !is null) 88 print (doc.tree, (const(T)[][] s...) 89 { 90 size_t i=0; 91 foreach(t; s) 92 { 93 if(i+t.length >= content.length) 94 throw new XmlException("Buffer is to small"); 95 96 content[i..t.length] = t; 97 i+=t.length; 98 } 99 content.length = i; 100 }); 101 else 102 print (doc.tree, (const(T)[][] s...){foreach(t; s) content ~= t;}); 103 return content; 104 } 105 106 /*********************************************************************** 107 108 Generate a text representation of the document tree 109 110 ***********************************************************************/ 111 112 final void print (Doc doc, OutputStream stream) 113 { 114 print (doc.tree, (const(T)[][] s...){foreach(t; s) stream.write(t);}); 115 } 116 117 /*********************************************************************** 118 119 Generate a representation of the given node-subtree 120 121 ***********************************************************************/ 122 123 final void print (Node root, scope void delegate(const(T)[][]...) emit) 124 { 125 T[256] tmp; 126 T[256] spaces = ' '; 127 128 // ignore whitespace from mixed-model values 129 const(T)[] rawValue (Node node) 130 { 131 foreach (c; node.rawValue) 132 if (c > 32) 133 return node.rawValue; 134 return null; 135 } 136 137 void printNode (Node node, uint indent) 138 { 139 // check for cached output 140 if (node.end && quick) 141 { 142 auto p = node.start; 143 auto l = node.end - p; 144 // nasty hack to retain whitespace while 145 // dodging prior EndElement instances 146 if (*p is '>') 147 ++p, --l; 148 emit (p[0 .. l]); 149 } 150 else 151 switch (node.id) 152 { 153 case XmlNodeType.Document: 154 foreach (n; node.children) 155 printNode (n, indent); 156 break; 157 158 case XmlNodeType.Element: 159 if (indentation > 0) 160 emit (Eol, spaces[0..indent]); 161 emit ("<", node.toString(tmp)); 162 163 foreach (attr; node.attributes) 164 emit (` `, attr.toString(tmp), `="`, attr.rawValue, `"`); 165 166 auto value = rawValue (node); 167 if (node.child) 168 { 169 emit (">"); 170 if (value.length) 171 emit (value); 172 foreach (child; node.children) 173 printNode (child, indent + indentation); 174 175 // inhibit newline if we're closing Data 176 if (node.lastChild.id != XmlNodeType.Data && indentation > 0) 177 emit (Eol, spaces[0..indent]); 178 emit ("</", node.toString(tmp), ">"); 179 } 180 else 181 if (value.length) 182 emit (">", value, "</", node.toString(tmp), ">"); 183 else 184 emit ("/>"); 185 break; 186 187 // ingore whitespace data in mixed-model 188 // <foo> 189 // <bar>blah</bar> 190 // 191 // a whitespace Data instance follows <foo> 192 case XmlNodeType.Data: 193 auto value = rawValue (node); 194 if (value.length) 195 emit (node.rawValue); 196 break; 197 198 case XmlNodeType.Comment: 199 emit ("<!--", node.rawValue, "-->"); 200 break; 201 202 case XmlNodeType.PI: 203 emit ("<?", node.rawValue, "?>"); 204 break; 205 206 case XmlNodeType.CData: 207 emit ("<![CDATA[", node.rawValue, "]]>"); 208 break; 209 210 case XmlNodeType.Doctype: 211 emit ("<!DOCTYPE ", node.rawValue, ">"); 212 break; 213 214 default: 215 emit ("<!-- unknown node type -->"); 216 break; 217 } 218 } 219 220 printNode (root, 0); 221 } 222 } 223 224 /// 225 unittest 226 { 227 istring document = "<blah><xml>foo</xml></blah>"; 228 229 auto doc = new Document!(char); 230 doc.parse (document.dup); 231 232 auto p = new DocPrinter!(char); 233 char[1024] buf; 234 auto newbuf = p.print (doc, buf); 235 test(document == newbuf); 236 test(buf.ptr == newbuf.ptr); 237 test(document == p.print(doc)); 238 }