/* * Copyright (c) 2016-2023 Martin Donath * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ /* ---------------------------------------------------------------------------- * Types * ------------------------------------------------------------------------- */ /** * Position table */ export type PositionTable = number[][] /** * Position */ export type Position = number /* ---------------------------------------------------------------------------- * Functions * ------------------------------------------------------------------------- */ /** * Highlight all occurrences in a string * * This function receives a field's value (e.g. like `title` or `text`), it's * position table that was generated during indexing, and the positions found * when executing the query. It then highlights all occurrences, and returns * their concatenation. In case of multiple blocks, two are returned. * * @param input - Input value * @param table - Table for indexing * @param positions - Occurrences * @param full - Full results * * @returns Highlighted string value */ export function highlight( input: string, table: PositionTable, positions: Position[], full = false ): string { return highlightAll([input], table, positions, full).pop()! } /** * Highlight all occurrences in a set of strings * * @param inputs - Input values * @param table - Table for indexing * @param positions - Occurrences * @param full - Full results * * @returns Highlighted string values */ export function highlightAll( inputs: string[], table: PositionTable, positions: Position[], full = false ): string[] { /* Map blocks to input values */ const mapping = [0] for (let t = 1; t < table.length; t++) { const prev = table[t - 1] const next = table[t] /* Check if table points to new block */ const p = prev[prev.length - 1] >>> 2 & 0x3FF const q = next[0] >>> 12 /* Add block to mapping */ mapping.push(+(p > q) + mapping[mapping.length - 1]) } /* Highlight strings one after another */ return inputs.map((input, i) => { let cursor = 0 /* Map occurrences to blocks */ const blocks = new Map() for (const p of positions.sort((a, b) => a - b)) { const index = p & 0xFFFFF const block = p >>> 20 if (mapping[block] !== i) continue /* Ensure presence of block group */ let group = blocks.get(block) if (typeof group === "undefined") blocks.set(block, group = []) /* Add index to group */ group.push(index) } /* Just return string, if no occurrences */ if (blocks.size === 0) return input /* Compute slices */ const slices: string[] = [] for (const [block, indexes] of blocks) { const t = table[block] /* Extract positions and length */ const start = t[0] >>> 12 const end = t[t.length - 1] >>> 12 const length = t[t.length - 1] >>> 2 & 0x3FF /* Add prefix, if full results are desired */ if (full && start > cursor) slices.push(input.slice(cursor, start)) /* Extract and highlight slice */ let slice = input.slice(start, end + length) for (const j of indexes.sort((a, b) => b - a)) { /* Retrieve offset and length of match */ const p = (t[j] >>> 12) - start const q = (t[j] >>> 2 & 0x3FF) + p /* Wrap occurrence */ slice = [ slice.slice(0, p), "", slice.slice(p, q), "", slice.slice(q) ].join("") } /* Update cursor */ cursor = end + length /* Append slice and abort if we have two */ if (slices.push(slice) === 2) break } /* Add suffix, if full results are desired */ if (full && cursor < input.length) slices.push(input.slice(cursor)) /* Return highlighted slices */ return slices.join("") }) }