Pman.Gnumeric.js
[Pman.Core] / Pman.Gnumeric.js
1 //<script type="text/javascript">
2 /**
3 * @class Pman Gnumeric.
4 *-> load up a remote xml file of a gnumeric document.
5
6 * -> convert into a usable data structure
7
8 * -> ?? apply templated values ??
9 * -> allow modification of fields
10
11 * -> render to screen.
12
13 * -> send for conversion to XLS (via ssconvert)
14
15 * Usage:
16 <pre><code>
17
18     new Pman.Gnumeric( {
19       url: rootURL + '/xxx/yyy/templates/reports/myreport.xml',
20       data: { ..... },
21       listeners : {
22           load : function()
23           {
24           
25                x.applyData({ ... }); // key value data looks for {value} in strings and replaces it..
26                
27                x.set('A3', 'test');
28                
29                mypanel.update(x.toHTML());
30                
31                x.download()       
32                
33            }
34       }
35     });
36     
37
38 </code></pre>
39
40 * @constructor
41 * @param {Object} cfg   Configuration object.
42 */
43  
44
45
46 Pman.Gnumeric = function (cfg)
47 {
48     cfg.data = cfg.data || {};
49     
50     
51     
52     
53     
54     
55     this.addEvents({
56         /**
57              * @event load
58              * Fires when source document has been loaded
59              * @param {Pman.Gnumerci} this
60              */
61             "load" : true
62     }); 
63     
64     Roo.util.Observable.call(this,cfg);
65     
66     this.defaultCell = {
67         c : 0,
68         r : 0,
69         valueType : 0,
70         valueFormat : '',
71         value : '',
72         colspan: 1,
73         rowspan: 1
74           
75     };
76      
77     this.load();
78     
79    
80     
81     
82 }
83 Roo.extend(Pman.Gnumeric, Roo.util.Observable, {
84     
85     /**
86      * @cfg {String} url the source of the Gnumeric document.
87      */
88     url : false,
89       /**
90      * @cfg {Object} data overlay data for spreadsheet - from constructor.
91      */
92     data : false,
93      /**
94      * @cfg {String} downloadURL where GnumerictoExcel.php is...
95      */
96      
97     downloadURL : false,
98     
99     /**
100      * @type {XmlDocument} doc the gnumeric xml document
101      */
102     doc : false,
103     
104     /**
105      * @type {XmlNode} sheet the 'Sheet' element 
106      */
107     sheet : false,
108     
109     /**
110      * @type {XmlNode} sheet the 'Cells' element 
111      */    
112     cellholder : false
113     /**
114      * @type {Object} grid the map[row][col] = cellData 
115      */
116     grid : false,
117     /**
118      * @type {Object} colInfo - list of column sizes
119      */
120     colInfo : false,
121     /**
122      * @type {Object} rowInfo - list of row sizes
123      */
124     rowInfo : false,
125     
126     /**
127      * @type {Number} cmax - maximum number of columns
128      */
129     cmax: false,
130     /**
131      * @type {Object} rmax - maximum number of rows
132      */
133     rmax : false,
134        /**
135      * @type {String} stylesheetID id of stylesheet created to render spreadsheat
136      */
137     stylesheetID : false,
138     /**
139      * load:
140      * run the connection, parse document and fire load event..
141      * can be run multiple times with new data..
142      * 
143     */
144     
145     load : function(url)
146     {
147         this.url = url || this.url;
148         if (!this.url) {
149             return;
150         }
151         // reset stufff..
152         this.doc = false;
153         this.sheet = false;
154         this.grid = false;
155         this.colInfo = false;
156         this.rowInfo = false;
157         this.cmax = false;
158         this.rmax = false;
159         
160         if (this.stylesheetID) {
161             
162             Roo.util.CSS.removeStyleSheet(this.stylesheetID);
163             this.stylesheetID = false;
164             
165         }
166         
167         _t = this;
168         var c = new Roo.data.Connection();
169         c.request({
170             url: this.url,
171             method:  'GET',
172             success : function(resp, opts) {
173                 _t.response = resp;
174                 _t.doc = resp.responseXML;
175                 _t.parseDoc();
176                 _t.parseStyles();
177                 _t.overlayStyles();
178                 _t.applyData();
179     
180                 _t.fireEvent('load', _t);
181             },
182             failure : function()
183             {
184                 Roo.MessageBox.alert("Error", "Failed to Load Template for Spreadsheet");
185             }
186         });
187         
188
189     },
190     
191     
192      
193     
194     
195     /**
196      * toRC:
197      * convert 'A1' style position to row/column reference
198      * 
199      * @arg {String} k cell name
200      * @return {Object}  as { r: {Number} , c: {Number}  }
201      */
202     
203     toRC : function(k)
204     {
205           var c = k.charCodeAt(0)-64;
206         var n = k.substring(1);
207         if (k.charCodeAt(1) > 64) {
208             c *=26;
209             c+=k.charCodeAt(1)-64;
210             n = k.substring(2);
211         }
212         return { c:c -1 ,r: (n*1)-1 }
213     },
214       /**
215      * rangeToRC:
216      * convert 'A1:B1' style position to array of row/column references
217      * 
218      * @arg {String} k cell range
219      * @return {Array}  as [ { r: {Number} , c: {Number}  }. { r: {Number} , c: {Number}  } ]
220      */
221     rangeToRC : function(s) {
222         var ar = s.split(':');
223         return [ this.toRC(ar[0]) , this.toRC(ar[1])]
224     },
225     
226     
227     
228    
229     
230     /**
231      * parseDoc:
232      * convert XML document into cells and other data..
233      * 
234      */
235     parseDoc : function() 
236     {
237         var _t = this;
238         this.grid = {}
239         this.rmax = 1;
240         this.cmax = 1;
241         
242         this.sheet = _t.doc.getElementsByTagNameNS('*','Sheet')[0];
243         
244         
245         this.cellholder = this.sheet.getElementsByTagNameNS('*','Cells');
246         var cells = this.sheet.getElementsByTagNameNS('*','Cell');
247
248         
249         
250         Roo.each(cells, function(c) {
251            // Roo.log(c);
252             var row = c.getAttribute('Row') * 1;
253             var col = c.getAttribute('Col') * 1;
254             _t.cmax = Math.max(col+1, _t.cmax);
255             _t.rmax = Math.max(row+1, _t.rmax);
256             var vt = c.getAttribute('ValueType');
257             var vf = c.getAttribute('ValueFormat');
258             var val = c.textContent;
259             
260             if (typeof(_t.grid[row]) == 'undefined') {
261                 _t.grid[row] ={};
262             }
263             _t.grid[row][col] = Roo.applyIf({
264                 valueType : vt,
265                 valueFormat : vf,
266                 value : val,
267                 dom: c,
268                 r: row,
269                 c: col
270             }, _t.defaultCell);
271         });
272        
273         for (var r = 0; r < this.rmax;r++) {
274             if (typeof(this.grid[r]) == 'undefined') {
275               this.grid[r] ={};
276             }
277             for (var c = 0; c < this.cmax;c++) {
278                 if (typeof(this.grid[r][c]) == 'undefined') {
279                     continue;
280                 }
281                 //this.print( "[" + r + "]["+c+"]=" + grid[r][c].value +'<br/>');
282             }
283         }
284         
285         var merge = this.sheet.getElementsByTagNameNS('*','Merge');
286
287         Roo.each(merge, function(c) {
288             var rc = _t.rangeToRC(c.textContent);
289             //Roo.log(JSON.stringify(rc))
290             if (typeof(_t.grid[rc[0].r][rc[0].c]) == 'undefined') {
291                 _t.grid[rc[0].r][rc[0].c] =  Roo.applyIf({ r : rc[0].r, c : rc[0].c }, _t.defaultCell);
292             }
293                 
294             _t.grid[rc[0].r][rc[0].c].colspan = (rc[1].c - rc[0].c) + 1;
295             _t.grid[rc[0].r][rc[0].c].rowspan = (rc[1].r - rc[0].r) + 1;
296             for(var r = (rc[0].r); r < (rc[1].r+1); r++) {
297                for(var c = rc[0].c; c < (rc[1].c+1); c++) {
298                     //Roo.log('adding alias : ' + r+','+c);
299                    _t.grid[r][c] = _t.grid[rc[0].r][rc[0].c];
300                }
301            }
302             
303             
304         });
305         // read colinfo..
306         var ci = this.sheet.getElementsByTagNameNS('*','ColInfo');
307         this.colInfo = {};
308         
309         Roo.each(ci, function(c) {
310             var count = c.getAttribute('Count') || 1;
311             var s =  c.getAttribute('No')*1;
312             for(var i =0; i < count; i++) {
313                 _t.colInfo[s+i] = Math.floor(c.getAttribute('Unit')*1);
314             }
315         });
316         
317         
318         ci = this.sheet.getElementsByTagNameNS('*','RowInfo');
319         
320         this.rowInfo = {};
321         Roo.each(ci, function(c) {
322             var count = c.getAttribute('Count') || 1;
323             var s =  c.getAttribute('No')*1;
324             for(var i =0; i < count; i++) {
325                 _t.rowInfo[s+i] = Math.floor(c.getAttribute('Unit')*1);
326             }
327         });
328     
329         
330         
331      
332         
333     },
334      /**
335      * overlayStyles:
336      * put the style info onto the cell data.
337      * 
338      */
339     overlayStyles : function ()
340     {
341            // apply styles.
342         var _t = this;
343         Roo.each(this.styles, function(s) {
344        
345             for (var r = s.r; r < s.r1;r++) {
346                 if (typeof(_t.grid[r]) == 'undefined') {
347                    continue;
348                 }
349                 for (var c = s.c; c < s.c1;c++) {
350                     if (c > _t.cmax) continue;
351     
352                     if (typeof(_t.grid[r][c]) == 'undefined') _t.grid[r][c] = Roo.applyIf({ r: r , c : c }, _t.defaultCell);
353                     var g=_t.grid[r][c];
354                     if (typeof(g.cls) =='undefined') {
355                         g.cls = [];
356                         g.styles = [];
357                     }
358                     if (g.cls.indexOf(s.name)  > -1) continue;
359                     g.cls.push(s.name);
360                     g.styles.push(s.dom);
361                     
362                 }
363             }
364         });
365     },
366      /**
367      * parseStyles: 
368      *  read the style information
369      * generates a stylesheet for the current file
370      * this should be disposed of really.....
371      * 
372      */
373     parseStyles : function() {
374                 
375         var srs = this.sheet.getElementsByTagNameNS('*','StyleRegion');
376         var _t  = this;
377         var ent = {};
378         
379         var map =  {
380             HAlign : function(ent,v) { 
381                 ent['text-align'] = { '1' : 'left', '8': 'center', '4' : 'right'}[v] || 'left';
382             },
383             VAlign : function(ent,v) { 
384                 ent['vertical-align'] = { '1' : 'top', '4': 'middel', '8' : 'bottom'}[v]  || 'top'
385             },
386             Fore : function(ent,v) { 
387                 var col=[];
388                 Roo.each(v.split(':'), function(c) { col.push(Math.round(parseInt(c,16)/256)); })
389                 ent['color'] = 'rgb(' + col.join(',') + ')';
390             },
391             Back : function(ent,v) { 
392                 var col=[];
393                 Roo.each(v.split(':'), function(c) { col.push(Math.round(parseInt(c,16)/256)); })
394                 ent['background-color'] = 'rgb(' + col.join(',') + ')';
395             },
396             FontUnit : function(ent,v) { 
397                 ent['font-size'] = v + 'px';
398             },
399             FontBold : function(ent,v) { 
400                 if (v*1 < 1) return;
401                 ent['font-weight'] = 'bold';
402             },
403             FontItalic : function(ent,v) { 
404                 if (v*0 < 1) return;
405                 //ent['font-weight'] = 'bold';
406             },
407             FontName : function(ent,v) { 
408                 ent['font-family'] = v;
409             },
410             BorderStyle : function(ent,v) { 
411                 var vv  = v.split('-');
412                 ent['border-'+vv[0]+'-style'] = 'solid';
413                 ent['border-'+vv[0]+'-width'] = vv[1]+'px';
414             },
415             BorderColor : function(ent,v) { 
416                 var vv  = v.split('-');
417                 var col=[];
418                 Roo.each(vv[1].split(':'), function(c) { col.push(Math.round(parseInt(c,16)/256)); })
419                 ent['border-'+vv[0]+'-color'] = 'rgb(' + col.join(',') + ')';
420             }
421         }
422         function add(e, k, v) {
423             //Roo.log(k,v);
424             e.gstyle[k] = v;
425             if (typeof(map[k]) == 'undefined') {
426                 return;
427             }
428             map[k](e.style,v);    
429         }
430         var css = {};
431         var styles = [];
432         var sid= Roo.id();
433         
434         
435         Roo.each(srs, function(sr,n)
436         {
437             ent = {
438                 c : sr.getAttribute('startCol') *1,
439                 r : sr.getAttribute('startRow')*1,
440                 c1 : (sr.getAttribute('endCol')*1) +1,
441                 r1 : (sr.getAttribute('endRow')*1) +1,
442                 style : {},  // key val of style for HTML..
443                 gstyle : {}, // key val of attributes used..
444                 name : sid +'-gstyle-' + n,
445                 dom : sr
446                 
447             };
448     
449             Roo.each(sr.getElementsByTagNameNS('*','Style')[0].attributes, function(e) { 
450                 add(ent, e.name, e.value);
451             });
452             if (sr.getElementsByTagNameNS('*','Font').length) {
453                 Roo.each(sr.getElementsByTagNameNS('*','Font')[0].attributes, function(e) { 
454                      add(ent, 'Font'+e.name, e.value);
455     
456                 });
457                 add(ent, 'FontName', sr.getElementsByTagNameNS('*','Font')[0].textContent);
458     
459             }
460             if (sr.getElementsByTagNameNS('*','StyleBorder').length) {
461                 Roo.each(sr.getElementsByTagNameNS('*','StyleBorder')[0].childNodes, function(e) {
462                     if (!e.tagName) {
463                         return;
464                     }
465                     Roo.each(e.attributes, function(ea) { 
466                         add(ent, 'Border'+ea.name, e.tagName.split(':')[1].toLowerCase() + '-' + ea.value);
467                     });
468                 })
469                     
470             }
471             styles.push(ent);
472             css['.'+ent.name] = ent.style;
473         });
474         
475         this.styles = styles;
476         
477         this.stylesheetID = sid;
478         Roo.util.CSS.createStyleSheet(css, sid);
479     },
480
481     /* ---------------------------------------  AFTER LOAD METHODS... ----------------------- */
482     /**
483      * set: 
484      * Set the value of a cell..
485      * @param {String} cell name of cell, eg. C10
486      * @param {Value} value to put in cell..
487      * 
488      * Cells should exist at present, we do not make them up...
489      */
490      
491     
492     set : function(cell, v) {
493         
494         var cs= typeof(cell) == 'string' ? this.toRC(cell) : cell;
495         //Roo.log(    this.grid[cs.r][cs.c]);
496         // need to generate clell if it doe
497         if (typeof(this.grid[cs.r]) == 'undefined') {
498             Roo.log('no row:' + cell);
499             this.grid[cs.r] = []; // create a row..
500             //return;
501         }
502         if (typeof(this.grid[cs.r][cs.c]) == 'undefined') {
503             Roo.log('cell not defined:' + cell);
504         }
505         if (typeof(this.grid[cs.r][cs.c].dom) == 'undefined') {
506             Roo.log('no default content for cell:' + cell);
507             
508             
509             
510             
511             return;
512         }
513         this.grid[cs.r][cs.c].value=  v;
514
515         this.grid[cs.r][cs.c].dom.textContent=  v;
516     },
517     
518     
519     copyRow : function(src, dest) {
520         if (dest == src) {
521             return;
522         }
523        // Roo.log('create Row' + dest);
524         if (typeof(this.grid[dest]) == 'undefined') {
525             this.grid[dest] = {}
526         }
527         
528            
529         for (var c = 0; c < this.cmax; c++) {
530
531             this.copyCell({ r: src, c: c } , { r: dest, c: c});
532             
533         }
534         this.rmax = Math.max(this.rmax, dest +1);
535         
536     },
537     
538     createCell: function(r,c)
539     {
540         //<gnm:Cell Row="6" Col="5" ValueType="60">Updated</gnm:Cell>    
541         var nc = this.doc.createElement('gnm:Cell');
542         this.cellholder.appendChild(nc);
543         nc.setAttribute('Row', r);
544         nc.setAttribute('Col', c);
545         nc.setAttribute('ValueType', '60');
546         nc.textContent = '';
547         return nc;
548
549     },
550     
551     
552     copyCell : function(src, dest)
553     {
554         var old = this.grid[src.r][src.c];
555         // is it an alias...
556         if ((old.c != src.c)  || (old.r != src.r)) {
557             // only really works on horizonatal merges..
558             
559             this.grid[dest.r][dest.c] = this.grid[desc.r][old.c]; // let's hope it exists.
560             return;
561         }
562         
563         
564         var nc = Roo.apply({}, this.grid[src.r][src.c]);
565         
566         nc.value = '';
567         if (typeof(old.dom) == 'undefined') {
568             Roo.log("No cell to copy for " + Roo.encode(src));
569             return;
570         }
571         this.grid[dest.r][dest.c] = nc;
572         nc.dom = old.dom.cloneNode(true);
573         nc.dom.setAttribute('Row', dest.r);
574         nc.dom.setAttribute('Cell', dest.c);
575         nc.dom.textContent = '';
576         old.dom.parentNode.appendChild(nc.dom);
577         if (!old.styles || !old.styles.length) {
578             return;
579         }
580         //Roo.log("DEST");
581         //Roo.log(dest);
582         //Roo.log("STYLES");
583         //  .styles...
584         Roo.each(old.styles, function(s) {
585             // try and extend existing styles..
586             var er = s.getAttribute('endRow') * 1;
587             var ec = s.getAttribute('endCol') * 1;
588             //Roo.log(s);
589             if (dest.r == er) {
590                 s.setAttribute('endRow', dest.r + 1);
591             }
592             if (dest.c == ec) {
593                 s.setAttribute('endCol', dest.c + 1);
594             }
595             /*var ns = s.cloneNode(true);
596             s.parentNode.appendChild(ns);
597             ns.setAttribute('startCol', dest.c);
598             ns.setAttribute('startRow', dest.r);
599             ns.setAttribute('endCol', dest.c + 1);
600             ns.setAttribute('endRow', dest.r +1);
601             */
602         });
603         
604     },
605     
606     
607     /**
608      * applyData: 
609      * Set the value of a cell..
610      * @param {String} cell name of cell, eg. C10
611      * @param {Value} value to put in cell..
612      * 
613      * Cells should exist at present, we do not make them up...
614      */
615      
616     applyData : function(data)
617     {
618         
619         data = data || this.data;
620         for (var r = 0; r < this.rmax;r++) {
621             if (typeof(this.grid[r]) == 'undefined') continue;
622             for (var c = 0; c < this.cmax;c++) {  
623                 if (typeof(this.grid[r][c]) == 'undefined') {
624                     continue;
625                 }
626                 if (!this.grid[r][c].value.length 
627                         || !this.grid[r][c].value.match(/\{/)) {
628                     continue;
629                 }
630                 
631                 var x = new Roo.Template({ html: this.grid[r][c].value });
632                 try {
633                     var res = x.applyTemplate(data);
634                     //Roo.log("set " + r  + "," + c + ":"+res)
635                     this.set({ r: r, c: c}, x.applyTemplate(data));
636                 } catch (e) {
637                  //   Roo.log(e.toString());
638                   //  Roo.log(e);
639                     // continue?
640                 }
641                 
642             }
643         }
644             
645     },
646     
647     
648      /**
649      * toHTML: 
650      * Convert spreadsheet into a HTML table.
651      */
652             
653     toHTML :function()
654     {
655          var _t = this;
656         function calcWidth(sc, span)
657         {
658             var n =0;
659             for(var i =sc; i< sc+span;i++) {
660                 n+=_t.colInfo[i];
661             }   
662             return n;
663         }
664         
665         var grid = this.grid;
666         // lets do a basic dump..
667         var out = '<table style="table-layout:fixed;" cellpadding="0" cellspacing="0">';
668         for (var r = 0; r < this.rmax;r++) {
669             out += '<tr style="height:'+this.rowInfo[r]+'px;">';
670             for (var c = 0; c < this.cmax;c++) {
671                 var g = (typeof(grid[r][c]) == 'undefined') ? defaultCell  : grid[r][c];
672                 
673                 if (typeof(g.cls) =='undefined') g.cls = [];
674                 var w= calcWidth(c,g.colspan);
675                 out+=String.format('<td colspan="{0}" rowspan="{1}"  class="{4}"><div style="{3}">{2}</div></td>', 
676                     g.colspan, g.rowspan, g.value,
677                     'overflow:hidden;' + 
678                     'width:'+w+'px;' +
679                    
680                     'text-overflow:ellipsis;' +
681                     'white-space:nowrap;',
682                      g.cls.join(' ')
683     
684     
685                 );
686                 c+=(g.colspan-1);
687             }
688             out += '</tr>';
689         }
690         //Roo.log(out);
691         return out+'</table>';
692         
693         
694         
695     },
696     /**
697      * download:
698      * @param {String} name  filename to downlaod (without xls)
699      * @param {String} callback  (optional) - callback to call after callback is complete.
700      */
701     download : function(name,callback)
702     {
703         name = name || "Missing_download_filename";
704         
705         if (this.downloadURL && this.downloadURL.charAt(this.downloadURL .length-1) != '/') {
706             this.downloadURL += '/';
707         }
708         
709         var ser = new XMLSerializer();
710         var x = new Pman.Download({
711             method: 'POST',
712             params : {
713                xml : ser.serializeToString(this.doc),
714                format : 'xls', //xml
715                debug : 0
716                
717             },
718             url : (this.downloadURL || (baseURL + '/GnumericToExcel/')) + name + '.xls',
719             success : function() {
720                 Roo.MessageBox.alert("Alert", "File should have downloaded now");
721                 if (callback) {
722                     callback();
723                 }
724             }
725         });
726          
727     }
728
729 });