From 15a3fc5877d261964e6a7acb611803dd571e2f84 Mon Sep 17 00:00:00 2001 From: rehlert Date: Fri, 12 Jun 2026 12:17:13 +0200 Subject: [PATCH] parses xjustiz and orders attachments --- .idea/workspace.xml | 21 ++++-- src/lib/zip-processing.ts | 135 ++++++++++++++++++++++++++++++++++++-- src/routes/+page.svelte | 4 -- 3 files changed, 144 insertions(+), 16 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 91597f3..6fb4981 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,8 +2,8 @@ - - + + @@ -80,7 +88,8 @@ diff --git a/src/lib/zip-processing.ts b/src/lib/zip-processing.ts index fadcdff..a8d8f80 100644 --- a/src/lib/zip-processing.ts +++ b/src/lib/zip-processing.ts @@ -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>((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(); + + 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(); + 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 => { const archiveBytes = new Uint8Array(await file.arrayBuffer()); const files = await unzipArchive(archiveBytes); @@ -91,9 +211,12 @@ export const extractZipArchive = async (file: File): Promise {file.name} - {file.attachments.length} relevante Datei{file.attachments.length === 1 ? "" : "en"} - {#if file.xjustizNachrichtXml} - - xjustiz_nachricht.xml vorhanden - {/if} {/each}