quarta-feira, 28 de agosto de 2013

HTML - PDF ITextSharp & XmlWorker - C#

Olá a todos,

Hoje eu vou tentar explicar como converter um arquivo HTML para um PDF.

Primeiramente,precisamos criar um projeto webform (O código que será explicado nesse post também pode ser reutilizado para um projeto MVC,tranquilamente).


Vamos lá então.

Abra seu Visual Studio (nesse exemplo estou utilizando a versão 2012).

Com o Visual Studio aberto:

File > New > Project

Selecione o seguinte tipo de Projeto: Asp.net Empty Web Application



Nomeie como  HtmlToPDF


Apos a criação do projeto,precisamos instalar dois projetos via nuget
Para isso no visual studio selecione:

Tools > Library Package Manager > Package Manager Console

E execute os seguintes comandos
Install-Package iTextSharp 
Install-Package itextsharp.xmlworker 

Após o sucesso da instalação.

Crie uma classe chamada PDFHelper
eis a classe PDFHelper por completa.
------------------------------------------------------------------------

using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Web;
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.tool.xml;
using iTextSharp.tool.xml.html;
using iTextSharp.tool.xml.parser;
using iTextSharp.tool.xml.pipeline.css;
using iTextSharp.tool.xml.pipeline.end;
using iTextSharp.tool.xml.pipeline.html;

namespace HtmlToPDF
{
    public class PDFHelper
    {
        ///
        /// Exportar um HTML fornecido.
        ///
        /// O HTML.
        /// Nome do Arquivo.
        /// Link para o CSS.
        public static void Export(string html, string fileName, string linkCss)
        {
            ////reset response
            HttpContext.Current.Response.Clear();
            HttpContext.Current.Response.ContentType = "application/pdf";

            ////define pdf filename
            HttpContext.Current.Response.AddHeader("content-disposition", "attachment; filename=" + fileName);


            //Gera o arquivo PDF
            using (var document = new Document(PageSize.A4, 40, 40, 40, 40))
            {


                html = FormatImageLinks(html);

                //define o  output do  HTML
                var memStream = new MemoryStream();
                TextReader xmlString = new StringReader(html);

                PdfWriter writer = PdfWriter.GetInstance(document, memStream);

                document.Open();

                //Registra todas as fontes no computador cliente.
                FontFactory.RegisterDirectories();

                // Set factories
                var htmlContext = new HtmlPipelineContext(null);
                htmlContext.SetTagFactory(Tags.GetHtmlTagProcessorFactory());

                // Set css
                ICSSResolver cssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(false);
                cssResolver.AddCssFile(HttpContext.Current.Server.MapPath(linkCss), true);

                // Exporta
                IPipeline pipeline = new CssResolverPipeline(cssResolver,
                                                             new HtmlPipeline(htmlContext,
                                                                              new PdfWriterPipeline(document, writer)));
                var worker = new XMLWorker(pipeline, true);
                var xmlParse = new XMLParser(true, worker);
                xmlParse.Parse(xmlString);
                xmlParse.Flush();

                document.Close();
                document.Dispose();

                HttpContext.Current.Response.BinaryWrite(memStream.ToArray());
            }

            HttpContext.Current.Response.End();
            HttpContext.Current.Response.Flush();
        }

        ///
        /// Convertemos o link relativo para um link absoluto
        ///
        /// input.
        ///
        public static string FormatImageLinks(string input)
        {
            if (input == null)
                return string.Empty;
            string tempInput = input;
            const string pattern = @"";
            HttpContext context = HttpContext.Current;

            //Modificamos a URL relativa para abosuluta,caso exista alguma imagem em nossa pagina HTML.
            foreach (Match m in Regex.Matches(input, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.RightToLeft))
            {
                if (!m.Success) continue;
                string tempM = m.Value;
                const string pattern1 = "src=[\'|\"](.+?)[\'|\"]";
                var reImg = new Regex(pattern1, RegexOptions.IgnoreCase | RegexOptions.Multiline);
                Match mImg = reImg.Match(m.Value);

                if (!mImg.Success) continue;
                string src = mImg.Value.ToLower().Replace("src=", "").Replace("\"", "").Replace("\'", "");

                if (src.StartsWith("http://") || src.StartsWith("https://")) continue;
                //Inserimos a nova URL na tag img
                src = "src=\"" + context.Request.Url.Scheme + "://" +
                      context.Request.Url.Authority + src + "\"";
                try
                {
                    tempM = tempM.Remove(mImg.Index, mImg.Length);
                    tempM = tempM.Insert(mImg.Index, src);

                    // inserimos a nova url img para todo o código html
                    tempInput = tempInput.Remove(m.Index, m.Length);
                    tempInput = tempInput.Insert(m.Index, tempM);
                }
                catch (Exception)
                {

                }
            }
            return tempInput;
        }
    }

}

------------------------------------------------------------------------

Agora para utilizar sua classe basta chama-la da seguinte forma:

HTML: Seu código HTML(.aspx) ou uma string que contenha o HTML.

PDFHelper.Export(HTML, "NomeQueVoceEscolheuParaOArquivo", "~/Style/style.css");


Pronto!

3 comentários:

  1. Olá Jefferson
    estou tentando utilizar o teu código e estou enfrentando um problema, o CSS não é carregado no PDF

    FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None);
    Document doc = new Document();
    PdfWriter writer = PdfWriter.GetInstance(doc, fs);
    doc.Open();

    var htmlContext = new HtmlPipelineContext(null);
    htmlContext.SetTagFactory(Tags.GetHtmlTagProcessorFactory());

    ICSSResolver cssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(false);
    cssResolver.AddCssFile(Server.MapPath("~/Themes/Css/teste.css"), true);

    IPipeline pipeline = new CssResolverPipeline(cssResolver, new HtmlPipeline(htmlContext, new PdfWriterPipeline(doc, writer)));

    using (var sw = new StringWriter())
    {
    ViewData["clientData"] = JsonConvert.DeserializeObject((clientData));
    ViewData["machineData"] = JsonConvert.DeserializeObject>>((machineData));

    ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(this.ControllerContext, "~/Views/Customer/SelfColor/Contract/Contract/Partials/ContractPDF.cshtml");
    var viewContext = new ViewContext(this.ControllerContext, viewResult.View, this.ViewData, this.TempData, sw);
    viewResult.View.Render(viewContext, sw);

    TextReader xmlString = new StringReader(sw.GetStringBuilder().ToString());

    var worker = new XMLWorker(pipeline, true);
    var xmlParse = new XMLParser(true, worker);
    xmlParse.Parse(xmlString);
    }

    pode me ajudar por favor?

    ResponderExcluir
  2. Mesmo problema aqui quando o CSS está dentro da tag HTML, ele não "entende" e renderiza como texto. Não sei porque o método XMLWorker não está funcionando se todos os parâmetros dizem para ele trabalhar com HTML. O antigo e obsoleto HTMLWorker fazia seu trabalho.

    ResponderExcluir
  3. Cara, você me salvou, muito obrigado pelo post, funcionou perfeitamente.

    ResponderExcluir