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