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 }