parses xjustiz and orders attachments
This commit is contained in:
Generated
+15
-6
@@ -2,8 +2,8 @@
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="89f9fc3a-63ad-41cb-8d25-f07df352eb98" name="Changes" comment="">
|
||||
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/pnpm-lock.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/pnpm-lock.yaml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/lib/zip-processing.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/lib/zip-processing.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/routes/+page.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/routes/+page.svelte" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
@@ -34,7 +34,7 @@
|
||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||
"cidr.known.project.marker": "true",
|
||||
"codeWithMe.voiceChat.enabledByDefault": "false",
|
||||
"git-widget-placeholder": "main",
|
||||
"git-widget-placeholder": "features/zip-processing",
|
||||
"last_opened_file_path": "/home/rehlert/repos/juri-merger",
|
||||
"nodejs_package_manager_path": "pnpm",
|
||||
"settings.editor.selected.configurable": "preferences.pluginManager",
|
||||
@@ -50,7 +50,7 @@
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1781169139290</updated>
|
||||
<workItem from="1781169141138" duration="309000" />
|
||||
<workItem from="1781251694220" duration="5995000" />
|
||||
<workItem from="1781251694220" duration="7259000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="adapts styling of rvg.legal">
|
||||
<option name="closed" value="true" />
|
||||
@@ -68,7 +68,15 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1781257473070</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="3" />
|
||||
<task id="LOCAL-00003" summary="zips are getting extracted">
|
||||
<option name="closed" value="true" />
|
||||
<created>1781258303003</created>
|
||||
<option name="number" value="00003" />
|
||||
<option name="presentableId" value="LOCAL-00003" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1781258303003</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="4" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@@ -80,7 +88,8 @@
|
||||
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
|
||||
<MESSAGE value="adapts styling of rvg.legal" />
|
||||
<MESSAGE value="adds dropzone to add zips" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="adds dropzone to add zips" />
|
||||
<MESSAGE value="zips are getting extracted" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="zips are getting extracted" />
|
||||
</component>
|
||||
<component name="XDebuggerManager">
|
||||
<breakpoint-manager>
|
||||
|
||||
+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
|
||||
};
|
||||
};
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
try {
|
||||
const processedZipArchive = await extractZipArchive(file);
|
||||
processedZipArchive.xjustizNachrichtXml
|
||||
processedZipFiles = [...processedZipFiles, processedZipArchive];
|
||||
} catch (error) {
|
||||
console.error(`Failed to process ${file.name}`, error);
|
||||
@@ -53,9 +52,6 @@
|
||||
{#each processedZipFiles as file}
|
||||
<li class="rounded-container bg-primary-50 px-4 py-3 text-[14px] font-medium text-primary-900">
|
||||
{file.name} - {file.attachments.length} relevante Datei{file.attachments.length === 1 ? "" : "en"}
|
||||
{#if file.xjustizNachrichtXml}
|
||||
- xjustiz_nachricht.xml vorhanden
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user