Kokizzu
Kokizzu

Reputation: 26888

How to convert html table to csv string

I have a table with thead, tfoot, tr, td and th inside it. The td or th has colspan or rowspan attribute, how to convert it to csv string with every merged cells unmerged as multiple fields?

for example:

<table>
  <thead>
    <tr>
      <th>1</th>
      <th>2</th>
    </tr>
  </thead>
  <tr>
    <td rowspan='2'>3</td>
    <td>4</td>
  </tr>
  <tr>
    <td>5</td>
  </tr>
  <tfoot>
    <tr>
      <th colspan='2'>6 <span style='display:none'> 7 
</span> 8</th>
    </tr>
  </tfoot>
</table>

should be outputting:

"1","2"
"3","4"
"","5"
"6  7  8",""

Is there any other plugin other than table2CSV? because this one displaying tfoot on the first row instead on the last row, also merged cells shown as single field.

Upvotes: 0

Views: 2244

Answers (2)

Ismael Miguel
Ismael Miguel

Reputation: 4241

I have made a plugin 100% jQuery-free.

You can integrate it with jQuery in no time.

The goal was to try to make this work in the best possible way.

Here is the code:

(function(window,undefined){

    window.T2CSV=function(table){
        if(! (table instanceof window.HTMLTableElement))
        {
            throw new window.TypeError('A <table> element is required, instead '+table+' was passed');
        }

        var tr,thead,csv,tfoot,cols,prop=(table.innerText===undefined?'textContent':'innerText'),
            setVars=function(){
                var elements=table.getElementsByTagName('tr');

                if(elements.length<1)
                {
                    throw new window.RangeError('At least 1 <tr> element is required, you have 0 on your <table>.');
                }

                tr=Array.prototype.slice.call(elements,1);
                thead=elements[0];
                cols=thead.children.length;
                elements=null; //free memory
                csv='';

            },
            render={
                header:function(){
                    if(! (thead.children[0] instanceof window.HTMLTableCellElement))
                    {
                        throw new window.RangeError('At least 1 <tr> element with 1 <td> or <th> is required.');
                    }

                    for(var i=0,children=thead.children,l=children.length,csv=[];i<l;i++)
                    {
                        csv[csv.length]='"'+children[i][prop]+'"';
                    }
                    children=null; //free memory
                    return csv;
                },
                data:function(){

                    if(!tr.length)
                    {
                        return '';
                    }

                    for(var i=0,l=tr.length,csv=[],tfoot=false;i<l;i++)
                    {
                        if(!tfoot && tr[i].parentNode.tagName=='TFOOT')
                        {
                            tfoot=tr[i];
                            continue;
                        }
                        csv[csv.length]=render.row(tr[i]);
                    }

                    if(tfoot)
                    {
                        csv[csv.length]=render.row(tfoot);
                    }

                    return csv.join('\r\n');
                },
                row:function(tr){
                    var td=tr.getElementsByTagName('td');

                    if(!td.length)
                    {
                        td=tr.getElementsByTagName('th');
                    }

                    for(var i=0,tmp=[];i<cols;i++)
                    {
                        tmp[i]=td[i]?'"'+td[i][prop]+'"':'""';
                    }
                    return tmp+'';
                }
            };

        setVars();

        return {
            toString:function(){
                if(csv)
                {
                    return csv;
                }

                return csv = [render.header(),render.data()].join('\r\n');
            },
            valueOf:function(){return this.toString();},
            refresh:function(){
                setVars();
            }
        }

    }

})(function(){}.constructor('return this')());

This: function(){}.constructor('return this')() is a beautiful trick inspired by JSFuck, which returns the REAL window objects AT ALL TIMES!
Check here for the source: http://www.jsfuck.com/

It lacks comments, but I'm pretty sure that what I'm doing is easy to understand.

If I'm wrong, drop a comment and I will make this easier to understand.

The usage is simple: just pass a table (a pure table, no jQuery in it or it will choke) and convert it to a string.

The generated csv is cached, so, multiple accesses are really fast.

The method .refresh() will destroy that cache.

This isn't the most efficient method, but it works.

For obvious reasons, the output will be a bit different.

Instead of this:

"1","2"
"3","4"
"","5"
"6  7  8",""

It produces this:

"1","2"
"3","4"
"5",""
"6  7  8",""

The code can be easily customized.

I'm thinking about updating it and adding escapes for the separators and configurations.

Check it here in action: http://jsfiddle.net/qw8ponhu/2/


Update (15/11/2014):

I have made an improved version!

It now escapes slashes and double-quotes.

I still haven't added support for more delimiters and text quotes.

Here is the code I have:

(function(window,undefined){
    window.T2CSV=function(table){
        if(!(table instanceof window.HTMLTableElement))
        {
            throw new window.TypeError('A <table> element is required, instead '+table+' was passed');
        }

        var tr,thead,cols,tfoot,csv={
                header:'',
                data:[],
                footer:'',
                string:''
            },
            prop=(table.innerText===undefined?'textContent':'innerText'),
            setVars=function(){
                var elements=table.getElementsByTagName('tr');

                if(elements.length<1)
                {
                    throw new window.RangeError('At least 1 <tr> element is required, you have 0 on your <table>.');
                }

                tr=Array.prototype.slice.call(elements,1);
                thead=elements[0];
                cols=thead.children.length;
                elements=null; //free memory
                csv={
                    header:'',
                    data:[],
                    footer:'',
                    string:''
                };
            },
            addSlashes=function(data){
                return data.replace(/([\\"])/g,'\\$1');
            },
            render={
                header:function(){
                    if(! (thead.children[0] instanceof window.HTMLTableCellElement))
                    {
                        throw new window.RangeError('At least 1 <tr> element with 1 <td> or <th> is required.');
                    }

                    for(var i=0,children=thead.children,l=children.length,tmp=[];i<l;i++)
                    {
                        tmp[tmp.length]='"'+addSlashes(children[i][prop])+'"';
                    }
                    children=null; //free memory
                    return csv.header=tmp;
                },
                data:function(){

                    if(!tr.length)
                    {
                        return '';
                    }

                    for(var i=0,l=tr.length,tmp=[],tfoot=false;i<l;i++)
                    {
                        if(!tfoot && tr[i].parentNode.tagName=='TFOOT')
                        {
                            tfoot=tr[i];
                            continue;
                        }
                        csv.data[tmp.length]=tmp[tmp.length]=render.row(tr[i]);
                    }

                    if(tfoot)
                    {
                        csv.footer=tmp[tmp.length]=render.row(tfoot);
                    }

                    return tmp.join('\r\n');
                },
                row:function(tr){
                    var td=tr.getElementsByTagName('td');

                    if(!td.length)
                    {
                        td=tr.getElementsByTagName('th');
                    }

                    for(var i=0,tmp=[];i<cols;i++)
                    {
                        tmp[i]=td[i]?'"'+addSlashes(td[i][prop])+'"':'""';
                    }
                    return tmp+'';
                }
            };

        setVars();

        return {
            toString:function(){
                if(csv.string)
                {
                    return csv.string;
                }

                return csv.string = [render.header(),render.data()].join('\r\n');
            },
            valueOf:function(){return this.toString();},
            refresh:function(){
                setVars();
            },
            getHeader:function(){
                return csv.header;
            },
            getFooter:function(){
                return csv.footer;
            },
            getRows:function(){
                return csv.data;
            },
            getRow:function(row){
                return csv.data[row>>0];
            }
        };

    }

})(function(){}.constructor('return this')());

You can check it working in here: http://jsfiddle.net/qw8ponhu/6/

Upvotes: 1

Abercrombieande
Abercrombieande

Reputation: 709

If you aren't using IE you can do it with table2CSV & JQuery

$(document).ready(function() {

  $('table').each(function() {
    var $table = $(this);

    var $button = $("<button type='button'>");
    $button.text("Export to spreadsheet");
    $button.insertAfter($table);

    $button.click(function() {
      var csv = $table.table2CSV({delivery:'value'});
      window.location.href = 'data:text/csv;charset=UTF-8,'
                            + encodeURIComponent(csv);
    });
  });
})

Upvotes: 0

Related Questions