meca 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -5
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +1 -1
- package/dist/cli/validate.d.ts +1 -0
- package/dist/cli/validate.d.ts.map +1 -0
- package/dist/cli/validate.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -1
- package/dist/{MECA_manifest.dtd → manifest-1.0.dtd} +2 -4
- package/dist/manifest.d.ts +55 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +194 -0
- package/dist/meca.cjs +3185 -2736
- package/dist/transfer-1.0.dtd +44 -0
- package/dist/transfer.d.ts +71 -0
- package/dist/transfer.d.ts.map +1 -0
- package/dist/transfer.js +276 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +30 -0
- package/dist/{validate/dtd.d.ts → validate.d.ts} +1 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +163 -0
- package/dist/version.d.ts +2 -1
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +1 -1
- package/package.json +9 -8
- package/dist/validate/dtd.js +0 -175
- package/dist/validate/index.d.ts +0 -1
- package/dist/validate/index.js +0 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<!-- DOCTYPE manifest PUBLIC “-//MECA//DTD Transfer v1.0//en" "https://meca.zip/transfer-1.0.dtd" -->
|
|
2
|
+
<!-- ============================================================= -->
|
|
3
|
+
<!-- SPECIAL CHARACTER MODULES -->
|
|
4
|
+
<!-- ============================================================= -->
|
|
5
|
+
<!-- SPECIAL CHARACTERS DECLARATIONS -->
|
|
6
|
+
<!-- Declares any standard XML special character
|
|
7
|
+
entities used in this DTD -->
|
|
8
|
+
<!ENTITY % xmlspecchars.ent
|
|
9
|
+
PUBLIC
|
|
10
|
+
"-//NLM//DTD JATS (Z39.96) JATS DTD Suite XML Special Characters Module v1.2 20190208//EN"
|
|
11
|
+
"JATS-xmlspecchars1.ent" >
|
|
12
|
+
<!-- CUSTOM SPECIAL CHARACTERS DECLARATIONS -->
|
|
13
|
+
<!-- Declares any custom special character
|
|
14
|
+
entities created for this Suite -->
|
|
15
|
+
<!ENTITY % chars.ent PUBLIC
|
|
16
|
+
"-//NLM//DTD JATS (Z39.96) JATS DTD Suite Custom Special Characters Module v1.2
|
|
17
|
+
20190208//EN"
|
|
18
|
+
"JATS-chars1.ent" >
|
|
19
|
+
<!ELEMENT transfer (transfer-source, destination, processing-instructions?)>
|
|
20
|
+
<!ATTLIST transfer xmlns CDATA #FIXED "https://manuscriptexchange.org/schema/transfer">
|
|
21
|
+
<!ATTLIST transfer transfer-version CDATA #REQUIRED>
|
|
22
|
+
<!ELEMENT transfer-source (service-provider?, publication, security?)>
|
|
23
|
+
<!ELEMENT service-provider (provider-name?, contact?)>
|
|
24
|
+
<!ELEMENT provider-name (#PCDATA)>
|
|
25
|
+
<!ELEMENT contact (contact-name?, (email|phone)+)>
|
|
26
|
+
<!ATTLIST contact contact-role CDATA #IMPLIED>
|
|
27
|
+
<!ELEMENT contact-name (surname?, given-names?)>
|
|
28
|
+
<!ELEMENT surname (#PCDATA)>
|
|
29
|
+
<!ELEMENT given-names (#PCDATA)>
|
|
30
|
+
<!ELEMENT email (#PCDATA)>
|
|
31
|
+
<!ELEMENT phone (#PCDATA)>
|
|
32
|
+
<!ELEMENT publication (publication-title, acronym?, contact?)>
|
|
33
|
+
<!ATTLIST publication type CDATA #IMPLIED>
|
|
34
|
+
<!ELEMENT publication-title (#PCDATA)>
|
|
35
|
+
<!ELEMENT acronym (#PCDATA)>
|
|
36
|
+
<!ELEMENT security (authentication-code)>
|
|
37
|
+
<!ELEMENT authentication-code (#PCDATA)>
|
|
38
|
+
<!ELEMENT destination (service-provider, publication, security?)>
|
|
39
|
+
<!-- The processing instructions tag is a placeholder for implementation-specific instructions between
|
|
40
|
+
vendors and publishers-->
|
|
41
|
+
<!ELEMENT processing-instructions (processing-instruction*, processing-comments?)>
|
|
42
|
+
<!ELEMENT processing-instruction (#PCDATA)>
|
|
43
|
+
<!ATTLIST processing-instruction processing-sequence CDATA #IMPLIED>
|
|
44
|
+
<!ELEMENT processing-comments (#PCDATA)>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { GenericParent } from 'myst-common';
|
|
2
|
+
import type { Element, DeclarationAttributes } from 'xml-js';
|
|
3
|
+
import type { Logger } from 'myst-cli-utils';
|
|
4
|
+
export declare const TRANSFER = "transfer.xml";
|
|
5
|
+
export declare const TRANSFER_DTD = "transfer-1.0.dtd";
|
|
6
|
+
type Options = {
|
|
7
|
+
log?: Logger;
|
|
8
|
+
};
|
|
9
|
+
export type Contact = {
|
|
10
|
+
role?: string;
|
|
11
|
+
name?: {
|
|
12
|
+
surname?: string;
|
|
13
|
+
given?: string;
|
|
14
|
+
};
|
|
15
|
+
email?: string;
|
|
16
|
+
phone?: string;
|
|
17
|
+
};
|
|
18
|
+
export type ServiceProvider = {
|
|
19
|
+
name?: string;
|
|
20
|
+
contact?: Contact;
|
|
21
|
+
};
|
|
22
|
+
export type Publication = {
|
|
23
|
+
type?: string;
|
|
24
|
+
title?: string;
|
|
25
|
+
acronym?: string;
|
|
26
|
+
contact?: Contact;
|
|
27
|
+
};
|
|
28
|
+
export type Security = {
|
|
29
|
+
auth?: string;
|
|
30
|
+
};
|
|
31
|
+
export type Location = {
|
|
32
|
+
provider?: ServiceProvider;
|
|
33
|
+
publication?: Publication;
|
|
34
|
+
security?: Security;
|
|
35
|
+
};
|
|
36
|
+
export type Instruction = {
|
|
37
|
+
sequence?: string;
|
|
38
|
+
instruction?: string;
|
|
39
|
+
};
|
|
40
|
+
export type ProcessingInstructions = {
|
|
41
|
+
instructions?: Instruction[];
|
|
42
|
+
comments?: string[];
|
|
43
|
+
};
|
|
44
|
+
export type Transfer = {
|
|
45
|
+
source?: Location;
|
|
46
|
+
destination?: Location;
|
|
47
|
+
instructions?: ProcessingInstructions;
|
|
48
|
+
};
|
|
49
|
+
export declare class TransferXml {
|
|
50
|
+
declaration?: DeclarationAttributes;
|
|
51
|
+
doctype?: string;
|
|
52
|
+
rawXML: string;
|
|
53
|
+
raw: Element;
|
|
54
|
+
log: Logger;
|
|
55
|
+
tree: GenericParent;
|
|
56
|
+
constructor(data: string, opts?: Options);
|
|
57
|
+
get localDtd(): string;
|
|
58
|
+
validateXml(remoteDtd?: string): Promise<boolean | undefined>;
|
|
59
|
+
get version(): string;
|
|
60
|
+
get source(): Location | undefined;
|
|
61
|
+
get destination(): Location | undefined;
|
|
62
|
+
get instructions(): ProcessingInstructions | undefined;
|
|
63
|
+
}
|
|
64
|
+
type WriteOptions = {
|
|
65
|
+
/** Some providers want a simplified XML output that changes the names of some of the XML elements. */
|
|
66
|
+
simplifiedXML?: boolean;
|
|
67
|
+
dtdUrl?: string;
|
|
68
|
+
};
|
|
69
|
+
export declare function createTransferXml(transfer: Transfer, opts?: WriteOptions): string;
|
|
70
|
+
export {};
|
|
71
|
+
//# sourceMappingURL=transfer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transfer.d.ts","sourceRoot":"","sources":["../src/transfer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAe,aAAa,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,KAAK,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAE7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAK7C,eAAO,MAAM,QAAQ,iBAAiB,CAAC;AACvC,eAAO,MAAM,YAAY,qBAAqB,CAAC;AAE/C,KAAK,OAAO,GAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhC,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB,YAAY,CAAC,EAAE,sBAAsB,CAAC;CACvC,CAAC;AA2CF,qBAAa,WAAW;IACtB,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,aAAa,CAAC;gBAER,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;IAsBxC,IAAI,QAAQ,IAAI,MAAM,CAOrB;IAEK,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM;IAqBpC,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,MAAM,IAAI,QAAQ,GAAG,SAAS,CAOjC;IAED,IAAI,WAAW,IAAI,QAAQ,GAAG,SAAS,CAOtC;IAED,IAAI,YAAY,IAAI,sBAAsB,GAAG,SAAS,CAerD;CACF;AAED,KAAK,YAAY,GAAG;IAClB,sGAAsG;IACtG,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAyHF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,YAAY,UA+BxE"}
|
package/dist/transfer.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { js2xml, xml2js } from 'xml-js';
|
|
13
|
+
import { convertToUnist, xmllintValidate } from 'jats-xml';
|
|
14
|
+
import { tic } from 'myst-cli-utils';
|
|
15
|
+
import fetch from 'node-fetch';
|
|
16
|
+
import { createTempFolder, elementWithText, removeTempFolder, select, selectAll } from './utils.js';
|
|
17
|
+
export const TRANSFER = 'transfer.xml';
|
|
18
|
+
export const TRANSFER_DTD = 'transfer-1.0.dtd';
|
|
19
|
+
function extractContact(node) {
|
|
20
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
21
|
+
if (!node)
|
|
22
|
+
return undefined;
|
|
23
|
+
const role = (node === null || node === void 0 ? void 0 : node['contact-role']) || (node === null || node === void 0 ? void 0 : node.role);
|
|
24
|
+
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;
|
|
25
|
+
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;
|
|
26
|
+
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;
|
|
27
|
+
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;
|
|
28
|
+
const contact = {
|
|
29
|
+
role,
|
|
30
|
+
name: given || surname ? { given, surname } : undefined,
|
|
31
|
+
email,
|
|
32
|
+
phone,
|
|
33
|
+
};
|
|
34
|
+
return contact;
|
|
35
|
+
}
|
|
36
|
+
function extractProvider(node) {
|
|
37
|
+
var _a, _b, _c;
|
|
38
|
+
if (!node)
|
|
39
|
+
return undefined;
|
|
40
|
+
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;
|
|
41
|
+
const contact = extractContact(select('contact', node));
|
|
42
|
+
const provider = { name, contact };
|
|
43
|
+
return provider;
|
|
44
|
+
}
|
|
45
|
+
function extractPublication(node) {
|
|
46
|
+
var _a, _b, _c, _d, _e, _f;
|
|
47
|
+
if (!node)
|
|
48
|
+
return undefined;
|
|
49
|
+
const type = node._type;
|
|
50
|
+
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;
|
|
51
|
+
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;
|
|
52
|
+
const contact = extractContact(select('contact', node));
|
|
53
|
+
const serviceProvider = { type, title, acronym, contact };
|
|
54
|
+
return serviceProvider;
|
|
55
|
+
}
|
|
56
|
+
function extractSecurity(node) {
|
|
57
|
+
var _a, _b, _c;
|
|
58
|
+
if (!node)
|
|
59
|
+
return undefined;
|
|
60
|
+
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;
|
|
61
|
+
if (!auth)
|
|
62
|
+
return undefined;
|
|
63
|
+
return { auth };
|
|
64
|
+
}
|
|
65
|
+
export class TransferXml {
|
|
66
|
+
constructor(data, opts) {
|
|
67
|
+
var _a, _b;
|
|
68
|
+
const toc = tic();
|
|
69
|
+
this.rawXML = data;
|
|
70
|
+
this.log = (_a = opts === null || opts === void 0 ? void 0 : opts.log) !== null && _a !== void 0 ? _a : console;
|
|
71
|
+
try {
|
|
72
|
+
this.raw = xml2js(data, { compact: false });
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
throw new Error('Problem parsing the TransferXML document, please ensure it is XML');
|
|
76
|
+
}
|
|
77
|
+
const { declaration, elements } = this.raw;
|
|
78
|
+
this.declaration = declaration === null || declaration === void 0 ? void 0 : declaration.attributes;
|
|
79
|
+
if (!((elements === null || elements === void 0 ? void 0 : elements.length) === 2 && elements[0].type === 'doctype' && elements[1].name === 'transfer')) {
|
|
80
|
+
throw new Error('Element <transfer> is not the only element of the transfer.xml');
|
|
81
|
+
}
|
|
82
|
+
this.doctype = elements[0].doctype;
|
|
83
|
+
const converted = convertToUnist(elements[1]);
|
|
84
|
+
this.tree = select('transfer', converted);
|
|
85
|
+
(_b = this.log) === null || _b === void 0 ? void 0 : _b.debug(toc('Parsed and converted transfer.xml to unist tree in %s'));
|
|
86
|
+
}
|
|
87
|
+
get localDtd() {
|
|
88
|
+
// This works both compiled and in tests
|
|
89
|
+
const dtd = fs.existsSync(path.join(__dirname, TRANSFER_DTD))
|
|
90
|
+
? path.join(__dirname, TRANSFER_DTD)
|
|
91
|
+
: path.join(__dirname, '..', 'static', TRANSFER_DTD);
|
|
92
|
+
if (fs.existsSync(dtd))
|
|
93
|
+
return dtd;
|
|
94
|
+
throw new Error(`Unable to locate transfer DTD file ${TRANSFER_DTD} in meca lib distribution`);
|
|
95
|
+
}
|
|
96
|
+
validateXml(remoteDtd) {
|
|
97
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
98
|
+
const tempFolder = createTempFolder();
|
|
99
|
+
fs.writeFileSync(path.join(tempFolder, TRANSFER), this.rawXML);
|
|
100
|
+
let dtdFile = this.localDtd;
|
|
101
|
+
if (remoteDtd) {
|
|
102
|
+
const data = yield (yield fetch(remoteDtd)).text();
|
|
103
|
+
dtdFile = path.join(tempFolder, TRANSFER_DTD);
|
|
104
|
+
fs.writeFileSync(dtdFile, data);
|
|
105
|
+
}
|
|
106
|
+
const manifestIsValid = yield xmllintValidate(this, path.join(tempFolder, TRANSFER), dtdFile).catch(() => {
|
|
107
|
+
this.log.error(`${TRANSFER} DTD validation failed`);
|
|
108
|
+
return false;
|
|
109
|
+
});
|
|
110
|
+
removeTempFolder(tempFolder);
|
|
111
|
+
return manifestIsValid;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
get version() {
|
|
115
|
+
return this.tree['transfer-version'] || this.tree.version;
|
|
116
|
+
}
|
|
117
|
+
get source() {
|
|
118
|
+
const source = select('transfer-source', this.tree) || select('source', this.tree);
|
|
119
|
+
if (!source)
|
|
120
|
+
return undefined;
|
|
121
|
+
const provider = extractProvider(select('service-provider', source));
|
|
122
|
+
const publication = extractPublication(select('publication', source));
|
|
123
|
+
const security = extractSecurity(select('security', source));
|
|
124
|
+
return { provider, publication, security };
|
|
125
|
+
}
|
|
126
|
+
get destination() {
|
|
127
|
+
const source = select('transfer-destination', this.tree) || select('destination', this.tree);
|
|
128
|
+
if (!source)
|
|
129
|
+
return undefined;
|
|
130
|
+
const provider = extractProvider(select('service-provider', source));
|
|
131
|
+
const publication = extractPublication(select('publication', source));
|
|
132
|
+
const security = extractSecurity(select('security', source));
|
|
133
|
+
return { provider, publication, security };
|
|
134
|
+
}
|
|
135
|
+
get instructions() {
|
|
136
|
+
const parent = select('processing-instructions', this.tree);
|
|
137
|
+
if (!parent)
|
|
138
|
+
return undefined;
|
|
139
|
+
const instructions = selectAll('processing-instruction,instruction', parent).map((node) => {
|
|
140
|
+
var _a, _b;
|
|
141
|
+
return ({
|
|
142
|
+
sequence: node['processing-sequence'] || node.sequence,
|
|
143
|
+
instruction: (_b = (_a = node.children) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value,
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
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) || ''; });
|
|
147
|
+
return { instructions, comments };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function writeContactElement(opts, contact) {
|
|
151
|
+
var _a, _b, _c, _d, _e, _f;
|
|
152
|
+
if (!contact)
|
|
153
|
+
return undefined;
|
|
154
|
+
return {
|
|
155
|
+
type: 'element',
|
|
156
|
+
name: 'contact',
|
|
157
|
+
attributes: contact.role
|
|
158
|
+
? { [(opts === null || opts === void 0 ? void 0 : opts.simplifiedXML) ? 'role' : 'contact-role']: contact.role }
|
|
159
|
+
: undefined,
|
|
160
|
+
elements: [
|
|
161
|
+
((_a = contact.name) === null || _a === void 0 ? void 0 : _a.surname) || ((_b = contact.name) === null || _b === void 0 ? void 0 : _b.given)
|
|
162
|
+
? {
|
|
163
|
+
type: 'element',
|
|
164
|
+
name: 'contact-name',
|
|
165
|
+
elements: [
|
|
166
|
+
((_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,
|
|
167
|
+
((_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,
|
|
168
|
+
].filter((e) => !!e),
|
|
169
|
+
}
|
|
170
|
+
: undefined,
|
|
171
|
+
contact.email ? elementWithText('email', contact.email) : undefined,
|
|
172
|
+
contact.phone ? elementWithText('phone', contact.phone) : undefined,
|
|
173
|
+
].filter((e) => !!e),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function writeServiceProviderElement(opts, provider) {
|
|
177
|
+
if (!provider)
|
|
178
|
+
return undefined;
|
|
179
|
+
return {
|
|
180
|
+
type: 'element',
|
|
181
|
+
name: 'service-provider',
|
|
182
|
+
elements: [
|
|
183
|
+
provider.name ? elementWithText('provider-name', provider.name) : undefined,
|
|
184
|
+
writeContactElement(opts, provider.contact),
|
|
185
|
+
].filter((e) => !!e),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function writePublicationElement(opts, publication) {
|
|
189
|
+
if (!publication)
|
|
190
|
+
return undefined;
|
|
191
|
+
return {
|
|
192
|
+
type: 'element',
|
|
193
|
+
name: 'publication',
|
|
194
|
+
attributes: publication.type ? { type: publication.type } : undefined,
|
|
195
|
+
elements: [
|
|
196
|
+
publication.title
|
|
197
|
+
? elementWithText((opts === null || opts === void 0 ? void 0 : opts.simplifiedXML) ? 'title' : 'publication-title', publication.title)
|
|
198
|
+
: undefined,
|
|
199
|
+
publication.acronym ? elementWithText('acronym', publication.acronym) : undefined,
|
|
200
|
+
writeContactElement(opts, publication.contact),
|
|
201
|
+
].filter((e) => !!e),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function writeLocationElement(name, opts, location) {
|
|
205
|
+
if (!location)
|
|
206
|
+
return undefined;
|
|
207
|
+
return {
|
|
208
|
+
type: 'element',
|
|
209
|
+
name: name === 'source' ? ((opts === null || opts === void 0 ? void 0 : opts.simplifiedXML) ? 'source' : 'transfer-source') : 'destination',
|
|
210
|
+
elements: [
|
|
211
|
+
writeServiceProviderElement(opts, location.provider),
|
|
212
|
+
writePublicationElement(opts, location.publication),
|
|
213
|
+
location.security
|
|
214
|
+
? {
|
|
215
|
+
type: 'element',
|
|
216
|
+
name: 'security',
|
|
217
|
+
elements: [
|
|
218
|
+
location.security.auth
|
|
219
|
+
? elementWithText('authentication-code', location.security.auth)
|
|
220
|
+
: undefined,
|
|
221
|
+
].filter((e) => !!e),
|
|
222
|
+
}
|
|
223
|
+
: undefined,
|
|
224
|
+
].filter((e) => !!e),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function writeInstructionsElement(opts, instructions) {
|
|
228
|
+
var _a, _b, _c;
|
|
229
|
+
if (!instructions)
|
|
230
|
+
return undefined;
|
|
231
|
+
return {
|
|
232
|
+
type: 'element',
|
|
233
|
+
name: 'processing-instructions',
|
|
234
|
+
elements: [
|
|
235
|
+
...((_b = (_a = instructions.instructions) === null || _a === void 0 ? void 0 : _a.map(({ instruction, sequence }) => instruction
|
|
236
|
+
? elementWithText((opts === null || opts === void 0 ? void 0 : opts.simplifiedXML) ? 'instruction' : 'processing-instruction', instruction, sequence
|
|
237
|
+
? { [(opts === null || opts === void 0 ? void 0 : opts.simplifiedXML) ? 'sequence' : 'processing-sequence']: sequence }
|
|
238
|
+
: undefined)
|
|
239
|
+
: undefined).filter((e) => !!e)) !== null && _b !== void 0 ? _b : []),
|
|
240
|
+
...(_c = instructions.comments) === null || _c === void 0 ? void 0 : _c.map((comment) => comment
|
|
241
|
+
? elementWithText((opts === null || opts === void 0 ? void 0 : opts.simplifiedXML) ? 'comments' : 'processing-comments', comment)
|
|
242
|
+
: undefined).filter((e) => !!e),
|
|
243
|
+
].filter((e) => !!e),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
export function createTransferXml(transfer, opts) {
|
|
247
|
+
var _a;
|
|
248
|
+
const element = {
|
|
249
|
+
type: 'element',
|
|
250
|
+
elements: [
|
|
251
|
+
{
|
|
252
|
+
type: 'doctype',
|
|
253
|
+
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'}"`,
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
type: 'element',
|
|
257
|
+
name: 'transfer',
|
|
258
|
+
attributes: {
|
|
259
|
+
[(opts === null || opts === void 0 ? void 0 : opts.simplifiedXML) ? 'version' : 'transfer-version']: '1.0',
|
|
260
|
+
xmlns: 'https://manuscriptexchange.org/schema/transfer',
|
|
261
|
+
},
|
|
262
|
+
elements: [
|
|
263
|
+
writeLocationElement('source', opts, transfer.source),
|
|
264
|
+
writeLocationElement('destination', opts, transfer.destination),
|
|
265
|
+
writeInstructionsElement(opts, transfer.instructions),
|
|
266
|
+
].filter((e) => !!e),
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
declaration: { attributes: { version: '1.0', encoding: 'UTF-8' } },
|
|
270
|
+
};
|
|
271
|
+
const manifest = js2xml(element, {
|
|
272
|
+
compact: false,
|
|
273
|
+
spaces: 2,
|
|
274
|
+
});
|
|
275
|
+
return manifest;
|
|
276
|
+
}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { GenericNode } from 'myst-common';
|
|
2
|
+
import type { Element } from 'xml-js';
|
|
3
|
+
export declare function createTempFolder(): string;
|
|
4
|
+
export declare function removeTempFolder(tempFolder?: string): void;
|
|
5
|
+
export declare function select<T extends GenericNode>(selector: string, node?: GenericNode): T | undefined;
|
|
6
|
+
export declare function selectAll<T extends GenericNode>(selector: string, node?: GenericNode): T[];
|
|
7
|
+
export declare function elementWithText(name: string, text: string, attributes?: Record<string, string>): Element;
|
|
8
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAItC,wBAAgB,gBAAgB,WAE/B;AAED,wBAAgB,gBAAgB,CAAC,UAAU,CAAC,EAAE,MAAM,QAUnD;AAED,wBAAgB,MAAM,CAAC,CAAC,SAAS,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,CAAC,GAAG,SAAS,CAEjG;AAED,wBAAgB,SAAS,CAAC,CAAC,SAAS,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,CAAC,EAAE,CAE1F;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,OAAO,CAET"}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { select as unistSelect, selectAll as unistSelectAll } from 'unist-util-select';
|
|
5
|
+
export function createTempFolder() {
|
|
6
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'meca'));
|
|
7
|
+
}
|
|
8
|
+
export function removeTempFolder(tempFolder) {
|
|
9
|
+
if (tempFolder && fs.existsSync(tempFolder)) {
|
|
10
|
+
if (fs.rmSync) {
|
|
11
|
+
// Node >= 14.14
|
|
12
|
+
fs.rmSync(tempFolder, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
// Node < 14.14
|
|
16
|
+
fs.rmdirSync(tempFolder, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function select(selector, node) {
|
|
21
|
+
var _a;
|
|
22
|
+
return ((_a = unistSelect(selector, node)) !== null && _a !== void 0 ? _a : undefined);
|
|
23
|
+
}
|
|
24
|
+
export function selectAll(selector, node) {
|
|
25
|
+
var _a;
|
|
26
|
+
return ((_a = unistSelectAll(selector, node)) !== null && _a !== void 0 ? _a : undefined);
|
|
27
|
+
}
|
|
28
|
+
export function elementWithText(name, text, attributes) {
|
|
29
|
+
return { type: 'element', name, elements: [{ type: 'text', text }], attributes };
|
|
30
|
+
}
|
|
@@ -18,3 +18,4 @@ export declare function validateMeca(session: ISession, file: string, opts: Part
|
|
|
18
18
|
* Logs confirmation message if valid and throws an error if invalid.
|
|
19
19
|
*/
|
|
20
20
|
export declare function validateMecaWrapper(session: ISession, file: string, opts: Partial<JatsOptions>): Promise<void>;
|
|
21
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAwBtD;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,WAAW,CAAC,oBAqH7F;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,CAAC,WAAW,CAAC,iBAQ3B"}
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import AdmZip from 'adm-zip';
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import { validateJatsAgainstDtd } from 'jats-xml';
|
|
15
|
+
import { ItemTypes, MANIFEST, ManifestXml } from './manifest.js';
|
|
16
|
+
import { createTempFolder, removeTempFolder } from './utils.js';
|
|
17
|
+
import { TRANSFER, TransferXml } from './transfer.js';
|
|
18
|
+
const KNOWN_ITEM_TYPES = Object.values(ItemTypes);
|
|
19
|
+
/**
|
|
20
|
+
* Function to log debug message for passing check
|
|
21
|
+
*/
|
|
22
|
+
function debugCheck(session, msg) {
|
|
23
|
+
session.log.debug(chalk.green(`✓ ${msg}`));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Function to log an error and clean temp folder
|
|
27
|
+
*/
|
|
28
|
+
function errorAndClean(session, msg, tempFolder) {
|
|
29
|
+
session.log.error(msg);
|
|
30
|
+
removeTempFolder(tempFolder);
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validate a given file as MECA bundle
|
|
35
|
+
*
|
|
36
|
+
* Returns true if file is valid.
|
|
37
|
+
*
|
|
38
|
+
* Validation checks:
|
|
39
|
+
* - File exists and is zip format
|
|
40
|
+
* - Bundle includes manifest.xlm which validates against DTD
|
|
41
|
+
* - manifest matches items present in the bundle
|
|
42
|
+
* - manifest item types match known types
|
|
43
|
+
* - JATS items validate
|
|
44
|
+
*/
|
|
45
|
+
export function validateMeca(session, file, opts) {
|
|
46
|
+
var _a;
|
|
47
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
48
|
+
if (!fs.existsSync(file))
|
|
49
|
+
return errorAndClean(session, `Input file does not exists: ${file}`);
|
|
50
|
+
if (!(file.endsWith('.meca') || file.endsWith('-meca.zip'))) {
|
|
51
|
+
session.log.warn(`Some providers may require a file ending with '.meca' or '-meca.zip'`);
|
|
52
|
+
}
|
|
53
|
+
let mecaZip;
|
|
54
|
+
try {
|
|
55
|
+
mecaZip = new AdmZip(file);
|
|
56
|
+
}
|
|
57
|
+
catch (_b) {
|
|
58
|
+
return errorAndClean(session, `Input file is not a zip archive: ${file}`);
|
|
59
|
+
}
|
|
60
|
+
debugCheck(session, 'is zip archive');
|
|
61
|
+
const manifestEntry = mecaZip.getEntry(MANIFEST);
|
|
62
|
+
if (!manifestEntry) {
|
|
63
|
+
return errorAndClean(session, `Input zip archive does not include required manifest file '${MANIFEST}'`);
|
|
64
|
+
}
|
|
65
|
+
debugCheck(session, `includes ${MANIFEST}`);
|
|
66
|
+
const manifestString = manifestEntry.getData().toString();
|
|
67
|
+
const manifest = new ManifestXml(manifestString, { log: session.log });
|
|
68
|
+
const tempFolder = createTempFolder();
|
|
69
|
+
const manifestIsValid = yield manifest.validateXml();
|
|
70
|
+
if (!manifestIsValid) {
|
|
71
|
+
return errorAndClean(session, `${MANIFEST} DTD validation failed`, tempFolder);
|
|
72
|
+
}
|
|
73
|
+
const transferFiles = yield Promise.all((_a = manifest.transferMetadata) === null || _a === void 0 ? void 0 : _a.map((item) => __awaiter(this, void 0, void 0, function* () {
|
|
74
|
+
const entry = mecaZip.getEntry(item.href);
|
|
75
|
+
if (!entry)
|
|
76
|
+
return false;
|
|
77
|
+
const data = entry.getData().toString();
|
|
78
|
+
console.log(data);
|
|
79
|
+
try {
|
|
80
|
+
const transfer = new TransferXml(data);
|
|
81
|
+
const valid = yield transfer.validateXml();
|
|
82
|
+
if (!valid)
|
|
83
|
+
session.log.error(`${TRANSFER} DTD validation failed`);
|
|
84
|
+
return valid;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
session.log.error(`Could not read ${item.href} or DTD validation failed`);
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
})));
|
|
91
|
+
if (!transferFiles.reduce((a, b) => a && b, true)) {
|
|
92
|
+
return errorAndClean(session, `${TRANSFER} validation failed`, tempFolder);
|
|
93
|
+
}
|
|
94
|
+
debugCheck(session, `${MANIFEST} passes schema validation`);
|
|
95
|
+
const manifestItems = manifest.items;
|
|
96
|
+
const zipEntries = mecaZip.getEntries();
|
|
97
|
+
// Get all file and folder names in the zip file.
|
|
98
|
+
// Folders may not be explicitly listed in zipEntries, so we compute all folders from file paths.
|
|
99
|
+
const zipEntryNames = new Set();
|
|
100
|
+
zipEntries.forEach((entry) => {
|
|
101
|
+
const nameParts = entry.entryName.split('/');
|
|
102
|
+
for (let i = 1; i <= nameParts.length; i++) {
|
|
103
|
+
zipEntryNames.add(nameParts.slice(0, i).join('/'));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
const manifestExtras = manifestItems
|
|
107
|
+
.map((item) => item.href)
|
|
108
|
+
.filter((href) => !zipEntryNames.has(href.replace(/\/$/, ''))); // Ignore trailing slash
|
|
109
|
+
const zipExtras = zipEntries
|
|
110
|
+
.filter((entry) => entry.entryName !== MANIFEST)
|
|
111
|
+
.filter((entry) => !entry.isDirectory)
|
|
112
|
+
.filter((entry) => !manifestItems.map((item) => item.href).includes(entry.entryName))
|
|
113
|
+
.map((entry) => entry.entryName);
|
|
114
|
+
if (zipExtras.length) {
|
|
115
|
+
session.log.warn(`MECA bundle includes items missing from manifest:\n- ${zipExtras.join('\n- ')}`);
|
|
116
|
+
}
|
|
117
|
+
if (manifestExtras.length) {
|
|
118
|
+
return errorAndClean(session, `manifest items missing from MECA bundle:\n- ${manifestExtras.join('\n- ')}`, tempFolder);
|
|
119
|
+
}
|
|
120
|
+
debugCheck(session, 'manifest matches MECA bundle contents');
|
|
121
|
+
manifestItems.forEach((item) => {
|
|
122
|
+
if (!item.mediaType) {
|
|
123
|
+
session.log.warn(`manifest item missing media-type: ${item.href}`);
|
|
124
|
+
}
|
|
125
|
+
if (!item.itemType) {
|
|
126
|
+
session.log.warn(`manifest item missing item-type: ${item.href}`);
|
|
127
|
+
}
|
|
128
|
+
else if (!KNOWN_ITEM_TYPES.includes(item.itemType)) {
|
|
129
|
+
session.log.warn(`manifest item has unknown item-type "${item.itemType}": ${item.href} `);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
const jatsFiles = manifestItems
|
|
133
|
+
.filter((item) => item.itemType === 'article-metadata')
|
|
134
|
+
.map((item) => item.href);
|
|
135
|
+
const invalidJatsFiles = (yield Promise.all(jatsFiles.map((jatsFile) => __awaiter(this, void 0, void 0, function* () {
|
|
136
|
+
mecaZip.extractEntryTo(jatsFile, tempFolder);
|
|
137
|
+
const isValid = yield validateJatsAgainstDtd(session, path.join(tempFolder, ...jatsFile.split('/')), opts);
|
|
138
|
+
return isValid ? undefined : jatsFile;
|
|
139
|
+
})))).filter((jatsFile) => !!jatsFile);
|
|
140
|
+
if (invalidJatsFiles.length) {
|
|
141
|
+
return errorAndClean(session, `JATS DTD validation failed:\n- ${invalidJatsFiles.join('\n- ')}`, tempFolder);
|
|
142
|
+
}
|
|
143
|
+
debugCheck(session, 'JATS validation passed');
|
|
144
|
+
removeTempFolder(tempFolder);
|
|
145
|
+
return true;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Validate a given file as MECA bundle
|
|
150
|
+
*
|
|
151
|
+
* Logs confirmation message if valid and throws an error if invalid.
|
|
152
|
+
*/
|
|
153
|
+
export function validateMecaWrapper(session, file, opts) {
|
|
154
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
155
|
+
const success = yield validateMeca(session, file, opts);
|
|
156
|
+
if (success) {
|
|
157
|
+
session.log.info(chalk.greenBright('MECA validation passed!'));
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
throw new Error('MECA validation failed.');
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,OAAO,UAAU,CAAC;AACxB,eAAe,OAAO,CAAC"}
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const version = '1.0.
|
|
1
|
+
const version = '1.0.5';
|
|
2
2
|
export default version;
|