Javascript replace() регулярное выражение слишком жадное

Я пытаюсь очистить поле ввода HTML. Я хочу сохранить некоторые из тегов, но не все, поэтому я не могу просто использовать .text() при чтении значения элемента. У меня возникли некоторые проблемы с регулярным выражением в JavaScript в Safari. Вот фрагмент кода (я скопировал этот фрагмент регулярного выражения из другого ответа SO потока):

aString.replace (/<\s*a.*href=\"(.*?)\".*>(.*?)<\/a>/gi, '$2 (Link->$1)' ) ;

Вот пример ввода, который терпит неудачу:

<a href="http://blar.pirates.net/black/ship.html">Go here please.</a></p><p class="p1"><a href="http://blar.pirates.net/black/ship.html">http://blar.pirates.net/black/ship.html</a></p>

Идея состоит в том, что href будет извлечен и выведен в виде простого текста рядом с текстом, который был бы связан. Таким образом, приведенный выше вывод в конечном итоге должен выглядеть примерно так:

Go here please (Link->http://blar.pirates.net/black/ship.html)
http://blar.pirates.net/black/ship.html (Link->http://blar.pirates.net/black/ship.html)

Тем не менее, регулярное выражение захватывает вплоть до второго </a> тег на первом совпадении, поэтому я теряю первую строку вывода. (На самом деле, он будет захватывать как можно дальше вниз по списку, пока элементы привязки смежны.) Входные данные представляют собой одну длинную строку, а не разбиваются на строки с CR/LF или чем-либо еще.

Я пытался использовать не жадный флаг, как это (обратите внимание на 2-й знак вопроса):

/<\s*a.*href=\"(.*?)\".*?>(.*?)<\/a>/ig

Но это, похоже, ничего не изменило (по крайней мере, в нескольких тестерах / парсерах, которые я пробовал, один из которых здесь: http://refiddle.com/). Также попробовал /U флаг, но это не помогло (или эти парсеры не узнали его).

Какие-либо предложения?

3 ответа

Решение

В шаблоне есть несколько ошибок и возможных улучшений:

/<
\s*    #  not needed (browsers don't recognize "< a" as an "a" tag)

a      #  if you want to avoid a confusion between an "a" tag and the start
       # of an "abbr" tag, you can add a word boundary or better, a "\s+" since
       # there is at least one white character after.

.      #  The dot match all except newlines, if you have an "a" tag on several
       # lines, your pattern will fail. Since Javascript doesn't have the 
       # "singleline" or "dotall" mode, you must replace it with `[\s\S]` that
       # can match all characters (all that is a space + all that is not a space)

*      #  Quantifiers are greedy by default. ".*" will match all until the end of
       # the line, "[\s\S]*" will match all until the end of the string!
       # This will cause to the regex engine a lot of backtracking until the last
       # "href" will be found (and it is not always the one you want)

href=  # You can add a word boundary before the "h" and put optional spaces around
       # the equal sign to make your pattern more "waterproof": \bhref\s*=\s*

\"     #  Don't need to be escaped, as Markasoftware notices it, an attribute
       # value is not always between double quotes. You can have single quotes or
       # no quotes at all. (1)
(.*?)
\"     # same thing
.*     # same thing: match all until the last >
>(.*?)<\/a>/gi

(1) -> О кавычках и значении атрибута href:

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

\bhref\s*=\s*(["']?)([^"'\s>]*)\1

подробности:

\bhref\s*=\s*
(["']?)     # capture group 1: can contain a single, a double quote or nothing 
([^"'\s>]*) # capture group 2: all that is not a quote to stop before the possible
            # closing quote, a space (urls don't have spaces, however javascript
            # code can contain spaces) or a ">" to stop at the first space or
            # before the end of the tag if quotes are not used. 
\1          # backreference to the capture group 1

Обратите внимание, что вы используете этот подшаблон, добавляете группу захвата, а содержимое между a Теги теперь в группе захвата 3. Подумайте, чтобы изменить в вашей строке замены $2 в $3,

В общем, вы можете написать свой шаблон так:

aString.replace(/<a\s+[\s\S]*?\bhref\s*=\s*(["']?)([^"'\s>]*)\1[^>]*>([\s\S]*?)<\/a>/gi,
               '$3 (Link->$1)');

Использование

href="[^"]+"

вместо

href=\"(.*?)\"

в основном это будет захватывать любой персонаж, пока не встретит следующий "

Хотя, вероятно, было бы проще реализовать что-то вроде синтаксиса уценки, чтобы вам не пришлось беспокоиться об удалении неправильных тегов, просто удалите все и замените уценки их аналогами тегов html при отображении текста.

Например, на SO вы можете сделать ссылку, просто используя

[link text](http://linkurl.com)

и регулярное выражение для замены будет

var displayText = "This is just some text [and this is a link](http://example.com) and then more text";
var linkMarkdown = /\[([^\]]+)\]\(([^\)]+)\)/;
displayText.replace(linkMarkdown,'<a href="$2">$1</a>');

Или используйте уже созданную библиотеку, которая будет выполнять преобразования.

Спасибо всем за предложения; это мне очень помогло и у меня было много идей по его улучшению.

Но я думаю, что нашел причину неудачи оригинального регулярного выражения. Ответ Касимира касается этого, но я не понял его, пока не наткнулся на это исправление.

Я искал не в том месте для проблемы, здесь:

/<\s*a.*href=\"(.*?)\".*>(.*?)<\/a>/gi
                       ^

Я смог исправить исходный запрос, вставив знак вопроса после a.*hre площадь, как это:

/<\s*a.*?href=\"(.*?)\".*>(.*?)<\/a>/gi
        ^

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

- С

Другие вопросы по тегам