web-dev-qa-db-fra.com

Convertissez les documents Word et docx en PDF dans .NET Core sans Microsoft.Office.Interop

Je dois afficher les fichiers Word .doc et .docx dans un navigateur. Pour ce faire, il n’existe aucun moyen réel pour le client et ces documents ne peuvent pas être partagés avec Google Docs ou Microsoft Office 365 pour des raisons juridiques.

Les navigateurs ne peuvent pas afficher Word, mais peuvent afficher le format PDF. Je souhaite donc convertir ces documents en PDF sur le serveur, puis les afficher.

Je sais que cela peut être fait à l'aide de Microsoft.Office.Interop.Word, mais mon application est .NET Core et n'a pas accès à Office Interop. Il pourrait être exécuté sur Azure, mais également dans un conteneur Docker.

Il semble y avoir beaucoup de questions similaires à cela, cependant, la plupart d'entre elles parlent de .NET entièrement structuré ou supposent que le serveur est un système d'exploitation Windows et toute réponse ne me sert à rien.

Comment convertir des fichiers .doc et .docx en .pdfsans accès à Microsoft.Office.Interop.Word?

36
Keith

Ce fut un tel PITA, pas étonnant que toutes les solutions tierces facturent 500 $ par développeur.

La bonne nouvelle est le Open XML SDK récemment ajouté au support de .Net Standard donc il semble que vous ayez de la chance avec le format .docx.

Mauvaise nouvelle pour le moment , les bibliothèques de génération PDF ne proposent pas beaucoup de choix sous .NET Core. Comme il ne semble pas que vous souhaitiez en payer un et que vous ne puissiez pas utiliser légalement un service tiers, nous n’avons guère le choix, à part de lancer le nôtre.

Le principal problème est de transformer le contenu du document Word en PDF. L'un des moyens les plus populaires consiste à lire le Docx en HTML et à l'exporter au format PDF. C'était difficile à trouver, mais il existe une version .Net Core d'OpenXMLSDK - PowerTools qui prend en charge la transformation de Docx en HTML. La demande de tirage est "sur le point d'être acceptée", vous pouvez l'obtenir à partir d'ici:

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

Maintenant que nous pouvons extraire le contenu d'un document au format HTML, nous devons le convertir en PDF. Il existe quelques bibliothèques pour convertir HTML en PDF, par exemple DinkToPdf est un wrapper multiplateforme autour du code HTML Webkit vers la bibliothèque libwkhtmltox PDF.

Je pensais que DinkToPdf était meilleur que https: //code.msdn.Microsoft.com/How-to-export-HTML-to-PDF-c5afd0ce


Docx en HTML

En résumé, téléchargez le projet .Net Core d'OpenXMLSDK-PowerTools et construisez-le (il suffit de visualiser OpenXMLPowerTools.Core et OpenXMLPowerTools.Core.Example - ignorez l'autre projet). Définissez OpenXMLPowerTools.Core.Example en tant que projet StartUp. Exécutez le projet de console:

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

Assurez-vous que le fichier test.docx est un document Word valide avec du texte, sinon vous pourriez recevoir une erreur:

le package spécifié n'est pas valide. la partie principale est manquante

Si vous exécutez le projet, vous verrez que le code HTML ressemble presque exactement au contenu du document Word:

enter image description here

Cependant, si vous essayez un document Word avec des images ou des liens, vous remarquerez qu'ils sont manquants ou cassés.

Cet article CodeProject résout ces problèmes: https: //www.codeproject.com/Articles/1162184/Csharp-Docx-to-HTML-to-Docx

J'ai dû changer la méthode static Uri FixUri(string brokenUri) pour renvoyer un Uri et j'ai ajouté des messages d'erreur conviviaux.

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

Vous aurez peut-être besoin du package System.Drawing.Common NuGet pour utiliser ImageFormat

Maintenant nous pouvons obtenir des images:

enter image description here

Si vous souhaitez uniquement afficher les fichiers Word .docx dans un navigateur Web, il est préférable de ne pas convertir le code HTML au format PDF, car cela augmenterait considérablement la bande passante. Vous pouvez stocker le code HTML dans un système de fichiers, dans un nuage ou en dB à l'aide d'une technologie VPP.


HTML en PDF

La prochaine chose que nous devons faire est de passer le code HTML à DinkToPdf. Téléchargez la solution DinkToPdf (90 MB). Construisez la solution - il faudra un certain temps pour que tous les packages soient restaurés et que la solution soit compilée.

IMPORTANT:

La bibliothèque DinkToPdf nécessite les fichiers libwkhtmltox.so et libwkhtmltox.dll à la racine de votre projet si vous souhaitez exécuter Linux et Windows. Il existe également un fichier libwkhtmltox.dylib pour Mac si vous en avez besoin.

Ces dll se trouvent dans le dossier v0.12.4. Selon votre PC, 32 ou 64 bits, copiez les 3 fichiers dans le dossier DinkToPdf-master\DinkToPfd.TestConsoleApp\bin\Debug\netcoreapp1.1.

IMPORTANT 2:

Assurez-vous que libgdiplus est installé dans votre image Docker ou sur votre machine Linux. La bibliothèque libwkhtmltox.so en dépend.

Définissez DinkToPfd.TestConsoleApp en tant que projet StartUp et modifiez le fichier Program.cs pour qu'il lise htmlContent à partir du fichier HTML enregistré avec Open-Xml-PowerTools au lieu du texte 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]" }
        }
    }
};

Le résultat entre Docx et PDF est assez impressionnant et je doute que beaucoup de gens soulignent de nombreuses différences (surtout s’ils ne voient jamais l’original):

enter image description here

Ps. Je réalise que vous vouliez convertir .doc et .docx en PDF. Je vous suggère de créer vous-même un service permettant de convertir .doc en docx à l'aide d'une technologie Windows/Microsoft non-serveur spécifique. Le format de la documentation est binaire et n'est pas destiné à automatisation côté bureau du bureau .

49
Jeremy Thompson

Utiliser le binaire LibreOffice

Le projet LibreOffice est une alternative multiplate-forme Open Source pour MS Office. Nous pouvons utiliser ses capacités pour exporter les fichiers doc et docx vers PDF. Actuellement, LibreOffice n'a pas d'API officielle pour .NET, nous allons donc parler directement au binaire soffice.

C'est une sorte de solution "simpliste", mais je pense que c'est la solution avec moins de bogues et un coût raisonnable. Un autre avantage de cette méthode est que vous n'êtes pas limité à la conversion de doc et docx: vous pouvez le convertir à partir de tous les formats pris en charge par LibreOffice (par exemple, odt, html, tableur, etc.).

La mise en oeuvre

J'ai écrit un simple programme c# qui utilise le binaire soffice. Ceci est juste une preuve de concept (et mon premier programme dans c#). Il prend en charge Windows prêt à l'emploi et Linux uniquement si le package LibreOffice a été installé.

C'est 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);
            }
        }
    }
}

Ressources

Résultats

Je l'avais testé sur Arch Linux, compilé avec mono. Je l'exécute en utilisant mon et le binaire Linux, et avec wine: en utilisant le binaire Windows.

Vous pouvez trouver les résultats dans le répertoire Tests :

Fichiers d'entrée: testdoc.doc , testdocx.docx

Les sorties:

9
Shmuel H.

J'ai récemment fait cela avec FreeSpire.Doc . Il a une limite de 3 pages pour la version gratuite, mais il peut facilement convertir un fichier docx en PDF en utilisant quelque chose comme ceci

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

Je couds ensuite ces fichiers PDF individuels ensemble à l’aide de 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();
        }
    }

Je ne sais pas si cela convient à votre cas d'utilisation car vous n'avez pas précisé la taille des documents que vous essayez d'écrire, mais s'ils sont> 3 pages ou si vous pouvez les manipuler pour faire moins de 3 pages, cela vous permettra de les convertir. en pdfs

Comme mentionné dans les commentaires ci-dessous, il est également impossible d'aider avec les langues RTL, merci à @Aria pour l'avoir signalé.

4
Bomie

Désolé, je n'ai pas assez de réputation pour commenter, mais je voudrais mettre mes deux sous sur la réponse de Jeremy Thompson. Et espérons que cela aide quelqu'un.

Quand je parcourais la réponse de Jeremy Thompson, après avoir téléchargé OpenXMLSDK-PowerTools et exécuté OpenXMLPowerTools.Core.Example, j'ai eu une erreur comme

the specified package is invalid. the main part is missing

à la ligne

var document = WordprocessingDocument.Open(source);

Après avoir lutté pendant quelques heures, j'ai constaté que le fichier test.docx copié dans un fichier bin n'était que de 1 ko. Pour résoudre ce problème, cliquez avec le bouton droit de la souris sur test.docx> Properties, définissez Copy to Output Directory sur Copy always pour résoudre ce problème.

J'espère que cela aidera des novices comme moi :)

1
Samuel Leung

Pour convertir DOCX en PDF même avec des espaces réservés, j'ai créé un " rapport gratuit (" Report-From-DocX-HTML-To-PDF-Converter ") bibliothèque avec .NET CORE sous la licence MIT , parce que j'étais tellement énervé qu'aucune solution simple n'existait et que toutes les solutions commerciales étaient super chères. Vous pouvez le trouver ici avec une description détaillée et un exemple de projet:

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

Il peut effectuer ces conversions (à côté d'un moteur de modèle/rapport remplaçant les espaces réservés):

  • DOCX à DOCX
  • DOCX en PDF
  • DOCX en HTML
  • HTML à HTML
  • HTML en PDF
  • HTML à DOCX

Vous pouvez ajouter des espaces réservés pour:

  • des textes
  • répéter des lignes de table
  • images
0
Smart In Media