Fix #8004 - try and fix start line / end line cached node output
[roobuilder] / src / JsRender / NodeToJs.vala
1 /**
2  * 
3  * Code to convert node tree to Javascript...
4  * 
5  * usage : x = (new JsRender.NodeToJs(node)).munge();
6  * 
7  *
8  *  We are changing this to output as we go.
9  *   However... since line-endings on properties have ',' (not ;) like vala.
10  *           we have to be a bit smarter about how to output.
11  *
12  *   
13  *
14 */
15
16
17 namespace JsRender {
18
19         public class NodeToJs : NodeWriter {
20
21                 static uint indent = 1;
22                 static string indent_str = " ";
23                 
24                  
25                 Gee.ArrayList<string>  doubleStringProps;  // need to think if this is a good idea like this
26          
27          
28                   
29                 Gee.HashMap<string,string> out_props;
30                 Gee.HashMap<string,string> out_listeners;       
31                 Gee.HashMap<string,Node> out_nodeprops;
32                 Gee.ArrayList<Node> out_children;
33                 Gee.HashMap<string,Gee.ArrayList<Node>> out_props_array;
34                 Gee.HashMap<string,Gee.ArrayList<string>> out_props_array_plain;        
35                 
36
37          
38                 
39          
40
41                 public NodeToJs( JsRender file,  Node node,  string pad , NodeWriter? parent, Gee.ArrayList<string> doubleStringProps) 
42                  
43                 {
44                         base(file, node, pad.length, parent);
45                         this.doubleStringProps = doubleStringProps;
46                         
47                         this.pad = pad;  
48                         this.node.node_pad = pad;
49                          
50                         this.out_props = new Gee.HashMap<string,string>();
51                         this.out_listeners = new Gee.HashMap<string,string>();  
52                         
53                         
54                         this.out_nodeprops = new Gee.HashMap<string,Node>() ;
55                         this.out_children = new Gee.ArrayList<Node> ();
56                         
57                         this.out_props_array = new Gee.HashMap<string,Gee.ArrayList<Node>>(); // filled in by 'checkChildren'
58                         this.out_props_array_plain = new Gee.HashMap<string,Gee.ArrayList<string>>() ;
59                  
60                         this.cur_line = parent == null ? 0 : parent.cur_line  ; //-1 as we usuall concat onto the existin gline?
61                          
62                         this.output = "";
63                         this.top = parent == null ? this : parent.top;
64                         // reset the maps...
65                         if (parent == null) {
66                                 node.node_lines = new Gee.ArrayList<int>();
67                                 node.node_lines_map = new Gee.HashMap<int,Node>();
68                          }
69                 }
70                 
71                 
72                 
73                 
74                 
75                 
76                 public override string munge ( )
77                 {
78                         //return this.mungeToString(this.node);
79                         if (this.node.as_source_version > 0 && 
80                                 this.node.as_source_version == this.node.updated_count &&
81                                 this.node.as_source_start_line == cur_line &&
82                                 this.node.as_source != ""
83                                 
84                         ) {
85                                 return this.node.as_source;
86                         }
87                         this.node.as_source_start_line = cur_line;
88                         this.checkChildren();
89                         this.readProps();
90                         //this.readArrayProps();
91                         this.readListeners();
92
93                         if (!this.node.props.has_key("* xinclude")) {
94                                 this.iterChildren();
95                         }
96                         
97                         
98                         
99                         // no properties to output...
100                         //if (this.els.size < 1) {
101                         //      return "";
102                         //}
103
104                         this.mungeOut();
105                         
106                         this.node.as_source_version = this.node.updated_count;
107                         this.node.as_source == this.ret;
108                         return this.ret;
109
110                          
111                 } 
112                         /**
113                 
114                 This currently works by creating a key/value array of this.els, which is just an array of properties..
115                 this is so that join() works...
116                 
117                 how this could work:
118                 a) output header
119                 b) output plan properties.
120                 c) output listeners..
121                 c) output *prop
122                 g) output prop_arrays..
123                 d) output children
124                 e) 
125                 
126                 
127                 
128                 */
129                 
130                 public Gee.ArrayList<string> orderedPropKeys() {
131                 
132                         var ret = new Gee.ArrayList<string> ();
133                         var niter = this.out_props.map_iterator();
134                         while(niter.next()) {
135                                 ret.add(niter.get_key());
136                         }
137                         
138                         ret.sort((  a,  b) => {
139                                 return ((string)a).collate((string)b);
140                                 //if (a == b) return 0;
141                                 //return a < b ? -1 : 1;
142                         });
143                         return ret;
144                 }
145                 public Gee.ArrayList<string> orderedListenerKeys() {
146                 
147                         var ret = new Gee.ArrayList<string> ();
148                         var niter = this.out_listeners.map_iterator();
149                         while(niter.next()) {
150                                 ret.add(niter.get_key());
151                         }
152                         
153                         ret.sort((  a,  b) => {
154                                 return ((string)a).collate((string)b);
155                                 //if (a == b) return 0;
156                                 //return a < b ? -1 : 1;
157                         });
158                         return ret;
159                 }
160                 
161
162                 public string mungeOut()
163                 {
164                         this.node.line_start = this.cur_line;
165                         this.top.node.setNodeLine(this.cur_line, this.node);
166                         var spad = this.pad.substring(0, this.pad.length-indent);
167                         
168                         if (this.node.props.has_key("* xinclude")) {
169                                 this.addJsLine("Roo.apply(" + this.node.props.get("* xinclude").val + "._tree(), {",0 );
170                  
171                         } else {
172                                 this.addJsLine("{", 0);
173                         }
174                         var suffix = "";
175                         // output the items...
176                         // work out remaining items...
177                  
178                         // output xns / xtype first..
179                         if (this.out_props.has_key("xtype")) {
180                                 var v = this.out_props.get("xtype");
181                                  
182                                 this.node.setLine(this.cur_line, "p","xtype"); 
183                                 this.addJsLine(this.pad + "xtype" + " : " + v + suffix, ',');
184                         }
185                         
186                         // plain properties.
187                         var iter = this.orderedPropKeys().list_iterator();
188                         while(iter.next()) {
189          
190                                  
191                                 var k = iter.get();
192                                 if (k == "xns" || k == "xtype") {
193                                         continue;
194                                 }
195
196                                 var v = this.out_props.get(k);
197                                 this.node.setLine(this.cur_line, "p",k); 
198
199                                 this.addJsLine(this.pad + k + " : " + v + suffix, ',');
200
201                                 this.node.setLine(this.cur_line, "e", k);
202                                 
203                         }
204                  
205                         // listeners..
206                         
207                         if (this.out_listeners.size > 0 ) { 
208                                  
209                                 this.addJsLine(this.pad + "listeners : {", 0);
210                                 iter = this.orderedListenerKeys().list_iterator();
211                                  
212                                 while(iter.next()) {
213                                         
214                                         var k = iter.get();
215                                         var v = this.out_listeners.get(k);
216
217                                         this.node.setLine(this.cur_line, "l",k); //listener
218                                         this.addJsLine(this.pad + indent_str + k + " : " + v , ',');
219
220                                         this.node.setLine(this.cur_line, "x", k);
221                                 }
222                                 
223                                 this.closeLine();
224                                 this.addJsLine(this.pad + "}" ,',');
225                                 
226                         }
227                         
228                         //------- at this point it is the end of the code relating directly to the object..
229                         
230                         if (this.out_props.has_key("xns")) {
231                                 var v = this.out_props.get("xns");
232                                  
233                                 this.node.setLine(this.cur_line, "p","xns"); 
234                                 this.addJsLine(this.pad + "xns" + " : " + v + suffix, ',');
235                                 this.node.setLine(this.cur_line, "p","| xns"); 
236                                 this.addJsLine(this.pad + "'|xns' : '" + v + "'", ',');
237                                 this.node.setLine(this.cur_line, "e", "xns");
238                                  
239                         }
240                         
241                         this.node.line_end = this.cur_line;
242                         
243                         // * prop
244
245                         var niter = this.out_nodeprops.map_iterator();
246
247                         while(niter.next()) {
248
249                                 //print("add str: %s\n", addstr);
250                                 this.node.setLine(this.cur_line, "p",niter.get_key());
251                          
252                                 var addstr = this.mungeChildNew(this.pad + indent_str, niter.get_value());
253                                 this.addJsLine(this.pad + niter.get_key() + " : " + addstr, ',');
254                                         
255                                 this.node.setLine(this.cur_line, "e", "");
256                         }                        
257                         // prop arrays...
258                         
259                         var piter = this.out_props_array.map_iterator();
260
261                         while(piter.next()) {
262                                  
263                                 this.node.setLine(this.cur_line, "p",piter.get_key());
264                                 this.addJsLine(this.pad + piter.get_key() + " : [", 0);
265                                 
266                                 var pliter = piter.get_value().list_iterator();
267                                 while (pliter.next()) {
268                                         var addstr = this.mungeChildNew(this.pad + indent_str  + indent_str, pliter.get());
269                                         this.addJsLine(this.pad + indent_str + addstr, ',');
270                                         this.node.setLine(this.cur_line, "e", "");
271                                 }
272                                 this.closeLine();
273                                 
274                                 this.addJsLine(this.pad + "]" , ',');                   
275                          
276                         }       
277                         
278                         // children..
279                         if (this.out_children.size > 0) {
280                                 this.addJsLine(this.pad + "items  : [" , 0);
281                                 var cniter = this.out_children.list_iterator();
282                                 while (cniter.next()) {
283                                         suffix = cniter.has_next()  ? "," : "";
284                                         var addstr = this.mungeChildNew(this.pad + indent_str  + indent_str, cniter.get());
285                                         this.addJsLine(this.pad + indent_str + addstr, ',');
286                                         this.node.setLine(this.cur_line, "e", "");
287                                         
288                                 }
289                                 this.closeLine();
290                                 this.addJsLine(this.pad +   "]",',');
291                         }
292                         this.node.setLine(this.cur_line, "e", "");
293                         this.closeLine();
294                         if (this.node.props.has_key("* xinclude")) {
295                                 this.addJsLine(spad + "})",0);
296                  
297                         } else {
298                                 this.addJsLine( spad + "}", 0);
299                         }
300                         
301                         this.node.sortLines();
302                         
303                         
304                         
305                         return this.ret;
306                 
307                 }
308                 
309                 /**
310                 * Line endings
311                 *     if we end with a ','
312                 *
313                 */
314
315                 char last_line_end = '!'; 
316                 
317                 /**
318                 * add a line - note we will end up with an extra line break 
319                 *     at beginning of nodes doing this..
320                 *
321                 * @param str = text to add..
322                 * @param line_end = 0  (just add a line break)
323                 *        line_end = ','  and ","
324                 *  
325                 */
326                 public void addJsLine(string str, char line_end)
327                 {
328                         if (this.last_line_end != '!') {
329                                 this.output += (this.last_line_end == 0 ? "" : this.last_line_end.to_string()) + "\n"; 
330                         }
331                         this.last_line_end = line_end;
332                         this.cur_line += str.split("\n").length;
333                         this.output += str;
334                         
335                         
336                         //this.ret +=  "/*%d(%d-%d)*/ ".printf(this.cur_line -1, this.node.line_start,this.node.line_end) + str + "\n";
337                         
338                         
339                 }
340                 public void closeLine() // send this before '}' or ']' to block output of ',' ...
341                 {
342                         this.last_line_end = 0;
343                 }
344                 
345          
346                 public string mungeChildNew(string pad ,  Node cnode )
347                 {
348                         var x = new  NodeToJs( this.file, cnode,  pad , this, this.doubleStringProps); 
349
350                  
351                         x.munge();
352                         return x.ret;
353                 }
354                 
355                 /**
356                 * loop through items[] array see if any of the children have '* prop'
357                 * -- which means they are a property of this node.
358                 * -- ADD TO : this.opt_props_array  
359                 *
360                 */
361                 
362                 public void checkChildren () 
363                 {
364                         
365                          
366                         // look throught he chilren == looking for * prop.. -- fixme might not work..
367                         
368                         
369                         if (!this.node.hasChildren()) {
370                                 return;
371                         }
372                         // look for '*props'
373                    var items = this.node.readItems(); 
374                         for (var ii =0; ii< items.size; ii++) {
375                                 var pl =  items.get(ii);
376                                 if (!pl.props.has_key("* prop")) {
377                                         //newitems.add(pl);
378                                         continue;
379                                 }
380                                 
381                                 //print(JSON.stringify(pl,null,4));
382                                 // we have a prop...
383                                 //var prop = pl['*prop'] + '';
384                                 //delete pl['*prop'];
385                                 var prop = pl.get("* prop");
386                                 //print("got prop "+ prop + "\n");
387                                 
388                                 // name ends in [];
389                                 if (! Regex.match_simple("\\[\\]$", prop)) {
390                                         // it's a standard prop..
391                                         
392                                         // munge property..??
393                                         
394                                         this.out_nodeprops.set(prop, pl);
395                                          
396                                         continue;
397                                 }
398
399
400
401                                 
402                                 var sprop  = prop.replace("[]", "");
403                                 //print("sprop is : " + sprop + "\n");
404                                 
405                                 // it's an array type..
406                                 //var old = "";
407                                 if (!this.out_props_array.has_key(sprop)) {
408                                         this.out_props_array.set(sprop, new Gee.ArrayList<Node>());
409                                 }
410                                 
411                                  
412                                 this.out_props_array.get(sprop).add( pl);
413                                 //this.ar_props.set(sprop, nstr);
414                                  
415                                 
416                         }
417                          
418                 }
419                 /*
420          * Standardize this crap...
421          * 
422          * standard properties (use to set)
423          *          If they are long values show the dialog..
424          *
425          * someprop : ....
426          * bool is_xxx  :: can show a pulldown.. (true/false)
427          * string html  
428          * $ string html  = string with value interpolated eg. baseURL + ".." 
429          *  Clutter.ActorAlign x_align  (typed)  -- shows pulldowns if type is ENUM? 
430          * $ untypedvalue = javascript untyped value...  
431          * _ string html ... = translatable..
432
433          * 
434          * object properties (not part of the GOjbect being wrapped?
435          * # Gee.ArrayList<Xcls_fileitem> fileitems
436          * 
437          * signals
438          * @ void open 
439          * 
440          * methods -- always text editor..
441          * | void clearFiles
442          * | someJSmethod
443          * 
444          * specials
445          * * prop -- string
446          * * args  -- string
447          * * ctor -- string
448          * * init -- big string?
449          * 
450          * event handlers (listeners)
451          *   just shown 
452          * 
453          * -----------------
454          * special ID values
455          *  +XXXX -- indicates it's a instance property / not glob...
456          *  *XXXX -- skip writing glob property (used as classes that can be created...)
457          * 
458          * 
459          */
460                 public void readProps()
461                 {
462                         string left;
463                         Regex func_regex ;
464          
465                         try {
466                                 func_regex = new Regex("^\\s+|\\s+$");
467                         } catch (RegexError e) {
468                                 print("failed to build regex");
469                                 return;
470                         }
471                         // sort the key's so they always get rendered in the same order..
472                         
473                         var keys = new Gee.ArrayList<string>();
474                         var piter = this.node.props.map_iterator();
475                         while (piter.next() ) {
476                         
477
478                                 keys.add( piter.get_key()); // since are keys are nice and clean now..
479                         }
480                         
481                         keys.sort((  a,  b) => {
482                                 return ((string)a).collate((string)b);
483                                 //if (a == b) return 0;
484                                 //return a < b ? -1 : 1;
485                         });
486                         
487                         var has_cms = this.node.has("cms-id");
488                         
489                         for (var i = 0; i< keys.size; i++) {
490                                 var prop = this.node.get_prop(keys.get(i));
491                                 //("ADD KEY %s\n", key);
492                                 var k = prop.name;
493                                 var ktype  = prop.rtype;
494                                 var kflag = prop.ptype;
495                                 var v = prop.val;
496                                  
497                                 
498                                 //if (this.skip.contains(k) ) {
499                                 //      continue;
500                                 //}
501                                 if (  Regex.match_simple("\\[\\]$", k)) {
502                                         // array .. not supported... here?
503                                          
504                                 }
505                                 
506                                 string leftv = k;
507                                 // skip builder stuff. prefixed with  '.' .. just like unix fs..
508                                 //if (kflag == ".") { // |. or . -- do not output..
509                                 //      continue;
510                                 //}
511                                 if (kflag == NodePropType.SPECIAL) {
512                                         // ignore '* prop'; ??? 
513                                         continue;
514                                 }
515                                 
516                                 // handle cms-id // html
517                                 if (has_cms && k == "cms-id") {
518                                         continue; // ignore it...
519                                 }
520                                 // html must not be a dynamic property...
521                                 // note - we do not translate this either...
522                                 if (has_cms && k == "html" && kflag !=  NodePropType.RAW) {
523                                          
524
525                                         this.out_props.set("html", "Pman.Cms.content(" + 
526                                                 this.node.quoteString(this.file.name + "::" + this.node.get("cms-id")) +
527                                                  ", " +
528                                                 this.node.quoteString(v) +
529                                                  ")");
530                                                  
531                                         continue;        
532                                 }
533                                 
534                                 
535                                         
536                                 
537                                 if (Lang.isKeyword(leftv) || Lang.isBuiltin(leftv)) {
538                                         left = "'" + leftv + "'";
539                                 } else if (Regex.match_simple("[^A-Za-z_]+",leftv)) { // not plain a-z... - quoted.
540                                         var val = this.node.quoteString(leftv);
541                                         
542                                         left = "'" + val.substring(1, val.length-2).replace("'", "\\'") + "'";
543                                 } else {
544                                         left = leftv;
545                                 }
546                                  
547                                  
548                                 // next.. is it a function.. or a raw string..
549                                 if (
550                                         kflag == NodePropType.METHOD 
551                                         || 
552                                         kflag == NodePropType.RAW 
553                                         || 
554                                         ktype == "function" // ??? why woudl return type be function? << messed up..
555                                        
556                                         // ??? any others that are raw output..
557                                         ) {
558                                         // does not hapepnd with arrays.. 
559                                         if (v.length < 1) {  //if (typeof(el) == 'string' && !obj[i].length) { //skip empty.
560                                                 continue;
561                                         }
562                                         /*
563                                         print(v);
564                                         string str = "";
565                                         try {
566                                                 str = func_regex.replace(v,v.length, 0, "");
567                                         } catch(Error e) {
568                                                 print("regex failed");
569                                                 return "";
570                                         }
571                                         */
572                                         var str = v.strip();
573                                           
574                                         var lines = str.split("\n");
575                                         var nstr = "" + str;
576                                         if (lines.length > 0) {
577                                                 nstr =  string.joinv("\n" + this.pad, lines);
578                                                 //nstr =  string.joinv("\n", lines);
579                                         }
580                                         this.out_props.set(left, nstr);
581                                         
582                                         
583
584                                         
585                                         
586                                         //print("==> " +  str + "\n");
587                                         //this.els.add(left + " : "+  nstr);
588                                         continue;
589                                 }
590                                 // standard..
591                                 
592                                 
593                                 if (
594                                         Lang.isNumber(v) 
595                                         || 
596                                         Lang.isBoolean(v)
597                                         ||
598                                         ktype.down() == "boolean"
599                                                 || 
600                                         ktype.down() == "bool"
601                                         || 
602                                         ktype.down() == "number"
603                                         || 
604                                         ktype.down() == "int"
605                                         ) { // boolean or number...?
606                                         this.out_props.set(left, v.down());
607                                         //this.els.add(left + " : " + v.down() );
608                                         continue;
609                                 }
610                                 
611                                 // is it a translated string?
612                                 
613                                 
614                                 
615                                 
616                                 // strings..
617                                 // doubleStringProps is a list of keys like 'name' 'title' etc.. that we know can be translated..
618                            
619                                 if ((this.doubleStringProps.index_of(k) > -1) || 
620                                         (ktype.down() == "string" && k[0] == '_')  // strings starting with '_'
621                                 
622                                 ) {
623                                         // then use the translated version...
624                                         
625                                         var com = " /* " + 
626                                                 (v.split("\n").length > 1 ?
627                                                         ("\n" + this.pad +  string.joinv(this.pad +  "\n", v.split("\n")).replace("*/", "* - /") + "\n" + this.pad + "*/ ") :
628                                                         (v.replace("*/", "* - /") + " */")
629                                                 );
630                                         
631                                         //this.els.add(left + " : _this._strings['" + 
632                                         //      GLib.Checksum.compute_for_string (ChecksumType.MD5, v) +
633                                         //      "']"
634                                         //);
635                                         
636                                         // string is stored in Roo.vala
637                                         var  kname = GLib.Checksum.compute_for_string (ChecksumType.MD5, v.strip());
638
639                                         this.out_props.set(left, "_this._strings['" + kname + "']" + com);
640                                         continue;
641                                 }
642                          
643                                 // otherwise it needs to be encapsulated.. as single quotes..
644                                 
645                                 var vv = this.node.quoteString(v);
646                                 // single quote.. v.substring(1, v.length-1).replace("'", "\\'") + "'";
647                                 //this.els.add(left + " : " +  "'" + vv.substring(1, vv.length-2).replace("'", "\\'") + "'");
648                                 this.out_props.set(left,  "'" + vv.substring(1, vv.length-2).replace("'", "\\'") + "'");
649
650                            
651                            
652                            
653                         }
654                 }
655                  
656                 public void readListeners()
657                 {
658                         
659                         if (this.node.listeners.size < 1) {
660                                 return;
661                         }
662                         // munge the listeners.
663                         //print("ADDING listeners?");
664                 
665          
666                 
667                 
668                         var keys = new Gee.ArrayList<string>();
669                         var piter = this.node.listeners.map_iterator();
670                         while (piter.next() ) {
671                                  
672                                 keys.add(piter.get_key());
673                         }
674                         keys.sort((  a,  b) => {
675                                 return ((string)a).collate((string)b);
676                                 //if (a == b) return 0;
677                                 //return a < b ? -1 : 1;
678                         });
679                 
680                          
681                         for (var i = 0; i< keys.size; i++) {
682                                 var key = keys.get(i);
683                                 var val = this.node.listeners.get(key).val;
684                         
685                 
686                                  // 
687                                 var str = val.strip();
688                                 var lines = str.split("\n");
689                                 if (lines.length > 0) {
690                                         //str = string.joinv("\n" + this.pad + "           ", lines);
691                                         str = string.joinv("\n" + this.pad + indent_str + indent_str , lines);
692                                 }
693                                  
694                                 this.out_listeners.set(key.replace("|", "") ,str);
695                         
696                                 
697                         }
698                          
699                          
700
701                 }
702
703                 public void iterChildren()
704                 {
705                         
706                         var items = this.node.readItems();
707                         // finally munge the children...
708                         if (items.size < 1) {
709                                 return;
710                         }
711                         var itms = "items : [\n";
712                         //var n = 0;
713                         for(var i = 0; i < items.size;i++) {
714                                 var ele = items.get(i);
715                                 if (ele.props.has_key("* prop")) {
716                                         continue;
717                                 }
718                                  
719                                 this.out_children.add(ele);
720                                 
721                         }
722                         itms +=  "\n"+  this.pad + "]"  + "\n";
723                         //this.els.add(itms);
724                 }
725
726                         // finally output listeners...
727                         
728                 public void xIncludeToString()
729                 {
730                         
731
732                 }
733
734         }
735         
736 }
737         
738