Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

ITextSharp throws an error when you attempt to create a PdfTable with 0 columns.

I have a requirement to take XHTML that is generated using an XSLT transformation and generate a PDF from it. Currently I am using ITextSharp to do so. The problem that I am having is the XHTML that is generated sometimes contains tables with 0 rows, so when ITextSharp attempts to parse them into a table it throws and error saying there are 0 columns in the table.

The reason it says 0 columns is because ITextSharp sets the number of columns in the table to the maximum of the number of columns in each row, and since there are no rows the max number of columns in any given row is 0.

How do I go about catching these HTML table declarations with 0 rows and stop them from being parsed into PDF elements?

I've found the piece of code that is causing the error is within the HtmlPipeline, so I could copy and paste the implementation into a class extending HtmlPipeline and overriding its methods and then do my logic to check for empty tables there, but that seems sloppy and inefficient.

Is there a way to catch the empty table before it is parsed?

=Solution=

The Tag Processor

public class EmptyTableTagProcessor : Table
{
    public override IList<IElement> End(IWorkerContext ctx, Tag tag, IList<IElement> currentContent)
    {
        if (currentContent.Count > 0)
        {
            return base.End(ctx, tag, currentContent);
        }

        return new List<IElement>();
    }
}

And using the Tag Processor...

        //CSS
        var cssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(true);

        //HTML
        var fontProvider = new XMLWorkerFontProvider();
        var cssAppliers = new CssAppliersImpl(fontProvider);

        var tagProcessorFactory = Tags.GetHtmlTagProcessorFactory();
        tagProcessorFactory.AddProcessor(new EmptyTableTagProcessor(), new string[] { "table" });

        var htmlContext = new HtmlPipelineContext(cssAppliers);
        htmlContext.SetTagFactory(tagProcessorFactory);

        //PIPELINE
        var pipeline =
            new CssResolverPipeline(cssResolver,
            new HtmlPipeline(htmlContext,
            new PdfWriterPipeline(document, pdfWriter)));

        //XML WORKER
        var xmlWorker = new XMLWorker(pipeline, true);

        using (var stringReader = new StringReader(html))
        {
            xmlParser.Parse(stringReader);
        }

This solution removes the empty table tags and still writes the PDF as a part of the pipeline.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
576 views
Welcome To Ask or Share your Answers For Others

1 Answer

You should be able to write your own tag processor that accounts for that scenario by subclassing iTextSharp.tool.xml.html.AbstractTagProcessor. In fact, to make your life even easier you can subclass the already existing more specific iTextSharp.tool.xml.html.table.Table:

public class TableTagProcessor : iTextSharp.tool.xml.html.table.Table {

    public override IList<IElement> End(IWorkerContext ctx, Tag tag, IList<IElement> currentContent) {
        //See if we've got anything to work with
        if (currentContent.Count > 0) {
            //If so, let our parent class worry about it
            return base.End(ctx, tag, currentContent);
        }

        //Otherwise return an empty list which should make everyone happy
        return new List<IElement>();
    }
}

Unfortunately, if you want to use a custom tag processor you can't use the shortcut XMLWorkerHelper class and instead you'll need to parse the HTML into elements and add them to your document. To do that you'll need an instance of iTextSharp.tool.xml.IElementHandler which you can create like:

public class SampleHandler : iTextSharp.tool.xml.IElementHandler {
    //Generic list of elements
    public List<IElement> elements = new List<IElement>();
    //Add the supplied item to the list
    public void Add(IWritable w) {
        if (w is WritableElement) {
            elements.AddRange(((WritableElement)w).Elements());
        }
    }
}

You can use the above with the following code which includes some sample invalid HTML.

//Hold everything in memory
using (var ms = new MemoryStream()) {

    //Create new PDF document 
    using (var doc = new Document()) {
        using (var writer = PdfWriter.GetInstance(doc, ms)) {

            doc.Open();

            //Sample HTML
            string html = "<table><tr><td>Hello</td></tr></table><table></table>";

            //Create an instance of our element helper
            var XhtmlHelper = new SampleHandler();

            //Begin pipeline
            var htmlContext = new HtmlPipelineContext(null);

            //Get the default tag processor
            var tagFactory = iTextSharp.tool.xml.html.Tags.GetHtmlTagProcessorFactory();

            //Add an instance of our new processor
            tagFactory.AddProcessor(new TableTagProcessor(), new string[] { "table" });

            //Bind the above to the HTML context part of the pipeline
            htmlContext.SetTagFactory(tagFactory);

            //Get the default CSS handler and create some boilerplate pipeline stuff
            var cssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(false);
            var pipeline = new CssResolverPipeline(cssResolver, new HtmlPipeline(htmlContext, new ElementHandlerPipeline(XhtmlHelper, null)));//Here's where we add our IElementHandler

            //The worker dispatches commands to the pipeline stuff above
            var worker = new XMLWorker(pipeline, true);

            //Create a parser with the worker listed as the dispatcher
            var parser = new XMLParser();
            parser.AddListener(worker);

            //Finally, parse our HTML directly.
            using (TextReader sr = new StringReader(html)) {
                parser.Parse(sr);
            }

            //The above did not touch our document. Instead, all "proper" elements are stored in our helper class XhtmlHelper
            foreach (var element in XhtmlHelper.elements) {
                //Add these to the main document
                doc.Add(element);
            }

            doc.Close();

        }
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...