<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nichola Chaus</title>
    <description>The latest articles on DEV Community by Nichola Chaus (@nicholachaus).</description>
    <link>https://dev.to/nicholachaus</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4174765%2F0be44f2b-125f-45cb-862c-1e5e2366cbc4.png</url>
      <title>DEV Community: Nichola Chaus</title>
      <link>https://dev.to/nicholachaus</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nicholachaus"/>
    <language>en</language>
    <item>
      <title>I built a bank statement to Excel converter that never uploads your file</title>
      <dc:creator>Nichola Chaus</dc:creator>
      <pubDate>Sat, 10 Oct 2026 07:06:33 +0000</pubDate>
      <link>https://dev.to/nicholachaus/i-built-a-bank-statement-to-excel-converter-that-never-uploads-your-file-kji</link>
      <guid>https://dev.to/nicholachaus/i-built-a-bank-statement-to-excel-converter-that-never-uploads-your-file-kji</guid>
      <description>&lt;p&gt;Most "bank statement to Excel" sites ask you to upload the PDF to their server. For a document with your account number, balance and every purchase you made this month, that always felt like the wrong trade.&lt;/p&gt;

&lt;p&gt;So the converter I built runs entirely in the browser. Here is how it works and where it breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the PDF without a server
&lt;/h2&gt;

&lt;p&gt;A statement downloaded from online banking is a text PDF: every date, description and amount is a positioned text item. pdf.js (Mozilla's PDF engine, the one inside Firefox) gives you those items with x/y coordinates via page.getTextContent(). No upload, no OCR, it all happens on the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning text items into a table
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Cluster items into rows by their y coordinate, with a tolerance of about half the median text height.&lt;/li&gt;
&lt;li&gt;Find column anchors by clustering x positions across rows.&lt;/li&gt;
&lt;li&gt;Amounts are right-aligned, so their left edges drift (-4.75 vs -1,450.00). Before clustering I snap numbers that share a right edge to one column. Without this, debit and credit columns fall apart.&lt;/li&gt;
&lt;li&gt;Merge sparse columns, cap the width, glue wrapped lines back to their row.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last step bit me this week: a receipt with amounts like RUB 5,476.82 was not recognised as numbers, so the bigger bold total drifted left and got glued to the line above. Currency codes are now treated like currency symbols.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing a real .xlsx
&lt;/h2&gt;

&lt;p&gt;An .xlsx is a zip of XML files, so JSZip plus a small writer is enough. The first version wrote every cell as text, and SUM over the amount column returned 0. Now amounts like 1,488.00, (25.40) or 1.488,00 are written as numbers, while dates and account numbers with leading zeros stay text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it breaks
&lt;/h2&gt;

&lt;p&gt;[x] Scanned statements are pictures, there is no text layer. The page detects this and says so instead of producing an empty sheet.&lt;br&gt;
[x] The whole page lands in the sheet: bank name, address, footers. You delete those rows.&lt;br&gt;
[x] Very wide statements (more than 6 columns) get neighbouring columns merged.&lt;/p&gt;

&lt;p&gt;You can try it here, the file never leaves your machine: &lt;a href="https://www.devexthub.com/pdf-to-excel/bank-statement/" rel="noopener noreferrer"&gt;https://www.devexthub.com/pdf-to-excel/bank-statement/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have a statement layout that comes out wrong, I'd like to hear about it.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>privacy</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
