尝试通过克隆节点来替换HTML,但会得到奇怪的结果

时间:2013-12-06 20:18:49

标签: javascript regex google-chrome-extension treenode

我正在尝试将<abbr>标签添加到网站上找到的首字母缩写词中。我将其作为Chrome扩展程序运行,但我相当确定问题出在javascript本身内,并且与Chrome的内容没有太大关系(我将包括源代码以防万一)

我应该提一下,我正在使用this link中的大量代码,这些代码是在另一个答案中提出的。不幸的是,我得到了意想不到的结果,因为我的最终目标与那里讨论的内容略有不同。

首先,我有一个首字母缩略词数组(这里缩写,我在JSFiddle中包含了整个内容)

"ITPHR": "Inside-the-park home run:hits on which the batter successfully touched all four bases, without the contribution of a fielding error or the ball going outside the ball park.",
"pNERD": "Pitcher&#39;s NERD: expected aesthetic pleasure of watching an individual pitcher",
"RISP": "Runner In Scoring Position: a breakdown of the batter&#39;s batting average with runners in scoring position, which include runners at second and third bases.",
"SBA/ATT": "Stolen base attempts: total number of times the player has attempted to steal a base (SB+CS)",

然后来自先前链接的artile

matchText()函数
var matchText = function (node, regex, callback, excludeElements) {
    excludeElements || (excludeElements = ['script', 'style', 'iframe', 'canvas']);
    var child = node.firstChild;
    do {
        switch (child.nodeType) {
            case 1:
                if (excludeElements.indexOf(child.tagName.toLowerCase()) > -1) {
                    continue;
                }
                matchText(child, regex, callback, excludeElements);
                break;
            case 3:
                child.data.replace(regex, function (all) {
                    var args = [].slice.call(arguments),
                        offset = args[args.length - 2],
                        newTextNode = child.splitText(offset);
                    newTextNode.data = newTextNode.data.substr(all.length);
                    callback.apply(window, [child].concat(args));
                    child = newTextNode;
                });
             break;
        }
    } while (child = child.nextSibling);
    return node;
}

最后我的代码循环使用首字母缩略词数组并逐个搜索所有条款(这可能不是最佳的做事方式,如果你有更好的想法,请告诉我)

var abbrList = Object.keys(acronyms);
for (var i = 0; i < abbrList.length; i++) {
    var abbrev = abbrList[i];
    abbrevSearch = abbrev.replace('%', '\\%').replace('+', '\\+').replace('/', '\\/');
    console.log("Looking for " + abbrev);
    matchText(document.body.getElementsByTagName("*"), new RegExp("\\b" + abbrevSearch + "\\b", "g"), function (node, match, offset) {
        var span = document.createElement("abbr");
        // span.className = "sabrabbr"; // If someone decides to style them
        span.setAttribute("title", acronyms[abbrev].replace('&#39;', '\''));
        span.textContent = match;
        node.parentNode.insertBefore(span, node.nextSibling);
    });
}

此处提供的是特定于Chrome的文件:

的manifest.json     {       “name”:“SABR缩略语”,       “版本”:“0.1”,       “manifest_version”:2,       “description”:“为棒球中常用的首字母缩略词添加工具提示。”,

  "icons": {
    "16" : "images/16.png",
    "48" : "images/48.png",
    "128" : "images/128.png"
  },

  "permissions": [
    "activeTab"
  ],

  "browser_action": {
    "default_icon": "images/16.png",
    "default_title": "SABR Acronyms"
  },

  "content_scripts": [
  {
    "matches": ["http://*/*"],
    "js": ["content.js","jquery.min.js"],
    "css": ["sabr.css"]
  }
  ],

  "web_accessible_resources": ["content.js", "sabr.js", "sabr.css","jquery.min.js","jquery-2.0.3.min.map"]
}

content.js

var s = document.createElement('script');
s.src = chrome.extension.getURL('sabr.js');
(document.head||document.documentElement).appendChild(s);
s.onload = function() {
    s.parentNode.removeChild(s);
};

我在JSFiddle上传了所有内容,因为这是查看代码实际操作的最简单方法。我复制了一个页面的<body>...</body>,其中包含一篇文章,其中包含一些正在使用的首字母缩略词。应该拿起很多但不是。确切的匹配也会被选中,但不是所有的时间。似乎还存在单/双字母缩写词(例如表中的IP)的问题。正则表达式非常简单,我认为\b可以解决问题。

谢谢!

1 个答案:

答案 0 :(得分:0)

您的代码存在一些问题(或者可能更多)。

  1. Chrome以自己的方式检测字边界,因此\b无法正常工作(例如,.被视为单词的一部分。)

  2. 您使用的是global修饰符,它返回了找到的所有匹配项的索引。但是在处理每个匹配时,您修改了child.data的内容,因此引用原始child.data的索引变得无用。只有在单个TextNode中有多个匹配项时,才会出现此问题。 (请注意,一旦此错误导致异常被引发,执行就会中止,因此不再处理其他TextNode。)

  3. 首字母缩略词按首字母缩略词列表中的出现顺序搜索(并替换)。这可能导致案例,其中只有首字母缩略词的子字符串将被识别为另一个首字母缩略词并被错误地替换。例如。如果在ERA之前预订了ERA+,则DOM中的所有ERA+次出现都会被<abbr ...>ERA</abbr>+替换,之后将不会被识别为ERA+次出现。

  4. 与上述问题类似,已经处理过的首字母缩略词的子字符串可以随后被识别为另一个首字母缩略词并被相应地替换。例如。如果在ERA+之前搜索ERA,则会发生以下情况:
    ERA+
    - &GT; <abbr (title_for_ERA+)>ERA+</abbr>
    - &GT; <abbr (title_for_ERA+)><abbr (title_for_ERA)>ERA</abbr>+</abbr>

  5. 您的单字母“首字母缩略词”也会匹配不应包含的字符(例如E中的E-mailG中的Paul G.等。


  6. (在许多可能的方式中)我选择解决上述问题:

    对于(1):
    我使用\b...\b而不是(^|[^A-Za-z0-9_])(...)([^A-Za-z0-9_]|$) 这将查找在搜索下我们的首字母缩写词之前和之后不是单词字符的一个字符(或分别为字符串开头(^)或结束($))。由于实际首字母缩略词匹配之前和之后的匹配字符(如果有)需要放回常规TextNodes,因此在replace回调中会创建并处理3个反向引用(请参阅下面的代码)。

    对于(2):
    我删除了全局修饰符并一次匹配一个匹配项 这也需要稍作修改,以便随后搜索在当前匹配后使用child.data部分创建的新TextNode。

    对于(3):
    在开始搜索和替换操作之前,我通过减少长度来命令缩略词数组,因此在分拣机首字母缩略词(可能是前者的子串)之前搜索(和替换)更长的首字母缩略词。例如。在ERA+之前始终会替换ERAIP/GS之前始终会替换IP。 (注意,这解决了问题(3),但我们仍然需要处理(4)。)

    对于(4):
    每次我创建一个新的<abbr>节点时,我都会向它添加一个类。稍后,当我遇到具有该特殊类的元素时,我跳过它(因为我不希望在已经匹配的首字母缩略词的子字符串中发生任何替换)。

    对于(5):
    好吧,我很好,但我不是 Jon Skeet :) 除非你想引入一些人工智能,否则你无能为力,但我想这也不是什么问题(也就是你可以忍受它)。

    (如上所述,上述解决方案既不是唯一可用的,也可能也不是最佳解决方案。)


    那就是说,这是我的代码版本(带有更多的miror(大部分风格)变化):

    var matchText = function (node, regex, callback, excludeElements) {
        excludeElements
                || (excludeElements = ['script', 'style', 'iframe', 'canvas']);
        var child = node.firstChild;
        if (!child) {
            return;
        }
    
        do {
            switch (child.nodeType) {
                case 1:
                    if ((child.className === 'sabrabbr') ||
                            (excludeElements.indexOf(
                                    child.tagName.toLowerCase()) > -1)) {
                        continue;
                    }
                    matchText(child, regex, callback, excludeElements);
                    break;
                case 3:
                    child.data.replace(regex, function (fullMatch, g1, g2, g3, idx,
                                                        original) {
                        var offset = idx + g1.length;
                        newTextNode = child.splitText(offset);
                        newTextNode.data = newTextNode.data.substr(g2.length);
                        callback.apply(window, [child, g2]);
                        child = child.nextSibling;
                    });
                    break;
            }
        } while (child = child.nextSibling);
        return node;
    }
    
    var abbrList = Object.keys(acronyms).sort(function(a, b) {
        return b.length - a.length;
    });
    for (var i = 0; i < abbrList.length; i++) {
        var abbrev = abbrList[i];
        abbrevSearch = abbrev.replace('%', '\\%').replace('+', '\\+').replace('/', '\\/');
        console.log("Looking for " + abbrev);
        var regex = new RegExp("(^|[^A-Za-z0-9_])(" + abbrevSearch
                               + ")([^A-Za-z0-9_]|$)", "");
        matchText(document.body, regex, function (node, match) {
            var span = document.createElement("abbr");
            span.className = "sabrabbr";
            span.title = acronyms[abbrev].replace('&#39;', '\'');
            span.textContent = match;
            node.parentNode.insertBefore(span, node.nextSibling);
        });
    }
    

    对于那么成功的少数人来说,还有 short demo