Заменить URL в тексте ссылками HTML

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

http://example.com

на textarea. Как получить PHP, чтобы обнаружить, что это http:// ссылке, а затем распечатать его как

print "<a href='http://www.example.com'>http://www.example.com</a>";

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

еще неплохо было бы, если у вас есть ссылка такая as

http://example.com/test.php?val1=bla&val2blablabla%20bla%20bla.bl

исправить его

print "<a href='http://example.com/test.php?val1=bla&val2=bla%20bla%20bla.bla'>";
print "http://example.com/test.php";
print "</a>";

Это просто после мысли.. stackoverflow также может использовать это: D

Идеи

15 ответов


давайте посмотрим на требования. У вас есть пользовательский текст, который вы хотите отобразить с гиперссылками url.

  1. префикс протокола "http://" должен быть необязательным.
  2. должны приниматься как домены, так и IP-адреса.
  3. любой допустимый домен верхнего уровня должен быть принят, например .Аэро И.xn--jxalpdlp.
  4. номера портов должны быть разрешены.
  5. URL-адреса должны быть разрешены в нормальных условиях предложение. Например, в "Визит stackoverflow.com.", конечный период не является частью URL-адреса.
  6. вы, вероятно, хотите разрешить URL-адреса "https://", а также, возможно, и другие.
  7. как всегда при отображении пользовательского текста в HTML, вы хотите предотвратить межсайтовый скриптинг (XSS). Кроме того, вы захотите, чтобы амперсанды в URL-адресах были правильно сбежал as &.
  8. вам, вероятно, не нужна поддержка IPv6-адресов.
  9. редактировать: As отмеченная в комментариях поддержка email-адресов-это определенно плюс.
  10. редактировать: поддерживается только ввод простого текста – HTML-теги на входе не должны соблюдаться. (Версия Bitbucket поддерживает ввод HTML.)

редактировать: проверить Bitbucket для последней версии с поддержкой адресов электронной почты, аутентифицированных URL-адресов, URL-адресов в кавычках и скобках, ввода HTML, а также обновленного TLD список.

пожалуйста, сообщите об ошибках и запросах на улучшение с помощью Bitbucket проблема tracker. Их легче отслеживать таким образом (и не загромождать область комментариев).

вот мое мнение:

<?php
$text = <<<EOD
Here are some URLs:
stackoverflow.com/questions/1188129/pregreplace-to-detect-html-php
Here's the answer: http://www.google.com/search?rls=en&q=42&ie=utf-8&oe=utf-8&hl=en. What was the question?
A quick look at http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax is helpful.
There is no place like 127.0.0.1! Except maybe http://news.bbc.co.uk/1/hi/england/surrey/8168892.stm?
Ports: 192.168.0.1:8080, https://example.net:1234/.
Beware of Greeks bringing internationalized top-level domains: xn--hxajbheg2az3al.xn--jxalpdlp.
And remember.Nobody is perfect.

<script>alert('Remember kids: Say no to XSS-attacks! Always HTML escape untrusted input!');</script>
EOD;

$rexProtocol = '(https?://)?';
$rexDomain   = '((?:[-a-zA-Z0-9]{1,63}\.)+[-a-zA-Z0-9]{2,63}|(?:[0-9]{1,3}\.){3}[0-9]{1,3})';
$rexPort     = '(:[0-9]{1,5})?';
$rexPath     = '(/[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]*?)?';
$rexQuery    = '(\?[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?';
$rexFragment = '(#[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?';

// Solution 1:

function callback($match)
{
    // Prepend http:// if no protocol specified
    $completeUrl = $match[1] ? $match[0] : "http://{$match[0]}";

    return '<a href="' . $completeUrl . '">'
        . $match[2] . $match[3] . $match[4] . '</a>';
}

print "<pre>";
print preg_replace_callback("&\b$rexProtocol$rexDomain$rexPort$rexPath$rexQuery$rexFragment(?=[?.!,;:\"]?(\s|$))&",
    'callback', htmlspecialchars($text));
print "</pre>";
  • чтобы правильно избежать
  • As продемонстрировано "и помните.Никто не совершенен."линия (в которой помните.Никто не рассматривается как URL-адрес из-за отсутствующего пространства), дальнейшая проверка допустимых доменов верхнего уровня может быть в порядке.

редактировать: следующий код исправляет вышеуказанные две проблемы, но довольно многословен, так как я более или менее повторно реализую preg_replace_callback используя preg_match.

// Solution 2:

$validTlds = array_fill_keys(explode(" ", ".aero .asia .biz .cat .com .coop .edu .gov .info .int .jobs .mil .mobi .museum .name .net .org .pro .tel .travel .ac .ad .ae .af .ag .ai .al .am .an .ao .aq .ar .as .at .au .aw .ax .az .ba .bb .bd .be .bf .bg .bh .bi .bj .bm .bn .bo .br .bs .bt .bv .bw .by .bz .ca .cc .cd .cf .cg .ch .ci .ck .cl .cm .cn .co .cr .cu .cv .cx .cy .cz .de .dj .dk .dm .do .dz .ec .ee .eg .er .es .et .eu .fi .fj .fk .fm .fo .fr .ga .gb .gd .ge .gf .gg .gh .gi .gl .gm .gn .gp .gq .gr .gs .gt .gu .gw .gy .hk .hm .hn .hr .ht .hu .id .ie .il .im .in .io .iq .ir .is .it .je .jm .jo .jp .ke .kg .kh .ki .km .kn .kp .kr .kw .ky .kz .la .lb .lc .li .lk .lr .ls .lt .lu .lv .ly .ma .mc .md .me .mg .mh .mk .ml .mm .mn .mo .mp .mq .mr .ms .mt .mu .mv .mw .mx .my .mz .na .nc .ne .nf .ng .ni .nl .no .np .nr .nu .nz .om .pa .pe .pf .pg .ph .pk .pl .pm .pn .pr .ps .pt .pw .py .qa .re .ro .rs .ru .rw .sa .sb .sc .sd .se .sg .sh .si .sj .sk .sl .sm .sn .so .sr .st .su .sv .sy .sz .tc .td .tf .tg .th .tj .tk .tl .tm .tn .to .tp .tr .tt .tv .tw .tz .ua .ug .uk .us .uy .uz .va .vc .ve .vg .vi .vn .vu .wf .ws .ye .yt .yu .za .zm .zw .xn--0zwm56d .xn--11b5bs3a9aj6g .xn--80akhbyknj4f .xn--9t4b11yi5a .xn--deba0ad .xn--g6w251d .xn--hgbk6aj7f53bba .xn--hlcj6aya9esc7a .xn--jxalpdlp .xn--kgbechtv .xn--zckzah .arpa"), true);

$position = 0;
while (preg_match("{\b$rexProtocol$rexDomain$rexPort$rexPath$rexQuery$rexFragment(?=[?.!,;:\"]?(\s|$))}", $text, &$match, PREG_OFFSET_CAPTURE, $position))
{
    list($url, $urlPosition) = $match[0];

    // Print the text leading up to the URL.
    print(htmlspecialchars(substr($text, $position, $urlPosition - $position)));

    $domain = $match[2][0];
    $port   = $match[3][0];
    $path   = $match[4][0];

    // Check if the TLD is valid - or that $domain is an IP address.
    $tld = strtolower(strrchr($domain, '.'));
    if (preg_match('{\.[0-9]{1,3}}', $tld) || isset($validTlds[$tld]))
    {
        // Prepend http:// if no protocol specified
        $completeUrl = $match[1][0] ? $url : "http://$url";

        // Print the hyperlink.
        printf('<a href="%s">%s</a>', htmlspecialchars($completeUrl), htmlspecialchars("$domain$port$path"));
    }
    else
    {
        // Not a valid URL.
        print(htmlspecialchars($url));
    }

    // Continue text parsing from after the URL.
    $position = $urlPosition + strlen($url);
}

// Print the remainder of the text.
print(htmlspecialchars(substr($text, $position)));

вот что я нашел, что это испытанный

function make_links_blank($text)
{
  return  preg_replace(
     array(
       '/(?(?=<a[^>]*>.+<\/a>)
             (?:<a[^>]*>.+<\/a>)
             |
             ([^="\']?)((?:https?|ftp|bf2|):\/\/[^<> \n\r]+)
         )/iex',
       '/<a([^>]*)target="?[^"\']+"?/i',
       '/<a([^>]+)>/i',
       '/(^|\s)(www.[^<> \n\r]+)/iex',
       '/(([_A-Za-z0-9-]+)(\.[_A-Za-z0-9-]+)*@([A-Za-z0-9-]+)
       (\.[A-Za-z0-9-]+)*)/iex'
       ),
     array(
       "stripslashes((strlen('\2')>0?'\1<a href=\"\2\">\2</a>\3':'\0'))",
       '<a\1',
       '<a\1 target="_blank">',
       "stripslashes((strlen('\2')>0?'\1<a href=\"http://\2\">\2</a>\3':'\0'))",
       "stripslashes((strlen('\2')>0?'<a href=\"mailto:\0\">\0</a>':'\0'))"
       ),
       $text
   );
}

это работает для меня. И он работает для электронных писем и URL, извините, чтобы ответить на мой собственный вопрос. :(

но это единственное, что работает

вот ссылка, где я ее нашел: http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/PHP/Q_21878567.html

Sry заранее за то, что это эксперты-обмен.


вы guyz говорите способ продвижения и сложные вещи, которые хороши для некоторой ситуации, но в основном нам нужно простое небрежное решение. Как насчет просто этого?

preg_replace('/(http[s]{0,1}\:\/\/\S{4,})\s{0,}/ims', '<a href="" target="_blank"></a> ', $text_msg);

просто попробуйте и дайте мне знать, какой сумасшедший url он не удовлетворяет.


вот код, использующий регулярные выражения в функции

<?php
//Function definations
function MakeUrls($str)
{
$find=array('`((?:https?|ftp)://\S+[[:alnum:]]/?)`si','`((?<!//)(www\.\S+[[:alnum:]]/?))`si');

$replace=array('<a href="" target="_blank"></a>', '<a href="http://" target="_blank"></a>');

return preg_replace($find,$replace,$str);
}
//Function testing
$str="www.cloudlibz.com";
$str=MakeUrls($str);
echo $str;
?>

Я использую эту функцию, она работает для меня

function AutoLinkUrls($str,$popup = FALSE){
    if (preg_match_all("#(^|\s|\()((http(s?)://)|(www\.))(\w+[^\s\)\<]+)#i", $str, $matches)){
        $pop = ($popup == TRUE) ? " target=\"_blank\" " : "";
        for ($i = 0; $i < count($matches['0']); $i++){
            $period = '';
            if (preg_match("|\.$|", $matches['6'][$i])){
                $period = '.';
                $matches['6'][$i] = substr($matches['6'][$i], 0, -1);
            }
            $str = str_replace($matches['0'][$i],
                    $matches['1'][$i].'<a href="http'.
                    $matches['4'][$i].'://'.
                    $matches['5'][$i].
                    $matches['6'][$i].'"'.$pop.'>http'.
                    $matches['4'][$i].'://'.
                    $matches['5'][$i].
                    $matches['6'][$i].'</a>'.
                    $period, $str);
        }//end for
    }//end if
    return $str;
}//end AutoLinkUrls

все кредиты идут на -http://snipplr.com/view/68586/

наслаждайтесь!


это регулярное выражение должно соответствовать любой ссылке, кроме этих новых доменов верхнего уровня 3 + символов...

{
  \b
  # Match the leading part (proto://hostname, or just hostname)
  (
    # http://, or https:// leading part
    (https?)://[-\w]+(\.\w[-\w]*)+
  |
    # or, try to find a hostname with more specific sub-expression
    (?i: [a-z0-9] (?:[-a-z0-9]*[a-z0-9])? \. )+ # sub domains
    # Now ending .com, etc. For these, require lowercase
    (?-i: com\b
        | edu\b
        | biz\b
        | gov\b
        | in(?:t|fo)\b # .int or .info
        | mil\b
        | net\b
        | org\b
        | [a-z][a-z]\.[a-z][a-z]\b # two-letter country code
    )
  )

  # Allow an optional port number
  ( : \d+ )?

  # The rest of the URL is optional, and begins with /
  (
    /
    # The rest are heuristics for what seems to work well
    [^.!,?;"\'()\[\]\{\}\s\x7F-\xFF]*
    (
      [.!,?]+ [^.!,?;"\'()\[\]\{\}\s\x7F-\xFF]+
    )*
  )?
}ix

это должно получить вам адреса электронной почты:

$string = "bah bah steve@gmail.com foo";
$match = preg_match('/[^\x00-\x20()<>@,;:\".[\]\x7f-\xff]+(?:\.[^\x00-\x20()<>@,;:\".[\]\x7f-\xff]+)*\@[^\x00-\x20()<>@,;:\".[\]\x7f-\xff]+(?:\.[^\x00-\x20()<>@,;:\".[\]\x7f-\xff]+)+/', $string, $array);
print_r($array);

// outputs:
Array
(
    [0] => steve@gmail.com
)

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

Это измененная версия кода, опубликованная: Angel.Король.47 в июле 27,09:

$text = preg_replace(
 array(
   '/(^|\s|>)(www.[^<> \n\r]+)/iex',
   '/(^|\s|>)([_A-Za-z0-9-]+(\.[A-Za-z]{2,3})?\.[A-Za-z]{2,4}\/[^<> \n\r]+)/iex',
   '/(?(?=<a[^>]*>.+<\/a>)(?:<a[^>]*>.+<\/a>)|([^="\']?)((?:https?):\/\/([^<> \n\r]+)))/iex'
 ),  
 array(
   "stripslashes((strlen('\2')>0?'\1<a href=\"http://\2\" target=\"_blank\">\2</a>&nbsp;\3':'\0'))",
   "stripslashes((strlen('\2')>0?'\1<a href=\"http://\2\" target=\"_blank\">\2</a>&nbsp;\4':'\0'))",
   "stripslashes((strlen('\2')>0?'\1<a href=\"\2\" target=\"_blank\">\3</a>&nbsp;':'\0'))",
 ),  
 $text
);

изменения:

  • Я удалил правила № 2 и № 3 (я не уверен в каких ситуациях полезны).
  • удален разбор электронной почты, поскольку мне это действительно не нужно.
  • я добавил еще одно правило, которое позволяет распознавать URL-адреса в виде: [домен]/* (без www). Например :example.com/faq/ " (множественный tld: домен.{2-3}.{2-4}/)
  • при разборе строк, начинающихся с " http://", он удаляет его из метки ссылки.
  • добавлена "target=' _blank '" для всех ссылок.
  • Urls можно указать сразу после любого (?) метка. Например: www.example.com

Как "Северин Løvborg" заявил, Эта функция не избежать ссылок. Я попробовал его/ее класс, но он просто не работал, как я ожидал (если вы не доверяете своим пользователям, сначала попробуйте его / ее код).


Как я уже упоминал в одном из комментариев выше моего VPS, который работает php 7, запущен испуская предупреждения предупреждение: preg_replace (): модификатор /e больше не поддерживается, используйте preg_replace_callback вместо. Буфер после замены был пуст / false.

я переписал код и сделал некоторые улучшения. Если вы считаете, что вы должны быть в разделе автора, не стесняйтесь редактировать комментарий выше имени функции make_links_blank. Я намеренно не используя закрывающий php ?> чтобы избежать вставки пробелов в выходные данные.

<?php

class App_Updater_String_Util {
    public static function get_default_link_attribs( $regex_matches = [] ) {
        $t = ' target="_blank" ';
        return $t;
    }

    /**
     * App_Updater_String_Util::set_protocol();
     * @param string $link
     * @return string
     */
    public static function set_protocol( $link ) {
        if ( ! preg_match( '#^https?#si', $link ) ) {
            $link = 'http://' . $link;
        }
        return $link;
    }

/**
     * Goes through text and makes whatever text that look like a link an html link
     * which opens in a new tab/window (by adding target attribute).
     * 
     * Usage: App_Updater_String_Util::make_links_blank( $text );
     * 
     * @param str $text
     * @return str
     * @see http://stackoverflow.com/questions/1188129/replace-urls-in-text-with-html-links
     * @author Angel.King.47 | http://dashee.co.uk
     * @author Svetoslav Marinov (Slavi) | http://orbisius.com
     */
    public static function make_links_blank( $text ) {
        $patterns = [
            '#(?(?=<a[^>]*>.+?<\/a>)
                 (?:<a[^>]*>.+<\/a>)
                 |
                 ([^="\']?)((?:https?|ftp):\/\/[^<> \n\r]+)
             )#six' => function ( $matches ) {
                $r1 = empty( $matches[1] ) ? '' : $matches[1];
                $r2 = empty( $matches[2] ) ? '' : $matches[2];
                $r3 = empty( $matches[3] ) ? '' : $matches[3];

                $r2 = empty( $r2 ) ? '' : App_Updater_String_Util::set_protocol( $r2 );
                $res = ! empty( $r2 ) ? "$r1<a href=\"$r2\">$r2</a>$r3" : $matches[0];
                $res = stripslashes( $res );

                return $res;
             },

            '#(^|\s)((?:https?://|www\.|https?://www\.)[^<>\ \n\r]+)#six' => function ( $matches ) {
                $r1 = empty( $matches[1] ) ? '' : $matches[1];
                $r2 = empty( $matches[2] ) ? '' : $matches[2];
                $r3 = empty( $matches[3] ) ? '' : $matches[3];

                $r2 = ! empty( $r2 ) ? App_Updater_String_Util::set_protocol( $r2 ) : '';
                $res = ! empty( $r2 ) ? "$r1<a href=\"$r2\">$r2</a>$r3" : $matches[0];
                $res = stripslashes( $res );

                return $res;
            },

            // Remove any target attribs (if any)
            '#<a([^>]*)target="?[^"\']+"?#si' => '<a\1',

            // Put the target attrib
            '#<a([^>]+)>#si' => '<a\1 target="_blank">',

            // Make emails clickable Mailto links
            '/(([\w\-]+)(\.[\w\-]+)*@([\w\-]+)
                (\.[\w\-]+)*)/six' => function ( $matches ) {

                $r = $matches[0];
                $res = ! empty( $r ) ? "<a href=\"mailto:$r\">$r</a>" : $r;
                $res = stripslashes( $res );

                return $res;
            },
        ];

        foreach ( $patterns as $regex => $callback_or_replace ) {
            if ( is_callable( $callback_or_replace ) ) {
                $text = preg_replace_callback( $regex, $callback_or_replace, $text );
            } else {
                $text = preg_replace( $regex, $callback_or_replace, $text );
            }
        }

        return $text;
    }
}

что-то вроде :

<?php
if(preg_match('@^http://(.*)\s|$@g', $textarea_url, $matches)) {
    echo '<a href=http://", $matches[1], '">', $matches[1], '</a>';
}
?>

этой class изменяет URL-адреса в текст и при сохранении домашнего url-адреса как есть. Надеюсь, это поможет вам сэкономить время.Наслаждаться.

class RegClass 
{ 

     function preg_callback_url($matches) 
     { 
        //var_dump($matches); 
        //Get the matched URL  text <a>text</a>
        $text = $matches[2];
        //Get the matched URL link <a href ="http://www.test.com">text</a>
        $url = $matches[1];

        if($url=='href ="http://www.test.com"'){
         //replace all a tag as it is
         return '<a href='.$url.' rel="nofollow"> '.$text.' </a>'; 

         }else{
         //replace all a tag to text
         return " $text " ;
         }
} 
function ParseText($text){ 

    $text = preg_replace( "/www\./", "http://www.", $text );
        $regex ="/http:\/\/http:\/\/www\./"
    $text = preg_replace( $regex, "http://www.", $text );
        $regex2 = "/https:\/\/http:\/\/www\./";
    $text = preg_replace( $regex2, "https://www.", $text );

        return preg_replace_callback('/<a\s(.+?)>(.+?)<\/a>/is',
                array( &$this,        'preg_callback_url'), $text); 
      } 

} 
$regexp = new RegClass();
echo $regexp->ParseText($text);

Если вы хотите доверять IANA, вы можете получить свой текущий список официально поддерживаемых дву в использовании там, как:

  $validTLDs = 
explode("\n", file_get_contents('http://data.iana.org/TLD/tlds-alpha-by-domain.txt')); //get the official list of valid tlds
  array_shift($validTLDs); //throw away first line containing meta data
  array_pop($validTLDs); //throw away last element which is empty

делает решение #2 Серена Левборга немного менее подробным и избавляет вас от хлопот обновления списка, в настоящее время новые дву выбрасываются так небрежно ;)


это сработало для меня (превратило один из ответов в функцию PHP)

function make_urls_from_text ($text){
   return preg_replace('/(http[s]{0,1}\:\/\/\S{4,})\s{0,}/ims', '<a href="" target="_blank"> </a>', $text);
}

Это должно получить вашу ручку twitter, не касаясь вашей электронной почты /(?.])) @([A-Za-z]+[A-Za-z0-9]+) / i


при сопоставлении полной спецификации url трудно, вот регулярное выражение, которое обычно делает хорошую работу:

([\w-]+(\.[\w-]+)*@([a-z0-9-]+(\.[a-z0-9-]+)*?\.[a-z]{2,6}|(\d{1,3}\.){3}\d{1,3})(:\d{4})?)

чтобы использовать это в preg_replace, однако, вам нужно избежать этого. Как так:

$pattern = "/([\w-]+(\.[\w-]+)*@([a-z0-9-]+(\.[a-z0-9-]+)*?\.[a-z]{2,6}|(\d{1,3}\.){3}\d{1,3})(:\d{4})?)/";
$replaced_texttext = preg_replace($pattern, '<a href="" title=""></a>', $text);