You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS应用文本搜索高亮问题:阿拉伯语断词与变音符号不敏感需求

Hey,我帮你搞定这两个WebView搜索的问题,还把那个jQuery方案的核心逻辑转成原生JS整合进去了——不用再依赖jQuery啦!

解决iOS WebView文本搜索的阿拉伯语断词与变音符号不敏感问题

你的两个问题其实都和文本处理的细节有关:阿拉伯语的RTL特性+连字依赖上下文,以及变音符号的匹配逻辑。下面是具体的解决方案和整合后的完整代码:

问题1:阿拉伯语单词断开的根源与修复

阿拉伯语是从右到左的语言,字符的形态(比如连字)完全依赖前后文的字符。你原来的代码直接拆分文本节点、插入span的方式会破坏这种上下文关联,导致单词被拆碎。

正确的做法是用**Range API**来选中匹配的文本片段,再用span包裹——这个API会自动处理文本的上下文结构,保留阿拉伯语的原始连字形态,不会出现断词。

问题2:变音符号不敏感搜索的实现

要让不带变音符号的搜索词匹配到带变音符号的内容,我们需要把原文本和搜索词里的所有阿拉伯语变音符号(Tashkeel,Unicode范围是\u064B-\u065F)都移除,再做匹配。我写了一个简单的工具函数来处理这个逻辑。

整合后的完整原生JS代码

下面的代码完全替代你原来的实现,同时解决了两个问题:

var uiWebview_SearchResultCount = 0;

// 工具函数:移除阿拉伯语变音符号(Tashkeel)
function removeTashkeel(text) {
    return text.replace(/[\u064B-\u065F]/g, '');
}

// 工具函数:用Range包裹匹配文本,避免阿拉伯语断词
function wrapMatchedText(range) {
    const span = document.createElement("span");
    span.className = "uiWebviewHighlight";
    span.style.backgroundColor = "#007DC8a3";
    span.style.borderRadius = "3px";
    span.setAttribute("id", `SEARCH WORD${uiWebview_SearchResultCount}`);
    
    // 用Range包裹文本,保留上下文形态
    range.surroundContents(span);
    uiWebview_SearchResultCount++;
}

/*! 
 * @method uiWebview_HighlightAllOccurencesOfStringForElement
 * @abstract 递归搜索元素及其子节点,高亮匹配文本
 * @param {Element} element - 要搜索的HTML元素
 * @param {string} keyword - 预处理后的搜索关键词(已去变音符号+小写)
 */
function uiWebview_HighlightAllOccurencesOfStringForElement(element, keyword) {
    if (!element) return;

    if (element.nodeType === Node.TEXT_NODE) {
        const originalText = element.nodeValue;
        const normalizedText = removeTashkeel(originalText).toLowerCase();
        let idx = normalizedText.indexOf(keyword);

        while (idx !== -1) {
            // 计算匹配文本在原始文本中的准确位置(处理变音符号导致的长度差)
            let startPos = 0;
            let tempNormalized = "";
            while (tempNormalized.length < idx) {
                tempNormalized += removeTashkeel(originalText[startPos]);
                startPos++;
            }

            let endPos = startPos;
            let keywordLength = 0;
            while (keywordLength < keyword.length) {
                tempNormalized += removeTashkeel(originalText[endPos]);
                keywordLength = tempNormalized.slice(idx).length;
                endPos++;
            }

            // 创建Range选中匹配片段
            const range = document.createRange();
            range.setStart(element, startPos);
            range.setEnd(element, endPos);

            // 高亮匹配文本
            wrapMatchedText(range);

            // 更新节点,继续搜索剩余文本
            element = range.endContainer;
            normalizedText = removeTashkeel(element.nodeValue).toLowerCase();
            idx = normalizedText.indexOf(keyword);
        }
    } else if (element.nodeType === Node.ELEMENT_NODE) {
        if (element.style.display !== "none" && element.nodeName.toLowerCase() !== 'select') {
            // 正向遍历子节点,避免DOM修改导致索引混乱
            Array.from(element.childNodes).forEach(child => {
                uiWebview_HighlightAllOccurencesOfStringForElement(child, keyword);
            });
        }
    }
}

// 主搜索入口:预处理关键词后开始搜索
function uiWebview_HighlightAllOccurencesOfString(keyword) {
    uiWebview_RemoveAllHighlights();
    // 对搜索关键词做同样的预处理:去变音符号+小写
    const normalizedKeyword = removeTashkeel(keyword).toLowerCase();
    uiWebview_HighlightAllOccurencesOfStringForElement(document.body, normalizedKeyword);
}

// 递归移除所有高亮元素
function uiWebview_RemoveAllHighlightsForElement(element) {
    if (!element) return false;

    if (element.nodeType === Node.ELEMENT_NODE) {
        if (element.className === "uiWebviewHighlight") {
            // 将span内的文本放回原位置,移除span并合并相邻文本节点
            const textNode = document.createTextNode(element.textContent);
            element.parentNode.replaceChild(textNode, element);
            textNode.parentNode.normalize();
            return true;
        } else {
            let normalized = false;
            Array.from(element.childNodes).forEach(child => {
                if (uiWebview_RemoveAllHighlightsForElement(child)) {
                    normalized = true;
                }
            });
            if (normalized) element.normalize();
        }
    }
    return false;
}

// 移除所有高亮的入口函数
function uiWebview_RemoveAllHighlights() {
    uiWebview_SearchResultCount = 0;
    uiWebview_RemoveAllHighlightsForElement(document.body);
}

// 滚动到指定索引的搜索结果
function uiWebview_ScrollTo(idx) {
    const targetId = `SEARCH WORD${uiWebview_SearchResultCount - idx}`;
    const targetElement = document.getElementById(targetId);
    if (targetElement) {
        targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }
}

关键修改点说明

  1. 阿拉伯语断词修复:用Range.surroundContents()替代直接拆分文本节点,完美保留阿拉伯语的连字结构,不会出现单词断开的情况。
  2. 变音符号不敏感搜索:removeTashkeel函数统一处理原文本和搜索词的变音符号,确保匹配不受变音符号影响。
  3. DOM遍历优化:把反向遍历改成正向遍历(Array.from().forEach),避免因修改DOM导致的子节点索引错乱问题。
  4. 滚动体验升级:添加了平滑滚动和居中对齐的选项,用户体验更好。

内容的提问来源于stack exchange,提问作者enigrify

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:15:29