meca
Version:
Types and utilities for working with MECA
278 lines (277 loc) • 13.9 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 fetch from 'node-fetch';
import { createTempFolder, elementWithText, removeTempFolder, select, selectAll } from './utils.js';
export const TRANSFER = 'transfer.xml';
export const TRANSFER_DTD = 'transfer-1.0.dtd';
function extractContact(node) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
if (!node)
return undefined;
const role = (node === null || node === void 0 ? void 0 : node['contact-role']) || (node === null || node === void 0 ? void 0 : node.role);
const surname = (_c = (_b = (_a = select('surname', node)) === null || _a === void 0 ? void 0 : _a.children) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.value;
const given = (_f = (_e = (_d = select('given-names,given-name', node)) === null || _d === void 0 ? void 0 : _d.children) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.value;
const email = (_j = (_h = (_g = select('email', node)) === null || _g === void 0 ? void 0 : _g.children) === null || _h === void 0 ? void 0 : _h[0]) === null || _j === void 0 ? void 0 : _j.value;
const phone = (_m = (_l = (_k = select('phone', node)) === null || _k === void 0 ? void 0 : _k.children) === null || _l === void 0 ? void 0 : _l[0]) === null || _m === void 0 ? void 0 : _m.value;
const contact = {
role,
name: given || surname ? { given, surname } : undefined,
email,
phone,
};
return contact;
}
function extractProvider(node) {
var _a, _b, _c;
if (!node)
return undefined;
const name = (_c = (_b = (_a = select('provider-name,name', node)) === null || _a === void 0 ? void 0 : _a.children) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.value;
const contact = extractContact(select('contact', node));
const provider = { name, contact };
return provider;
}
function extractPublication(node) {
var _a, _b, _c, _d, _e, _f;
if (!node)
return undefined;
const type = node._type;
const title = (_c = (_b = (_a = select('publication-title,title', node)) === null || _a === void 0 ? void 0 : _a.children) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.value;
const acronym = (_f = (_e = (_d = select('acronym', node)) === null || _d === void 0 ? void 0 : _d.children) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.value;
const contact = extractContact(select('contact', node));
const serviceProvider = { type, title, acronym, contact };
return serviceProvider;
}
function extractSecurity(node) {
var _a, _b, _c;
if (!node)
return undefined;
const auth = (_c = (_b = (_a = select('authentication-code', node)) === null || _a === void 0 ? void 0 : _a.children) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.value;
if (!auth)
return undefined;
return { auth };
}
export class TransferXml {
constructor(data, opts) {
var _a, _b;
const toc = tic();
this.rawXML = data;
this.log = (_a = opts === null || opts === void 0 ? void 0 : opts.log) !== null && _a !== void 0 ? _a : console;
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 === 'transfer')) {
throw new Error('Element <transfer> is not the only element of the transfer.xml');
}
this.doctype = elements[0].doctype;
const converted = convertToUnist(elements[1]);
this.tree = select('transfer', converted);
(_b = this.log) === null || _b === void 0 ? void 0 : _b.debug(toc('Parsed and converted transfer.xml to unist tree in %s'));
}
get localDtd() {
// This works both compiled and in tests
const dtd = fs.existsSync(path.join(__dirname, TRANSFER_DTD))
? path.join(__dirname, TRANSFER_DTD)
: path.join(__dirname, '..', 'static', TRANSFER_DTD);
if (fs.existsSync(dtd))
return dtd;
throw new Error(`Unable to locate transfer DTD file ${TRANSFER_DTD} in meca lib distribution`);
}
validateXml(remoteDtd) {
return __awaiter(this, void 0, void 0, function* () {
const tempFolder = createTempFolder();
fs.writeFileSync(path.join(tempFolder, TRANSFER), this.rawXML);
let dtdFile = this.localDtd;
if (remoteDtd) {
const data = yield (yield fetch(remoteDtd)).text();
dtdFile = path.join(tempFolder, TRANSFER_DTD);
fs.writeFileSync(dtdFile, data);
}
const manifestIsValid = yield xmllintValidate(this, path.join(tempFolder, TRANSFER), dtdFile).catch(() => {
this.log.error(`${TRANSFER} DTD validation failed`);
return false;
});
removeTempFolder(tempFolder);
return manifestIsValid;
});
}
get version() {
return this.tree['transfer-version'] || this.tree.version;
}
get source() {
const source = select('transfer-source', this.tree) || select('source', this.tree);
if (!source)
return undefined;
const provider = extractProvider(select('service-provider', source));
const publication = extractPublication(select('publication', source));
const security = extractSecurity(select('security', source));
return { provider, publication, security };
}
get destination() {
const source = select('transfer-destination', this.tree) || select('destination', this.tree);
if (!source)
return undefined;
const provider = extractProvider(select('service-provider', source));
const publication = extractPublication(select('publication', source));
const security = extractSecurity(select('security', source));
return { provider, publication, security };
}
get instructions() {
const parent = select('processing-instructions', this.tree);
if (!parent)
return undefined;
const instructions = selectAll('processing-instruction,instruction', parent).map((node) => {
var _a, _b;
return ({
sequence: node['processing-sequence'] || node.sequence,
instruction: (_b = (_a = node.children) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value,
});
});
const comments = selectAll('processing-comments,comments', parent).map((node) => { var _a, _b; return ((_b = (_a = node.children) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value) || ''; });
return { instructions, comments };
}
}
function writeContactElement(opts, contact) {
var _a, _b, _c, _d, _e, _f;
if (!contact)
return undefined;
return {
type: 'element',
name: 'contact',
attributes: contact.role
? { [(opts === null || opts === void 0 ? void 0 : opts.simplifiedXML) ? 'role' : 'contact-role']: contact.role }
: undefined,
elements: [
((_a = contact.name) === null || _a === void 0 ? void 0 : _a.surname) || ((_b = contact.name) === null || _b === void 0 ? void 0 : _b.given)
? {
type: 'element',
name: 'contact-name',
elements: [
((_c = contact.name) === null || _c === void 0 ? void 0 : _c.surname) ? elementWithText('surname', (_d = contact.name) === null || _d === void 0 ? void 0 : _d.surname) : undefined,
((_e = contact.name) === null || _e === void 0 ? void 0 : _e.given) ? elementWithText('given-names', (_f = contact.name) === null || _f === void 0 ? void 0 : _f.given) : undefined,
].filter((e) => !!e),
}
: undefined,
contact.email ? elementWithText('email', contact.email) : undefined,
contact.phone ? elementWithText('phone', contact.phone) : undefined,
].filter((e) => !!e),
};
}
function writeServiceProviderElement(opts, provider) {
if (!provider)
return undefined;
return {
type: 'element',
name: 'service-provider',
elements: [
provider.name ? elementWithText('provider-name', provider.name) : undefined,
writeContactElement(opts, provider.contact),
].filter((e) => !!e),
};
}
function writePublicationElement(opts, publication) {
if (!publication)
return undefined;
return {
type: 'element',
name: 'publication',
attributes: publication.type ? { type: publication.type } : undefined,
elements: [
publication.title
? elementWithText((opts === null || opts === void 0 ? void 0 : opts.simplifiedXML) ? 'title' : 'publication-title', publication.title)
: undefined,
publication.acronym ? elementWithText('acronym', publication.acronym) : undefined,
writeContactElement(opts, publication.contact),
].filter((e) => !!e),
};
}
function writeLocationElement(name, opts, location) {
if (!location)
return undefined;
return {
type: 'element',
name: name === 'source' ? ((opts === null || opts === void 0 ? void 0 : opts.simplifiedXML) ? 'source' : 'transfer-source') : 'destination',
elements: [
writeServiceProviderElement(opts, location.provider),
writePublicationElement(opts, location.publication),
location.security
? {
type: 'element',
name: 'security',
elements: [
location.security.auth
? elementWithText('authentication-code', location.security.auth)
: undefined,
].filter((e) => !!e),
}
: undefined,
].filter((e) => !!e),
};
}
function writeInstructionsElement(opts, instructions) {
var _a, _b, _c;
if (!instructions)
return undefined;
return {
type: 'element',
name: 'processing-instructions',
elements: [
...((_b = (_a = instructions.instructions) === null || _a === void 0 ? void 0 : _a.map(({ instruction, sequence }) => instruction
? elementWithText((opts === null || opts === void 0 ? void 0 : opts.simplifiedXML) ? 'instruction' : 'processing-instruction', instruction, sequence
? { [(opts === null || opts === void 0 ? void 0 : opts.simplifiedXML) ? 'sequence' : 'processing-sequence']: sequence }
: undefined)
: undefined).filter((e) => !!e)) !== null && _b !== void 0 ? _b : []),
...(_c = instructions.comments) === null || _c === void 0 ? void 0 : _c.map((comment) => comment
? elementWithText((opts === null || opts === void 0 ? void 0 : opts.simplifiedXML) ? 'comments' : 'processing-comments', comment)
: undefined).filter((e) => !!e),
].filter((e) => !!e),
};
}
export function createTransferXml(transfer, opts) {
var _a;
const element = {
type: 'element',
elements: [
{
type: 'doctype',
doctype: `transfer 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/transfer-1.0.dtd'}"`,
},
{
type: 'element',
name: 'transfer',
attributes: {
[(opts === null || opts === void 0 ? void 0 : opts.simplifiedXML) ? 'version' : 'transfer-version']: '1.0',
xmlns: 'https://manuscriptexchange.org/schema/transfer',
},
elements: [
writeLocationElement('source', opts, transfer.source),
writeLocationElement('destination', opts, transfer.destination),
writeInstructionsElement(opts, transfer.instructions),
].filter((e) => !!e),
},
],
declaration: { attributes: { version: '1.0', encoding: 'UTF-8' } },
};
const manifest = js2xml(element, {
compact: false,
spaces: 2,
});
return manifest;
}