2009-04-16 6 views
29

Quelqu'un at-il un bon code C# (et des expressions régulières) qui va analyser une chaîne et "linkify" toutes les URLs qui peuvent être dans la chaîne?Code C# pour lier les URL dans une chaîne

+0

Cela semble être la question à la base d'une expression régulière canonique Solution. Peut-être que quelqu'un pourrait éditer le titre pour aider les chercheurs à le trouver? – JasonSmith

Répondre

42

Il est une tâche assez simple, vous pouvez acheive avec Regex et un prêt-à-go expression régulière de:

Quelque chose comme:

var html = Regex.Replace(html, @"^(http|https|ftp)\://[a-zA-Z0-9\-\.]+" + 
         "\.[a-zA-Z]{2,3}(:[a-zA-Z0-9]*)?/?" + 
         "([a-zA-Z0-9\-\._\?\,\'/\\\+&%\$#\=~])*$", 
         "<a href=\"$1\">$1</a>"); 

Vous peut également être intéressé non seulement par la création de liens, mais aussi par le raccourcissement d'URL. Voici un bon article sur ce sujet:

Voir aussi:

+1

Bonjour. Bonne réponse. La plupart des suggestions dans votre message (et liens) semblent fonctionner mais elles semblent toutes casser n'importe quels liens existants dans le texte évalué. –

+0

VSvous pouvez essayer différentes expressions reg de regixlib.com et trouver celle qui vous convient le mieux. –

+0

@VSmith: Voulez-vous dire que vous avez une chaîne comme "bonjour there, voir: http://www.b.com"; et vous voulez seulement relier le second? –

4

Il est pas si facile que vous pouvez lire dans ce blog post by Jeff Atwood. Il est particulièrement difficile de détecter la fin d'une URL.

Par exemple, est la partie entre parenthèses de fin de l'URL ou non:

  • http ​: //en.wikipedia.org/wiki/PCTools (CentralPointSoftware)
  • une URL entre parenthèses (http ​: //en.wikipedia.org) plus de texte

Dans le premier cas, les parenthèses font partie de l'URL. Dans le second cas, ils ne le sont pas!

+1

Et comme vous pouvez le voir à partir des URLs linkées dans cette réponse, tout le monde ne le comprend pas correctement :) – Ray

+0

Eh bien, en fait, je ne voulais pas que les deux URL soient liées. Mais il semble que ce n'est pas pris en charge. – M4N

+0

L'expression rationnelle de Jeff semble mal afficher dans mon navigateur, je crois que cela devrait être: "\ (? \ Bhttp: // [-A-Za-z0-9 + & @ # /%? = ~ _() | !: ,.;] * [- A-Za-z0-9 + & @ # /% = ~ _() |] " –

6
protected string Linkify(string SearchText) { 
    // this will find links like: 
    // http://www.mysite.com 
    // as well as any links with other characters directly in front of it like: 
    // href="http://www.mysite.com" 
    // you can then use your own logic to determine which links to linkify 
    Regex regx = new Regex(@"\b(((\S+)?)(@|mailto\:|(news|(ht|f)tp(s?))\://)\S+)\b", RegexOptions.IgnoreCase); 
    SearchText = SearchText.Replace("&nbsp;", " "); 
    MatchCollection matches = regx.Matches(SearchText); 

    foreach (Match match in matches) { 
     if (match.Value.StartsWith("http")) { // if it starts with anything else then dont linkify -- may already be linked! 
      SearchText = SearchText.Replace(match.Value, "<a href='" + match.Value + "'>" + match.Value + "</a>"); 
     } 
    } 

    return SearchText; 
} 
+0

Bravo pour poster celui-là :) –

+0

Nous avons fini par utiliser quelque chose de très similaire, avec une modification. Nous avons fini par nous assurer que le remplacement ne se produit qu'une seule fois. Cela signifie que nous allons manquer certains liens (liens qui se produisent plus d'une fois) mais supprime la possibilité de liens tronqués dans deux cas: 1) Quand il y a deux liens où l'un est plus détaillé que l'autre. par exemple. "http://google.com http://google.com/reader" 2) Lorsqu'il existe un mélange de liens HTML avec des liens en texte brut. par exemple. "Http://google.com Google" si (input.IndexOf (match.Value) == input.LastIndexOf (match.Value)) { ...} –

10

bien, après beaucoup de recherches sur ce sujet, et plusieurs tentatives de fixer des moments où

  1. personnes entrent dans http://www.sitename.com et www.sitename.com dans le même poste
  2. fixe à parenthisis comme (http://www.sitename.com) et http://msdn.microsoft.com/en-us/library/aa752574(vs.85).aspx
  3. longues urls comme: http://www.amazon.com/gp/product/b000ads62g/ref=s9_simz_gw_s3_p74_t1?pf_rd_m=atvpdkikx0der&pf_rd_s=center-2&pf_rd_r=04eezfszazqzs8xfm9yd&pf_rd_t=101&pf_rd_p=470938631&pf_rd_i=507846

nous utilisons maintenant cette extension HtmlHelper ...Je pensais partager et obtenir des commentaires:

private static Regex regExHttpLinks = new Regex(@"(?<=\()\b(https?://|www\.)[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|](?=\))|(?<=(?<wrap>[=~|_#]))\b(https?://|www\.)[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|](?=\k<wrap>)|\b(https?://|www\.)[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|]", RegexOptions.Compiled | RegexOptions.IgnoreCase); 

    public static string Format(this HtmlHelper htmlHelper, string html) 
    { 
     if (string.IsNullOrEmpty(html)) 
     { 
      return html; 
     } 

     html = htmlHelper.Encode(html); 
     html = html.Replace(Environment.NewLine, "<br />"); 

     // replace periods on numeric values that appear to be valid domain names 
     var periodReplacement = "[[[replace:period]]]"; 
     html = Regex.Replace(html, @"(?<=\d)\.(?=\d)", periodReplacement); 

     // create links for matches 
     var linkMatches = regExHttpLinks.Matches(html); 
     for (int i = 0; i < linkMatches.Count; i++) 
     { 
      var temp = linkMatches[i].ToString(); 

      if (!temp.Contains("://")) 
      { 
       temp = "http://" + temp; 
      } 

      html = html.Replace(linkMatches[i].ToString(), String.Format("<a href=\"{0}\" title=\"{0}\">{1}</a>", temp.Replace(".", periodReplacement).ToLower(), linkMatches[i].ToString().Replace(".", periodReplacement))); 
     } 

     // Clear out period replacement 
     html = html.Replace(periodReplacement, "."); 

     return html; 
    } 
1

Il est classe:

public class TextLink 
{ 
    #region Properties 

    public const string BeginPattern = "((http|https)://)?(www.)?"; 

    public const string MiddlePattern = @"([a-z0-9\-]*\.)+[a-z]+(:[0-9]+)?"; 

    public const string EndPattern = @"(/\S*)?"; 

    public static string Pattern { get { return BeginPattern + MiddlePattern + EndPattern; } } 

    public static string ExactPattern { get { return string.Format("^{0}$", Pattern); } } 

    public string OriginalInput { get; private set; } 

    public bool Valid { get; private set; } 

    private bool _isHttps; 

    private string _readyLink; 

    #endregion 

    #region Constructor 

    public TextLink(string input) 
    { 
     this.OriginalInput = input; 

     var text = Regex.Replace(input, @"(^\s)|(\s$)", "", RegexOptions.IgnoreCase); 

     Valid = Regex.IsMatch(text, ExactPattern); 

     if (Valid) 
     { 
      _isHttps = Regex.IsMatch(text, "^https:", RegexOptions.IgnoreCase); 
      // clear begin: 
      _readyLink = Regex.Replace(text, BeginPattern, "", RegexOptions.IgnoreCase); 
      // HTTPS 
      if (_isHttps) 
      { 
       _readyLink = "https://www." + _readyLink; 
      } 
      // Default 
      else 
      { 
       _readyLink = "http://www." + _readyLink; 
      } 
     } 
    } 

    #endregion 

    #region Methods 

    public override string ToString() 
    { 
     return _readyLink; 
    } 

    #endregion 
} 

utiliser dans cette méthode:

public static string ReplaceUrls(string input) 
{ 
    var result = Regex.Replace(input.ToSafeString(), TextLink.Pattern, match => 
    { 
     var textLink = new TextLink(match.Value); 
     return textLink.Valid ? 
      string.Format("<a href=\"{0}\" target=\"_blank\">{1}</a>", textLink, textLink.OriginalInput) : 
      textLink.OriginalInput; 
    }); 
    return result; 
} 

cas de test:

[TestMethod] 
public void RegexUtil_TextLink_Parsing() 
{ 
    Assert.IsTrue(new TextLink("smthing.com").Valid); 
    Assert.IsTrue(new TextLink("www.smthing.com/").Valid); 
    Assert.IsTrue(new TextLink("http://smthing.com").Valid); 
    Assert.IsTrue(new TextLink("http://www.smthing.com").Valid); 
    Assert.IsTrue(new TextLink("http://www.smthing.com/").Valid); 
    Assert.IsTrue(new TextLink("http://www.smthing.com/publisher").Valid); 

    // port 
    Assert.IsTrue(new TextLink("http://www.smthing.com:80").Valid); 
    Assert.IsTrue(new TextLink("http://www.smthing.com:80/").Valid); 
    // https 
    Assert.IsTrue(new TextLink("https://smthing.com").Valid); 

    Assert.IsFalse(new TextLink("").Valid); 
    Assert.IsFalse(new TextLink("smthing.com.").Valid); 
    Assert.IsFalse(new TextLink("smthing.com-").Valid); 
} 

[TestMethod] 
public void RegexUtil_TextLink_ToString() 
{ 
    // default 
    Assert.AreEqual("http://www.smthing.com", new TextLink("smthing.com").ToString()); 
    Assert.AreEqual("http://www.smthing.com", new TextLink("http://www.smthing.com").ToString()); 
    Assert.AreEqual("http://www.smthing.com/", new TextLink("smthing.com/").ToString()); 

    Assert.AreEqual("https://www.smthing.com", new TextLink("https://www.smthing.com").ToString()); 
} 
+0

Cela fonctionne bien, mais il correspond à des choses comme o.context, ou une autre chaîne qui a une période en eux. Serait bien de forcer .com/.org/.net etc, quelque part dans la chaîne –

+0

Aussi, il force www, ce qui n'est pas toujours le cas. –

Questions connexes