private static string GetPlainTextBody(Item message) { // When there's no text whatsoever in the email, EWS may return null rather than an empty string. We normalize // to empty string to avoid repeated null checks later on, since empty string represents the same meaning as null // in our context. var text = message.Body.Text ?? string.Empty; return(message.Body.BodyType == BodyType.Text ? text : EmailBodyProcessingUtils.ConvertHtmlMessageToPlainText(text)); }
/// <param name="workItemId">The ID of the work item to modify </param> /// <param name="comment">Comment to add to description</param> /// <param name="commentIsHtml"></param> /// <param name="values">List of fields to change</param> /// <param name="attachments"></param> public void ModifyWorkItem(int workItemId, string comment, bool commentIsHtml, Dictionary <string, string> values, MessageAttachmentCollection attachments) { if (workItemId <= 0) { return; } var workItem = _tfsStore.GetWorkItem(workItemId); workItem.Open(); if (commentIsHtml) { workItem.History = EmailBodyProcessingUtils.UpdateEmbeddedImageLinks(comment, attachments.Attachments); } else { workItem.History = comment.Replace("\n", "<br>"); } foreach (var key in values.Keys) { TryApplyFieldValue(workItem, key, values[key]); } if (attachments != null) { string GetAttachmentKey(string fileName, long length) { return($"{fileName}|{length}"); } var existingAttachments = new HashSet <string>(workItem.Attachments .OfType <Attachment>() .Select(attachment => GetAttachmentKey(attachment.Name, attachment.Length))); foreach (var attachment in attachments.Attachments) { var localFileInfo = new FileInfo(attachment.FilePath); string key = GetAttachmentKey(localFileInfo.Name, localFileInfo.Length); // If there's already an attachment with the same file name and size, don't bother re-uploading it // TODO: this may break embedded images for html replies since we update the img tag above to point to a local path we don't upload // However, we haven't confirmed this breaks and replying with an identical image is unlikely, so we ignore this for now if (!existingAttachments.Contains(key)) { workItem.Attachments.Add(new Attachment(attachment.FilePath)); } } } ValidateAndSaveWorkItem(workItem); workItem.Save(); }
private static string GetPlainTextBody(Item message) { string plainTextBody; if (!message.TryGetProperty(EWSExtendedProperty.PidTagBody, out plainTextBody)) { plainTextBody = EmailBodyProcessingUtils.ConvertHtmlMessageToPlainText(message.Body.Text); // Fallback } // When there's no text whatsoever in the email, EWS may return null rather than an empty string. We normalize // to empty string to avoid repeated null checks later on, since empty string represents the same meaning as null // in our context. return(plainTextBody ?? string.Empty); }
public void TestGetLastMessageTextBasic() { var message = new IncomingEmailMessageMock(); var lastMessageText = RandomDataHelper.GetBody(_rand.Next()); var numOfReplies = _rand.Next(0, 100); var bodyBuilder = new StringBuilder(lastMessageText); for (var i = 0; i < numOfReplies; i++) { bodyBuilder.AppendLine(RandomDataHelper.GetRandomMessageSeparator(_rand.Next())); bodyBuilder.Append(RandomDataHelper.GetBody(_rand.Next())); } message.PlainTextBody = bodyBuilder.ToString(); Assert.AreEqual(lastMessageText, EmailBodyProcessingUtils.GetLastMessageText(message), "Verifying extracted last message text correctness"); }
public void TestConvertHtmlMessageToHtmlRegression() { const string regressionMessagesFolder = "RegressionMessages"; foreach (var originalFilename in Directory.GetFiles(regressionMessagesFolder, "*.orig")) { Trace.WriteLine(string.Format("Processing regression file {0}", originalFilename)); var baseFilename = Path.GetFileNameWithoutExtension(originalFilename); var expectedFilename = Path.Combine(regressionMessagesFolder, baseFilename + ".expected"); var html = File.ReadAllText(originalFilename); var expectedText = Normalize(File.ReadAllText(expectedFilename)); var convertedHtml = Normalize(EmailBodyProcessingUtils.ConvertHtmlMessageToPlainText(html)); Assert.AreEqual(expectedText, convertedHtml); } }
public void TestConvertHtmlMessageToPlainTextBasic() { var properties = new StringProperties(); properties.MinNumberOfCodePoints = 20; RandomDataHelper.Ranges.ForEach(properties.UnicodeRanges.Add); properties.UnicodeRanges.Remove(new UnicodeRange('<', '<')); properties.UnicodeRanges.Remove(new UnicodeRange('>', '>')); // Can't have '<' or '>' chars in the content, since it breaks the HTML processing. This is OK, since real HTML should never have these // chracters either (they will be escaped as < and >) var expectedText = StringFactory.GenerateRandomString(properties, _rand.Next()).Trim().Replace("<", "").Replace(">", ""); var htmlText = string.Format("<html><head></head><body><p>{0}</p></body></html>", expectedText); var plainText = EmailBodyProcessingUtils.ConvertHtmlMessageToPlainText(htmlText); Assert.AreEqual(plainText, expectedText); }
public void TestGetLastMessageText_NoPrevious() { string original = @"<html> <body> This is a boring email. </body> </html>"; // Note: it's acceptable to not preserve whitespace / insert empty HTML tags because we're // manipulating HTML, not plain text. As long as the rendered page isn't impacted, all is well string expected = @"<html><head></head><body> This is a boring email. </body></html>"; string actual = EmailBodyProcessingUtils.GetLastMessageText_Html(original); Assert.AreEqual(Normalize(expected), Normalize(actual)); }
public void TestGetLastMessageText_Previous_FromColon() { string original = @"<html> <body> <div class=""wrapppingBothMessageAndReply""> <p class=""customStyling"">This is random text with some custom styling and outlook-generated elements.<o:p></o:p></p> <div> <div style=""border:none;border-top:solid""> <p class=""customStyling""><b>From:</b> someAddress;<br><b>Subject:</b> RE: Build error<o:p></o:p> </p> </div> </div> <p class=""customStyling""> <o:p> </o:p> </p> <p class=""customStyling"">text of the reply </p> </div> <div>Something after the containing div</div> </body> </html>"; // Note: it's acceptable to not preserve whitespace because it's // manipulating HTML, not plain text. As long as the rendered page isn't impacted, all is well // Note that we expect that both // 1. Elements following the latest message are removed // 2. Anything in the same element as the latest message but after the start of the previous should be cleared out string expected = @"<html><head></head><body> <div class=""wrapppingBothMessageAndReply""> <p class=""customStyling"">This is random text with some custom styling and outlook-generated elements.<o:p></o:p></p> <div> <div style=""border:none;border-top:solid""> <p class=""customStyling""><b></b></p></div></div></div></body></html>"; string actual = EmailBodyProcessingUtils.GetLastMessageText_Html(original); Assert.AreEqual(Normalize(expected), Normalize(actual)); }
public void TestGetLastMessageText_EmailClientsSchemas() { const string schemasFolder = "LastMessageSchemas"; foreach (var originalFilename in Directory.GetFiles(schemasFolder, "*.orig")) { Trace.WriteLine(string.Format("Processing email schema file {0}", originalFilename)); var baseFilename = Path.GetFileNameWithoutExtension(originalFilename); var expectedFilename = Path.Combine(schemasFolder, baseFilename + ".expected"); var original = File.ReadAllText(originalFilename); var expected = Normalize(File.ReadAllText(expectedFilename)); var actual = Normalize(EmailBodyProcessingUtils.GetLastMessageText_Html(original)); // Note: it's acceptable to not preserve whitespace because it's // manipulating HTML, not plain text. As long as the rendered page isn't impacted, all is well // Note that we expect that both // 1. Elements following the latest message are removed // 2. Anything in the same element as the latest message but after the start of the previous should be cleared out Assert.AreEqual(expected, actual); } }
private void InitWorkItemFields(IIncomingEmailMessage message, Dictionary <string, string> workItemUpdates, MessageAttachmentCollection attachments) { var resolver = new SpecialValueResolver(message, _workItemManager.GetNameResolver()); workItemUpdates["Title"] = resolver.Subject; var rawConversationIndex = message.ConversationId; workItemUpdates[_config.WorkItemSettings.ConversationIndexFieldName] = rawConversationIndex.Substring(0, Math.Min(rawConversationIndex.Length, TfsTextFieldMaxLength)); bool enableImgUpdating = _config.WorkItemSettings.EnableExperimentalHtmlFeatures; foreach (var defaultFieldValue in _config.WorkItemSettings.DefaultFieldValues) { var result = resolver.Resolve(defaultFieldValue.Value); if (enableImgUpdating && message.IsHtmlBody && defaultFieldValue.Value == SpecialValueResolver.RawMessageBodyKeyword) { result = EmailBodyProcessingUtils.UpdateEmbeddedImageLinks(result, attachments.Attachments); } workItemUpdates[defaultFieldValue.Field] = result; } }
public void TestUpdateEmbeddedImageLinks_Basic() { string original = @"<html> <body> <img src=""cid:123"" > </body> </html>"; IReadOnlyCollection <MessageAttachmentInfo> attachmentInfo = new List <MessageAttachmentInfo> { new MessageAttachmentInfo(@"x:\image.png", "123"), }; // Note: it's acceptable to not preserve whitespace / insert empty HTML tags because we're // manipulating HTML, not plain text. As long as the rendered page isn't impacted, all is well string expected = @"<html><head></head><body> <img src=""file:///x:/image.png""> </body></html>"; string actual = EmailBodyProcessingUtils.UpdateEmbeddedImageLinks(original, attachmentInfo); Assert.AreEqual(Normalize(expected), Normalize(actual)); }
private static string GetPlainTextBody(Item message) { return(message.Body.BodyType == BodyType.Text ? message.Body.Text : EmailBodyProcessingUtils.ConvertHtmlMessageToPlainText(message.Body.Text)); }
public string GetLastMessageText() { return(EmailBodyProcessingUtils.GetLastMessageText(this)); }
public string GetLastMessageText(bool enableExperimentalHtmlFeatures) { return(EmailBodyProcessingUtils.GetLastMessageText(this, enableExperimentalHtmlFeatures)); }