meca
Version:
Types and utilities for working with MECA
196 lines (195 loc) • 9.2 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 'node:fs';
import path from 'node:path';
import { js2xml, xml2js } from 'xml-js';
import { convertToUnist } from 'jats-utils';
import { xmllintValidate } from 'jats-xml';
import { tic } from 'myst-cli-utils';
import { createTempFolder, elementWithText, removeTempFolder, select, selectAll } from './utils.js';
export const MANIFEST = 'manifest.xml';
export const MANIFEST_DTD = 'manifest-1.0.dtd';
export var ItemTypes;
(function (ItemTypes) {
ItemTypes["articleMetadata"] = "article-metadata";
ItemTypes["articleSupportingFile"] = "article-supporting-file";
ItemTypes["manuscript"] = "manuscript";
ItemTypes["manuscriptSupportingFile"] = "manuscript-supporting-file";
ItemTypes["articleSource"] = "article-source";
ItemTypes["articleSourceEnvironment"] = "article-source-environment";
ItemTypes["articleSourceDirectory"] = "article-source-directory";
ItemTypes["transferMetadata"] = "transfer-metadata";
})(ItemTypes || (ItemTypes = {}));
export class ManifestXml {
constructor(data, opts) {
var _a, _b;
const toc = tic();
this.log = (_a = opts === null || opts === void 0 ? void 0 : opts.log) !== null && _a !== void 0 ? _a : console;
this.rawXML = data;
if (opts === null || opts === void 0 ? void 0 : opts.source)
this.source = opts.source;
try {
this.raw = xml2js(data, { compact: false });
}
catch (error) {
throw new Error('Problem parsing the TransferXML document, please ensure it is XML');
}
const { declaration, elements } = this.raw;
this.declaration = declaration === null || declaration === void 0 ? void 0 : declaration.attributes;
if (!((elements === null || elements === void 0 ? void 0 : elements.length) === 2 && elements[0].type === 'doctype' && elements[1].name === 'manifest')) {
throw new Error('Element <manifest> is not the only element of the manifest.xml');
}
this.doctype = elements[0].doctype;
const converted = convertToUnist(elements[1]);
this.tree = select('manifest', converted);
(_b = this.log) === null || _b === void 0 ? void 0 : _b.debug(toc('Parsed and converted manifest.xml to unist tree in %s'));
}
get localDtd() {
// This works both compiled and in tests
const dtd = fs.existsSync(path.join(__dirname, MANIFEST_DTD))
? path.join(__dirname, MANIFEST_DTD)
: path.join(__dirname, '..', 'static', MANIFEST_DTD);
if (fs.existsSync(dtd))
return dtd;
throw new Error(`Unable to locate manifest DTD file ${MANIFEST_DTD} in meca lib distribution`);
}
validateXml(remoteDtd) {
return __awaiter(this, void 0, void 0, function* () {
const tempFolder = createTempFolder();
fs.writeFileSync(path.join(tempFolder, MANIFEST), this.rawXML);
let dtdFile = this.localDtd;
if (remoteDtd) {
const data = yield (yield fetch(remoteDtd)).text();
dtdFile = path.join(tempFolder, MANIFEST_DTD);
fs.writeFileSync(dtdFile, data);
}
const manifestIsValid = yield xmllintValidate(this, path.join(tempFolder, MANIFEST), dtdFile).catch(() => {
this.log.error(`${MANIFEST} DTD validation failed`);
return false;
});
removeTempFolder(tempFolder);
return manifestIsValid;
});
}
get version() {
return this.tree['manifest-version'] || this.tree.version;
}
get items() {
const items = selectAll(`item`, this.tree)
.map((item) => {
var _a, _b, _c, _d, _e, _f, _g, _h;
const instances = selectAll(`instance`, item);
if (instances.length === 0) {
this.log.warn('Item without an instance');
return undefined;
}
if (instances.length > 1) {
this.log.warn('Item has multiple instances, only the first is used.');
}
const instance = instances[0];
const title = (_b = (_a = select(`item-title`, item)) === null || _a === void 0 ? void 0 : _a.children) === null || _b === void 0 ? void 0 : _b[0].value;
const description = (_d = (_c = select(`item-description`, item)) === null || _c === void 0 ? void 0 : _c.children) === null || _d === void 0 ? void 0 : _d[0].value;
const fileOrder = (_f = (_e = select(`file-order`, item)) === null || _e === void 0 ? void 0 : _e.children) === null || _f === void 0 ? void 0 : _f[0].value;
const metadata = Object.fromEntries((_h = (_g = selectAll('metadata', item)) === null || _g === void 0 ? void 0 : _g.map((n) => {
var _a;
return [
n['metadata-name'] || n.name,
(_a = n === null || n === void 0 ? void 0 : n.children) === null || _a === void 0 ? void 0 : _a[0].value,
];
})) !== null && _h !== void 0 ? _h : []);
return {
id: item.id,
itemType: item['item-type'],
version: item['item-version'] || item['version'],
title,
description,
href: instance['xlink:href'] || instance.href,
mediaType: instance['media-type'],
fileOrder,
metadata,
};
})
.filter((item) => !!item);
return items;
}
get itemTypes() {
const itemTypes = new Set();
this.items.forEach((item) => {
if (item.itemType)
itemTypes.add(item.itemType);
});
return [...itemTypes];
}
get articleMetadata() {
const items = this.items.filter((item) => item.itemType === ItemTypes.articleMetadata);
if (items.length > 1)
this.log.warn('More than 1 article metadata found');
return items[0];
}
get transferMetadata() {
return this.items.filter((item) => item.itemType === ItemTypes.transferMetadata);
}
}
function writeManifestItem(item, opts) {
const { id, version, href, itemType, mediaType, title, description, fileOrder, metadata } = item;
return {
type: 'element',
name: 'item',
attributes: {
id,
'item-type': itemType,
'item-version': version,
},
elements: [
title ? elementWithText('item-title', title) : undefined,
description ? elementWithText('item-description', description) : undefined,
fileOrder ? elementWithText('file-order', fileOrder) : undefined,
metadata && Object.keys(metadata !== null && metadata !== void 0 ? metadata : {}).length > 0
? {
type: 'element',
name: 'item-metadata',
elements: Object.entries(metadata).map(([k, v]) => elementWithText('metadata', v, { 'metadata-name': k })),
}
: undefined,
{
type: 'element',
name: 'instance',
attributes: {
'media-type': mediaType,
[(opts === null || opts === void 0 ? void 0 : opts.noXLink) ? 'href' : 'xlink:href']: href,
},
},
].filter((e) => !!e),
};
}
export function createManifestXml(manifestItems, opts) {
var _a;
const element = {
type: 'element',
elements: [
{
type: 'doctype',
doctype: `manifest PUBLIC "-//MECA//DTD Manifest v1.0//en" "${(_a = opts === null || opts === void 0 ? void 0 : opts.dtdUrl) !== null && _a !== void 0 ? _a : 'https://meca.zip/manifest-1.0.dtd'}"`,
},
{
type: 'element',
name: 'manifest',
attributes: Object.assign({ 'manifest-version': '1', xmlns: 'https://manuscriptexchange.org/schema/manifest' }, ((opts === null || opts === void 0 ? void 0 : opts.noXLink) ? {} : { 'xmlns:xlink': 'http://www.w3.org/1999/xlink' })),
elements: manifestItems.map((item) => writeManifestItem(item, opts)),
},
],
declaration: { attributes: { version: '1.0', encoding: 'UTF-8' } },
};
const manifest = js2xml(element, {
compact: false,
spaces: 2,
});
return manifest;
}