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} colInfoDom - column sizes dom element
123      */
124     colInfoDom : false,
125     /**
126      * @type {Object} rowInfo - list of row sizes
127      */
128     rowInfo : false,
129      /**
130      * @type {Object} rowInfoDom - dom elements with sizes
131      */
132     rowInfoDom : false,
133     /**
134      * @type {Number} cmax - maximum number of columns
135      */
136     cmax: false,
137     /**
138      * @type {Object} rmax - maximum number of rows
139      */
140     rmax : false,
141        /**
142      * @type {String} stylesheetID id of stylesheet created to render spreadsheat
143      */
144     stylesheetID : false,
145     /**
146      * @type {Number} rowOffset - used by table importer to enable multiple tables to be improted
147      */
148     
149     rowOffset : 0,
150     
151     /**
152      * load:
153      * run the connection, parse document and fire load event..
154      * can be run multiple times with new data..
155      * 
156     */
157     
158     load : function(url)
159     {
160         this.url = url || this.url;
161         if (!this.url) {
162             return;
163         }
164         // reset stufff..
165         this.doc = false;
166         this.sheet = false;
167         this.grid = false;
168         this.colInfo = false;
169         this.colInfoDom = false;
170         this.rowInfo = false;
171         this.rowInfoDom = false;
172         this.cmax = false;
173         this.rmax = false;
174         
175         if (this.stylesheetID) {
176             
177             Roo.util.CSS.removeStyleSheet(this.stylesheetID);
178             this.stylesheetID = false;
179             
180         }
181         
182         _t = this;
183         var c = new Roo.data.Connection();
184         c.request({
185             url: this.url,
186             method:  'GET',
187             success : function(resp, opts) {
188                 
189                 Roo.log('resp:');
190                 Roo.log(resp);
191                 _t.response = resp;
192                 _t.doc = resp.responseXML;
193                 
194                 _t.parseDoc(0);
195                 
196                 
197                 _t.applyData();
198     
199                 _t.fireEvent('load', _t);
200             },
201             failure : function()
202             {
203                 Roo.MessageBox.alert("Error", "Failed to Load Template for Spreadsheet");
204             }
205         });
206         
207
208     },
209     
210     
211      
212     RCtoCell : function(r,c)
213     {
214         // we wil only support AA not AAA
215         var top = Math.floor(c/26);
216         var bot = c % 26;
217         var cc = top > 0 ? String.fromCharCode('A'.charCodeAt(0) + top) : '';
218         cc += String.fromCharCode('A'.charCodeAt(0)  + bot);
219         return cc+'' +r;
220         
221     },
222     
223     /**
224      * toRC:
225      * convert 'A1' style position to row/column reference
226      * 
227      * @arg {String} k cell name
228      * @return {Object}  as { r: {Number} , c: {Number}  }
229      */
230     
231     toRC : function(k)
232     {
233         var c = k.charCodeAt(0)-64;
234         var n = k.substring(1);
235         if (k.charCodeAt(1) > 64) {
236             c *=26;
237             c+=k.charCodeAt(1)-64;
238             n = k.substring(2);
239         }
240         return { c:c -1 ,r: (n*1)-1 }
241     },
242       /**
243      * rangeToRC:
244      * convert 'A1:B1' style position to array of row/column references
245      * 
246      * @arg {String} k cell range
247      * @return {Array}  as [ { r: {Number} , c: {Number}  }. { r: {Number} , c: {Number}  } ]
248      */
249     rangeToRC : function(s) {
250         var ar = s.split(':');
251         return [ this.toRC(ar[0]) , this.toRC(ar[1])]
252     },
253     
254     
255     
256    
257     
258     /**
259      * parseDoc:
260      * convert XML document into cells and other data..
261      * 
262      */
263     parseDoc : function(sheetnum) 
264     {
265         var _t = this;
266         this.grid = {}
267         this.rmax = 1;
268         this.cmax = 1;
269         
270         this.sheet = _t.doc.getElementsByTagNameNS('*','Sheet')[sheetnum];
271         
272         
273         this.cellholder = this.sheet.getElementsByTagNameNS('*','Cells')[0];
274         var cells = this.sheet.getElementsByTagNameNS('*','Cell');
275
276         
277         
278         Roo.each(cells, function(c) {
279            // Roo.log(c);
280             var row = c.getAttribute('Row') * 1;
281             var col = c.getAttribute('Col') * 1;
282             _t.cmax = Math.max(col+1, _t.cmax);
283             _t.rmax = Math.max(row+1, _t.rmax);
284             var vt = c.getAttribute('ValueType');
285             var vf = c.getAttribute('ValueFormat');
286             var val = c.textContent;
287             
288             if (typeof(_t.grid[row]) == 'undefined') {
289                 _t.grid[row] ={};
290             }
291             _t.grid[row][col] = Roo.applyIf({
292                 valueType : vt,
293                 valueFormat : vf,
294                 value : val,
295                 dom: c,
296                 r: row,
297                 c: col
298             }, _t.defaultCell);
299         });
300        
301         for (var r = 0; r < this.rmax;r++) {
302             if (typeof(this.grid[r]) == 'undefined') {
303               this.grid[r] ={};
304             }
305             for (var c = 0; c < this.cmax;c++) {
306                 if (typeof(this.grid[r][c]) == 'undefined') {
307                     continue;
308                 }
309                 //this.print( "[" + r + "]["+c+"]=" + grid[r][c].value +'<br/>');
310             }
311         }
312         
313         var merge = this.sheet.getElementsByTagNameNS('*','Merge');
314
315         Roo.each(merge, function(c) {
316             var rc = _t.rangeToRC(c.textContent);
317             //Roo.log(JSON.stringify(rc));
318             if (typeof(_t.grid[rc[0].r][rc[0].c]) == 'undefined') {
319                 //Roo.log(["creating empty cell for  ",rc[0].r,  rc[0].c ]);
320                  _t.createCell(rc[0].r,  rc[0].c );
321                 //_t.grid[rc[0].r][rc[0].c] =  //Roo.applyIf({ r : rc[0].r, c : rc[0].c }, _t.defaultCell);
322             }
323                 
324             _t.grid[rc[0].r][rc[0].c].colspan = (rc[1].c - rc[0].c) + 1;
325             _t.grid[rc[0].r][rc[0].c].rowspan = (rc[1].r - rc[0].r) + 1;
326             for(var r = (rc[0].r); r < (rc[1].r+1); r++) {
327                for(var cc = rc[0].c; cc < (rc[1].c+1); cc++) {
328                     //Roo.log('adding alias : ' + r+','+c);
329                    _t.grid[r][cc] = _t.grid[rc[0].r][rc[0].c];
330                }
331            }
332             
333             
334         });
335         // read colinfo..
336         var ci = this.sheet.getElementsByTagNameNS('*','ColInfo');
337         this.colInfo = {};
338         this.colInfoDom = {};
339         
340         Roo.each(ci, function(c) {
341             var count = c.getAttribute('Count') || 1;
342             var s =  c.getAttribute('No')*1;
343             for(var i =0; i < count; i++) {
344                 _t.colInfo[s+i] = Math.floor(c.getAttribute('Unit')*1);
345                 _t.colInfoDom[s+i] = c;
346             }
347         });
348         
349         
350         ci = this.sheet.getElementsByTagNameNS('*','RowInfo');
351         
352         this.rowInfo = {};
353         this.rowInfoDom = {};
354         Roo.each(ci, function(c) {
355             var count = c.getAttribute('Count') || 1;
356             var s =  c.getAttribute('No')*1;
357             for(var i =0; i < count; i++) {
358                 _t.rowInfoDom[s+i] = c;
359                 _t.rowInfo[s+i] = Math.floor(c.getAttribute('Unit')*1);
360             }
361         });
362     
363         _t.parseStyles();
364         _t.overlayStyles();
365                 
366         
367      
368         
369     },
370      /**
371      * overlayStyles:
372      * put the style info onto the cell data.
373      * 
374      */
375     overlayStyles : function ()
376     {
377            // apply styles.
378         var _t = this;
379         Roo.each(this.styles, function(s) {
380        
381             for (var r = s.r; r < s.r1;r++) {
382                 if (typeof(_t.grid[r]) == 'undefined') {
383                    continue;
384                 }
385                 for (var c = s.c; c < s.c1;c++) {
386                     if (c > _t.cmax) {
387                         continue;
388                     }
389     
390                     if (typeof(_t.grid[r][c]) == 'undefined') {
391                         _t.createCell(r,c);
392                         //_t.grid[r][c] = Roo.applyIf({ r: r , c : c }, _t.defaultCell);
393                     }
394                     var g=_t.grid[r][c];
395                     if (typeof(g.cls) =='undefined') {
396                         g.cls = [];
397                         g.styles = [];
398                     }
399                     if (g.cls.indexOf(s.name)  > -1) {
400                        continue;
401                     }
402                     g.cls.push(s.name);
403                     g.styles.push(s.dom);
404                     
405                 }
406             }
407         });
408     },
409      /**
410      * parseStyles: 
411      *  read the style information
412      * generates a stylesheet for the current file
413      * this should be disposed of really.....
414      * 
415      */
416     parseStyles : function() {
417                 
418         var srs = this.sheet.getElementsByTagNameNS('*','StyleRegion');
419         var _t  = this;
420         var ent = {};
421         
422         var map =  {
423             HAlign : function(ent,v) { 
424                 ent['text-align'] = { '1' : 'left', '8': 'center', '4' : 'right'}[v] || 'left';
425             },
426             VAlign : function(ent,v) { 
427                 ent['vertical-align'] = { '1' : 'top', '4': 'middle', '8' : 'bottom'}[v]  || 'top'
428             },
429             Fore : function(ent,v) { 
430                 var col=[];
431                 Roo.each(v.split(':'), function(c) { col.push(Math.round(parseInt(c,16)/256)); });
432                 ent['color'] = 'rgb(' + col.join(',') + ')';
433             },
434             Back : function(ent,v) { 
435                 var col=[];
436                 Roo.each(v.split(':'), function(c) { col.push(Math.round(parseInt(c,16)/256)); });
437                 ent['background-color'] = 'rgb(' + col.join(',') + ')';
438             },
439             FontUnit : function(ent,v) { 
440                 ent['font-size'] = v + 'px';
441             },
442             FontBold : function(ent,v) { 
443                 if (v*1 < 1) { return; }
444                 ent['font-weight'] = 'bold';
445             },
446             FontItalic : function(ent,v) { 
447                 if (v*0 < 1) { return; }
448                 //ent['font-weight'] = 'bold';
449             },
450             FontName : function(ent,v) { 
451                 ent['font-family'] = v;
452             },
453             BorderStyle : function(ent,v) { 
454                 var vv  = v.split('-');
455                 ent['border-'+vv[0]+'-style'] = 'solid';
456                 ent['border-'+vv[0]+'-width'] = vv[1]+'px';
457             },
458             BorderColor : function(ent,v) { 
459                 var vv  = v.split('-');
460                 var col=[];
461                 Roo.each(vv[1].split(':'), function(c) { col.push(Math.round(parseInt(c,16)/256)); });
462                 ent['border-'+vv[0]+'-color'] = 'rgb(' + col.join(',') + ')';
463             }
464         };
465         function add(e, k, v) {
466             //Roo.log(k,v);
467             e.gstyle[k] = v;
468             if (typeof(map[k]) == 'undefined') {
469                 return;
470             }
471             map[k](e.style,v);    
472         }
473         var css = {};
474         var styles = [];
475         var sid= Roo.id();
476         
477         
478         Roo.each(srs, function(sr,n)
479         {
480             ent = {
481                 c : sr.getAttribute('startCol') *1,
482                 r : sr.getAttribute('startRow')*1,
483                 c1 : (sr.getAttribute('endCol')*1) +1,
484                 r1 : (sr.getAttribute('endRow')*1) +1,
485                 style : {},  // key val of style for HTML..
486                 gstyle : {}, // key val of attributes used..
487                 name : sid +'-gstyle-' + n,
488                 dom : sr
489                 
490             };
491     
492             Roo.each(sr.getElementsByTagNameNS('*','Style')[0].attributes, function(e) { 
493                 add(ent, e.name, e.value);
494             });
495             if (sr.getElementsByTagNameNS('*','Font').length) {
496                 Roo.each(sr.getElementsByTagNameNS('*','Font')[0].attributes, function(e) { 
497                      add(ent, 'Font'+e.name, e.value);
498     
499                 });
500                 add(ent, 'FontName', sr.getElementsByTagNameNS('*','Font')[0].textContent);
501     
502             }
503             if (sr.getElementsByTagNameNS('*','StyleBorder').length) {
504                 Roo.each(sr.getElementsByTagNameNS('*','StyleBorder')[0].childNodes, function(e) {
505                     if (!e.tagName) {
506                         return;
507                     }
508                     Roo.each(e.attributes, function(ea) { 
509                         add(ent, 'Border'+ea.name, e.tagName.split(':')[1].toLowerCase() + '-' + ea.value);
510                     });
511                 })
512                     
513             }
514             styles.push(ent);
515             css['.'+ent.name] = ent.style;
516         });
517         
518         this.styles = styles;
519         
520         this.stylesheetID = sid;
521         Roo.util.CSS.createStyleSheet(css, sid);
522     },
523
524     
525     
526     
527     /* ---------------------------------------  AFTER LOAD METHODS... ----------------------- */
528     /**
529      * set: 
530      * Set the value of a cell..
531      * @param {String} cell name of cell, eg. C10 or { c: 1, r :1 }
532          
533      * @param {Value} value to put in cell..
534      * @param {ValueType} type of value
535      * @param {ValueFormat} value format of cell
536      * 
537      * Cells should exist at present, we do not make them up...
538      */
539      
540     
541     set : function(cell, v, vt, vf) {
542         
543         var cs= typeof(cell) == 'string' ? this.toRC(cell) : cell;
544         
545         
546         Roo.log( cs.r+ ',' + cs.c + ' = '+ v);
547         // need to generate clell if it doe
548         if (typeof(this.grid[cs.r]) == 'undefined') {
549             Roo.log('no row:' + cell);
550             this.grid[cs.r] = []; // create a row..
551             //return;
552         }
553         if (typeof(this.grid[cs.r][cs.c]) == 'undefined') {
554             Roo.log('cell not defined:' + cell);
555             this.createCell(cs.r,cs.c);
556         }
557         // cell might not be rendered yet... so if we try and create a cell, it overrides the default formating..
558         
559         if (typeof(this.grid[cs.r][cs.c].dom) == 'undefined') {
560             Roo.log('no default content for cell:' + cell);
561             Roo.log(this.grid[cs.r][cs.c]);
562             //this.createCell(cs.r,cs.c);
563             //return;
564         }
565         this.grid[cs.r][cs.c].value=  v;
566         if (this.grid[cs.r][cs.c].dom) {
567             this.grid[cs.r][cs.c].dom.textContent=  v;
568         }
569         
570         
571         if (typeof(vt) != 'undefined') {
572             this.grid[cs.r][cs.c].valueType = vt;
573             this.grid[cs.r][cs.c].dom.setAttribute('ValueType', vt);
574             if (vt === '' || vt === false) { // value type is empty for formula's
575                 this.grid[cs.r][cs.c].dom.removeAttribute('ValueType');
576             }
577         }
578         if (typeof(vf) != 'undefined' && vf !== false) {
579             this.grid[cs.r][cs.c].valueFormat = vf;
580             this.grid[cs.r][cs.c].dom.setAttribute('ValueFormat', vf);
581             if (vf === '' || vf === false) { // value type is empty for formula's
582                 this.grid[cs.r][cs.c].dom.removeAttribute('ValueFormat');
583             }
584         }
585         
586     },
587     
588     // private
589     copyRow : function(src, dest) {
590         if (dest == src) {
591             return;
592         }
593        // Roo.log('create Row' + dest);
594         if (typeof(this.grid[dest]) == 'undefined') {
595             this.grid[dest] = {}
596         }
597         
598            
599         for (var c = 0; c < this.cmax; c++) {
600
601             this.copyCell({ r: src, c: c } , { r: dest, c: c});
602             
603         }
604         this.rmax = Math.max(this.rmax, dest +1);
605         
606     },
607     
608     // private
609     
610     createCell: function(r,c)
611     {
612         //<gnm:Cell Row="6" Col="5" ValueType="60">Updated</gnm:Cell>    
613         var nc = this.doc.createElementNS('http://www.gnumeric.org/v10.dtd', 'gnm:Cell');
614         this.cellholder.appendChild(nc);
615         var lb = this.doc.createTextNode("\n");// add a line break..
616         this.cellholder.appendChild(lb);
617         
618         nc.setAttribute('Row', new String(r));
619         nc.setAttribute('Col', new String(c));
620         nc.setAttribute('ValueType', '60');
621         nc.textContent = '';
622         
623         this.grid[r][c] = Roo.applyIf({
624             valueType : '60',
625             valueFormat : '',
626             value : '',
627             dom: nc,
628             r: r,
629             c: c
630             }, _t.defaultCell);
631         
632         return nc;
633
634     },
635     
636     // private
637     copyCell : function(src, dest)
638     {
639         var old = this.grid[src.r][src.c];
640         // is it an alias...
641         if ((old.c != src.c)  || (old.r != src.r)) {
642             // only really works on horizonatal merges..
643             
644             this.grid[dest.r][dest.c] = this.grid[desc.r][old.c]; // let's hope it exists.
645             return;
646         }
647         
648         
649         var nc = Roo.apply({}, this.grid[src.r][src.c]);
650         
651         nc.value = '';
652         if (typeof(old.dom) == 'undefined') {
653             Roo.log("No cell to copy for " + Roo.encode(src));
654             return;
655         }
656         this.grid[dest.r][dest.c] = nc;
657         nc.dom = old.dom.cloneNode(true);
658         nc.dom.setAttribute('Row', dest.r);
659         nc.dom.setAttribute('Cell', dest.c);
660         nc.dom.textContent = '';
661         old.dom.parentNode.appendChild(nc.dom);
662         if (!old.styles || !old.styles.length) {
663             return;
664         }
665         //Roo.log("DEST");
666         //Roo.log(dest);
667         //Roo.log("STYLES");
668         //  .styles...
669         Roo.each(old.styles, function(s) {
670             // try and extend existing styles..
671             var er = s.getAttribute('endRow') * 1;
672             var ec = s.getAttribute('endCol') * 1;
673             //Roo.log(s);
674             if (dest.r == er) {
675                 s.setAttribute('endRow', dest.r + 1);
676             }
677             if (dest.c == ec) {
678                 s.setAttribute('endCol', dest.c + 1);
679             }
680             /*var ns = s.cloneNode(true);
681             s.parentNode.appendChild(ns);
682             ns.setAttribute('startCol', dest.c);
683             ns.setAttribute('startRow', dest.r);
684             ns.setAttribute('endCol', dest.c + 1);
685             ns.setAttribute('endRow', dest.r +1);
686             */
687         });
688         
689     },
690     
691     
692     /**
693      * applyData: 
694      * Set the value of a cell..
695      * @param {String} cell name of cell, eg. C10
696      * @param {Value} value to put in cell..
697      * 
698      * Cells should exist at present, we do not make them up...
699      */
700      
701     applyData : function(data)
702     {
703         
704         data = data || this.data;
705         for (var r = 0; r < this.rmax;r++) {
706             if (typeof(this.grid[r]) == 'undefined') {
707                 continue;
708             }
709             for (var c = 0; c < this.cmax;c++) {  
710                 if (typeof(this.grid[r][c]) == 'undefined') {
711                     continue;
712                 }
713                 if (!this.grid[r][c].value.length 
714                         || !this.grid[r][c].value.match(/\{/)) {
715                     continue;
716                 }
717                 
718                 var x = new Roo.Template({ html: this.grid[r][c].value });
719                 try {
720                     var res = x.applyTemplate(data);
721                     //Roo.log("set " + r  + "," + c + ":"+res)
722                     this.set({ r: r, c: c}, x.applyTemplate(data));
723                 } catch (e) {
724                  //   Roo.log(e.toString());
725                   //  Roo.log(e);
726                     // continue?
727                 }
728                 
729             }
730         }
731             
732     },
733     
734     readTableData : function(table)
735     {
736         // read the first row.
737         var tds = Roo.get(table).select('tr').item(0).select('td');
738         var maxnc = 0;
739         
740         Roo.get(table).select('tr').each(function(trs) {
741             var nc = 0;
742            
743             trs.select('td').each(function(td) {
744                 var cs = td.dom.getAttribute('colspan');
745                 cs = cs ? cs * 1 : 1;
746                 nc += cs;
747             });
748             maxnc = Math.max(nc, maxnc);
749         });
750         
751         var tr = document.createElement('tr');
752         table.appendChild(tr);
753         var ar = {};
754         for (i =0; i < maxnc; i++) {
755             ar[i] = document.createElement('td');
756             tr.appendChild(ar[i]);
757         }
758         // find the left.
759         var ret = { cols : maxnc, pos : {} };
760         for (i =0; i < maxnc; i++) {
761             ret.pos[ Roo.get(ar[i]).getLeft()] =i;
762         }
763         ret.near = function(p) {
764             // which one is nearest..
765             
766             if (this.pos[p]) {
767                 return this.pos[p];
768             }
769             var prox = 100000;
770             var match = 0;
771             for(var i in this.pos) {
772                 var dis = Math.abs(p-i);
773                 if (dis < prox) {
774                     prox = dis;
775                     match = this.pos[i];
776                 }
777             }
778             return match;
779             
780         }
781         table.removeChild(tr);
782         return ret;
783     },
784     
785      
786    
787      
788     /**
789      * importTable: 
790      * Import a table and put it into the spreadsheet
791      * @param {HTMLTable} datagrid dom element of html table.
792      * @param {Number} xoff X offset to start rendering to
793      * @param {Number} yoff Y offset to start rendering to
794      **/
795      
796  
797     importTable : function (datagrid, xoff,yoff)
798     {
799         if (!datagrid) {
800             Roo.log("Error table not found!?");
801             return;
802         }
803         xoff = xoff || 0;
804         yoff = yoff || 0;
805         
806         
807         var table_data = this.readTableData(datagrid);
808         
809         // oroginally this cleaned line breaks, but we acutally need them..
810         var cleanHTML = function (str) {
811             
812             var ret = str;
813             ret = ret.replace(/&nbsp;/g,' ');
814            // ret = ret.replace(/\n/g,'.');
815           //  ret = ret.replace(/\r/g,'.');
816             var i;
817              
818             return ret;
819         };
820
821         
822         // <cell col="A" row="1">Test< / cell>
823         // <cell col="B" row="2" type="Number" format="test1">30< / cell>
824         var rowOffsets = {};
825         var rows = datagrid.getElementsByTagName('tr');
826         //alert(rows.length);
827         
828         
829         for(var row=0;row<rows.length;row++) {
830             
831             // let's see what affect this has..
832             // it might mess things up..
833             
834             if (rows[row].getAttribute('xls:height')) {
835                 this.setRowHeight(row + yoff +1, 1* rows[row].getAttribute('xls:height'));
836             } else {
837                 this.setRowHeight( row + yoff +1, Roo.get(rows[row]).getHeight());
838             }
839             
840          
841             var cols = rows[row].getElementsByTagName('td');
842             
843             
844             for(var col=0;col < cols.length; col++) {
845                 
846                 
847                
848                 
849                 var colspan = cols[col].getAttribute('colspan');
850                 colspan  = colspan ? colspan *1 : 1;
851                 
852                 var rowspan = cols[col].getAttribute('rowspan');
853                 rowspan = rowspan ? rowspan * 1 : 1;
854                 
855                 var realcol = table_data.near( Roo.get(cols[col]).getLeft() );
856                 
857                 
858                 
859                 if (colspan > 1 || rowspan > 1) {
860                     
861                     // getting thisese right is tricky..
862                     this.mergeRegion(
863                         realcol + xoff,
864                         row + yoff +1,
865                         realcol+ xoff + (colspan -1),
866                         row + yoff + rowspan 
867                     );
868                     
869                 }
870                 
871                 // skip blank cells
872                 // set the style first..
873                 this.parseHtmlStyle( cols[col], row + yoff, realcol + xoff   , colspan, rowspan);
874                 
875                 if (!cols[col].childNodes.length) {
876                      
877                     continue;
878                 }
879                 
880                 
881                 
882                 
883                 var vt = '60';
884                 var vf = false;
885                 var xlstype = cols[col].getAttribute('xls:type');
886                 switch(xlstype) {
887                     case 'int':
888                         vt = 30; // int!!!!
889                     
890                         break;
891                         
892                     case 'float':
893                         vt = 40; // float!!!!
894                         if (cols[col].getAttribute('xls:floatformat')) {
895                             vf = cols[col].getAttribute('xls:floatformat');
896                         }
897                         break;
898                         
899                     case 'date':
900                         vt = 30;
901                         //ValueFormat="d/m/yyyy" 38635  
902                         var vf = 'd/m/yyy';
903                         if (cols[col].getAttribute('xls:dateformat')) {
904                             vf= cols[col].getAttribute('xls:dateformat');
905                         }
906                         
907                        
908                         
909                         break;
910                     
911                     default:
912                        
913                         break;
914                 }
915                
916                 if (!cols[col].childNodes[0].nodeValue) {
917                    
918                     continue;
919                 }
920                 if (!cols[col].childNodes[0].nodeValue.replace(/^\s*|\s*$/g,"").length) {
921                   
922                     continue;
923                 }
924                 // strip me.!
925                 var cell_value_text = cleanHTML(cols[col].childNodes[0].nodeValue);
926        
927                 if (cols[col].getAttribute('xls:percent')) {
928                     cell_value_text = '' + ((cell_value_text * 1) / 100);
929                 }
930
931                 if (cell_value_text.length && (vt == 30) && xlstype == 'date') {
932                     var bits = cell_value_text.split(/-/);
933                     var cur = new Date(bits[0],bits[1]-1,bits[2]);
934                     cell_value_text = '' + Math.round((cur.getTime() - Date.UTC(1899,11,30)) / (24 * 60 * 60 * 1000));
935                 }
936
937                 
938                 
939                 if (cols[col].getAttribute('xls:formula')) {
940                     var s = cols[col].getAttribute('xls:formula');
941                     vt = '';
942                     cell_value_text = s.replace(/#row#/g,(row + yoff + 1));
943                 }
944                 this.set({ r: row + yoff, c : realcol + xoff }, cell_value_text, vt, vf);
945                  
946                   
947                 
948                 
949                 
950             }
951         }
952         this.rowOffset += rows.length;
953         
954     },
955     
956     
957     
958     parseHtmlStyle : function(dom, row, col, colspan, rowspan) {
959         
960         function toCol (rgb) {
961             
962             var ar = rgb.replace(/rgb[a]?\(/, '').replace(/\)/, '').replace(/ /, '').split(',');
963             var rcs = [];
964             ar = ar.slice(0,3);
965             Roo.each(ar, function(c) { 
966                 rcs.push((c*c).toString(16)) ;   
967             });
968             return rcs.join(':');
969             
970         }
971         
972         var el = Roo.get(dom);
973         var map =  {
974             'text-align'  : function(ent,v) { 
975                 ent['HAlign'] = { 'left' : '1', 'center' : '8' ,  'right' : '4' }[v] || '1';
976             },
977             'vertical-align': function(ent,v) { 
978                 ent['VAlign'] = { 'top' : '1', 'middel' : '8' ,  'bottom' : '4' }[v] || '1';
979             },
980             
981             'color': function(ent,v) { 
982                 ent['Fore'] = toCol(v);
983                 // this is a bit dumb.. we assume that if it's not black text, then it's shaded..
984                 if (ent['Fore'] != '0:0:0') {
985                     ent['Shade'] = 1;
986                 }
987                 
988             },
989             'background-color' : function(ent,v) { 
990                 ent['Back'] = toCol(v);
991                  
992             }
993             
994         };
995        
996         var ent = {
997                 HAlign:"1",
998                 VAlign:"2",
999                 WrapText:"0",
1000                 ShrinkToFit:"0",
1001                 Rotation:"0",
1002                 Shade:"0",
1003                 Indent:"0",
1004                 Locked:"0",
1005                 Hidden:"0",
1006                 Fore:"0:0:0",
1007                 Back:"FFFF:FFFF:FFFF",
1008                 PatternColor:"0:0:0",
1009                 Format:"General"
1010         };
1011            
1012         for(var k in map) {
1013             var val = el.getStyle(k);
1014             if (!val || !val.length) {
1015                continue;
1016             }
1017             map[k](ent,val);
1018         }
1019         // special flags..
1020         if (el.dom.getAttribute('xls:wraptext')) {
1021             ent.WrapText = 1;
1022         }
1023         if (el.dom.getAttribute('xls:valign')) {
1024             ent.VAlign= 1;
1025         }
1026         if (el.dom.getAttribute('xls:halign')) {
1027             ent.HAlign= 1;
1028         }
1029         // fonts..
1030         var fmap = {
1031             
1032            
1033             'font-size' : function(ent,v) { 
1034                 ent['Unit'] = v.replace(/px/, '');
1035             },
1036             'font-weight' : function(ent,v) { 
1037                 if (v != 'bold') {
1038                    return;
1039                 }
1040                 ent['Bold'] = 1;
1041             },
1042             'font-style' : function(ent,v) { 
1043                 if (v != 'italic') {
1044                     return;
1045                 }
1046                 ent['Italic'] = 1;
1047             } 
1048         };
1049        
1050         var fent = {
1051             Unit:"10",
1052             Bold:"0",
1053             Italic:"0",
1054             Underline:"0",
1055             StrikeThrough:"0"
1056         };
1057         
1058         for(var k in fmap) {
1059             var val = el.getStyle(k);
1060             if (!val || !val.length) {
1061                continue;
1062             }
1063             fmap[k](fent,val);
1064         }
1065         var font = el.getStyle('font-family') || 'Sans';
1066         if (font.split(',').length > 1) {
1067             font = font.split(',')[1].replace(/\s+/, '');
1068         }
1069         
1070         
1071         /// -- now create elements..
1072         
1073         var objs = this.sheet.getElementsByTagNameNS('*','Styles')[0];
1074         
1075         //<gnm:StyleRegion startCol="0" startRow="0" endCol="255" endRow="65535"
1076         var sr = this.doc.createElementNS('http://www.gnumeric.org/v10.dtd', 'gnm:StyleRegion');
1077         objs.appendChild(sr);
1078         objs.appendChild(this.doc.createTextNode("\n"));// add a line break..
1079
1080         sr.setAttribute('startCol', col);
1081         sr.setAttribute('endCol', col+ colspan-1);
1082         sr.setAttribute('startRow', row);
1083         sr.setAttribute('endRow', row + rowspan -1);
1084         
1085         
1086         var st = this.doc.createElementNS('http://www.gnumeric.org/v10.dtd', 'gnm:Style');
1087         sr.appendChild(st);
1088         // do we need some defaults..
1089         for(var k in ent) {
1090             //Roo.log(k);
1091             st.setAttribute(k, ent[k]);
1092         }
1093         
1094         var fo = this.doc.createElementNS('http://www.gnumeric.org/v10.dtd', 'gnm:Font');
1095         st.appendChild(fo);
1096         // do we need some defaults..
1097         for(var k in fent) {
1098             fo.setAttribute(k, fent[k]);
1099         }
1100         fo.textContent  = font;
1101         
1102         var sb = false;
1103         // borders..
1104         Roo.each(['top','left','bottom','right'], function(p) {
1105             var w = el.getStyle('border-' + p + '-width').replace(/px/, '');
1106             if (!w || !w.length || (w*1) < 1) {
1107                 return;
1108             }
1109             if (!sb) {
1110                 sb= this.doc.createElementNS('http://www.gnumeric.org/v10.dtd', 'gnm:StyleBorder');
1111             }
1112             var be = this.doc.createElementNS('http://www.gnumeric.org/v10.dtd', 'gnm:' + p[0].toUpperCase() + p.substring(1));
1113             be.setAttribute('Style', '1');
1114             be.setAttribute('Color', '0:0:0'); // fixme..
1115             sb.appendChild(be);
1116             
1117         }, this);
1118         // start adding them all together..
1119         
1120         if (sb) {
1121             st.appendChild(sb);
1122         }
1123         
1124         
1125         
1126         
1127     },
1128     
1129     
1130     
1131     /**
1132      * writeImage:
1133      * write an image (needs base64 data to write it)
1134      * 
1135      * 
1136      * @param {Number} row  row to put it in (rows start at 0)
1137      * @param {Number} col  column to put it in
1138      * @param {Number} data  the base64 description of the images
1139      * @param {Number} width image width
1140      * @param {Number} width image height
1141      * 
1142      */
1143     
1144     
1145     writeImage : function (row, col, data, width, height) 
1146     {
1147         
1148         if (!data) {
1149             throw "write Image called with missing data";
1150         }
1151         // our default height width is 50/50 ?!
1152         //console.log('w='+width+',height='+height);
1153                 //        <gmr:Objects>
1154         row*=1;
1155         col*=1;
1156         height*=1;
1157         width*=1;
1158         var objs = this.sheet.getElementsByTagNameNS('*','Objects')[0];
1159         var soi = this.doc.createElementNS('http://www.gnumeric.org/v10.dtd', 'gnm:SheetObjectImage');
1160         
1161         //<gmr:SheetObjectImage 
1162         //      ObjectBound="A3:J8" 
1163         //      ObjectOffset="0.375 0.882 0.391 0.294" 
1164         //      ObjectAnchorType="16 16 16 16" 
1165         //      Direction="17" 
1166         //      crop-top="0.000000" 
1167         //      crop-bottom="0.000000" 
1168         //      crop-left="0.000000" 
1169         //      crop-right="0.000000">
1170                 
1171                 
1172         //alert(gnumeric_colRowToName(row,col));
1173                
1174         // this is where we really have fun!!!... 
1175         // since our design currently assumes the height is enough to fit
1176         // stuff in, we only really need to work out how wide it has to be..
1177         
1178         // note we should probably use centralized calcs if it fits in the first cell!
1179         
1180         // step 1 - work out how many columns it will span..
1181         // lets hope the spreadsheet is big enought..
1182         var colwidth = 0;
1183         var endcol=col;
1184         for ( endcol=col;endcol <100; endcol++) {
1185             if (!this.colInfo[endcol]) {
1186                 this.colInfo[endcol] = 100; // eak fudge
1187             }
1188             colwidth += this.colInfo[endcol];
1189             if (colwidth > width) {
1190                 break;
1191             }
1192         }
1193        
1194         
1195         soi.setAttribute('ObjectBound',
1196             //gnumeric_colRowToName(row,col) + ':' + gnumeric_colRowToName(row+1,col+1));
1197             this.RCtoCell(row,col) + ':' + this.RCtoCell(row,endcol));
1198      
1199         var ww = 0.01; // offset a bit...
1200         var hh = 0.01; //
1201         
1202         var ww2 = 1 - ((colwidth - width) / this.colInfo[endcol]);
1203         var hh2 = 0.99;
1204         
1205         var offset_str = ww + ' '  + hh + ' ' + ww2 + ' '+hh2;
1206         //console.log(offset_str );
1207         //alert(offset_str);
1208         soi.setAttribute('ObjectOffset', offset_str);
1209         soi.setAttribute('ObjectAnchorType','16 16 16 16');
1210         soi.setAttribute('Direction','17');
1211         soi.setAttribute('crop-top','0.000000');
1212         soi.setAttribute('crop-bottom','0.000000');
1213         soi.setAttribute('crop-left','0.000000');
1214         soi.setAttribute('crop-right','0.000000');
1215                 // <Content image-type="jpeg" size-bytes="3900">......  < / Content>
1216         var content = this.doc.createElement('Content');
1217         content.setAttribute('image-type','jpeg');
1218         //alert(imgsrc);
1219         
1220         content.setAttribute('size-bytes',data.length);
1221         content.textContent = data;
1222         soi.appendChild(content);
1223         objs.appendChild(soi);
1224         return true;
1225                 //< /gnm:SheetObjectImage>
1226                 // < /gnm:Objects>
1227
1228     },
1229  
1230     /**
1231      * mergeRegion:
1232      * Merge cells in the spreadsheet. (does not check if existing merges exist..)
1233      * 
1234      * @param {Number} col1  first column 
1235      * @param {Number} row1  first row
1236      * @param {Number} col2  to column 
1237      * @param {Number} row2  to row
1238      * 
1239      */
1240     mergeRegion : function (col1,row1,col2,row2)
1241     {
1242         var cell = this.doc.createElementNS('http://www.gnumeric.org/v10.dtd', 'gnm:Merge');
1243         //if (col1 > 50|| col2 > 50) { // do not merge cols off to right?
1244        //     return;
1245         //}
1246         
1247         cell.textContent = this.RCtoCell(row1,col1) + ':' + this.RCtoCell(row2,col2);
1248         
1249         //var merges = this.gnumeric.getElementsByTagNameNS('*','MergedRegions');
1250         var merges = this.sheet.getElementsByTagNameNS('*','MergedRegions');
1251         if (!merges || !merges.length) {
1252             merges = this.doc.createElementNS('http://www.gnumeric.org/v10.dtd','gnm:MergedRegions');
1253             var sl = this.sheet.getElementsByTagNameNS('*','SheetLayout')[0];
1254             this.sheet.insertBefore(merges,sl);
1255         } else {
1256             merges = merges[0];
1257         }
1258         merges.appendChild(cell);
1259     
1260     },
1261     /**
1262      * setRowHeight:
1263      * Sets the height of a row.
1264      * 
1265      * @param {Number} r  the row to set the height of. (rows start at 0)
1266      * @param {Number} height (in pixels)
1267      */
1268     setRowHeight : function (r,height)
1269     {
1270         
1271         //<gmr:Rows DefaultSizePts="12.75">
1272         //   <gmr:RowInfo No="2" Unit="38.25" MarginA="0" MarginB="0" HardSize="1"/>
1273     //  < /gmr:Rows>
1274         
1275         // this doesnt handle row ranges very well.. - with 'count in them..'
1276         
1277         if (this.rowInfoDom[r]) {
1278             this.rowInfoDom[r].setAttribute('Unit', height);
1279             return;
1280         }
1281     
1282         var rows = this.sheet.getElementsByTagNameNS('*','Rows')[0]; // assume this exists..
1283         var ri = this.doc.createElementNS('http://www.gnumeric.org/v10.dtd','gnm:RowInfo');
1284         // assume we have no rows..
1285         ri.setAttribute('No', r-1);
1286         ri.setAttribute('Unit', height);
1287         ri.setAttribute('MarginA', 0);
1288         ri.setAttribute('MarginB', 0);
1289         ri.setAttribute('HardSize', 1);
1290         rows.appendChild(ri);
1291         this.rowInfoDom[r] = ri;
1292     },
1293      
1294     /**
1295      * setSheetName: 
1296      * Set the sheet name.
1297      * @param {String} title for sheet
1298      **/
1299     setSheetName : function(name,sheet)
1300     {
1301         sheet = sheet || 0;
1302         /*
1303         <gnm:SheetNameIndex>
1304         <gnm:SheetName>Sheet1</gnm:SheetName>
1305         <gnm:SheetName>Sheet2</gnm:SheetName>
1306         <gnm:SheetName>Sheet3</gnm:SheetName>
1307         </gnm:SheetNameIndex>
1308         */
1309         // has to set sheet name on index and body..
1310         Roo.log(sheet);
1311         Roo.log(name);
1312         var sheetnames = this.doc.getElementsByTagNameNS('*','SheetName');
1313         if (sheet >=  sheetnames.length) {
1314             
1315             sheetnames[0].parentNode.appendChild(sheetnames[sheetnames.length-1].cloneNode(true));
1316             // copy body.
1317             sheetnames = this.doc.getElementsByTagNameNS('*','Sheet');
1318             sheetnames[0].parentNode.appendChild(sheetnames[sheetnames.length-1].cloneNode(true));
1319             var sn = this.doc.getElementsByTagNameNS('*','Sheet')[sheet];
1320             var cls = sn.getElementsByTagNameNS('*','Cells')[0];
1321             while (cls.childNodes.length) {
1322                 cls.removeChild(cls.firstChild);
1323             }
1324             
1325         }
1326         
1327         var sheetn = this.doc.getElementsByTagNameNS('*','SheetName')[sheet];
1328         sheetn.textContent = name;
1329         var sheetb = this.doc.getElementsByTagNameNS('*','Sheet')[sheet].getElementsByTagNameNS('*','Name')[0];
1330         sheetb.textContent = name;
1331         this.parseDoc(sheet);
1332         
1333         
1334         
1335         
1336     },
1337      /**
1338      * setColumnWidth: 
1339      * Set the column width
1340      * @param {Number} column number (starts at '0')
1341      * @param {Number} width size of column
1342      **/
1343     setColumnWidth : function(column, width)
1344     {
1345         column = column *1; 
1346         width= width*1;
1347         if (typeof(this.colInfoDom[column]) == 'undefined') {
1348             var cols = this.doc.getElementsByTagNameNS('*','Cols')[0];
1349             var ri = this.doc.createElementNS('http://www.gnumeric.org/v10.dtd', 'gnm:ColInfo');
1350             ri.setAttribute('No', column);
1351             ri.setAttribute('Unit', width);
1352             ri.setAttribute('MarginA', 2);
1353             ri.setAttribute('MarginB', 2);
1354             ri.setAttribute('HardSize', 1);
1355             cols.appendChild(ri);
1356             this.colInfo[column] = width;
1357             this.colInfoDom[column]  = ri;
1358             return;
1359         }
1360         this.colInfoDom[column].setAttribute('Unit', width);
1361         
1362     },
1363     
1364     
1365     
1366     
1367     
1368      /**
1369      * toHTML: 
1370      * Convert spreadsheet into a HTML table.
1371      */
1372             
1373     toHTML :function()
1374     {
1375          var _t = this;
1376         function calcWidth(sc, span)
1377         {
1378             var n =0;
1379             for(var i =sc; i< sc+span;i++) {
1380                 n+=_t.colInfo[i];
1381             }   
1382             return n;
1383         }
1384         
1385         var grid = this.grid;
1386         // lets do a basic dump..
1387         var out = '<table style="table-layout:fixed;" cellpadding="0" cellspacing="0">';
1388         for (var r = 0; r < this.rmax;r++) {
1389             out += '<tr style="height:'+this.rowInfo[r]+'px;">';
1390             for (var c = 0; c < this.cmax;c++) {
1391                 if (typeof(grid[r][c]) == 'undefined')  {
1392                     this.createCell(r,c);
1393                     
1394                 }
1395                 var g = grid[r][c];
1396                 
1397                 if (typeof(g.cls) =='undefined') {
1398                     g.cls = [];
1399                 }
1400                 var w= calcWidth(c,g.colspan);
1401                 
1402                 var value = g.value[0] == '=' ? 'CALCULATED' : g.value;
1403                 
1404                 try {
1405                     if(
1406                         g.styles[0].firstElementChild.getAttribute('Format') == "D\\-MMM\\-YYYY;@" &&
1407                         g.value[0] != '=' &&
1408                         !isNaN(value * 1) && 
1409                         value != 0
1410                     ){
1411                         value = new Date(value * 24 * 60 * 60 * 1000 + new Date('1899-12-30').getTime()).format('d-M-Y');
1412                     }
1413                     
1414                 } catch(e) {
1415                     
1416                 }
1417                 
1418                 out+=String.format('<td colspan="{0}" rowspan="{1}"  class="{4}"><div style="{3}">{2}</div></td>', 
1419                     g.colspan, g.rowspan, value,
1420                     'overflow:hidden;' + 
1421                     'width:'+w+'px;' +
1422                    
1423                     'text-overflow:ellipsis;' +
1424                     'white-space:nowrap;',
1425                      g.cls.join(' ')
1426     
1427     
1428                 );
1429                 c+=(g.colspan-1);
1430             }
1431             out += '</tr>';
1432         }
1433         //Roo.log(out);
1434         return out+'</table>';
1435         
1436         
1437         
1438     },
1439     /**
1440      * download:
1441      * @param {String} name  filename to downlaod (without xls)
1442      * @param {String} callback  (optional) - callback to call after callback is complete.
1443      */
1444     download : function(name,callback)
1445     {
1446         name = name || "Missing_download_filename";
1447         
1448         if (this.downloadURL && this.downloadURL.charAt(this.downloadURL.length-1) != '/') {
1449             this.downloadURL += '/';
1450         }
1451         
1452         var ser = new XMLSerializer();
1453         var x = new Pman.Download({
1454             method: 'POST',
1455             timeout : 120000, // quite a long wait.. 2 minutes.
1456             params : {
1457                xml : ser.serializeToString(this.doc),
1458                format : 'xls', //xml
1459                debug : 0
1460                
1461             },
1462             url : (this.downloadURL || (baseURL + '/GnumericToExcel/')) + name + '.xls',
1463             success : function() {
1464                 Roo.MessageBox.alert("Alert", "File should have downloaded now");
1465                 if (callback) {
1466                     callback();
1467                 }
1468             }
1469         });
1470          
1471     }
1472
1473 });