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":"","offset":13},{"tag":"","offset":265},{"tag":"","offset":271},{"tag":"","offset":828},{"tag":"","offset":883},{"tag":"","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, };