1 module jin.tree; 2 3 import std.stdio; 4 import std.string; 5 import std.conv; 6 import std.outbuffer; 7 import std.algorithm; 8 import std.array; 9 import std.json; 10 import std.xml; 11 import std.file; 12 import std.path; 13 14 class Tree { 15 16 string name; 17 string value; 18 string baseUri; 19 size_t row; 20 size_t col; 21 Tree[] childs; 22 23 this( 24 string name , 25 string value , 26 Tree[] childs , 27 string baseUri = "" , 28 size_t row = 0 , 29 size_t col = 0 30 ) { 31 this.name = name; 32 this.value = value; 33 this.childs = childs; 34 this.baseUri = baseUri; 35 this.row = row; 36 this.col = col; 37 } 38 39 this ( 40 DirEntry input , 41 string baseUri = null , 42 size_t row = 1 , 43 size_t col = 1 44 ) { 45 if( baseUri is null ) baseUri = absolutePath( input.name ); 46 this( cast( string ) read( input.name ) , baseUri , row , col ); 47 } 48 49 this ( 50 File input , 51 string baseUri = null , 52 size_t row = 1 , 53 size_t col = 1 54 ) { 55 if( baseUri is null ) baseUri = input.name.absolutePath.asNormalizedPath.array; 56 this( cast( string ) read( input.name ) , baseUri , row , col ); 57 } 58 59 unittest { 60 assert( new Tree( "foo\nbar\n" , "" ).length == 2 ); 61 assert( new Tree( "foo\nbar\n" , "" )[1].name == "bar" ); 62 assert( new Tree( "foo\n\n\n" , "" ).length == 1 ); 63 64 assert( new Tree( "\\foo\n\\bar\n" , "" ).value == "foo\nbar" ); 65 assert( new Tree( "\\foo\n\\bar\n" , "" ).length == 0 ); 66 67 assert( new Tree( "foo bar \\pol" , "" )[0][0].value == "pol" ); 68 assert( new Tree( "foo bar\n\t\\pol\n\t\\men" , "" )[0][0].value == "pol\nmen" ); 69 } 70 this ( 71 string input , 72 string baseUri , 73 size_t row = 1 , 74 size_t col = 1 75 ) { 76 this( "" , null , [] , baseUri , row , col ); 77 Tree[] stack = [ this ]; 78 79 Tree parent = this; 80 while( input.length ) { 81 82 auto name = input.takeUntil( "\t\n \\" ); 83 if( name.length ) { 84 auto next = new Tree( name , null , [] , baseUri , row , col ); 85 parent.childs ~= next; 86 parent = next; 87 col += name.length + input.take( " " ).length; 88 continue; 89 } 90 if( !input.length ) break; 91 92 if( input[0] == '\\' ) { 93 auto value = input.takeUntil( "\n" )[ 1 .. $ ]; 94 if( parent.value is null ) parent.value = value; 95 else parent.value ~= "\n" ~ value; 96 } 97 if( !input.length ) break; 98 99 if( input[0] != '\n' ) { 100 throw new Exception( "Unexpected symbol (" ~ input[0] ~ ")" ); 101 } 102 input = input[ 1 .. $ ]; 103 col = 1; 104 row += 1; 105 106 auto indent = input.take( "\t" ).length; 107 col = indent; 108 if( indent > stack.length ) { 109 throw new Exception( "Too many TABs " ~ row.to!string ~ ":" ~ col.to!string ); 110 } 111 112 stack ~= parent; 113 stack.length = indent + 1; 114 parent = stack[ indent ]; 115 } 116 } 117 118 Tree make( 119 string name = null , 120 string value = null , 121 Tree[] childs = null , 122 string baseUri = null , 123 size_t row = 0 , 124 size_t col = 0 125 ) { 126 return new Tree( 127 name ? name : this.name , 128 value ? value : this.value , 129 childs ? childs : this.childs , 130 baseUri ? baseUri : this.baseUri , 131 row ? row : this.row , 132 col ? col : this.col 133 ); 134 } 135 136 static Tree fromJSON( string json ) { 137 return Tree.fromJSON( parseJSON( json ) ); 138 } 139 static Tree fromJSON( JSONValue json ) { 140 switch( json.type ) { 141 case JSON_TYPE.FALSE : 142 return new Tree( "false" , "" , [] ); 143 case JSON_TYPE.TRUE : 144 return new Tree( "true" , "" , [] ); 145 case JSON_TYPE.NULL : 146 return new Tree( "null" , "" , [] ); 147 case JSON_TYPE.FLOAT : 148 return new Tree( "float" , json.floating.to!string , [] ); 149 case JSON_TYPE.INTEGER : 150 return new Tree( "int" , json.integer.to!string , [] ); 151 case JSON_TYPE.UINTEGER : 152 return new Tree( "int" , json.uinteger.to!string , [] ); 153 case JSON_TYPE.STRING : 154 return new Tree( "string" , json.str , [] ); 155 case JSON_TYPE.ARRAY : 156 return new Tree( "list" , "" , json.array.map!( json => Tree.fromJSON( json ) ).array ); 157 case JSON_TYPE.OBJECT : 158 Tree[] childs = []; 159 foreach( key , value ; json.object ) { 160 childs ~= new Tree( "*" , key , [ new Tree( ":" , "" , [ Tree.fromJSON( value ) ] ) ] ); 161 } 162 return new Tree( "dict" , "" , childs ); 163 default: 164 throw new Error( "Unsupported type: " ~ json.type ); 165 } 166 } 167 168 static Tree fromXML( string xml ) { 169 return Tree.fromXML( new Document( xml ) ); 170 } 171 static Tree fromXML( Item xml ) { 172 173 auto el = cast( Element ) xml; 174 if( el ) { 175 Tree[] attrs; 176 foreach( key , val ; el.tag.attr ){ 177 attrs ~= new Tree( "@" , "" , [ new Tree( key , val ) ] ); 178 } 179 auto childs = el.items.map!( Tree.fromXML ).filter!( a => a ).array; 180 return new Tree( el.tag.name , "" , attrs ~ childs ); 181 } 182 183 auto com = cast( Comment ) xml; 184 if( com ) { 185 return new Tree( "--" , com.to!string[ 4 .. $ - 3 ] , [] ); 186 } 187 188 auto txt = cast( Text ) xml; 189 if( txt ) { 190 if( txt.to!string.all!( isSpace ) ) return null; 191 return new Tree( "'" , com.to!string , [] ); 192 } 193 194 throw new Error( "Unsupported node type!" ); 195 } 196 197 OutputType pipe( 198 OutputType 199 ) ( 200 OutputType output , 201 string prefix = "" 202 ) { 203 if( this.name.length ) output.write( this.name ~ " " ); 204 205 auto chunks = this.value.length ? this.value.split( "\n" ) : []; 206 207 if( chunks.length + this.childs.length == 1 ) { 208 if( chunks.length ) output.write( "\\" ~ chunks[0] ~ "\n" ); 209 else childs[0].pipe( output , prefix ); 210 } else { 211 output.write( "\n" ); 212 if( this.name.length ) prefix ~= "\t"; 213 214 foreach( chunk ; chunks ) output.write( prefix ~ "\\" ~ chunk ~ "\n" ); 215 216 foreach( child ; this.childs ) { 217 output.write( prefix ); 218 child.pipe( output , prefix ); 219 } 220 } 221 222 return output; 223 } 224 225 override string toString() { 226 OutBuffer buf = new OutBuffer; 227 this.pipe( buf ); 228 return buf.to!string; 229 } 230 231 Tree expand() { 232 return this.make( null , null , [ new Tree( "@" , this.uri , [] ) ] ~ this.childs.map!( child => child.expand ).array ); 233 } 234 235 string uri( ) { 236 return this.baseUri ~ "#" ~ this.row.to!string ~ ":" ~ this.col.to!string; 237 } 238 239 unittest { 240 auto tree = new Tree( "foo \\1\nbar \\2" , "" ); 241 assert( tree["bar"][0].to!string == "bar \\2\n" ); 242 } 243 auto opIndex( string path ) { 244 return this[ path.split( " " ) ]; 245 } 246 247 unittest { 248 assert( new Tree( "foo bar \\2" , "" )[ [ "foo" , "bar" ] ].to!string == "bar \\2\n" ); 249 } 250 auto opIndex( string[] path ) { 251 Tree[] next = [ this ]; 252 foreach( string name ; path ) { 253 if( !next.length ) break; 254 Tree[] prev = next; 255 next = []; 256 foreach( Tree item ; prev ) { 257 foreach( Tree child ; item.childs ) { 258 if( child.name != name ) continue; 259 next ~= child; 260 } 261 } 262 } 263 return new Tree( "" , "" , next ); 264 } 265 266 Tree opIndex( size_t index ) { 267 return this.childs[ index ]; 268 } 269 270 Tree[] opSlice( size_t start , size_t end ) { 271 return this.childs[ start .. end ]; 272 } 273 274 size_t length( ) { 275 return this.childs.length; 276 } 277 278 size_t opDollar( ) { 279 return this.childs.length; 280 } 281 282 } 283 284 string take( ref string input , string symbols ) { 285 auto i = 0; 286 while( i < input.length ) { 287 auto symbol = input[i]; 288 if( symbols.indexOf( symbol ) == -1 ) { 289 break; 290 } else { 291 i += 1; 292 } 293 } 294 auto res = input[ 0 .. i ]; 295 input = input[ i .. $ ]; 296 return res; 297 } 298 299 string takeUntil( ref string input , string symbols ) { 300 auto i = 0; 301 while( i < input.length ) { 302 auto symbol = input[i]; 303 if( symbols.indexOf( symbol ) == -1 ) { 304 i += 1; 305 } else { 306 break; 307 } 308 } 309 auto res = input[ 0 .. i ]; 310 input = input[ i .. $ ]; 311 return res; 312 }