javascript - Hvordan erstatte vanlige URL-er med lenker?


Jeg bruker funksjonen nedenfor for å finne URL-er i en gitt tekst og erstatte dem med HTML-lenker. Det regulære uttrykket fungerer fint, men for øyeblikket erstatter jeg bare det første treffet.

Hvordan kan jeg erstatte alle URL-ene? Jeg antar at jeg burde bruke exec-kommandoen, men jeg har ikke helt skjønt hvordan man gjør det.

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>"); 
}

Svar #1

Først og fremst, å lage ditt eget regexp for å analysere URL-er er en forferdelig idé. Du må anta at dette er et vanlig nok problem til at noen har skrevet, feilsøkt og testet et bibliotek for det, i henhold til RFC-ene. URI-er er komplekse - sjekk koden for URL-parsing i Node.js og Wikipedia-siden om URI-skjemaer.

Det finnes en mengde spesialtilfeller når det gjelder å analysere URL-er: internasjonale domenenavn, faktiske (.museum) vs. ikke-eksisterende (.etc) TLD-er, merkelig tegnsetting inkludert parenteser, tegnsetting på slutten av URL-en, IPV6-vertsnavn osv.

Jeg har sett på en haug med biblioteker, og det er noen få verdt å bruke til tross for noen ulemper:

  • Soapbox's linkify har fått lagt ned betydelig innsats, og en større omstrukturering i juni 2015 fjernet avhengigheten av jQuery. Den har fortsatt problemer med IDN-er.
  • AnchorMe er en nykommer som påstår å være raskere og mer strømlinjeformet. Noen IDN-problemer også.
  • Autolinker.js lister funksjoner veldig spesifikt (f.eks. "Vil håndtere HTML-inndata på riktig måte. Verktøyet vil ikke endre href-attributtet inne i anker()-tagger"). Jeg vil teste det når en demo blir tilgjengelig.

Biblioteker som jeg raskt har diskvalifisert for denne oppgaven:

  • Djangos urlize håndterte ikke enkelte TLD-er riktig (her er den offisielle listen over gyldige TLD-er. Ingen demo.
  • autolink-js ville ikke oppdage "www.google.com" uten http://, så det er ikke helt egnet for automatisk lenking av "vanlige URL-er" (uten en skjema/protokoll) som finnes i ren tekst.
  • Ben Almans linkify har ikke blitt vedlikeholdt siden 2009.

Hvis du insisterer på et regulært uttrykk, er det mest omfattende URL-regexet fra Component, selv om det vil feilaktig oppdage noen ikke-eksisterende tosifrede TLD-er ved å se på det.

Svar #2

Erstatte URL-er med lenker (Svar på det generelle problemet)

Det vanlige regulære uttrykket i spørsmålet overser mange spesialtilfeller. Når man skal oppdage URL-er, er det alltid bedre å bruke et spesialisert bibliotek som håndterer internasjonale domenenavn, nye TLD-er som .museum, parenteser og annen tegnsetting innenfor og på slutten av URL-en, og mange andre spesialtilfeller. Se Jeff Atwoods blogginnlegg The Problem With URLs for en forklaring på noen av de andre problemene.

Den best oppsummering av URL-matching-biblioteker er i Dan Dascalescus svar


(per februar 2014)

"Lag et regulært uttrykk som erstatter mer enn ett treff" (Svar på det spesifikke problemet)

Legg til en "g" på slutten av regulæruttrykket for å aktivere global samsvar:

/ig;

Men det løser bare problemet i spørsmålet der regulæruttrykket bare erstattet det første treffet. Ikke bruk den koden.

Svar #3

Jeg har gjort noen små endringer i Travis sin kode (bare for å unngå unødvendig nydeklarering - men det fungerer kjempebra for mine behov, så bra jobbet!)

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;
}

Svar #4

Gjorde noen optimaliseringer på Travis' Linkify()-kode ovenfor. Jeg har også fikset en feil der e-postadresser med subdomeneformat ikke ble matchet (dvs. [email ).

I tillegg endret jeg implementasjonen for å prototype String-klassen slik at elementer kan matches på denne måten:

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

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

Uansett, her er manuset:

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>');
    };
}

Svar #5

Takk, dette var veldig hjelpsomt. Jeg ønsket også noe som kunne lenke ting som så ut som en URL - som et grunnleggende krav ville det lenke noe som www.yahoo.com, selv om http:// protokollprefikset ikke var til stede. Så i utgangspunktet, hvis "www." er til stede, vil det lenke det og anta at det er http://. Jeg ønsket også at e-poster skulle bli til mailto: lenker. EKSEMPEL: www.yahoo.com ville bli konvertert til www.yahoo.com

Her er koden jeg endte opp med (en kombinasjon av kode fra denne siden og andre ting jeg fant på nettet, og andre ting jeg gjorde på egen hånd):

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
}

I den andre erstatningen erstatter (^|[^/])-delen bare www.whatever.com hvis det ikke allerede er prefikset med/-- for å unngå dobbeltlenking hvis en URL allerede ble lenket i den første erstatningen. Det er også mulig at www.whatever.com kan være i begynnelsen av strengen, som er den første "eller"-betingelsen i den delen av regexen.

Dette kan integreres som en jQuery-plugin som Jesse P illustrerte ovenfor -- men jeg ønsket spesifikt en vanlig funksjon som ikke virket på et eksisterende DOM-element, fordi jeg tar tekst jeg har og legger den til i DOM-en, og jeg vil at teksten skal bli "linkifisert" før jeg legger den til, så jeg sender teksten gjennom denne funksjonen. Fungerer flott.

Svar #6

Å identifisere nettadresser er vanskelig fordi de ofte er omgitt av skilletegn, og fordi brukere ofte ikke bruker den fullstendige formen av URL-en. Mange JavaScript-funksjoner finnes for å erstatte URL-er med hyperkoblinger, men jeg kunne ikke finne en som fungerer like godt som urlize-filteret i det Python-baserte web-rammeverket Django. Jeg portet derfor Djangos urlize-funksjon til JavaScript:

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

Et eksempel:

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;"

Det andre argumentet, hvis sant, fører til at rel="nofollow" settes inn. Det tredje argumentet, hvis sant, gjør om tegn som har spesiell betydning i HTML til sikre tegn. Se README-filen.

Svar #7

Jeg søkte på Google etter noe nyere og kom over denne:

$('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/

Fungerer veldig bra for vanlige lenker.

Svar #8

Jeg gjorde en endring i Roshambo String.linkify() til emailAddressPattern for å gjenkjenne [email adresser

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>');
    };
}

Relaterte artikler