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 }