Reemplazar URL en texto con enlaces HTML

Resuelto Angel.King.47 asked hace 54 años • 17 respuestas

Sin embargo, aquí hay un diseño: por ejemplo, pongo un enlace como

http://ejemplo.com

en el área de texto . ¿Cómo hago para que PHP detecte que es un http://enlace y luego lo imprima como

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

Recuerdo haber hecho algo como esto antes, sin embargo, no era infalible, seguía rompiéndose en enlaces complejos.

Otra buena idea sería si tienes un enlace como

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

arreglarlo para que así sea

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

Este es solo un pensamiento posterior. Stackoverflow probablemente también podría usar esto: D

Algunas ideas

Angel.King.47 avatar Jan 01 '70 08:01 Angel.King.47
Aceptado

Veamos los requisitos. Tiene texto sin formato proporcionado por el usuario, que desea mostrar con URL con hipervínculos.

  1. El prefijo del protocolo "http://" debe ser opcional.
  2. Se deben aceptar tanto dominios como direcciones IP.
  3. Se debe aceptar cualquier dominio de nivel superior válido, por ejemplo, .aero y .xn--jxalpdlp.
  4. Se deben permitir números de puerto.
  5. Las URL deben permitirse en contextos de oraciones normales. Por ejemplo, en "Visite stackoverflow.com", el punto final no forma parte de la URL.
  6. Probablemente también quieras permitir las URL "https://", y quizás también otras.
  7. Como siempre, cuando se muestra texto proporcionado por el usuario en HTML, desea evitar secuencias de comandos entre sitios (XSS). Además, querrás que los signos y en las URL tengan el carácter de escape correcto como &.
  8. Probablemente no necesite soporte para direcciones IPv6.
  9. Editar : Como se señaló en los comentarios, la compatibilidad con direcciones de correo electrónico es definitivamente una ventaja.
  10. Editar : solo se admitirá la entrada de texto sin formato; no se deben respetar las etiquetas HTML en la entrada. (La versión de Bitbucket admite entrada HTML).

Editar : consulte GitHub para obtener la última versión, que admite direcciones de correo electrónico, URL autenticadas, URL entre comillas y paréntesis, entrada HTML y una lista de TLD actualizada.

Aquí está mi opinión:

<?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>";
  • Para escapar correctamente de los caracteres < y &, paso todo el texto a través de htmlspecialchars antes de procesarlo. Esto no es ideal, ya que el escape html puede provocar una detección errónea de los límites de la URL.
  • Como lo demuestra el "Y recuerda. Nadie es perfecto". línea (en la que recuerde. Nadie se trata como una URL, debido al espacio que falta), es posible que sea necesario realizar más comprobaciones sobre los dominios de nivel superior válidos.

Editar : el siguiente código soluciona los dos problemas anteriores, pero es un poco más detallado ya que estoy más o menos implementando preg_replace_callbackel uso de 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)));
Søren Løvborg avatar Jul 27 '2009 14:07 Søren Løvborg

Ustedes están hablando de cosas complejas y avanzadas que son buenas para alguna situación, pero sobre todo necesitamos una solución simple y descuidada. ¿Qué tal simplemente esto?

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

Pruébelo y déjeme saber qué URL loca no satisface.

Raheel Hasan avatar Apr 20 '2015 09:04 Raheel Hasan