在没有 Microsoft.Office.Interop 的 .NET Core 中将 Word doc 和 docx 格式转换为 PDF

Posted

技术标签:

【中文标题】在没有 Microsoft.Office.Interop 的 .NET Core 中将 Word doc 和 docx 格式转换为 PDF【英文标题】:Convert Word doc and docx format to PDF in .NET Core without Microsoft.Office.Interop 【发布时间】:2018-03-16 18:47:50 【问题描述】:

我需要在浏览器中显示 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 的访问权限?

【问题讨论】:

这就像要求在没有 Microsoft 帮助的情况下将 Word 转换为 PDF。这在理论上是可能的,但 Word 是一个如此庞大的应用程序,在一般情况下,它实际上是不可能的,Word 仍然是最好的。您可以将您的核心应用程序连接到公开转换服务的不透明专用 Windows 框(不要忽视许可问题)。否则,如果你限制你的转换目标,有一些库应该会有所帮助(aspose、itextsharp 等)。另外,请记住,doc 和 docx 是根本不同的格式,解决方案可能会有所不同。 @SimonMourier docx (据说)是一种开放格式(微软在这方面推动了很长时间),但它相当糟糕 - 在引擎盖下它只是一个 zip 中的 xml 文件的负载。 doc 是二进制的,但 20 年来几乎没有变化,并且已经有很多针对该格式的解析器。 Office 一直是桌面应用程序,对服务器来说是一项昂贵的责任,我不能是第一个/唯一一个提出这个要求的人。 @SimonMourier 我以前使用过 Aspose - 我的团队并没有给我留下深刻的印象,它的功能非常昂贵,而且它是完整的 .NET,所以无论如何在这里都没有用。 iText 非常适合 PDF 操作,但当有大量开源的 PDF API 时,它也很昂贵。 好吧,看起来你已经有了所有的答案;事实上,你不是唯一一个寻找圣杯的人:-) 我真的不明白这个问题。这些格式有很多开源实现。例如,您可以获取 libreoffice 二进制文件并运行 soffice --convert-to pdf --nologo name.docx,然后您将获得一个 pdf 文件。 【参考方案1】:

这太痛苦了,难怪所有第三方解决方案都向每位开发者收取 500 美元。

好消息是Open XML SDK recently added support for .Net Standard,所以看起来你很幸运使用.docx 格式。

坏消息目前.NET Core 上的 PDF 生成库没有太多选择。由于您似乎不想付费,而且您不能合法使用第三方服务,所以我们别无选择,只能自己推出。

主要问题是将 Word 文档内容转换为 PDF。一种流行的方法是将 Docx 读入 html 并将其导出为 PDF。很难找到,但是 OpenXMLSDK-PowerTools 的 .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 文档,否则您可能会收到错误:

指定的包无效。主要部分不见了

如果您运行该项目,您将看到 HTML 看起来几乎与 Word 文档中的内容一模一样:

但是,如果您尝试使用带有图片或链接的 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

现在我们可以获取图片了:

如果您只想在网络浏览器中显示 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 设置为 StartUp 项目并更改 Program.cs 文件以从使用 Open-Xml-PowerTools 而不是 Lorium Ipsom 文本保存的 HTML 文件中读取 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 的结果令人印象深刻,我怀疑很多人会找出许多不同之处(尤其是如果他们从未看过原件的话):

附言。我知道您想将.doc.docx 都转换为PDF。我建议自己制作一项服务,使用特定的非服务器 Windows/Microsoft 技术将 .doc 转换为 docx。文档格式为二进制,不适用于server side automation of office。

【讨论】:

干杯,很好的答案。我想我可能已经找到了最后一块拼图,因为我找到了一个开源 .NET Mono doc > docx 转换器,它可以是 ported to .NET Core。 @JeremyThompson 我已经在 .NET Core 中启动并运行了 b2xtranslator,从专用 ZIP 实现切换到 System.IO.Compression,并修复了奇怪的命令行测试以仅使用 NUnit。它仍然不完全存在 - 致力于让所有单元测试通过并添加新的以涵盖更多用例/代码。如果您(或任何人)有兴趣,请寻找贡献者。 @vapcguy - 您没有阅读问题。 OP 特别无法在 Linux 上安装 Office,并且 KB257757 表示不支持办公自动化服务器端。 @JeremyThompson 是的,你是对的......:facepalm:错过了那部分。 @BorisLipschitz 我知道这是旧的,但为了其他人的利益,您安装 System.Drawing.Common NuGet 包以使用 ImageFormat【参考方案2】:

使用 LibreOffice 二进制文件

LibreOffice 项目是 MS Office 的开源跨平台替代方案。我们可以使用它的功能将docdocx 文件导出到PDF。目前,LibreOffice 没有用于 .NET 的官方 API,因此,我们将直接与 soffice 二进制文件对话。

这是一种“hacky”解决方案,但我认为它是具有较少错误和可能维护成本的解决方案。此方法的另一个优点是您不限于从 docdocx 转换:您可以从 LibreOffice 支持的每种格式(例如 odt、html、电子表格等)转换它。

实现

我编写了一个简单的c# 程序,它使用soffice 二进制文件。这只是一个概念验证(也是我在c# 中的第一个程序)。它支持开箱即用的WindowsLinux,前提是已经安装了LibreOffice 软件包。

这是main.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Reflection;

namespace DocToPdf

    public class LibreOfficeFailedException : Exception
    
        public LibreOfficeFailedException(int exitCode)
            : base(string.Format("LibreOffice has failed with ", exitCode))
            
    

    class Program
    
        static string getLibreOfficePath() 
            switch (Environment.OSVersion.Platform) 
                case PlatformID.Unix:
                    return "/usr/bin/soffice";
                case PlatformID.Win32NT:
                    string binaryDirectory = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                    return binaryDirectory + "\\Windows\\program\\soffice.exe";
                default:
                    throw new PlatformNotSupportedException ("Your OS is not supported");
            
        

        static void Main(string[] args) 
            string libreOfficePath = getLibreOfficePath();

            // FIXME: file name escaping: I have not idea how to do it in .NET.
            ProcessStartInfo procStartInfo = new ProcessStartInfo(libreOfficePath, string.Format("--convert-to pdf --nologo 0", args[0]));
            procStartInfo.RedirectStandardOutput = true;
            procStartInfo.UseShellExecute = false;
            procStartInfo.CreateNoWindow = true;
            procStartInfo.WorkingDirectory = Environment.CurrentDirectory;

            Process process = new Process()  StartInfo =      procStartInfo, ;
            process.Start();
            process.WaitForExit();

            // Check for failed exit code.
            if (process.ExitCode != 0) 
                throw new LibreOfficeFailedException(process.ExitCode);
            
        
    

资源

The project repository:包含 Windows LibreOffice 二进制文件的包示例。

结果

我在 Arch Linux 上测试过它,用 mono 编译。我使用 mon 和 Linux 二进制文件运行它,并使用 wine: 使用 Windows 二进制文件。

您可以在Tests目录中找到结果:

输入文件:testdoc.doc、testdocx.docx

输出:

葡萄酒:testdoc、testdocx。

单声道:testdoc、testdocx。

【讨论】:

请注意 libreoffice 将无法正确转换使用专有字体的办公文档(如果我没记错的话,verdana 有问题),除非它们安装在操作系统上。除了这个字体问题,没有太大问题。 如果文件名是用户输入的值,则在处理文件名时要格外小心。这可能会导致在您的服务器上执行代码。 谢谢你,你拯救了我的一天!令人难以置信的是,在任何地方都找不到任何免费且运行良好的软件,只有针对这种常见任务的付费软件或服务【参考方案3】:

我最近使用FreeSpire.Doc 完成了这项工作。免费版的限制为 3 页,但它可以使用以下方式轻松地将 docx 文件转换为 PDF:

private void ConvertToPdf()

    try
    
        for (int i = 0; i < listOfDocx.Count; i++)
        
            CurrentModalText = "Converting To PDF";
            CurrentLoadingNum += 1;

            string savePath = PdfTempStorage + i + ".pdf";
            listOfPDF.Add(savePath);

            Spire.Doc.Document document = new Spire.Doc.Document(listOfDocx[i], FileFormat.Auto);
            document.SaveToFile(savePath, FileFormat.PDF);
        
    
    catch (Exception e)
    
        throw e;
    

然后我稍后使用iTextSharp.pdf 将这些单独的 PDF 缝合在一起:

public static byte[] concatAndAddContent(List<byte[]> pdfByteContent, List<MailComm> localList)

    using (var ms = new MemoryStream())
    
        using (var doc = new Document())
        
            using (var copy = new PdfSmartCopy(doc, ms))
            
                doc.Open();
                // add checklist at the start
                using (var db = new StudyContext())
                
                    var contentId = localList[0].ContentID;
                    var temp = db.MailContentTypes.Where(x => x.ContentId == contentId).ToList();
                    if (!temp[0].Code.Equals("LAB"))
                    
                        pdfByteContent.Insert(0, CheckListCreation.createCheckBox(localList));
                    
                

                // Loop through each byte array
                foreach (var p in pdfByteContent)
                
                    // Create a PdfReader bound to that byte array
                    using (var reader = new PdfReader(p))
                    
                        // Add the entire document instead of page-by-page
                        copy.AddDocument(reader);
                    
                

                doc.Close();
            
        

        // Return just before disposing
        return ms.ToArray();
    

我不知道这是否适合您的用例,因为您尚未指定要编写的文档的大小,但如果它们小于 3 页,或者您可以将它们操作为小于3 页,它可以让您将它们转换成 PDF。

正如下面的 cmets 中提到的,它也无法帮助 RTL 语言,感谢@Aria 指出这一点。

【讨论】:

只是为了澄清,因为你没有提到它。 “Spire.Doc”在转换后的 PDF 顶部留下一个红色的“警告评估”水印。在 Nuget 上搜索时,查找“FreeSpire.Doc”,此版本不包含水印。不错的 API,这应该标记为答案 imo。 是的,这就是我所做的,抱歉,我应该更具体一些。希望这个答案对您有所帮助! 我正在使用 FreeSpire.Doc,但仍然收到 eval 警告。 我最近测试的 free 有一些问题,1-主要问题是关于 RTL 文档,例如波斯语,我所有的字符都搞砸了,不可读。 2-文件末尾有签名。 3- 太慢了。 @MarioZ 感谢您的评论,我已经通过为需要此功能的服务器安装 Microsoft Word 解决了这个问题,但将来我们可能会更改它,我很高兴与您交谈。感谢您提供有用的链接。【参考方案4】:

对不起,我没有足够的声誉来发表评论,但想把我的两分钱花在杰里米·汤普森的回答上。并希望这对某人有所帮助。

当我浏览 Jeremy Thompson 的答案时,在下载 OpenXMLSDK-PowerTools 并运行 OpenXMLPowerTools.Core.Example 后,出现类似

的错误
the specified package is invalid. the main part is missing

在线上

var document = WordprocessingDocument.Open(source);

挣扎了几个小时后,我发现复制到bin文件的test.docx只有1kb。要解决这个问题,右击test.docx > Properties,将Copy to Output Directory 设置为Copy always 即可解决此问题。

希望这对像我这样的新手有帮助:)

【讨论】:

【参考方案5】:

为了将 DOCX 转换为 PDF(即使使用占位符),我在 MIT 下使用 .NET CORE 创建了一个免费的“Report-From-DocX-HTML-To-PDF-Converter” 库许可证,因为我很紧张,以至于没有简单的解决方案存在,而且所有的商业解决方案都非常昂贵。你可以在这里找到它的详细描述和示例项目:

https://github.com/smartinmedia/Net-Core-DocX-HTML-To-PDF-Converter

您只需要免费的 LibreOffice。我建议使用 LibreOffice 便携版,因此它不会更改您的服务器设置中的任何内容。看看文件“soffice.exe”(在 Linux 上的名称不同)的位置,因为您需要它来填充变量“locationOfLibreOfficeSoffice”。

以下是从 DOCX 转换为 HTML 的工作原理:

string locationOfLibreOfficeSoffice =   @"C:\PortableApps\LibreOfficePortable\App\libreoffice\program\soffice.exe";

var docxLocation = "MyWordDocument.docx";

var rep = new ReportGenerator(locationOfLibreOfficeSoffice);

//Convert from DOCX to PDF
test.Convert(docxLocation, Path.Combine(Path.GetDirectoryName(docxLocation), "Test-Template-out.pdf"));


//Convert from DOCX to HTML
test.Convert(docxLocation, Path.Combine(Path.GetDirectoryName(docxLocation), "Test-Template-out.html"));

如您所见,您还可以从 DOCX 转换为 HTML。此外,您可以将占位符放入 Word 文档中,然后您可以用值“填充”该文档。但是,这不在您的问题范围内,但您可以在 Github (README) 上阅读相关内容。

【讨论】:

我有几个问题: 1. 在生产中用于每分钟 10 次 docx 到 pdf 转换的平均负载时是否存在任何已知问题? 2. 便携式 libreoffice 大约 1 GB。您能否指出可以删除哪些文件夹/文件以使其更轻而不影响功能? 转换也需要 10 多秒。正常吗?【参考方案6】:

这是对 Jeremy Thompson 非常有帮助的回答的补充。除了 word 文档正文之外,我还希望将 word 文档的页眉(和页脚)转换为 HTML。我不想修改 Open-Xml-PowerTools,所以我修改了 Jeremy 示例中的 Main() 和 ParseDOCX(),并添加了两个新函数。 ParseDOCX 现在接受一个字节数组,因此原始 Word Docx 不会被修改。

static void Main(string[] args)

    var fileInfo = new FileInfo(@"c:\temp\MyDocWithImages.docx");
    byte[] fileBytes = File.ReadAllBytes(fileInfo.FullName);
    string htmlText = string.Empty;
    string htmlHeader = string.Empty;
    try
    
        htmlText = ParseDOCX(fileBytes, fileInfo.Name, false);
        htmlHeader = ParseDOCX(fileBytes, fileInfo.Name, true);
    
    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(fileBytes, fileInfo.Name, false);
            htmlHeader = ParseDOCX(fileBytes, fileInfo.Name, true);
        
    

    var writer = File.CreateText("test1.html");
    writer.WriteLine(htmlText.ToString());
    writer.Dispose();
    var writer2 = File.CreateText("header1.html");
    writer2.WriteLine(htmlHeader.ToString());
    writer2.Dispose();


private static string ParseDOCX(byte[] fileBytes, string filename, bool headerOnly)

    try
    
        using (MemoryStream memoryStream = new MemoryStream())
        
            memoryStream.Write(fileBytes, 0, fileBytes.Length);
            using (WordprocessingDocument wDoc = WordprocessingDocument.Open(memoryStream, true))
            
                int imageCounter = 0;
                var pageTitle = filename;
                var part = wDoc.CoreFilePropertiesPart;
                if (part != null)
                
                    pageTitle = (string)part.GetXDocument()
                                            .Descendants(DC.title)
                                            .FirstOrDefault() ?? filename;
                

                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;
                    
                ;

                // Put header into document body, and remove everything else
                if (headerOnly)
                
                    MoveHeaderToDocumentBody(wDoc);
                

                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";
    


private static void MoveHeaderToDocumentBody(WordprocessingDocument wDoc)

    MainDocumentPart mainDocument = wDoc.MainDocumentPart;
    XElement docRoot = mainDocument.GetXDocument().Root;
    XElement body = docRoot.Descendants(W.body).First();
    // Only handles first header. Header info: https://docs.microsoft.com/en-us/office/open-xml/how-to-replace-the-header-in-a-word-processing-document
    HeaderPart header = mainDocument.HeaderParts.FirstOrDefault();
    XElement headerRoot = header.GetXDocument().Root;

    AddXElementToBody(headerRoot, body);

    // document body will have new headers when we return from this function
    return;


private static void AddXElementToBody(XElement sourceElement, XElement body)

    // Clone the children nodes
    List<XElement> children = sourceElement.Elements().ToList();
    List<XElement> childClones = children.Select(el => new XElement(el)).ToList();

    // Clone the section properties nodes
    List<XElement> sections = body.Descendants(W.sectPr).ToList();
    List<XElement> sectionsClones = sections.Select(el => new XElement(el)).ToList();

    // clear body
    body.Descendants().Remove();

    // add source elements to body
    foreach (var child in childClones)
    
        body.Add(child);
    

    // add section properties to body
    foreach (var section in sectionsClones)
    
        body.Add(section);
    

    // get text from alternate content if needed - either choice or fallback node
    XElement alternate = body.Descendants(MC.AlternateContent).FirstOrDefault();
    if (alternate != null)
    
        var choice = alternate.Descendants(MC.Choice).FirstOrDefault();
        var fallback = alternate.Descendants(MC.Fallback).FirstOrDefault();
        if (choice != null)
        
            var choiceChildren = choice.Elements();
            foreach(var choiceChild in choiceChildren)
            
                body.Add(choiceChild);
            
        
        else if (fallback != null)
        
            var fallbackChildren = fallback.Elements();
            foreach (var fallbackChild in fallbackChildren)
            
                body.Add(fallbackChild);
            
        
    

您可以添加类似的方法来处理 Word 文档页脚。

就我而言,然后我将 HTML 文件转换为图像(使用 Net-Core-Html-To-Image,同样基于 wkHtmlToX)。我将标题和正文图像组合在一起(使用Magick.NET-Q16-AnyCpu),将标题图像放在正文图像的顶部。

【讨论】:

【参考方案7】:

如果您可以访问 Office 365,则可以实施替代解决方案。这比我之前的回答限制更少,但需要购买。

我获得了一个图形 API 令牌、我想要使用的网站以及我想要使用的驱动器。

之后我抓取了 docx 的字节数组

    public static async Task<Stream> GetByteArrayOfDocumentAsync(string baseFilePathLocation)
    
        var byteArray = File.ReadAllBytes(baseFilePathLocation);
        using var stream = new MemoryStream();
        stream.Write(byteArray, 0, (int) byteArray.Length);

        return stream;
    

这个流然后通过我们的图形 api 令牌使用客户端设置上传到图形 api

        public static async Task<string> UploadFileAsync(HttpClient client,
                                                     string siteId,
                                                     MemoryStream stream,
                                                     string driveId,
                                                     string fileName,
                                                     string folderName = "root")
    

        var result = await client.PutAsync(
            $"https://graph.microsoft.com/v1.0/sites/siteId/drives/driveId/items/folderName:/fileName:/content",
            new ByteArrayContent(stream.ToArray()));
        var res = JsonSerializer.Deserialize<SharepointDocument>(await result.Content.ReadAsStringAsync());
        return res.id;
    

然后我们使用提供的 api 从图形 api 下载以通过以下方式获取 PDF 文件

        public static async Task<Stream> GetPdfOfDocumentAsync(HttpClient client,
                                                            string siteId,
                                                            string driveId,
                                                            string documentId)
    


        var getRequest =
            await client.GetAsync(
                $"https://graph.microsoft.com/v1.0/sites/siteId/drives/driveId/items/documentId/content?format=pdf");
        return await getRequest.Content.ReadAsStreamAsync();

    

这给出了一个由刚刚创建的文档组成的流。

【讨论】:

以上是关于在没有 Microsoft.Office.Interop 的 .NET Core 中将 Word doc 和 docx 格式转换为 PDF的主要内容,如果未能解决你的问题,请参考以下文章

有没有啥方法可以在没有 Json 的 NSData 的情况下单独发布参数

Vue Prop 没有初始化器,也没有在构造函数中明确赋值

在 Facebook 上分享有时效果很好,有时效果不佳(没有标题、没有描述、没有缩略图)

在没有隐藏溢出的情况下,transitionend 事件在 FireFox 中没有持续触发

有没有办法在没有活动的情况下使用 Viewpager 实现 TabLayout?

有没有办法在没有大括号的 tsx typescript 代码中进行评论?