88 lines
3.0 KiB
TypeScript
88 lines
3.0 KiB
TypeScript
import { isoToGerman } from '$lib/shared/utils/date';
|
|
|
|
export interface FilenameParseResult {
|
|
/** ISO format: YYYY-MM-DD */
|
|
dateIso?: string;
|
|
/** "Firstname Lastname" — order reversed from filename convention */
|
|
personName?: string;
|
|
/** Ready-to-use title, e.g. "Hans Mueller (12.03.1965)" */
|
|
suggestedTitle?: string;
|
|
}
|
|
|
|
// A date token is either YYYY-MM-DD or YYYYMMDD with a plausible month/day range.
|
|
function tryParseDate(s: string): string | undefined {
|
|
if (/^\d{4}-\d{2}-\d{2}$/.test(s)) {
|
|
const m = parseInt(s.slice(5, 7));
|
|
const d = parseInt(s.slice(8, 10));
|
|
if (m >= 1 && m <= 12 && d >= 1 && d <= 31) return s;
|
|
} else if (/^\d{8}$/.test(s)) {
|
|
const m = parseInt(s.slice(4, 6));
|
|
const d = parseInt(s.slice(6, 8));
|
|
if (m >= 1 && m <= 12 && d >= 1 && d <= 31)
|
|
return `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}`;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
const NAME_PART = /^\p{L}+$/u;
|
|
|
|
/**
|
|
* Parses a structured filename and extracts a date and person name.
|
|
*
|
|
* Supported conventions (date-first or date-last, compound last names supported):
|
|
* YYYY-MM-DD_Lastname_Firstname.ext
|
|
* YYYYMMDD_Lastname_Firstname.ext
|
|
* YYYYMMDD_de_Gruyter_Walter.ext ← compound last name: lastName="de Gruyter"
|
|
* Lastname_Firstname_YYYY-MM-DD.ext
|
|
* Lastname_Firstname_YYYYMMDD.ext
|
|
* de_Gruyter_Walter_YYYYMMDD.ext ← compound last name: lastName="de Gruyter"
|
|
*
|
|
* Algorithm: split on "_", identify the date token (first or last segment),
|
|
* treat the outermost remaining segment as firstName, rest as lastName parts.
|
|
* Returns {} for anything that doesn't match cleanly.
|
|
*/
|
|
export function parseFilename(filename: string): FilenameParseResult {
|
|
const dot = filename.lastIndexOf('.');
|
|
if (dot < 0) return {}; // no extension — not a real file
|
|
const stem = filename.slice(0, dot);
|
|
const parts = stem.split('_');
|
|
|
|
// Minimum: date + at least one lastName segment + firstName = 3 parts
|
|
if (parts.length < 3) return {};
|
|
|
|
let dateIso: string;
|
|
let nameParts: string[];
|
|
|
|
const dateFromFirst = tryParseDate(parts[0]);
|
|
if (dateFromFirst) {
|
|
dateIso = dateFromFirst;
|
|
nameParts = parts.slice(1);
|
|
} else {
|
|
const dateFromLast = tryParseDate(parts[parts.length - 1]);
|
|
if (!dateFromLast) return {};
|
|
dateIso = dateFromLast;
|
|
nameParts = parts.slice(0, -1);
|
|
}
|
|
|
|
// Need at least lastName + firstName after removing the date
|
|
if (nameParts.length < 2) return {};
|
|
|
|
// All name segments must be pure letters (covers umlauts via \p{L})
|
|
if (!nameParts.every((p) => NAME_PART.test(p))) return {};
|
|
|
|
const firstName = nameParts[nameParts.length - 1];
|
|
const lastName = nameParts.slice(0, -1).join(' ');
|
|
const personName = `${firstName} ${lastName}`;
|
|
const suggestedTitle = `${personName} (${isoToGerman(dateIso)})`;
|
|
|
|
return { dateIso, personName, suggestedTitle };
|
|
}
|
|
|
|
export function stripExtension(filename: string): string {
|
|
return filename.replace(/\.[^/.]+$/, '');
|
|
}
|
|
|
|
export function bulkTitleFromFilename(filename: string): string {
|
|
return stripExtension(filename).replace(/[_-]+/g, ' ').trim();
|
|
}
|