web-dev-qa-db-ja.com

Microsoft.Office.Interopを使用せずに、.NET CoreでWord docおよびdocx形式をPDFに変換します

Word .docおよび.docxファイルをブラウザーに表示する必要があります。これを行う実際のクライアント側の方法はなく、これらのドキュメントを法的な理由でGoogleドキュメントやMicrosoft Office 365と共有することはできません。

ブラウザはWordを表示できませんが、PDFを表示できます。したがって、これらのドキュメントをサーバー上でPDFに変換してから表示したいと思います。

これはMicrosoft.Office.Interop.Wordを使用して行うことができますが、私のアプリケーションは.NET Coreであり、Office相互運用機能にアクセスできません。 Azureで実行することもできますが、他のDockerコンテナーで実行することもできます。

これには多くの同様の質問があるように見えますが、ほとんどはフルフレームワーク.NETについて尋ねているか、サーバーがWindows OSであり、どんな答えも私には役に立たないと仮定しています。

.docおよび.docxファイルを.pdfに変換するにはどうすればいいですか?なしMicrosoft.Office.Interop.Wordへのアクセス

36
Keith

これはそのようなPITAでした。すべてのサードパーティソリューションが開発者ごとに500ドルを請求しているのも不思議ではありません。

幸いなことに、 Open XML SDKは.Net Standardのサポートを最近追加しました ですので、.docxフォーマットで運がよさそうです。

悪いニュース現時点では.NET CoreのPDF生成ライブラリには多くの選択肢がありません。支払いたいとは思えないので、合法的にサードパーティのサービスを使用することはできません。

主な問題は、WordドキュメントコンテンツをPDFに変換することです。一般的な方法の1つは、DocxをHTMLに読み込んでPDFにエクスポートすることです。見つけるのは困難でしたが、DocxからHTMLへの変換をサポートするOpenXMLSDK-PowerToolsの.Net Coreバージョンがあります。プルリクエストは「受け入れられようとしている」ため、ここから取得できます。

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

Test.docxがいくつかのテキストを含む有効なWord文書であることを確認してください。有効でない場合、エラーが発生する可能性があります。

指定されたパッケージは無効です。主な部分がありません

プロジェクトを実行すると、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";
    }
}

ImageFormatを使用するには、System.Drawing.Common NuGetパッケージが必要になる場合があります

これで画像を取得できます:

enter image description here

WebブラウザーでWord .docxファイルのみを表示する場合は、HTMLをPDFに変換しないほうが帯域幅が大幅に増加するためです。 HTMLをファイルシステム、クラウド、またはVPPテクノロジーを使用したdBで保存できます。


HTMLからPDFへ

次に行う必要があるのは、HTMLをDinkToPdfに渡すことです。 DinkToPdf(90 MB)ソリューションをダウンロードします。ソリューションをビルドします-すべてのパッケージが復元され、ソリューションがコンパイルされるまでに時間がかかります。

重要:

LinuxおよびWindowsで実行する場合、DinkToPdfライブラリーのプロジェクトのルートにlibwkhtmltox.soおよびlibwkhtmltox.dllファイルが必要です。必要な場合は、Mac用のlibwkhtmltox.dylibファイルもあります。

これらのdllはv0.12.4フォルダーにあります。 32ビットまたは64ビットのPCに応じて、3つのファイルをDinkToPdf-master\DinkToPfd.TestConsoleApp\bin\Debug\netcoreapp1.1フォルダーにコピーします。

重要2:

LibgdiplusがDockerイメージまたはLinuxマシンにインストールされていることを確認してください。 libwkhtmltox.soライブラリはそれに依存しています。

DinkToPfd.TestConsoleAppをStartUpプロジェクトとして設定し、Program.csファイルを変更して、Lorium IpsomテキストではなくOpen-Xml-PowerToolsで保存された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の結果は非常に印象的であり、多くの人々が多くの違いを見つけることを疑っています(特にオリジナルを見ない場合):

enter image description here

追伸.doc.docxの両方をPDFに変換したいと思っています。特定の非サーバーWindows/Microsoftテクノロジーを使用して、.docをdocxに変換するサービスを自分で作成することをお勧めします。 doc形式はバイナリであり、 サーバー側のオフィスの自動化 向けではありません。

49
Jeremy Thompson

LibreOfficeバイナリを使用する

LibreOfficeプロジェクトは、MS Office向けのオープンソースのクロスプラットフォーム代替です。その機能を使用して、docおよびdocxファイルをPDFにエクスポートできます。現在、LibreOfficeには.NET用の公式APIがないため、sofficeバイナリと直接やり取りします。

これは一種の「ハッキング」ソリューションですが、バグの数が少なく、コストを維持できるソリューションだと思います。この方法のもう1つの利点は、docdocxからの変換に制限されないことです。LibreOfficeのすべてのサポート形式(odt、html、スプレッドシートなど)から変換できます。

実装

sofficeバイナリを使用する簡単なc#プログラムを作成しました。これは単なる概念実証です(c#の最初のプログラム)。 LibreOfficeパッケージがインストールされている場合のみ、Windowsをそのままサポートし、Linuxをサポートします。

これは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);
            }
        }
    }
}

資源

結果

monoでコンパイルされたArch Linuxでテストしました。 monとLinuxバイナリを使用して実行し、wine:でWindowsバイナリを使用します。

結果は Tests ディレクトリにあります:

入力ファイル: testdoc.doctestdocx.docx

出力:

9
Shmuel H.

最近、これを 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に

以下のコメントで言及されているように、RTL言語を支援することもできません。それを指摘してくれた@Ariaに感謝します。

4
Bomie

申し訳ありませんが、コメントするほどの評判はありませんが、ジェレミートンプソンの答えに2セントを費やしたいと思います。そして、これが誰かを助けることを願っています。

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 DirectoryCopy alwaysに設定してこの問題を解決します。

これが私のような初心者に役立つことを願っています:)

1
Samuel Leung

プレースホルダーを使用してDOCXをPDFに変換するために、無料の "Report-From-DocX-HTML-To-PDF-Converter"ライブラリを作成しました。 MITライセンスの下でのNET CORE。私はとても単純なソリューションが存在せず、すべての商用ソリューションが非常に高価だったので非常に恐れていたからです。詳細な説明とプロジェクトの例については、こちらをご覧ください。

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

次の変換を実行できます(プレースホルダーを置き換えるテンプレート/レポートエンジンと共に):

  • DOCXからDOCX
  • DOCXからPDFへ
  • DOCXからHTML
  • HTMLからHTML
  • HTMLからPDF
  • HTMLからDOCX

以下のプレースホルダーを追加できます。

  • テキスト
  • 繰り返しテーブル行
  • 画像
0
Smart In Media