当前位置: 首页 > 知识库问答 >
问题:

MimeKit.MimeMessage转换为浏览器可呈现的HTML

焦正德
2023-03-14
//////////////////////////////////////////////////////////////////////////////////////////
// use MimeKit to parse the message
//////////////////////////////////////////////////////////////////////////////////////////
MimeKit.MimeMessage msg = MimeKit.MimeMessage.Load(stream);

//////////////////////////////////////////////////////////////////////////////////////////
// use HtmlAgilityPack to parse the resulting html in order to fix inline images
//////////////////////////////////////////////////////////////////////////////////////////
HtmlAgilityPack.HtmlDocument hdoc = new HtmlAgilityPack.HtmlDocument();
hdoc.LoadHtml(msg.HtmlBody);
// find all image nodes
var images = hdoc.DocumentNode.Descendants("img");
foreach (var img in images)
{                        
    // check that this is an inline image
    string cid = img.Attributes["src"].Value;
    if (cid.StartsWith("cid:"))
    {
        // remove the cid part of the attribute
        cid = cid.Remove(0, 4);
        // find image object in MimeMessage
        MimeKit.MimePart part = msg.BodyParts.First(x => x.ContentId == cid) as MimeKit.MimePart;
        if (part != null)
        {
            using (MemoryStream mstream = new MemoryStream())
            {
                // get the raw image content
                part.ContentObject.WriteTo(mstream);
                mstream.Flush();
                byte[] imgbytes = mstream.ToArray();
                // fix the image source by making it an embedded image
                img.Attributes["src"].Value = "data:" + part.ContentType.MimeType + ";" + part.ContentTransferEncoding.ToString().ToLower() + "," +
                    System.Text.ASCIIEncoding.ASCII.GetString(imgbytes);
            }
        }
    }
}

// write the resulting html to the output stream
hdoc.Save(outputStream);

共有1个答案

葛意远
2023-03-14

您的解决方案与我以前在MimeKit的MessageReader示例中使用的逻辑类似,但现在MimeKit提供了一个更好的解决方案:

/// <summary>
/// Visits a MimeMessage and generates HTML suitable to be rendered by a browser control.
/// </summary>
class HtmlPreviewVisitor : MimeVisitor
{
    List<MultipartRelated> stack = new List<MultipartRelated> ();
    List<MimeEntity> attachments = new List<MimeEntity> ();
    readonly string tempDir;
    string body;

    /// <summary>
    /// Creates a new HtmlPreviewVisitor.
    /// </summary>
    /// <param name="tempDirectory">A temporary directory used for storing image files.</param>
    public HtmlPreviewVisitor (string tempDirectory)
    {
        tempDir = tempDirectory;
    }

    /// <summary>
    /// The list of attachments that were in the MimeMessage.
    /// </summary>
    public IList<MimeEntity> Attachments {
        get { return attachments; }
    }

    /// <summary>
    /// The HTML string that can be set on the BrowserControl.
    /// </summary>
    public string HtmlBody {
        get { return body ?? string.Empty; }
    }

    protected override void VisitMultipartAlternative (MultipartAlternative alternative)
    {
        // walk the multipart/alternative children backwards from greatest level of faithfulness to the least faithful
        for (int i = alternative.Count - 1; i >= 0 && body == null; i--)
            alternative[i].Accept (this);
    }

    protected override void VisitMultipartRelated (MultipartRelated related)
    {
        var root = related.Root;

        // push this multipart/related onto our stack
        stack.Add (related);

        // visit the root document
        root.Accept (this);

        // pop this multipart/related off our stack
        stack.RemoveAt (stack.Count - 1);
    }

    // look up the image based on the img src url within our multipart/related stack
    bool TryGetImage (string url, out MimePart image)
    {
        UriKind kind;
        int index;
        Uri uri;

        if (Uri.IsWellFormedUriString (url, UriKind.Absolute))
            kind = UriKind.Absolute;
        else if (Uri.IsWellFormedUriString (url, UriKind.Relative))
            kind = UriKind.Relative;
        else
            kind = UriKind.RelativeOrAbsolute;

        try {
            uri = new Uri (url, kind);
        } catch {
            image = null;
            return false;
        }

        for (int i = stack.Count - 1; i >= 0; i--) {
            if ((index = stack[i].IndexOf (uri)) == -1)
                continue;

            image = stack[i][index] as MimePart;
            return image != null;
        }

        image = null;

        return false;
    }

    // Save the image to our temp directory and return a "file://" url suitable for
    // the browser control to load.
    // Note: if you'd rather embed the image data into the HTML, you can construct a
    // "data:" url instead.
    string SaveImage (MimePart image, string url)
    {
        string fileName = url.Replace (':', '_').Replace ('\\', '_').Replace ('/', '_');
        string path = Path.Combine (tempDir, fileName);

        if (!File.Exists (path)) {
            using (var output = File.Create (path))
                image.ContentObject.DecodeTo (output);
        }

        return "file://" + path.Replace ('\\', '/');
    }

    // Replaces <img src=...> urls that refer to images embedded within the message with
    // "file://" urls that the browser control will actually be able to load.
    void HtmlTagCallback (HtmlTagContext ctx, HtmlWriter htmlWriter)
    {
        if (ctx.TagId == HtmlTagId.Image && !ctx.IsEndTag && stack.Count > 0) {
            ctx.WriteTag (htmlWriter, false);

            // replace the src attribute with a file:// URL
            foreach (var attribute in ctx.Attributes) {
                if (attribute.Id == HtmlAttributeId.Src) {
                    MimePart image;
                    string url;

                    if (!TryGetImage (attribute.Value, out image)) {
                        htmlWriter.WriteAttribute (attribute);
                        continue;
                    }

                    url = SaveImage (image, attribute.Value);

                    htmlWriter.WriteAttributeName (attribute.Name);
                    htmlWriter.WriteAttributeValue (url);
                } else {
                    htmlWriter.WriteAttribute (attribute);
                }
            }
        } else if (ctx.TagId == HtmlTagId.Body && !ctx.IsEndTag) {
            ctx.WriteTag (htmlWriter, false);

            // add and/or replace oncontextmenu="return false;"
            foreach (var attribute in ctx.Attributes) {
                if (attribute.Name.ToLowerInvariant () == "oncontextmenu")
                    continue;

                htmlWriter.WriteAttribute (attribute);
            }

            htmlWriter.WriteAttribute ("oncontextmenu", "return false;");
        } else {
            // pass the tag through to the output
            ctx.WriteTag (htmlWriter, true);
        }
    }

    protected override void VisitTextPart (TextPart entity)
    {
        TextConverter converter;

        if (body != null) {
            // since we've already found the body, treat this as an attachment
            attachments.Add (entity);
            return;
        }

        if (entity.IsHtml) {
            converter = new HtmlToHtml {
                HtmlTagCallback = HtmlTagCallback
            };
        } else if (entity.IsFlowed) {
            var flowed = new FlowedToHtml ();
            string delsp;

            if (entity.ContentType.Parameters.TryGetValue ("delsp", out delsp))
                flowed.DeleteSpace = delsp.ToLowerInvariant () == "yes";

            converter = flowed;
        } else {
            converter = new TextToHtml ();
        }

        string text = entity.Text;

        body = converter.Convert (entity.Text);
    }

    protected override void VisitTnefPart (TnefPart entity)
    {
        // extract any attachments in the MS-TNEF part
        attachments.AddRange (entity.ExtractAttachments ());
    }

    protected override void VisitMessagePart (MessagePart entity)
    {
        // treat message/rfc822 parts as attachments
        attachments.Add (entity);
    }

    protected override void VisitMimePart (MimePart entity)
    {
        // realistically, if we've gotten this far, then we can treat this as an attachment
        // even if the IsAttachment property is false.
        attachments.Add (entity);
    }
}

然后要使用这个自定义的HTMLPreviewVisitor类,您将有一个类似于以下内容的方法:

void Render (WebBrowser browser, MimeMessage message)
{
    var tmpDir = Path.Combine (Path.GetTempPath (), message.MessageId);
    var visitor = new HtmlPreviewVisitor (tmpDir);

    Directory.CreateDirectory (tmpDir);

    message.Accept (visitor);

    browser.DocumentText = visitor.HtmlBody;
}

我知道这似乎是很多代码,但它涵盖的不仅仅是简单的案例。您将注意到它还处理text/plaintext/plain的呈现;如果HTML不可用,则format=flowed正文。它还正确地只使用作为封装多部分/相关树的一部分的图像。

string SaveImage (MimePart image, string url)
{
    using (var output = new MemoryStream ()) {
        image.ContentObject.DecodeTo (output);

        var buffer = output.GetBuffer ();
        int length = (int) output.Length;

        return string.Format ("data:{0};base64,{1}", image.ContentType.MimeType, Convert.ToBase64String (buffer, 0, length));
    }
}
 类似资料:
  • 问题内容: 最长的时间,我一直想了解为什么当浏览器在呈现的HTML元素之间存在换行符时,为什么在它们之间添加空白区域,例如: 上面的html将输出“ HelloWorld”字符串 , 在“ Hello”和“ World”之间 没有 空格,但是在以下示例中: 上面的html将输出“ Hello World”字符串 , 在“ Hello”和“ World”之间 有 一个空格。 现在,我可以毫无疑问地接

  • 问题内容: 我需要一个命令行工具(或Javascript / PHP,但我认为命令行是一种方式)来进行渲染并获取URL的渲染内容,但是重要的是我不仅要渲染CSS / Html / images,还要渲染Javascript。 例如,诸如“ renderengine http://www.google.es outputfile.html”之类的命令和网络内容(已解析的html和javascript

  • 问题内容: 我正在将代码从Node.js转换为浏览器的javascript,但是node.js中的缓冲区存在问题。如何在Javascript中使用它们? 这是一个例子: 我需要将javascript中的[66、6f,6f]转换为“ foo”,反之亦然。我怎样才能做到这一点?注意:必须在没有Node.js的情况下完成此操作。 问题答案: 使用https://github.com/substack/n

  • 有人能给出在“https://network.axial.net/a/company/business-team-san-francisco/”这样的网站上使用JSoup的正确方法吗?

  • Localhost很好,但上传到服务器时不工作 %pdf-1.3 1 0 obj<>Endobj2 0 obj<>Endobj3 0 obj<>>/Mediabox[0.000 0.000 595.280 841.890]>>Endobj4 0 obj<>stream x 2 300 p@&b m-l l,br b 5 jr k drr f b k endstream endobj 8 0 obj