I am trying to calculate the monthly expenses from my table.
Want to sum up all the amount month wise and display in total like for January month total will be $180, March will be $230 and May $200. The amount should reflect in the total column. I have created this table using ng-repeat of angular framework (dynamic table)
JSFIDDLE
I have tried below code which will sum up all the individual cols having only numeric values. This code is not working for merged rows.
for (col = 1; col < ncol + 1; col++) {
console.log("column: " + col);
sum = 0;
$("tr").each(function(rowindex) {
$(this).find("td:nth-child(" + col + ")").each(function(rowindex) {
newval = $(this).find("input").val();
console.log(newval);
if (isNaN(newval)) {
$(this).html(sum);
} else {
sum += parseInt(newval);
}
});
});
}
});
Any help on this will be really helpful.
I would add a data-month attribute to the cell displaying the amount, it is not visible to users, but super helpful for you. Have a look at the solution below.
function getMonth(month) {
var monthCells = $("td[data-month='" + month + "']"); // get all TDs with a data month attribute
var sum = 0;
for(var i = 0; i < monthCells.length; i++){ // iterate over the tds
var amountCell = monthCells[i]; // get a td for given iteration
var amountCellText = $(amountCell).text(); // get the text content
sum += parseInt(amountCellText.replace(/\D/, "")); // in amoutnCellText replace anything that's not a digit into an empty string
}
return sum;
}
console.log(getMonth("march"))
table, th, td {
border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tr>
<th>Month</th>
<th>Savings</th>
<th>Total</th>
</tr>
<tr>
<td rowspan="2">January</td>
<td data-month="january">$100</td>
</tr>
<tr>
<td data-month="january">$80</td>
</tr>
<tr>
<td rowspan="2">March</td>
<td data-month="march">$200</td>
</tr>
<tr>
<td data-month="march">$30</td>
</tr>
<tr>
<td>May</td>
<td data-month="may">$200</td>
</tr>
</table>
How about something like this?
vm.data = [
{
month: 'January',
savings: [
{ amount: 100 },
{ amount: 200}
]
},
{
month: 'February',
savings: [
{ amount: 300 },
{ amount: 400 }
]
}
];
In html:
<table class="table table-bordered table-condensed">
<tr>
<th>Month</th>
<th>Savings</th>
<th>Total</th>
</tr>
<tr ng-repeat="row in vm.data">
<td>{{row.month}}</td>
<td>
<table class="table table-bordered table-condensed">
<tr ng-repeat="s in row.savings">
<td>${{s.amount}}</td>
</tr>
</table>
</td>
<td>${{vm.getTotal(row.savings)}}</td>
</tr>
</table>
In JS:
vm.getTotal = getTotal;
function getTotal(savings) {
var total = 0;
angular.forEach(savings,
function (row) {
total += row.amount;
});
return total;
}
The key here is data modeling so that you have a simple function in getting total. Hope it will help.
Sample
Related
input : <table> <tr> <td>100</td> </tr> <tr> <td>200</td> </tr> </table> from the input it should return me the sum=300 instead I'm getting the output as 100200
function Save_Name()
{
var Invoice = $("#text_invoice").val();
var Name = $("#text_name").val();
var Date = $("#text_date").val();
var Amount = 0;
var n = $("table").find("tr").length;
if (n - 1 > 0)
{
for (var i = 0 ; i < n; i++)
{
var Amt = $("#table1").find("tr").eq(i).find("td").eq(2).text();
Amount += parseFloat(Amt);
}
}
}
eq starts with 0 index.The output 100200 is because it is concatenating the string instead of adding the values.Also use parseInt to convert the number from string to integer.
function Save_Name() {
var Amount = 0;
// will get all tr
var n = $("#table1 tr");
n.each(function(i, v) {
// table have only one td per tr so eq(0) will give first td
// trim is used to remove any white space
Amount += parseInt($(v).eq(0).text().trim(), 10)
})
console.log(Amount)
}
Save_Name()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="table1">
<tr>
<td>100</td>
</tr>
<tr>
<td>200</td>
</tr>
</table>
Here's another way to do the same thing. Basically, I use map to create a map of integer values, then I use "get" to convert them into an array, and finally I use reduce to calculate the sum of the values in the array.
P.S.: To calculate the second column, all you need to do is to use nth-child(2) instead of nth-child(1).
$(function(){
var sum = $('#table1 tr td:nth-child(1)').map(function() {
return parseInt($(this).html(), 10)
}).get().reduce(function(a, v){
return a + v;
}, 0);
console.log(sum);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<table id="table1">
<tr>
<td>100</td>
</tr>
<tr>
<td>200</td>
</tr>
</table>
I have alert the value of sum Amount and it is equal to 300 .
You were doing some mistake as eq(2) instead of eq(0)
But Here's the working snippet:
function Save_Name()
{
var Amount = 0;
var n = $('#test tr').length;
// alert(n);
if (n - 1 > 0)
{
for (var i = 0 ; i < n; i++)
{
var Amt = $("#test").find("tr").eq(i).find("td").eq(0).text();
Amount += parseFloat(Amt);
}
alert(Amount);
}
}
Save_Name();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id='test'>
<tr> <td>100 </td></tr>
<tr> <td>200 </td></tr>
</table>
How to get the sum and average of the last column.In my code it wont get the correct value if the table has one,two and three rows.This works only on table with 4 rows.I know something wrong with my code but i cant figure it out how the loop works within .each function.
Important note:this runs with keyup event to update table data.Its not just a display.To be exact it is an update form.
Desired output
Item | value1 | value 2 | value3 |value 4 | Average
01 90 88 87 80 82.25
Total average 82.25 result of 82.25/1 number of row
if two rows
Item | value1 | value 2 | value3 |value 4 | Average
01 90 88 87 80 82.25
02 80 85 86 84 83.75
Total average 83 result of (82.25+83.75)/2 number of rows
But the result comes out with multiple loops
Here is the console.log(totalAverage)
86.25
176
264.75
353.5
442.25
86.25
176
264.75
353.5
442.25
Problem:How to suppress or skip this unnecessary values.I only need the 86.25 to display in total-ave.Note: this is only single row right now and have already incountered this miscaculation, how much more if the table has multiple rows then?
Html
<tr>
<th colspan="12"><h4>Card</h4></th>
</tr>
<tr>
<th colspan="3">Subjects</th>
<th colspan="2">First Grading</th>
<th colspan="2">Second Grading</th>
<th colspan="2">Third Grading</th>
<th colspan="2">Fourth Grading</th>
<th>Average</th>
</tr>
</thead>
<tbody>
#foreach($update_card['AllGrade'] as $subject)
{!! Form::hidden('grade_id[]',$subject['grade_id']) !!}
<tr>
<th colspan="3">{!! $subject->subject !!}</th>
<td colspan="2">{!! Form::text('term_1[]',$subject->term_1,['class'=>'form-control','name[]'=>'term_1','id[]'=>'term_1','value'=>'0']) !!}</td>
<td colspan="2">{!! Form::text('term_2[]',$subject->term_2,['class'=>'form-control','name[]'=>'term_2','id[]'=>'term_2','value'=>'0']) !!}</td>
<td colspan="2">{!! Form::text('term_3[]',$subject->term_3,['class'=>'form-control','name[]'=>'term_3','id[]'=>'term_4','value'=>'0']) !!}</td>
<td colspan="2">{!! Form::text('term_4[]',$subject->term_4,['class'=>'form-control','name[]'=>'term_4','id[]'=>'term_4','value'=>'0']) !!}</td>
<td colspan="2" class="average" id="average" value="0"> Average</td>
</tr>
#endforeach
<tr>
<th colspan="11">Total Average:</th>
<th>{!! Form::text('scholar_GPA',$update_card->scholar_GPA,['class'=>'form-control total-ave','name' => 'total-ave','id' => 'total-ave','value' => '0']) !!}</th>
</tr>
Javascript snippet
$("input").on("keyup", function(){
$("tbody tr").each(function() {
var col=1;
var tr =1;
var t = 0;
var a = 0;
$(this).children('td').not(':last').each(function () {
var number = ($(this).children('input').length == 0) ? $(this).html() : $(this).children('input').first().val();
// console.log(number);
// console.log(col);
t += parseInt(number);
// console.log(t);
a = t/col;
col++;
});
$(this).children('td').last().html(a);//last td of the row
// console.log(a);
col=1;
tr++;
});
calcTotalave();
});
// calculate total average
function calcTotalave() {
var totalAve = 0;
var tot=0;
var c = 2;
var count =0;
$( ".average" ).each(function() {
// console.log($(this).html());
var thisAve = parseFloat($(this).html());
if(!thisAve || thisAve === NaN || thisAve == "") {
thisAve = 0;
}
totalAve += thisAve;
//alert('count'+thisAve+totalAve);
console.log(totalAve);
count++;
});
c++;
totalAve = totalAve/c;
// console.log(totalAve);
$("#total-ave").val(totalAve);
}
UPDATED: fiddle below with comments, press space-bar to run, based around the below function.
the fiddle is made to cycle through and calc cells by row, so no .average class required. You will need to adapt it for your html table layouts as per your database output.
calcTotalave();
});
// calculate total average
function calcTotalave() {
var totalAve = 0;
$( ".average" ).each(function() {
var thisAve = parseFloat($(this).text()) || 0; // always return a number
totalAve += thisAve;
});
var Aver = totalAve / $('.average').length;
console.log(totalAve);
$("#total-ave").text(Aver);
}
instead of classing each cell as .average you could use the selector to target all the cells td of a given row:
$('input').change(function() {
calTotalAverages(); // the magic and collect the total average
});
function calTotalAverages(){
var SumAve = 0, nums = 0; // to collect total averages and the number of rows
$('tr').each(function(i) {
if (i > 0) { // ignore the first row
var $this = $(this);
SumAve += calcRowAve($this); // add up the returned averages and run the function
nums ++; // count the rows
}
}); // cycle through each row
var sum = (SumAve / nums);
$('#total-ave').text(sum.toFixed(2)); // display the total average
return sum; // return the total average
}
// calculate total average
function calcRowAve(targetRow) {
var totalAve = 0,
targetCells = targetRow.children(),
targLength = targetCells.length - 2; // total number of values in a row
targetCells.each(function(i) {
if (i > 0 && i <= targLength) {
var thisAve = parseFloat($('input',this).val()) || parseFloat($(this).text()) || 0; // always return a number
totalAve += thisAve;
} // check to ignore the first cell and the last
});
var Aver = totalAve / targLength; // get the average
targetCells.last().text(Aver); // update the last cell of the row
return Aver; // return the average for this row
}
#total-ave {
position: fixed;
right: 2em;
top: 8em;
}
input{
width: 5em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tr>
<th>item 1</th>
<th>value 1</th>
<th>value 2</th>
<th>value 3</th>
<th>value 4</th>
<th>average</th>
</tr>
<tr>
<td>1</td>
<td>90</td>
<td>88</td>
<td>87</td>
<td>80</td>
<td></td>
</tr>
<tr>
<td>2</td>
<td>80</td>
<td>85</td>
<td>86</td>
<td>84</td>
<td></td>
</tr>
<tr>
<td>3</td>
<td><input type='number'></td>
<td><input type='number'></td>
<td><input type='number'></td>
<td><input type='number'></td>
<td></td>
</tr>
</table>
<div id='total-ave'></div>
I have a simple piece of code that in my mind should work
HTML
<table cellpadding="0" cellspacing="0" border="0">
<tbody><tr>
<th scope="row">Quantity</th>
<th id="js-QuantityID-1" scope="col">1</th>
<th id="js-QuantityID-2" scope="col">2</th>
<th id="js-QuantityID-3" scope="col">3</th>
<th id="js-QuantityID-4" scope="col">4</th>
<th id="js-QuantityID-5" scope="col">5</th>
<th id="js-QuantityID-6" scope="col">10</th>
<th id="js-QuantityID-7" scope="col">15</th>
<th id="js-QuantityID-8" scope="col">20</th>
<th id="js-QuantityID-9" scope="col">30</th>
<th id="js-QuantityID-10" scope="col">40</th>
<th id="js-QuantityID-11" scope="col">100</th>
</tr>
<tr>
<th scope="row">Price (inc. VAT)</th>
<td id="js-PriceID-1">£45.60</td>
<td id="js-PriceID-2">£76.80</td>
<td id="js-PriceID-3">£97.20</td>
<td id="js-PriceID-4">£128.40</td>
<td id="js-PriceID-5">£172.80</td>
<td id="js-PriceID-6">£307.20</td>
<td id="js-PriceID-7">£402.00</td>
<td id="js-PriceID-8">£432.00</td>
<td id="js-PriceID-9">£630.00</td>
<td id="js-PriceID-10">£840.00</td>
<td id="js-PriceID-11">£2100.00</td>
</tr>
</tbody>
</table>
Javascript
function getTableContents() {
var productArray = [];
for (var x = 0; x <= 12; x++) {
productArray.push({ Price: $('#js-PriceID-' + x).text(), Qty: $('#js-QuantityID-' + x).text() });
}
console.log("Array: " + productArray);
}
But at the end of the execution of this code, I end up with an array with the two properties 'undefined'. If I manually enter the selector ID it works fine, it seems to be with the for loop and getting the values at runtime.
Why is this and is there a workaround?
First item in the loop is 0, and last item is 12. That's why.
Try your loop as follows:
for (var x=1; x<=11; x++)
The loop runs from 0 until 12 so it will look up js-PriceID-0 and js-PriceID-12, which both don't exist. Same goes for js-QuantityID-0and js-QuantityID-12.
Use this instead:
for (var x = 1; x < 12; x++) {
productArray.push({ Price: $('#js-PriceID-' + x).text(), Qty: $('#js-QuantityID-' + x).text() });
}
To save some time you could also do something like this:
function getTableContents() {
var productArray = [];
// loop through all elements with an id that starts with "js-QuantityID-"
$("th[id^='js-QuantityID-']").each(function () {
var n = $(this).attr("id").replace('js-QuantityID-',''); // get the number from the id
productArray.push({
Price: $('#js-PriceID-' + n).text(),
Qty: $(this).text()
});
});
console.log("Array: ", productArray);
}
I have this code that I use to calculate totals in specific columns based on the text in a different column. It works just fine, but I'm learning, so I would like to know if there is way to consolidate this code. As you can see I run a "each()" twice, once for each column. The first each check for "A" in the first column, then goes to the second column and adds the rows that meet the criteria. Similar on the second column, just that it looks for "B" and add columns 3. Is there a way to run the each function only once and check both column at the same time?
JS:
//Second Column
var total = 0;
$("#theTable tr:contains('A') td:nth-of-type(2)").each(function () {
var pending = parseInt($(this).text());
total += pending;
});
$("#theTable tfoot tr:last-of-type td:nth-of-type(2)").text(total);
//Third Column
var total2 = 0;
$("#theTable tr:contains('B') td:nth-of-type(3)").each(function () {
var pending2 = parseInt($(this).text());
total2 += pending2;
});
$("#theTable tfoot tr:last-of-type td:nth-of-type(3)").text(total2);
HTML:
<table id="theTable">
<thead>
<tr>
<th>MONTH</th>
<th>PENDING</th>
<th>DENIED</th>
</tr>
</thead>
<tbody></tbody>
<tr>
<td>A</td>
<td>2</td>
<td>4</td>
</tr>
<tr>
<td>B</td>
<td>1</td>
<td>3</td>
</tr>
<tr>
<td>A</td>
<td>3</td>
<td>2</td>
</tr>
<tr>
<td>C</td>
<td>3</td>
<td>2</td>
</tr>
<tr>
<td>A</td>
<td>2</td>
<td>2</td>
</tr>
<tr>
<td>B</td>
<td>4</td>
<td>2</td>
</tr>
<tfoot>
<tr>
<td>TOTALS:</td>
<td></td>
<td></td>
</tr>
</tfoot>
This may look simple for some of you, but again, I'm just learning some JS now.
Thanks!
You could try something like this:
var total = {A:{row:1,t:0},B:{row:2,t:0}};
$('#theTable tr').each(function() {
$row = $(this);
$.each(total, function(key, col) {
rowFil = $row.filter(':contains("' + key + '")');
col.t += (rowFil) ? +rowFil.find('td:eq(' + col.row + ')').text() : 0;
});
});
$("#theTable tfoot tr:last td:eq(1)").text(total.A.t);
$("#theTable tfoot tr:last td:eq(2)").text(total.B.t);
someThing of this sort might Help ...
var trs = $('#'+tblID).find('tr');
var total1 = 0;
var total2 = 0;
$.each(trs, function(k, v) {
if ($(v).text == "A"){
total1 += parseInt($(v).parent('tr').find('td:eq(2)').text());
}
if ($(v).text == "B"){
total2 += parseInt($(v).parent('tr').find('td:eq(3)').text())
}
});
Here is another approach - I've summed up all statistics for all possible values:
var totals = [];
$('#theTable tbody tr').each(function(e) {
var tds= $(this).find('td');
var index = $(tds[0]).text();
var pending = parseInt($(tds[1]).text(), 10);
var denied = parseInt($(tds[2]).text(), 10);
if (totals[index] == undefined)
totals[index] = { Pending: 0, Denied: 0 };
totals[index].Pending += pending;
totals[index].Denied += denied;
});
for (var key in totals)
$('#theTable tfoot').append('<tr><td>'+key+'</td><td>'+
totals[key].Pending+'</td><td>'+totals[key].Denied+'</td></tr>');
I've also updated markup a little, here is jsfiddle. The code may be not so pretty, but doing more stuff and can be refactored.
Creating a second table with the sums makes it easier to analyse the data.
SOLUTION
JS
//make a list of unique months
var months = [];
$('#theTable tr td:nth-of-type(1)').each(function(){
var month = $(this).text();
if(months.indexOf(month) < 0) months.push(month);
});
console.log('months', months);
//make a data structure with sums
var data = {};
var tr = $('#theTable tr');
$.each(months, function(){
var month = this;
data[month] = {
pending: 0,
denied: 0
};
tr.each(function(){
var ch = $(this).children();
var m = $(ch[0]).text();
var pending = $(ch[1]).text();
var denied = $(ch[2]).text();
if(m == month) {
data[month].pending += parseInt(pending);
data[month].denied += parseInt(denied);
}
});
});
console.log('data', data);
//make a table with the data
var table = $('<table>');
table.append($('<tr>'+
'<th>MONTH</th>'+
'<th>PENDING</th>'+
'<th>DENIED</th>'+
'</tr>'));
$.each(data, function(month){
table.append($('<tr>'+
'<td>'+month+'</td>'+
'<td>'+data[month].pending+'</td>'+
'<td>'+data[month].denied+'</td>'+
'</tr>'));
});
$('body').append(table);
I have a simple HTML table, which uses rowspans in some random columns. An example might look like
A | B |
---|---| C
D | |
---| E |---
F | | G
I'd like to iterate over the rows such that I see rows as A,B,C, D,E,C, then F,E,G.
I think I can probably cobble together something very convoluted using cell.index() to check for "missed" columns in later rows, but I'd like something a little more elegant...
without jquery:
function tableToMatrix(table) {
var M = [];
for (var i = 0; i < table.rows.length; i++) {
var tr = table.rows[i];
M[i] = [];
for (var j = 0, k = 0; j < M[0].length || k < tr.cells.length;) {
var c = (M[i-1]||[])[j];
// first check if there's a continuing cell above with rowSpan
if (c && c.parentNode.rowIndex + c.rowSpan > i) {
M[i].push(...Array.from({length: c.colSpan}, () => c))
j += c.colSpan;
} else if (tr.cells[k]) {
var td = tr.cells[k++];
M[i].push(...Array.from({length: td.colSpan}, () => td));
j += td.colSpan;
}
}
}
return M;
}
var M = tableToMatrix(document.querySelector('table'));
console.table(M.map(r => r.map(c => c.innerText)));
var pre = document.createElement('pre');
pre.innerText = M.map(row => row.map(c => c.innerText).join('\t')).join('\n');
document.body.append(pre);
td {
border: 1px solid rgba(0,0,0,.3);
}
<table>
<tr>
<td colspan=2>A</td>
<td rowspan=2>B</td>
</tr>
<tr>
<td>C</td>
<td rowspan=3>D</td>
</tr>
<tr>
<td rowspan=2>E</td>
<td rowspan=4>F</td>
</tr>
<tr></tr>
<tr>
<td rowspan=2 colspan=2>G</td>
</tr>
<tr></tr>
<tr>
<td rowspan=3 colspan=3>H</td>
</tr>
<tr></tr>
<tr></tr>
<tr>
<td colspan=3>I</td>
</tr>
</table>
Try this:
<table id="tbl">
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td colspan="2" rowspan="2">A</td>
<td rowspan="2">C</td>
</tr>
<tr>
<td rowspan="2">E</td>
</tr>
<tr>
<td>F</td>
<td>G</td>
</tr>
</table>
Script:
var finalResult = '';
var totalTds = $('#tbl TR')[0].length;
var trArray = [];
var trArrayValue = [];
var trIndex = 1;
$('#tbl TR').each(function(){
var currentTr = $(this);
var tdIndex = 1;
trArray[trIndex] = [];
trArrayValue[trIndex] = [];
var tdActuallyTraversed = 0;
var colspanCount = 1;
$('#tbl TR').first().children().each(function(){
if(trIndex > 1 && trArray[trIndex - 1][tdIndex] > 1)
{
trArray[trIndex][tdIndex] = trArray[trIndex - 1][tdIndex] - 1;
trArrayValue[trIndex][tdIndex] = trArrayValue[trIndex - 1][tdIndex];
finalResult = finalResult + trArrayValue[trIndex][tdIndex];
}
else
{
if(colspanCount <= 1)
{
colspanCount = currentTr.children().eq(tdActuallyTraversed).attr('colspan') != undefined ? currentTr.children().eq(tdActuallyTraversed).attr('colspan') : 1;
}
if(colspanCount > 1 && tdIndex > 1)
{
trArray[trIndex][tdIndex] = currentTr.children().eq(tdActuallyTraversed + colspanCount).attr('rowspan') != undefined ?currentTr.children().eq(tdActuallyTraversed + colspanCount).attr('rowspan') : 1;
trArrayValue[trIndex][tdIndex] = trArrayValue[trIndex][tdIndex - 1];
colspanCount--;
}
else
{
trArray[trIndex][tdIndex] = currentTr.children().eq(tdActuallyTraversed).attr('rowspan') != undefined ?currentTr.children().eq(tdActuallyTraversed).attr('rowspan') : 1;
trArrayValue[trIndex][tdIndex] = currentTr.children().eq(tdActuallyTraversed).html();
tdActuallyTraversed++;
}
finalResult = finalResult + trArrayValue[trIndex][tdIndex];
}
tdIndex++;
});
trIndex++;
});
alert(finalResult);
Fiddle
i am not sure about the performance, but it works well.
what I understood with your question is: You want to split the merged cell with same value and then iterate the table simply by row.
I've created a JSFiddle that will split the merged cells with the same value. Then you'll have a table that can be iterated simply by rows to get the desired output that you specified.
See it running here http://jsfiddle.net/9PZQj/3/
Here's the complete code:
<table id="tbl" border = "1">
<tr>
<td>A</td>
<td>B</td>
<td rowspan="2">C</td>
</tr>
<tr>
<td>D</td>
<td rowspan="2">E</td>
</tr>
<tr>
<td>F</td>
<td>G</td>
</tr>
</table>
<br>
<div id="test"> </div>
Here's the jquery that is used to manipulate the table's data.
var tempTable = $('#tbl').clone(true);
var tableBody = $(tempTable).children();
$(tableBody).children().each(function(index , item){
var currentRow = item;
$(currentRow).children().each(function(index1, item1){
if($(item1).attr("rowspan"))
{
// copy the cell
var item2 = $(item1).clone(true);
// Remove rowspan
$(item1).removeAttr("rowspan");
$(item2).removeAttr("rowspan");
// last item's index in next row
var indexOfLastElement = $(currentRow).next().last().index();
if(indexOfLastElement <= index1)
{
$(currentRow).next().append(item2)
}
else
{
// intermediate cell insertion at right position
$(item2).insertBefore($(currentRow).next().children().eq(index1))
}
}
});
console.log(currentRow)
});
$('#test').append(tempTable);
You can use this Gist. It supports all the requirements by W3C, even "rowspan=0" (which seems to be only supported by Firefox).