Auto detect URL, phone number, email in TextBlock

回眸只為那壹抹淺笑 提交于 2019-12-21 06:28:12

问题


I would like to automatically highlight URL, Email and phone number in UWP. It is possible in Android but it seems this features has been forgotten by Microsoft. In my use case, I get the text from a web service, so I don't know the text format which is a user text input on the web platform.


回答1:


The platform is not supporting this feature (yet). When I've to do the same thing, I've ended with my own solution which is to:

  • create an attached property receiving the text to format
  • use some regex to extract the URL, phone numbers, email addresses from it
  • generate an Inlines collection which I'm injecting to the attached TextBlock control

The regex used are covering a lot of cases but some edge cases can be still missing.

It is used this way:

<TextBlock  uwpext:TextBlock.InteractiveText="Here is a link www.bing.com to send to a@a.com or 0000000000" />

The attached property code:

    // -------------------------------------------------------------------------------------------
    /// <summary>
    /// The regex to detect the URL from the text content
    /// It comes from https://gist.github.com/gruber/249502 (http://daringfireball.net/2010/07/improved_regex_for_matching_urls)
    /// </summary>
    private static readonly Regex UrlRegex = new Regex(@"(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'"".,<>?«»“”‘’]))", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(500));

    // -------------------------------------------------------------------------------------------
    /// <summary>
    /// The regex to detect the email addresses
    /// It comes from https://msdn.microsoft.com/en-us/library/01escwtf.aspx
    /// </summary>
    private static readonly Regex EmailRegex    = new Regex(@"(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(500));

    // -------------------------------------------------------------------------------------------
    /// <summary>
    /// The regex to detect the phone numbers from the raw message
    /// </summary>
    private static readonly Regex PhoneRegex    = new Regex(@"\+?[\d\-\(\)\. ]{5,}", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250));

    // -------------------------------------------------------------------------------------------
    /// <summary>
    /// The default prefix to use to convert a relative URI to an absolute URI
    /// The Windows RunTime is only working with absolute URI
    /// </summary>
    private const string    RelativeUriDefaultPrefix    = "http://";


    // -------------------------------------------------------------------------------------------
    /// <summary>
    /// The dependency property to generate an interactive text in a text block.
    /// When setting this property, we will parse the value and transform the hyperlink or the email address to interactive fields that the user can interact width.
    /// The raw text will be parsed and convert to a collection of inlines.
    /// </summary>
    public static readonly DependencyProperty InteractiveTextProperty = DependencyProperty.RegisterAttached("InteractiveText", typeof(string), typeof(TextBlock), new PropertyMetadata(null, OnInteractiveTextChanged));

    // -------------------------------------------------------------------------------------------
    /// <summary>
    /// The event callback for the interactive text changed event
    /// We will parse the raw text and generate the inlines that will wrap the interactive items (URL...)
    /// </summary>
    /// <param name="d">the object which has raised the event</param>
    /// <param name="e">the change information</param>
    private static void OnInteractiveTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var textBlock   = d as Windows.UI.Xaml.Controls.TextBlock;
        if(textBlock == null) return;

        // we remove all the inlines
        textBlock.Inlines.Clear();

        // if we have no data, we do not need to go further
        var rawText = e.NewValue as string;
        if(string.IsNullOrEmpty(rawText)) return;


        var lastPosition    = 0;
        var matches         = new Match[3];
        do
        {
            matches[0]  = UrlRegex.Match(rawText, lastPosition);
            matches[1]  = EmailRegex.Match(rawText, lastPosition);
            matches[2]  = PhoneRegex.Match(rawText, lastPosition);

            var firstMatch  = matches.Where(x => x.Success).OrderBy(x => x.Index).FirstOrDefault();
            if(firstMatch == matches[0])
            {
                // the first match is an URL
                CreateRunElement(textBlock, rawText, lastPosition, firstMatch.Index);
                lastPosition    = CreateUrlElement(textBlock, firstMatch);
            }
            else if(firstMatch == matches[1])
            {
                // the first match is an email
                CreateRunElement(textBlock, rawText, lastPosition, firstMatch.Index);
                lastPosition    = CreateContactElement(textBlock, firstMatch, null);
            }
            else if(firstMatch == matches[2])
            {
                // the first match is a phonenumber
                CreateRunElement(textBlock, rawText, lastPosition, firstMatch.Index);
                lastPosition    = CreateContactElement(textBlock, null, firstMatch);
            }
            else
            {
                // no match, we add the whole text
                textBlock.Inlines.Add(new Run { Text = rawText.Substring(lastPosition) });
                lastPosition    = rawText.Length;
            }  
        }
        while(lastPosition < rawText.Length);
    }

    // -------------------------------------------------------------------------------------------
    /// <summary>
    /// This method will extract a fragment of the raw text string, create a Run element with the fragment and
    /// add it to the textblock inlines collection
    /// </summary>
    /// <param name="textBlock">the textblock where to add the run element</param>
    /// <param name="rawText">the raw text where the fragment will be extracted</param>
    /// <param name="startPosition">the start position to extract the fragment</param>
    /// <param name="endPosition">the end position to extract the fragment</param>
    private static void CreateRunElement(Windows.UI.Xaml.Controls.TextBlock textBlock, string rawText, int startPosition, int endPosition)
    {
        var fragment    = rawText.Substring(startPosition, endPosition - startPosition);
        textBlock.Inlines.Add(new Run { Text = fragment });
    }

    // -------------------------------------------------------------------------------------------
    /// <summary>
    /// Create an URL element with the provided match result from the URL regex
    /// It will create the Hyperlink element that will contain the URL and add it to the provided textblock
    /// </summary>
    /// <param name="textBlock">the textblock where to add the hyperlink</param>
    /// <param name="urlMatch">the match for the URL to use to create the hyperlink element</param>
    /// <returns>the newest position on the source string for the parsing</returns>
    private static int CreateUrlElement(Windows.UI.Xaml.Controls.TextBlock textBlock, Match urlMatch)
    {
        Uri targetUri;
        if(Uri.TryCreate(urlMatch.Value, UriKind.RelativeOrAbsolute, out targetUri))
        {
            var link            = new Hyperlink();
            link.Inlines.Add(new Run { Text= urlMatch.Value });

            if(targetUri.IsAbsoluteUri)
                link.NavigateUri    = targetUri;
            else
                link.NavigateUri    = new Uri(RelativeUriDefaultPrefix + targetUri.OriginalString);


            textBlock.Inlines.Add(link);
        }
        else
        {
            textBlock.Inlines.Add(new Run { Text= urlMatch.Value });
        }

        return urlMatch.Index + urlMatch.Length;
    }

    // -------------------------------------------------------------------------------------------
    /// <summary>
    /// Create a hyperlink element with the provided match result from the regex that will open the contact application
    /// with the provided contact information (it should be a phone number or an email address
    /// This is used only if the email address / phone number is not prefixed with the mailto: / tel: scheme
    /// It will create the Hyperlink element that will contain the email/phone number hyperlink and add it to the provided textblock.
    /// Clicking on the link will open the contact application
    /// </summary>
    /// <param name="textBlock">the textblock where to add the hyperlink</param>
    /// <param name="emailMatch">the match for the email to use to create the hyperlink element. Set to null if not available but at least one of emailMatch and phoneMatch must be not null.</param>
    /// <param name="phoneMatch">the match for the phone number to create the hyperlink element. Set to null if not available but at least one of emailMatch and phoneMatch must be not null.</param>
    /// <returns>the newest position on the source string for the parsing</returns>
    private static int CreateContactElement(Windows.UI.Xaml.Controls.TextBlock textBlock, Match emailMatch, Match phoneMatch)
    {
        var currentMatch    = emailMatch ?? phoneMatch;

        var link            = new Hyperlink();
        link.Inlines.Add(new Run { Text= currentMatch.Value });
        link.Click          += (s, a) =>
        {
            var contact     = new Contact();
            if(emailMatch != null)  contact.Emails.Add(new ContactEmail { Address   = emailMatch.Value  });
            if(phoneMatch != null)  contact.Phones.Add(new ContactPhone { Number    = phoneMatch.Value.StripNonDigitsCharacters() });

            ContactManager.ShowFullContactCard(contact, new FullContactCardOptions());
        };

        textBlock.Inlines.Add(link);
        return currentMatch.Index + currentMatch.Length;
    }

    // -------------------------------------------------------------------------------------------
    /// <summary>
    /// Return the InteractiveText value on the provided object
    /// </summary>
    /// <param name="obj">the object to query</param>
    /// <returns>the InteractiveText value</returns>
    public static string GetInteractiveText(DependencyObject obj)
    {
        return (string) obj.GetValue(InteractiveTextProperty);
    }

    // -------------------------------------------------------------------------------------------
    /// <summary>
    /// SEt the InteractiveText value on the provided object
    /// </summary>
    /// <param name="obj">the object to query</param>
    /// <param name="value">the value to set</param>
    public static void SetInteractiveText(DependencyObject obj, string value)
    {
        obj.SetValue(InteractiveTextProperty, value);
    }


来源:https://stackoverflow.com/questions/38208741/auto-detect-url-phone-number-email-in-textblock

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!