2016-11-20 10 views
3

Я разрабатываю расширение Google Chrome, которое позволяет автоматически применять правило CSS выделения к выбранному вами слову.Выделите слово текста на странице с помощью .replace()

У меня есть следующий код

var elements = document.getElementsByTagName('*'); 

for (var i=0; i<elements.length; i++) { 
    var element = elements[i]; 

    for (var j=0; j<element.childNodes.length; j++) { 
     var node = element.childNodes[j]; 

     if(node.nodeType === 3) { 
      var text = node.nodeValue; 

      var fetchedText = text.match(/teste/gi); 

      if(fetchedText) { 
       var replacedText = element.innerHTML.replace(/(teste)/gi, "<span style=\"background-color: yellow\">$1</span>"); 

       if (replacedText !== text) { 
        element.innerHTML = replacedText; 
       } 
      } 
     } 
    } 
} 

Который ломает и замораживает мою вкладку Chrome. Однако, если я переключаюсь с element.innerHTML = replacedText; на element.innerHTML = "text";, это работает.

Я не могу найти что-то не так со следующим кодом.

+0

Вы вошли в систему, что означало 'replaceText' для значения? –

+0

@ScottMarcus, когда я записываю 'replaceText', он показывает правильное значение, которое представляет собой, например,' teste '. Однако, если я использую это на 'element.innerHTML', он сбрасывает мою вкладку. – rafaelcpalmeida

+0

Вы уверены, что в консоли отображается последовательность '' '' escape-последовательности? Вы пытались изменить строку: '' $ 1 "'? –

ответ

0

Погрешности я испытывал был из-за рекурсивный цикл, так как, например, я искал ключевое слово teste и я вставлял новый элемент с содержимым <span style=\"background-color: #ffff00\">teste</span>, который заставит скрипт попытаться заменить новое ключевое слово teste еще раз и так далее.

Я пришел с этой функцией:

function applyReplacementRule(node) { 
    // Ignore any node whose tag is banned 
    if (!node || $.inArray(node.tagName, hwBannedTags) !== -1) { return; } 

    try { 
     $(node).contents().each(function (i, v) { 
      // Ignore any child node that has been replaced already or doesn't contain text 
      if (v.isReplaced || v.nodeType !== Node.TEXT_NODE) { return; } 

      // Apply each replacement in order 
      hwReplacements.then(function (replacements) { 
       replacements.words.forEach(function (replacement) { 
        //if(!replacement.active) return; 
        var matchedText = v.textContent.match(new RegExp(replacement, "i")); 

        if (matchedText) { 
         // Use `` instead of '' or "" if you want to use ${variable} inside a string 
         // For more information visit https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals 
         var replacedText = node.innerHTML.replace(new RegExp(`(${replacement})`, "i"), "<span style=\"background-color: #ffff00\">$1</span>"); 

         node.innerHTML = replacedText; 
        } 
       }); 
      }).catch(function (reason) { 
       console.log("Handle rejected promise (" + reason + ") here."); 
      }); 

      v.isReplaced = true; 
     }); 
    } catch (err) { 
     // Basically this means that an iframe had a cross-domain source 
     if (err.name !== "SecurityError") 
     { throw err; } 
    } 
} 

Где изменить свойство узла и «сказать», что я уже изменить этот узел, так что я не в конечном итоге на рекурсивном бесконечный цикл снова.

P.S. Как вы видите, это решение использует jQuery. Я попытаюсь переписать это, чтобы использовать только Vanilla JS.

+0

Ваше решение по-прежнему использует RegExp для изменения '.innerHTML' родительского элемента. В результате это все равно нарушит любой HTML, который содержит слово, которое вы заменяете, когда текстовый узел также содержит это слово. Другими словами, хотя он не делает замену, если замена не произойдет в реальном тексте, это не помешает замене также изменить HTML (например, в 'src =" yourWord "или' href = "http: // foo.com/yourWord/bar.html "'). – Makyen

+0

Просто комментарий, не предназначенный для критики: вы используете две строки комментариев, чтобы объяснить использование шаблона. Хотя приятно объяснить это, в этой ситуации не имеет смысла использовать его, если вы могли бы заменить его на ''(' + replacement + ')''. Использование прямой конкатенации строк не заставит вас почувствовать, что вам нужны две строки комментариев, чтобы объяснить и не ограничили бы ваш код Chrome> = ver. 41. – Makyen

+0

FYI: Вы в настоящее время перебираете список слов, каждый из которых вы заменяете. Вы используете два разных RegExp, когда вы можете использовать только один (тест на существование не волнует наличие слова в группе захвата). Было бы намного эффективнее предварительно создать единый RegExp, который включает в себя все слова в массиве 'words'. Это приведет к выполнению только одного слова .replace() 'для всех слов. Это позволит сэкономить немало времени в вашем внутреннем цикле. [Этот ответ] (http://stackoverflow.com/a/40576258/3773011) имеет пример этого. – Makyen

3

Сначала вы тестируете узлы #text, чтобы узнать, содержит ли текст слово, которое вы пытаетесь выделить, но затем выполняет замену на родительском элементе .innerHTML. Есть пара проблем с этим.

  • Бесконечные замены: При изменении .innerHTML родительского элемента вы измените childNodes массив. Вы делаете это так, чтобы добавить узел в массив, содержащий текст, который нужно заменить. Таким образом, при продолжении сканирования массива childNodes вы всегда найдете (новый) узел, содержащий текст, который хотите заменить. Итак, вы снова его замените, создав еще один узел с более высоким индексом в массиве childNodes. Повторите бесконечно.
  • Использование RegExp для замены текста в .innerHTML. Хотя вы уже протестировали, чтобы текст, который вы хотите заменить, фактически содержался в текстовом узле, это не мешает вашему RegExp от также заменять любые соответствующие слова в фактическом HTML-элементе элемента (например, в src="yourWord", href="http://foo.com/yourWord/bar.html", или если попытки выделить такие слова, как style, color, background, span, id, height, width, button, form, input и т.д.).
  • Вы не проверяете, хотите ли вы изменить текст в <script> или <style> тегах.
  • Вы проверяете, что вы изменяете только текстовые узлы (т. Е. Вы проверяете на node.nodeType === 3). Если вы не проверяли для этого вам также будет иметь следующие возможные проблемы, связанные с использованием .innerHTML для изменения HTML:
    • Вы могли бы в конечном итоге изменить атрибуты или фактические HTML-теги, в зависимости от того, что вы меняетесь с .replace(). Это может полностью нарушить макет страницы и функциональность.
    • При изменении .innerHTML DOM для этой части страницы полностью воссоздан. Это означает, что элементы, в то время как новые элементы могут быть одного типа с одинаковыми атрибутами, любые прослушиватели событий, которые были прикреплены к старым элементам, не будут привязаны к новым элементам. Это может значительно нарушить функциональность страницы.
    • Неоднократно изменяющиеся большие части DOM могут быть довольно сложными для повторной обработки страницы. В зависимости от того, как вы это сделаете, вы можете столкнуться с серьезными проблемами, связанными с восприятием пользователями.

Таким образом, если вы собираетесь использовать RegExp, чтобы заменить текст, необходимо выполнить операцию только на содержание #text узла, а не на .innerHTML родительского узла. Поскольку вы хотите создать дополнительные элементы HTML (например, новые элементы <span style="">, с дочерними узлами #text), есть некоторые сложности.

Невозможно присвоить HTML текст в текстовый узел для создания новых HTML-узлов:

Там нет никакого способа, чтобы назначить новый HTML непосредственно в текстовый узел и он оценивается как HTML, создание новых узлов. Присвоение объекту текстового узла .innerHTML создаст такое свойство в объекте (как и на любом объекте), но не изменит текст, отображаемый на экране (то есть фактическое значение узла #text). Таким образом, он не выполнит то, что вы хотите сделать: он не будет создавать никаких новых дочерних элементов HTML родительского узла.

Способ сделать это, который имеет наименьшее влияние на DOM страницы (то есть наименее вероятно, чтобы сломать существующий JavaScript на странице), заключается в создании <span> для включения новых текстовых узлов, которые вы создаете (текст, который был в узел #text, который не находится в вашем цветном <span>) вместе с потенциально многочисленными <span> элементами, которые вы создаете. Это приведет к замене единственного узла #text одним элементом <span>. Хотя это создаст дополнительные потомки, оно оставит число дочерних элементов в родительском элементе неизменным. Таким образом, любой JavaScript, который полагался на это, не будет затронут. Учитывая, что мы меняем DOM, нет возможности не разрывать другой JavaScript, но это должно свести к минимуму эту возможность.

Некоторые примеры того, как вы можете это сделать: См this answer (заменяет список слов с этими словами в кнопках) и this answer (места весь текст в <p> элементы которых разделены пробелами в кнопки) для полных расширений, которые выполняют регулярные выражения замените его новым HTML. См. this answer, который делает в основном то же самое, но делает ссылку (имеет другую реализацию, которая пересекает DOM с помощью TreeWalker, чтобы найти узлы #text вместо NodeIterator, как используется в двух других примерах).

Вот код, который будет выполнять замену, которую вы желающая на каждом текстовой узле в document.body и создать новый HTML, необходимый, чтобы иметь style быть различными в части текста:

Там это другие способы сделать это. Тем не менее, они будут генерировать более значительные изменения в структуре дочерних элементов для этого конкретного элемента (например, несколько дополнительных узлов родительского элемента). У этого есть более высокий потенциал для взлома любого JavaScript уже на странице, которая опирается на текущую структуру страницы. Фактически, любое изменение, подобное этому, может нарушить текущий JavaScript.

Код в этом ответе был изменен из кода в this other answer of mine

+0

На самом деле я не получил этот тип ошибки, потому что я изменял элемент, содержащий этот текстовый узел. Он работал нормально, если я заменил содержимое, которое я хотел, для чего-либо еще, что слово, которое я искал – rafaelcpalmeida

+0

@rafaelcpalmeida, Да, моя ошибка в описании одной части проблемы (сделаны предположения, как обычно, плохой поступок). Я обновил ответ с исправленным описанием проблем (не меняя решение). – Makyen