c# - 在 .NET Core 中将 Word doc 和 docx 格式转换为 PDF,无需 Microsoft.Office.Interop

标签 c# pdf ms-word .net-core

我需要显示 Word .doc.docx浏览器中的文件。没有真正的客户端方法可以做到这一点,出于法律原因,这些文档不能与 Google 文档或 Microsoft Office 365 共享。

浏览器不能显示Word,但可以显示PDF,所以我想在服务器上将这些文档转换为PDF,然后显示出来。

我知道这可以使用 Microsoft.Office.Interop.Word 来完成,但我的应用程序是 .NET Core,无法访问 Office 互操作。它可以在 Azure 上运行,但也可以在其他任何设备上的 Docker 容器中运行。

似乎有很多与此类似的问题,但是大多数都在询问完整框架 .NET 或假设服务器是 Windows 操作系统,任何答案对我都没用。

我如何转换 .doc.docx文件到 .pdf无法访问 Microsoft.Office.Interop.Word ?

最佳答案

这真是太痛苦了,难怪所有第三方解决方案都向每个开发人员收取 500 美元。
好消息是Open XML SDK recently added support for .Net Standard所以看起来你很幸运获得了 .docx格式。
目前的坏消息是 .NET Core 上的 PDF 生成库没有太多选择。由于看起来您不想为一个服务付费并且您不能合法地使用第三方服务,因此我们别无选择,只能推出我们自己的服务。
主要问题是将 Word 文档内容转换为 PDF。一种流行的方法是将 Docx 读入 HTML 并将其导出为 PDF。很难找到,但有 OpenXMLSDK 的 .Net Core 版本- 电动工具 支持将 Docx 转换为 HTML。拉取请求“即将被接受”,您可以从这里获取:
https://github.com/OfficeDev/Open-Xml-PowerTools/tree/abfbaac510d0d60e2f492503c60ef897247716cf
现在我们可以将文档内容提取为 HTML,我们需要将其转换为 PDF。有一些库可以将 HTML 转换为 PDF,例如 DinkToPdf是围绕 Webkit HTML 到 PDF 库 libwkhtmltox 的跨平台包装器。
我认为 DinkToPdf 比 https://code.msdn.microsoft.com/How-to-export-HTML-to-PDF-c5afd0ce 更好

Docx 转 HTML
让我们把它放在一起,下载 OpenXMLSDK-PowerTools .Net Core 项目并构建它(只有 OpenXMLPowerTools.Core 和 OpenXMLPowerTools.Core.Example - 忽略其他项目)。
将 OpenXMLPowerTools.Core.Example 设置为 StartUp 项目。将 Word 文档添加到项目(例如 test.docx)并设置此 docx 文件属性 Copy To Output = If Newer运行控制台项目:

static void Main(string[] args)
{
    var source = Package.Open(@"test.docx");
    var document = WordprocessingDocument.Open(source);
    HtmlConverterSettings settings = new HtmlConverterSettings();
    XElement html = HtmlConverter.ConvertToHtml(document, settings);

    Console.WriteLine(html.ToString());
    var writer = File.CreateText("test.html");
    writer.WriteLine(html.ToString());
    writer.Dispose();
    Console.ReadLine();
确保 test.docx 是包含一些文本的有效 Word 文档,否则您可能会收到错误消息:

the specified package is invalid. the main part is missing


如果您运行该项目,您将看到 HTML 与 Word 文档中的内容几乎一模一样:
enter image description here
但是,如果您尝试使用带有图片或链接的 Word 文档,您会发现它们丢失或损坏。
这篇 CodeProject 文章解决了这些问题:https://www.codeproject.com/Articles/1162184/Csharp-Docx-to-HTML-to-Docx
我不得不改变 static Uri FixUri(string brokenUri)返回 Uri 的方法我添加了用户友好的错误消息。
static void Main(string[] args)
{
    var fileInfo = new FileInfo(@"c:\temp\MyDocWithImages.docx");
    string fullFilePath = fileInfo.FullName;
    string htmlText = string.Empty;
    try
    {
        htmlText = ParseDOCX(fileInfo);
    }
    catch (OpenXmlPackageException e)
    {
        if (e.ToString().Contains("Invalid Hyperlink"))
        {
            using (FileStream fs = new FileStream(fullFilePath,FileMode.OpenOrCreate, FileAccess.ReadWrite))
            {
                UriFixer.FixInvalidUri(fs, brokenUri => FixUri(brokenUri));
            }
            htmlText = ParseDOCX(fileInfo);
        }
    }

    var writer = File.CreateText("test1.html");
    writer.WriteLine(htmlText.ToString());
    writer.Dispose();
}
        
public static Uri FixUri(string brokenUri)
{
    string newURI = string.Empty;
    if (brokenUri.Contains("mailto:"))
    {
        int mailToCount = "mailto:".Length;
        brokenUri = brokenUri.Remove(0, mailToCount);
        newURI = brokenUri;
    }
    else
    {
        newURI = " ";
    }
    return new Uri(newURI);
}

public static string ParseDOCX(FileInfo fileInfo)
{
    try
    {
        byte[] byteArray = File.ReadAllBytes(fileInfo.FullName);
        using (MemoryStream memoryStream = new MemoryStream())
        {
            memoryStream.Write(byteArray, 0, byteArray.Length);
            using (WordprocessingDocument wDoc =
                                        WordprocessingDocument.Open(memoryStream, true))
            {
                int imageCounter = 0;
                var pageTitle = fileInfo.FullName;
                var part = wDoc.CoreFilePropertiesPart;
                if (part != null)
                    pageTitle = (string)part.GetXDocument()
                                            .Descendants(DC.title)
                                            .FirstOrDefault() ?? fileInfo.FullName;

                WmlToHtmlConverterSettings settings = new WmlToHtmlConverterSettings()
                {
                    AdditionalCss = "body { margin: 1cm auto; max-width: 20cm; padding: 0; }",
                    PageTitle = pageTitle,
                    FabricateCssClasses = true,
                    CssClassPrefix = "pt-",
                    RestrictToSupportedLanguages = false,
                    RestrictToSupportedNumberingFormats = false,
                    ImageHandler = imageInfo =>
                    {
                        ++imageCounter;
                        string extension = imageInfo.ContentType.Split('/')[1].ToLower();
                        ImageFormat imageFormat = null;
                        if (extension == "png") imageFormat = ImageFormat.Png;
                        else if (extension == "gif") imageFormat = ImageFormat.Gif;
                        else if (extension == "bmp") imageFormat = ImageFormat.Bmp;
                        else if (extension == "jpeg") imageFormat = ImageFormat.Jpeg;
                        else if (extension == "tiff")
                        {
                            extension = "gif";
                            imageFormat = ImageFormat.Gif;
                        }
                        else if (extension == "x-wmf")
                        {
                            extension = "wmf";
                            imageFormat = ImageFormat.Wmf;
                        }

                        if (imageFormat == null) return null;

                        string base64 = null;
                        try
                        {
                            using (MemoryStream ms = new MemoryStream())
                            {
                                imageInfo.Bitmap.Save(ms, imageFormat);
                                var ba = ms.ToArray();
                                base64 = System.Convert.ToBase64String(ba);
                            }
                        }
                        catch (System.Runtime.InteropServices.ExternalException)
                        { return null; }

                        ImageFormat format = imageInfo.Bitmap.RawFormat;
                        ImageCodecInfo codec = ImageCodecInfo.GetImageDecoders()
                                                    .First(c => c.FormatID == format.Guid);
                        string mimeType = codec.MimeType;

                        string imageSource =
                                string.Format("data:{0};base64,{1}", mimeType, base64);

                        XElement img = new XElement(Xhtml.img,
                                new XAttribute(NoNamespace.src, imageSource),
                                imageInfo.ImgStyleAttribute,
                                imageInfo.AltText != null ?
                                    new XAttribute(NoNamespace.alt, imageInfo.AltText) : null);
                        return img;
                    }
                };

                XElement htmlElement = WmlToHtmlConverter.ConvertToHtml(wDoc, settings);
                var html = new XDocument(new XDocumentType("html", null, null, null),
                                                                            htmlElement);
                var htmlString = html.ToString(SaveOptions.DisableFormatting);
                return htmlString;
            }
        }
    }
    catch
    {
        return "The file is either open, please close it or contains corrupt data";
    }
}
您可能需要 System.Drawing.Common NuGet 包才能使用 ImageFormat
现在我们可以得到图像:
enter image description here
如果您只想在 Web 浏览器中显示 Word .docx 文件,最好不要将 HTML 转换为 PDF,因为这会显着增加带宽。您可以使用 VPP 技术将 HTML 存储在文件系统、云或 dB 中。

HTML 转 PDF
接下来我们需要做的是将 HTML 传递给 DinkToPdf。下载 DinkToPdf (90 MB) 解决方案。构建解决方案 - 恢复所有包和编译解决方案需要一段时间。
重要提示:
如果要在 Linux 和 Windows 上运行,DinkToPdf 库需要项目根目录中的 libwkhtmltox.so 和 libwkhtmltox.dll 文件。如果您需要,还有一个适用于 Mac 的 libwkhtmltox.dylib 文件。
这些 DLL 位于 v0.12.4 文件夹中。根据您的 PC(32 位或 64 位),将 3 个文件复制到 DinkToPdf-master\DinkToPfd.TestConsoleApp\bin\Debug\netcoreapp1.1 文件夹。
重要事项 2:
确保在 Docker 镜像或 Linux 机器上安装了 libgdiplus。 libwkhtmltox.so 库依赖于它。
将 DinkToPfd.TestConsoleApp 设置为启动项目并更改 Program.cs 文件以从使用 Open-Xml-PowerTools 保存的 HTML 文件而不是 Lorium Ipsom 文本读取 htmlContent。
var doc = new HtmlToPdfDocument()
{
    GlobalSettings = {
        ColorMode = ColorMode.Color,
        Orientation = Orientation.Landscape,
        PaperSize = PaperKind.A4,
    },
    Objects = {
        new ObjectSettings() {
            PagesCount = true,
            HtmlContent = File.ReadAllText(@"C:\TFS\Sandbox\Open-Xml-PowerTools-abfbaac510d0d60e2f492503c60ef897247716cf\ToolsTest\test1.html"),
            WebSettings = { DefaultEncoding = "utf-8" },
            HeaderSettings = { FontSize = 9, Right = "Page [page] of [toPage]", Line = true },
            FooterSettings = { FontSize = 9, Right = "Page [page] of [toPage]" }
        }
    }
};
Docx 与 PDF 的结果令人印象深刻,我怀疑很多人会挑出很多差异(特别是如果他们从未见过原件):
enter image description here
附言。我知道您想同时转换 .doc.docx到 PDF。我建议自己制作一个服务,使用特定的非服务器 Windows/Microsoft 技术将 .doc 转换为 docx。 doc 格式是二进制的,不适用于 server side automation of office .

关于c# - 在 .NET Core 中将 Word doc 和 docx 格式转换为 PDF,无需 Microsoft.Office.Interop,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46580718/

相关文章:

c# - MVC 4 - Kendo 网格数据绑定(bind)

c# - AddSingleton<>() 与 AddSingleton()

python - 如何区分数字创建的 PDF 和可搜索的 PDF?

vba - MS WORD VBA 使用变量查找替换文本

php - Word 的 HTML 分页符 - page-break-before :always not working

c# - 在构造函数中使用 'this' 关键字

c# - StackExchange.Redis - 如何克隆 HashMap

html - 浏览器如何支持 PDF 查看以及哪些浏览器

android - 如何在 Android 中以原始格式创建 PDF 文件

html - 在 Word 中打开 HTML 文件时不显示 CSS