Как заменить простые URL ссылками?

380

Я использую функцию ниже, чтобы сопоставлять URL-адреса внутри заданного текста и заменять их на ссылки HTML. Регулярное выражение работает отлично, но в настоящее время я заменяю только первое совпадение.

Как я могу заменить весь URL? Я предполагаю, что я должен использовать команду exec, но я действительно не понял, как это сделать.

function replaceURLWithHTMLLinks(text) {
    var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/i;
    return text.replace(exp,"<a href='$1'>$1</a>"); 
}
Теги:

20 ответов

259
Лучший ответ

Во-первых, перетащить собственное регулярное выражение для анализа URL-адресов - ужасная идея. Вы должны себе представить, что это довольно распространенная проблема, которую кто-то написал, отладил и протестировал библиотеку для нее, согласно RFC. URI являются сложными - проверьте код для анализа URL в Node.js и на странице Википедии схемы URI.

Есть тонны крайних случаев, когда дело доходит до разбора URL-адресов: международные доменные имена, фактический (.museum) против несуществующего (< .etc) TLD, странная пунктуация, включая круглые скобки, пунктуацию в конце URL, имена хостов IPV6 и т.д.

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

Библиотеки, которые я быстро дисквалифицировал для этой задачи:

Если вы настаиваете на регулярном выражении, наиболее полным является URL regexp из Component, хотя он будет ложно обнаруживать некоторые несуществующие два (TLD), просматривая его.

  • 3
    Жаль, что URL regexp from Component не прокомментировано, некоторые объяснения того, что он делает, было бы полезно. Autolinker.js прокомментирован очень хорошо и имеет тесты. Библиотека urlize.js ссылка на которую содержится в ответе Вебьорна Льюзы, также выглядит многообещающе и хорошо поддерживается, хотя в ней нет тестов.
  • 1
    Regex101.com автоматически «объясняет» регулярное выражение, но удачи в этом :) Я также быстро обнаружил случай сбоя с неверным TLD (та же ссылка).
Показать ещё 8 комментариев
279

Замена ссылок на ссылки (ответ на общую проблему)

Регулярное выражение в вопросе пропускает много крайних случаев. При обнаружении URL-адресов всегда лучше использовать специализированную библиотеку, которая обрабатывает международные доменные имена, новые TLD, такие как .museum, круглые скобки и другие знаки пунктуации внутри и в конце URL-адреса и многие другие случаи. См. Сообщение блога Джеффа Атвуда Проблема с URL-адресами для объяснения некоторых других проблем.

лучшее резюме библиотек соответствия URL находится в Dan Dascalescu Отвечать Изображение 4735
(по состоянию на февраль 2014 года)


"Сделать регулярное выражение заменять более одного соответствия" (ответ на конкретную проблему)

Добавьте "g" в конец регулярного выражения, чтобы включить глобальное сопоставление:

/ig;

Но это только фиксирует проблему в вопросе, где регулярное выражение заменяет только первое совпадение. Не используйте этот код.

128

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

function linkify(inputText) {
    var replacedText, replacePattern1, replacePattern2, replacePattern3;

    //URLs starting with http://, https://, or ftp://
    replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
    replacedText = inputText.replace(replacePattern1, '<a href="$1" target="_blank">$1</a>');

    //URLs starting with "www." (without // before it, or it'd re-link the ones done above).
    replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
    replacedText = replacedText.replace(replacePattern2, '$1<a href="http://$2" target="_blank">$2</a>');

    //Change email addresses to mailto:: links.
    replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
    replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');

    return replacedText;
}
  • 1
    Как отредактировать этот код, чтобы не наносить вред встроенным объектам и фреймам .. (встроенные объекты YouTube и фреймамам)
  • 5
    В коде есть ошибка, которая соответствует адресам электронной почты здесь. [a-zA-Z]{2,6} следует читать что-то вроде (?:[a-zA-Z]{2,6})+ , чтобы соответствовать более сложным доменным именам, например, email @ example. co.uk.
Показать ещё 12 комментариев
63

Сделал некоторые оптимизации для кода Travis Linkify() выше. Я также исправил ошибку, в которой адреса электронной почты с форматами типа субдомена не были бы сопоставлены (например, [email protected]).

Кроме того, я изменил реализацию для прототипа класса String, чтобы элементы могли быть сопоставлены следующим образом:

var text = '[email protected]';
text.linkify();

'http://stackoverflow.com/'.linkify();

В любом случае, здесь script:

if(!String.linkify) {
    String.prototype.linkify = function() {

        // http://, https://, ftp://
        var urlPattern = /\b(?:https?|ftp):\/\/[a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|]/gim;

        // www. sans http:// or https://
        var pseudoUrlPattern = /(^|[^\/])(www\.[\S]+(\b|$))/gim;

        // Email addresses
        var emailAddressPattern = /[\w.]+@[a-zA-Z_-]+?(?:\.[a-zA-Z]{2,6})+/gim;

        return this
            .replace(urlPattern, '<a href="$&">$&</a>')
            .replace(pseudoUrlPattern, '$1<a href="http://$2">$2</a>')
            .replace(emailAddressPattern, '<a href="mailto:$&">$&</a>');
    };
}
  • 0
    Лучшее на мой взгляд, так как функции Prototype делают вещи намного чище :)
  • 0
    кажется, он не работает с такими адресами электронной почты: [email protected] [email protected] и т. д.
Показать ещё 4 комментария
21

Спасибо, это было очень полезно. Я также хотел, чтобы что-то связало вещи, которые выглядели как URL-адрес - в качестве основного требования, это связало бы что-то вроде www.yahoo.com, даже если префикс протокола http://отсутствовал. Так что в принципе, если "www". присутствует, он свяжет его и предположим, что он http://. Я также хотел, чтобы электронные письма превращались в mailto: links. ПРИМЕР: www.yahoo.com будет преобразован на www.yahoo.com.

Здесь код, в который я попал (комбинация кода с этой страницы и других вещей, которые я нашел в Интернете, и другие вещи, которые я сделал сам):

function Linkify(inputText) {
    //URLs starting with http://, https://, or ftp://
    var replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
    var replacedText = inputText.replace(replacePattern1, '<a href="$1" target="_blank">$1</a>');

    //URLs starting with www. (without // before it, or it'd re-link the ones done above)
    var replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
    var replacedText = replacedText.replace(replacePattern2, '$1<a href="http://$2" target="_blank">$2</a>');

    //Change email addresses to mailto:: links
    var replacePattern3 = /(\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,6})/gim;
    var replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');

    return replacedText
}

Во второй замене часть (^ | [^/]) заменяет только www.whatever.com, если она уже не префикс // - чтобы избежать двойной ссылки, если URL-адрес уже был связан в первом заменить. Кроме того, возможно, что www.whatever.com может находиться в начале строки, что является первым "или" условием в этой части регулярного выражения.

Это может быть интегрировано как плагин jQuery как Jesse P, показанное выше, но я специально хотел, чтобы регулярная функция, которая не воздействовала на существующий элемент DOM, потому что я беру текст, который у меня есть, а затем добавляю его в DOM, и я хочу, чтобы текст был "привязан", прежде чем добавить его, поэтому я передаю текст через эту функцию. Отлично работает.

  • 1
    Существует проблема со вторым шаблоном, который сам по себе соответствует простому «www.domain.com». Проблема существует, когда в URL есть какой-то реферер, например: & location = http% 3A% 2F% 2Fwww.amazon.com% 2FNeil-Young% 2Fe% 2FB000APYJWA% 3Fqid% 3D1280679945% 26sr% 3D8-2-ent & tag = tra0c7 -20 & linkCode = ur2 & camp = 1789 & creative = 9325 - в этом случае ссылка автоматически связывается снова. Быстрое решение состоит в том, чтобы добавить символ «f» после отрицательного списка, который содержит «/». Таким образом, выражение: replacePattern2 = /(^|[^\/f])(www\.[\S]+(\b|$))/gim
  • 0
    Приведенный выше код провалит много тестов для крайних случаев. При обнаружении URL-адресов лучше полагаться на специализированную библиотеку. Вот почему
Показать ещё 1 комментарий
17

Идентификация URL-адресов сложна, поскольку они часто окружены знаками препинания и потому, что пользователи часто не используют полную форму URL-адреса. Многие функции JavaScript существуют для замены URL-адресов гиперссылками, но мне не удалось найти тот, который работает, а также фильтр urlize в веб-среде Django на основе Python. Поэтому я поместил функцию Django urlize в JavaScript:

https://github.com/ljosa/urlize.js

Пример:

urlize('Go to SO (stackoverflow.com) and ask. <grin>', 
       {nofollow: true, autoescape: true})
=> "Go to SO (<a href="http://stackoverflow.com" rel="nofollow">stackoverflow.com</a>) and ask. &lt;grin&gt;"

Второй аргумент, если true, вызывает вставку rel="nofollow". Третий аргумент, если true, ускользает от символов, имеющих особое значение в HTML. Смотрите файл README.

  • 0
    Также работает с источником HTML, таким как: www.web.com <a href = "https: // github. Com"> url </ a> некоторый текст
  • 0
    @Paulius: если вы установите для параметра django_compatible значение false, он немного лучше справится с этим вариантом использования.
Показать ещё 2 комментария
10

Я внес изменения в Roshambo String.linkify() в адрес emailAddressPattern, чтобы узнать адреса aaa.bbb. @ccc.ddd

if(!String.linkify) {
    String.prototype.linkify = function() {

        // http://, https://, ftp://
        var urlPattern = /\b(?:https?|ftp):\/\/[a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|]/gim;

        // www. sans http:// or https://
        var pseudoUrlPattern = /(^|[^\/])(www\.[\S]+(\b|$))/gim;

        // Email addresses *** here I've changed the expression ***
        var emailAddressPattern = /(([a-zA-Z0-9_\-\.]+)@[a-zA-Z_]+?(?:\.[a-zA-Z]{2,6}))+/gim;

        return this
            .replace(urlPattern, '<a target="_blank" href="$&">$&</a>')
            .replace(pseudoUrlPattern, '$1<a target="_blank" href="http://$2">$2</a>')
            .replace(emailAddressPattern, '<a target="_blank" href="mailto:$1">$1</a>');
    };
}
  • 0
    Приведенный выше код провалит много тестов для крайних случаев. При обнаружении URL-адресов лучше полагаться на специализированную библиотеку. Вот почему
7
5

Это решение работает, как и многие другие, и фактически использует одно и то же регулярное выражение, как и одно из них, однако вместо возврата строки HTML это вернет фрагмент документа, содержащий элемент A и любые применимые текстовые узлы.

 function make_link(string) {
    var words = string.split(' '),
        ret = document.createDocumentFragment();
    for (var i = 0, l = words.length; i < l; i++) {
        if (words[i].match(/[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/gi)) {
            var elm = document.createElement('a');
            elm.href = words[i];
            elm.textContent = words[i];
            if (ret.childNodes.length > 0) {
                ret.lastChild.textContent += ' ';
            }
            ret.appendChild(elm);
        } else {
            if (ret.lastChild && ret.lastChild.nodeType === 3) {
                ret.lastChild.textContent += ' ' + words[i];
            } else {
                ret.appendChild(document.createTextNode(' ' + words[i]));
            }
        }
    }
    return ret;
}

Есть некоторые оговорки, а именно, с более старой поддержкой IE и textContent.

здесь - это демонстрация.

  • 2
    @DanDascalescu Вместо того, чтобы подавлять голосование, возможно, предоставьте свои упомянутые крайние случаи.
  • 0
    Нужно ли мне? Взгляните на регулярное выражение компонента для URL . Но если вы настаиваете, бегите против набора тестов Бена Алмана . Я начал вносить неудачные тесты, например, для urlize , но вскоре понял, что это стоит делать только для серьезных библиотечных усилий. При всем уважении, приведенный выше ответ - StackOverflow, а не библиотека с открытым исходным кодом, пытающаяся правильно проанализировать URL-адреса.
Показать ещё 4 комментария
3

Я искал в google что-то новое и натолкнулся на это:

$('p').each(function(){
   $(this).html( $(this).html().replace(/((http|https|ftp):\/\/[\w?=&.\/-;#~%-]+(?![\w\s?&.\/;#~%"=-]*>))/g, '<a href="$1">$1</a> ') );
});

demo: http://jsfiddle.net/kachibito/hEgvc/1/

Хорошо работает для обычных ссылок.

  • 0
    Что такое "нормальные ссылки" здесь? Посмотрите на развилку вашей демоверсии здесь: jsfiddle.net/hEgvc/27 Люди бы раскрыли непокрытое и сделали бы это простым способом. URI - не простая вещь в соответствии с RFC3986, и если вы хотите охватить только «Нормальные ссылки», я рекомендую следовать этому регулярному выражению хотя бы: ^ (([^: /? #] +):)? (// ([ ^ /? #] *)?) ([^? #] *) (? \ ([^ #] *))? (# (. *?))
  • 1
    Я имел в виду что-нибудь в формате http://example.com/folder/folder/folder/ или https://example.org/blah т. Д. - просто ваш типичный не сумасшедший формат URL, который будет соответствовать 95-99% случаев использования. там Я использую это для внутренней административной области, поэтому мне не нужно ничего необычного, чтобы ловить пограничные случаи или хэш-ссылки.
3

Держите это просто! Скажите, чего у вас нет, а не то, что вы можете иметь:)

Как упоминалось выше, URL-адреса могут быть довольно сложными, особенно после "?", и не все из них начинаются с "www". например maps.bing.com/something?key=!"£$%^*()&lat=65&lon&lon=20

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

Match

http(s):// (anything but a space)+

www. (anything but a space)+

Где "ничего" [^'"<>\s] ... в основном жадный матч, переносящий на вас пробел, цитату, угловую скобку или конец строки

также:

Не забудьте проверить, что он еще не находится в формате URL, например. текст содержит href="..." или src="..."

Добавить ref = nofollow (при необходимости)

Это решение не так "хорошо", как упомянутые выше библиотеки, но намного проще и хорошо работает на практике.

if html.match( /(href)|(src)/i )) {
    return html; // text already has a hyper link in it
    }

html = html.replace( 
            /\b(https?:\/\/[^\s\(\)\'\"\<\>]+)/ig, 
            "<a ref='nofollow' href='$1'>$1</a>" 
            );

html = html.replace( 
            /\s(www\.[^\s\(\)\'\"\<\>]+)/ig, 
            "<a ref='nofollow' href='http://$1'>$1</a>" 
            );

html = html.replace( 
             /^(www\.[^\s\(\)\'\"\<\>]+)/ig, 
            "<a ref='nofollow' href='http://$1'>$1</a>" 
            );

return html;
3

Если вам нужно показать более короткую ссылку (только домен), но с таким же длинным URL-адресом, вы можете попробовать изменить версию кода Sam Hasler, опубликованную выше

function replaceURLWithHTMLLinks(text) {
    var exp = /(\b(https?|ftp|file):\/\/([-A-Z0-9+&@#%?=~_|!:,.;]*)([-A-Z0-9+&@#%?\/=~_|!:,.;]*)[-A-Z0-9+&@#\/%=~_|])/ig;
    return text.replace(exp, "<a href='$1' target='_blank'>$3</a>");
}
2

Следует отметить предупреждения о сложности URI, но простой ответ на ваш вопрос:
Чтобы заменить каждое соответствие, вам нужно добавить флаг /g в конец RegEx:
/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi

2

Reg Ex: /(\b((https?|ftp|file):\/\/|(www))[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]*)/ig

function UriphiMe(text) {
      var exp = /(\b((https?|ftp|file):\/\/|(www))[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]*)/ig; 
      return text.replace(exp,"<a href='$1'>$1</a>");
}

Ниже приведены некоторые проверенные строки:

Примечание. Если вы не хотите передавать www как действительный, просто используйте ниже reg ex: /(\b((https?|ftp|file):\/\/|(www))[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig

  • 0
    Приведенный выше код провалит много тестов для крайних случаев. При обнаружении URL-адресов ВСЕГДА лучше полагаться на специализированную библиотеку. Вот почему
1

Правильное обнаружение URL-адресов с помощью международных доменов и поддержка астральных символов - это не тривиальная вещь. linkify-it библиотека создает регулярное выражение из множество условий, а конечный размер - около 6 килобайт:). Это более точно, чем все библиотеки, в настоящее время ссылающиеся в принятом ответе.

Смотрите ссылку на демонстрацию, чтобы проверить живые все грани и проверить свои.

Если вам нужно связать источник HTML, вы должны сначала его разобрать и повторить каждый текстовый токен.

0

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

ссылка: https://github.com/alexcorvi/anchorme.js

  • 0
    ссылка мертва
  • 0
    @tttony Извините! обновлено.
Показать ещё 1 комментарий
0

Замените URL-адреса в тексте ссылками HTML, игнорируйте URL-адреса в теге href/pre. https://github.com/JimLiu/auto-link

0

После ввода нескольких источников у меня есть решение, которое работает хорошо. Это связано с написанием собственного кода замены.

Отвечать.

Fiddle.

function replaceURLWithHTMLLinks(text) {
    var re = /(\(.*?)?\b((?:https?|ftp|file):\/\/[-a-z0-9+&@#\/%?=~_()|!:,.;]*[-a-z0-9+&@#\/%=~_()|])/ig;
    return text.replace(re, function(match, lParens, url) {
        var rParens = '';
        lParens = lParens || '';

        // Try to strip the same number of right parens from url
        // as there are left parens.  Here, lParenCounter must be
        // a RegExp object.  You cannot use a literal
        //     while (/\(/g.exec(lParens)) { ... }
        // because an object is needed to store the lastIndex state.
        var lParenCounter = /\(/g;
        while (lParenCounter.exec(lParens)) {
            var m;
            // We want m[1] to be greedy, unless a period precedes the
            // right parenthesis.  These tests cannot be simplified as
            //     /(.*)(\.?\).*)/.exec(url)
            // because if (.*) is greedy then \.? never gets a chance.
            if (m = /(.*)(\.\).*)/.exec(url) ||
                    /(.*)(\).*)/.exec(url)) {
                url = m[1];
                rParens = m[2] + rParens;
            }
        }
        return lParens + "<a href='" + url + "'>" + url + "</a>" + rParens;
    });
}
  • 2
    Приведенный выше код (и большинство регулярных выражений в целом) не пройдёт множество тестов для крайних случаев. При обнаружении URL-адресов лучше полагаться на специализированную библиотеку. Вот почему
  • 0
    Дэн, есть ли такая библиотека? Хотя в этом случае мы все равно соответствовали бы приведенному выше регулярному выражению, чтобы код никогда не мог выводить мусор, когда что-то вроде мусора (даже если другая библиотека сертифицирует мусор как действительный URL / URI) в качестве ввода.
0

Обнаружение электронной почты в ответе Travitron выше не работает для меня, поэтому я расширил/заменил его следующим кодом (С#).

// Change e-mail addresses to mailto: links.
const RegexOptions o = RegexOptions.Multiline | RegexOptions.IgnoreCase;
const string pat3 = @"([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,6})";
const string rep3 = @"<a href=""mailto:$1@$2.$3"">$1@$2.$3</a>";
text = Regex.Replace(text, pat3, rep3, o);

Это позволяет использовать такие адреса электронной почты, как "[email protected]".

  • 0
    Приведенный выше код провалит много тестов для крайних случаев. При обнаружении URL-адресов ВСЕГДА лучше полагаться на специализированную библиотеку. Вот почему
  • 0
    Спасибо, @DanDascalescu Как правило, это всегда лучше чрезмерно обобщать.
0

Мне нужно было сделать обратное и сделать html-ссылки только в URL-адресе, но я изменил ваше регулярное выражение, и оно работает как шарм, спасибо:)

var exp = /<a\s.*href=['"](\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])['"].*>.*<\/a>/ig;

source = source.replace(exp,"$1");
  • 0
    Я не вижу смысла вашего регулярного выражения. Это соответствует всему, заменяя все на все. По сути, ваш код ничего не делает.
  • 8
    Думаю, мне следует подождать, чтобы оставить комментарий, чтобы люди могли закончить редактирование. извиняюсь.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню