import { escapeHtml, escapeStringRegexp } from "../../../../util"; import type { IElasticHighlighPropertyInfo } from "../../../../base/Root/Module/ItemDefinition/PropertyDefinition/types"; /** * Given full highlights it will extract them into standard highlights to be used * for standard matching * @param highlights */ export function extractHighlightsFromFull(highlights: IElasticHighlighPropertyInfo): IElasticHighlighPropertyInfo { if (!highlights || !highlights.highlights) { return highlights; } // we will find all between and and mark it as each token that matched if (highlights.full) { // this will be it const newValue: IElasticHighlighPropertyInfo = { full: false, highlights: [], match: highlights.match, lang: highlights.lang, }; // now we loop in highlights.highlights.forEach((h) => { // start accumulating and specify if we are in let accum = ""; let isInHighlightMatch = false; // now start loop into characters for (let char of h) { // and put them into the accumulator accum += char; // now we are starting to get in if (accum.endsWith("")) { accum = ""; isInHighlightMatch = true; } // and we have ended the match if (accum.endsWith("")) { if (isInHighlightMatch) { // if we were into it we will replace the bad ending const tokensMatched = accum.replace("", ""); // and split that to get each token tokensMatched.split(" ").forEach((token) => { // trim it and inject it into const trimmed = token.trim(); if (trimmed) { newValue.highlights.push(trimmed); } }); } // and we clear our accumulator accum = ""; isInHighlightMatch = false; } } }); return newValue; } else { return highlights; } } /** * Applies the highlights * @param strRaw a plain string value * @param highlights the highlights to apply * @returns an object on whether it was applied, if it was, the result will be HTML encoded as rich text */ export function applyHighlights(strRaw: string, highlightsRaw: IElasticHighlighPropertyInfo) { if (!strRaw || !highlightsRaw || Object.keys(highlightsRaw).length === 0 || !highlightsRaw.highlights) { return { value: strRaw, applied: false, }; } const highlights = extractHighlightsFromFull(highlightsRaw); const splitted = highlights.match.trim().split(" "); const lastWordPrefix = splitted[splitted.length - 1].toLowerCase(); if (!lastWordPrefix) { return { value: strRaw, applied: false, }; } const htmlEscaped = escapeHtml(strRaw); const expectedHighlights = (highlights.highlights || []).map((p) => escapeHtml(p.toLowerCase())); const lastWordRegex: RegExp = new RegExp(escapeStringRegexp(escapeHtml(lastWordPrefix)), "ig"); const expectedHighlightsReg = expectedHighlights.map((p) => new RegExp(escapeStringRegexp(p), "ig")); let newHTMLEscaped = htmlEscaped; let applied = false; for (let regToReplace of expectedHighlightsReg) { newHTMLEscaped = newHTMLEscaped.replaceAll(regToReplace, (str) => { const lower = str.toLowerCase(); if (lower === lastWordPrefix) { applied = true; return "" + str + ""; } // we replace only if we are prefixed with the given last word regex prefix const selfReplaced = str.replaceAll(lastWordRegex, (str2, pos) => pos === 0 ? (str2 + "") : str2); // it changed if (selfReplaced !== str) { // partial prefix match applied = true; return "" + selfReplaced; } else { // eg when words like "hacer" match "haciendo" // they are the same verb but different so the whole is fully matched applied = true; return "" + str + ""; } }); } if (!applied) { return { value: strRaw, applied, } } return { value: newHTMLEscaped, applied, } }