meca 1.0.4 → 1.0.6

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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # meca
2
2
 
3
3
  [![meca on npm](https://img.shields.io/npm/v/meca.svg)](https://www.npmjs.com/package/meca)
4
- [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/curvenote/meca/blob/main/LICENSE)
5
- [![CI](https://github.com/curvenote/meca/workflows/CI/badge.svg)](https://github.com/curvenote/meca/actions)
4
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/curvenote/jats/blob/main/LICENSE)
5
+ [![CI](https://github.com/curvenote/jats/workflows/CI/badge.svg)](https://github.com/curvenote/jats/actions)
6
6
 
7
7
  Types and utilities for working with MECA bundles documents in Node and Typescript.
8
8
 
@@ -17,11 +17,130 @@ meca -v
17
17
 
18
18
  ## What is MECA?
19
19
 
20
- ## Packages
20
+ Manuscript Exchange Common Approach (MECA) is a [NISO standard](https://www.niso.org/standards-committees/meca) for transferring scientific manuscripts between vendors. It is a ZIP file with a `manifest.xml`, which contains a JATS file as the `article-metadata` and other source materials.
21
21
 
22
- See packages folder:
22
+ ## From the command line
23
23
 
24
- - meca
24
+ Commands available:
25
+
26
+ `validate`: validate the MECA zip file, including the JATS
27
+
28
+ ```bash
29
+ meca validate my-meca-file.zip
30
+ ```
31
+
32
+ ## From Typescript
33
+
34
+ The `manifest.xml` can be read and written as follows.
35
+
36
+ ```typescript
37
+ import fs from 'fs';
38
+ import { ManifestXml, createManifestXml } from 'meca';
39
+
40
+ const data = fs.readFileSync('manifest.xml').toString();
41
+ const manifest = new ManifestXml(data);
42
+ console.log(manifest.items);
43
+
44
+ // Write a manifest file
45
+ const roundTrip = createManifestXml(manifest.items);
46
+ fs.writeFileSync('manifest.xml', roundTrip);
47
+ ```
48
+
49
+ The `ManifestItem` has the following shape:
50
+
51
+ ```typescript
52
+ type ManifestItem = {
53
+ id?: string;
54
+ itemType?: string;
55
+ version?: string;
56
+ title?: string;
57
+ description?: string;
58
+ href: string;
59
+ mediaType?: string;
60
+ fileOrder?: string;
61
+ metadata?: Record<string, string>;
62
+ };
63
+ ```
64
+
65
+ which translates to the following XML, for example, from the NISO spec:
66
+
67
+ ```xml
68
+ <item id="b-456" item-type="figure" item-version="0">
69
+ <item-description>Figure</item-description>
70
+ <file-order>3</file-order>
71
+ <item-metadata>
72
+ <metadata metadata-name="Figure Number">1</metadata>
73
+ <metadata metadata-name="Caption"
74
+ >This is the caption for Figure 1</metadata>
75
+ </item-metadata>
76
+ <instance media-type="image/jpeg" xlink:href="wrist_scaphoidvx_diagnosis.jpg" />
77
+ </item>
78
+ ```
79
+
80
+ We assume that there is only one instance for each `item` and will warn if that is not the case.
81
+
82
+ ### transfer.xml
83
+
84
+ ```typescript
85
+ import fs from 'fs';
86
+ import { TransferXml, createTransferXml } from 'meca';
87
+
88
+ const data = fs.readFileSync('manifest.xml').toString();
89
+ const transfer = new TransferXml(data);
90
+ console.log(transfer.source);
91
+
92
+ // Write a transfer file
93
+ const roundTrip = createTransferXml({ source, destination, instructions });
94
+ fs.writeFileSync('transfer.xml', roundTrip);
95
+ ```
96
+
97
+ The `source` has the following structure:
98
+
99
+ ```typescript
100
+ const source = {
101
+ provider: {
102
+ name: 'Aries Systems',
103
+ contact: {
104
+ name: { given: 'Mary', surname: 'Smith' },
105
+ email: 'MarySmith@sample.email',
106
+ phone: '444-555-0101',
107
+ },
108
+ },
109
+ publication: {
110
+ type: 'journal',
111
+ title: 'The Journal of the American Medical Association',
112
+ acronym: 'JAMA',
113
+ contact: {
114
+ email: 'MyJournal@ariessys.com',
115
+ },
116
+ },
117
+ };
118
+ ```
119
+
120
+ Which creates the following XML:
121
+
122
+ ```xml
123
+ <transfer-source>
124
+ <service-provider>
125
+ <provider-name>Aries Systems</provider-name>
126
+ <contact>
127
+ <contact-name>
128
+ <surname>Smith</surname>
129
+ <given-names>Mary</given-names>
130
+ </contact-name>
131
+ <email>MarySmith@sample.email</email>
132
+ <phone>444-555-0101</phone>
133
+ </contact>
134
+ </service-provider>
135
+ <publication type="journal">
136
+ <publication-title>The Journal of the American Medical Association</publication-title>
137
+ <acronym>JAMA</acronym>
138
+ <contact>
139
+ <email>MyJournal@ariessys.com</email>
140
+ </contact>
141
+ </publication>
142
+ </transfer-source>
143
+ ```
25
144
 
26
145
  ---
27
146
 
@@ -1,2 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
  export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
package/dist/cli/index.js CHANGED
@@ -4,6 +4,6 @@ import version from '../version.js';
4
4
  import { addValidateCLI } from './validate.js';
5
5
  const program = new Command();
6
6
  addValidateCLI(program);
7
- program.version(`v${version}`, '-v, --version', 'Print the current version of jats-xml');
7
+ program.version(`v${version}`, '-v, --version', 'Print the current version of meca');
8
8
  program.option('-d, --debug', 'Log out any errors to the console.');
9
9
  program.parse(process.argv);
@@ -1,2 +1,3 @@
1
1
  import { Command } from 'commander';
2
2
  export declare function addValidateCLI(program: Command): void;
3
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/cli/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AA8C5C,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,QAE9C"}
@@ -1,7 +1,7 @@
1
1
  import { Command, Option } from 'commander';
2
2
  import { clirun } from 'myst-cli-utils';
3
3
  import { getSession } from 'jats-xml';
4
- import { validateMecaWrapper } from '../validate/index.js';
4
+ import { validateMecaWrapper } from '../validate.js';
5
5
  function makeValidateCLI(program) {
6
6
  const command = new Command('validate')
7
7
  .description(`
package/dist/index.d.ts CHANGED
@@ -1,2 +1,5 @@
1
1
  export { default as version } from './version.js';
2
- export * from './validate/index.js';
2
+ export * from './manifest.js';
3
+ export * from './validate.js';
4
+ export * from './transfer.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAClD,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,4 @@
1
1
  export { default as version } from './version.js';
2
- export * from './validate/index.js';
2
+ export * from './manifest.js';
3
+ export * from './validate.js';
4
+ export * from './transfer.js';
@@ -1,6 +1,4 @@
1
- <!-- DOCTYPE manifest PUBLIC “-//MECA//DTD Manifest v1.0//en"
2
- "https://www.manuscriptexchange.org/schema/manifest-1.0.dtd"
3
- -->
1
+ <!-- DOCTYPE manifest PUBLIC “-//MECA//DTD Manifest v1.0//en" "https://meca.zip/manifest-1.0.dtd" -->
4
2
  <!-- ============================================================= -->
5
3
  <!-- SPECIAL CHARACTER MODULES -->
6
4
  <!-- ============================================================= -->
@@ -40,4 +38,4 @@
40
38
  <!ATTLIST instance media-type CDATA #IMPLIED>
41
39
  <!ATTLIST instance origin CDATA #IMPLIED>
42
40
  <!ATTLIST instance conversion CDATA #IMPLIED>
43
- <!ATTLIST instance composition CDATA #IMPLIED>
41
+ <!ATTLIST instance composition CDATA #IMPLIED>
@@ -0,0 +1,55 @@
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 MANIFEST = "manifest.xml";
5
+ export declare const MANIFEST_DTD = "manifest-1.0.dtd";
6
+ type Options = {
7
+ log?: Logger;
8
+ source?: string;
9
+ };
10
+ export declare enum ItemTypes {
11
+ articleMetadata = "article-metadata",
12
+ articleSupportingFile = "article-supporting-file",
13
+ manuscript = "manuscript",
14
+ manuscriptSupportingFile = "manuscript-supporting-file",
15
+ articleSource = "article-source",
16
+ articleSourceEnvironment = "article-source-environment",
17
+ articleSourceDirectory = "article-source-directory",
18
+ transferMetadata = "transfer-metadata"
19
+ }
20
+ export type ManifestItem = {
21
+ id?: string;
22
+ itemType?: string;
23
+ version?: string;
24
+ title?: string;
25
+ description?: string;
26
+ href: string;
27
+ mediaType?: string;
28
+ fileOrder?: string;
29
+ metadata?: Record<string, string>;
30
+ };
31
+ export declare class ManifestXml {
32
+ declaration?: DeclarationAttributes;
33
+ doctype?: string;
34
+ rawXML: string;
35
+ raw: Element;
36
+ log: Logger;
37
+ tree: GenericParent;
38
+ source?: string;
39
+ constructor(data: string, opts?: Options);
40
+ get localDtd(): string;
41
+ validateXml(remoteDtd?: string): Promise<boolean | undefined>;
42
+ get version(): string;
43
+ get items(): ManifestItem[];
44
+ get itemTypes(): string[];
45
+ get articleMetadata(): ManifestItem | undefined;
46
+ get transferMetadata(): ManifestItem[];
47
+ }
48
+ type WriteOptions = {
49
+ /** Some publishers prefer `href` instead of `xlink:href`, which is in the spec */
50
+ noXLink?: boolean;
51
+ dtdUrl?: string;
52
+ };
53
+ export declare function createManifestXml(manifestItems: ManifestItem[], opts?: WriteOptions): string;
54
+ export {};
55
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,KAAK,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAE7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAI7C,eAAO,MAAM,QAAQ,iBAAiB,CAAC;AACvC,eAAO,MAAM,YAAY,qBAAqB,CAAC;AAE/C,KAAK,OAAO,GAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD,oBAAY,SAAS;IACnB,eAAe,qBAAqB;IACpC,qBAAqB,4BAA4B;IACjD,UAAU,eAAe;IACzB,wBAAwB,+BAA+B;IACvD,aAAa,mBAAmB;IAChC,wBAAwB,+BAA+B;IACvD,sBAAsB,6BAA6B;IACnD,gBAAgB,sBAAsB;CACvC;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC,CAAC;AAEF,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;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEJ,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;IAuBxC,IAAI,QAAQ,IAAI,MAAM,CAOrB;IAEK,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM;IAqBpC,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,KAAK,IAAI,YAAY,EAAE,CAmC1B;IAED,IAAI,SAAS,IAAI,MAAM,EAAE,CAMxB;IAED,IAAI,eAAe,IAAI,YAAY,GAAG,SAAS,CAI9C;IAED,IAAI,gBAAgB,IAAI,YAAY,EAAE,CAErC;CACF;AAED,KAAK,YAAY,GAAG;IAClB,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAuCF,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,YAAY,EAAE,EAAE,IAAI,CAAC,EAAE,YAAY,UA4BnF"}
@@ -0,0 +1,194 @@
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 { createTempFolder, elementWithText, removeTempFolder, select, selectAll } from './utils.js';
16
+ export const MANIFEST = 'manifest.xml';
17
+ export const MANIFEST_DTD = 'manifest-1.0.dtd';
18
+ export var ItemTypes;
19
+ (function (ItemTypes) {
20
+ ItemTypes["articleMetadata"] = "article-metadata";
21
+ ItemTypes["articleSupportingFile"] = "article-supporting-file";
22
+ ItemTypes["manuscript"] = "manuscript";
23
+ ItemTypes["manuscriptSupportingFile"] = "manuscript-supporting-file";
24
+ ItemTypes["articleSource"] = "article-source";
25
+ ItemTypes["articleSourceEnvironment"] = "article-source-environment";
26
+ ItemTypes["articleSourceDirectory"] = "article-source-directory";
27
+ ItemTypes["transferMetadata"] = "transfer-metadata";
28
+ })(ItemTypes || (ItemTypes = {}));
29
+ export class ManifestXml {
30
+ constructor(data, opts) {
31
+ var _a, _b;
32
+ const toc = tic();
33
+ this.log = (_a = opts === null || opts === void 0 ? void 0 : opts.log) !== null && _a !== void 0 ? _a : console;
34
+ this.rawXML = data;
35
+ if (opts === null || opts === void 0 ? void 0 : opts.source)
36
+ this.source = opts.source;
37
+ try {
38
+ this.raw = xml2js(data, { compact: false });
39
+ }
40
+ catch (error) {
41
+ throw new Error('Problem parsing the TransferXML document, please ensure it is XML');
42
+ }
43
+ const { declaration, elements } = this.raw;
44
+ this.declaration = declaration === null || declaration === void 0 ? void 0 : declaration.attributes;
45
+ if (!((elements === null || elements === void 0 ? void 0 : elements.length) === 2 && elements[0].type === 'doctype' && elements[1].name === 'manifest')) {
46
+ throw new Error('Element <manifest> is not the only element of the manifest.xml');
47
+ }
48
+ this.doctype = elements[0].doctype;
49
+ const converted = convertToUnist(elements[1]);
50
+ this.tree = select('manifest', converted);
51
+ (_b = this.log) === null || _b === void 0 ? void 0 : _b.debug(toc('Parsed and converted manifest.xml to unist tree in %s'));
52
+ }
53
+ get localDtd() {
54
+ // This works both compiled and in tests
55
+ const dtd = fs.existsSync(path.join(__dirname, MANIFEST_DTD))
56
+ ? path.join(__dirname, MANIFEST_DTD)
57
+ : path.join(__dirname, '..', 'static', MANIFEST_DTD);
58
+ if (fs.existsSync(dtd))
59
+ return dtd;
60
+ throw new Error(`Unable to locate manifest DTD file ${MANIFEST_DTD} in meca lib distribution`);
61
+ }
62
+ validateXml(remoteDtd) {
63
+ return __awaiter(this, void 0, void 0, function* () {
64
+ const tempFolder = createTempFolder();
65
+ fs.writeFileSync(path.join(tempFolder, MANIFEST), this.rawXML);
66
+ let dtdFile = this.localDtd;
67
+ if (remoteDtd) {
68
+ const data = yield (yield fetch(remoteDtd)).text();
69
+ dtdFile = path.join(tempFolder, MANIFEST_DTD);
70
+ fs.writeFileSync(dtdFile, data);
71
+ }
72
+ const manifestIsValid = yield xmllintValidate(this, path.join(tempFolder, MANIFEST), dtdFile).catch(() => {
73
+ this.log.error(`${MANIFEST} DTD validation failed`);
74
+ return false;
75
+ });
76
+ removeTempFolder(tempFolder);
77
+ return manifestIsValid;
78
+ });
79
+ }
80
+ get version() {
81
+ return this.tree['manifest-version'] || this.tree.version;
82
+ }
83
+ get items() {
84
+ const items = selectAll(`item`, this.tree)
85
+ .map((item) => {
86
+ var _a, _b, _c, _d, _e, _f, _g, _h;
87
+ const instances = selectAll(`instance`, item);
88
+ if (instances.length === 0) {
89
+ this.log.warn('Item without an instance');
90
+ return undefined;
91
+ }
92
+ if (instances.length > 1) {
93
+ this.log.warn('Item has multiple instances, only the first is used.');
94
+ }
95
+ const instance = instances[0];
96
+ 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;
97
+ 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;
98
+ 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;
99
+ const metadata = Object.fromEntries((_h = (_g = selectAll('metadata', item)) === null || _g === void 0 ? void 0 : _g.map((n) => {
100
+ var _a;
101
+ return [
102
+ n['metadata-name'] || n.name,
103
+ (_a = n === null || n === void 0 ? void 0 : n.children) === null || _a === void 0 ? void 0 : _a[0].value,
104
+ ];
105
+ })) !== null && _h !== void 0 ? _h : []);
106
+ return {
107
+ id: item.id,
108
+ itemType: item['item-type'],
109
+ version: item['item-version'] || item['version'],
110
+ title,
111
+ description,
112
+ href: instance['xlink:href'] || instance.href,
113
+ mediaType: instance['media-type'],
114
+ fileOrder,
115
+ metadata,
116
+ };
117
+ })
118
+ .filter((item) => !!item);
119
+ return items;
120
+ }
121
+ get itemTypes() {
122
+ const itemTypes = new Set();
123
+ this.items.forEach((item) => {
124
+ if (item.itemType)
125
+ itemTypes.add(item.itemType);
126
+ });
127
+ return [...itemTypes];
128
+ }
129
+ get articleMetadata() {
130
+ const items = this.items.filter((item) => item.itemType === ItemTypes.articleMetadata);
131
+ if (items.length > 1)
132
+ this.log.warn('More than 1 article metadata found');
133
+ return items[0];
134
+ }
135
+ get transferMetadata() {
136
+ return this.items.filter((item) => item.itemType === ItemTypes.transferMetadata);
137
+ }
138
+ }
139
+ function writeManifestItem(item, opts) {
140
+ const { id, version, href, itemType, mediaType, title, description, fileOrder, metadata } = item;
141
+ return {
142
+ type: 'element',
143
+ name: 'item',
144
+ attributes: {
145
+ id,
146
+ 'item-type': itemType,
147
+ 'item-version': version,
148
+ },
149
+ elements: [
150
+ title ? elementWithText('item-title', title) : undefined,
151
+ description ? elementWithText('item-description', description) : undefined,
152
+ fileOrder ? elementWithText('file-order', fileOrder) : undefined,
153
+ metadata && Object.keys(metadata !== null && metadata !== void 0 ? metadata : {}).length > 0
154
+ ? {
155
+ type: 'element',
156
+ name: 'item-metadata',
157
+ elements: Object.entries(metadata).map(([k, v]) => elementWithText('metadata', v, { 'metadata-name': k })),
158
+ }
159
+ : undefined,
160
+ {
161
+ type: 'element',
162
+ name: 'instance',
163
+ attributes: {
164
+ [(opts === null || opts === void 0 ? void 0 : opts.noXLink) ? 'href' : 'xlink:href']: href,
165
+ 'media-type': mediaType,
166
+ },
167
+ },
168
+ ].filter((e) => !!e),
169
+ };
170
+ }
171
+ export function createManifestXml(manifestItems, opts) {
172
+ var _a;
173
+ const element = {
174
+ type: 'element',
175
+ elements: [
176
+ {
177
+ type: 'doctype',
178
+ 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'}"`,
179
+ },
180
+ {
181
+ type: 'element',
182
+ name: 'manifest',
183
+ 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' })),
184
+ elements: manifestItems.map((item) => writeManifestItem(item, opts)),
185
+ },
186
+ ],
187
+ declaration: { attributes: { version: '1.0', encoding: 'UTF-8' } },
188
+ };
189
+ const manifest = js2xml(element, {
190
+ compact: false,
191
+ spaces: 2,
192
+ });
193
+ return manifest;
194
+ }