4
\$\begingroup\$

I need a method which will parse text to RichTextBlock-compatible XAML markup with clickable links.

Example:

Input:

"Hello, check new service for students! Check edusty.ru and http://edusty.ru/test?text1=abc&args=123 sites. Check edusty.ru/privacy.html to read privacy policy."

Output:

<Paragraph xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" TextAlignment="Left" FontSize="20"  FontWeight="Normal" FontStyle="Normal" FontStretch="Normal" >
     <Run Text="Hello, check new service for students! Check "/> 
     <Hyperlink NavigateUri="http://edusty.ru">edusty.ru</Hyperlink> 
     <Run Text=" and "/>
     <Hyperlink NavigateUri="http://edusty.ru/test?text1=abc&amp;args=123">http://edusty.ru/test?text1=abc&amp;args=123</Hyperlink> 
     <Run Text=" sites. Check "/>
     <Hyperlink NavigateUri="http://edusty.ru/privacy.html">edusty.ru/privacy.html</Hyperlink>
     <Run Text=" to read privacy policy."></Run>
</Paragraph>

I wrote this method:

public static string ParseLinksToXaml(this string source)
        {
            var transformedText = Regex.Replace(source,
                @"((https?:\/\/)?(ftps?:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}(:[0-9]{1,5})?(\/\S*)?)",
                @"""/> <Hyperlink NavigateUri=""$1"">$1</Hyperlink> <Run Text=""", RegexOptions.IgnoreCase);
            transformedText = WebUtility.HtmlEncode(transformedText);
            var matches = Regex.Matches(transformedText, @"&quot;\/&gt;\s&lt;Hyperlink NavigateUri=&quot;.*?&quot;&gt;|&lt;\/Hyperlink&gt;\s&lt;Run Text=&quot;");
            foreach (var match in matches)
                transformedText = transformedText.Replace(match.ToString(), WebUtility.HtmlDecode(match.ToString()));

            matches = Regex.Matches(transformedText, @"NavigateUri=\""(.*?)"">");
            foreach (Match match in matches)
            {
                var link = match.Groups[1].ToString();
                link = link.Replace("&", "&amp;");
                link = link.Contains("://") ? link : "http://" + link;
                transformedText = transformedText.ReplaceFirst("\"" + match.Groups[1] + "\"", "\"" + link + "\"");
            }
            var xaml = @"<Paragraph xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" TextAlignment=""Left"" FontSize=""20""  FontWeight=""Normal"" FontStyle=""Normal"" FontStretch=""Normal"" >
                                    <Run Text=""" + transformedText + @"""></Run></Paragraph>";
            return xaml;
        }

It works, but the code really looks bad. Can anyone help rewrite/optimize this?

\$\endgroup\$
0

1 Answer 1

3
\$\begingroup\$

Whenever you're concatenating strings inside a loop's body, +-concatenation should be ruled out; transformedText would be a StringBuilder instance, to which each iteration would Append.

However XAML is a subset of XML; as such, there is no reason to treat your output as purely strings: using the classes in the System.Linq.Xml namespace (otherwise known as "Linq-to-Xml"), you can rewrite this in a much more elegant and object-oriented way.

Extending System.String like this is a bit overkill, too. I'm not sure I'd like working in a code base where every single string out there has this ParseLinksToXaml extension method: that functionality is very specialized, and IMO deserves its own class where the client code is actually explicit about what's going on:

var parser = new StringLinksToXamlConverter();
var xaml = parser.ParseLinksToXaml(myString);

The string-building stuff going on under the hood should be an implementation detail - the returned value would be potentially much more useful to the client code if an XDocument was returned, leaving it up to the client code to query or output as a string.

\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.