1 module text.xml.Writer;
2 
3 import dxml.util;
4 import dxml.writer;
5 import std.array;
6 import std.range;
7 import std.typecons;
8 import text.xml.Tree;
9 
10 /**
11  * This struct is used to output XML string representation in a specific
12  * format.
13  */
14 struct CustomXmlWriter(Flag!"pretty" pretty, Sink)
15 {
16     private XMLWriter!Sink writer;
17     private bool skipIndent = false;
18 
19     static if (pretty)
20     {
21         private enum newline = Newline.yes;
22         private enum insertIndent = InsertIndent.yes;
23     }
24     else
25     {
26         private enum newline = Newline.no;
27         private enum insertIndent = InsertIndent.no;
28     }
29 
30     public this(Sink sink)
31     {
32         this.writer = xmlWriter(sink);
33     }
34 
35     public Sink sink()
36     {
37         return this.writer.output;
38     }
39 
40     public void openStartTag(string name)
41     {
42         this.writer.openStartTag(name, newline);
43     }
44 
45     public void writeAttr(string name, string value)
46     {
47         this.writer.writeAttr(name, value);
48     }
49 
50     public void closeStartTag(Flag!"emptyTag" emptyTag = No.emptyTag)
51     {
52         this.writer.closeStartTag(emptyTag ? EmptyTag.yes : EmptyTag.no);
53     }
54 
55     public void writeText(string text)
56     {
57         this.writer.writeText(text.encodeText, Newline.no, InsertIndent.no);
58         this.skipIndent = true;
59     }
60 
61     public void writeCDATA(string text)
62     {
63         this.writer.writeCDATA(text.encodeText, newline, insertIndent);
64     }
65 
66     public void writeEndTag(string name)
67     {
68         this.writer.writeEndTag(name, this.skipIndent ? Newline.no : newline);
69         this.skipIndent = false;
70     }
71 
72     private void finishTag(const XmlNode document)
73     {
74         foreach (attribute; document.attributes)
75         {
76             writeAttr(attribute.name, attribute.value);
77         }
78         if (document.children.empty)
79         {
80             closeStartTag(Yes.emptyTag);
81         }
82         else
83         {
84             closeStartTag();
85             foreach (child; document.children)
86             {
87                 writeImpl(child);
88             }
89             writeEndTag(document.tag);
90         }
91     }
92 
93     private void writeImpl(const XmlNode document)
94     {
95         switch (document.type) with (XmlNode.Type)
96         {
97             case element:
98                 openStartTag(document.tag);
99                 finishTag(document);
100                 break;
101             case comment:
102                 break;
103             case cdata:
104                 writeCDATA(document.text);
105                 break;
106             case text:
107                 writeText(document.text);
108                 break;
109             default:
110                 assert(false);
111         }
112     }
113 
114     public void write(const XmlNode document)
115     {
116         if (document.type == XmlNode.Type.element)
117         {
118             if (!document.tag.empty)
119             {
120                 this.writer.openStartTag(document.tag, Newline.no);
121                 finishTag(document);
122                 static if (pretty)
123                 {
124                     this.writer.output.put('\n');
125                 }
126             }
127             else
128             {
129                 write(document.children.front);
130             }
131         }
132         else
133         {
134             writeImpl(document);
135         }
136     }
137 }
138 
139 template customXmlWriter(Flag!"pretty" pretty)
140 {
141     CustomXmlWriter!(pretty, Sink) customXmlWriter(Sink)(Sink sink)
142     {
143         return typeof(return)(sink);
144     }
145 }