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