-
Notifications
You must be signed in to change notification settings - Fork 32
/
xml_helpers.ts
109 lines (100 loc) · 2.94 KB
/
xml_helpers.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import { DEFAULT_FONT_SIZE } from "../../constants";
import { concat } from "../../helpers";
import {
XLSXExportFile,
XLSXStructure,
XMLAttributes,
XMLAttributeValue,
XMLString,
} from "../../types/xlsx";
// -------------------------------------
// XML HELPERS
// -------------------------------------
export function createXMLFile(
doc: XMLDocument,
path: string,
contentType?: string
): XLSXExportFile {
return {
content: new XMLSerializer().serializeToString(doc),
path,
contentType,
};
}
function xmlEscape(str: XMLAttributeValue): string {
return String(str)
.replace(/\&/g, "&")
.replace(/\</g, "<")
.replace(/\>/g, ">")
.replace(/\"/g, """)
.replace(/\'/g, "'");
}
export function formatAttributes(attrs: XMLAttributes): XMLString {
return new XMLString(attrs.map(([key, val]) => `${key}="${xmlEscape(val)}"`).join(" "));
}
export function parseXML(xmlString: XMLString): XMLDocument {
const document = new DOMParser().parseFromString(xmlString.toString(), "text/xml");
const parserError = document.querySelector("parsererror");
if (parserError) {
const errorString = parserError.innerHTML;
const lineNumber = parseInt(errorString.split(":")[0], 10);
const xmlStringArray = xmlString.toString().trim().split("\n");
const xmlPreview = xmlStringArray
.slice(Math.max(lineNumber - 3, 0), Math.min(lineNumber + 2, xmlStringArray.length))
.join("\n");
throw new Error(`XML string could not be parsed: ${errorString}\n${xmlPreview}`);
}
return document;
}
export function getDefaultXLSXStructure(): XLSXStructure {
return {
relsFiles: [],
sharedStrings: [],
// default Values that will always be part of the style sheet
styles: [
{
fontId: 0,
fillId: 0,
numFmtId: 0,
borderId: 0,
verticalAlignment: "center",
},
],
fonts: [
{
size: DEFAULT_FONT_SIZE,
family: 2,
color: "000000",
name: "Calibri",
},
],
fills: [{ reservedAttribute: "none" }, { reservedAttribute: "gray125" }],
borders: [{}],
numFmts: [],
dxfs: [],
};
}
export function createOverride(partName: string, contentType: string): XMLString {
return escapeXml/*xml*/ `
<Override ContentType="${contentType}" PartName="${partName}" />
`;
}
export function joinXmlNodes(xmlNodes: XMLString[]): XMLString {
return new XMLString(xmlNodes.join("\n"));
}
/**
* Escape interpolated values except if the value is already
* a properly escaped XML string.
*
* ```
* escapeXml`<t>${"This will be escaped"}</t>`
* ```
*/
export function escapeXml(strings: TemplateStringsArray, ...expressions): XMLString {
let str = [strings[0]];
for (let i = 0; i < expressions.length; i++) {
const value = expressions[i] instanceof XMLString ? expressions[i] : xmlEscape(expressions[i]);
str.push(value + strings[i + 1]);
}
return new XMLString(concat(str));
}