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