4
\$\begingroup\$

I'm fetching tweets from the Twitter API and display them in a TextBlock using multiple Inline elements so that I can highlight (and link) entities embedded in the tweet: hashtags, urls and user_mentions. media entities are currently ignored.

Tweets also usually contains emojis. From the API they are returned as two unicode characters. Even though they are displayed properly in the TextBlock, due to the nature of C#, the fact that they consist of (at least) two characters, the indices in the tweets are "off" when trying to locate the entities in the tweet because the indices count emojis as one character, and C# counts them as two.

Because I'm using MVVM, and there's no way of binding to the inlines in a TextBlock, I have created a dependency property that I can bind to. Through an IValueConverter I can bind a tweet to a TextBlock, and the converter creates inlines, which the dependency property puts into the TextBlock.

I have managed to display tweets correctly, but there are quite a few checks and balances to handle when looping through the entities and creating the inlines, so I would really appreciate some input on how (if?) this can be handled more efficiently.

So I present to you, my ValueConverter

public class TweetToTextblockConverter : IValueConverter
{
 public object Convert(object value, Type targetType,
 object parameter, CultureInfo culture)
 {
 Tweet tweet = value as Tweet;
 // Tweet is my wrapper class for tweets from Twitter API.
 // It contains the same property values as the API object, but in a
 // more properly named fashion
 if (tweet == null) return value;
 List<Inline> textList = new List<Inline>();
 if (tweet.Entities.Any())
 {
 // Unicode emojis take up two characters in a string, and are counted as two,
 // so combine all unicode characters to one character, and get old vs. new
 // indices, and use this as a lookup when adding entities
 int[] textIndices = StringInfo.ParseCombiningCharacters(tweet.Text);
 int prevIndex = 0;
 foreach (Entity entity in tweet.Entities.OrderBy(e => e.StartIndex))
 {
 // Display text between the current and previous entity
 // (or start of tweet text)
 int length = textIndices[entity.StartIndex] - prevIndex;
 if (length < 0) length = 0;
 prevIndex = prevIndex > tweet.Text.Length ? tweet.Text.Length : prevIndex;
 string subText = tweet.Text.Substring(prevIndex, length);
 textList.Add(new Run(subText));
 // Add a link to the entity
 Hyperlink hyperlink = new Hyperlink
 {
 NavigateUri = entity.Uri,
 ToolTip = entity.Tooltip,
 BaselineAlignment = BaselineAlignment.Center
 };
 hyperlink.Inlines.Add(entity.DisplayUri);
 hyperlink.RequestNavigate += Hyperlink_RequestNavigate;
 textList.Add(hyperlink);
 // Save the end index of the entity so that the next run through the
 // loop can display the text between this entity and the next one
 if (entity.EndIndex > textIndices.Length)
 {
 prevIndex = textIndices.Length - 1;
 }
 else
 {
 prevIndex = textIndices[entity.EndIndex - 1] + 1;
 }
 }
 // Add the last text that will not be added by the above loop
 int finalLength = tweet.Text.Length - prevIndex;
 if (finalLength < 0) finalLength = 0;
 prevIndex = prevIndex > tweet.Text.Length ? tweet.Text.Length : prevIndex;
 string finalSubText = tweet.Text.Substring(prevIndex, finalLength);
 textList.Add(new Run(finalSubText));
 }
 else
 {
 textList.Add(new Run(tweet.Text));
 }
 return textList;
 }
 private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
 {
 // This will open inline hyperlinks in the user's default browser
 Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
 }
 public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
 {
 throw new NotImplementedException();
 }
}
asked Jun 23, 2017 at 22:57
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

There are two code blocks that are basically identical. This

int length = textIndices[entity.StartIndex] - prevIndex;
if (length < 0) length = 0;
prevIndex = prevIndex > tweet.Text.Length ? tweet.Text.Length : prevIndex;
string subText = tweet.Text.Substring(prevIndex, length);

and that

int finalLength = tweet.Text.Length - prevIndex;
if (finalLength < 0) finalLength = 0;
prevIndex = prevIndex > tweet.Text.Length ? tweet.Text.Length : prevIndex;
string finalSubText = tweet.Text.Substring(prevIndex, finalLength);

You should try to encapsulate this logic otherwise if you need to change anything later you'll need to think to do it twice.


Alternatively you can attach the item that you process outside the loop to the entity collection and let the loop do the same job for it too:

tweet.Entities.OrderBy(e => e.StartIndex).Concat(new [] { Entity { ... } })

this way you wouldn't have to copy/paste the repeated logic. And if it doesn't have a link or an URL then it's easier to add an if to check it then duplicating the code.

answered Jun 24, 2017 at 13:24
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.