2009-04-16 13 views
29

Czy ktoś ma dobry kod C# (i wyrażenia regularne), który będzie parsował ciąg znaków i "łączył" dowolne adresy URL, które mogą znajdować się w ciągu znaków?Kod C# do linki na adresy URL w ciągu znaków

+0

To wydaje się być pytanie z oparty wyrażenie-Kanoniczne regularne rozwiązanie. Być może ktoś mógłby edytować tytuł, aby pomóc go znaleźć? – JasonSmith

Odpowiedz

42

Jest to dość proste zadanie można acheive go Regex i gotowy do przejść wyrażenie regularne od:

Coś jak:

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

Ty może również zainteresować się nie tylko tworzeniem linków, ale także skracaniem adresów URL. Oto dobry artykuł na ten temat:

Zobacz również:

+1

Hi. Świetna odpowiedź. Większość sugestii w twoim poście (i linkach) wydaje się działać, ale wszystkie wydają się łamać wszelkie istniejące linki w ocenianym tekście. –

+0

VSmith możesz wypróbować różne wyrażenia reg z regixlib.com i znaleźć, który z nich działa najlepiej dla Ciebie. –

+0

@VSmith: Sugerujesz, że masz ciąg taki jak "hello there, zobacz: http://www.b.com"; a ty chcesz tylko połączyć drugą? –

4

To nie jest takie proste, jak można przeczytać w tym blog post by Jeff Atwood. Szczególnie trudno jest wykryć, gdzie kończy się adres URL.

Na przykład, część tylną nawiasie URL czy:

  • http ​: //en.wikipedia.org/wiki/PCTools (CentralPointSoftware)
  • URL w nawiasach (http ​: //en.wikipedia.org) więcej tekstu

W pierwszym przypadku nawiasy są częścią adresu URL. W drugim przypadku nie są!

+1

I jak widać z adresów URL w tej odpowiedzi, nie każdy to robi dobrze :) – Ray

+0

Tak naprawdę, nie chciałem, aby te dwa adresy URL były opatrzone. Ale wygląda na to, że nie jest to obsługiwane. – M4N

+0

Wyrażenie Jeffa wygląda źle w mojej przeglądarce, uważam, że powinno być: "\ (? \ 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

Pozdrawiamy za opublikowanie tego :) –

+0

Skończyło się na użyciu czegoś bardzo podobnego, z jedną modyfikacją. W końcu upewniliśmy się, że wymiana nastąpi tylko raz. Oznacza to, że w efekcie stracimy niektóre linki (linki, które występują więcej niż jeden raz), ale usuniemy możliwość zniekształconych linków w dwóch przypadkach: 1) Kiedy są dwa łącza, gdzie jeden jest bardziej szczegółowy niż drugi. np. "http://google.com http://google.com/reader" 2) Kiedy jest mieszanka linków HTML ze zwykłymi linkami tekstowymi. np. "Http://google.com Google" if (input.IndexOf (match.Value) == input.LastIndexOf (match.Value)) { ... } –

10

dobrze, po wiele badań na ten temat, a kilka prób, aby naprawić razy kiedy

  1. ludzi wchodzi w http://www.sitename.com i www.sitename.com w tym samym poście
  2. poprawki do parenthisis podobne (http://www.sitename.com) i http://msdn.microsoft.com/en-us/library/aa752574(vs.85).aspx
  3. długich adresy, takie jak: 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

używamy teraz rozszerzenia HtmlHelper ...myślałem, że dzielić się i uzyskać wszelkie komentarze:

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

Istnieje klasa:

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 
} 

Użyj go w ten sposób:

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

przypadków testowych:

[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

To działa dobrze, jednak pasuje na takie rzeczy jak o.context lub inny ciąg, który ma w nich okres. Byłoby miło zmusić .com/.org/.net etc, gdzieś w łańcuchu –

+0

Wymusza to również www, co nie zawsze ma miejsce. –

Powiązane problemy