<?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: ZeraBooks</title>
    <description>The latest articles on DEV Community by ZeraBooks (zerabooks).</description>
    <link>https://dev.to/zerabooks</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%2Forganization%2Fprofile_image%2F12438%2Fe2a47c3e-66f4-48bc-a405-dadbc0d26dbf.png</url>
      <title>DEV Community: ZeraBooks</title>
      <link>https://dev.to/zerabooks</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zerabooks"/>
    <language>en</language>
    <item>
      <title>Parsing OFX bank files by hand: SGML without end tags, GMT dates and backwards card signs</title>
      <dc:creator>Damin Mutti</dc:creator>
      <pubDate>Fri, 16 Oct 2026 01:39:00 +0000</pubDate>
      <link>https://dev.to/zerabooks/parsing-ofx-bank-files-by-hand-sgml-without-end-tags-gmt-dates-and-backwards-card-signs-1b7d</link>
      <guid>https://dev.to/zerabooks/parsing-ofx-bank-files-by-hand-sgml-without-end-tags-gmt-dates-and-backwards-card-signs-1b7d</guid>
      <description>&lt;p&gt;An OFX file is a bank or card download in Open Financial Exchange, the format Microsoft, Intuit and CheckFree announced in 1997. Version 1.x is SGML, where a leaf value like &lt;code&gt;&amp;lt;TRNAMT&amp;gt;-64.18&lt;/code&gt; has no end tag. Version 2.x is XML with every element closed. To parse either one correctly you need three things: a tolerant tokenizer, careful date handling and a sign rule that ignores TRNTYPE.&lt;/p&gt;

&lt;p&gt;If you only need the data, use &lt;code&gt;ofxtools&lt;/code&gt;, the open-source Python library that reads both styles and QFX too. This post is for when you want to know what the library is doing, or you are stuck on a file it rejects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: tell the two versions apart
&lt;/h2&gt;

&lt;p&gt;Read the first non-empty line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OFXHEADER:100        -&amp;gt; 1.x, SGML, header is key:value lines
&amp;lt;?xml version=...    -&amp;gt; 2.x, XML, followed by &amp;lt;?OFX OFXHEADER="200" ...?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 2.x file goes straight into any XML parser. A 1.x file will not, because the header is not XML and most leaves are never closed. Branch on this line before anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: tokenize 1.x SGML without end tags
&lt;/h2&gt;

&lt;p&gt;In the 1.x style, aggregates (the blocks, like &lt;code&gt;&amp;lt;STMTTRN&amp;gt;&lt;/code&gt;) open and close. Elements (single values) only open: the value runs until the next &lt;code&gt;&amp;lt;&lt;/code&gt;. So the rule is simple. A tag followed by text is a leaf. A tag followed directly by another tag opens a block.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;TAG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;(/?)([A-Z0-9.]+)&amp;gt;([^&amp;lt;]*)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_sgml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;OFX&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):]&lt;/span&gt;          &lt;span class="c1"&gt;# skip the key:value header
&lt;/span&gt;    &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ROOT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;children&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;
    &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;closing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;closing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Close a block only if it is open. A stray end tag on a
&lt;/span&gt;            &lt;span class="c1"&gt;# leaf is ignored instead of collapsing the whole tree.
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;children&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;children&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;
            &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;children&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two notes. Tag names include dots because Intuit's extensions look like &lt;code&gt;INTU.BID&lt;/code&gt;. And the guard on closing tags matters: write it as "pop until the name matches" without checking first, and one unexpected end tag empties your stack.&lt;/p&gt;

&lt;p&gt;Once you have the tree, the path to transactions is &lt;code&gt;OFX &amp;gt; BANKMSGSRSV1 &amp;gt; STMTTRNRS &amp;gt; STMTRS &amp;gt; BANKTRANLIST &amp;gt; STMTTRN&lt;/code&gt; for bank accounts, and &lt;code&gt;CREDITCARDMSGSRSV1 &amp;gt; CCSTMTTRNRS &amp;gt; CCSTMTRS &amp;gt; BANKTRANLIST &amp;gt; STMTTRN&lt;/code&gt; for cards. Check both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: dates, time zones and the day that moves
&lt;/h2&gt;

&lt;p&gt;The full DTPOSTED form is &lt;code&gt;YYYYMMDDHHMMSS.XXX[offset:TZ]&lt;/code&gt;, for example &lt;code&gt;20260914093000.000[-5:CDT]&lt;/code&gt;. The offset is hours from GMT and the name after the colon is optional. Banks may truncate from the right, so &lt;code&gt;20260914&lt;/code&gt; alone is legal, and the spec says to read a truncated value as GMT.&lt;/p&gt;

&lt;p&gt;That last rule causes a classic bug. Parse &lt;code&gt;20260914&lt;/code&gt; as midnight GMT, convert to America/Chicago, and the transaction lands on September 13. Your output disagrees with the PDF statement by a day, for every transaction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="n"&gt;DT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(\d{8})(\d{6})?(?:\.\d+)?(?:\[([+-]?[\d.]+)(?::\w+)?\])?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_ofx_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;clock&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Date only. Keep it a calendar date; don't push it through a zone.
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y%m%d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;tz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y%m%d%H%M%S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This handles the full form and the date-only form. If your bank sends other truncations, such as hours and minutes without seconds, add those cases explicitly rather than loosening the regex until anything matches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: card signs and why TRNTYPE is a trap
&lt;/h2&gt;

&lt;p&gt;TRNAMT is signed by the transaction's effect on the customer. On checking, deposits are positive and purchases negative. On a credit card, a purchase is negative because it raises what you owe, and a payment to the card is positive. LEDGERBAL on a card is negative when there is a balance owed.&lt;/p&gt;

&lt;p&gt;TRNTYPE (DEBIT, CREDIT, INT and so on) is a label. The spec is clear that the sign decides the effect, so &lt;code&gt;INT&lt;/code&gt; can be interest earned on savings (positive) or interest charged on a card (negative). Code that does &lt;code&gt;if trntype == "DEBIT": amount = -abs(amount)&lt;/code&gt; will get real files wrong.&lt;/p&gt;

&lt;p&gt;If you are loading a card into a double-entry ledger as a liability, flip the sign once, at the account level, based on whether the statement came from &lt;code&gt;CCSTMTRS&lt;/code&gt;. Never per transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: dedupe on FITID
&lt;/h2&gt;

&lt;p&gt;Every STMTTRN carries a FITID, the bank's unique ID for that transaction. Store it. Overlapping downloads are common, and FITID is the only reliable key; date plus amount plus NAME collides on two identical coffees.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing the file doesn't contain
&lt;/h2&gt;

&lt;p&gt;A statement download has LEDGERBAL at the end but no opening balance. You can't prove completeness from the file alone. If you need that proof, compare the derived opening (LEDGERBAL minus the sum of TRNAMT) with the previous period's closing balance.&lt;/p&gt;

&lt;p&gt;Disclosure: I'm Damin, I build ZeraBooks. We took the other route: ZeraBooks reads the PDF statement instead, because it prints both balances, and writes &lt;a href="https://www.zerabooks.com/convert-bank-statement-to-csv" rel="noopener noreferrer"&gt;CSV from the bank statement&lt;/a&gt; after checking the totals tie. It doesn't read or write OFX. The full annotated 2.x sample and the sign table are in our page on &lt;a href="https://www.zerabooks.com/what-is-an-ofx-file" rel="noopener noreferrer"&gt;the OFX file format&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>parsing</category>
      <category>fintech</category>
      <category>xml</category>
    </item>
  </channel>
</rss>
