Repository files navigation

License: MITStatic BadgeStatic Badgetest

ebics-parser

Lightweight and very fast parser for EBICS files for Node.js & TypeScript. Only two deps (date-fns, zod).

Used by Airporting

Tested from NodeJS 18.x to 24.x

Linkedin

Setup

# Install
yarn add @airporting/ebics-parser
# Or with npm
npm install @airporting/ebics-parser

Requires Node.js 18+.

Usage

As a library

importebicsParserfrom'@airporting/ebics-parser';constfileContent=`0130012 00585EUR2 00010156142 280323 0000006564827I0430012056800585EUR2 0001015614218290323 290323DOMUSVI 0000000 0000000359190{0530012056800585EUR2 0001015614218290323 NPYDOMUSVI0530012056800585EUR2 0001015614218290323 LCC/INV/3867 8.2.20230530012056800585EUR2 0001015614218290323 RCNNOTPROVIDED0530012056800585EUR2 0001015614218290323 PDOFR FRANCE0730012 00585EUR2 00010156142 290323 0000006363387I`;constresult=ebicsParser(fileContent);console.log(result);

CLI tool

yarn parse <filepath>

Reads an EBICS file, parses it, and writes the result as JSON to <filename>.parsed.json in the same directory.

Result structure

[{details: {countTransactions: 1,},amounts: {start: 656482.79,// opening balanceend: 636338.79,// closing balancediff: 20144,// |start - end|transactions: 35919,// sum of transaction amounts},header: {record_code: '01',bank_code: '30012',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',prev_date: '2023-03-28',prev_amount: '656482.79',// _1.._5: padding fields},footer: {record_code: '07',bank_code: '30012',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',next_date: '2023-03-29',next_amount: '636338.79',},transactions: [{record_code: '04',bank_code: '30012',internal_code: '0568',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',operation_code: '18',operation_date: '2023-03-29',value_date: '2023-03-29',label: 'DOMUSVI',amount: '35919',debtor_name: 'DOMUSVI',remittance_information_1: '/INV/3867 8.2.2023',end2end_identification: 'NOTPROVIDED',PDO: 'FR FRANCE',_rank: '0',// position in transaction list (not an official field)},],// problems detected during parse, null if noneproblems: [{message: "Sum of transactions (35919) doesn't match with difference between start amount 656482.79 and end amount 636338.79",},],},]

Architecture

src/
├── index.ts # Entry point: split lines, demultiplex, chunk by account
├── cli.ts # CLI tool (yarn parse)
├── account/
│ ├── index.ts # Per-account parser: orchestrates header/footer/transactions
│ └── schema.ts # Zod validation schema for parsed accounts
├── header/index.ts # Record 01 parser (opening balance)
├── footer/index.ts # Record 07 parser (closing balance)
├── transactionHeader/index.ts # Record 04/03 parser (transaction main line)
├── transactionBody/
│ ├── index.ts # Record 05 parser (transaction detail line)
│ └── qualifier/index.ts # SEPA qualifier resolver (CBE, IBE, NBE, etc.)
├── transaction/index.ts # Shared types (TransactionFields, QualifierType)
├── amount/index.ts # EBICS amount decoding (letter → digit + sign)
└── shared/
└── parseLineWithParts.ts # Generic regex-based line parser (shared by all record parsers)

Parsing flow

  1. Split & clean — Input text is split into lines, empty lines removed
  2. Demultiplex — If all lines start with 01, detect concatenated records and split them
  3. Chunk — Group lines by account (each 01 line starts a new account)
  4. Parse account — For each chunk:
    • 01headerParser (opening balance)
    • 04/03transactionHeaderParser (transaction)
    • 05transactionBodyParserqualifierResolver (SEPA details)
    • 07footerParser (closing balance)
  5. Validate — Zod schema validation, amount consistency check

EBICS line record codes

CodeDescription
01Account header (opening balance)
03Transaction (variant of 04, treated as 04)
04Transaction main line
05Transaction detail (qualifier-based SEPA fields)
07Account footer (closing balance)

External docs to understand the EBICS standard

https://www.ca-centreloire.fr/Vitrine/ObjCommun/Fic/CentreLoire/Entreprise/GuideParamRel_EDI.pdf

With more details on qualifier "05" (additional details on transaction, qualifier "04") https://www.promosoft.fr/wp-content/uploads/2013/06/Livre_blanc_SEPA.pdf

Opinions, known bugs & pitfalls

Opinions

We took some decisions about what is allowed or not in an EBICS parse result. We still don't have found a complete and exhaustive docs about this format. So everything is based on our guesses. See section "Whatever".

In transactions items, fields internal_code and operation_code need at least one of them to be filled.

Line record code

Most lines start with code 01 (start balance), 07 (end balance), 04 (main transaction line) and 05 (transaction detail line). But we already met lines with record code 03. We have guess that it's a very similar usage of record code 04, so for the moment, when met, transformed in 04.

Transactions _rank

For each transaction in an account, we have added a field _rank. It helps to know at which position in the account it was positionned.

Details

For each account, we provide a set of data which is guessed by our parser. It's NOT provided by the original file. See the example above to know the exact usage.

Whatever

  • We try to manage when carriage return is not present in the raw text file, we didn't found any bug yet but you probably will. We do the best we can.
  • If you find a bug, don't hesitate to open an issue or a pull request.
  • If you have a doc with complete specs, we'll thanks you a lot.

Thanks

Made by Airporting people with 🧡.

About

Parser for EBICS file (from CFONB) for Node.JS and TypeScript

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

License: MITStatic BadgeStatic Badgetest

ebics-parser

Lightweight and very fast parser for EBICS files for Node.js & TypeScript. Only two deps (date-fns, zod).

Used by Airporting

Tested from NodeJS 18.x to 24.x

Linkedin

Setup

# Install
yarn add @airporting/ebics-parser
# Or with npm
npm install @airporting/ebics-parser

Requires Node.js 18+.

Usage

As a library

importebicsParserfrom'@airporting/ebics-parser';constfileContent=`0130012 00585EUR2 00010156142 280323 0000006564827I0430012056800585EUR2 0001015614218290323 290323DOMUSVI 0000000 0000000359190{0530012056800585EUR2 0001015614218290323 NPYDOMUSVI0530012056800585EUR2 0001015614218290323 LCC/INV/3867 8.2.20230530012056800585EUR2 0001015614218290323 RCNNOTPROVIDED0530012056800585EUR2 0001015614218290323 PDOFR FRANCE0730012 00585EUR2 00010156142 290323 0000006363387I`;constresult=ebicsParser(fileContent);console.log(result);

CLI tool

yarn parse <filepath>

Reads an EBICS file, parses it, and writes the result as JSON to <filename>.parsed.json in the same directory.

Result structure

[{details: {countTransactions: 1,},amounts: {start: 656482.79,// opening balanceend: 636338.79,// closing balancediff: 20144,// |start - end|transactions: 35919,// sum of transaction amounts},header: {record_code: '01',bank_code: '30012',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',prev_date: '2023-03-28',prev_amount: '656482.79',// _1.._5: padding fields},footer: {record_code: '07',bank_code: '30012',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',next_date: '2023-03-29',next_amount: '636338.79',},transactions: [{record_code: '04',bank_code: '30012',internal_code: '0568',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',operation_code: '18',operation_date: '2023-03-29',value_date: '2023-03-29',label: 'DOMUSVI',amount: '35919',debtor_name: 'DOMUSVI',remittance_information_1: '/INV/3867 8.2.2023',end2end_identification: 'NOTPROVIDED',PDO: 'FR FRANCE',_rank: '0',// position in transaction list (not an official field)},],// problems detected during parse, null if noneproblems: [{message: "Sum of transactions (35919) doesn't match with difference between start amount 656482.79 and end amount 636338.79",},],},]

Architecture

src/
├── index.ts # Entry point: split lines, demultiplex, chunk by account
├── cli.ts # CLI tool (yarn parse)
├── account/
│ ├── index.ts # Per-account parser: orchestrates header/footer/transactions
│ └── schema.ts # Zod validation schema for parsed accounts
├── header/index.ts # Record 01 parser (opening balance)
├── footer/index.ts # Record 07 parser (closing balance)
├── transactionHeader/index.ts # Record 04/03 parser (transaction main line)
├── transactionBody/
│ ├── index.ts # Record 05 parser (transaction detail line)
│ └── qualifier/index.ts # SEPA qualifier resolver (CBE, IBE, NBE, etc.)
├── transaction/index.ts # Shared types (TransactionFields, QualifierType)
├── amount/index.ts # EBICS amount decoding (letter → digit + sign)
└── shared/
└── parseLineWithParts.ts # Generic regex-based line parser (shared by all record parsers)

Parsing flow

  1. Split & clean — Input text is split into lines, empty lines removed
  2. Demultiplex — If all lines start with 01, detect concatenated records and split them
  3. Chunk — Group lines by account (each 01 line starts a new account)
  4. Parse account — For each chunk:
    • 01headerParser (opening balance)
    • 04/03transactionHeaderParser (transaction)
    • 05transactionBodyParserqualifierResolver (SEPA details)
    • 07footerParser (closing balance)
  5. Validate — Zod schema validation, amount consistency check

EBICS line record codes

CodeDescription
01Account header (opening balance)
03Transaction (variant of 04, treated as 04)
04Transaction main line
05Transaction detail (qualifier-based SEPA fields)
07Account footer (closing balance)

External docs to understand the EBICS standard

https://www.ca-centreloire.fr/Vitrine/ObjCommun/Fic/CentreLoire/Entreprise/GuideParamRel_EDI.pdf

With more details on qualifier "05" (additional details on transaction, qualifier "04") https://www.promosoft.fr/wp-content/uploads/2013/06/Livre_blanc_SEPA.pdf

Opinions, known bugs & pitfalls

Opinions

We took some decisions about what is allowed or not in an EBICS parse result. We still don't have found a complete and exhaustive docs about this format. So everything is based on our guesses. See section "Whatever".

In transactions items, fields internal_code and operation_code need at least one of them to be filled.

Line record code

Most lines start with code 01 (start balance), 07 (end balance), 04 (main transaction line) and 05 (transaction detail line). But we already met lines with record code 03. We have guess that it's a very similar usage of record code 04, so for the moment, when met, transformed in 04.

Transactions _rank

For each transaction in an account, we have added a field _rank. It helps to know at which position in the account it was positionned.

Details

For each account, we provide a set of data which is guessed by our parser. It's NOT provided by the original file. See the example above to know the exact usage.

Whatever

  • We try to manage when carriage return is not present in the raw text file, we didn't found any bug yet but you probably will. We do the best we can.
  • If you find a bug, don't hesitate to open an issue or a pull request.
  • If you have a doc with complete specs, we'll thanks you a lot.

Thanks

Made by Airporting people with 🧡.

About

Parser for EBICS file (from CFONB) for Node.JS and TypeScript

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

License: MITStatic BadgeStatic Badgetest

ebics-parser

Lightweight and very fast parser for EBICS files for Node.js & TypeScript. Only two deps (date-fns, zod).

Used by Airporting

Tested from NodeJS 18.x to 24.x

Linkedin

Setup

# Install
yarn add @airporting/ebics-parser
# Or with npm
npm install @airporting/ebics-parser

Requires Node.js 18+.

Usage

As a library

importebicsParserfrom'@airporting/ebics-parser';constfileContent=`0130012 00585EUR2 00010156142 280323 0000006564827I0430012056800585EUR2 0001015614218290323 290323DOMUSVI 0000000 0000000359190{0530012056800585EUR2 0001015614218290323 NPYDOMUSVI0530012056800585EUR2 0001015614218290323 LCC/INV/3867 8.2.20230530012056800585EUR2 0001015614218290323 RCNNOTPROVIDED0530012056800585EUR2 0001015614218290323 PDOFR FRANCE0730012 00585EUR2 00010156142 290323 0000006363387I`;constresult=ebicsParser(fileContent);console.log(result);

CLI tool

yarn parse <filepath>

Reads an EBICS file, parses it, and writes the result as JSON to <filename>.parsed.json in the same directory.

Result structure

[{details: {countTransactions: 1,},amounts: {start: 656482.79,// opening balanceend: 636338.79,// closing balancediff: 20144,// |start - end|transactions: 35919,// sum of transaction amounts},header: {record_code: '01',bank_code: '30012',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',prev_date: '2023-03-28',prev_amount: '656482.79',// _1.._5: padding fields},footer: {record_code: '07',bank_code: '30012',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',next_date: '2023-03-29',next_amount: '636338.79',},transactions: [{record_code: '04',bank_code: '30012',internal_code: '0568',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',operation_code: '18',operation_date: '2023-03-29',value_date: '2023-03-29',label: 'DOMUSVI',amount: '35919',debtor_name: 'DOMUSVI',remittance_information_1: '/INV/3867 8.2.2023',end2end_identification: 'NOTPROVIDED',PDO: 'FR FRANCE',_rank: '0',// position in transaction list (not an official field)},],// problems detected during parse, null if noneproblems: [{message: "Sum of transactions (35919) doesn't match with difference between start amount 656482.79 and end amount 636338.79",},],},]

Architecture

src/
├── index.ts # Entry point: split lines, demultiplex, chunk by account
├── cli.ts # CLI tool (yarn parse)
├── account/
│ ├── index.ts # Per-account parser: orchestrates header/footer/transactions
│ └── schema.ts # Zod validation schema for parsed accounts
├── header/index.ts # Record 01 parser (opening balance)
├── footer/index.ts # Record 07 parser (closing balance)
├── transactionHeader/index.ts # Record 04/03 parser (transaction main line)
├── transactionBody/
│ ├── index.ts # Record 05 parser (transaction detail line)
│ └── qualifier/index.ts # SEPA qualifier resolver (CBE, IBE, NBE, etc.)
├── transaction/index.ts # Shared types (TransactionFields, QualifierType)
├── amount/index.ts # EBICS amount decoding (letter → digit + sign)
└── shared/
└── parseLineWithParts.ts # Generic regex-based line parser (shared by all record parsers)

Parsing flow

  1. Split & clean — Input text is split into lines, empty lines removed
  2. Demultiplex — If all lines start with 01, detect concatenated records and split them
  3. Chunk — Group lines by account (each 01 line starts a new account)
  4. Parse account — For each chunk:
    • 01headerParser (opening balance)
    • 04/03transactionHeaderParser (transaction)
    • 05transactionBodyParserqualifierResolver (SEPA details)
    • 07footerParser (closing balance)
  5. Validate — Zod schema validation, amount consistency check

EBICS line record codes

CodeDescription
01Account header (opening balance)
03Transaction (variant of 04, treated as 04)
04Transaction main line
05Transaction detail (qualifier-based SEPA fields)
07Account footer (closing balance)

External docs to understand the EBICS standard

https://www.ca-centreloire.fr/Vitrine/ObjCommun/Fic/CentreLoire/Entreprise/GuideParamRel_EDI.pdf

With more details on qualifier "05" (additional details on transaction, qualifier "04") https://www.promosoft.fr/wp-content/uploads/2013/06/Livre_blanc_SEPA.pdf

Opinions, known bugs & pitfalls

Opinions

We took some decisions about what is allowed or not in an EBICS parse result. We still don't have found a complete and exhaustive docs about this format. So everything is based on our guesses. See section "Whatever".

In transactions items, fields internal_code and operation_code need at least one of them to be filled.

Line record code

Most lines start with code 01 (start balance), 07 (end balance), 04 (main transaction line) and 05 (transaction detail line). But we already met lines with record code 03. We have guess that it's a very similar usage of record code 04, so for the moment, when met, transformed in 04.

Transactions _rank

For each transaction in an account, we have added a field _rank. It helps to know at which position in the account it was positionned.

Details

For each account, we provide a set of data which is guessed by our parser. It's NOT provided by the original file. See the example above to know the exact usage.

Whatever

  • We try to manage when carriage return is not present in the raw text file, we didn't found any bug yet but you probably will. We do the best we can.
  • If you find a bug, don't hesitate to open an issue or a pull request.
  • If you have a doc with complete specs, we'll thanks you a lot.

Thanks

Made by Airporting people with 🧡.

About

Parser for EBICS file (from CFONB) for Node.JS and TypeScript

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

License: MITStatic BadgeStatic Badgetest

ebics-parser

Lightweight and very fast parser for EBICS files for Node.js & TypeScript. Only two deps (date-fns, zod).

Used by Airporting

Tested from NodeJS 18.x to 24.x

Linkedin

Setup

# Install
yarn add @airporting/ebics-parser
# Or with npm
npm install @airporting/ebics-parser

Requires Node.js 18+.

Usage

As a library

importebicsParserfrom'@airporting/ebics-parser';constfileContent=`0130012 00585EUR2 00010156142 280323 0000006564827I0430012056800585EUR2 0001015614218290323 290323DOMUSVI 0000000 0000000359190{0530012056800585EUR2 0001015614218290323 NPYDOMUSVI0530012056800585EUR2 0001015614218290323 LCC/INV/3867 8.2.20230530012056800585EUR2 0001015614218290323 RCNNOTPROVIDED0530012056800585EUR2 0001015614218290323 PDOFR FRANCE0730012 00585EUR2 00010156142 290323 0000006363387I`;constresult=ebicsParser(fileContent);console.log(result);

CLI tool

yarn parse <filepath>

Reads an EBICS file, parses it, and writes the result as JSON to <filename>.parsed.json in the same directory.

Result structure

[{details: {countTransactions: 1,},amounts: {start: 656482.79,// opening balanceend: 636338.79,// closing balancediff: 20144,// |start - end|transactions: 35919,// sum of transaction amounts},header: {record_code: '01',bank_code: '30012',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',prev_date: '2023-03-28',prev_amount: '656482.79',// _1.._5: padding fields},footer: {record_code: '07',bank_code: '30012',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',next_date: '2023-03-29',next_amount: '636338.79',},transactions: [{record_code: '04',bank_code: '30012',internal_code: '0568',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',operation_code: '18',operation_date: '2023-03-29',value_date: '2023-03-29',label: 'DOMUSVI',amount: '35919',debtor_name: 'DOMUSVI',remittance_information_1: '/INV/3867 8.2.2023',end2end_identification: 'NOTPROVIDED',PDO: 'FR FRANCE',_rank: '0',// position in transaction list (not an official field)},],// problems detected during parse, null if noneproblems: [{message: "Sum of transactions (35919) doesn't match with difference between start amount 656482.79 and end amount 636338.79",},],},]

Architecture

src/
├── index.ts # Entry point: split lines, demultiplex, chunk by account
├── cli.ts # CLI tool (yarn parse)
├── account/
│ ├── index.ts # Per-account parser: orchestrates header/footer/transactions
│ └── schema.ts # Zod validation schema for parsed accounts
├── header/index.ts # Record 01 parser (opening balance)
├── footer/index.ts # Record 07 parser (closing balance)
├── transactionHeader/index.ts # Record 04/03 parser (transaction main line)
├── transactionBody/
│ ├── index.ts # Record 05 parser (transaction detail line)
│ └── qualifier/index.ts # SEPA qualifier resolver (CBE, IBE, NBE, etc.)
├── transaction/index.ts # Shared types (TransactionFields, QualifierType)
├── amount/index.ts # EBICS amount decoding (letter → digit + sign)
└── shared/
└── parseLineWithParts.ts # Generic regex-based line parser (shared by all record parsers)

Parsing flow

  1. Split & clean — Input text is split into lines, empty lines removed
  2. Demultiplex — If all lines start with 01, detect concatenated records and split them
  3. Chunk — Group lines by account (each 01 line starts a new account)
  4. Parse account — For each chunk:
    • 01headerParser (opening balance)
    • 04/03transactionHeaderParser (transaction)
    • 05transactionBodyParserqualifierResolver (SEPA details)
    • 07footerParser (closing balance)
  5. Validate — Zod schema validation, amount consistency check

EBICS line record codes

CodeDescription
01Account header (opening balance)
03Transaction (variant of 04, treated as 04)
04Transaction main line
05Transaction detail (qualifier-based SEPA fields)
07Account footer (closing balance)

External docs to understand the EBICS standard

https://www.ca-centreloire.fr/Vitrine/ObjCommun/Fic/CentreLoire/Entreprise/GuideParamRel_EDI.pdf

With more details on qualifier "05" (additional details on transaction, qualifier "04") https://www.promosoft.fr/wp-content/uploads/2013/06/Livre_blanc_SEPA.pdf

Opinions, known bugs & pitfalls

Opinions

We took some decisions about what is allowed or not in an EBICS parse result. We still don't have found a complete and exhaustive docs about this format. So everything is based on our guesses. See section "Whatever".

In transactions items, fields internal_code and operation_code need at least one of them to be filled.

Line record code

Most lines start with code 01 (start balance), 07 (end balance), 04 (main transaction line) and 05 (transaction detail line). But we already met lines with record code 03. We have guess that it's a very similar usage of record code 04, so for the moment, when met, transformed in 04.

Transactions _rank

For each transaction in an account, we have added a field _rank. It helps to know at which position in the account it was positionned.

Details

For each account, we provide a set of data which is guessed by our parser. It's NOT provided by the original file. See the example above to know the exact usage.

Whatever

  • We try to manage when carriage return is not present in the raw text file, we didn't found any bug yet but you probably will. We do the best we can.
  • If you find a bug, don't hesitate to open an issue or a pull request.
  • If you have a doc with complete specs, we'll thanks you a lot.

Thanks

Made by Airporting people with 🧡.

About

Parser for EBICS file (from CFONB) for Node.JS and TypeScript

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

License: MITStatic BadgeStatic Badgetest

ebics-parser

Lightweight and very fast parser for EBICS files for Node.js & TypeScript. Only two deps (date-fns, zod).

Used by Airporting

Tested from NodeJS 18.x to 24.x

Linkedin

Setup

# Install
yarn add @airporting/ebics-parser
# Or with npm
npm install @airporting/ebics-parser

Requires Node.js 18+.

Usage

As a library

importebicsParserfrom'@airporting/ebics-parser';constfileContent=`0130012 00585EUR2 00010156142 280323 0000006564827I0430012056800585EUR2 0001015614218290323 290323DOMUSVI 0000000 0000000359190{0530012056800585EUR2 0001015614218290323 NPYDOMUSVI0530012056800585EUR2 0001015614218290323 LCC/INV/3867 8.2.20230530012056800585EUR2 0001015614218290323 RCNNOTPROVIDED0530012056800585EUR2 0001015614218290323 PDOFR FRANCE0730012 00585EUR2 00010156142 290323 0000006363387I`;constresult=ebicsParser(fileContent);console.log(result);

CLI tool

yarn parse <filepath>

Reads an EBICS file, parses it, and writes the result as JSON to <filename>.parsed.json in the same directory.

Result structure

[{details: {countTransactions: 1,},amounts: {start: 656482.79,// opening balanceend: 636338.79,// closing balancediff: 20144,// |start - end|transactions: 35919,// sum of transaction amounts},header: {record_code: '01',bank_code: '30012',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',prev_date: '2023-03-28',prev_amount: '656482.79',// _1.._5: padding fields},footer: {record_code: '07',bank_code: '30012',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',next_date: '2023-03-29',next_amount: '636338.79',},transactions: [{record_code: '04',bank_code: '30012',internal_code: '0568',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',operation_code: '18',operation_date: '2023-03-29',value_date: '2023-03-29',label: 'DOMUSVI',amount: '35919',debtor_name: 'DOMUSVI',remittance_information_1: '/INV/3867 8.2.2023',end2end_identification: 'NOTPROVIDED',PDO: 'FR FRANCE',_rank: '0',// position in transaction list (not an official field)},],// problems detected during parse, null if noneproblems: [{message: "Sum of transactions (35919) doesn't match with difference between start amount 656482.79 and end amount 636338.79",},],},]

Architecture

src/
├── index.ts # Entry point: split lines, demultiplex, chunk by account
├── cli.ts # CLI tool (yarn parse)
├── account/
│ ├── index.ts # Per-account parser: orchestrates header/footer/transactions
│ └── schema.ts # Zod validation schema for parsed accounts
├── header/index.ts # Record 01 parser (opening balance)
├── footer/index.ts # Record 07 parser (closing balance)
├── transactionHeader/index.ts # Record 04/03 parser (transaction main line)
├── transactionBody/
│ ├── index.ts # Record 05 parser (transaction detail line)
│ └── qualifier/index.ts # SEPA qualifier resolver (CBE, IBE, NBE, etc.)
├── transaction/index.ts # Shared types (TransactionFields, QualifierType)
├── amount/index.ts # EBICS amount decoding (letter → digit + sign)
└── shared/
└── parseLineWithParts.ts # Generic regex-based line parser (shared by all record parsers)

Parsing flow

  1. Split & clean — Input text is split into lines, empty lines removed
  2. Demultiplex — If all lines start with 01, detect concatenated records and split them
  3. Chunk — Group lines by account (each 01 line starts a new account)
  4. Parse account — For each chunk:
    • 01headerParser (opening balance)
    • 04/03transactionHeaderParser (transaction)
    • 05transactionBodyParserqualifierResolver (SEPA details)
    • 07footerParser (closing balance)
  5. Validate — Zod schema validation, amount consistency check

EBICS line record codes

CodeDescription
01Account header (opening balance)
03Transaction (variant of 04, treated as 04)
04Transaction main line
05Transaction detail (qualifier-based SEPA fields)
07Account footer (closing balance)

External docs to understand the EBICS standard

https://www.ca-centreloire.fr/Vitrine/ObjCommun/Fic/CentreLoire/Entreprise/GuideParamRel_EDI.pdf

With more details on qualifier "05" (additional details on transaction, qualifier "04") https://www.promosoft.fr/wp-content/uploads/2013/06/Livre_blanc_SEPA.pdf

Opinions, known bugs & pitfalls

Opinions

We took some decisions about what is allowed or not in an EBICS parse result. We still don't have found a complete and exhaustive docs about this format. So everything is based on our guesses. See section "Whatever".

In transactions items, fields internal_code and operation_code need at least one of them to be filled.

Line record code

Most lines start with code 01 (start balance), 07 (end balance), 04 (main transaction line) and 05 (transaction detail line). But we already met lines with record code 03. We have guess that it's a very similar usage of record code 04, so for the moment, when met, transformed in 04.

Transactions _rank

For each transaction in an account, we have added a field _rank. It helps to know at which position in the account it was positionned.

Details

For each account, we provide a set of data which is guessed by our parser. It's NOT provided by the original file. See the example above to know the exact usage.

Whatever

  • We try to manage when carriage return is not present in the raw text file, we didn't found any bug yet but you probably will. We do the best we can.
  • If you find a bug, don't hesitate to open an issue or a pull request.
  • If you have a doc with complete specs, we'll thanks you a lot.

Thanks

Made by Airporting people with 🧡.

About

Parser for EBICS file (from CFONB) for Node.JS and TypeScript

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

License: MITStatic BadgeStatic Badgetest

ebics-parser

Lightweight and very fast parser for EBICS files for Node.js & TypeScript. Only two deps (date-fns, zod).

Used by Airporting

Tested from NodeJS 18.x to 24.x

Linkedin

Setup

# Install
yarn add @airporting/ebics-parser
# Or with npm
npm install @airporting/ebics-parser

Requires Node.js 18+.

Usage

As a library

importebicsParserfrom'@airporting/ebics-parser';constfileContent=`0130012 00585EUR2 00010156142 280323 0000006564827I0430012056800585EUR2 0001015614218290323 290323DOMUSVI 0000000 0000000359190{0530012056800585EUR2 0001015614218290323 NPYDOMUSVI0530012056800585EUR2 0001015614218290323 LCC/INV/3867 8.2.20230530012056800585EUR2 0001015614218290323 RCNNOTPROVIDED0530012056800585EUR2 0001015614218290323 PDOFR FRANCE0730012 00585EUR2 00010156142 290323 0000006363387I`;constresult=ebicsParser(fileContent);console.log(result);

CLI tool

yarn parse <filepath>

Reads an EBICS file, parses it, and writes the result as JSON to <filename>.parsed.json in the same directory.

Result structure

[{details: {countTransactions: 1,},amounts: {start: 656482.79,// opening balanceend: 636338.79,// closing balancediff: 20144,// |start - end|transactions: 35919,// sum of transaction amounts},header: {record_code: '01',bank_code: '30012',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',prev_date: '2023-03-28',prev_amount: '656482.79',// _1.._5: padding fields},footer: {record_code: '07',bank_code: '30012',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',next_date: '2023-03-29',next_amount: '636338.79',},transactions: [{record_code: '04',bank_code: '30012',internal_code: '0568',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',operation_code: '18',operation_date: '2023-03-29',value_date: '2023-03-29',label: 'DOMUSVI',amount: '35919',debtor_name: 'DOMUSVI',remittance_information_1: '/INV/3867 8.2.2023',end2end_identification: 'NOTPROVIDED',PDO: 'FR FRANCE',_rank: '0',// position in transaction list (not an official field)},],// problems detected during parse, null if noneproblems: [{message: "Sum of transactions (35919) doesn't match with difference between start amount 656482.79 and end amount 636338.79",},],},]

Architecture

src/
├── index.ts # Entry point: split lines, demultiplex, chunk by account
├── cli.ts # CLI tool (yarn parse)
├── account/
│ ├── index.ts # Per-account parser: orchestrates header/footer/transactions
│ └── schema.ts # Zod validation schema for parsed accounts
├── header/index.ts # Record 01 parser (opening balance)
├── footer/index.ts # Record 07 parser (closing balance)
├── transactionHeader/index.ts # Record 04/03 parser (transaction main line)
├── transactionBody/
│ ├── index.ts # Record 05 parser (transaction detail line)
│ └── qualifier/index.ts # SEPA qualifier resolver (CBE, IBE, NBE, etc.)
├── transaction/index.ts # Shared types (TransactionFields, QualifierType)
├── amount/index.ts # EBICS amount decoding (letter → digit + sign)
└── shared/
└── parseLineWithParts.ts # Generic regex-based line parser (shared by all record parsers)

Parsing flow

  1. Split & clean — Input text is split into lines, empty lines removed
  2. Demultiplex — If all lines start with 01, detect concatenated records and split them
  3. Chunk — Group lines by account (each 01 line starts a new account)
  4. Parse account — For each chunk:
    • 01headerParser (opening balance)
    • 04/03transactionHeaderParser (transaction)
    • 05transactionBodyParserqualifierResolver (SEPA details)
    • 07footerParser (closing balance)
  5. Validate — Zod schema validation, amount consistency check

EBICS line record codes

CodeDescription
01Account header (opening balance)
03Transaction (variant of 04, treated as 04)
04Transaction main line
05Transaction detail (qualifier-based SEPA fields)
07Account footer (closing balance)

External docs to understand the EBICS standard

https://www.ca-centreloire.fr/Vitrine/ObjCommun/Fic/CentreLoire/Entreprise/GuideParamRel_EDI.pdf

With more details on qualifier "05" (additional details on transaction, qualifier "04") https://www.promosoft.fr/wp-content/uploads/2013/06/Livre_blanc_SEPA.pdf

Opinions, known bugs & pitfalls

Opinions

We took some decisions about what is allowed or not in an EBICS parse result. We still don't have found a complete and exhaustive docs about this format. So everything is based on our guesses. See section "Whatever".

In transactions items, fields internal_code and operation_code need at least one of them to be filled.

Line record code

Most lines start with code 01 (start balance), 07 (end balance), 04 (main transaction line) and 05 (transaction detail line). But we already met lines with record code 03. We have guess that it's a very similar usage of record code 04, so for the moment, when met, transformed in 04.

Transactions _rank

For each transaction in an account, we have added a field _rank. It helps to know at which position in the account it was positionned.

Details

For each account, we provide a set of data which is guessed by our parser. It's NOT provided by the original file. See the example above to know the exact usage.

Whatever

  • We try to manage when carriage return is not present in the raw text file, we didn't found any bug yet but you probably will. We do the best we can.
  • If you find a bug, don't hesitate to open an issue or a pull request.
  • If you have a doc with complete specs, we'll thanks you a lot.

Thanks

Made by Airporting people with 🧡.

About

Parser for EBICS file (from CFONB) for Node.JS and TypeScript

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

License: MITStatic BadgeStatic Badgetest

ebics-parser

Lightweight and very fast parser for EBICS files for Node.js & TypeScript. Only two deps (date-fns, zod).

Used by Airporting

Tested from NodeJS 18.x to 24.x

Linkedin

Setup

# Install
yarn add @airporting/ebics-parser
# Or with npm
npm install @airporting/ebics-parser

Requires Node.js 18+.

Usage

As a library

importebicsParserfrom'@airporting/ebics-parser';constfileContent=`0130012 00585EUR2 00010156142 280323 0000006564827I0430012056800585EUR2 0001015614218290323 290323DOMUSVI 0000000 0000000359190{0530012056800585EUR2 0001015614218290323 NPYDOMUSVI0530012056800585EUR2 0001015614218290323 LCC/INV/3867 8.2.20230530012056800585EUR2 0001015614218290323 RCNNOTPROVIDED0530012056800585EUR2 0001015614218290323 PDOFR FRANCE0730012 00585EUR2 00010156142 290323 0000006363387I`;constresult=ebicsParser(fileContent);console.log(result);

CLI tool

yarn parse <filepath>

Reads an EBICS file, parses it, and writes the result as JSON to <filename>.parsed.json in the same directory.

Result structure

[{details: {countTransactions: 1,},amounts: {start: 656482.79,// opening balanceend: 636338.79,// closing balancediff: 20144,// |start - end|transactions: 35919,// sum of transaction amounts},header: {record_code: '01',bank_code: '30012',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',prev_date: '2023-03-28',prev_amount: '656482.79',// _1.._5: padding fields},footer: {record_code: '07',bank_code: '30012',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',next_date: '2023-03-29',next_amount: '636338.79',},transactions: [{record_code: '04',bank_code: '30012',internal_code: '0568',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',operation_code: '18',operation_date: '2023-03-29',value_date: '2023-03-29',label: 'DOMUSVI',amount: '35919',debtor_name: 'DOMUSVI',remittance_information_1: '/INV/3867 8.2.2023',end2end_identification: 'NOTPROVIDED',PDO: 'FR FRANCE',_rank: '0',// position in transaction list (not an official field)},],// problems detected during parse, null if noneproblems: [{message: "Sum of transactions (35919) doesn't match with difference between start amount 656482.79 and end amount 636338.79",},],},]

Architecture

src/
├── index.ts # Entry point: split lines, demultiplex, chunk by account
├── cli.ts # CLI tool (yarn parse)
├── account/
│ ├── index.ts # Per-account parser: orchestrates header/footer/transactions
│ └── schema.ts # Zod validation schema for parsed accounts
├── header/index.ts # Record 01 parser (opening balance)
├── footer/index.ts # Record 07 parser (closing balance)
├── transactionHeader/index.ts # Record 04/03 parser (transaction main line)
├── transactionBody/
│ ├── index.ts # Record 05 parser (transaction detail line)
│ └── qualifier/index.ts # SEPA qualifier resolver (CBE, IBE, NBE, etc.)
├── transaction/index.ts # Shared types (TransactionFields, QualifierType)
├── amount/index.ts # EBICS amount decoding (letter → digit + sign)
└── shared/
└── parseLineWithParts.ts # Generic regex-based line parser (shared by all record parsers)

Parsing flow

  1. Split & clean — Input text is split into lines, empty lines removed
  2. Demultiplex — If all lines start with 01, detect concatenated records and split them
  3. Chunk — Group lines by account (each 01 line starts a new account)
  4. Parse account — For each chunk:
    • 01headerParser (opening balance)
    • 04/03transactionHeaderParser (transaction)
    • 05transactionBodyParserqualifierResolver (SEPA details)
    • 07footerParser (closing balance)
  5. Validate — Zod schema validation, amount consistency check

EBICS line record codes

CodeDescription
01Account header (opening balance)
03Transaction (variant of 04, treated as 04)
04Transaction main line
05Transaction detail (qualifier-based SEPA fields)
07Account footer (closing balance)

External docs to understand the EBICS standard

https://www.ca-centreloire.fr/Vitrine/ObjCommun/Fic/CentreLoire/Entreprise/GuideParamRel_EDI.pdf

With more details on qualifier "05" (additional details on transaction, qualifier "04") https://www.promosoft.fr/wp-content/uploads/2013/06/Livre_blanc_SEPA.pdf

Opinions, known bugs & pitfalls

Opinions

We took some decisions about what is allowed or not in an EBICS parse result. We still don't have found a complete and exhaustive docs about this format. So everything is based on our guesses. See section "Whatever".

In transactions items, fields internal_code and operation_code need at least one of them to be filled.

Line record code

Most lines start with code 01 (start balance), 07 (end balance), 04 (main transaction line) and 05 (transaction detail line). But we already met lines with record code 03. We have guess that it's a very similar usage of record code 04, so for the moment, when met, transformed in 04.

Transactions _rank

For each transaction in an account, we have added a field _rank. It helps to know at which position in the account it was positionned.

Details

For each account, we provide a set of data which is guessed by our parser. It's NOT provided by the original file. See the example above to know the exact usage.

Whatever

  • We try to manage when carriage return is not present in the raw text file, we didn't found any bug yet but you probably will. We do the best we can.
  • If you find a bug, don't hesitate to open an issue or a pull request.
  • If you have a doc with complete specs, we'll thanks you a lot.

Thanks

Made by Airporting people with 🧡.

About

Parser for EBICS file (from CFONB) for Node.JS and TypeScript

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

License: MITStatic BadgeStatic Badgetest

ebics-parser

Lightweight and very fast parser for EBICS files for Node.js & TypeScript. Only two deps (date-fns, zod).

Used by Airporting

Tested from NodeJS 18.x to 24.x

Linkedin

Setup

# Install
yarn add @airporting/ebics-parser
# Or with npm
npm install @airporting/ebics-parser

Requires Node.js 18+.

Usage

As a library

importebicsParserfrom'@airporting/ebics-parser';constfileContent=`0130012 00585EUR2 00010156142 280323 0000006564827I0430012056800585EUR2 0001015614218290323 290323DOMUSVI 0000000 0000000359190{0530012056800585EUR2 0001015614218290323 NPYDOMUSVI0530012056800585EUR2 0001015614218290323 LCC/INV/3867 8.2.20230530012056800585EUR2 0001015614218290323 RCNNOTPROVIDED0530012056800585EUR2 0001015614218290323 PDOFR FRANCE0730012 00585EUR2 00010156142 290323 0000006363387I`;constresult=ebicsParser(fileContent);console.log(result);

CLI tool

yarn parse <filepath>

Reads an EBICS file, parses it, and writes the result as JSON to <filename>.parsed.json in the same directory.

Result structure

[{details: {countTransactions: 1,},amounts: {start: 656482.79,// opening balanceend: 636338.79,// closing balancediff: 20144,// |start - end|transactions: 35919,// sum of transaction amounts},header: {record_code: '01',bank_code: '30012',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',prev_date: '2023-03-28',prev_amount: '656482.79',// _1.._5: padding fields},footer: {record_code: '07',bank_code: '30012',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',next_date: '2023-03-29',next_amount: '636338.79',},transactions: [{record_code: '04',bank_code: '30012',internal_code: '0568',desk_code: '00585',currency_code: 'EUR',nb_of_dec: '2',account_nb: '00010156142',operation_code: '18',operation_date: '2023-03-29',value_date: '2023-03-29',label: 'DOMUSVI',amount: '35919',debtor_name: 'DOMUSVI',remittance_information_1: '/INV/3867 8.2.2023',end2end_identification: 'NOTPROVIDED',PDO: 'FR FRANCE',_rank: '0',// position in transaction list (not an official field)},],// problems detected during parse, null if noneproblems: [{message: "Sum of transactions (35919) doesn't match with difference between start amount 656482.79 and end amount 636338.79",},],},]

Architecture

src/
├── index.ts # Entry point: split lines, demultiplex, chunk by account
├── cli.ts # CLI tool (yarn parse)
├── account/
│ ├── index.ts # Per-account parser: orchestrates header/footer/transactions
│ └── schema.ts # Zod validation schema for parsed accounts
├── header/index.ts # Record 01 parser (opening balance)
├── footer/index.ts # Record 07 parser (closing balance)
├── transactionHeader/index.ts # Record 04/03 parser (transaction main line)
├── transactionBody/
│ ├── index.ts # Record 05 parser (transaction detail line)
│ └── qualifier/index.ts # SEPA qualifier resolver (CBE, IBE, NBE, etc.)
├── transaction/index.ts # Shared types (TransactionFields, QualifierType)
├── amount/index.ts # EBICS amount decoding (letter → digit + sign)
└── shared/
└── parseLineWithParts.ts # Generic regex-based line parser (shared by all record parsers)

Parsing flow

  1. Split & clean — Input text is split into lines, empty lines removed
  2. Demultiplex — If all lines start with 01, detect concatenated records and split them
  3. Chunk — Group lines by account (each 01 line starts a new account)
  4. Parse account — For each chunk:
    • 01headerParser (opening balance)
    • 04/03transactionHeaderParser (transaction)
    • 05transactionBodyParserqualifierResolver (SEPA details)
    • 07footerParser (closing balance)
  5. Validate — Zod schema validation, amount consistency check

EBICS line record codes

CodeDescription
01Account header (opening balance)
03Transaction (variant of 04, treated as 04)
04Transaction main line
05Transaction detail (qualifier-based SEPA fields)
07Account footer (closing balance)

External docs to understand the EBICS standard

https://www.ca-centreloire.fr/Vitrine/ObjCommun/Fic/CentreLoire/Entreprise/GuideParamRel_EDI.pdf

With more details on qualifier "05" (additional details on transaction, qualifier "04") https://www.promosoft.fr/wp-content/uploads/2013/06/Livre_blanc_SEPA.pdf

Opinions, known bugs & pitfalls

Opinions

We took some decisions about what is allowed or not in an EBICS parse result. We still don't have found a complete and exhaustive docs about this format. So everything is based on our guesses. See section "Whatever".

In transactions items, fields internal_code and operation_code need at least one of them to be filled.

Line record code

Most lines start with code 01 (start balance), 07 (end balance), 04 (main transaction line) and 05 (transaction detail line). But we already met lines with record code 03. We have guess that it's a very similar usage of record code 04, so for the moment, when met, transformed in 04.

Transactions _rank

For each transaction in an account, we have added a field _rank. It helps to know at which position in the account it was positionned.

Details

For each account, we provide a set of data which is guessed by our parser. It's NOT provided by the original file. See the example above to know the exact usage.

Whatever

  • We try to manage when carriage return is not present in the raw text file, we didn't found any bug yet but you probably will. We do the best we can.
  • If you find a bug, don't hesitate to open an issue or a pull request.
  • If you have a doc with complete specs, we'll thanks you a lot.

Thanks

Made by Airporting people with 🧡.

About

Parser for EBICS file (from CFONB) for Node.JS and TypeScript

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages