jats-cli 0.0.0
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 +89 -0
- package/dist/convert.d.ts +3 -0
- package/dist/convert.d.ts.map +1 -0
- package/dist/convert.js +13 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/jats-test.d.ts +9 -0
- package/dist/jats-test.d.ts.map +1 -0
- package/dist/jats-test.js +106 -0
- package/dist/jats.cjs +26164 -0
- package/dist/parse.d.ts +7 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +198 -0
- package/dist/validate.d.ts +3 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +24 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/package.json +70 -0
package/dist/parse.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { ISession } from 'myst-cli-utils';
|
|
3
|
+
import { type ResolutionOptions } from 'jats-fetch';
|
|
4
|
+
import { Jats } from 'jats-xml';
|
|
5
|
+
export declare function parseJats(session: ISession, file: string, opts?: ResolutionOptions): Promise<Jats>;
|
|
6
|
+
export declare function addDownloadCLI(program: Command): void;
|
|
7
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAS/C,OAAO,EAA0C,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC5F,OAAO,EAAE,IAAI,EAAiB,MAAM,UAAU,CAAC;AA+C/C,wBAAsB,SAAS,CAC7B,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,iBAAoD,GACzD,OAAO,CAAC,IAAI,CAAC,CAef;AA2JD,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,QAI9C"}
|
package/dist/parse.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { extname } from 'path';
|
|
4
|
+
import { clirun, getSession, isUrl, tic, writeFileToFolder } from 'myst-cli-utils';
|
|
5
|
+
import { doi } from 'doi-utils';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { formatPrinciples, highlightFAIR } from 'fair-principles';
|
|
8
|
+
import { formatDate, toDate } from 'jats-utils';
|
|
9
|
+
import { Tags } from 'jats-tags';
|
|
10
|
+
import { toText } from 'myst-common';
|
|
11
|
+
import { select, selectAll } from 'unist-util-select';
|
|
12
|
+
import { downloadJatsFromUrl, DEFAULT_RESOLVERS } from 'jats-fetch';
|
|
13
|
+
import { Jats, findArticleId } from 'jats-xml';
|
|
14
|
+
function hasValidExtension(output) {
|
|
15
|
+
return ['.xml', '.jats'].includes(extname(output).toLowerCase());
|
|
16
|
+
}
|
|
17
|
+
async function downloadAndSaveJats(session, urlOrDoi, output, opts = { resolvers: DEFAULT_RESOLVERS }) {
|
|
18
|
+
if (fs.existsSync(urlOrDoi)) {
|
|
19
|
+
throw new Error(`File "${urlOrDoi}" is local and cannot be downloaded!`);
|
|
20
|
+
}
|
|
21
|
+
if (!(doi.validate(urlOrDoi) || isUrl(urlOrDoi))) {
|
|
22
|
+
throw new Error(`Path must be a URL or DOI, not "${urlOrDoi}"`);
|
|
23
|
+
}
|
|
24
|
+
if (!hasValidExtension(output)) {
|
|
25
|
+
session.log.warn(`The extension ${extname(output)} is not a valid extension for JATS, try using ".xml" or ".jats"`);
|
|
26
|
+
}
|
|
27
|
+
const { success, data, source } = await downloadJatsFromUrl(session, urlOrDoi, opts);
|
|
28
|
+
if (!success || !data) {
|
|
29
|
+
logAboutJatsFailing(session, [source]);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
writeFileToFolder(output, data);
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
function logAboutJatsFailing(session, jatsUrls) {
|
|
36
|
+
session.log.warn('⛔️ JATS may not be Open Access 😭, you should petition your local representative 🪧');
|
|
37
|
+
session.log.info(`${chalk.green(`\nThe XML ${chalk.bold('should')} be here:\n\n${jatsUrls.join('\n')}`)}\n`);
|
|
38
|
+
const FAIR = highlightFAIR('A', { chalk });
|
|
39
|
+
session.log.info(`Some publishers aggressively block programmatic access, which isn't ${FAIR}.`);
|
|
40
|
+
session.log.debug(formatPrinciples('A*', { chalk }));
|
|
41
|
+
session.log.info(`\n${chalk.blue('The link may work in a browser.')}\n`);
|
|
42
|
+
}
|
|
43
|
+
export async function parseJats(session, file, opts = { resolvers: DEFAULT_RESOLVERS }) {
|
|
44
|
+
const toc = tic();
|
|
45
|
+
if (fs.existsSync(file)) {
|
|
46
|
+
session.log.debug(`Found ${file} locally, parsing`);
|
|
47
|
+
const data = fs.readFileSync(file).toString();
|
|
48
|
+
return new Jats(data, { log: session.log });
|
|
49
|
+
}
|
|
50
|
+
const { success, source, data } = await downloadJatsFromUrl(session, file, opts);
|
|
51
|
+
if (!success || !data) {
|
|
52
|
+
logAboutJatsFailing(session, [source]);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
const jats = new Jats(data, { source, log: session.log });
|
|
56
|
+
session.log.debug(toc(`Downloaded and parsed JATS file in %s`));
|
|
57
|
+
return jats;
|
|
58
|
+
}
|
|
59
|
+
function formatLongString(data, offset = 0, length = 88 - offset) {
|
|
60
|
+
const out = [data.slice(0, length)];
|
|
61
|
+
let left = data.slice(length);
|
|
62
|
+
while (left.length > length) {
|
|
63
|
+
out.push(left.slice(0, length).trim());
|
|
64
|
+
left = left.slice(length);
|
|
65
|
+
}
|
|
66
|
+
if (left)
|
|
67
|
+
out.push(left.trim());
|
|
68
|
+
return out.join(`\n${' '.repeat(offset)}`);
|
|
69
|
+
}
|
|
70
|
+
function formatDictionary(dict, opts) {
|
|
71
|
+
const maxLabel = Object.keys(dict).reduce((a, b) => Math.max(a, b.length), 0);
|
|
72
|
+
return Object.entries(dict)
|
|
73
|
+
.map(([k, t]) => {
|
|
74
|
+
var _a;
|
|
75
|
+
if (!t)
|
|
76
|
+
return null;
|
|
77
|
+
let wrap = typeof (opts === null || opts === void 0 ? void 0 : opts.wrap) === 'boolean' ? opts.wrap : true;
|
|
78
|
+
let value = t;
|
|
79
|
+
let color = chalk.yellow.bold;
|
|
80
|
+
if (t && typeof t === 'object') {
|
|
81
|
+
if (!t.value)
|
|
82
|
+
return null;
|
|
83
|
+
color = (_a = t.label) !== null && _a !== void 0 ? _a : color;
|
|
84
|
+
value = t.value;
|
|
85
|
+
wrap = typeof t.wrap === 'boolean' ? t.wrap : wrap;
|
|
86
|
+
}
|
|
87
|
+
const wrapped = wrap ? formatLongString(String(value), maxLabel + 2) : String(value);
|
|
88
|
+
return `${color(k)}:${' '.repeat(maxLabel - k.length + 1)}${wrapped}`;
|
|
89
|
+
})
|
|
90
|
+
.filter((o) => !!o)
|
|
91
|
+
.join('\n');
|
|
92
|
+
}
|
|
93
|
+
async function jatsSummaryCLI(session, file) {
|
|
94
|
+
var _a, _b, _c;
|
|
95
|
+
const jats = await parseJats(session, file);
|
|
96
|
+
const summary = {
|
|
97
|
+
Source: { value: jats.source, wrap: false },
|
|
98
|
+
DOI: jats.doi ? { value: doi.buildUrl(jats.doi), wrap: false } : null,
|
|
99
|
+
Title: (_a = toText(jats.articleTitle)) === null || _a === void 0 ? void 0 : _a.replace(/\n/g, ' '),
|
|
100
|
+
Date: formatDate(toDate(jats.publicationDate)),
|
|
101
|
+
Authors: jats.articleAuthors
|
|
102
|
+
.map((a) => `${toText(select(Tags.givenNames, a))} ${toText(select(Tags.surname, a))}`)
|
|
103
|
+
.join(', '),
|
|
104
|
+
Abstract: (_b = toText(jats.abstract)) === null || _b === void 0 ? void 0 : _b.replace(/\n/g, ' '),
|
|
105
|
+
Keywords: jats.keywords.map((k) => toText(k)).join(', '),
|
|
106
|
+
License: (_c = jats.license) === null || _c === void 0 ? void 0 : _c['xlink:href'],
|
|
107
|
+
};
|
|
108
|
+
if (jats.body) {
|
|
109
|
+
summary.Figures = {
|
|
110
|
+
label: chalk.blue.bold,
|
|
111
|
+
value: String(selectAll(Tags.fig, jats.body).length),
|
|
112
|
+
};
|
|
113
|
+
summary.Equations = {
|
|
114
|
+
label: chalk.blue.bold,
|
|
115
|
+
value: String(selectAll(Tags.dispFormula, jats.body).length),
|
|
116
|
+
};
|
|
117
|
+
summary.Tables = {
|
|
118
|
+
label: chalk.blue.bold,
|
|
119
|
+
value: String(selectAll(Tags.table, jats.body).length),
|
|
120
|
+
};
|
|
121
|
+
summary.Code = {
|
|
122
|
+
label: chalk.blue.bold,
|
|
123
|
+
value: String(selectAll(Tags.code, jats.body).length),
|
|
124
|
+
};
|
|
125
|
+
summary.Sections = {
|
|
126
|
+
label: chalk.blue.bold,
|
|
127
|
+
value: String(selectAll(Tags.sec, jats.body).length),
|
|
128
|
+
};
|
|
129
|
+
summary.Paragraphs = {
|
|
130
|
+
label: chalk.blue.bold,
|
|
131
|
+
value: String(selectAll(Tags.p, jats.body).length),
|
|
132
|
+
};
|
|
133
|
+
summary.Citations = { label: chalk.blue.bold, value: String(jats.references.length) };
|
|
134
|
+
summary['Cross-References'] = {
|
|
135
|
+
label: chalk.blue.bold,
|
|
136
|
+
value: String(selectAll(Tags.xref, jats.body).length),
|
|
137
|
+
};
|
|
138
|
+
summary['Sub Articles'] = { label: chalk.blue.bold, value: String(jats.subArticles.length) };
|
|
139
|
+
}
|
|
140
|
+
session.log.info(formatDictionary(summary));
|
|
141
|
+
if (!jats.body) {
|
|
142
|
+
session.log.warn('\nThis is a partial JATS record that does not have <body>.');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function jatsReferencesCLI(session, file) {
|
|
146
|
+
const jats = await parseJats(session, file);
|
|
147
|
+
const sorted = jats.references
|
|
148
|
+
.map((ref) => {
|
|
149
|
+
const doiString = findArticleId(ref, 'doi');
|
|
150
|
+
const title = toText(select(Tags.articleTitle, ref));
|
|
151
|
+
const year = toText(select(Tags.year, ref));
|
|
152
|
+
const surnames = selectAll(Tags.surname, ref);
|
|
153
|
+
const short = surnames.length > 2
|
|
154
|
+
? toText(surnames[0]) + ' et al.'
|
|
155
|
+
: surnames.length === 2
|
|
156
|
+
? toText(surnames[0]) + ' and ' + toText(surnames[1])
|
|
157
|
+
: toText(surnames[0]);
|
|
158
|
+
const s = selectAll(`[rid=${ref.id}]`, jats.body);
|
|
159
|
+
return {
|
|
160
|
+
Citation: `${short} (${year})`,
|
|
161
|
+
Title: title,
|
|
162
|
+
DOI: doiString ? doi.buildUrl(doiString) : null,
|
|
163
|
+
Count: s.length,
|
|
164
|
+
};
|
|
165
|
+
})
|
|
166
|
+
.sort((a, b) => b.Count - a.Count);
|
|
167
|
+
sorted.forEach((r) => {
|
|
168
|
+
session.log.info(formatDictionary(r, { wrap: false }) + '\n');
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function makeSummaryCLI(program) {
|
|
172
|
+
const command = new Command('summary')
|
|
173
|
+
.description('Parse a JATS file and provide a summary')
|
|
174
|
+
.argument('<jats>', 'The JATS file or remote URL to be parsed')
|
|
175
|
+
.action(clirun(jatsSummaryCLI, { program, getSession }));
|
|
176
|
+
return command;
|
|
177
|
+
}
|
|
178
|
+
function makeReferencesCLI(program) {
|
|
179
|
+
const command = new Command('refs')
|
|
180
|
+
.alias('references')
|
|
181
|
+
.description('Parse a JATS file and provide a summary')
|
|
182
|
+
.argument('<jats>', 'The JATS file or remote URL to be parsed')
|
|
183
|
+
.action(clirun(jatsReferencesCLI, { program, getSession }));
|
|
184
|
+
return command;
|
|
185
|
+
}
|
|
186
|
+
function makeDownloadCLI(program) {
|
|
187
|
+
const command = new Command('download')
|
|
188
|
+
.description('Parse a JATS file and provide a summary')
|
|
189
|
+
.argument('<url>', 'The JATS url or a DOI')
|
|
190
|
+
.argument('<output>', 'The JATS output file')
|
|
191
|
+
.action(clirun(downloadAndSaveJats, { program, getSession }));
|
|
192
|
+
return command;
|
|
193
|
+
}
|
|
194
|
+
export function addDownloadCLI(program) {
|
|
195
|
+
program.addCommand(makeDownloadCLI(program));
|
|
196
|
+
program.addCommand(makeSummaryCLI(program));
|
|
197
|
+
program.addCommand(makeReferencesCLI(program));
|
|
198
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AA6C5C,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,QAE9C"}
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Command, Option } from 'commander';
|
|
2
|
+
import { validateJatsAgainstDtdWrapper } from 'jats-xml';
|
|
3
|
+
import { clirun, getSession } from 'myst-cli-utils';
|
|
4
|
+
function makeValidateCLI(program) {
|
|
5
|
+
const command = new Command('validate')
|
|
6
|
+
.description(`
|
|
7
|
+
Validate JATS file against DTD schema.
|
|
8
|
+
|
|
9
|
+
The JATS DTD schema file is fetched from nih.gov ftp server if not available locally.
|
|
10
|
+
This will attempt to infer the specific JATS DTD version, library, etc from the file header,
|
|
11
|
+
but options are available to override the inferred values.
|
|
12
|
+
`)
|
|
13
|
+
.argument('<file>', 'JATS file to validate')
|
|
14
|
+
.addOption(new Option('--library <value>', 'JATS library - archiving, publishing, or authoring (default: archiving, if value cannot be inferred from file)'))
|
|
15
|
+
.addOption(new Option('--jats <version>', 'JATS version, must be 1.1 or later (default: 1.3, if value cannot be inferred from file)'))
|
|
16
|
+
.addOption(new Option('--mathml <version>', 'MathML version, 2 or 3 (default: 3, if value cannot be inferred from file)'))
|
|
17
|
+
.addOption(new Option('--oasis', 'Use OASIS table model (default: false, if value cannot be inferred from file)'))
|
|
18
|
+
.addOption(new Option('--directory <value>', 'Directory to save DTD file'))
|
|
19
|
+
.action(clirun(validateJatsAgainstDtdWrapper, { program, getSession }));
|
|
20
|
+
return command;
|
|
21
|
+
}
|
|
22
|
+
export function addValidateCLI(program) {
|
|
23
|
+
program.addCommand(makeValidateCLI(program));
|
|
24
|
+
}
|
|
@@ -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
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jats-cli",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Typescript CLI for with JATS",
|
|
5
|
+
"author": "Rowan Cockett <rowan@curvenote.com>",
|
|
6
|
+
"homepage": "https://github.com/curvenote/jats",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"type": "module",
|
|
10
|
+
"exports": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"jats",
|
|
17
|
+
"open-science",
|
|
18
|
+
"publishing"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/curvenote/jats.git"
|
|
26
|
+
},
|
|
27
|
+
"bin": {
|
|
28
|
+
"jats": "./dist/jats.cjs"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"copy:version": "echo \"const version = '\"$npm_package_version\"';\nexport default version;\" > src/version.ts",
|
|
32
|
+
"clean": "rm -rf dist",
|
|
33
|
+
"unlink": "npm uninstall -g jats-cli",
|
|
34
|
+
"link": "npm run unlink; npm link;",
|
|
35
|
+
"dev": "npm run copy:version && npm run link && esbuild src/index.ts --bundle --outfile=dist/jats.cjs --platform=node --watch",
|
|
36
|
+
"test": "npm run copy:version && vitest run",
|
|
37
|
+
"test:watch": "npm run copy:version && vitest watch",
|
|
38
|
+
"lint": "eslint \"src/**/*.ts*\" -c ./.eslintrc.cjs",
|
|
39
|
+
"lint:format": "prettier --check \"src/**/*.{ts,tsx,md}\"",
|
|
40
|
+
"build:esm": "tsc",
|
|
41
|
+
"build:cli": "esbuild src/index.ts --bundle --outfile=dist/jats.cjs --platform=node",
|
|
42
|
+
"build": "npm-run-all -l clean copy:version -p build:esm build:cli"
|
|
43
|
+
},
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/curvenote/jats/issues"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"doi-utils": "^2.0.0",
|
|
49
|
+
"fair-principles": "^2.0.0",
|
|
50
|
+
"jats-convert": "^0.0.0",
|
|
51
|
+
"jats-fetch": "^1.0.9",
|
|
52
|
+
"jats-tags": "^1.0.9",
|
|
53
|
+
"jats-utils": "^1.0.9",
|
|
54
|
+
"jats-xml": "^1.0.9",
|
|
55
|
+
"js-yaml": "^4.1.0",
|
|
56
|
+
"unist-util-is": "^5.2.1",
|
|
57
|
+
"unist-util-select": "^4.0.0"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"chalk": "^5.2.0",
|
|
61
|
+
"commander": "^10.0.1"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/js-yaml": "^4.0.5",
|
|
65
|
+
"chalk": "^5.2.0",
|
|
66
|
+
"commander": "^10.0.1",
|
|
67
|
+
"myst-cli-utils": "^2.0.0",
|
|
68
|
+
"myst-common": "^1.0.0"
|
|
69
|
+
}
|
|
70
|
+
}
|