Соответствует строке, только если она отсутствует в тегах <script>или <a> - PullRequest
4 голосов
/ 11 января 2011

Я работаю над плагином браузера, который заменяет все экземпляры "someString" (как определено сложным регулярным выражением) на <a href="http://domain.com/$1">$1</a>.Это обычно работает нормально, просто делая глобальную замену innerHTML тела.Однако он разрывает страницу, когда находит (и заменяет) «someString» внутри тегов <script> (то есть как переменную JS или другую ссылку JS).Он также ломается, если «someString» уже является частью якоря.

Так что в основном я хочу сделать глобальную замену для всех экземпляров «someString», если только он не попадает в набор тегов <script></script> или <a></a>.

По сути, сейчас у меня есть:

var body = document.getElementsByTagName('body')[0].innerHTML;
body = body.replace(/(someString)/gi, '<a href="http://domain.com/$1">$1</a>');
document.getElementsByTagName('body')[0].innerHTML = body;

Но, очевидно, этого недостаточно.Я боролся уже пару часов и читаю все ответы здесь (включая многие непреклонные, которые настаивают на том, что регулярное выражение не должно использоваться с HTML), поэтому я открыт для предложений о том, как это сделать.Я бы предпочел использовать прямой JS, но при необходимости могу использовать jQuery.

Редактировать - Пример HTML :

<body>
  someString
  <script type="text/javascript">
  var someString = 'blah';
  console.log(someString);
  </script>
  <a href="someString.html">someString</a>
</body>

В этом случае только самый первый экземпляр"someString" следует заменить.

Ответы [ 5 ]

2 голосов
/ 14 января 2011

Попробуйте и посмотрите, соответствует ли он вашим потребностям (протестировано в IE 8 и Chrome).

<script src="jquery-1.4.4.js" type="text/javascript"></script>
<script>
  var pattern = /(someString)/gi;
  var replacement = "<a href=\"http://domain.com/$1\">$1</a>";

  $(function() {
    $("body :not(a,script)")
      .contents()
      .filter(function() { 
        return this.nodeType == 3 && this.nodeValue.search(pattern) != -1;
      })
      .each(function() {
        var span = document.createElement("span");
        span.innerHTML = "&nbsp;" + $.trim(this.nodeValue.replace(pattern, replacement));
        this.parentNode.insertBefore(span, this);
        this.parentNode.removeChild(this);
      });
  });
</script>

Код использует jQuery для поиска всех текстовых узлов в пределах <body> документа, которые не находятся в блоках <anchor> или <script> и содержат шаблон поиска.Как только они найдены, вставляется диапазон, содержащий измененное содержимое целевого узла, и старый текстовый узел удаляется.

Единственная проблема, с которой я столкнулся, заключалась в том, что IE 8 обрабатывает текстовые узлы, содержащие только пробелы, иначе, чем Chrome, поэтому иногда замена теряет начальный пробел, поэтому вставка неразрывного пробела перед текстом, содержащим регулярное выражениезамена.

2 голосов
/ 11 января 2011

Ну, вы можете использовать XPath с Mozilla (если вы пишете плагин для FireFox).Вызов document.evaluate.Или вы можете использовать библиотеку XPath для этого (есть несколько) ...

var matches = document.evaluate(
    '//*[not(name() = "a") and not(name() = "script") and contains(., "string")]',
    document,
    null,
    XPathResult.UNORDERED_NODE_ITERATOR_TYPE
    null
);

Затем замените, используя функцию обратного вызова:

var callback = function(node) {
    var text = node.nodeValue;
    text = text.replace(/(someString)/gi, '<a href="http://domain.com/$1">$1</a>');
    var div = document.createElement('div');
    div.innerHTML = text;
    for (var i = 0, l = div.childNodes.length; i < l; i++) {
        node.parentNode.insertBefore(div.childNodes[i], node);
    }
    node.parentNode.removeChild(node);
};
var nodes = [];
//cache the tree since we want to modify it as we iterate
var node = matches.iterateNext();
while (node) {
    nodes.push(node);
    node = matches.iterateNext();
}
for (var key = 0, length = nodes.length; key < length; key++) {
    node = nodes[key];
    // Check for a Text node
    if (node.nodeType == Node.TEXT_NODE) {
        callback(node);
    } else {
        for (var i = 0, l = node.childNodes.length; i < l; i++) {
            var child = node.childNodes[i];
            if (child.nodeType == Node.TEXT_NODE) {
                callback(child);
            }
        }
    }
}
1 голос
/ 12 января 2011

Вы можете попробовать следующее:

/(someString)(?![^<]*?(<\/a>|<\/script>))/

Я не проверял каждый сценарий, но в основном он использует отрицательный прогноз, чтобы найти следующую открывающую скобку после someString , и если эта скобка является частью тега привязки или закрытия скрипта, она не совпадает.

Ваш пример работает в этой скрипке , хотя, конечно, это не такне охватывает все возможности.В тех случаях, когда innerHTML в вашем <a></a> содержит теги (например, <b> или <span>), или если код в ваших тегах сценария генерирует html (содержит строки с тегами в нем), вам потребуется нечто более сложное.

1 голос
/ 11 января 2011

Еще одна идея: если вы используете jQuery, вы можете использовать псевдоселектор: contains.

$('*:contains(someString)').each(function(i)
{
    var markup = $(this).html();
    // modify markup to insert anchor tag
    $(this).html(markup)
});

Это будет захватывать любой элемент DOM, который содержит 'someString' в своем тексте. Я не думаю, что он пройдет через <script> теги или около того, вы должны быть хорошими.

1 голос
/ 11 января 2011

Я знаю, что вы не хотите это слышать, но это не похоже на работу для регулярных выражений.Регулярные выражения не очень хорошо подходят для отрицательных совпадений, прежде чем становятся сложными и нечитаемыми.

Возможно, это регулярное выражение может быть достаточно близко, хотя:

/>[^<]*(someString)[^<]*</

Он захватывает любой экземпляр someString, который находится между a> и <. </p>

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...