1 // SemiTwist Library
2 // Written in the D programming language.
3 
4 module semitwist.treeout;
5 
6 import std.array;
7 import std.conv;
8 import std.range : ElementType;
9 import std.regex;
10 import std.stdio;
11 import std.string;
12 import std.traits;
13 
14 version(DigitalMars)
15 	private immutable isDM = true;
16 else
17 	private immutable isDM = false;
18 
19 static import std.compiler;
20 static if(isDM && std.compiler.version_minor <= 53)
21 	import std.ctype;
22 else
23 {
24 	import std.ascii;
25 	private alias std.ascii.digits digits;
26 	private alias std.ascii.letters letters;
27 }
28 
29 import semitwist.util.all;
30 
31 string replicate(string str, int num)
32 {
33 	if(num < 1)
34 		return "";
35 	
36 	string ret = "";
37 	foreach(i; 0..num)
38 		ret ~= str;
39 		
40 	return ret;
41 }
42 
43 abstract class TreeFormatter
44 {
45 	string fullIndent(int nodeDepth)
46 	{
47 		return strip()? "" : indent().replicate(nodeDepth);
48 	}
49 	
50 	string newline()
51 	{
52 		return strip()? "" : "\n";
53 	}
54 	
55 	string indent();
56 	bool strip();
57 	string processData(string content, int nodeDepth);
58 	string processComment(string content, int nodeDepth);
59 	string processAttribute(string name, string value, int nodeDepth);
60 	string processNode(string name, string attributes, string content, int nodeDepth);
61 	string reduceAttributes(string[] attributes, int nodeDepth);
62 	string reduceNodes(string[] nodes, int nodeDepth);
63 	string finalize(string content);
64 }
65 
66 class XMLFormatter(bool _strip, string _indent="\t") : TreeFormatter
67 {
68 	string toValidName(string str)
69 	{
70 		str = replace(str, regex("[^a-zA-Z0-9]"), "_");
71 /+		std.algorithm.map!(
72 			(char a)
73 		    { return inPattern(a, [digits, letters])? a : '_'; }
74 		)(str);
75 +//+		str.map
76 		(
77 			(char a)
78 		    { return inPattern(a, [digits, letters])? a : '_'; }
79 		);+/
80 
81 		if(str.length > 0 && !inPattern(str[0], [digits, letters]) && str[0] != '_')
82 			str = "_"~str;
83 		
84 		return str;
85 	}
86 	
87 	override string indent()
88 	{
89 		return _indent;
90 	}
91 	
92 	override bool strip()
93 	{
94 		return _strip;
95 	}
96 	
97 	override string processData(string content, int nodeDepth)
98 	{
99 		string str = fullIndent(nodeDepth);
100 		str ~= "<![CDATA[";
101 		str ~= content;
102 		str ~= "]]>";
103 		str ~= newline;
104 		return str;
105 	}
106 	
107 	override string processComment(string content, int nodeDepth)
108 	{
109 		string str = fullIndent(nodeDepth);
110 		str ~= "<!--";
111 		str ~= content;
112 		str ~= "-->";
113 		str ~= newline;
114 		return str;
115 	}
116 	
117 	override string processAttribute(string name, string value, int nodeDepth)
118 	{
119 		string str = " ";
120 		str ~= toValidName(name);
121 		str ~= `="`;
122 		str ~= value;
123 		str ~= `"`;
124 		return str;
125 	}
126 	
127 	override string processNode(string name, string attributes, string content, int nodeDepth)
128 	{
129 		auto formatStr =
130 			(content=="")? 
131 			"%1$s<%3$s%4$s />%2$s"
132 			:
133 			("%1$s<%3$s%4$s>%2$s"~
134 			"%5$s"~
135 			"%1$s</%3$s>%2$s");
136 		
137 		auto currFullIndent = fullIndent(nodeDepth);
138 		auto nl = newline();
139 		auto validName = toValidName(name);
140 		
141 		auto str = currFullIndent;
142 		str ~= "<";
143 		str ~= validName;
144 		str ~= attributes;
145 		if(content=="")
146 		{
147 			str ~= " />";
148 			str ~= nl;
149 		}
150 		else
151 		{
152 			str ~= ">";
153 			str ~= nl;
154 			str ~= content;
155 			str ~= currFullIndent;
156 			str ~= "</";
157 			str ~= validName;
158 			str ~= ">";
159 			str ~= nl;
160 		}
161 
162 		return str;
163 	}
164 	
165 	override string reduceAttributes(string[] attributes, int nodeDepth)
166 	{
167 		string str;
168 		foreach(attr; attributes)
169 			str ~= attr;
170 		return str;
171 		//return reduce!(`a~b`)(attributes);
172 //		return attributes.reduce!(`a~" "~b`)(); // Don't work
173 	}
174 	
175 	override string reduceNodes(string[] nodes, int nodeDepth)
176 	{
177 		string str;
178 		foreach(node; nodes)
179 			str ~= node;
180 		return str;
181 		//return reduce!(`a~b`)(nodes);
182 	}
183 	
184 	override string finalize(string content)
185 	{
186 		return content;
187 	}
188 }
189 
190 class JSONFormatter(bool _strip, string _indent="\t") : TreeFormatter
191 {
192 	override string fullIndent(int nodeDepth)
193 	{
194 		return super.fullIndent(nodeDepth+1);
195 	}
196 
197 	override string indent()
198 	{
199 		return _indent;
200 	}
201 	
202 	override bool strip()
203 	{
204 		return _strip;
205 	}
206 	
207 	string processString(string content)
208 	{
209 		content = content.replace(`\`, `\\`).replace(`"`, `\"`);
210 		string str = `"`;
211 		str ~= content;
212 		str ~= `"`;
213 		return str;
214 	}
215 	
216 	string processList(string[] elements, int nodeDepth)
217 	{
218 		if(elements.length==0)
219 			return "";
220 		
221 		string str;
222 		string sep = ", ";
223 		sep ~= newline;
224 		sep ~= fullIndent(nodeDepth);
225 		foreach(i, elem; elements)
226 		{
227 			if(i != 0)
228 				str ~= sep;
229 			str ~= elem;
230 		}
231 		return str;
232 	}
233 	
234 	string processPair(string name, string content)
235 	{
236 		string str = processString(name);
237 		str ~= ": ";
238 		str ~= content;
239 		return str;
240 	}
241 	
242 	override string processComment(string content, int nodeDepth)
243 	{
244 		return "";
245 	}
246 
247 	override string processData(string content, int nodeDepth)
248 	{
249 		return processString(content);
250 	}
251 	
252 	override string processAttribute(string name, string value, int nodeDepth)
253 	{
254 		return processPair(name, processString(value));
255 	}
256 	
257 	override string processNode(string name, string attributes, string content, int nodeDepth)
258 	{
259 		return processNode(name, attributes, content, nodeDepth, false);
260 	}
261 	
262 	string processNode(string name, string attributes, string content, int nodeDepth, bool nameless)
263 	{
264 		string attrAndContent = "";
265 		if(attributes != "" && content == "")
266 		{
267 			attrAndContent = fullIndent(nodeDepth+1);
268 			attrAndContent ~= attributes;
269 			attrAndContent ~= newline;
270 		}
271 		else if(attributes == "" && content != "")
272 		{
273 			attrAndContent = fullIndent(nodeDepth+1);
274 			attrAndContent ~= content;
275 			attrAndContent ~= newline;
276 		}
277 		else if(attributes != "" && content != "")
278 		{
279 			attrAndContent = fullIndent(nodeDepth+1);
280 			attrAndContent ~= attributes;
281 			attrAndContent ~= ", ";
282 			attrAndContent ~= newline;
283 			attrAndContent ~= fullIndent(nodeDepth+1);
284 			attrAndContent ~= content;
285 			attrAndContent ~= newline;
286 		}
287 
288 		name = nameless? "" : processString(name)~": ";
289 		
290 		string str = name;
291 		str ~= "{";
292 		str ~= newline;
293 		str ~= attrAndContent;
294 		str ~= fullIndent(nodeDepth);
295 		str ~= "}";
296 		return str;
297 	}
298 	
299 	override string reduceAttributes(string[] attributes, int nodeDepth)
300 	{
301 		return processList(attributes, nodeDepth+1);
302 	}
303 	
304 	override string reduceNodes(string[] nodes, int nodeDepth)
305 	{
306 		return processList(nodes, nodeDepth+1);
307 	}
308 	
309 	override string finalize(string content)
310 	{
311 		return processNode("", "", content, -1, true);
312 	}
313 }
314 
315 TreeFormatter formatterTrimmedXML;
316 TreeFormatter formatterPrettyXML;
317 TreeFormatter formatterTrimmedJSON;
318 TreeFormatter formatterPrettyJSON;
319 static this()
320 {
321 	formatterTrimmedXML  = new XMLFormatter !(true);
322 	formatterPrettyXML   = new XMLFormatter !(false);
323 	formatterTrimmedJSON = new JSONFormatter!(true);
324 	formatterPrettyJSON  = new JSONFormatter!(false);
325 }
326 
327 abstract class TreeNodeBase
328 {
329 	abstract string toString(TreeFormatter formatter, string content, int nodeDepth);
330 }
331 
332 /// NOTE: Might not be implemented correctly by the formatters, atm.
333 class TreeNodeData : TreeNodeBase 
334 {
335 	string data;
336 	
337 	this(){}
338 	
339 	// ctor templates don't seem to work
340 /*	this(T)(T data)
341 	{
342 		string dataStr;
343 		
344 		static if(is(T:string))
345 			dataStr = data;
346 		else
347 			dataStr = format("%s", data);
348 		
349 		this.data = dataStr;
350 	}
351 */
352 	this(string data)
353 	{
354 		this.data = data;
355 	}
356 
357 	override string toString(TreeFormatter formatter, string content, int nodeDepth)
358 	{
359 		return formatter.processData(content, nodeDepth);
360 	}
361 }
362 
363 class TreeNodeComment : TreeNodeData
364 {
365 	this(){}
366 	this(string data)
367 	{
368 		super(data);
369 	}
370 
371 	override string toString(TreeFormatter formatter, string content, int nodeDepth)
372 	{
373 		return formatter.processComment(content, nodeDepth);
374 	}
375 }
376 
377 class TreeNode : TreeNodeBase
378 {
379 	string name;
380 	string[string] attributes;
381 	
382 	TreeNodeBase[] subNodes;
383 	
384 	this(string name, string[string] attributes)
385 	{
386 		this(name, cast(TreeNode)null, attributes);
387 	}
388 	
389 	this(string name, TreeNodeBase subNodes=null, string[string] attributes=null)
390 	{
391 		TreeNodeBase[] contentArray;
392 		if(subNodes !is null)
393 			contentArray ~= subNodes;
394 
395 		this(name, contentArray, attributes);
396 	}
397 	
398 	this(string name, TreeNodeBase[] subNodes, string[string] attributes=null)
399 	{
400 		mixin(initMember("name", "subNodes", "attributes"));
401 	}
402 	
403 	TreeNode addAttribute(T, U)(T name, U value)
404 	{
405 		string nameStr;
406 		string valueStr;
407 		
408 		static if(is(T==string))
409 			nameStr = name;
410 		else
411 			nameStr = to!(string)(name);
412 			
413 		static if(is(U==string))
414 			valueStr = value;
415 		else
416 			valueStr = to!(string)(value);
417 			
418 		attributes[nameStr] = valueStr;
419 		return this;
420 	}
421 	
422 	TreeNode addAttributes(string[string] attributes)
423 	{
424 		foreach(string key, string value; attributes)
425 			this.attributes[key] = value;
426 			
427 		return this;
428 	}
429 	
430 	TreeNode addContent(TreeNodeBase subNodes)
431 	{
432 		this.subNodes ~= subNodes;
433 		return this;
434 	}
435 	
436 	TreeNode addContent(TreeNodeBase[] subNodes)
437 	{
438 		foreach(TreeNodeBase node; subNodes)
439 			this.subNodes ~= node;
440 			
441 		return this;
442 	}
443 	
444 	string format(TreeFormatter formatter)
445 	{
446 		return formatter.finalize(this.toString(formatter, "", 0));
447 	}
448 	
449 	override string toString(TreeFormatter formatter, string content, int nodeDepth)
450 	{
451 		auto reduceAttributes = &formatter.reduceAttributes;
452 		auto reduceNodes      = &formatter.reduceNodes;
453 		
454 		//alias ElementType!(typeof(attributes)) AttributeType;
455 		alias ElementType!(typeof(subNodes))   SubNodeType;
456 		alias string       AttributeType;
457 		//alias TreeNodeBase SubNodeType;
458 		
459 		auto attrStr =
460 			reduceAttributes(
461 				attributes
462 				.mapAAtoA((AttributeType val, AttributeType key) {
463 					return formatter.processAttribute(key, val, nodeDepth);
464 				}),
465 				nodeDepth
466 			);
467 		
468 		auto contentStr =
469 			reduceNodes(
470 				subNodes
471 				.map((SubNodeType a){
472 					return a.toString(formatter, "", nodeDepth+1);
473 				}),
474 				nodeDepth
475 			);
476 		
477 		return formatter.processNode(name, attrStr, contentStr, nodeDepth);
478 	}
479 }