Example #1
0
        public static void ParseUrls(MessageModel msg)
        {
            // clone MessageParts
            IList <MessagePartModel> parts = new List <MessagePartModel>(msg.MessageParts);

            foreach (MessagePartModel part in parts)
            {
                if (part is UrlMessagePartModel)
                {
                    // no need to reparse URL parts
                    continue;
                }
                if (!(part is TextMessagePartModel))
                {
                    continue;
                }

                TextMessagePartModel textPart = (TextMessagePartModel)part;
                Match urlMatch = UrlRegex.Match(textPart.Text);
                // OPT: fast regex scan
                if (!urlMatch.Success)
                {
                    // no URLs in this MessagePart, nothing to do
                    continue;
                }

                // found URL(s)
                // remove current MessagePartModel as we need to split it
                int idx = msg.MessageParts.IndexOf(part);
                msg.MessageParts.RemoveAt(idx);

                string[] textPartParts = textPart.Text.Split(new char[] { ' ' });
                for (int i = 0; i < textPartParts.Length; i++)
                {
                    string textPartPart = textPartParts[i];
                    urlMatch = UrlRegex.Match(textPartPart);
                    if (urlMatch.Success)
                    {
                        UrlMessagePartModel urlPart = new UrlMessagePartModel(textPartPart);
                        //urlPart.ForegroundColor = new TextColor();
                        msg.MessageParts.Insert(idx++, urlPart);
                        msg.MessageParts.Insert(idx++, new TextMessagePartModel(" "));
                    }
                    else
                    {
                        // FIXME: we put each text part into it's own object, instead of combining them (the smart way)
                        TextMessagePartModel notUrlPart = new TextMessagePartModel(textPartPart + " ");
                        // restore formatting / colors from the original text part
                        notUrlPart.IsHighlight     = textPart.IsHighlight;
                        notUrlPart.ForegroundColor = textPart.ForegroundColor;
                        notUrlPart.BackgroundColor = textPart.BackgroundColor;
                        notUrlPart.Bold            = textPart.Bold;
                        notUrlPart.Italic          = textPart.Italic;
                        notUrlPart.Underline       = textPart.Underline;
                        msg.MessageParts.Insert(idx++, notUrlPart);
                    }
                }
            }
        }
Example #2
0
        public void ToMarkup()
        {
            MessageModel testmodel = new MessageModel();
            testmodel.IsCompactable = false;
            TextMessagePartModel textmodel;
            UrlMessagePartModel urlmodel;

            textmodel = new TextMessagePartModel("normal");
            testmodel.MessageParts.Add(textmodel);

            textmodel = new TextMessagePartModel("blue");
            textmodel.ForegroundColor = TextColor.Parse("0000FF");
            testmodel.MessageParts.Add(textmodel);

            textmodel = new TextMessagePartModel("bold");
            textmodel.Bold = true;
            testmodel.MessageParts.Add(textmodel);

            textmodel = new TextMessagePartModel("bold2");
            textmodel.Bold = true;
            testmodel.MessageParts.Add(textmodel);

            textmodel = new TextMessagePartModel("normal");
            testmodel.MessageParts.Add(textmodel);

            textmodel = new TextMessagePartModel("underline");
            textmodel.Underline = true;
            testmodel.MessageParts.Add(textmodel);

            textmodel = new TextMessagePartModel("combined");
            textmodel.Underline = true;
            textmodel.Bold = true;
            textmodel.Italic = true;
            textmodel.ForegroundColor = TextColor.Parse("00FF00");
            textmodel.BackgroundColor = TextColor.Parse("0000FF");
            testmodel.MessageParts.Add(textmodel);

            urlmodel = new UrlMessagePartModel("http://www.smuxi.org");
            testmodel.MessageParts.Add(urlmodel);

            textmodel = new TextMessagePartModel("normal");
            testmodel.MessageParts.Add(textmodel);

            string expected = "normal<span color='#0000FF'>blue</span>" +
                "<b>bold</b><b>bold2</b>normal<u>underline</u>" +
                "<span color='#00FF00'><u><b><i>combined</i></b></u></span>" +
                "<span color='#00008B'><u>http://www.smuxi.org</u></span>normal";
            string tested = PangoTools.ToMarkup(testmodel);

            Assert.AreEqual(expected, tested);
        }
Example #3
0
        void ParseHtml(XmlNode node, TextMessagePartModel model)
        {
            TextMessagePartModel submodel;
            string nodetype = node.Name.ToLower();

            if (model is UrlMessagePartModel)
            {
                submodel = new UrlMessagePartModel(model);
            }
            else if (nodetype == "a")
            {
                submodel = new UrlMessagePartModel(model);
                (submodel as UrlMessagePartModel).Url = node.Attributes.GetNamedItem("href").Value;
            }
            else
            {
                submodel = new TextMessagePartModel(model);
            }
            switch (nodetype)
            {
            case "b":
            case "strong":
                submodel.Bold = true;
                break;

            case "i":
            case "em":
                submodel.Italic = true;
                break;

            case "u":
                submodel.Underline = true;
                break;

            default:
                break;
            }
            if (node.Attributes != null)
            {
                ParseStyle(node.Attributes.GetNamedItem("style"), submodel);
            }
            if (node.HasChildNodes)
            {
                foreach (XmlNode child in node.ChildNodes)
                {
                    // clone this model
                    TextMessagePartModel nextmodel;
                    if (submodel is UrlMessagePartModel)
                    {
                        nextmodel = new UrlMessagePartModel(submodel);
                    }
                    else
                    {
                        nextmodel = new TextMessagePartModel(submodel);
                    }
                    ParseHtml(child, nextmodel);
                }
            }
            else
            {
                // final node
                if (nodetype == "br")
                {
                    AppendText("\n");
                }
                else if (nodetype == "img")
                {
                    AppendUrl(node.Attributes.GetNamedItem("src").Value, "[image placeholder - UNIMPLEMENTED]");
                }
                else
                {
                    model.Text = HttpUtility.HtmlDecode(node.Value);
                    AppendText(model);
                }
            }
        }
Example #4
0
        public static IList <MessagePartModel> ParsePatterns(TextMessagePartModel textPart,
                                                             List <MessagePatternModel> patterns)
        {
            if (textPart == null)
            {
                throw new ArgumentNullException("textPart");
            }
            if (patterns == null)
            {
                throw new ArgumentNullException("patterns");
            }

            var msgParts = new List <MessagePartModel>();

            if (patterns.Count == 0)
            {
                // all patterns have been tried -> this text is PURE text
                msgParts.Add(textPart);
                return(msgParts);
            }

            var remainingPatterns = new List <MessagePatternModel>(patterns);
            var pattern           = remainingPatterns.First();

            remainingPatterns.Remove(pattern);

            var match = pattern.MessagePartPattern.Match(textPart.Text);

            if (!match.Success)
            {
                // no matches in this MessagePart, try other smartlinks
                return(ParsePatterns(textPart, remainingPatterns));
            }

            int lastindex = 0;

            do
            {
                var groupValues = new string[match.Groups.Count];
                int i           = 0;
                foreach (Group @group in match.Groups)
                {
                    groupValues[i++] = @group.Value;
                }

                string url;
                if (String.IsNullOrEmpty(pattern.LinkFormat))
                {
                    url = match.Value;
                }
                else
                {
                    url = String.Format(pattern.LinkFormat, groupValues);
                }
                string text;
                if (String.IsNullOrEmpty(pattern.TextFormat))
                {
                    text = match.Value;
                }
                else
                {
                    text = String.Format(pattern.TextFormat, groupValues);
                }

                if (lastindex != match.Index)
                {
                    // there were some non-matching-chars before the match
                    // copy that to a TextMessagePartModel
                    var notMatchPart = new TextMessagePartModel(textPart);
                    // only take the proper chunk of text
                    notMatchPart.Text = textPart.Text.Substring(lastindex, match.Index - lastindex);
                    // and try other patterns on this part
                    var parts = ParsePatterns(notMatchPart, remainingPatterns);
                    foreach (var part in parts)
                    {
                        msgParts.Add(part);
                    }
                }

                MessagePartModel msgPart;
                if (pattern.MessagePartType == typeof(UrlMessagePartModel))
                {
                    // no need to set URL and text if they are the same
                    text    = text == url ? null : text;
                    msgPart = new UrlMessagePartModel(url, text);
                }
                else if (pattern.MessagePartType == typeof(ImageMessagePartModel))
                {
                    msgPart = new ImageMessagePartModel(url, text);
                }
                else
                {
                    msgPart = new TextMessagePartModel(text);
                }
                msgParts.Add(msgPart);
                lastindex = match.Index + match.Length;
                match     = match.NextMatch();
            } while (match.Success);

            if (lastindex != textPart.Text.Length)
            {
                // there were some non-url-chars before this url
                // copy TextMessagePartModel
                var notMatchPart = new TextMessagePartModel(textPart);
                // only take the proper chunk of text
                notMatchPart.Text = textPart.Text.Substring(lastindex);
                // and try other smartlinks on this part
                var parts = ParsePatterns(notMatchPart, remainingPatterns);
                foreach (var part in parts)
                {
                    msgParts.Add(part);
                }
            }
            return(msgParts);
        }
Example #5
0
        public static void ParseUrls(MessageModel msg)
        {
            string urlRegex;
            //urlRegex = "((([a-zA-Z][0-9a-zA-Z+\\-\\.]*:)?/{0,2}[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?(#[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?)");
            // It was constructed according to the BNF grammar given in RFC 2396 (http://www.ietf.org/rfc/rfc2396.txt).

            /*
            urlRegex = @"^(?<s1>(?<s0>[^:/\?#]+):)?(?<a1>" +
                                  @"//(?<a0>[^/\?#]*))?(?<p0>[^\?#]*)" +
                                  @"(?<q1>\?(?<q0>[^#]*))?" +
                                  @"(?<f1>#(?<f0>.*))?");
            */

            urlRegex = @"(^| )(((https?|ftp):\/\/)|www\.)(([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|net|org|info|biz|gov|name|edu|[a-zA-Z][a-zA-Z]))(:[0-9]+)?((\/|\?)[^ ""]*[^ ,;\.:"">)])?";
            Regex reg = new Regex(urlRegex, RegexOptions.IgnoreCase);
            // clone MessageParts
            IList<MessagePartModel> parts = new List<MessagePartModel>(msg.MessageParts);
            foreach (MessagePartModel part in parts) {
                if (part is UrlMessagePartModel) {
                    // no need to reparse URL parts
                    continue;
                }
                if (!(part is TextMessagePartModel)) {
                    continue;
                }

                TextMessagePartModel textPart = (TextMessagePartModel) part;
                Match urlMatch = reg.Match(textPart.Text);
                // OPT: fast regex scan
                if (!urlMatch.Success) {
                    // no URLs in this MessagePart, nothing to do
                    continue;
                }

                // found URL(s)
                // remove current MessagePartModel as we need to split it
                int idx = msg.MessageParts.IndexOf(part);
                msg.MessageParts.RemoveAt(idx);

                string[] textPartParts = textPart.Text.Split(new char[] {' '});
                for (int i = 0; i < textPartParts.Length; i++) {
                    string textPartPart = textPartParts[i];
                    urlMatch = reg.Match(textPartPart);
                    if (urlMatch.Success) {
                        UrlMessagePartModel urlPart = new UrlMessagePartModel(textPartPart);
                        //urlPart.ForegroundColor = new TextColor();
                        msg.MessageParts.Insert(idx++, urlPart);
                        msg.MessageParts.Insert(idx++, new TextMessagePartModel(" "));
                    } else {
                        // FIXME: we put each text part into it's own object, instead of combining them (the smart way)
                        TextMessagePartModel notUrlPart = new TextMessagePartModel(textPartPart + " ");
                        // restore formatting / colors from the original text part
                        notUrlPart.IsHighlight     = textPart.IsHighlight;
                        notUrlPart.ForegroundColor = textPart.ForegroundColor;
                        notUrlPart.BackgroundColor = textPart.BackgroundColor;
                        notUrlPart.Bold            = textPart.Bold;
                        notUrlPart.Italic          = textPart.Italic;
                        notUrlPart.Underline       = textPart.Underline;
                        msg.MessageParts.Insert(idx++, notUrlPart);
                    }
                }
            }
        }
Example #6
0
        private void InsertToBuffer(Gtk.TextBuffer buffer, ref Gtk.TextIter iter, UrlMessagePartModel urlPart)
        {
            var linkText = urlPart.Text ?? urlPart.Url;

            Uri uri;
            try {
                uri = new Uri(urlPart.Url);
            } catch (UriFormatException ex) {
            #if LOG4NET
                _Logger.Error("AddMessage(): Invalid URL: " + urlPart.Url, ex);
            #endif
                buffer.Insert(ref iter, linkText);
                return;
            }

            var tags = new List<Gtk.TextTag>();
            // link URI tag
            var linkTag = new LinkTag(uri);
            linkTag.TextEvent += OnLinkTagTextEvent;
            _MessageTextTagTable.Add(linkTag);
            tags.Add(linkTag);

            // link style tag
            tags.Add(LinkTag);

            buffer.InsertWithTags(ref iter, linkText, tags.ToArray());
        }
Example #7
0
 void ParseHtml(XmlNode node, TextMessagePartModel model)
 {
     TextMessagePartModel submodel;
     string nodetype = node.Name.ToLower();
     if (model is UrlMessagePartModel) {
         submodel = new UrlMessagePartModel(model);
     } else if (nodetype == "a") {
         submodel = new UrlMessagePartModel(model);
         (submodel as UrlMessagePartModel).Url = node.Attributes.GetNamedItem("href").Value;
     } else {
         submodel = new TextMessagePartModel(model);
     }
     switch (nodetype) {
         case "b":
         case "strong":
             submodel.Bold = true;
             break;
         case "i":
         case "em":
             submodel.Italic = true;
             break;
         case "u":
             submodel.Underline = true;
             break;
         default:
             break;
     }
     if (node.Attributes != null) {
         ParseStyle(node.Attributes.GetNamedItem("style"), submodel);
     }
     if (node.HasChildNodes) {
         foreach (XmlNode child in node.ChildNodes) {
             // clone this model
             TextMessagePartModel nextmodel;
             if (submodel is UrlMessagePartModel) {
                 nextmodel = new UrlMessagePartModel(submodel);
             } else {
                 nextmodel = new TextMessagePartModel(submodel);
             }
             ParseHtml(child, nextmodel);
         }
     } else {
         // final node
         if (nodetype == "br") {
             AppendText("\n");
         } else if (nodetype == "img") {
             AppendUrl(node.Attributes.GetNamedItem("src").Value, "[image placeholder - UNIMPLEMENTED]");
         } else {
             model.Text = HttpUtility.HtmlDecode(node.Value);
             AppendText(model);
         }
     }
 }
Example #8
0
        public static IList<MessagePartModel> ParsePatterns(TextMessagePartModel textPart,
                                                            List<MessagePatternModel> patterns)
        {
            if (textPart == null) {
                throw new ArgumentNullException("textPart");
            }
            if (patterns == null) {
                throw new ArgumentNullException("patterns");
            }

            var msgParts = new List<MessagePartModel>();
            if (patterns.Count == 0) {
                // all patterns have been tried -> this text is PURE text
                msgParts.Add(textPart);
                return msgParts;
            }

            var remainingPatterns = new List<MessagePatternModel>(patterns);
            var pattern = remainingPatterns.First();
            remainingPatterns.Remove(pattern);

            var match = pattern.MessagePartPattern.Match(textPart.Text);
            if (!match.Success) {
                // no matches in this MessagePart, try other smartlinks
                return ParsePatterns(textPart, remainingPatterns);
            }

            int lastindex = 0;
            do {
                var groupValues = new string[match.Groups.Count];
                int i = 0;
                foreach (Group @group in match.Groups) {
                    groupValues[i++] = @group.Value;
                }

                string url;
                if (String.IsNullOrEmpty(pattern.LinkFormat)) {
                    url = match.Value;
                } else {
                    url = String.Format(pattern.LinkFormat, groupValues);
                }
                string text;
                if (String.IsNullOrEmpty(pattern.TextFormat)) {
                    text = match.Value;
                } else {
                    text = String.Format(pattern.TextFormat, groupValues);
                }

                if (lastindex != match.Index) {
                    // there were some non-matching-chars before the match
                    // copy that to a TextMessagePartModel
                    var notMatchPart = new TextMessagePartModel(textPart);
                    // only take the proper chunk of text
                    notMatchPart.Text = textPart.Text.Substring(lastindex, match.Index - lastindex);
                    // and try other patterns on this part
                    var parts = ParsePatterns(notMatchPart, remainingPatterns);
                    foreach (var part in parts) {
                        msgParts.Add(part);
                    }
                }

                MessagePartModel msgPart;
                if (pattern.MessagePartType == typeof(UrlMessagePartModel)) {
                    // no need to set URL and text if they are the same
                    text = text == url ? null : text;
                    msgPart = new UrlMessagePartModel(url, text);
                } else if (pattern.MessagePartType == typeof(ImageMessagePartModel)) {
                    msgPart = new ImageMessagePartModel(url, text);
                } else {
                    msgPart = new TextMessagePartModel(text);
                }
                msgParts.Add(msgPart);
                lastindex = match.Index + match.Length;
                match = match.NextMatch();
            } while (match.Success);

            if (lastindex != textPart.Text.Length) {
                // there were some non-url-chars before this url
                // copy TextMessagePartModel
                var notMatchPart = new TextMessagePartModel(textPart);
                // only take the proper chunk of text
                notMatchPart.Text = textPart.Text.Substring(lastindex);
                // and try other smartlinks on this part
                var parts = ParsePatterns(notMatchPart, remainingPatterns);
                foreach (var part in parts) {
                    msgParts.Add(part);
                }
            }
            return msgParts;
        }
Example #9
0
        public static IList <MessagePartModel> ParsePatterns(TextMessagePartModel textPart,
                                                             List <MessagePatternModel> patterns)
        {
            if (textPart == null)
            {
                throw new ArgumentNullException("textPart");
            }
            if (patterns == null)
            {
                throw new ArgumentNullException("patterns");
            }

            var msgParts = new List <MessagePartModel>();

            if (patterns.Count == 0)
            {
                // all patterns have been tried -> this text is PURE text
                msgParts.Add(textPart);
                return(msgParts);
            }

            var remainingPatterns = new List <MessagePatternModel>(patterns);
            var pattern           = remainingPatterns.First();

            remainingPatterns.Remove(pattern);

            var match = pattern.MessagePartPattern.Match(textPart.Text);

            if (!match.Success)
            {
                // no matches in this MessagePart, try other smartlinks
                return(ParsePatterns(textPart, remainingPatterns));
            }

            int lastindex = 0;

            do
            {
                var startDelimiterLength = 0;
                var regexDelimiterForStartOfPatternValue = match.Groups[MessageBuilderSettings.StartDelimiterGroupName];
                if (regexDelimiterForStartOfPatternValue != null)
                {
                    startDelimiterLength = regexDelimiterForStartOfPatternValue.Value.Length;
                }
                var endDelimiterLength = 0;
                var regexDelimiterForEndOfPatternValue = match.Groups[MessageBuilderSettings.EndDelimiterGroupName];
                if (regexDelimiterForEndOfPatternValue != null)
                {
                    endDelimiterLength = regexDelimiterForEndOfPatternValue.Value.Length;
                }

                var groupValues = match.Groups.Cast <Group>()

                                  // don't get the delimiter because it only determines
                                  // the start or end of pattern, which is not part of the pattern
                                  .Where(g => g != regexDelimiterForStartOfPatternValue &&
                                         g != regexDelimiterForEndOfPatternValue)

                                  .Select(g => g.Value).ToArray();

                string url;
                if (String.IsNullOrEmpty(pattern.LinkFormat))
                {
                    url = match.Value;
                    url = url.Substring(0 + startDelimiterLength, url.Length - (startDelimiterLength - endDelimiterLength));
                }
                else
                {
                    url = String.Format(pattern.LinkFormat, groupValues);
                }
                string text;
                if (String.IsNullOrEmpty(pattern.TextFormat))
                {
                    text = match.Value;
                }
                else
                {
                    text = String.Format(pattern.TextFormat, groupValues);
                }
                text = text.Substring(0 + startDelimiterLength, text.Length - (startDelimiterLength + endDelimiterLength));

                if (lastindex != match.Index)
                {
                    // there were some non-matching-chars before the match
                    // copy that to a TextMessagePartModel
                    var notMatchPart = new TextMessagePartModel(textPart);
                    // only take the proper chunk of text
                    notMatchPart.Text = textPart.Text.Substring(lastindex, match.Index + startDelimiterLength - lastindex);
                    // and try other patterns on this part
                    var parts = ParsePatterns(notMatchPart, remainingPatterns);
                    foreach (var part in parts)
                    {
                        msgParts.Add(part);
                    }
                }

                MessagePartModel msgPart;
                if (pattern.MessagePartType == typeof(UrlMessagePartModel))
                {
                    // no need to set URL and text if they are the same
                    text    = text == url ? null : text;
                    msgPart = new UrlMessagePartModel(url, text);
                }
                else if (pattern.MessagePartType == typeof(ImageMessagePartModel))
                {
                    msgPart = new ImageMessagePartModel(url, text);
                }
                else
                {
                    msgPart = new TextMessagePartModel(text);
                }
                msgParts.Add(msgPart);
                lastindex = match.Index + match.Length - endDelimiterLength;
                match     = match.NextMatch();
            } while (match.Success);

            if (lastindex != textPart.Text.Length)
            {
                // there were some non-matching-chars after the last match
                // copy TextMessagePartModel
                var notMatchPart = new TextMessagePartModel(textPart);
                // only take the proper chunk of text
                notMatchPart.Text = textPart.Text.Substring(lastindex);
                // and try other smartlinks on this part
                var parts = ParsePatterns(notMatchPart, remainingPatterns);
                foreach (var part in parts)
                {
                    msgParts.Add(part);
                }
            }
            return(msgParts);
        }
Example #10
0
        public static void ParseUrls(MessageModel msg)
        {
            // clone MessageParts
            IList<MessagePartModel> parts = new List<MessagePartModel>(msg.MessageParts);
            foreach (MessagePartModel part in parts) {
                if (part is UrlMessagePartModel) {
                    // no need to reparse URL parts
                    continue;
                }
                if (!(part is TextMessagePartModel)) {
                    continue;
                }

                TextMessagePartModel textPart = (TextMessagePartModel) part;
                Match urlMatch = UrlRegex.Match(textPart.Text);
                // OPT: fast regex scan
                if (!urlMatch.Success) {
                    // no URLs in this MessagePart, nothing to do
                    continue;
                }

                // found URL(s)
                // remove current MessagePartModel as we need to split it
                int idx = msg.MessageParts.IndexOf(part);
                msg.MessageParts.RemoveAt(idx);

                string[] textPartParts = textPart.Text.Split(new char[] {' '});
                for (int i = 0; i < textPartParts.Length; i++) {
                    string textPartPart = textPartParts[i];
                    urlMatch = UrlRegex.Match(textPartPart);
                    if (urlMatch.Success) {
                        UrlMessagePartModel urlPart = new UrlMessagePartModel(textPartPart);
                        //urlPart.ForegroundColor = new TextColor();
                        msg.MessageParts.Insert(idx++, urlPart);
                        msg.MessageParts.Insert(idx++, new TextMessagePartModel(" "));
                    } else {
                        // FIXME: we put each text part into it's own object, instead of combining them (the smart way)
                        TextMessagePartModel notUrlPart = new TextMessagePartModel(textPartPart + " ");
                        // restore formatting / colors from the original text part
                        notUrlPart.IsHighlight     = textPart.IsHighlight;
                        notUrlPart.ForegroundColor = textPart.ForegroundColor;
                        notUrlPart.BackgroundColor = textPart.BackgroundColor;
                        notUrlPart.Bold            = textPart.Bold;
                        notUrlPart.Italic          = textPart.Italic;
                        notUrlPart.Underline       = textPart.Underline;
                        msg.MessageParts.Insert(idx++, notUrlPart);
                    }
                }
            }
        }
Example #11
0
        public static IList<MessagePartModel> ParsePatterns(TextMessagePartModel textPart,
                                                            List<MessagePatternModel> patterns)
        {
            if (textPart == null) {
                throw new ArgumentNullException("textPart");
            }
            if (patterns == null) {
                throw new ArgumentNullException("patterns");
            }

            var msgParts = new List<MessagePartModel>();
            if (patterns.Count == 0) {
                // all patterns have been tried -> this text is PURE text
                msgParts.Add(textPart);
                return msgParts;
            }

            var remainingPatterns = new List<MessagePatternModel>(patterns);
            var pattern = remainingPatterns.First();
            remainingPatterns.Remove(pattern);

            var match = pattern.MessagePartPattern.Match(textPart.Text);
            if (!match.Success) {
                // no matches in this MessagePart, try other smartlinks
                return ParsePatterns(textPart, remainingPatterns);
            }

            int lastindex = 0;
            do {
                var delimiterLength = 0;
                var regexDelimiterForEndOfPatternValue = match.Groups[MessageBuilderSettings.EndDelimiterGroupName];
                if (regexDelimiterForEndOfPatternValue != null) {
                    delimiterLength = regexDelimiterForEndOfPatternValue.Value.Length;
                }

                var groupValues = match.Groups.Cast<Group>()

                    // don't get the delimiter because it only determines
                    // the end of pattern, which is not part of the pattern
                    .Where(g => g != regexDelimiterForEndOfPatternValue)

                    .Select(g => g.Value).ToArray();

                string url;
                if (String.IsNullOrEmpty(pattern.LinkFormat)) {
                    url = match.Value;
                } else {
                    url = String.Format(pattern.LinkFormat, groupValues);
                }
                url = url.Substring(0, url.Length - delimiterLength);
                string text;
                if (String.IsNullOrEmpty(pattern.TextFormat)) {
                    text = match.Value;
                } else {
                    text = String.Format(pattern.TextFormat, groupValues);
                }
                text = text.Substring(0, text.Length - delimiterLength);

                if (lastindex != match.Index) {
                    // there were some non-matching-chars before the match
                    // copy that to a TextMessagePartModel
                    var notMatchPart = new TextMessagePartModel(textPart);
                    // only take the proper chunk of text
                    notMatchPart.Text = textPart.Text.Substring(lastindex, match.Index - lastindex);
                    // and try other patterns on this part
                    var parts = ParsePatterns(notMatchPart, remainingPatterns);
                    foreach (var part in parts) {
                        msgParts.Add(part);
                    }
                }

                MessagePartModel msgPart;
                if (pattern.MessagePartType == typeof(UrlMessagePartModel)) {
                    // no need to set URL and text if they are the same
                    text = text == url ? null : text;
                    msgPart = new UrlMessagePartModel(url, text);
                } else if (pattern.MessagePartType == typeof(ImageMessagePartModel)) {
                    msgPart = new ImageMessagePartModel(url, text);
                } else {
                    msgPart = new TextMessagePartModel(text);
                }
                msgParts.Add(msgPart);
                lastindex = match.Index + match.Length - delimiterLength;
                match = match.NextMatch();
            } while (match.Success);

            if (lastindex != textPart.Text.Length) {
                // there were some non-url-chars before this url
                // copy TextMessagePartModel
                var notMatchPart = new TextMessagePartModel(textPart);
                // only take the proper chunk of text
                notMatchPart.Text = textPart.Text.Substring(lastindex);
                // and try other smartlinks on this part
                var parts = ParsePatterns(notMatchPart, remainingPatterns);
                foreach (var part in parts) {
                    msgParts.Add(part);
                }
            }
            return msgParts;
        }