【问题标题】:Auto detect URL, phone number, email in TextBlock自动检测 TextBlock 中的 URL、电话号码、电子邮件
【发布时间】:2016-07-05 16:52:25
【问题描述】:

我想在 UWP 中自动突出显示 URL、电子邮件和电话号码。在 Android 中是可能的,但微软似乎已经忘记了这个功能。 在我的用例中,我从 Web 服务获取文本,所以我不知道 Web 平台上的用户文本输入的文本格式。

【问题讨论】:

    标签: xaml uwp textblock richtextblock


    【解决方案1】:

    平台不支持此功能(目前)。当我必须做同样的事情时,我以自己的解决方案结束:

    • 创建一个附加属性接收要格式化的文本
    • 使用一些正则表达式从中提取 URL、电话号码、电子邮件地址
    • 生成一个 Inlines 集合,我将其注入到附加的 TextBlock 控件中

    使用的正则表达式涵盖了很多情况,但仍可能缺少一些边缘情况。

    这样使用:

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

    附加属性代码:

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

    【讨论】:

    • 感谢您的代码,但我似乎有问题。我收到了初始化错误:“Le texte associé à ce code d'erreur est introuvable.Failed to assign to property 'MyProject.TextBlockExtensions.InteractiveText'。[Line: 38 Position: 20]”
    • 没有更多细节,很难看出哪里出了问题。根据错误消息,您的控件可能存在类型问题。该属性只能绑定到 TextBox。如果您尝试将其绑定到其他内容,则会引发此类错误。有关详细信息,请参阅此问题:stackoverflow.com/questions/17971195/…。您还可以从这里找到有用的信息:stackoverflow.com/questions/5832208/…。希望对你有帮助
    • 谢谢文森特,我发现我的错误:我的扩展类没有公开。现在看来效果不错。
    • 很好的解决方案!我稍微更改了电话正则表达式以仅包含独立号码:(^|\s)\+?[()0-9\s-/]{5,}($|\s)(匹配“呼叫:01189998819991197253”但不匹配“Errorcode_01189998819991197253”)
    猜你喜欢
    • 2012-03-10
    • 1970-01-01
    • 2019-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-12
    相关资源
    最近更新 更多