UNPKG

meca

Version:
163 lines (162 loc) 7.4 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import fs from 'fs'; import path from 'path'; import AdmZip from 'adm-zip'; import chalk from 'chalk'; import { validateJatsAgainstDtd } from 'jats-xml'; import { ItemTypes, MANIFEST, ManifestXml } from './manifest.js'; import { createTempFolder, removeTempFolder } from './utils.js'; import { TRANSFER, TransferXml } from './transfer.js'; const KNOWN_ITEM_TYPES = Object.values(ItemTypes); /** * Function to log debug message for passing check */ function debugCheck(session, msg) { session.log.debug(chalk.green(`✓ ${msg}`)); } /** * Function to log an error and clean temp folder */ function errorAndClean(session, msg, tempFolder) { session.log.error(msg); removeTempFolder(tempFolder); return false; } /** * Validate a given file as MECA bundle * * Returns true if file is valid. * * Validation checks: * - File exists and is zip format * - Bundle includes manifest.xlm which validates against DTD * - manifest matches items present in the bundle * - manifest item types match known types * - JATS items validate */ export function validateMeca(session, file, opts) { var _a; return __awaiter(this, void 0, void 0, function* () { if (!fs.existsSync(file)) return errorAndClean(session, `Input file does not exists: ${file}`); if (!(file.endsWith('.meca') || file.endsWith('-meca.zip'))) { session.log.warn(`Some providers may require a file ending with '.meca' or '-meca.zip'`); } let mecaZip; try { mecaZip = new AdmZip(file); } catch (_b) { return errorAndClean(session, `Input file is not a zip archive: ${file}`); } debugCheck(session, 'is zip archive'); const manifestEntry = mecaZip.getEntry(MANIFEST); if (!manifestEntry) { return errorAndClean(session, `Input zip archive does not include required manifest file '${MANIFEST}'`); } debugCheck(session, `includes ${MANIFEST}`); const manifestString = manifestEntry.getData().toString(); const manifest = new ManifestXml(manifestString, { log: session.log }); const tempFolder = createTempFolder(); const manifestIsValid = yield manifest.validateXml(); if (!manifestIsValid) { return errorAndClean(session, `${MANIFEST} DTD validation failed`, tempFolder); } const transferFiles = yield Promise.all((_a = manifest.transferMetadata) === null || _a === void 0 ? void 0 : _a.map((item) => __awaiter(this, void 0, void 0, function* () { const entry = mecaZip.getEntry(item.href); if (!entry) return false; const data = entry.getData().toString(); try { const transfer = new TransferXml(data, { log: session.log }); const valid = yield transfer.validateXml(); if (!valid) session.log.error(`${TRANSFER} DTD validation failed`); return valid; } catch (error) { session.log.error(`Could not read ${item.href} or DTD validation failed`); return false; } }))); if (!transferFiles.reduce((a, b) => a && b, true)) { return errorAndClean(session, `${TRANSFER} validation failed`, tempFolder); } debugCheck(session, `${MANIFEST} passes schema validation`); const manifestItems = manifest.items; const zipEntries = mecaZip.getEntries(); // Get all file and folder names in the zip file. // Folders may not be explicitly listed in zipEntries, so we compute all folders from file paths. const zipEntryNames = new Set(); zipEntries.forEach((entry) => { const nameParts = entry.entryName.split('/'); for (let i = 1; i <= nameParts.length; i++) { zipEntryNames.add(nameParts.slice(0, i).join('/')); } }); const manifestExtras = manifestItems .map((item) => item.href) .filter((href) => !zipEntryNames.has(href.replace(/\/$/, ''))); // Ignore trailing slash const zipExtras = zipEntries .filter((entry) => entry.entryName !== MANIFEST) .filter((entry) => !entry.isDirectory) .filter((entry) => !manifestItems.map((item) => item.href).includes(entry.entryName)) .map((entry) => entry.entryName); if (zipExtras.length) { session.log.warn(`MECA bundle includes items missing from manifest:\n- ${zipExtras.join('\n- ')}`); } if (manifestExtras.length) { return errorAndClean(session, `manifest items missing from MECA bundle:\n- ${manifestExtras.join('\n- ')}`, tempFolder); } debugCheck(session, 'manifest matches MECA bundle contents'); manifestItems.forEach((item) => { if (!item.mediaType) { session.log.warn(`manifest item missing media-type: ${item.href}`); } if (!item.itemType) { session.log.warn(`manifest item missing item-type: ${item.href}`); } else if (!KNOWN_ITEM_TYPES.includes(item.itemType)) { session.log.warn(`manifest item has unknown item-type "${item.itemType}": ${item.href} `); } }); const jatsFiles = manifestItems .filter((item) => item.itemType === 'article-metadata') .map((item) => item.href); const invalidJatsFiles = (yield Promise.all(jatsFiles.map((jatsFile) => __awaiter(this, void 0, void 0, function* () { mecaZip.extractEntryTo(jatsFile, tempFolder); const isValid = yield validateJatsAgainstDtd(session, path.join(tempFolder, ...jatsFile.split('/')), opts); return isValid ? undefined : jatsFile; })))).filter((jatsFile) => !!jatsFile); if (invalidJatsFiles.length) { return errorAndClean(session, `JATS DTD validation failed:\n- ${invalidJatsFiles.join('\n- ')}`, tempFolder); } debugCheck(session, 'JATS validation passed'); removeTempFolder(tempFolder); return true; }); } /** * Validate a given file as MECA bundle * * Logs confirmation message if valid and throws an error if invalid. */ export function validateMecaWrapper(session, file, opts) { return __awaiter(this, void 0, void 0, function* () { const success = yield validateMeca(session, file, opts); if (success) { session.log.info(chalk.greenBright('MECA validation passed!')); } else { throw new Error('MECA validation failed.'); } }); }