Kei*_*ith 39 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
?
Jer*_*son 57
这就是PITA,难怪所有第三方解决方案每个开发者收取500美元.
好消息是Open XML SDK最近添加了对.Net Standard的支持,所以看起来你很喜欢这种.docx
格式.
坏消息目前还没有对.NET核心生成PDF库很多的选择.因为看起来你不想支付一个而且你不能合法地使用第三方服务,我们别无选择,除了自己动手.
主要问题是将Word文档内容转换为PDF.其中一种流行的方法是将Docx读入HTML并将其导出为PDF.很难找到,但OpenXMLSDK- PowerTools的 .Net Core版本支持将Docx转换为HTML.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();
Run Code Online (Sandbox Code Playgroud)
如果您运行该项目,您将看到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";
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们可以得到图像:
如果您只想在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]" }
}
}
};
Run Code Online (Sandbox Code Playgroud)
Docx与PDF的结果相当令人印象深刻,我怀疑很多人会发现很多差异(特别是如果他们从未看到原版):
PS.我意识到你想要转换为PDF .doc
和.docx
PDF.我建议您自己使用特定的非服务器Windows/Microsoft技术将.doc转换为docx.doc格式是二进制格式,不适用于办公室的服务器端自动化.
Shm*_* H. 10
LibreOffice项目是MS Office的开源跨平台替代方案.我们可以使用它的功能导出doc
和docx
文件到PDF
.目前,LibreOffice没有.NET的官方API,因此,我们将直接与soffice
二进制文件对话.
这是一种"hacky"解决方案,但我认为这是解决方案,可以减少错误并保持成本.此方法的另一个优点是您不限于转换doc
和docx
:您可以从每种格式的LibreOffice支持转换它(例如odt,html,电子表格等).
我写了一个c#
使用soffice
二进制文件的简单程序.这只是一个概念验证(和我的第一个程序c#
).它支持Windows
开箱即用,并且Linux
只有在安装了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);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我在Arch Linux上测试了它,用它编译mono
.我使用mon和Linux二进制文件运行它,并使用wine
:使用Windows二进制文件.
您可以在Tests目录中找到结果:
输入文件:testdoc.doc,testdocx.docx
Gom*_*nik 10
如果您使用容器化解决方案(Docker)没有遇到任何问题,那么这里有一个非常好的项目:
哥登堡计划
我之前确实尝试过。它已经使用 LibreOffice 将 docx 转换为 pdf,但它还有更多功能。另外,它是一个无状态的 dockerized api,它是自给自足的。
我最近使用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;
}
}
Run Code Online (Sandbox Code Playgroud)
然后,我稍后使用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();
}
}
Run Code Online (Sandbox Code Playgroud)
我不知道这是否适合您的用例,因为您没有指定要编写的文档的大小,但是如果它们的页面数大于3页,或者您可以将它们控制为少于3页,那么它将允许您对其进行转换转换成pdf
如下面的评论中所述,它也无法使用RTL语言,谢谢@Aria指出这一点。
抱歉,我没有足够的声誉来发表评论,但我想对杰里米·汤普森的答案发表两分钱。希望这对某人有帮助。
当我浏览 Jeremy Thompson 的答案时,下载OpenXMLSDK-PowerTools
并运行后OpenXMLPowerTools.Core.Example
,我收到了类似的错误
the specified package is invalid. the main part is missing
Run Code Online (Sandbox Code Playgroud)
在线上
var document = WordprocessingDocument.Open(source);
Run Code Online (Sandbox Code Playgroud)
经过几个小时的努力,我发现test.docx
复制到bin文件只有1kb。要解决这个问题,右键test.docx
> Properties
,设置Copy to Output Directory
即可Copy always
解决这个问题。
希望这对像我这样的新手有帮助:)
归档时间: |
|
查看次数: |
32317 次 |
最近记录: |