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