87 lines
3.2 KiB
TypeScript
87 lines
3.2 KiB
TypeScript
import { PDFDocument, PDFDict, PDFName, PDFRawStream, PDFArray, PDFString } from '../../../plugins.js';
|
|
import { BaseXMLExtractor } from './base.extractor.js';
|
|
|
|
/**
|
|
* Standard PDF XML extractor that extracts XML from embedded files
|
|
* Works with PDF/A-3 documents that follow the standard for embedding files
|
|
*/
|
|
export class StandardXMLExtractor extends BaseXMLExtractor {
|
|
/**
|
|
* Extract XML from a PDF buffer using standard PDF/A-3 embedded files
|
|
* @param pdfBuffer PDF buffer
|
|
* @returns XML content or null if not found
|
|
*/
|
|
public async extractXml(pdfBuffer: Uint8Array | Buffer): Promise<string | null> {
|
|
try {
|
|
const pdfDoc = await PDFDocument.load(pdfBuffer);
|
|
|
|
// Get the document's metadata dictionary
|
|
const namesDictObj = pdfDoc.catalog.lookup(PDFName.of('Names'));
|
|
if (!(namesDictObj instanceof PDFDict)) {
|
|
console.warn('No Names dictionary found in PDF! This PDF does not contain embedded files.');
|
|
return null;
|
|
}
|
|
|
|
// Get the embedded files dictionary
|
|
const embeddedFilesDictObj = namesDictObj.lookup(PDFName.of('EmbeddedFiles'));
|
|
if (!(embeddedFilesDictObj instanceof PDFDict)) {
|
|
console.warn('No EmbeddedFiles dictionary found! This PDF does not contain embedded files.');
|
|
return null;
|
|
}
|
|
|
|
// Get the names array
|
|
const filesSpecObj = embeddedFilesDictObj.lookup(PDFName.of('Names'));
|
|
if (!(filesSpecObj instanceof PDFArray)) {
|
|
console.warn('No files specified in EmbeddedFiles dictionary!');
|
|
return null;
|
|
}
|
|
|
|
// Try to find an XML file in the embedded files
|
|
for (let i = 0; i < filesSpecObj.size(); i += 2) {
|
|
const fileNameObj = filesSpecObj.lookup(i);
|
|
const fileSpecObj = filesSpecObj.lookup(i + 1);
|
|
|
|
if (!(fileNameObj instanceof PDFString) || !(fileSpecObj instanceof PDFDict)) {
|
|
continue;
|
|
}
|
|
|
|
// Get the filename as string
|
|
const fileName = fileNameObj.decodeText();
|
|
|
|
// Check if it's a known invoice XML file name
|
|
const isKnownFileName = this.knownFileNames.some(
|
|
knownName => fileName.toLowerCase() === knownName.toLowerCase()
|
|
);
|
|
|
|
// Check if it's any XML file or has invoice-related keywords
|
|
const isXmlFile = fileName.toLowerCase().endsWith('.xml') ||
|
|
fileName.toLowerCase().includes('zugferd') ||
|
|
fileName.toLowerCase().includes('factur-x') ||
|
|
fileName.toLowerCase().includes('xrechnung') ||
|
|
fileName.toLowerCase().includes('invoice');
|
|
|
|
if (isKnownFileName || isXmlFile) {
|
|
const efDictObj = fileSpecObj.lookup(PDFName.of('EF'));
|
|
if (!(efDictObj instanceof PDFDict)) {
|
|
continue;
|
|
}
|
|
|
|
const fileStream = efDictObj.lookup(PDFName.of('F'));
|
|
if (fileStream instanceof PDFRawStream) {
|
|
const xmlContent = await this.extractXmlFromStream(fileStream, fileName);
|
|
if (xmlContent) {
|
|
return xmlContent;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
console.warn('No valid XML found in embedded files');
|
|
return null;
|
|
} catch (error) {
|
|
console.error('Error in standard extraction:', error);
|
|
return null;
|
|
}
|
|
}
|
|
}
|