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));
}
|