| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 | /******************************************************************************* Copyright: Copyright (C) 2008 Kris Bell. All rights reserved. License: BSD style: $(LICENSE) version: Initial release: March 2008 Authors: Kris *******************************************************************************/ module tango.text.xml.DocPrinter; private import tango.io.model.IConduit; private import tango.text.xml.Document; private import tango.core.Exception : XmlException; /******************************************************************************* Simple Document printer, with support for serialization caching where the latter avoids having to generate unchanged sub-trees *******************************************************************************/ class DocPrinter(T) { public alias Document!(T) Doc; /// the typed document public alias Doc.Node Node; /// generic document node private bool quick = true; private uint indentation = 2; version (Win32) private enum const(T)[] Eol = "\r\n"; else private enum const(T)[] Eol = "\n"; /*********************************************************************** Sets the number of spaces used when increasing indentation levels. Use a value of zero to disable explicit formatting ***********************************************************************/ final DocPrinter indent (uint indentation) { this.indentation = indentation; return this; } /*********************************************************************** Enable or disable use of cached document snippets. These represent document branches that remain unaltered, and can be emitted verbatim instead of traversing the tree ***********************************************************************/ final DocPrinter cache (bool yes) { this.quick = yes; return this; } /*********************************************************************** Generate a text representation of the document tree ***********************************************************************/ final T[] print (Doc doc, T[] content=null) { if(content !is null) print (doc.tree, (const(T)[][] s...) { size_t i=0; foreach(t; s) { if(i+t.length >= content.length) throw new XmlException("Buffer is to small"); content[i..t.length] = t; i+=t.length; } content.length = i; }); else print (doc.tree, (const(T)[][] s...){foreach(t; s) content ~= t;}); return content; } /*********************************************************************** Generate a text representation of the document tree ***********************************************************************/ final void print (Doc doc, OutputStream stream) { print (doc.tree, (const(T)[][] s...){foreach(t; s) stream.write(t);}); } /*********************************************************************** Generate a representation of the given node-subtree ***********************************************************************/ final void print (Node root, scope void delegate(const(T)[][]...) emit) { T[256] tmp; T[256] spaces = ' '; // ignore whitespace from mixed-model values const(T)[] rawValue (Node node) { foreach (c; node.rawValue) if (c > 32) return node.rawValue; return null; } void printNode (Node node, uint indent) { // check for cached output if (node.end && quick) { auto p = node.start; auto l = node.end - p; // nasty hack to retain whitespace while // dodging prior EndElement instances if (*p is '>') ++p, --l; emit (p[0 .. l]); } else switch (node.id) { case XmlNodeType.Document: foreach (n; node.children) printNode (n, indent); break; case XmlNodeType.Element: if (indentation > 0) emit (Eol, spaces[0..indent]); emit ("<", node.toString(tmp)); foreach (attr; node.attributes) emit (` `, attr.toString(tmp), `="`, attr.rawValue, `"`); auto value = rawValue (node); if (node.child) { emit (">"); if (value.length) emit (value); foreach (child; node.children) printNode (child, indent + indentation); // inhibit newline if we're closing Data if (node.lastChild.id != XmlNodeType.Data && indentation > 0) emit (Eol, spaces[0..indent]); emit ("</", node.toString(tmp), ">"); } else if (value.length) emit (">", value, "</", node.toString(tmp), ">"); else emit ("/>"); break; // ingore whitespace data in mixed-model // <foo> // <bar>blah</bar> // // a whitespace Data instance follows <foo> case XmlNodeType.Data: auto value = rawValue (node); if (value.length) emit (node.rawValue); break; case XmlNodeType.Comment: emit ("<!--", node.rawValue, "-->"); break; case XmlNodeType.PI: emit ("<?", node.rawValue, "?>"); break; case XmlNodeType.CData: emit ("<![CDATA[", node.rawValue, "]]>"); break; case XmlNodeType.Doctype: emit ("<!DOCTYPE ", node.rawValue, ">"); break; default: emit ("<!-- unknown node type -->"); break; } } printNode (root, 0); } } debug import tango.text.xml.Document; debug import tango.util.log.Trace; unittest { const(char)[] document = "<blah><xml>foo</xml></blah>"; auto doc = new Document!(char); doc.parse (document); auto p = new DocPrinter!(char); char[1024] buf; auto newbuf = p.print (doc, buf); assert(document == newbuf); assert(buf.ptr == newbuf.ptr); assert(document == p.print(doc)); } |