@fin.cx/einvoice
Version:
A TypeScript module for creating, manipulating, and embedding XML data within PDF files specifically tailored for electronic invoice (einvoice) packages.
151 lines • 10.7 kB
JavaScript
import { BaseXMLExtractor } from './base.extractor.js';
/**
* Text-based XML extractor for PDF documents
* Extracts XML by searching for XML patterns in the PDF text
* Used as a fallback when other extraction methods fail
*/
export class TextXMLExtractor extends BaseXMLExtractor {
constructor() {
super(...arguments);
// Maximum chunk size to process at once (4MB)
this.CHUNK_SIZE = 4 * 1024 * 1024;
// Maximum number of chunks to check (effective 20MB search limit)
this.MAX_CHUNKS = 5;
// Common XML patterns to look for
this.XML_PATTERNS = [
'<?xml',
'<CrossIndustryInvoice',
'<CrossIndustryDocument',
'<Invoice',
'<CreditNote',
'<rsm:CrossIndustryInvoice',
'<rsm:CrossIndustryDocument',
'<ram:CrossIndustryDocument',
'<ubl:Invoice',
'<ubl:CreditNote',
'<FatturaElettronica'
];
}
/**
* Extract XML from a PDF buffer by searching for XML patterns in the text
* Uses a chunked approach to handle large files efficiently
* @param pdfBuffer PDF buffer
* @returns XML content or null if not found
*/
async extractXml(pdfBuffer) {
try {
console.log('Attempting text-based XML extraction from PDF...');
// Convert Buffer to Uint8Array if needed
const buffer = Buffer.isBuffer(pdfBuffer) ? new Uint8Array(pdfBuffer) : pdfBuffer;
// Try extracting XML using the chunked approach
return this.extractXmlFromBufferChunked(buffer);
}
catch (error) {
console.error('Error in text-based extraction:', error);
return null;
}
}
/**
* Extract XML from buffer using a chunked approach
* This helps avoid memory issues with large PDFs
* @param buffer Buffer to search in
* @returns XML content or null if not found
*/
extractXmlFromBufferChunked(buffer) {
// Process the PDF in chunks
for (let chunkIndex = 0; chunkIndex < this.MAX_CHUNKS; chunkIndex++) {
const startPos = chunkIndex * this.CHUNK_SIZE;
if (startPos >= buffer.length)
break;
const endPos = Math.min(startPos + this.CHUNK_SIZE, buffer.length);
const chunk = buffer.slice(startPos, endPos);
// Try to extract XML from this chunk
const chunkResult = this.processChunk(chunk, startPos);
if (chunkResult) {
return chunkResult;
}
}
console.warn('No valid XML found in any chunk of the PDF');
return null;
}
/**
* Process a single chunk of the PDF buffer
* @param chunk Chunk buffer to process
* @param chunkOffset Offset position of the chunk in the original buffer
* @returns XML content or null if not found
*/
processChunk(chunk, chunkOffset) {
try {
// First try UTF-8 encoding for this chunk
const utf8String = this.decodeBufferToString(chunk, 'utf-8');
let xmlContent = this.searchForXmlInString(utf8String);
if (xmlContent) {
console.log(`Found XML content in chunk at offset ${chunkOffset} using UTF-8 encoding`);
return xmlContent;
}
// If UTF-8 fails, try Latin-1 (ISO-8859-1) which can handle binary better
const latin1String = this.decodeBufferToString(chunk, 'latin1');
xmlContent = this.searchForXmlInString(latin1String);
if (xmlContent) {
console.log(`Found XML content in chunk at offset ${chunkOffset} using Latin-1 encoding`);
return xmlContent;
}
// No XML found in this chunk
return null;
}
catch (error) {
console.warn(`Error processing chunk at offset ${chunkOffset}:`, error);
return null;
}
}
/**
* Safely decode a buffer to string using the specified encoding
* @param buffer Buffer to decode
* @param encoding Encoding to use ('utf-8' or 'latin1')
* @returns Decoded string
*/
decodeBufferToString(buffer, encoding) {
try {
if (encoding === 'utf-8') {
return new TextDecoder('utf-8', { fatal: false }).decode(buffer);
}
else {
// For Latin-1 we can use a direct mapping (bytes 0-255 map directly to code points 0-255)
// This is more reliable for binary data than TextDecoder for legacy encodings
return Array.from(buffer)
.map(byte => String.fromCharCode(byte))
.join('');
}
}
catch (error) {
console.warn(`Error decoding buffer using ${encoding}:`, error);
// Return empty string on error to allow processing to continue
return '';
}
}
/**
* Search for XML patterns in a string
* @param content String to search in
* @returns XML content or null if not found
*/
searchForXmlInString(content) {
if (!content)
return null;
// Search for each XML pattern
for (const pattern of this.XML_PATTERNS) {
const patternIndex = content.indexOf(pattern);
if (patternIndex !== -1) {
console.log(`Found XML pattern "${pattern}" at position ${patternIndex}`);
// Try to extract the XML content starting from the pattern position
const xmlContent = this.extractXmlFromString(content, patternIndex);
// Validate the extracted content
if (xmlContent && this.isValidXml(xmlContent)) {
console.log('Successfully extracted and validated XML from text');
return xmlContent;
}
}
}
return null;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGV4dC5leHRyYWN0b3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90cy9mb3JtYXRzL3BkZi9leHRyYWN0b3JzL3RleHQuZXh0cmFjdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRXZEOzs7O0dBSUc7QUFDSCxNQUFNLE9BQU8sZ0JBQWlCLFNBQVEsZ0JBQWdCO0lBQXREOztRQUNFLDhDQUE4QztRQUM3QixlQUFVLEdBQUcsQ0FBQyxHQUFHLElBQUksR0FBRyxJQUFJLENBQUM7UUFFOUMsa0VBQWtFO1FBQ2pELGVBQVUsR0FBRyxDQUFDLENBQUM7UUFFaEMsa0NBQWtDO1FBQ2pCLGlCQUFZLEdBQUc7WUFDOUIsT0FBTztZQUNQLHVCQUF1QjtZQUN2Qix3QkFBd0I7WUFDeEIsVUFBVTtZQUNWLGFBQWE7WUFDYiwyQkFBMkI7WUFDM0IsNEJBQTRCO1lBQzVCLDRCQUE0QjtZQUM1QixjQUFjO1lBQ2QsaUJBQWlCO1lBQ2pCLHFCQUFxQjtTQUN0QixDQUFDO0lBc0lKLENBQUM7SUFwSUM7Ozs7O09BS0c7SUFDSSxLQUFLLENBQUMsVUFBVSxDQUFDLFNBQThCO1FBQ3BELElBQUksQ0FBQztZQUNILE9BQU8sQ0FBQyxHQUFHLENBQUMsa0RBQWtELENBQUMsQ0FBQztZQUVoRSx5Q0FBeUM7WUFDekMsTUFBTSxNQUFNLEdBQUcsTUFBTSxDQUFDLFFBQVEsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxVQUFVLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztZQUVsRixnREFBZ0Q7WUFDaEQsT0FBTyxJQUFJLENBQUMsMkJBQTJCLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDbEQsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixPQUFPLENBQUMsS0FBSyxDQUFDLGlDQUFpQyxFQUFFLEtBQUss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