From 8e2e5eb5bd31fcff0ecb298404420c051588b055 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Tue, 18 Oct 2022 14:32:10 -0400 Subject: [PATCH] CSR-731 | Refactor cms-content-helper.js Also fixed a looping issue --- src/helpers/cms-content-helper.js | 189 +++++++++++------- src/layouts/quote/quote.vue | 51 ++--- .../service-package-radio.vue | 8 +- 3 files changed, 147 insertions(+), 101 deletions(-) diff --git a/src/helpers/cms-content-helper.js b/src/helpers/cms-content-helper.js index ac945d2b0..aff7586d1 100644 --- a/src/helpers/cms-content-helper.js +++ b/src/helpers/cms-content-helper.js @@ -40,7 +40,8 @@ export function fetchCmsContentForPage(fmgPage) { // Function to convert a string, into a matching global state item. function mapStringToState(str) { // Pull all matches out of the string. - const regexExp = new RegExp("{(.*?):(.*?)}", "g"); + //const regexExp = new RegExp("{(.*?):(.*?)}", "g"); + const regexExp = new RegExp("{([^{}]*?):([^{}]*?)}", "g"); const regexMatches = [...str.matchAll(regexExp)]; const globalStateMatches = regexMatches.filter(match => { return match[1] === dynamicStrings.GLOBAL_STATE; @@ -93,11 +94,11 @@ function findAndReplaceGlobalStateValues(widgetModel, widgetName) { function processWidgetItemForReplacement(widgetModel, key) { // If we have a string, and it needs to be replaced. if (typeof widgetModel[key] === "string") { - widgetModel[key] = checkForIfStatements(widgetModel[key], dynamicStrings.GLOBAL_STATE, getStoreValueFromString); + widgetModel[key] = processIfStatements(widgetModel[key], dynamicStrings.GLOBAL_STATE, getStoreValueFromString); if (widgetModel[key].includes(dynamicStrings.GLOBAL_STATE)) { widgetModel[key] = mapStringToState(widgetModel[key]); } - return reconstructPageLevelIfStatements(widgetModel[key]); + return widgetModel[key]; } // If we have an object. array, etc @@ -116,75 +117,6 @@ function processWidgetItemForReplacement(widgetModel, key) { return widgetModel[key]; } -export function checkForIfStatements(str, ifConditionString, replaceStringCallback) { - // Pattern to split off each {if:...}, {else}, {end} and their trailing strings - const regexExp = new RegExp("(?^.+?)(?=(?:{if))|(?{if:)(?.*?:)(?.*?)}(?.*?)(?=(?:{(?:end|else|if:.*?)}))|(?{else})(?.*?)(?=(?:{(?:end|else|if:.*?)}))|(?{end})(?.*?)(?=(?:{(?:end|else|if:.*?)})|$)", "g"); - const regexMatches = [...str.matchAll(regexExp)]; - // Find and evaluate full globalState if statements - if (regexMatches.length) { - regexMatches.forEach((match, index) => { - if(match.groups.ifOperator) { - if(regexMatches[index+1].groups.endOperator) { - processIfStatement(regexMatches.slice(index, index+2), ifConditionString, replaceStringCallback) + regexMatches[index+1].groups.endTrailingString; - } - else if(regexMatches[index+2].groups.endOperator) { - processIfStatement(regexMatches.slice(index, index+3), ifConditionString, replaceStringCallback)+ regexMatches[index+2].groups.endTrailingString; - } - } - }); - const processedString = joinProcessedRegexArray(regexMatches); - return checkForIfStatements(processedString, ifConditionString, replaceStringCallback); - } else { - return str; - } -} - -function reconstructPageLevelIfStatements(str) { - return str.replaceAll("(((", "{").replaceAll(")))","}"); -} - -function joinProcessedRegexArray(regexMatches) { - let processedString = ""; - regexMatches.forEach((match) => { - processedString += match.groups.cleanString ?? match[0]; - }); - return processedString; -} - -function processIfStatement(ifStatementArray, ifConditionString, replaceStringCallback) { - if(ifStatementArray[0].groups.ifConditionType.includes(ifConditionString)) { - //replace the if condition with a real value - const ifCondition = replaceStringCallback(ifStatementArray[0].groups.ifCondition); - //convert the entire if statement - let processedIfStatementString; - if (ifCondition) { - processedIfStatementString = ifStatementArray[0].groups.ifTrailingString; - } else { - processedIfStatementString = ifStatementArray[1].groups.elseTrailingString ?? ""; - } - ifStatementArray.forEach((entry) => { - if (entry.groups.ifOperator) { - entry.groups.cleanString = processedIfStatementString; - } else if (entry.groups.endOperator) { - entry.groups.cleanString = entry.groups.endTrailingString; - } else { - entry.groups.cleanString = ""; - } - }) - } else { - // stow the if statement to reconstruct later when passing to the page - ifStatementArray.forEach((entry) => { - const groups = entry.groups; - if (groups.ifOperator) - groups.cleanString = "(((if:" + groups.ifConditionType + groups.ifCondition + ")))" + groups.ifTrailingString; - else if (groups.elseOperator) - groups.cleanString = "(((else)))" + groups.elseTrailingString; - else - groups.cleanString = "(((end)))" + groups.endTrailingString; - }); - } -} - function getStoreValueFromString(str) { let storeOrStateObject = str.includes('getters') ? store :store.state; for (const s of str.split(".")) { @@ -197,8 +129,121 @@ function getStoreValueFromString(str) { return storeOrStateObject; } +/////////////////////////////////// +// If Statement Processing Logic // +/////////////////////////////////// +/** + * Recursive function - replaces all instances of if statements from the CMS that utilize the specified ifConditionKeyword + * @param {*} str string - Input string to be processed + * @param {*} ifConditionKeyword string - Defines which if statements to process ex: 'globalState' + * @param {*} replacePlaceholderCallback function - Callback to replace CMS placeholder values + * @returns The processed string + */ +export function processIfStatements(str, ifConditionKeyword, replacePlaceholderCallback) { + const containsRelevantIfStatement = new RegExp("{if:" + ifConditionKeyword +":.+?}", "g").test(str); + if(!containsRelevantIfStatement) { + return str; + } else { + const ifStatementRegexExpression = getIfStatementRegexExpression(); + const ifStatementRegexMatches = [...str.matchAll(ifStatementRegexExpression)]; + const completeIfStatementArray = getFirstNonNestedIfStatement(ifStatementRegexMatches, ifConditionKeyword); + const processedIfStatementString = executeIfStatementAndGetProcessedString(completeIfStatementArray, replacePlaceholderCallback); + replaceIfStatementWithProcessedString(completeIfStatementArray, processedIfStatementString); + const reconstructedPostProcessedString = joinProcessedRegexArray(ifStatementRegexMatches); + return processIfStatements(reconstructedPostProcessedString, ifConditionKeyword, replacePlaceholderCallback); + } +} +function getFirstNonNestedIfStatement(matches, ifConditionKeyword) { + let index = 0; + for(const match of matches) { + if (match.groups.ifOperator && match.groups.ifConditionType === ifConditionKeyword) { + let interiorIndex = 0; + let nestedLevel = 0; + for (const interiorMatch of matches.slice(index+1)) { + if (interiorMatch.groups.ifOperator) { + if(interiorMatch.groups.ifConditionType === ifConditionKeyword) { + break; + } else { + nestedLevel++; + } + } else if (interiorMatch.groups.endOperator) { + if (nestedLevel) { + nestedLevel--; + } else { + return matches.slice(index, index + interiorIndex+2); + } + } + interiorIndex++; + } + } + index++; + } +} + +function joinProcessedRegexArray(regexMatches) { + let processedString = ""; + regexMatches.forEach((match) => { + processedString += match.groups.cleanString ?? match[0]; + }); + return processedString; +} + +function executeIfStatementAndGetProcessedString(ifStatementArray, replacePlaceholderCallback) { + const ifCondition = replacePlaceholderCallback(ifStatementArray[0].groups.ifCondition); + if (ifCondition) { + return ifStatementArray[0].groups.ifTrailingString; + } else { + return ifStatementArray[1].groups.elseTrailingString ?? ""; + } +} + +function replaceIfStatementWithProcessedString(ifStatementArray, processedIfStatementString) { + ifStatementArray.forEach((entry) => { + if (entry.groups.ifOperator) { + entry.groups.cleanString = processedIfStatementString; + } else if (entry.groups.endOperator) { + entry.groups.cleanString = entry.groups.endTrailingString; + } else { + entry.groups.cleanString = ""; + } + }) +} + +function getIfStatementRegexExpression() { + // Matches but does not capture: + // {if:...} or {else} or {end} + const anyLogicOperatorNonCapture = "(?:{(?:end|else|if:.*?)})"; + // NOTE: ? syntax stores the captured match like so: match.groups.variableName + const matchStartOfString = ( + "(?^.+?)" + // Match and Capture all characters (lazy), cannot be empty + "(?=(?:{if))" // Looks ahead but does not capture {if + ); + const matchIfOperator = ( + "(?{if:)" + // Match & Capture {if: + "(?.*?):" + // Match all chars up to and including next ":" - Capture all chars up to ":" + "(?.*?)}" + // Match all chars up to and including next "}" - Capture all chars up to "}" + "(?.*?)" + // Match and Capture all characters (lazy), can be empty + "(?=" + anyLogicOperatorNonCapture + ")" // Looks ahead but does not capture the next logic operator + ); + const matchElseOperator = ( + "(?{else})" + // Match & Capture {else} + "(?.*?)" + // Match & Capture all characters (lazy), can be empty + "(?=" + anyLogicOperatorNonCapture + ")" // Looks ahead but does not capture the next logic operator + ); + const matchEndOperator = ( + "(?{end})" + // Match & Capture {end} + "(?.*?)" + // Match & Capture all chracters (lazy), can be empty + "(?="+ anyLogicOperatorNonCapture + "|$)" // Looks ahead but does not capture the next logic operator + ); + // Combine all matching patterns, separated by "or" pipes + return new RegExp(matchStartOfString + "|" + matchIfOperator + "|" + matchElseOperator + "|" + matchEndOperator, "g"); +} + +////////////////////////////////////////// +// End of If Statement Processing Logic // +////////////////////////////////////////// export function doesCopyContainRouterLink(copy) { return copy.includes(this.dynamicStrings.ROUTER_LINK); diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index 20ec96dfa..7ad86e86c 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -24,6 +24,7 @@ groupName="CashOrInsuranceQuestion" /> )|(?:<\/ul>)/g; return copy.replace(regex, '');