2013-12-06 3 views
0

Я пытаюсь добавить <abbr> теги к аббревиатурам, найденным на веб-сайте. Я запускаю это как расширение Chrome, но я уверен, что проблема в самом javascript и не имеет большого отношения к материалам Chrome (я буду включать источник на всякий случай).Попытка заменить HTML путем клонирования узлов, но получить странные результаты

Я должен упомянуть, что я использую много кода от 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)", 

то matchText() функция от ранее связанного artile

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 { "Название": "Сабра" Сокращения, "вариант": "0.1", " manifest_version" : 2, "описание": "добавляет всплывающие подсказки с определением к наиболее часто используемым акронимы в бейсбол.",

"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> страницы, содержащей статью с несколькими используемыми акронимами. Многие из них должны быть подняты, но нет. Точные совпадения также подбираются, но не все время. Также кажется, что проблема заключается в одиночных/2-буквенных акронимах (таких как IP в таблице). Регулярное выражение довольно простое, я думал, что \b сделает трюк.

Спасибо!

ответ

0

Были проблемы с кодом (или, может быть, немного больше).

  1. Chrome обнаруживает слова-границы в своем роде, так \b не работает, как ожидалось (например, . считается частью слова).

  2. Вы использовали глобальный модификатор, который возвращал индексы всех найденных совпадений. Но при обработке каждого матча вы изменили содержимое child.data, поэтому индексы, которые ссылались на оригинал child.data, оказались бесполезными. Эта проблема возникла бы только в случае, если в одном TextNode было более 1 совпадения. (Обратите внимание, что после того, как эта ошибка привела к возникновению исключения, выполнение было прервано, поэтому никакие дополнительные TextNodes не обрабатывались.)

  3. Акроним был найден (и заменен) в порядке появления в списке сокращенных аббревиатур. Это может привести к случаям, когда только подстрока аббревиатуры будет признана в качестве другого аббревиатура и неправильно заменена. Например. если ERA был заархивирован до ERA+, все ERA+ вхождения в DOM будут заменены на <abbr ...>ERA</abbr>+ и не будут признаны в качестве ERA+ в дальнейшем.

  4. Как и вышеприведенная проблема, подстрока уже обработанного акронима, впоследствии может быть признана в качестве другого акронима и заменена по существу. Например. если ERA+ был искали до ERA произойдет следующее:
    ERA+
    -><abbr (title_for_ERA+)>ERA+</abbr>
    -><abbr (title_for_ERA+)><abbr (title_for_ERA)>ERA</abbr>+</abbr>

  5. Вашей один буква "сокращенный" также соответствует символам, они не должны (например, E в E-mail , G в Paul G. и т. Д.).


(Среди многих возможных способов) Я выбрал для решения вышеуказанных проблем, как это:

Для (1):
Вместо использования \b...\b я (^|[^A-Za-z0-9_])(...)([^A-Za-z0-9_]|$).
Это будет искать один символ, который не является символом слова до и после нашего аббревиатура под поиском (или для начала строки (^) или конца ($) соответственно). Поскольку совпадающие символы (если они есть) до и после фактического совпадения аббревиатуры должны быть возвращены в обычные текстовые узлы, 3 обратные ссылки создаются и обрабатываются соответствующим образом в обратном вызове replace (см. Код ниже).

Для (2):
Я удалил глобальный модификатор и сопоставил одно событие за раз.
Это также потребовало небольшой модификации, чтобы затем новый поиск TextNode, созданный с помощью части child.data после текущего совпадения.

В (3):
Перед началом операций поиска и замены Я заказал массив аббревиатур путем уменьшения длины, так что более длинные акронимы были поиск (и заменить) перед сортировщика аббревиатур (которые можно может быть подстроку бывший). Например. ERA+ всегда заменяется до ERA, IP/GS всегда заменяется до IP и т.д.
(Обратите внимание, что это решает проблему (3), но мы по-прежнему приходится иметь дело с (4).)

Для (4):
Каждый раз, когда я создаю новый узел <abbr>, я добавляю к нему класс. Позже, когда я сталкиваюсь с элементом с этим специальным классом, я пропускаю его (поскольку я не хочу, чтобы какие-либо замены выполнялись в подстроке уже согласованного акронима).

Для (5):
Ну, я хороший, но я не Jon Skeet :)
Существует не так много вы можете сделать об этом, если вы не хотите, чтобы принести на некоторые AI, но я полагаю, это не большая проблема (т. е. вы можете жить с ней).

(Как уже упоминалось выше решения не являются ни единственными, доступными и, вероятно, и не оптимальны.)


Это говорит, вот моя версия кода (еще с несколькими 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.