bae705aa97
- Add NFC ePassport roadmap (ICAO 9303, eIDAS) - Add TensorFlow.js edge face detection (BlazeFace) - Add structured audit logger (GDPR-compliant) - Risk scoring support Part of KYC Apple Native UX v1.1.0
198 lines
4.8 KiB
JavaScript
198 lines
4.8 KiB
JavaScript
/**
|
|
* SIE4 Parser
|
|
* Parses SIE4 accounting file format into structured objects.
|
|
*/
|
|
|
|
/**
|
|
* Tokenize a SIE line remainder (after the directive keyword).
|
|
* Handles:
|
|
* - Quoted strings "like this"
|
|
* - Object lists {key val ...} → replaced with placeholder '{}'
|
|
* - Regular whitespace-separated tokens
|
|
*/
|
|
function tokenize(str) {
|
|
const tokens = [];
|
|
let i = 0;
|
|
const s = str.trim();
|
|
|
|
while (i < s.length) {
|
|
// Skip whitespace
|
|
if (s[i] === ' ' || s[i] === '\t') { i++; continue; }
|
|
|
|
// Quoted string
|
|
if (s[i] === '"') {
|
|
let j = i + 1;
|
|
while (j < s.length && s[j] !== '"') j++;
|
|
tokens.push(s.slice(i + 1, j));
|
|
i = j + 1;
|
|
continue;
|
|
}
|
|
|
|
// Object list { ... } — treat whole block as one placeholder token
|
|
if (s[i] === '{') {
|
|
let depth = 0;
|
|
let j = i;
|
|
while (j < s.length) {
|
|
if (s[j] === '{') depth++;
|
|
else if (s[j] === '}') { depth--; if (depth === 0) break; }
|
|
j++;
|
|
}
|
|
tokens.push('{}');
|
|
i = j + 1;
|
|
continue;
|
|
}
|
|
|
|
// Regular token
|
|
let j = i;
|
|
while (j < s.length && s[j] !== ' ' && s[j] !== '\t' && s[j] !== '"') j++;
|
|
if (j > i) tokens.push(s.slice(i, j));
|
|
i = j;
|
|
}
|
|
|
|
return tokens;
|
|
}
|
|
|
|
/**
|
|
* Parse SIE4 file content (string) into a structured object.
|
|
* @param {string} content Raw SIE4 text (UTF-8 or already decoded)
|
|
* @returns {object}
|
|
*/
|
|
export function parseSIE4(content) {
|
|
const lines = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
|
|
|
|
const result = {
|
|
company: '',
|
|
org_nr: '',
|
|
fiscal_year: null,
|
|
accounts: [],
|
|
opening_balances: [],
|
|
closing_balances: [],
|
|
journal_entries: [],
|
|
};
|
|
|
|
let currentVer = null;
|
|
let inVerBlock = false;
|
|
|
|
for (const rawLine of lines) {
|
|
const line = rawLine.trim();
|
|
if (!line) continue;
|
|
|
|
// Block delimiters
|
|
if (line === '{') {
|
|
inVerBlock = true;
|
|
continue;
|
|
}
|
|
if (line === '}') {
|
|
if (currentVer) {
|
|
result.journal_entries.push(currentVer);
|
|
currentVer = null;
|
|
}
|
|
inVerBlock = false;
|
|
continue;
|
|
}
|
|
|
|
if (!line.startsWith('#')) continue;
|
|
|
|
// Split directive from rest
|
|
const spaceIdx = line.indexOf(' ');
|
|
const directive = (spaceIdx >= 0 ? line.slice(0, spaceIdx) : line).toUpperCase();
|
|
const rest = spaceIdx >= 0 ? line.slice(spaceIdx + 1) : '';
|
|
const tokens = tokenize(rest);
|
|
|
|
switch (directive) {
|
|
case '#FNAMN':
|
|
result.company = tokens[0] || '';
|
|
break;
|
|
|
|
case '#ORGNR':
|
|
result.org_nr = tokens[0] || '';
|
|
break;
|
|
|
|
case '#RAR': {
|
|
// #RAR year_offset from_date to_date
|
|
const yearOffset = parseInt(tokens[0], 10);
|
|
if (yearOffset === 0) {
|
|
result.fiscal_year = {
|
|
from: tokens[1] || '',
|
|
to: tokens[2] || '',
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
|
|
case '#KONTO':
|
|
// #KONTO account_nr account_name
|
|
result.accounts.push({
|
|
number: tokens[0] || '',
|
|
name: tokens[1] || '',
|
|
});
|
|
break;
|
|
|
|
case '#IB':
|
|
// #IB year_offset account amount [quantity]
|
|
result.opening_balances.push({
|
|
year_offset: parseInt(tokens[0], 10) || 0,
|
|
account: tokens[1] || '',
|
|
amount: parseFloat(tokens[2]) || 0,
|
|
});
|
|
break;
|
|
|
|
case '#UB':
|
|
// #UB year_offset account amount [quantity]
|
|
result.closing_balances.push({
|
|
year_offset: parseInt(tokens[0], 10) || 0,
|
|
account: tokens[1] || '',
|
|
amount: parseFloat(tokens[2]) || 0,
|
|
});
|
|
break;
|
|
|
|
case '#VER':
|
|
// #VER series number date "description" [reg_date] [reg_by]
|
|
currentVer = {
|
|
series: tokens[0] || '',
|
|
number: tokens[1] || '',
|
|
date: tokens[2] || '',
|
|
description: tokens[3] || '',
|
|
transactions: [],
|
|
};
|
|
break;
|
|
|
|
case '#TRANS':
|
|
// #TRANS account {object_list} amount [date] ["description"] [quantity]
|
|
// tokens: [account, {}, amount, date?, description?]
|
|
if (currentVer) {
|
|
currentVer.transactions.push({
|
|
account: tokens[0] || '',
|
|
amount: parseFloat(tokens[2]) || 0,
|
|
date: tokens[3] || currentVer.date,
|
|
description: tokens[4] || '',
|
|
});
|
|
}
|
|
break;
|
|
|
|
// Ignored directives (metadata only)
|
|
case '#FLAGGA':
|
|
case '#PROGRAM':
|
|
case '#FORMAT':
|
|
case '#GEN':
|
|
case '#SIETYP':
|
|
case '#VALUTA':
|
|
case '#TAXAR':
|
|
case '#OMFATTN':
|
|
case '#KPTYP':
|
|
case '#ENHET':
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If file ended without closing brace for last VER (malformed), still capture it
|
|
if (currentVer) {
|
|
result.journal_entries.push(currentVer);
|
|
}
|
|
|
|
return result;
|
|
}
|