Подтвердить что ты не робот

Преобразование Word doc и docx в PDF в .NET Core без Microsoft.Office.Interop

Мне нужно отображать файлы Word .doc и .docx в браузере. Там нет реального способа взаимодействия с клиентом, и эти документы нельзя распространять с документами Google или Microsoft Office 365 по юридическим причинам.

Браузеры не могут отображать Word, но могут отображать PDF, поэтому я хочу конвертировать эти документы в PDF на сервере, а затем отображать это.

Я знаю, что это можно сделать с помощью Microsoft.Office.Interop.Word, но мое приложение .NET Core и не имеет доступа к Office interop. Он может работать на Azure, но он может также работать в контейнере Docker на что-либо еще.

Похоже, что к этому относится много похожих вопросов, однако большинство из них спрашивают о полной инфраструктуре .NET или предположении, что сервер является ОС Windows, и любой ответ мне не подходит.

Как преобразовать файлы .doc и .docx в .pdf без доступа к Microsoft.Office.Interop.Word?

4b9b3361

Ответ 1

Это была такая PITA, неудивительно, что все сторонние решения стоят 500 долларов за разработчика.

Хорошей новостью является то, что в Open XML SDK недавно добавлена поддержка .Net Standard, поэтому, похоже, вам повезло с форматом .docx.

Плохая новость: на данный момент нет большого выбора для библиотек PDF в .NET Core. Поскольку это не похоже на то, что вы хотите заплатить за него, и вы не можете легально использовать стороннюю услугу, у нас нет иного выбора, кроме как самим кататься.

Основная проблема заключается в преобразовании содержимого документа Word в формат PDF. Одним из популярных способов является чтение Docx в HTML и его экспорт в PDF. Трудно было найти, но есть версия .Net Core OpenXMLSDK-PowerTools, которая поддерживает преобразование 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 в качестве проекта запуска. Запустите консольный проект:

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:

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

Вам может понадобиться пакет System.Drawing.Common NuGet, чтобы использовать ImageFormat

Теперь мы можем получить изображения:

enter image description here

Если вы хотите показывать файлы Word.docx только в веб-браузере, лучше не конвертировать HTML в PDF, поскольку это значительно увеличит пропускную способность. Вы можете хранить HTML в файловой системе, облаке или в дБ, используя технологию VPP.


HTML в PDF

Следующее, что нам нужно сделать, это передать HTML в DinkToPdf. Загрузите решение DinkToPdf (90 МБ). Создайте решение - потребуется время, чтобы все пакеты были восстановлены и решение скомпилировано.

ВАЖНО:

Библиотека DinkToPdf требует файл libwkhtmltox.so и libwkhtmltox.dll в корневом каталоге вашего проекта, если вы хотите работать в Linux и Windows. Там также есть файл libwkhtmltox.dylib для Mac, если он вам нужен.

Эти библиотеки находятся в папке v0.12.4. В зависимости от вашего компьютера, 32 или 64 бит, скопируйте 3 файла в папку DinkToPdf-master\DinkToPfd.TestConsoleApp\bin\Debug\netcoreapp1.1.

ВАЖНО 2:

Убедитесь, что у вас установлен libgdiplus в вашем образе Docker или на вашем Linux-компьютере. Библиотека libwkhtmltox.so зависит от этого.

Установите DinkToPfd.TestConsoleApp в качестве проекта автозагрузки и измените файл Program.cs так, чтобы он считывал htmlContent из файла HTML, сохраненного с помощью Open-Xml-PowerTools, вместо текста Lorium Ipsom.

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 vs PDF довольно впечатляющий, и я сомневаюсь, что многие люди заметят много различий (особенно если они никогда не увидят оригинал):

enter image description here

Ps. Я понимаю, что вы хотели конвертировать .doc и .docx в PDF. Я бы посоветовал сделать сервис самостоятельно, чтобы конвертировать .doc в docx с использованием определенной несерверной технологии Windows/Microsoft. Формат doc является двоичным и не предназначен для автоматизации работы офиса на стороне сервера.

Ответ 2

Использование двоичного файла LibreOffice

Проект LibreOffice - это кроссплатформенная альтернатива с открытым исходным кодом для MS Office. Мы можем использовать его возможности для экспорта файлов doc и docx в PDF. В настоящее время у LibreOffice нет официального API для .NET, поэтому мы поговорим непосредственно о двоичном soffice.

Это своего рода "хакерское" решение, но я думаю, что это решение с меньшим количеством ошибок и возможным обслуживанием. Еще одним преимуществом этого метода является то, что вы не ограничены конвертированием из 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);
            }
        }
    }
}

Ресурсы

Результаты

Я тестировал его на Arch Linux, скомпилированном с mono. Я запускаю его, используя mon и бинарный файл Linux, и с wine: используя бинарный файл Windows.

Вы можете найти результаты в каталоге тестов:

Входные файлы: testdoc.doc, testdocx.docx

Выходы:

Ответ 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;
        }
    }

Затем я сшиваю отдельные PDF файлы позже, используя itextsharp.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

Извините, у меня недостаточно репутации, чтобы комментировать, но я бы хотел поставить свои два цента на ответ джереми Томпсона. И надеюсь, что это поможет кому-то.

Когда я просматривал ответ джереми Томпсона, после загрузки OpenXMLSDK-PowerTools и запуска OpenXMLPowerTools.Core.Example я получил ошибку вроде

the specified package is invalid. the main part is missing

на линии

var document = WordprocessingDocument.Open(source);

После нескольких часов борьбы я обнаружил, что test.docx, скопированный в файл bin, занимает всего 1 КБ. Чтобы решить эту проблему, щелкните правой кнопкой мыши test.docx> Properties, установите Copy to Output Directory на Copy always, чтобы решить эту проблему.

Надеюсь, что это поможет некоторым новичкам, как я :)

Ответ 5

Такие API-интерфейсы, как SPIRE, должны демонстрировать определенную ответственность за декларирование ограниченности при продвижении бесплатных версий. Разработчик натыкается на фактическое ограничение только после его реализации. Фактически, предел описан в разделе, написанном где-то в середине лицензионного соглашения. Что слишком поздно, иногда..

Ответ 6

Для преобразования DOCX в PDF даже с заполнителями я создал бесплатную библиотеку "Report-From-DocX-HTML-To-PDF-Converter" с .NET CORE под лицензией MIT потому что я был настолько взволнован, что простого решения не было, а все коммерческие решения были очень дорогими. Вы можете найти его здесь с подробным описанием и примером проекта:

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).