// Ported from filius-ipad (GPLv2/GPLv3): // ios/FiliusPad/TopologyEditor/Persistence/TopologyFLSArchive.swift // importFiliusArchiveDocument, exportFiliusArchiveWithReport (the archive part) // Java counterpart: filius.rahmenprogramm.SzenarioVerwaltung (its save and load methods). // // A .fls holds the project in `projekt/konfiguration.xml` and the list of the // user's own Java applications in `projekt/anwendungen/EigeneAnwendungen.txt` // (plus their sources and classes). Everything besides the configuration is // carried through unchanged: this port cannot run custom Java applications, // but it must not destroy them either. import { canonicalEntryKey, FlsArchiveError, readZipEntries, writeZipEntries } from './zip.js'; export const CONFIGURATION_ENTRY_PATH = 'projekt/konfiguration.xml'; export const APPLICATIONS_ENTRY_PATH = 'projekt/anwendungen/EigeneAnwendungen.txt'; export interface FlsArchiveContents { /** `projekt/konfiguration.xml`, as bytes. */ readonly configurationXml: Uint8Array; /** Every other entry, carried through to the next save. */ readonly supplementalEntries: ReadonlyMap; /** All entry paths of the archive, sorted. */ readonly entryPaths: readonly string[]; } /** Opens a .fls: checks the archive and separates the configuration from the rest. */ export function readFlsArchive(archive: Uint8Array): FlsArchiveContents { const entries = readZipEntries(archive); const configurationXml = entries.get(CONFIGURATION_ENTRY_PATH); if (configurationXml === undefined) { throw new FlsArchiveError('missingConfiguration', CONFIGURATION_ENTRY_PATH, 'The required configuration entry is missing.'); } const supplementalEntries = new Map(entries); supplementalEntries.delete(CONFIGURATION_ENTRY_PATH); return { configurationXml, supplementalEntries, entryPaths: [...entries.keys()].sort(), }; } /** * Writes a .fls from a configuration and the entries carried along. A * carried entry may not pose as one of the two FILIUS entries under another * spelling (`PROJEKT/Konfiguration.xml`); the application list is added * empty when missing, as Java always writes it. */ export function writeFlsArchive( configurationXml: Uint8Array, supplementalEntries: ReadonlyMap = new Map(), ): Uint8Array { const criticalConfigurationKey = canonicalEntryKey(CONFIGURATION_ENTRY_PATH); const criticalApplicationsKey = canonicalEntryKey(APPLICATIONS_ENTRY_PATH); for (const path of supplementalEntries.keys()) { const key = canonicalEntryKey(path); const isExactCriticalPath = path === CONFIGURATION_ENTRY_PATH || path === APPLICATIONS_ENTRY_PATH; if ((key === criticalConfigurationKey || key === criticalApplicationsKey) && !isExactCriticalPath) { throw new FlsArchiveError('duplicateEntry', path, 'Supplemental entry conflicts with a critical FILIUS archive path.'); } } const entries = new Map(supplementalEntries); if (!entries.has(APPLICATIONS_ENTRY_PATH)) entries.set(APPLICATIONS_ENTRY_PATH, new Uint8Array()); entries.set(CONFIGURATION_ENTRY_PATH, configurationXml); return writeZipEntries(entries); }