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

我需要在浏览器中显示Word .doc.docx文件。 没有真正的客户端方式来执行此操作,并且出于法律原因,这些文档无法与Google文档或Microsoft Office 365共享。

浏览器无法显示Word,但可以显示PDF,因此我想在服务器上将这些文档转换为PDF然后显示。

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

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

如何在访问Microsoft.Office.Interop.Word 情况下.doc.docx文件转换为.pdf

这就是PITA,难怪所有第三方解决方案每个开发者收费500美元。

好消息是Open XML SDK最近添加了对.Net Standard的支持,所以看起来你很喜欢.docx格式。

目前的坏消息是.NET Core上的PDF生成库没有太多选择。 因为看起来你不想支付一个而且你不能合法地使用第三方服务,我们别无选择,只能推出自己的服务。

主要问题是将Word文档内容转换为PDF。 其中一种流行的方法是将Docx读入HTML并将其导出为PDF。 很难找到,但是有支持将Docx转换为HTML的OpenXMLSDK- PowerTools的 .Net Core版本。 Pull Request“即将被接受”,你可以从这里得到它:

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项目。 运行控制台项目:

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

如果您运行该项目,您将看到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"; } } 

现在我们可以得到图像:

在此处输入图像描述

如果您只想在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设置为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的结果相当令人印象深刻,我怀疑很多人会发现很多差异(特别是如果他们从未看到原版):

在此处输入图像描述

PS。 我意识到你想将.doc.docx转换为PDF。 我建议您自己使用特定的非服务器Windows / Microsoft技术将.doc转换为docx。 doc格式是二进制格式,不适用于办公室的服务器端自动化 。

使用LibreOffice二进制文件

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

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

实施

我写了一个使用soffice二进制文件的简单c#程序。 这只是一个概念validation(和我在c#第一个程序)。 只有安装了LibreOffice软件包,它才支持开箱即用的WindowsLinux

这是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); } } } } 

资源

  • 项目存储库 :包含Windows LibreOffice二进制文件的包的示例。

结果

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

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

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

输出:

  • Wine: testdoc , testdocx 。

  • 单声道: testdoc , testdocx 。

我最近用spire.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 pdfByteContent, List 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页,它将允许您转换它们到pdfs