123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
/*******************************************************************************

        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.text.xml.Document;

/*******************************************************************************

        Simple Document printer, with support for serialization caching 
        where the latter avoids having to generate unchanged sub-trees

*******************************************************************************/

class DocPrinter(T) : IXmlPrinter!(T)
{
        private bool quick = true;
        private uint indentation = 2;

        version (Win32)
                 private const T[] Eol = "\r\n";
           else
              private 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;

                print (doc.tree, (T[][] s...){foreach(t; s) content ~= t;});
                return content;
        }
        
        /***********************************************************************
        
                Generate a representation of the given node-subtree 

        ***********************************************************************/
        
        final void print (Node root, void delegate(T[][]...) emit)
        {
                T[256] tmp;
                T[256] spaces = ' ';

                // ignore whitespace from mixed-model values
                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 (DocPrinter)
{
        import tango.io.Stdout;
        import tango.text.xml.Document;

        void main()
        {
                char[] document = "<blah><xml>foo</xml></blah>";

                auto doc = new Document!(char);
                doc.parse (document);

                void test (char[][] s...){foreach (t; s) Stdout(t).flush;}

                auto p = new DocPrinter!(char);
                p (doc.tree, &test);
        }
}