1. ホーム
  2. c#

Microsoft.Office.Interop を使わずに .NET Core で Word doc および docx フォーマットを PDF に変換する

2023-09-26 19:28:16

質問

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

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

私は、これが Microsoft.Office.Interop.Word を使用して実行できることは知っていますが、私のアプリケーションは .NET Core で、Office interop にアクセスすることはできません。Azure 上で実行することもできますが、他の何かで Docker コンテナーで実行することもできます。

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

どのように .doc.docx ファイルから .pdf を使わずに へのアクセス Microsoft.Office.Interop.Word ?

どのように解決するのですか?

これでは、サードパーティのソリューションがすべて、開発者1人あたり500ドルを請求するのも無理はないでしょう。

良い知らせは Open XML SDK が最近、.Net Standard のサポートを追加したことです。 をサポートしたことです。 .docx 形式をサポートするようになりました。

悪い知らせ 現時点では には、.NET Core 上の PDF 生成ライブラリの選択肢があまりありません。お金を払って購入したいとは思えませんし、サード パーティのサービスを合法的に使用することもできないため、独自に開発する以外の選択肢はほとんどありません。

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

https://github.com/OfficeDev/Open-Xml-PowerTools/tree/abfbaac510d0d60e2f492503c60ef897247716cf

さて、ドキュメントの内容をHTMLに抽出することができたので、それをPDFに変換する必要があります。HTMLをPDFに変換するためのライブラリはいくつかありますが、例えば DinkToPdf は、Webkit HTML to 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が有効なワード文書であり、テキストがあることを確認してください。

指定されたパッケージは無効です。主要な部分が欠けています。

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

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

これで画像を取得できるようになりました。

Web ブラウザで Word .docx ファイルを表示したいだけなら、HTML を PDF に変換しないほうがよいでしょう。HTML をファイル システム、クラウド、または VPP テクノロジーを使用した dB に保存することができます。


HTMLからPDFへ

次に必要なことは、HTMLをDinkToPdfに渡すことです。DinkToPdf (90 MB) ソリューションをダウンロードします。ソリューションをビルドします。すべてのパッケージがリストアされ、ソリューションがコンパイルされるまでしばらく時間がかかります。

重要なことです。

DinkToPdf ライブラリは、Linux と Windows で動作させたい場合、プロジェクトのルートに libwkhtmltox.so と libwkhtmltox.dll ファイルが必要です。必要であれば、Mac 用の libwkhtmltox.dylib ファイルもあります。

これらの DLL は、v0.12.4 フォルダにあります。お使いのPCの32ビット、64ビットに応じて、3つのファイルをDinkToPdf-master DinkToPfd.TestConsoleApp\binDebug\netcoreapp1.1 フォルダーにコピーしてください。

重要なこと 2:

Docker イメージまたは Linux マシンに libgdiplus がインストールされていることを確認してください。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 の結果は非常に印象的で、多くの人が多くの違いを見つけることはないでしょう (特に、オリジナルを見たことがない場合は)。

Ps. 私はあなたが両方の .doc.docx をPDFに変換することができます。サーバーではない特定のWindows/Microsoftの技術を使って、.docをdocxに変換するサービスを自分で作ることをお勧めします。doc形式はバイナリであり、以下のような用途を想定していない。 オフィスのサーバー サイド オートメーション .