web-dev-qa-db-fra.com

Comment détecter la présence d'URL dans une chaîne

J'ai une chaîne d'entrée dire Please go to http://stackoverflow.com. La partie url de la chaîne est détectée et un ancre <a href=""></a> est automatiquement ajouté par de nombreux navigateurs/IDE/applications. Donc, cela devient Please go to <a href='http://stackoverflow.com'>http://stackoverflow.com</a>.

Je dois faire la même chose avec Java.

26
Rakesh N

Utilisez Java.net.URL pour ça !!

Pourquoi ne pas utiliser la classe principale en Java pour ce fichier "Java.net.URL" et le laisser valider l'URL. 

Bien que le code suivant viole le principe d’or "Utiliser l’exception uniquement dans des conditions exceptionnelles", cela n’a aucun sens de tenter de réinventer la roue de quelque chose de très mature sur la plate-forme Java.

Voici le code:

import Java.net.URL;
import Java.net.MalformedURLException;

// Replaces URLs with html hrefs codes
public class URLInString {
    public static void main(String[] args) {
        String s = args[0];
        // separate input by spaces ( URLs don't have spaces )
        String [] parts = s.split("\\s+");

        // Attempt to convert each item into an URL.   
        for( String item : parts ) try {
            URL url = new URL(item);
            // If possible then replace with anchor...
            System.out.print("<a href=\"" + url + "\">"+ url + "</a> " );    
        } catch (MalformedURLException e) {
            // If there was an URL that was not it!...
            System.out.print( item + " " );
        }

        System.out.println();
    }
}

En utilisant l'entrée suivante:

"Please go to http://stackoverflow.com and then mailto:[email protected] to download a file from    ftp://user:pass@someserver/someFile.txt"

Produit la sortie suivante:

Please go to <a href="http://stackoverflow.com">http://stackoverflow.com</a> and then <a href="mailto:[email protected]">mailto:[email protected]</a> to download a file from    <a href="ftp://user:pass@someserver/someFile.txt">ftp://user:pass@someserver/someFile.txt</a>

Bien sûr, différents protocoles peuvent être gérés de différentes manières ... Vous pouvez obtenir toutes les informations avec les getters de la classe d'URL, par exemple 

 url.getProtocol();

Ou le reste des attributs: spec, port, fichier, requête, ref, etc. etc.

http://Java.Sun.com/javase/6/docs/api/Java/net/URL.html

Gère tous les protocoles (du moins ceux que la plate-forme Java connaît) et, comme avantage supplémentaire, s'il existe une URL que Java ne reconnaît pas et est finalement incorporée à la classe d'URL (par la mise à jour de la bibliothèque), vous obtiendrez c'est transparent!

56
OscarRyz

Bien que cela ne soit pas spécifique à Java, Jeff Atwood a récemment publié un article sur les pièges que vous pourriez rencontrer en essayant de localiser et de faire correspondre des URL dans du texte arbitraire:

Le problème avec les URL

Cela donne une bonne expression rationnelle qui peut être utilisée avec l'extrait de code que vous devez utiliser pour gérer correctement (plus ou moins) les parens.

La regex:

\(?\bhttp://[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|]

Le nettoyage de paren:

if (s.StartsWith("(") && s.EndsWith(")"))
{
    return s.Substring(1, s.Length - 2);
}
14
Michael Burr

Vous pouvez faire quelque chose comme ça (ajustez la regex selon vos besoins):

String originalString = "Please go to http://www.stackoverflow.com";
String newString = originalString.replaceAll("http://.+?(com|net|org)/{0,1}", "<a href=\"$0\">$0</a>");
4
Jason Coco

Le code suivant apporte ces modifications à "l'approche Atwood":

  1. Détecte https en plus de http (l'ajout d'autres schémas est trivial)
  2. L'indicateur CASE_INSENSTIVE est utilisé puisque HtTpS: // est valide.
  3. Les ensembles de parenthèses correspondants sont décollés (ils peuvent être imbriqués dans N’importe quel niveau). De plus, toutes les parenthèses de gauche non appariées sont Supprimées, mais les parenthèses de droite sont laissées intactes (à respecter Les URL de style wikipedia) 
  4. L'URL est HTML codé dans le texte du lien.
  5. L'attribut target est passé dans le paramètre de méthode. D'autres attributs peuvent être ajoutés à volonté.
  6. \ B n’utilise pas\b pour identifier une rupture Word avant de faire correspondre une URL. Les URL peuvent commencer par une parenthèse gauche ou http [s]: // sans autre exigence.

Remarques:

  • Les StringUtils d'Apache Commons Lang sont utilisés dans le code ci-dessous
  • L'appel à HtmlUtil.encode () ci-dessous est un util qui appelle finalement Du code Tomahawk pour coder en HTML le texte du lien, mais tout utilitaire similaire fera l'affaire.
  • Voir le commentaire de méthode pour une utilisation dans JSF ou d'autres environnements dans lesquels la sortie est HTML codée par défaut.

Ceci a été écrit en réponse aux exigences de nos clients et nous pensons qu'il représente un compromis raisonnable entre les caractères autorisés par la RFC et l'utilisation courante. Il est offert ici dans l’espoir d’être utile aux autres. 

Une extension supplémentaire pourrait être réalisée pour permettre la saisie de tous les caractères Unicode (c'est-à-dire non échappé avec% XX (hexadécimal à deux chiffres) et en hyperlien, mais nécessiterait l'acceptation de toutes les lettres Unicode avec une ponctuation limitée, puis une séparation sur les délimiteurs "acceptables" (par exemple,.,%, |, #, etc.), codant l’URL de chaque pièce puis recollant. Par exemple, http://en.wikipedia.org/wiki / Björn_Andrésen (dont la pile Le générateur de débordement ne détecte pas) serait "http://en.wikipedia.org/wiki/Bj%C3%B6rn_Andr%C3%A9sen" dans le href, mais contiendrait Björn_Andrésen dans le texte lié à la page.

// NOTES:   1) \w includes 0-9, a-z, A-Z, _
//          2) The leading '-' is the '-' character. It must go first in character class expression
private static final String VALID_CHARS = "-\\w+&@#/%=~()|";
private static final String VALID_NON_TERMINAL = "?!:,.;";

// Notes on the expression:
//  1) Any number of leading '(' (left parenthesis) accepted.  Will be dealt with.  
//  2) s? ==> the s is optional so either [http, https] accepted as scheme
//  3) All valid chars accepted and then one or more
//  4) Case insensitive so that the scheme can be hTtPs (for example) if desired
private static final Pattern URI_Finder_PATTERN = Pattern.compile("\\(*https?://["+ VALID_CHARS + VALID_NON_TERMINAL + "]*[" +VALID_CHARS + "]", Pattern.CASE_INSENSITIVE );

/**
 * <p>
 * Finds all "URL"s in the given _rawText, wraps them in 
 * HTML link tags and returns the result (with the rest of the text
 * html encoded).
 * </p>
 * <p>
 * We employ the procedure described at:
 * http://www.codinghorror.com/blog/2008/10/the-problem-with-urls.html
 * which is a <b>must-read</b>.
 * </p>
 * Basically, we allow any number of left parenthesis (which will get stripped away)
 * followed by http:// or https://.  Then any number of permitted URL characters
 * (based on http://www.ietf.org/rfc/rfc1738.txt) followed by a single character
 * of that set (basically, those minus typical punctuation).  We remove all sets of 
 * matching left & right parentheses which surround the URL.
 *</p>
 * <p>
 * This method *must* be called from a tag/component which will NOT
 * end up escaping the output.  For example:
 * <PRE>
 * <h:outputText ... escape="false" value="#{core:hyperlinkText(textThatMayHaveURLs, '_blank')}"/>
 * </pre>
 * </p>
 * <p>
 * Reason: we are adding <code>&lt;a href="..."&gt;</code> tags to the output *and*
 * encoding the rest of the string.  So, encoding the outupt will result in
 * double-encoding data which was already encoded - and encoding the <code>a href</code>
 * (which will render it useless).
 * </p>
 * <p>
 * 
 * @param   _rawText  - if <code>null</code>, returns <code>""</code> (empty string).
 * @param   _target   - if not <code>null</code> or <code>""</code>, adds a target attributed to the generated link, using _target as the attribute value.
 */
public static final String hyperlinkText( final String _rawText, final String _target ) {

    String returnValue = null;

    if ( !StringUtils.isBlank( _rawText ) ) {

        final Matcher matcher = URI_Finder_PATTERN.matcher( _rawText );

        if ( matcher.find() ) {

            final int originalLength    =   _rawText.length();

            final String targetText = ( StringUtils.isBlank( _target ) ) ? "" :  " target=\"" + _target.trim() + "\"";
            final int targetLength      =   targetText.length();

            // Counted 15 characters aside from the target + 2 of the URL (max if the whole string is URL)
            // Rough guess, but should keep us from expanding the Builder too many times.
            final StringBuilder returnBuffer = new StringBuilder( originalLength * 2 + targetLength + 15 );

            int currentStart;
            int currentEnd;
            int lastEnd     = 0;

            String currentURL;

            do {
                currentStart = matcher.start();
                currentEnd = matcher.end();
                currentURL = matcher.group();

                // Adjust for URLs wrapped in ()'s ... move start/end markers
                //      and substring the _rawText for new URL value.
                while ( currentURL.startsWith( "(" ) && currentURL.endsWith( ")" ) ) {
                    currentStart = currentStart + 1;
                    currentEnd = currentEnd - 1;

                    currentURL = _rawText.substring( currentStart, currentEnd );
                }

                while ( currentURL.startsWith( "(" ) ) {
                    currentStart = currentStart + 1;

                    currentURL = _rawText.substring( currentStart, currentEnd );
                }

                // Text since last match
                returnBuffer.append( HtmlUtil.encode( _rawText.substring( lastEnd, currentStart ) ) );

                // Wrap matched URL
                returnBuffer.append( "<a href=\"" + currentURL + "\"" + targetText + ">" + currentURL + "</a>" );

                lastEnd = currentEnd;

            } while ( matcher.find() );

            if ( lastEnd < originalLength ) {
                returnBuffer.append( HtmlUtil.encode( _rawText.substring( lastEnd ) ) );
            }

            returnValue = returnBuffer.toString();
        }
    } 

    if ( returnValue == null ) {
        returnValue = HtmlUtil.encode( _rawText );
    }

    return returnValue;

}
2
Jacob Zwiers

J'ai fait une petite bibliothèque qui fait exactement ceci:

https://github.com/robinst/autolink-Java

Quelques exemples délicats et les liens détectés:

1
robinst

Suggérer un moyen plus pratique de le faire en 2017:

<TextView
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:autoLink="web"
    Android:linksClickable="true"/>

ou Android:autoLink="all" pour toutes sortes de liens.

0
Beeing Jk

Vous posez deux questions distinctes.

  1. Quel est le meilleur moyen d'identifier les URL dans Strings? Voir ce fil de discussion
  2. Comment coder la solution ci-dessus en Java? d'autres réponses illustrant l'utilisation de String.replaceAll ont résolu ce problème
0
ykaganovich

Un bon raffinement à la réponse de PhiLho serait: msg.replaceAll("(?:https?|ftps?)://[\w/%.-][/\??\w=?\w?/%.-]?[/\?&\w=?\w?/%.-]*", "$0");

0
Sérgio Nunes

Primitif:

String msg = "Please go to http://stackoverflow.com";
String withURL = msg.replaceAll("(?:https?|ftps?)://[\\w/%.-]+", "<a href='$0'>$0</a>");
System.out.println(withURL);

Cela nécessite des améliorations, pour correspondre aux URL appropriées, et en particulier aux paramètres GET (? Foo = bar & x = 25)

0
PhiLho

J'ai écrit mon propre extracteur d'URI/URL et je me suis dit que quelqu'un pourrait trouver cela utile étant donné que IMHO est meilleur que les autres réponses parce que:

  • Son basé sur le flux et peut être utilisé sur de gros documents
  • Il est extensible pour traiter toutes sortes de "Atwood Paren" problèmes à travers une chaîne de stratégie.

Comme le code est un peu long pour un article (bien qu’un seul fichier Java), je l’ai mis sur Gist github .

Voici une signature de l’une des méthodes principales pour l’appeler afin de montrer comment elle répond aux points ci-dessus:

public static Iterator<ExtractedURI> extractURIs(
    final Reader reader,
    final Iterable<ToURIStrategy> strategies,
    String ... schemes);

Il existe une chaîne de stratégie par défaut qui gère la plupart des problèmes Atwood.

public static List<ToURIStrategy> DEFAULT_STRATEGY_CHAIN = ImmutableList.of(
    new RemoveSurroundsWithToURIStrategy("'"),
    new RemoveSurroundsWithToURIStrategy("\""),
    new RemoveSurroundsWithToURIStrategy("(", ")"),
    new RemoveEndsWithToURIStrategy("."),
    DEFAULT_STRATEGY,
    REMOVE_LAST_STRATEGY);

Prendre plaisir!

0
Adam Gent