easy-template-x 7.2.2 → 7.2.4
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/dist/cjs/easy-template-x.cjs +33 -21
- package/dist/es/easy-template-x.mjs +33 -21
- package/dist/types/compilation/tag.d.ts +1 -2
- package/dist/types/types.d.ts +0 -1
- package/dist/types/utils/array.d.ts +1 -2
- package/dist/types/xml/xml.d.ts +6 -3
- package/dist/types/xml/xmlNode.d.ts +1 -2
- package/package.json +3 -2
- package/src/compilation/tag.ts +1 -2
- package/src/compilation/templateCompiler.ts +1 -2
- package/src/office/mediaFiles.ts +1 -2
- package/src/office/openXmlPart.ts +2 -2
- package/src/office/relsFile.ts +2 -3
- package/src/office/xlsx.ts +2 -2
- package/src/plugins/chart/updateChart.ts +5 -6
- package/src/types.ts +0 -2
- package/src/utils/array.ts +2 -3
- package/src/xml/xml.tests.ts +6 -0
- package/src/xml/xml.ts +44 -23
- package/src/xml/xmlNode.ts +1 -2
|
@@ -543,7 +543,13 @@ class Parser {
|
|
|
543
543
|
return '';
|
|
544
544
|
});
|
|
545
545
|
}
|
|
546
|
-
serializeNode(node) {
|
|
546
|
+
serializeNode(node, options) {
|
|
547
|
+
return this._serializeNode(node, 0, options);
|
|
548
|
+
}
|
|
549
|
+
serializeFile(xmlNode) {
|
|
550
|
+
return Parser.xmlFileHeader + xml.parser.serializeNode(xmlNode);
|
|
551
|
+
}
|
|
552
|
+
_serializeNode(node, depth, options) {
|
|
547
553
|
if (!node) return '';
|
|
548
554
|
if (xml.query.isTextNode(node)) return xml.parser.encodeValue(node.textContent || '');
|
|
549
555
|
if (xml.query.isCommentNode(node)) {
|
|
@@ -563,21 +569,27 @@ class Parser {
|
|
|
563
569
|
const hasChildren = (node.childNodes || []).length > 0;
|
|
564
570
|
const suffix = hasChildren ? '' : '/';
|
|
565
571
|
const openTag = `<${node.nodeName}${attributes}${suffix}>`;
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
// close tag
|
|
572
|
-
const closeTag = `</${node.nodeName}>`;
|
|
573
|
-
xmlString = openTag + childrenXml + closeTag;
|
|
574
|
-
} else {
|
|
575
|
-
xmlString = openTag;
|
|
572
|
+
|
|
573
|
+
// No children
|
|
574
|
+
if (!hasChildren) {
|
|
575
|
+
return openTag;
|
|
576
576
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
577
|
+
|
|
578
|
+
// Close tag
|
|
579
|
+
const closeTag = `</${node.nodeName}>`;
|
|
580
|
+
|
|
581
|
+
// Children without indentation
|
|
582
|
+
const indentSize = options?.indent ?? 0;
|
|
583
|
+
if (!indentSize || node.childNodes.every(xml.query.isTextNode)) {
|
|
584
|
+
const childrenXml = node.childNodes.map(child => this._serializeNode(child, depth + 1, options)).join('');
|
|
585
|
+
return openTag + childrenXml + closeTag;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Children with indentation
|
|
589
|
+
const childIndent = "\n" + " ".repeat((depth + 1) * indentSize);
|
|
590
|
+
const childrenXml = node.childNodes.map(child => `${childIndent}${this._serializeNode(child, depth + 1, options)}`).join("");
|
|
591
|
+
const closeIndent = "\n" + " ".repeat(depth * indentSize);
|
|
592
|
+
return openTag + childrenXml + closeIndent + closeTag;
|
|
581
593
|
}
|
|
582
594
|
}
|
|
583
595
|
class Create {
|
|
@@ -908,19 +920,19 @@ let Modify$1 = class Modify {
|
|
|
908
920
|
removeChild(parent, childOrIndex) {
|
|
909
921
|
if (!parent) throw new InternalArgumentMissingError("parent");
|
|
910
922
|
if (childOrIndex === null || childOrIndex === undefined) throw new InternalArgumentMissingError("childOrIndex");
|
|
911
|
-
if (!parent.childNodes || !parent.childNodes.length) throw new Error('Parent node has
|
|
923
|
+
if (!parent.childNodes || !parent.childNodes.length) throw new Error('Parent node has no children');
|
|
912
924
|
|
|
913
|
-
//
|
|
925
|
+
// Get child index
|
|
914
926
|
let childIndex;
|
|
915
927
|
if (typeof childOrIndex === 'number') {
|
|
916
928
|
childIndex = childOrIndex;
|
|
917
929
|
} else {
|
|
918
930
|
childIndex = parent.childNodes.indexOf(childOrIndex);
|
|
919
|
-
if (childIndex === -1) throw new Error('
|
|
931
|
+
if (childIndex === -1) throw new Error('Selected child node is not a child of the specified parent');
|
|
920
932
|
}
|
|
921
933
|
if (childIndex >= parent.childNodes.length) throw new RangeError(`Child index ${childIndex} is out of range. Parent has only ${parent.childNodes.length} child nodes.`);
|
|
922
934
|
|
|
923
|
-
//
|
|
935
|
+
// Update references
|
|
924
936
|
const child = parent.childNodes[childIndex];
|
|
925
937
|
if (childIndex > 0) {
|
|
926
938
|
const beforeChild = parent.childNodes[childIndex - 1];
|
|
@@ -929,7 +941,7 @@ let Modify$1 = class Modify {
|
|
|
929
941
|
child.parentNode = null;
|
|
930
942
|
child.nextSibling = null;
|
|
931
943
|
|
|
932
|
-
//
|
|
944
|
+
// Remove and return
|
|
933
945
|
return parent.childNodes.splice(childIndex, 1)[0];
|
|
934
946
|
}
|
|
935
947
|
removeChildren(parent, predicate) {
|
|
@@ -5312,7 +5324,7 @@ class TemplateHandler {
|
|
|
5312
5324
|
/**
|
|
5313
5325
|
* Version number of the `easy-template-x` library.
|
|
5314
5326
|
*/
|
|
5315
|
-
version = "7.2.
|
|
5327
|
+
version = "7.2.4" ;
|
|
5316
5328
|
constructor(options) {
|
|
5317
5329
|
this.options = new TemplateHandlerOptions(options);
|
|
5318
5330
|
const delimiters = this.options.delimiters;
|
|
@@ -541,7 +541,13 @@ class Parser {
|
|
|
541
541
|
return '';
|
|
542
542
|
});
|
|
543
543
|
}
|
|
544
|
-
serializeNode(node) {
|
|
544
|
+
serializeNode(node, options) {
|
|
545
|
+
return this._serializeNode(node, 0, options);
|
|
546
|
+
}
|
|
547
|
+
serializeFile(xmlNode) {
|
|
548
|
+
return Parser.xmlFileHeader + xml.parser.serializeNode(xmlNode);
|
|
549
|
+
}
|
|
550
|
+
_serializeNode(node, depth, options) {
|
|
545
551
|
if (!node) return '';
|
|
546
552
|
if (xml.query.isTextNode(node)) return xml.parser.encodeValue(node.textContent || '');
|
|
547
553
|
if (xml.query.isCommentNode(node)) {
|
|
@@ -561,21 +567,27 @@ class Parser {
|
|
|
561
567
|
const hasChildren = (node.childNodes || []).length > 0;
|
|
562
568
|
const suffix = hasChildren ? '' : '/';
|
|
563
569
|
const openTag = `<${node.nodeName}${attributes}${suffix}>`;
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
// close tag
|
|
570
|
-
const closeTag = `</${node.nodeName}>`;
|
|
571
|
-
xmlString = openTag + childrenXml + closeTag;
|
|
572
|
-
} else {
|
|
573
|
-
xmlString = openTag;
|
|
570
|
+
|
|
571
|
+
// No children
|
|
572
|
+
if (!hasChildren) {
|
|
573
|
+
return openTag;
|
|
574
574
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
575
|
+
|
|
576
|
+
// Close tag
|
|
577
|
+
const closeTag = `</${node.nodeName}>`;
|
|
578
|
+
|
|
579
|
+
// Children without indentation
|
|
580
|
+
const indentSize = options?.indent ?? 0;
|
|
581
|
+
if (!indentSize || node.childNodes.every(xml.query.isTextNode)) {
|
|
582
|
+
const childrenXml = node.childNodes.map(child => this._serializeNode(child, depth + 1, options)).join('');
|
|
583
|
+
return openTag + childrenXml + closeTag;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Children with indentation
|
|
587
|
+
const childIndent = "\n" + " ".repeat((depth + 1) * indentSize);
|
|
588
|
+
const childrenXml = node.childNodes.map(child => `${childIndent}${this._serializeNode(child, depth + 1, options)}`).join("");
|
|
589
|
+
const closeIndent = "\n" + " ".repeat(depth * indentSize);
|
|
590
|
+
return openTag + childrenXml + closeIndent + closeTag;
|
|
579
591
|
}
|
|
580
592
|
}
|
|
581
593
|
class Create {
|
|
@@ -906,19 +918,19 @@ let Modify$1 = class Modify {
|
|
|
906
918
|
removeChild(parent, childOrIndex) {
|
|
907
919
|
if (!parent) throw new InternalArgumentMissingError("parent");
|
|
908
920
|
if (childOrIndex === null || childOrIndex === undefined) throw new InternalArgumentMissingError("childOrIndex");
|
|
909
|
-
if (!parent.childNodes || !parent.childNodes.length) throw new Error('Parent node has
|
|
921
|
+
if (!parent.childNodes || !parent.childNodes.length) throw new Error('Parent node has no children');
|
|
910
922
|
|
|
911
|
-
//
|
|
923
|
+
// Get child index
|
|
912
924
|
let childIndex;
|
|
913
925
|
if (typeof childOrIndex === 'number') {
|
|
914
926
|
childIndex = childOrIndex;
|
|
915
927
|
} else {
|
|
916
928
|
childIndex = parent.childNodes.indexOf(childOrIndex);
|
|
917
|
-
if (childIndex === -1) throw new Error('
|
|
929
|
+
if (childIndex === -1) throw new Error('Selected child node is not a child of the specified parent');
|
|
918
930
|
}
|
|
919
931
|
if (childIndex >= parent.childNodes.length) throw new RangeError(`Child index ${childIndex} is out of range. Parent has only ${parent.childNodes.length} child nodes.`);
|
|
920
932
|
|
|
921
|
-
//
|
|
933
|
+
// Update references
|
|
922
934
|
const child = parent.childNodes[childIndex];
|
|
923
935
|
if (childIndex > 0) {
|
|
924
936
|
const beforeChild = parent.childNodes[childIndex - 1];
|
|
@@ -927,7 +939,7 @@ let Modify$1 = class Modify {
|
|
|
927
939
|
child.parentNode = null;
|
|
928
940
|
child.nextSibling = null;
|
|
929
941
|
|
|
930
|
-
//
|
|
942
|
+
// Remove and return
|
|
931
943
|
return parent.childNodes.splice(childIndex, 1)[0];
|
|
932
944
|
}
|
|
933
945
|
removeChildren(parent, predicate) {
|
|
@@ -5310,7 +5322,7 @@ class TemplateHandler {
|
|
|
5310
5322
|
/**
|
|
5311
5323
|
* Version number of the `easy-template-x` library.
|
|
5312
5324
|
*/
|
|
5313
|
-
version = "7.2.
|
|
5325
|
+
version = "7.2.4" ;
|
|
5314
5326
|
constructor(options) {
|
|
5315
5327
|
this.options = new TemplateHandlerOptions(options);
|
|
5316
5328
|
const delimiters = this.options.delimiters;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { IMap } from "src/types";
|
|
2
1
|
import type { XmlGeneralNode, XmlTextNode } from "src/xml";
|
|
3
2
|
export declare const TagDisposition: Readonly<{
|
|
4
3
|
readonly Open: "Open";
|
|
@@ -14,7 +13,7 @@ export type TagPlacement = typeof TagPlacement[keyof typeof TagPlacement];
|
|
|
14
13
|
export type Tag = TextNodeTag | AttributeTag;
|
|
15
14
|
export interface BaseTag {
|
|
16
15
|
name: string;
|
|
17
|
-
options?:
|
|
16
|
+
options?: Record<string, any>;
|
|
18
17
|
rawText: string;
|
|
19
18
|
disposition: TagDisposition;
|
|
20
19
|
}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { IMap } from '../types';
|
|
2
1
|
export type ItemMapper<TIn, TOut = string> = (item: TIn, index: number) => TOut;
|
|
3
2
|
export declare function pushMany<T>(destArray: T[], items: T[]): void;
|
|
4
3
|
export declare function first<T>(array: T[]): T;
|
|
5
4
|
export declare function last<T>(array: T[]): T;
|
|
6
|
-
export declare function toDictionary<TIn, TOut = TIn>(array: TIn[], keySelector: ItemMapper<TIn>, valueSelector?: ItemMapper<TIn, TOut>):
|
|
5
|
+
export declare function toDictionary<TIn, TOut = TIn>(array: TIn[], keySelector: ItemMapper<TIn>, valueSelector?: ItemMapper<TIn, TOut>): Record<string, TOut>;
|
package/dist/types/xml/xml.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { XmlGeneralNode, XmlNode, XmlNodeType } from "./xmlNode";
|
|
2
2
|
import { XmlCommentNode } from "./xmlNode";
|
|
3
3
|
import { XmlTextNode } from "./xmlNode";
|
|
4
|
-
import type { IMap } from "src/types";
|
|
5
4
|
export type NodeTypeToNode<T extends XmlNodeType> = T extends typeof XmlNodeType.Text ? XmlTextNode : T extends typeof XmlNodeType.Comment ? XmlCommentNode : T extends typeof XmlNodeType.General ? XmlGeneralNode : XmlNode;
|
|
6
5
|
export type XmlNodePredicate = (node: XmlNode) => boolean;
|
|
7
6
|
export declare class XmlUtils {
|
|
@@ -10,17 +9,21 @@ export declare class XmlUtils {
|
|
|
10
9
|
readonly query: Query;
|
|
11
10
|
readonly modify: Modify;
|
|
12
11
|
}
|
|
12
|
+
export interface XmlSerializationOptions {
|
|
13
|
+
indent?: number;
|
|
14
|
+
}
|
|
13
15
|
declare class Parser {
|
|
14
16
|
private static xmlFileHeader;
|
|
15
17
|
private static readonly parser;
|
|
16
18
|
parse(str: string): XmlNode;
|
|
17
19
|
domParse(str: string): Document;
|
|
18
20
|
encodeValue(str: string): string;
|
|
19
|
-
serializeNode(node: XmlNode): string;
|
|
21
|
+
serializeNode(node: XmlNode, options?: XmlSerializationOptions): string;
|
|
20
22
|
serializeFile(xmlNode: XmlNode): string;
|
|
23
|
+
private _serializeNode;
|
|
21
24
|
}
|
|
22
25
|
interface XmlGeneralNodeInit {
|
|
23
|
-
attributes?:
|
|
26
|
+
attributes?: Record<string, string>;
|
|
24
27
|
childNodes?: XmlNode[];
|
|
25
28
|
}
|
|
26
29
|
declare class Create {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { IMap } from "src/types";
|
|
2
1
|
export declare const XmlNodeType: Readonly<{
|
|
3
2
|
readonly Text: "Text";
|
|
4
3
|
readonly General: "General";
|
|
@@ -27,5 +26,5 @@ export interface XmlCommentNode extends XmlNodeBase {
|
|
|
27
26
|
}
|
|
28
27
|
export interface XmlGeneralNode extends XmlNodeBase {
|
|
29
28
|
nodeType: typeof XmlNodeType.General;
|
|
30
|
-
attributes?:
|
|
29
|
+
attributes?: Record<string, string>;
|
|
31
30
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "easy-template-x",
|
|
3
|
-
"version": "7.2.
|
|
3
|
+
"version": "7.2.4",
|
|
4
4
|
"description": "Generate docx documents from templates, in Node or in the browser.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docx",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"build-src": "rollup -c",
|
|
50
50
|
"build-types": "tsc -p tsconfig.types.json --emitDeclarationOnly",
|
|
51
51
|
"build": "yarn build-types && yarn build-src",
|
|
52
|
+
"push-both": "git push && git checkout master && git merge develop && git push && git checkout develop",
|
|
52
53
|
"release": "yarn clean && yarn quality && yarn build && yarn dist-verify",
|
|
53
54
|
"dist-verify": "yarn dist-verify-cjs && yarn dist-verify-es",
|
|
54
55
|
"dist-verify-cjs": "cd dist-verify/cjs && npm install && node main.js",
|
|
@@ -59,7 +60,7 @@
|
|
|
59
60
|
},
|
|
60
61
|
"packageManager": "yarn@4.3.1",
|
|
61
62
|
"dependencies": {
|
|
62
|
-
"@xmldom/xmldom": "0.8.
|
|
63
|
+
"@xmldom/xmldom": "0.8.13",
|
|
63
64
|
"json5": "2.2.3",
|
|
64
65
|
"jszip": "3.10.1",
|
|
65
66
|
"lodash.get": "4.4.2"
|
package/src/compilation/tag.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { IMap } from "src/types";
|
|
2
1
|
import type { XmlGeneralNode, XmlTextNode } from "src/xml";
|
|
3
2
|
|
|
4
3
|
export const TagDisposition = Object.freeze({
|
|
@@ -20,7 +19,7 @@ export type Tag = TextNodeTag | AttributeTag;
|
|
|
20
19
|
|
|
21
20
|
export interface BaseTag {
|
|
22
21
|
name: string;
|
|
23
|
-
options?:
|
|
22
|
+
options?: Record<string, any>;
|
|
24
23
|
/**
|
|
25
24
|
* The full tag text, for instance: "{#my-tag}".
|
|
26
25
|
*/
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { UnclosedTagError, UnknownContentTypeError, UnopenedTagError } from '../errors';
|
|
2
2
|
import { PluginContent, TemplatePlugin } from '../plugins';
|
|
3
|
-
import { IMap } from '../types';
|
|
4
3
|
import { isPromiseLike, stringValue, toDictionary } from '../utils';
|
|
5
4
|
import { XmlNode } from '../xml';
|
|
6
5
|
import { DelimiterSearcher } from './delimiters';
|
|
@@ -27,7 +26,7 @@ export interface TemplateCompilerOptions {
|
|
|
27
26
|
*/
|
|
28
27
|
export class TemplateCompiler {
|
|
29
28
|
|
|
30
|
-
private readonly pluginsLookup:
|
|
29
|
+
private readonly pluginsLookup: Record<string, TemplatePlugin>;
|
|
31
30
|
private readonly delimiterSearcher: DelimiterSearcher;
|
|
32
31
|
private readonly tagParser: TagParser;
|
|
33
32
|
private readonly options: TemplateCompilerOptions;
|
package/src/office/mediaFiles.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { MimeType, MimeTypeHelper } from '../mimeType';
|
|
2
|
-
import { IMap } from '../types';
|
|
3
2
|
import { Binary, Path, sha1 } from '../utils';
|
|
4
3
|
import { Zip } from '../zip';
|
|
5
4
|
|
|
@@ -10,7 +9,7 @@ export class MediaFiles {
|
|
|
10
9
|
|
|
11
10
|
private static readonly mediaDir = 'word/media';
|
|
12
11
|
|
|
13
|
-
private hashes:
|
|
12
|
+
private hashes: Record<string, string>;
|
|
14
13
|
private readonly files = new Map<Binary, string>();
|
|
15
14
|
private nextFileId = 0;
|
|
16
15
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Constructor
|
|
1
|
+
import { Constructor } from "src/types";
|
|
2
2
|
import { Binary } from "src/utils";
|
|
3
3
|
import { xml, XmlNode } from "src/xml";
|
|
4
4
|
import { Zip } from "src/zip";
|
|
@@ -18,7 +18,7 @@ export class OpenXmlPart {
|
|
|
18
18
|
public readonly path: string;
|
|
19
19
|
|
|
20
20
|
private root: XmlNode;
|
|
21
|
-
private readonly openedParts:
|
|
21
|
+
private readonly openedParts: Record<string, OpenXmlPart> = {};
|
|
22
22
|
private readonly zip: Zip;
|
|
23
23
|
|
|
24
24
|
constructor(path: string, zip: Zip) {
|
package/src/office/relsFile.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { IMap } from "src/types";
|
|
2
1
|
import { Path } from "src/utils";
|
|
3
2
|
import { xml, XmlGeneralNode, XmlNode } from "src/xml";
|
|
4
3
|
import { Zip } from "src/zip";
|
|
@@ -11,8 +10,8 @@ import { Relationship, RelTargetMode } from "./relationship";
|
|
|
11
10
|
*/
|
|
12
11
|
export class RelsFile {
|
|
13
12
|
|
|
14
|
-
private rels:
|
|
15
|
-
private relTargets:
|
|
13
|
+
private rels: Record<string, Relationship>;
|
|
14
|
+
private relTargets: Record<string, string>;
|
|
16
15
|
private nextRelId = 0;
|
|
17
16
|
|
|
18
17
|
private readonly partDir: string;
|
package/src/office/xlsx.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MalformedFileError } from "src/errors";
|
|
2
|
-
import { Constructor
|
|
2
|
+
import { Constructor } from "src/types";
|
|
3
3
|
import { Binary } from "src/utils";
|
|
4
4
|
import { Zip } from "src/zip";
|
|
5
5
|
import { OpenXmlPart } from "./openXmlPart";
|
|
@@ -53,7 +53,7 @@ export class Xlsx {
|
|
|
53
53
|
|
|
54
54
|
public readonly mainDocument: OpenXmlPart;
|
|
55
55
|
|
|
56
|
-
private readonly _parts:
|
|
56
|
+
private readonly _parts: Record<string, OpenXmlPart> = {};
|
|
57
57
|
|
|
58
58
|
private readonly zip: Zip;
|
|
59
59
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { MalformedFileError, TemplateSyntaxError } from "src/errors";
|
|
2
2
|
import { OmlAttribute, OpenXmlPart, RelType, Xlsx } from "src/office";
|
|
3
|
-
import { IMap } from "src/types";
|
|
4
3
|
import { xml, XmlGeneralNode, XmlNode } from "src/xml";
|
|
5
4
|
import { ChartColors } from "./chartColors";
|
|
6
5
|
import {
|
|
@@ -580,7 +579,7 @@ async function updateEmbeddedExcelFile(existingChart: ExistingChart, chartData:
|
|
|
580
579
|
await xlsxPart.save(newXlsxBinary);
|
|
581
580
|
}
|
|
582
581
|
|
|
583
|
-
async function updateSharedStringsPart(workbookPart: OpenXmlPart, chartData: ChartData): Promise<
|
|
582
|
+
async function updateSharedStringsPart(workbookPart: OpenXmlPart, chartData: ChartData): Promise<Record<string, number>> {
|
|
584
583
|
|
|
585
584
|
// Get the shared strings part
|
|
586
585
|
const sharedStringsPart = await workbookPart.getFirstPartByType(RelType.SharedStrings);
|
|
@@ -595,7 +594,7 @@ async function updateSharedStringsPart(workbookPart: OpenXmlPart, chartData: Cha
|
|
|
595
594
|
root.childNodes = [];
|
|
596
595
|
|
|
597
596
|
let count = 0;
|
|
598
|
-
const sharedStrings:
|
|
597
|
+
const sharedStrings: Record<string, number> = {};
|
|
599
598
|
|
|
600
599
|
function addString(str: string) {
|
|
601
600
|
xml.modify.appendChild(root, xml.create.generalNode("si", {
|
|
@@ -643,7 +642,7 @@ async function updateSharedStringsPart(workbookPart: OpenXmlPart, chartData: Cha
|
|
|
643
642
|
return sharedStrings;
|
|
644
643
|
}
|
|
645
644
|
|
|
646
|
-
async function updateSheetPart(workbookPart: OpenXmlPart, sheetName: string, sharedStrings:
|
|
645
|
+
async function updateSheetPart(workbookPart: OpenXmlPart, sheetName: string, sharedStrings: Record<string, number>, chartData: ChartData): Promise<OpenXmlPart> {
|
|
647
646
|
|
|
648
647
|
// Get the sheet rel ID
|
|
649
648
|
const root = await workbookPart.xmlRoot();
|
|
@@ -679,7 +678,7 @@ async function updateSheetPart(workbookPart: OpenXmlPart, sheetName: string, sha
|
|
|
679
678
|
return sheetPart;
|
|
680
679
|
}
|
|
681
680
|
|
|
682
|
-
async function updateSheetRootStandard(workbookPart: OpenXmlPart, sheetRoot: XmlNode, chartData: StandardChartData, sharedStrings:
|
|
681
|
+
async function updateSheetRootStandard(workbookPart: OpenXmlPart, sheetRoot: XmlNode, chartData: StandardChartData, sharedStrings: Record<string, number>): Promise<XmlNode[]> {
|
|
683
682
|
|
|
684
683
|
// Create first row
|
|
685
684
|
const firstRow = `
|
|
@@ -728,7 +727,7 @@ async function updateSheetRootStandard(workbookPart: OpenXmlPart, sheetRoot: Xml
|
|
|
728
727
|
];
|
|
729
728
|
}
|
|
730
729
|
|
|
731
|
-
async function updateSheetRootScatter(workbookPart: OpenXmlPart, sheetRoot: XmlNode, chartData: ScatterChartData, sharedStrings:
|
|
730
|
+
async function updateSheetRootScatter(workbookPart: OpenXmlPart, sheetRoot: XmlNode, chartData: ScatterChartData, sharedStrings: Record<string, number>): Promise<XmlNode[]> {
|
|
732
731
|
|
|
733
732
|
const isBubbleChart = isBubbleChartData(chartData);
|
|
734
733
|
|
package/src/types.ts
CHANGED
package/src/utils/array.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { IMap } from '../types';
|
|
2
1
|
|
|
3
2
|
export type ItemMapper<TIn, TOut = string> = (item: TIn, index: number) => TOut;
|
|
4
3
|
|
|
@@ -18,11 +17,11 @@ export function last<T>(array: T[]): T {
|
|
|
18
17
|
return array[array.length - 1];
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
export function toDictionary<TIn, TOut = TIn>(array: TIn[], keySelector: ItemMapper<TIn>, valueSelector?: ItemMapper<TIn, TOut>):
|
|
20
|
+
export function toDictionary<TIn, TOut = TIn>(array: TIn[], keySelector: ItemMapper<TIn>, valueSelector?: ItemMapper<TIn, TOut>): Record<string, TOut> {
|
|
22
21
|
if (!array.length)
|
|
23
22
|
return {};
|
|
24
23
|
|
|
25
|
-
const res:
|
|
24
|
+
const res: Record<string, any> = {};
|
|
26
25
|
array.forEach((item, index) => {
|
|
27
26
|
const key = keySelector(item, index);
|
|
28
27
|
const value = (valueSelector ? valueSelector(item, index) : item);
|
package/src/xml/xml.tests.ts
CHANGED
|
@@ -39,6 +39,12 @@ describe(XmlUtils, () => {
|
|
|
39
39
|
const str = xml.parser.serializeNode(node);
|
|
40
40
|
expect(str).toEqual('<!-- comment -->');
|
|
41
41
|
});
|
|
42
|
+
|
|
43
|
+
it('serializes a node with indentation', () => {
|
|
44
|
+
const node = parseXml('<node><child>hello</child></node>', true);
|
|
45
|
+
const str = xml.parser.serializeNode(node, { indent: 2 });
|
|
46
|
+
expect(str).toEqual('<node>\n <child>hello</child>\n</node>');
|
|
47
|
+
});
|
|
42
48
|
});
|
|
43
49
|
|
|
44
50
|
describe(xml.create.fromDomNode, () => {
|
package/src/xml/xml.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { COMMENT_NODE_NAME, XmlGeneralNode, XmlNode, XmlNodeType } from "./xmlNo
|
|
|
5
5
|
import { TEXT_NODE_NAME, XmlCommentNode } from "./xmlNode";
|
|
6
6
|
import { XmlTextNode } from "./xmlNode";
|
|
7
7
|
import { XmlTreeIterator } from "./xmlTreeIterator";
|
|
8
|
-
import type { IMap } from "src/types";
|
|
9
8
|
|
|
10
9
|
export type NodeTypeToNode<T extends XmlNodeType> =
|
|
11
10
|
T extends typeof XmlNodeType.Text ? XmlTextNode :
|
|
@@ -23,6 +22,15 @@ export class XmlUtils {
|
|
|
23
22
|
public readonly modify = new Modify();
|
|
24
23
|
}
|
|
25
24
|
|
|
25
|
+
export interface XmlSerializationOptions {
|
|
26
|
+
/**
|
|
27
|
+
* If specified, the XML will be serialized pretty-printed with the
|
|
28
|
+
* specified number of spaces for each level of indentation. Otherwise, the
|
|
29
|
+
* XML will be serialized in a compact format (single line).
|
|
30
|
+
*/
|
|
31
|
+
indent?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
class Parser {
|
|
27
35
|
|
|
28
36
|
private static xmlFileHeader = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
|
|
@@ -75,7 +83,15 @@ class Parser {
|
|
|
75
83
|
});
|
|
76
84
|
}
|
|
77
85
|
|
|
78
|
-
public serializeNode(node: XmlNode): string {
|
|
86
|
+
public serializeNode(node: XmlNode, options?: XmlSerializationOptions): string {
|
|
87
|
+
return this._serializeNode(node, 0, options);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public serializeFile(xmlNode: XmlNode): string {
|
|
91
|
+
return Parser.xmlFileHeader + xml.parser.serializeNode(xmlNode);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private _serializeNode(node: XmlNode, depth: number, options?: XmlSerializationOptions): string {
|
|
79
95
|
if (!node)
|
|
80
96
|
return '';
|
|
81
97
|
|
|
@@ -102,33 +118,38 @@ class Parser {
|
|
|
102
118
|
const suffix = hasChildren ? '' : '/';
|
|
103
119
|
const openTag = `<${node.nodeName}${attributes}${suffix}>`;
|
|
104
120
|
|
|
105
|
-
|
|
121
|
+
// No children
|
|
122
|
+
if (!hasChildren) {
|
|
123
|
+
return openTag;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Close tag
|
|
127
|
+
const closeTag = `</${node.nodeName}>`;
|
|
106
128
|
|
|
107
|
-
|
|
129
|
+
// Children without indentation
|
|
130
|
+
const indentSize = options?.indent ?? 0;
|
|
131
|
+
if (!indentSize || node.childNodes.every(xml.query.isTextNode)) {
|
|
108
132
|
|
|
109
|
-
// child nodes
|
|
110
133
|
const childrenXml = node.childNodes
|
|
111
|
-
.map(child =>
|
|
134
|
+
.map(child => this._serializeNode(child, depth + 1, options))
|
|
112
135
|
.join('');
|
|
113
136
|
|
|
114
|
-
|
|
115
|
-
const closeTag = `</${node.nodeName}>`;
|
|
116
|
-
|
|
117
|
-
xmlString = openTag + childrenXml + closeTag;
|
|
118
|
-
} else {
|
|
119
|
-
xmlString = openTag;
|
|
137
|
+
return openTag + childrenXml + closeTag;
|
|
120
138
|
}
|
|
121
139
|
|
|
122
|
-
|
|
123
|
-
|
|
140
|
+
// Children with indentation
|
|
141
|
+
const childIndent = "\n" + " ".repeat((depth + 1) * indentSize);
|
|
142
|
+
const childrenXml = node.childNodes
|
|
143
|
+
.map(child => `${childIndent}${this._serializeNode(child, depth + 1, options)}`)
|
|
144
|
+
.join("");
|
|
124
145
|
|
|
125
|
-
|
|
126
|
-
return
|
|
146
|
+
const closeIndent = "\n" + " ".repeat(depth * indentSize);
|
|
147
|
+
return openTag + childrenXml + closeIndent + closeTag;
|
|
127
148
|
}
|
|
128
149
|
}
|
|
129
150
|
|
|
130
151
|
interface XmlGeneralNodeInit {
|
|
131
|
-
attributes?:
|
|
152
|
+
attributes?: Record<string, string>;
|
|
132
153
|
childNodes?: XmlNode[];
|
|
133
154
|
}
|
|
134
155
|
|
|
@@ -358,7 +379,7 @@ class Query {
|
|
|
358
379
|
return null;
|
|
359
380
|
}
|
|
360
381
|
}
|
|
361
|
-
|
|
382
|
+
|
|
362
383
|
if (curNode.nodeType !== nodeType) {
|
|
363
384
|
return null;
|
|
364
385
|
}
|
|
@@ -533,22 +554,22 @@ class Modify {
|
|
|
533
554
|
throw new InternalArgumentMissingError("childOrIndex");
|
|
534
555
|
|
|
535
556
|
if (!parent.childNodes || !parent.childNodes.length)
|
|
536
|
-
throw new Error('Parent node has
|
|
557
|
+
throw new Error('Parent node has no children');
|
|
537
558
|
|
|
538
|
-
//
|
|
559
|
+
// Get child index
|
|
539
560
|
let childIndex: number;
|
|
540
561
|
if (typeof childOrIndex === 'number') {
|
|
541
562
|
childIndex = childOrIndex;
|
|
542
563
|
} else {
|
|
543
564
|
childIndex = parent.childNodes.indexOf(childOrIndex);
|
|
544
565
|
if (childIndex === -1)
|
|
545
|
-
throw new Error('
|
|
566
|
+
throw new Error('Selected child node is not a child of the specified parent');
|
|
546
567
|
}
|
|
547
568
|
|
|
548
569
|
if (childIndex >= parent.childNodes.length)
|
|
549
570
|
throw new RangeError(`Child index ${childIndex} is out of range. Parent has only ${parent.childNodes.length} child nodes.`);
|
|
550
571
|
|
|
551
|
-
//
|
|
572
|
+
// Update references
|
|
552
573
|
const child = parent.childNodes[childIndex];
|
|
553
574
|
if (childIndex > 0) {
|
|
554
575
|
const beforeChild = parent.childNodes[childIndex - 1];
|
|
@@ -557,7 +578,7 @@ class Modify {
|
|
|
557
578
|
child.parentNode = null;
|
|
558
579
|
child.nextSibling = null;
|
|
559
580
|
|
|
560
|
-
//
|
|
581
|
+
// Remove and return
|
|
561
582
|
return parent.childNodes.splice(childIndex, 1)[0];
|
|
562
583
|
}
|
|
563
584
|
|
package/src/xml/xmlNode.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { IMap } from "src/types";
|
|
2
1
|
|
|
3
2
|
export const XmlNodeType = Object.freeze({
|
|
4
3
|
Text: "Text",
|
|
@@ -35,5 +34,5 @@ export interface XmlCommentNode extends XmlNodeBase {
|
|
|
35
34
|
|
|
36
35
|
export interface XmlGeneralNode extends XmlNodeBase {
|
|
37
36
|
nodeType: typeof XmlNodeType.General;
|
|
38
|
-
attributes?:
|
|
37
|
+
attributes?: Record<string, string>;
|
|
39
38
|
}
|