meca
Version:
Types and utilities for working with MECA
163 lines (162 loc) • 7.4 kB
JavaScript
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.');
}
});
}