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