parses xjustiz and orders attachments
This commit is contained in:
+129
-6
@@ -10,9 +10,8 @@ export type ZipAttachment = {
|
||||
};
|
||||
|
||||
export type ProcessedZipArchive = {
|
||||
name: string;
|
||||
attachments: ZipAttachment[];
|
||||
xjustizNachrichtXml: Uint8Array | null;
|
||||
name: string
|
||||
attachments: ZipAttachment[]
|
||||
};
|
||||
|
||||
const IMAGE_EXTENSIONS = new Set([
|
||||
@@ -30,6 +29,7 @@ const IMAGE_EXTENSIONS = new Set([
|
||||
]);
|
||||
|
||||
const ZIP_META_FILE_NAME = "xjustiz_nachricht.xml";
|
||||
const XML_TEXT_DECODER = new TextDecoder();
|
||||
|
||||
const unzipArchive = (data: Uint8Array) =>
|
||||
new Promise<Record<string, Uint8Array>>((resolve, reject) => {
|
||||
@@ -56,6 +56,126 @@ const isPdf = (path: string) => getExtension(path) === ".pdf";
|
||||
|
||||
const isImage = (path: string) => IMAGE_EXTENSIONS.has(getExtension(path));
|
||||
|
||||
const normalizeKey = (value: string) => value.replaceAll("\\", "/").toLowerCase().trim();
|
||||
|
||||
const matchesElementName = (element: Element, requestedName: string) => {
|
||||
const requestedLocalName = requestedName.split(".").pop() ?? requestedName;
|
||||
|
||||
return (
|
||||
element.localName === requestedName ||
|
||||
element.localName === requestedLocalName ||
|
||||
element.tagName === requestedName ||
|
||||
element.tagName === requestedLocalName ||
|
||||
element.tagName.endsWith(`:${requestedLocalName}`)
|
||||
);
|
||||
};
|
||||
|
||||
const getDirectChildByLocalName = (element: Element, localName: string) =>
|
||||
Array.from(element.children).find((child) => matchesElementName(child, localName)) ?? null;
|
||||
|
||||
const getFirstDescendantByLocalName = (root: Document | Element, localName: string) =>
|
||||
Array.from(root.querySelectorAll("*")).find((element) => matchesElementName(element, localName)) ?? null;
|
||||
|
||||
const getTextContentByPath = (root: Element, path: string[]) => {
|
||||
let current: Element | null = root;
|
||||
|
||||
for (const localName of path) {
|
||||
current = current ? getDirectChildByLocalName(current, localName) : null;
|
||||
|
||||
if (!current) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const textContent = current.textContent?.trim();
|
||||
|
||||
return textContent ? textContent : null;
|
||||
};
|
||||
|
||||
const parseXmlDocument = (xmlBytes: Uint8Array) => {
|
||||
const parser = new DOMParser();
|
||||
const document = parser.parseFromString(XML_TEXT_DECODER.decode(xmlBytes), "application/xml");
|
||||
|
||||
if (document.querySelector("parsererror")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return document;
|
||||
};
|
||||
|
||||
const getXJustizDocumentNames = (xmlBytes: Uint8Array) => {
|
||||
const document = parseXmlDocument(xmlBytes);
|
||||
|
||||
if (!document) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const schriftgutobjekte = getFirstDescendantByLocalName(document, "schriftgutobjekte");
|
||||
|
||||
if (!schriftgutobjekte) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const dokumentNodes = Array.from(schriftgutobjekte.children).filter((child) =>
|
||||
matchesElementName(child, "dokument"),
|
||||
);
|
||||
|
||||
return dokumentNodes
|
||||
.map((dokument) => getTextContentByPath(dokument, ["xjustiz.fachspezifischeDaten", "datei", "dateiname"]))
|
||||
.filter((name): name is string => Boolean(name));
|
||||
};
|
||||
|
||||
const orderAttachmentsByDocumentNames = (attachments: ZipAttachment[], documentNames: string[]) => {
|
||||
const buckets = new Map<string, ZipAttachment[]>();
|
||||
|
||||
for (const attachment of attachments) {
|
||||
const keys = new Set([normalizeKey(attachment.path), normalizeKey(attachment.name)]);
|
||||
|
||||
for (const key of keys) {
|
||||
const bucket = buckets.get(key) ?? [];
|
||||
bucket.push(attachment);
|
||||
buckets.set(key, bucket);
|
||||
}
|
||||
}
|
||||
|
||||
const usedAttachments = new Set<ZipAttachment>();
|
||||
const orderedAttachments: ZipAttachment[] = [];
|
||||
|
||||
const takeAttachment = (key: string) => {
|
||||
const bucket = buckets.get(key);
|
||||
|
||||
if (!bucket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nextAttachment = bucket.find((attachment) => !usedAttachments.has(attachment)) ?? null;
|
||||
|
||||
if (nextAttachment) {
|
||||
usedAttachments.add(nextAttachment);
|
||||
}
|
||||
|
||||
return nextAttachment;
|
||||
};
|
||||
|
||||
for (const documentName of documentNames) {
|
||||
const normalizedName = normalizeKey(documentName);
|
||||
const normalizedBaseName = normalizeKey(getBaseName(documentName));
|
||||
const matchedAttachment = takeAttachment(normalizedName) ?? takeAttachment(normalizedBaseName);
|
||||
|
||||
if (matchedAttachment) {
|
||||
orderedAttachments.push(matchedAttachment);
|
||||
}
|
||||
}
|
||||
|
||||
for (const attachment of attachments) {
|
||||
if (!usedAttachments.has(attachment)) {
|
||||
orderedAttachments.push(attachment);
|
||||
}
|
||||
}
|
||||
|
||||
return orderedAttachments;
|
||||
};
|
||||
|
||||
export const extractZipArchive = async (file: File): Promise<ProcessedZipArchive> => {
|
||||
const archiveBytes = new Uint8Array(await file.arrayBuffer());
|
||||
const files = await unzipArchive(archiveBytes);
|
||||
@@ -91,9 +211,12 @@ export const extractZipArchive = async (file: File): Promise<ProcessedZipArchive
|
||||
}
|
||||
}
|
||||
|
||||
const documentNames = xjustizNachrichtXml ? getXJustizDocumentNames(xjustizNachrichtXml) : [];
|
||||
const orderedAttachments = orderAttachmentsByDocumentNames(attachments, documentNames);
|
||||
const archiveName = documentNames[0] ? getBaseName(documentNames[0]) : file.name;
|
||||
|
||||
return {
|
||||
name: file.name,
|
||||
attachments,
|
||||
xjustizNachrichtXml,
|
||||
name: archiveName,
|
||||
attachments: orderedAttachments
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user