public void ExtractMentionsOrListsWithIndicesTest() { List <string> failures = new List <string>(); foreach (dynamic test in LoadTestSection <dynamic>("mentions_or_lists_with_indices")) { try { List <Extractor.Entity> actual = extractor.ExtractMentionsOrListsWithIndices(test.text); for (int i = 0; i < actual.Count; i++) { Extractor.Entity entity = actual[i]; Assert.AreEqual(test.expected[i].screen_name, entity.Value); Assert.AreEqual(test.expected[i].list_slug, entity.ListSlug); Assert.AreEqual(test.expected[i].indices[0], entity.Start); Assert.AreEqual(test.expected[i].indices[1], entity.End); } } catch (Exception) { failures.Add(test.description + ": " + test.text); } } if (failures.Any()) { Assert.Fail(string.Join("\n", failures)); } }
/// <summary> /// /// </summary> /// <param name="entity"></param> /// <param name="text"></param> /// <param name="builder"></param> public void LinkToCashtag(Extractor.Entity entity, string text, StringBuilder builder) { string cashtag = entity.Value; IDictionary <string, string> attrs = new Dictionary <string, string>(); attrs["href"] = CashtagUrlBase + cashtag; attrs["title"] = "$" + cashtag; attrs["class"] = CashtagClass; LinkToTextWithSymbol(entity, "$", cashtag, attrs, builder); }
public void URLEntitiesTest() { autolink.NoFollow = true; Extractor.Entity entity = new Extractor.Entity(0, 19, "http://t.co/0JG5Mcq", Extractor.EntityType.Url); entity.DisplayURL = "blog.twitter.com/2011/05/twitte…"; entity.ExpandedURL = "http://blog.twitter.com/2011/05/twitter-for-mac-update.html"; List <Extractor.Entity> entities = new List <Extractor.Entity>(); entities.Add(entity); String tweet = "http://t.co/0JG5Mcq"; String expected = "<a href=\"http://t.co/0JG5Mcq\" title=\"http://blog.twitter.com/2011/05/twitter-for-mac-update.html\" rel=\"nofollow\"><span class='tco-ellipsis'><span style='position:absolute;left:-9999px;'> </span></span><span style='position:absolute;left:-9999px;'>http://</span><span class='js-display-url'>blog.twitter.com/2011/05/twitte</span><span style='position:absolute;left:-9999px;'>r-for-mac-update.html</span><span class='tco-ellipsis'><span style='position:absolute;left:-9999px;'> </span>…</span></a>"; AssertAutolink(expected, autolink.AutoLinkEntities(tweet, entities)); }
/// <summary> /// /// </summary> /// <param name="entity"></param> /// <param name="symbol"></param> /// <param name="text"></param> /// <param name="attributes"></param> /// <param name="builder"></param> public void LinkToTextWithSymbol(Extractor.Entity entity, string symbol, string text, IDictionary <string, string> attributes, StringBuilder builder) { string taggedSymbol = string.IsNullOrWhiteSpace(SymbolTag) ? symbol : string.Format("<{0}>{1}</{0}>", SymbolTag, symbol); text = EscapeHTML(text); string taggedText = string.IsNullOrWhiteSpace(TextWithSymbolTag) ? text : string.Format("<{0}>{1}</{0}>", TextWithSymbolTag, text); bool includeSymbol = UsernameIncludeSymbol || !Regex.AT_SIGNS.IsMatch(symbol); if (includeSymbol) { LinkToText(entity, taggedSymbol.ToString() + taggedText, attributes, builder); } else { builder.Append(taggedSymbol); LinkToText(entity, taggedText, attributes, builder); } }
/// <summary> /// /// </summary> /// <param name="entity"></param> /// <param name="text"></param> /// <param name="builder"></param> public void LinkToMentionAndList(Extractor.Entity entity, string text, StringBuilder builder) { string mention = entity.Value; // Get the original at char from text as it could be a full-width char. string atChar = text.Substring(entity.Start, 1); IDictionary <string, string> attrs = new Dictionary <string, string>(); if (entity.ListSlug != null) { mention += entity.ListSlug; attrs["class"] = ListClass; attrs["href"] = ListUrlBase + mention; } else { attrs["class"] = UsernameClass; attrs["href"] = UsernameUrlBase + mention; } LinkToTextWithSymbol(entity, atChar, mention, attrs, builder); }
/// <summary> /// /// </summary> /// <param name="entity"></param> /// <param name="text"></param> /// <param name="builder"></param> public void LinkToHashtag(Extractor.Entity entity, string text, StringBuilder builder) { // Get the original hash char from text as it could be a full-width char. string hashChar = text.Substring(entity.Start, 1); string hashtag = entity.Value; IDictionary <string, string> attrs = new Dictionary <string, string>(); attrs["href"] = HashtagUrlBase + hashtag; attrs["title"] = "#" + hashtag; if (Regex.RTL_CHARACTERS.IsMatch(text)) { attrs["class"] = HashtagClass + " rtl"; } else { attrs["class"] = HashtagClass; } LinkToTextWithSymbol(entity, hashChar, hashtag, attrs, builder); }
/// <summary> /// /// </summary> /// <param name="entity"></param> /// <param name="text"></param> /// <param name="attributes"></param> /// <param name="builder"></param> public void LinkToText(Extractor.Entity entity, string text, IDictionary <string, string> attributes, StringBuilder builder) { if (NoFollow) { attributes["rel"] = "nofollow"; } if (LinkAttributeModifier != null) { LinkAttributeModifier.Modify(entity, attributes); } if (LinkTextModifier != null) { text = LinkTextModifier.Modify(entity, text); } // append <a> tag builder.Append("<a"); foreach (var entry in attributes) { builder.Append(" ").Append(EscapeHTML(entry.Key)).Append("=\"").Append(EscapeHTML(entry.Value)).Append("\""); } builder.Append(">").Append(text).Append("</a>"); }
/// <summary> /// /// </summary> /// <param name="entity"></param> /// <param name="text"></param> /// <param name="builder"></param> public void LinkToURL(Extractor.Entity entity, string text, StringBuilder builder) { string url = entity.Value; string linkText = EscapeHTML(url); if (entity.DisplayURL != null && entity.ExpandedURL != null) { // Goal: If a user copies and pastes a tweet containing t.co'ed link, the resulting paste // should contain the full original URL (expanded_url), not the display URL. // // Method: Whenever possible, we actually emit HTML that contains expanded_url, and use // font-size:0 to hide those parts that should not be displayed (because they are not part of display_url). // Elements with font-size:0 get copied even though they are not visible. // Note that display:none doesn't work here. Elements with display:none don't get copied. // // Additionally, we want to *display* ellipses, but we don't want them copied. To make this happen we // wrap the ellipses in a tco-ellipsis class and provide an onCopy handler that sets display:none on // everything with the tco-ellipsis class. // // As an example: The user tweets "hi http://longdomainname.com/foo" // This gets shortened to "hi http://t.co/xyzabc", with display_url = "…nname.com/foo" // This will get rendered as: // <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied --> // … // <!-- There's a chance the onCopy event handler might not fire. In case that happens, // we include an here so that the … doesn't bump up against the URL and ruin it. // The is inside the tco-ellipsis span so that when the onCopy handler *does* // fire, it doesn't get copied. Otherwise the copied text would have two spaces in a row, // e.g. "hi http://longdomainname.com/foo". // <span style='font-size:0'> </span> // </span> // <span style='font-size:0'> <!-- This stuff should get copied but not displayed --> // http://longdomai // </span> // <span class='js-display-url'> <!-- This stuff should get displayed *and* copied --> // nname.com/foo // </span> // <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied --> // <span style='font-size:0'> </span> // … // </span> // // Exception: pic.twitter.com images, for which expandedUrl = "https://twitter.com/#!/username/status/1234/photo/1 // For those URLs, display_url is not a substring of expanded_url, so we don't do anything special to render the elided parts. // For a pic.twitter.com URL, the only elided part will be the "https://", so this is fine. string displayURLSansEllipses = entity.DisplayURL.Replace("…", ""); int diplayURLIndexInExpandedURL = entity.ExpandedURL.IndexOf(displayURLSansEllipses); if (diplayURLIndexInExpandedURL != -1) { string beforeDisplayURL = entity.ExpandedURL.Substring(0, diplayURLIndexInExpandedURL); string afterDisplayURL = entity.ExpandedURL.Substring(diplayURLIndexInExpandedURL + displayURLSansEllipses.Length); string precedingEllipsis = entity.DisplayURL.StartsWith("…") ? "…" : ""; string followingEllipsis = entity.DisplayURL.EndsWith("…") ? "…" : ""; string invisibleSpan = "<span " + InvisibleTagAttrs + ">"; StringBuilder sb = new StringBuilder("<span class='tco-ellipsis'>"); sb.Append(precedingEllipsis); sb.Append(invisibleSpan).Append(" </span></span>"); sb.Append(invisibleSpan).Append(EscapeHTML(beforeDisplayURL)).Append("</span>"); sb.Append("<span class='js-display-url'>").Append(EscapeHTML(displayURLSansEllipses)).Append("</span>"); sb.Append(invisibleSpan).Append(EscapeHTML(afterDisplayURL)).Append("</span>"); sb.Append("<span class='tco-ellipsis'>").Append(invisibleSpan).Append(" </span>").Append(followingEllipsis).Append("</span>"); linkText = sb.ToString(); } else { linkText = entity.DisplayURL; } } IDictionary <string, string> attrs = new Dictionary <string, string>(); attrs["href"] = url; if (!string.IsNullOrWhiteSpace(entity.DisplayURL) && !string.IsNullOrWhiteSpace(entity.ExpandedURL)) { attrs["title"] = entity.ExpandedURL; } if (!string.IsNullOrWhiteSpace(UrlClass)) { attrs["class"] = UrlClass; } if (!string.IsNullOrWhiteSpace(UrlTarget)) { attrs["target"] = UrlTarget; } LinkToText(entity, linkText, attrs, builder); }