docxtemplater/es6/doc-utils.js
2021-09-04 10:15:02 +05:00

472 lines
10 KiB
JavaScript

"use strict";
const { DOMParser, XMLSerializer } = require("xmldom");
const { throwXmlTagNotFound } = require("./errors");
const { last, first } = require("./utils");
function parser(tag) {
return {
get(scope) {
if (tag === ".") {
return scope;
}
return scope[tag];
},
};
}
function getNearestLeftIndex(parsed, elements, index) {
for (let i = index; i >= 0; i--) {
const part = parsed[i];
for (let j = 0, len = elements.length; j < len; j++) {
const element = elements[j];
if (isStarting(part.value, element)) {
return j;
}
}
}
return null;
}
function getNearestRightIndex(parsed, elements, index) {
for (let i = index, l = parsed.length; i < l; i++) {
const part = parsed[i];
for (let j = 0, len = elements.length; j < len; j++) {
const element = elements[j];
if (isEnding(part.value, element)) {
return j;
}
}
}
return -1;
}
function getNearestLeft(parsed, elements, index) {
const found = getNearestLeftIndex(parsed, elements, index);
if (found !== -1) {
return elements[found];
}
return null;
}
function getNearestRight(parsed, elements, index) {
const found = getNearestRightIndex(parsed, elements, index);
if (found !== -1) {
return elements[found];
}
return null;
}
function buildNearestCache(postparsed, tags) {
return postparsed.reduce(function (cached, part, i) {
if (part.type === "tag" && tags.indexOf(part.tag) !== -1) {
cached.push({ i, part });
}
return cached;
}, []);
}
function getNearestLeftIndexWithCache(index, cache) {
if (cache.length === 0) {
return -1;
}
for (let i = 0, len = cache.length; i < len; i++) {
const current = cache[i];
const next = cache[i + 1];
if (
current.i < index &&
(!next || index < next.i) &&
current.part.position === "start"
) {
return i;
}
}
return -1;
}
function getNearestLeftWithCache(index, cache) {
const found = getNearestLeftIndexWithCache(index, cache);
if (found !== -1) {
return cache[found].part.tag;
}
return null;
}
function getNearestRightIndexWithCache(index, cache) {
if (cache.length === 0) {
return -1;
}
for (let i = 0, len = cache.length; i < len; i++) {
const current = cache[i];
const last = cache[i - 1];
if (
index < current.i &&
(!last || last.i < index) &&
current.part.position === "end"
) {
return i;
}
}
return -1;
}
function getNearestRightWithCache(index, cache) {
const found = getNearestRightIndexWithCache(index, cache);
if (found !== -1) {
return cache[found].part.tag;
}
return null;
}
function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
function startsWith(str, prefix) {
return str.substring(0, prefix.length) === prefix;
}
function unique(arr) {
const hash = {},
result = [];
for (let i = 0, l = arr.length; i < l; ++i) {
if (!hash.hasOwnProperty(arr[i])) {
hash[arr[i]] = true;
result.push(arr[i]);
}
}
return result;
}
function chunkBy(parsed, f) {
return parsed
.reduce(
function (chunks, p) {
const currentChunk = last(chunks);
const res = f(p);
if (currentChunk.length === 0) {
currentChunk.push(p);
return chunks;
}
if (res === "start") {
chunks.push([p]);
} else if (res === "end") {
currentChunk.push(p);
chunks.push([]);
} else {
currentChunk.push(p);
}
return chunks;
},
[[]]
)
.filter(function (p) {
return p.length > 0;
});
}
const defaults = {
paragraphLoop: false,
nullGetter(part) {
if (!part.module) {
return "undefined";
}
if (part.module === "rawxml") {
return "";
}
return "";
},
xmlFileNames: [],
parser,
linebreaks: false,
fileTypeConfig: null,
delimiters: {
start: "{",
end: "}",
},
};
function mergeObjects() {
const resObj = {};
let obj, keys;
for (let i = 0; i < arguments.length; i += 1) {
obj = arguments[i];
keys = Object.keys(obj);
for (let j = 0; j < keys.length; j += 1) {
resObj[keys[j]] = obj[keys[j]];
}
}
return resObj;
}
function xml2str(xmlNode) {
const a = new XMLSerializer();
return a.serializeToString(xmlNode).replace(/xmlns(:[a-z0-9]+)?="" ?/g, "");
}
function str2xml(str) {
if (str.charCodeAt(0) === 65279) {
// BOM sequence
str = str.substr(1);
}
const parser = new DOMParser();
return parser.parseFromString(str, "text/xml");
}
const charMap = [
["&", "&amp;"],
["<", "&lt;"],
[">", "&gt;"],
['"', "&quot;"],
["'", "&apos;"],
];
function escapeRegExp(str) {
// to be able to use a string as a regex
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
const charMapRegexes = charMap.map(function ([endChar, startChar]) {
return {
rstart: new RegExp(escapeRegExp(startChar), "g"),
rend: new RegExp(escapeRegExp(endChar), "g"),
start: startChar,
end: endChar,
};
});
function wordToUtf8(string) {
let r;
for (let i = charMapRegexes.length - 1; i >= 0; i--) {
r = charMapRegexes[i];
string = string.replace(r.rstart, r.end);
}
return string;
}
function utf8ToWord(string) {
if (typeof string !== "string") {
string = string.toString();
}
let r;
for (let i = 0, l = charMapRegexes.length; i < l; i++) {
r = charMapRegexes[i];
string = string.replace(r.rend, r.start);
}
return string;
}
// This function is written with for loops for performance
function concatArrays(arrays) {
const result = [];
for (let i = 0; i < arrays.length; i++) {
const array = arrays[i];
for (let j = 0, len = array.length; j < len; j++) {
result.push(array[j]);
}
}
return result;
}
const spaceRegexp = new RegExp(String.fromCharCode(160), "g");
function convertSpaces(s) {
return s.replace(spaceRegexp, " ");
}
function pregMatchAll(regex, content) {
/* regex is a string, content is the content. It returns an array of all matches with their offset, for example:
regex=la
content=lolalolilala
returns: [{array: {0: 'la'},offset: 2},{array: {0: 'la'},offset: 8},{array: {0: 'la'} ,offset: 10}]
*/
const matchArray = [];
let match;
while ((match = regex.exec(content)) != null) {
matchArray.push({ array: match, offset: match.index });
}
return matchArray;
}
function isEnding(value, element) {
return value === "</" + element + ">";
}
function isStarting(value, element) {
return (
value.indexOf("<" + element) === 0 &&
[">", " "].indexOf(value[element.length + 1]) !== -1
);
}
function getRight(parsed, element, index) {
const val = getRightOrNull(parsed, element, index);
if (val !== null) {
return val;
}
throwXmlTagNotFound({ position: "right", element, parsed, index });
}
function getRightOrNull(parsed, elements, index) {
if (typeof elements === "string") {
elements = [elements];
}
let level = 1;
for (let i = index, l = parsed.length; i < l; i++) {
const part = parsed[i];
for (let j = 0, len = elements.length; j < len; j++) {
const element = elements[j];
if (isEnding(part.value, element)) {
level--;
}
if (isStarting(part.value, element)) {
level++;
}
if (level === 0) {
return i;
}
}
}
return null;
}
function getLeft(parsed, element, index) {
const val = getLeftOrNull(parsed, element, index);
if (val !== null) {
return val;
}
throwXmlTagNotFound({ position: "left", element, parsed, index });
}
function getLeftOrNull(parsed, elements, index) {
if (typeof elements === "string") {
elements = [elements];
}
let level = 1;
for (let i = index; i >= 0; i--) {
const part = parsed[i];
for (let j = 0, len = elements.length; j < len; j++) {
const element = elements[j];
if (isStarting(part.value, element)) {
level--;
}
if (isEnding(part.value, element)) {
level++;
}
if (level === 0) {
return i;
}
}
}
return null;
}
function isTagStart(tagType, { type, tag, position }) {
return type === "tag" && tag === tagType && position === "start";
}
function isTagEnd(tagType, { type, tag, position }) {
return type === "tag" && tag === tagType && position === "end";
}
function isParagraphStart(options) {
return isTagStart("w:p", options) || isTagStart("a:p", options);
}
function isParagraphEnd(options) {
return isTagEnd("w:p", options) || isTagEnd("a:p", options);
}
function isTextStart(part) {
return part.type === "tag" && part.position === "start" && part.text;
}
function isTextEnd(part) {
return part.type === "tag" && part.position === "end" && part.text;
}
function isContent(p) {
return (
p.type === "placeholder" ||
(p.type === "content" && p.position === "insidetag")
);
}
const corruptCharacters = /[\x00-\x08\x0B\x0C\x0E-\x1F]/;
// 00 NUL '\0' (null character)
// 01 SOH (start of heading)
// 02 STX (start of text)
// 03 ETX (end of text)
// 04 EOT (end of transmission)
// 05 ENQ (enquiry)
// 06 ACK (acknowledge)
// 07 BEL '\a' (bell)
// 08 BS '\b' (backspace)
// 0B VT '\v' (vertical tab)
// 0C FF '\f' (form feed)
// 0E SO (shift out)
// 0F SI (shift in)
// 10 DLE (data link escape)
// 11 DC1 (device control 1)
// 12 DC2 (device control 2)
// 13 DC3 (device control 3)
// 14 DC4 (device control 4)
// 15 NAK (negative ack.)
// 16 SYN (synchronous idle)
// 17 ETB (end of trans. blk)
// 18 CAN (cancel)
// 19 EM (end of medium)
// 1A SUB (substitute)
// 1B ESC (escape)
// 1C FS (file separator)
// 1D GS (group separator)
// 1E RS (record separator)
// 1F US (unit separator)
function hasCorruptCharacters(string) {
return corruptCharacters.test(string);
}
function invertMap(map) {
return Object.keys(map).reduce(function (invertedMap, key) {
const value = map[key];
invertedMap[value] = invertedMap[value] || [];
invertedMap[value].push(key);
return invertedMap;
}, {});
}
module.exports = {
endsWith,
startsWith,
getNearestLeft,
getNearestRight,
getNearestLeftWithCache,
getNearestRightWithCache,
getNearestLeftIndex,
getNearestRightIndex,
getNearestLeftIndexWithCache,
getNearestRightIndexWithCache,
buildNearestCache,
isContent,
isParagraphStart,
isParagraphEnd,
isTagStart,
isTagEnd,
isTextStart,
isTextEnd,
unique,
chunkBy,
last,
first,
mergeObjects,
xml2str,
str2xml,
getRightOrNull,
getRight,
getLeftOrNull,
getLeft,
pregMatchAll,
convertSpaces,
escapeRegExp,
charMapRegexes,
hasCorruptCharacters,
defaults,
wordToUtf8,
utf8ToWord,
concatArrays,
invertMap,
charMap,
};