/** * Plausibilitätsprüfung von ISBN-10, ISBN-13, ISSN, Handle, DOI und URN */ /** * Prüft eine ISBN-10 auf Plausibilität * @param {string} isbn10 - Die zu prüfende ISBN-10 * @returns {boolean} - true wenn gültig, false wenn ungültig */ function validateISBN10(isbn10) { if (!isbn10) return false; // Normalisierung: Bindestriche und Leerzeichen entfernen const clean = isbn10.replace(/[-\s]/g, ''); // Muss genau 10 Zeichen haben if (clean.length !== 10) return false; // Erste 9 Zeichen müssen Ziffern sein, letztes Zeichen Ziffer oder X if (!/^\d{9}[\dX]$/.test(clean)) return false; // Prüfziffer berechnen let sum = 0; for (let i = 0; i < 9; i++) { sum += parseInt(clean[i]) * (10 - i); } const checkDigit = clean[9]; const calculatedCheck = (11 - (sum % 11)) % 11; return (calculatedCheck === 10 && checkDigit === 'X') || (calculatedCheck < 10 && checkDigit === calculatedCheck.toString()); } /** * Prüft eine ISBN-13 auf Plausibilität * @param {string} isbn13 - Die zu prüfende ISBN-13 * @returns {boolean} - true wenn gültig, false wenn ungültig */ function validateISBN13(isbn13) { if (!isbn13) return false; // Normalisierung: Bindestriche und Leerzeichen entfernen const clean = isbn13.replace(/[-\s]/g, ''); // Muss genau 13 Ziffern haben if (clean.length !== 13 || !/^\d{13}$/.test(clean)) return false; // Muss mit 978 oder 979 beginnen if (!clean.startsWith('978') && !clean.startsWith('979')) return false; // Prüfziffer berechnen (modulo 10) let sum = 0; for (let i = 0; i < 12; i++) { sum += parseInt(clean[i]) * (i % 2 === 0 ? 1 : 3); } const checkDigit = parseInt(clean[12]); const calculatedCheck = (10 - (sum % 10)) % 10; return checkDigit === calculatedCheck; } /** * Prüft einen DOI auf Plausibilität * @param {string} doi - Der zu prüfende DOI * @returns {boolean} - true wenn gültig, false wenn ungültig */ function validateDOI(doi) { if (!doi) return false; // DOI kann mit oder ohne "doi:" Präfix sein const clean = doi.replace(/^doi:\s*/i, ''); // Grundlegendes DOI-Format: 10.xxxx/yyyy // Registrant Code muss mit 10. beginnen const doiRegex = /^10\.\d{4,}\/[^\s]+$/; if (!doiRegex.test(clean)) return false; // Weitere Plausibilitätsprüfungen const parts = clean.split('/'); if (parts.length < 2) return false; const prefix = parts[0]; const suffix = parts.slice(1).join('/'); // Präfix muss 10.xxxx Format haben (mindestens 4 Ziffern nach 10.) if (!/^10\.\d{4,}$/.test(prefix)) return false; // Suffix darf nicht leer sein und keine Leerzeichen enthalten if (!suffix.trim() || /\s/.test(suffix)) return false; return true; } /** * Prüft eine URN auf Plausibilität * @param {string} urn - Die zu prüfende URN * @returns {boolean} - true wenn gültig, false wenn ungültig */ function validateURN(urn) { if (!urn) return false; // URN muss mit "urn:" beginnen (case-insensitive) if (!urn.toLowerCase().startsWith('urn:')) return false; // Grundlegendes URN-Format: urn:namespace:identifier const parts = urn.split(':'); if (parts.length < 3) return false; const namespace = parts[1]; const identifier = parts.slice(2).join(':'); // Namespace-Validierung if (!namespace || !/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(namespace)) { return false; } // Namespace darf nicht länger als 32 Zeichen sein if (namespace.length > 32) return false; // Identifier darf nicht leer sein if (!identifier) return false; // Prüfung auf reservierte Zeichen (vereinfacht) const reservedChars = /[<>"{}|\\^`\[\]]/; if (reservedChars.test(identifier)) return false; return true; } /** * Prüft eine ISSN auf Plausibilität * @param {string} issn - Die zu prüfende ISSN * @returns {boolean} - true wenn gültig, false wenn ungültig */ function validateISSN(issn) { if (!issn) return false; // Normalisierung: Bindestriche und Leerzeichen entfernen const clean = issn.replace(/[-\s]/g, ''); // Muss genau 8 Zeichen haben if (clean.length !== 8) return false; // Erste 7 Zeichen müssen Ziffern sein, letztes Zeichen Ziffer oder X if (!/^\d{7}[\dX]$/.test(clean)) return false; // Prüfziffer berechnen (modulo 11) let sum = 0; for (let i = 0; i < 7; i++) { sum += parseInt(clean[i]) * (8 - i); } const checkDigit = clean[7]; const calculatedCheck = (11 - (sum % 11)) % 11; return (calculatedCheck === 10 && checkDigit === 'X') || (calculatedCheck < 10 && checkDigit === calculatedCheck.toString()); } /** * Prüft eine eISSN (elektronische ISSN) auf Plausibilität * @param {string} eissn - Die zu prüfende eISSN * @returns {boolean} - true wenn gültig, false wenn ungültig */ function validateEISSN(eissn) { // eISSN hat das gleiche Format wie ISSN return validateISSN(eissn); } /** * Validierung verschiedener Identifier-Typen * @param {string} identifier - Der zu prüfende Identifier * @param {string} identifierType - Identifier Typ ("ISBN" | "ISSN" | "URN" | "DOI" | "Handle") * @returns {object} - Objekt mit Typ und Validierungsergebnis */ function validateIdentifier(identifier, identifierType) { if (!identifier) { return { type: 'unknown', valid: false, error: 'Leerer Identifier' }; } const clean = identifier.trim(); switch (identifierType) { case "URN": return { type: 'URN', valid: validateURN(clean), identifier: clean }; case "DOI": if (clean.toLowerCase().startsWith('doi:') || clean.startsWith('10.')) { return { type: 'DOI', valid: validateDOI(clean), identifier: clean }; } case "ISBN": const isbnClean = clean.replace(/[-\s]/g, ''); if (/^\d{10}$/.test(isbnClean) || /^\d{9}X$/.test(isbnClean)) { return { type: 'ISBN-10', valid: validateISBN10(clean), identifier: clean }; } if (/^\d{13}$/.test(isbnClean)) { return { type: 'ISBN-13', valid: validateISBN13(clean), identifier: clean }; } case "ISSN": const issnClean = clean.replace(/[-\s]/g, ''); if (/^\d{8}$/.test(issnClean) || /^\d{7}X$/.test(issnClean)) { return { type: 'ISSN', valid: validateISSN(clean), identifier: clean }; } case "Handle":if (clean.toLowerCase().startsWith('hdl:') || clean.toLowerCase().includes('hdl.handle.net') || clean.toLowerCase().includes('handle.net') || /^\d+(\.\d+)*\//.test(clean)) { return { type: 'Handle', valid: validateHandle(clean), identifier: clean }; } default: return { type: 'Identifikator unbekannt: Struktur ist kein DOI, keine URN, ISBN, ISSN oder Handle', valid: false, error: 'Bitte prüfen Sie den gewählten Identifikator und dessen Inhalt (z.B DOI: 10.1515/zfsoz-2025-2017, URN: urn:nbn:de:0035-vetrepository-783215)' }; } } /** * Hilfsfunktion: Normalisiert ISBN-Eingaben * @param {string} isbn - ISBN mit oder ohne Trennzeichen * @returns {string} - Normalisierte ISBN ohne Trennzeichen */ function normalizeISBN(isbn) { return isbn ? isbn.replace(/[-\s]/g, '') : ''; } /** * Hilfsfunktion: Extrahiert DOI ohne Präfix * @param {string} doi - DOI mit oder ohne "doi:" Präfix * @returns {string} - DOI ohne Präfix */ function normalizeDOI(doi) { return doi ? doi.replace(/^doi:\s*/i, '') : ''; } /** * Batch-Validierung für mehrere Identifier * @param {string[]} identifiers - Array von zu prüfenden Identifiern * @returns {object[]} - Array von Validierungsergebnissen */ function validateMultipleIdentifiers(identifiers) { if (!Array.isArray(identifiers)) { return []; } return identifiers.map(id => validateIdentifier(id)); } /** * Prüft ob ein String eine gültige ISBN (10 oder 13) ist * @param {string} isbn - Zu prüfende ISBN * @returns {boolean} - true wenn gültige ISBN-10 oder ISBN-13 */ function isValidISBN(isbn) { return validateISBN10(isbn) || validateISBN13(isbn); } function validateHandle(handle) { if (!handle) return false; // Handle kann mit oder ohne "hdl:" Präfix oder "http://hdl.handle.net/" sein let clean = handle.trim(); // Verschiedene Handle-Präfixe entfernen clean = clean.replace(/^hdl:\s*/i, ''); clean = clean.replace(/^https?:\/\/hdl\.handle\.net\//, ''); clean = clean.replace(/^https?:\/\/handle\.net\//, ''); // Handle-Format: prefix/suffix (z.B. 10.1000/123 oder 1721.1/12345) if (!clean.includes('/')) return false; const parts = clean.split('/'); if (parts.length < 2) return false; const prefix = parts[0]; const suffix = parts.slice(1).join('/'); // Prefix-Validierung if (!prefix || prefix.length === 0) return false; // Prefix darf nur Zahlen, Punkte und Bindestriche enthalten if (!/^[0-9.-]+$/.test(prefix)) return false; // Suffix-Validierung if (!suffix || suffix.length === 0) return false; // Suffix darf keine Leerzeichen enthalten if (/\s/.test(suffix)) return false; // Reservierte Zeichen prüfen const reservedChars = /[<>"{}|\\^`\[\]]/; if (reservedChars.test(suffix)) return false; return true; } // ======================================== // Beispiele für die Verwendung // ======================================== /* console.log('=== ISBN-10 Tests ==='); console.log('0-306-40615-2:', validateISBN10('0-306-40615-2')); // true console.log('0306406152:', validateISBN10('0306406152')); // true console.log('0306406151:', validateISBN10('0306406151')); // false console.log('0-19-852663-6:', validateISBN10('0-19-852663-6')); // true console.log('\n=== ISBN-13 Tests ==='); console.log('978-0-306-40615-7:', validateISBN13('978-0-306-40615-7')); // true console.log('9780306406157:', validateISBN13('9780306406157')); // true console.log('9780306406156:', validateISBN13('9780306406156')); // false console.log('979-0-000-00000-0:', validateISBN13('979-0-000-00000-0')); // true console.log('\n=== DOI Tests ==='); console.log('10.1000/182:', validateDOI('10.1000/182')); // true console.log('doi:10.1000/182:', validateDOI('doi:10.1000/182')); // true console.log('10.1038/nature12373:', validateDOI('10.1038/nature12373')); // true console.log('10.123/456:', validateDOI('10.123/456')); // false (zu kurz) console.log('\n=== URN Tests ==='); console.log('urn:isbn:0451450523:', validateURN('urn:isbn:0451450523')); // true console.log('urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66:', validateURN('urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66')); // true console.log('urn:nbn:de:bvb:19-146642:', validateURN('urn:nbn:de:bvb:19-146642')); // true console.log('not-a-urn:', validateURN('not-a-urn')); // false console.log('\n=== Hilfsfunktionen ==='); console.log('Normalisierte ISBN:', normalizeISBN('978-0-306-40615-7')); console.log('Normalisierter DOI:', normalizeDOI('doi:10.1000/182')); console.log('Gültige ISBN?:', isValidISBN('978-0-306-40615-7')); */