/****************************** HSBC Personal Statement to CSV v0.5 Copyright: Benjie Gillam (2012) License: WTFPL v2.0 ( http://en.wikipedia.org/wiki/WTFPL ) Instructions: Add the following bookmarklet to your browser: javascript:(function(s)%7Bvar%20e%3Ddocument.createElement('script')%3Be.src%3Ds%3Bdocument.head.appendChild(e)%3B%7D)('https%3A%2F%2Fraw.github.com%2Fgist%2F2157849%2FHSBC.js') Open this bookmarklet when you are looking at a previous statement or recent transactions in HSBC personal online banking and it will bring up a CSV copy of your transactions from that statement/list that you can copy to your clipboard and paste into a file (e.g. to import into Xero or similar services). Changelog: v0.5 - Add AJAX fetching of a full year of statements. v0.4.1 - Add status updates for AJAX fetches. v0.4 - Add AJAX fetching of extra information for abbreviated transactions v0.3 - Fix dates for accounts with Overdraft Limits v0.2 - Adds support for recent transactions, fixes balance=0 v0.1 - initial release Compatibility: only tested in Chrome 17 under Mac OS X against my own accounts. Use at your own risk. Educational purposes only. Etc etc. ******************************/ var textarea = null; var superarray = []; var additionalPending = 0; var statementPending = 0; var errorOccurred = false; var ascending = true; var div = document.createElement('div'); var h1 = document.createElement('h1'); h1.innerText = "Double-click to dismiss"; //div.insertBefore(h1,div.firstChild); div.appendChild(h1); div.appendChild(document.createTextNode("Date, Transaction Type, Payee, Amount, Balance (GBP)")); textarea = document.createElement('textarea'); textarea.style.width="100%"; textarea.style.height = "80%"; textarea.value = "If this message remains, then an error has occurred"; div.appendChild(textarea); div.style.position="fixed"; div.style.top = "100px"; div.style.bottom = "100px"; div.style.border = "10px solid black"; div.style.borderRadius = "25px"; div.style.padding = "20px"; div.style.left = "100px"; div.style.right = "100px"; div.style.zIndex = "100"; div.style.backgroundColor = "white"; div.addEventListener('dblclick',function(e) { div.parentNode.removeChild(div); },false); document.body.appendChild(div); function checkFinished() { if (errorOccurred) { div.style.backgroundColor = "red"; } if (additionalPending+statementPending == 0) { done(); } else { textarea.value = additionalPending+" additional info fetches remaining; "+statementPending+" statements fetches remaining..."; } }; function done() { superarray.reverse(); var results = []; for (var j = 0; j < superarray.length; j++) { var array = superarray[j]; if (!ascending) { array.reverse(); } for (var i = 0; i<array.length; i++) { results.push(array[i].join(",")); } } csv = results.join("\n"); textarea.value = csv; textarea.focus(); textarea.select(); } function parseStatement(statement,$,$rootEl,array) { var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; var previous = true; var tmp = $rootEl.find("div .hsbcTextRight").first()[0].innerText.split(" "); if (tmp.length > 4) { tmp = $rootEl.find("div .extPibRow .extTableColumn2").eq(2)[0].innerText.split(" "); previous = false; } var statementDay = parseInt(tmp[0].replace(/^0/,"")); var statementMonth = months.indexOf(tmp[1]); var statementYear = parseInt(tmp[2]); if (statementMonth == 0 && ascending) { statementYear--; } var table = $rootEl.find(".containerMain table").eq(1); var trs = table.find('tbody tr'); var currentMonth = statementMonth; var currentBalance = 0; for (var i = 0, l = trs.length; i<l; i++) { var tr = trs.eq(i); if (previous && (i == 0 || i == l-1)) { currentBalance = parseFloat(tr.find('td')[5].innerText); // Skip "Balance brought/carried forward" lines continue; } (function(){ var row = []; var tds = tr.find('td'); var amount = 0; for (var j = 0, m = tds.length; j<m; j++) { if (j >= 6) { continue; } var td = tds[j]; var skip = false; str = td.innerText; if (!previous) { var tmp = str.split("\n"); for (var k = 0; k < tmp.length; k++) { tmp[k] = $.trim(tmp[k]); } str = tmp.join("\n"); str = str.replace(/\n\n/g,"\n"); } str = str.replace(/[\v\0\r\t]/g," "); str = str.replace(/(^ | (?= )| $)/g,""); str = str.replace(/"/g,"\\\""); str = $.trim(str); str = str.replace(/\n/g, " | "); if (j == 0) { tmp = str.split(" "); var day = parseInt(tmp[0].replace(/^0/,"")); var month = months.indexOf(tmp[1]); if (currentMonth == 11 && month == 0) { statementYear++; } currentMonth = month; strMonth = ""+(month+1); if (strMonth.length < 2) { strMonth = "0"+strMonth; } strDay = ""+day; if (strDay.length < 2) { strDay = "0"+strDay; } str = ""+statementYear+"-"+strMonth+"-"+strDay; } if (j < 3) { str = "\""+str+"\""; if (j == 2) { // AJAX fetch more details. var a = tds.eq(j).find("a"); if (a && a.length) { var href = a.attr("href"); console.log("Additional: "+statement+" :: "+a[0].innerText); additionalPending++; $.ajax({ url:href }).done(function(data){ var a = data.indexOf("<strong>Additional details:</strong>"); if (a != -1) { var relevant = data.substr(a,1000); relevant = relevant.replace(/^[\s\S]*?<p>([\s\S]*?)<\/p>[\s\S]*$/,"$1"); relevant = relevant.replace(/<br \/>/g,""); relevant = $.trim(relevant); var tmp = relevant.split("\n"); for (var i = 0; i < tmp.length; i++) { tmp[i] = $.trim(tmp[i]); } relevant = tmp.join("\n"); relevant = relevant.replace(/\n\n/g,"\n"); relevant = relevant.replace(/\n/g," | "); row[2] = "\""+relevant+"\""; console.log(relevant); } additionalPending--; checkFinished(); }); } } } else { str = parseFloat(str); if (isNaN(str)) { str = 0.0; } if (j == 3) { amount -= str; skip = true; } else if (j == 4) { amount += str; str = amount; } else if (j == 5) { if (previous) { if (str == 0.0) { currentBalance = str = parseInt((currentBalance + amount)*100)/100; } else { currentBalance = str; } } } } if (!skip) { row.push(str); } } array.push(row); })(); } checkFinished(); } (function($){ var trs = $(".hsbcMainContent table.hsbcRowSeparator tr"); if (!trs || trs.length < 2) { var array = []; superarray.push(array); parseStatement("Current Page", $, $(document.body), array); } else { for (var i = 1; i<trs.length; i++) { (function(){ var a = trs.eq(i).find("td a").eq(0); var href = a.attr("href"); var array = []; superarray.push(array); if (href && href.length) { // Fetch it statement = a[0].innerText; console.log("Statement: "+statement); statementPending++; $.ajax({ url:href }).done(function(data){ data = data.replace(/^[\s\S]*<body[^>]*>([\s\S]*)<\/body[\s\S]*$/,"$1"); var div = document.createElement('div'); div.innerHTML = data; var $div = $(div); parseStatement(statement, $, $div, array); statementPending--; checkFinished(); }); } else { console.error("One of the links doesn't work!!"); errorOccurred = true; } })(); } } })(jQuery); checkFinished();