211 lines
5.1 KiB
JavaScript
211 lines
5.1 KiB
JavaScript
const {
|
|
getRightOrNull,
|
|
getRight,
|
|
getLeft,
|
|
getLeftOrNull,
|
|
chunkBy,
|
|
isTagStart,
|
|
isTagEnd,
|
|
isContent,
|
|
last,
|
|
first,
|
|
} = require("./doc-utils");
|
|
const {
|
|
XTTemplateError,
|
|
throwExpandNotFound,
|
|
getLoopPositionProducesInvalidXMLError,
|
|
} = require("./errors");
|
|
|
|
function lastTagIsOpenTag(tags, tag) {
|
|
if (tags.length === 0) {
|
|
return false;
|
|
}
|
|
const innerLastTag = last(tags).tag.substr(1);
|
|
const innerCurrentTag = tag.substr(2, tag.length - 3);
|
|
return innerLastTag.indexOf(innerCurrentTag) === 0;
|
|
}
|
|
|
|
function addTag(tags, tag) {
|
|
tags.push({ tag });
|
|
return tags;
|
|
}
|
|
|
|
function getListXmlElements(parts) {
|
|
/*
|
|
get the different closing and opening tags between two texts (doesn't take into account tags that are opened then closed (those that are closed then opened are returned)):
|
|
returns:[{"tag":"</w:r>","offset":13},{"tag":"</w:p>","offset":265},{"tag":"</w:tc>","offset":271},{"tag":"<w:tc>","offset":828},{"tag":"<w:p>","offset":883},{"tag":"<w:r>","offset":1483}]
|
|
*/
|
|
const tags = parts.filter(function (part) {
|
|
return part.type === "tag";
|
|
});
|
|
|
|
let result = [];
|
|
|
|
for (let i = 0, tag; i < tags.length; i++) {
|
|
tag = tags[i].value;
|
|
// closing tag
|
|
if (tag[1] === "/") {
|
|
if (lastTagIsOpenTag(result, tag)) {
|
|
result.pop();
|
|
} else {
|
|
result = addTag(result, tag);
|
|
}
|
|
} else if (tag[tag.length - 2] !== "/") {
|
|
result = addTag(result, tag);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function has(name, xmlElements) {
|
|
for (let i = 0; i < xmlElements.length; i++) {
|
|
const xmlElement = xmlElements[i];
|
|
if (xmlElement.tag.indexOf(`<${name}`) === 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getExpandToDefault(postparsed, pair, expandTags) {
|
|
const parts = postparsed.slice(pair[0].offset, pair[1].offset);
|
|
const xmlElements = getListXmlElements(parts);
|
|
const closingTagCount = xmlElements.filter(function (xmlElement) {
|
|
return xmlElement.tag[1] === "/";
|
|
}).length;
|
|
const startingTagCount = xmlElements.filter(function (xmlElement) {
|
|
const { tag } = xmlElement;
|
|
return tag[1] !== "/" && tag[tag.length - 2] !== "/";
|
|
}).length;
|
|
if (closingTagCount !== startingTagCount) {
|
|
return {
|
|
error: getLoopPositionProducesInvalidXMLError({
|
|
tag: first(pair).part.value,
|
|
offset: [first(pair).part.offset, last(pair).part.offset],
|
|
}),
|
|
};
|
|
}
|
|
for (let i = 0, len = expandTags.length; i < len; i++) {
|
|
const { contains, expand, onlyTextInTag } = expandTags[i];
|
|
if (has(contains, xmlElements)) {
|
|
if (onlyTextInTag) {
|
|
const left = getLeftOrNull(postparsed, contains, pair[0].offset);
|
|
const right = getRightOrNull(postparsed, contains, pair[1].offset);
|
|
if (left === null || right === null) {
|
|
continue;
|
|
}
|
|
|
|
const chunks = chunkBy(postparsed.slice(left, right), function (p) {
|
|
if (isTagStart(contains, p)) {
|
|
return "start";
|
|
}
|
|
if (isTagEnd(contains, p)) {
|
|
return "end";
|
|
}
|
|
return null;
|
|
});
|
|
|
|
if (chunks.length <= 2) {
|
|
continue;
|
|
}
|
|
|
|
const firstChunk = first(chunks);
|
|
const lastChunk = last(chunks);
|
|
|
|
const firstContent = firstChunk.filter(isContent);
|
|
const lastContent = lastChunk.filter(isContent);
|
|
if (firstContent.length !== 1 || lastContent.length !== 1) {
|
|
continue;
|
|
}
|
|
}
|
|
return { value: expand };
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function expandOne(part, index, postparsed, options) {
|
|
const expandTo = part.expandTo || options.expandTo;
|
|
if (!expandTo) {
|
|
return postparsed;
|
|
}
|
|
let right, left;
|
|
try {
|
|
left = getLeft(postparsed, expandTo, index);
|
|
right = getRight(postparsed, expandTo, index);
|
|
} catch (rootError) {
|
|
if (rootError instanceof XTTemplateError) {
|
|
throwExpandNotFound({
|
|
part,
|
|
rootError,
|
|
postparsed,
|
|
expandTo,
|
|
index,
|
|
...options.error,
|
|
});
|
|
}
|
|
throw rootError;
|
|
}
|
|
const leftParts = postparsed.slice(left, index);
|
|
const rightParts = postparsed.slice(index + 1, right + 1);
|
|
let inner = options.getInner({
|
|
postparse: options.postparse,
|
|
index,
|
|
part,
|
|
leftParts,
|
|
rightParts,
|
|
left,
|
|
right,
|
|
postparsed,
|
|
});
|
|
if (!inner.length) {
|
|
inner.expanded = [leftParts, rightParts];
|
|
inner = [inner];
|
|
}
|
|
return { left, right, inner };
|
|
}
|
|
|
|
function expandToOne(postparsed, options) {
|
|
let errors = [];
|
|
if (postparsed.errors) {
|
|
errors = postparsed.errors;
|
|
postparsed = postparsed.postparsed;
|
|
}
|
|
const results = [];
|
|
for (let i = 0, len = postparsed.length; i < len; i++) {
|
|
const part = postparsed[i];
|
|
if (part.type === "placeholder" && part.module === options.moduleName) {
|
|
try {
|
|
const result = expandOne(part, i, postparsed, options);
|
|
i = result.right;
|
|
results.push(result);
|
|
} catch (error) {
|
|
if (error instanceof XTTemplateError) {
|
|
errors.push(error);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const newParsed = [];
|
|
let currentResult = 0;
|
|
for (let i = 0, len = postparsed.length; i < len; i++) {
|
|
const part = postparsed[i];
|
|
const result = results[currentResult];
|
|
if (result && result.left === i) {
|
|
newParsed.push(...results[currentResult].inner);
|
|
currentResult++;
|
|
i = result.right;
|
|
} else {
|
|
newParsed.push(part);
|
|
}
|
|
}
|
|
return { postparsed: newParsed, errors };
|
|
}
|
|
|
|
module.exports = {
|
|
expandToOne,
|
|
getExpandToDefault,
|
|
};
|