87 votes

Convertir les documents Word et docx au format PDF en .NET Core sans Microsoft.Office.Interop

J'ai besoin d'afficher Word .doc y .docx dans un navigateur. Il n'existe pas de véritable moyen côté client pour ce faire 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 PDF, donc je veux convertir ces documents en PDF sur le serveur et ensuite les afficher.

Je sais que cela peut être fait en utilisant Microsoft.Office.Interop.Word mais mon application est .NET Core et n'a pas accès à l'interopérabilité avec Office. Elle pourrait être exécutée sur Azure, mais aussi dans un conteneur Docker sur n'importe quel autre site.

Il semble qu'il y ait beaucoup de questions similaires à celle-ci, mais la plupart d'entre elles portent sur le cadre complet .NET ou supposent que le serveur est un système d'exploitation Windows et toute réponse ne m'est d'aucune utilité.

Comment puis-je convertir .doc y .docx pour .pdf sans l'accès à Microsoft.Office.Interop.Word ?

7 votes

C'est comme demander de convertir de Word en PDF sans l'aide de Microsoft. C'est théoriquement possible, mais Word est une application tellement énorme que dans le cas général, c'est pratiquement impossible, Word reste le meilleur pour cela. Vous pourriez connecter vos applications principales à une boîte Windows dédiée opaque exposant un service de conversion (ne négligez pas les questions de licence). Sinon, si vous limitez vos ambitions de conversion, il existe quelques bibliothèques qui devraient vous aider (aspose, itextsharp, etc.). N'oubliez pas non plus que doc et docx sont des formats fondamentalement très différents et que les solutions peuvent varier en conséquence.

7 votes

@SimonMourier docx est (soi-disant) un format ouvert (Microsoft a fait pression pendant des années sur ce point) mais il est assez horrible - sous le capot, c'est juste un tas de fichiers xml dans un zip. doc est binaire, mais aussi pratiquement inchangé depuis 20 ans et de nombreux analyseurs syntaxiques pour ce format sont déjà disponibles. Office a toujours été une application de bureau et une responsabilité coûteuse sur les serveurs, je ne peux pas être la première ou la seule personne à demander cela.

2 votes

@SimonMourier J'ai déjà utilisé Aspose - mon équipe n'a pas été très impressionnée, c'est excessivement cher pour ce qu'il fait et c'est un gros .NET, donc pas utile ici de toute façon. iText est bon pour la manipulation des PDF, mais il est aussi cher alors qu'il y a beaucoup d'API PDF qui sont open source.

122voto

Jeremy Thompson Points 14428

C'était une telle douleur, pas étonnant que toutes les solutions tierces facturent 500 dollars par développeur.

La bonne nouvelle est que le Le SDK Open XML a récemment ajouté la prise en charge de la norme .Net. donc on dirait que vous avez de la chance avec la .docx format.

Mauvaise nouvelle au moment présent il n'y a pas beaucoup de choix pour les bibliothèques de génération de PDF sur .NET Core. Puisque vous ne semblez pas vouloir payer pour une bibliothèque et que vous ne pouvez pas légalement utiliser un service tiers, nous n'avons pas d'autre choix que de créer la nôtre.

Le principal problème est de transformer le contenu du document Word en PDF. L'une des méthodes les plus courantes consiste à lire le document Word en HTML et à l'exporter en PDF. C'était difficile à trouver, mais il y a une version .Net Core de l'OpenXMLSDK. PowerTools qui prend en charge la transformation de Docx en HTML. La Pull Request est "sur le point d'être acceptée", vous pouvez l'obtenir ici :

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

Maintenant que nous pouvons extraire le contenu du document en HTML, nous devons le convertir en PDF. Il existe quelques bibliothèques pour convertir le HTML en PDF, par exemple DinkToPdf est une enveloppe multiplateforme autour de la bibliothèque Webkit HTML to PDF libwkhtmltox.

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


Docx vers HTML

Pour résumer, téléchargez le projet OpenXMLSDK-PowerTools .Net Core et construisez-le (uniquement OpenXMLPowerTools.Core et OpenXMLPowerTools.Core.Example - ignorez les autres projets). Définissez OpenXMLPowerTools.Core.Example comme projet StartUp. Exécutez le projet de la 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 contenant du texte, sinon vous risquez d'obtenir une erreur :

Le paquet spécifié n'est pas valide. La partie principale est manquante.

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

enter image description here

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

Cet article de CodeProject aborde ces questions : https://www.codeproject.com/Articles/1162184/Csharp-Docx-to-HTML-to-Docx

J'ai dû changer le static Uri FixUri(string brokenUri) pour retourner 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 pouvez avoir besoin du paquet NuGet System.Drawing.Common pour utiliser ImageFormat.

Maintenant, nous pouvons obtenir des images :

enter image description here

Si vous souhaitez uniquement afficher des fichiers Word .docx dans un navigateur Web, il est préférable de ne pas convertir le HTML en PDF, car cela augmente considérablement la bande passante. Vous pouvez stocker le HTML dans un système de fichiers, dans le nuage ou dans un dB en utilisant une technologie VPP.


HTML à PDF

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

IMPORTANT :

La bibliothèque DinkToPdf nécessite les fichiers libwkhtmltox.so et libwkhtmltox.dll dans la racine de votre projet si vous voulez fonctionner sous Linux et Windows. Il y a aussi un fichier libwkhtmltox.dylib pour Mac si vous en avez besoin.

Ces DLLs se trouvent dans le dossier v0.12.4. En fonction de votre PC, 32 ou 64 bit, copiez les 3 fichiers dans le DinkToPdf-master \DinkToPfd.TestConsoleApp\bin\Debug\netcoreapp1.1 dossier.

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 comme projet StartUp et modifiez le fichier Program.cs pour lire le htmlContent 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 du Docx par rapport au PDF est assez impressionnant et je doute que beaucoup de gens puissent relever de nombreuses différences (surtout s'ils ne voient jamais l'original) :

enter image description here

Ps. Je réalise que tu voulais convertir les deux .doc y .docx en PDF. Je vous suggère de créer vous-même un service pour convertir le format .doc en docx en utilisant une technologie spécifique non serveur de Windows/Microsoft. Le format doc est binaire et n'est pas destiné à être utilisé par les utilisateurs. automatisation de la bureautique côté serveur .

0 votes

Pourquoi ne pas le faire passer par une application qui convertit pour vous ?

3 votes

Merci, excellente réponse. Je pense que j'ai peut-être la dernière pièce du puzzle car j'ai trouvé un logiciel open source .NET Mono doc > docx convertisseur qui peut être Porté vers .NET Core .

0 votes

Avez-vous envisagé la conception du 2nd Point ? github.com/OfficeDev/office-content/blob/master/en-us/ Je suppose que MSFT ne veut pas que vous l'utilisiez pour générer du HTML ou du PDF.

21voto

Shmuel H. Points 1292

Utilisation du binaire LibreOffice

Le projet LibreOffice est une alternative Open Source multiplateforme pour MS Office. Nous pouvons utiliser ses capacités pour exporter doc y docx pour PDF . Actuellement, LibreOffice n'a pas d'API officielle pour .NET, par conséquent, nous nous adresserons directement à l'API de LibreOffice. soffice binaire.

C'est une solution un peu "bricolée", mais je pense que c'est la solution la moins coûteuse en termes de bugs et de maintenance. Un autre avantage de cette méthode est que vous n'êtes pas limité à la conversion de doc y docx Vous pouvez le convertir à partir de tous les formats pris en charge par LibreOffice (par exemple, odt, html, tableur, etc.).

La mise en œuvre

J'ai écrit un simple c# qui utilise le soffice binaire. Il s'agit juste d'une preuve de concept (et de mon premier programme en anglais). c# ). Il prend en charge Windows hors de la boîte et Linux uniquement si le paquetage 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 Tests répertoire :

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

Sorties :

0 votes

Notez que libreoffice ne sera pas en mesure de convertir correctement les documents de bureau qui utilisent des polices propriétaires (avait le problème avec verdana si je me souviens bien) à moins qu'ils ne soient installés sur le système d'exploitation. En dehors de ce problème de police, je n'ai pas eu beaucoup de problèmes avec lui.

1 votes

Faites juste très attention à la manipulation du nom de fichier s'il s'agit d'une valeur d'entrée utilisateur. Cela peut conduire à l'exécution de code sur votre serveur.

1 votes

Merci, vous avez sauvé ma journée ! C'est incroyable comme on ne trouve rien de gratuit et de bien fait nulle part, seulement des logiciels ou des services payants pour une tâche aussi commune

19voto

Bomie Points 41

Je l'ai fait récemment 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 ces PDF individuels ensemble plus tard en utilisant 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, car vous n'avez pas précisé la taille des documents que vous essayez d'écrire, mais s'ils font > 3 pages ou si vous pouvez les manipuler pour qu'ils fassent moins de 3 pages, il vous permettra de les convertir en PDF.

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

8 votes

Juste pour clarifier parce que vous ne l'avez pas mentionné. "Spire.Doc" laisse un filigrane rouge "évaluation d'avertissement" en haut du PDF converti. Lors de la recherche sur Nuget, recherchez "FreeSpire.Doc", cette version ne contient pas le filigrane. Belle API, elle devrait être marquée comme réponse, je pense.

0 votes

Oui, c'est ce que j'ai fait, désolé, j'aurais dû être plus précis. J'espère que cette réponse vous a aidé un peu !

0 votes

J'utilise FreeSpire.Doc et je reçois toujours l'avertissement de l'évaluation.

5voto

Samuel Leung Points 25

Désolé, je n'ai pas assez de réputation pour commenter, mais j'aimerais apporter mon grain de sel à la réponse de Jeremy Thompson. Et j'espère que cela aidera quelqu'un.

Quand je parcourais la réponse de Jeremy Thompson, après avoir téléchargé OpenXMLSDK-PowerTools et exécuter 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 trouvé que le test.docx copié dans le fichier bin n'est que de 1kb. Pour résoudre ce problème, faites un clic droit test.docx > Properties ensemble Copy to Output Directory a Copy always résout ce problème.

J'espère que cela aidera un novice comme moi :)

4voto

Smart In Media Points 1

Pour convertir des documents DOCX en PDF, même avec des caractères de remplissage, j'ai créé un logiciel gratuit. "Report-From-DocX-HTML-To-PDF-Converter" (rapport de convertisseur de documents) avec .NET CORE sous l'onglet Licence MIT parce que j'étais tellement déconcerté par le fait qu'il n'existait pas de solution simple et que toutes les solutions commerciales étaient super chères. Vous pouvez le trouver ici avec une description détaillée et un projet d'exemple :

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

Vous n'avez besoin que de la version gratuite de LibreOffice. Je recommande d'utiliser l'édition portable de LibreOffice, afin qu'elle ne change rien aux paramètres de votre serveur. Regardez où se trouve le fichier "soffice.exe" (sous Linux il est appelé différemment), car vous en avez besoin pour remplir la variable "locationOfLibreOfficeSoffice".

Voici comment fonctionne la conversion de DOCX en 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"));

Comme vous le voyez, vous pouvez également convertir un document DOCX en HTML. Vous pouvez également placer des espaces dans le document Word, que vous pouvez ensuite "remplir" avec des valeurs. Cependant, cela n'entre pas dans le cadre de votre question, mais vous pouvez en savoir plus à ce sujet sur Github (README).

1 votes

J'ai quelques questions à vous poser : 1. Y a-t-il des problèmes connus lorsqu'il est utilisé en production pour une charge moyenne de 10 conversions docx vers pdf par minute ? 2. Le libreoffice portable fait environ 1 Go. Pouvez-vous indiquer quels dossiers / fichiers peuvent être supprimés pour l'alléger sans affecter les fonctionnalités ?

0 votes

Il faut également plus de 10 secondes pour effectuer la conversion. Est-ce normal ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X