Example #1
0
 private ApplicationFrame createView(IInitializationPreferences initializationPreferences) {
   try {
     displayStatus("Starting Platform...");
     return new GuiInitializer(initializationPreferences).initialize().getWindow();
   } catch (InitializationException e) {
     LoggerFactory.getLogger(Anathema.class).error("Could not start platform.", e);
     return new ErrorWindow(e);
   }
 }
Example #2
0
/** Verifies the signatures in an LTV document. */
public class LtvVerifier extends RootStoreVerifier {
  /** The Logger instance */
  protected static final Logger LOGGER = LoggerFactory.getLogger(LtvVerifier.class);

  /** Do we need to check all certificate, or only the signing certificate? */
  protected CertificateOption option = CertificateOption.SIGNING_CERTIFICATE;
  /** Verify root. */
  protected boolean verifyRootCertificate = true;

  /** A reader object for the revision that is being verified. */
  protected PdfReader reader;
  /** The fields in the revision that is being verified. */
  protected AcroFields fields;
  /** The date the revision was signed, or <code>null</code> for the highest revision. */
  protected Date signDate;
  /** The signature that covers the revision. */
  protected String signatureName;
  /** The PdfPKCS7 object for the signature. */
  protected PdfPKCS7 pkcs7;
  /** Indicates if we're working with the latest revision. */
  protected boolean latestRevision = true;
  /** The document security store for the revision that is being verified */
  protected PdfDictionary dss;

  /**
   * Creates a VerificationData object for a PdfReader
   *
   * @param reader a reader for the document we want to verify.
   * @throws GeneralSecurityException
   */
  public LtvVerifier(PdfReader reader) throws GeneralSecurityException {
    super(null);
    this.reader = reader;
    this.fields = reader.getAcroFields();
    List<String> names = fields.getSignatureNames();
    signatureName = names.get(names.size() - 1);
    this.signDate = new Date();
    pkcs7 = coversWholeDocument();
    LOGGER.info(
        String.format(
            "Checking %ssignature %s",
            pkcs7.isTsp() ? "document-level timestamp " : "", signatureName));
  }

  /**
   * Sets an extra verifier.
   *
   * @param verifier the verifier to set
   */
  public void setVerifier(CertificateVerifier verifier) {
    this.verifier = verifier;
  }

  /**
   * Sets the certificate option.
   *
   * @param option Either CertificateOption.SIGNING_CERTIFICATE (default) or
   *     CertificateOption.WHOLE_CHAIN
   */
  public void setCertificateOption(CertificateOption option) {
    this.option = option;
  }

  /** Set the verifyRootCertificate to false if you can't verify the root certificate. */
  public void setVerifyRootCertificate(boolean verifyRootCertificate) {
    this.verifyRootCertificate = verifyRootCertificate;
  }

  /**
   * Checks if the signature covers the whole document and throws an exception if the document was
   * altered
   *
   * @return a PdfPKCS7 object
   * @throws GeneralSecurityException
   */
  protected PdfPKCS7 coversWholeDocument() throws GeneralSecurityException {
    PdfPKCS7 pkcs7 = fields.verifySignature(signatureName);
    if (fields.signatureCoversWholeDocument(signatureName)) {
      LOGGER.info("The timestamp covers whole document.");
    } else {
      throw new VerificationException(null, "Signature doesn't cover whole document.");
    }
    if (pkcs7.verify()) {
      LOGGER.info("The signed document has not been modified.");
      return pkcs7;
    } else {
      throw new VerificationException(
          null, "The document was altered after the final signature was applied.");
    }
  }

  /**
   * Verifies all the document-level timestamps and all the signatures in the document.
   *
   * @throws IOException
   * @throws GeneralSecurityException
   */
  public List<VerificationOK> verify(List<VerificationOK> result)
      throws IOException, GeneralSecurityException {
    if (result == null) result = new ArrayList<VerificationOK>();
    while (pkcs7 != null) {
      result.addAll(verifySignature());
    }
    return result;
  }

  /**
   * Verifies a document level timestamp.
   *
   * @throws GeneralSecurityException
   * @throws IOException
   */
  public List<VerificationOK> verifySignature() throws GeneralSecurityException, IOException {
    LOGGER.info("Verifying signature.");
    List<VerificationOK> result = new ArrayList<VerificationOK>();
    // Get the certificate chain
    Certificate[] chain = pkcs7.getSignCertificateChain();
    verifyChain(chain);
    // how many certificates in the chain do we need to check?
    int total = 1;
    if (CertificateOption.WHOLE_CHAIN.equals(option)) {
      total = chain.length;
    }
    // loop over the certificates
    X509Certificate signCert;
    X509Certificate issuerCert;
    for (int i = 0; i < total; ) {
      // the certificate to check
      signCert = (X509Certificate) chain[i++];
      // its issuer
      issuerCert = null;
      if (i < chain.length) issuerCert = (X509Certificate) chain[i];
      // now lets verify the certificate
      LOGGER.info(signCert.getSubjectDN().getName());
      List<VerificationOK> list = verify(signCert, issuerCert, signDate);
      if (list.size() == 0) {
        try {
          signCert.verify(signCert.getPublicKey());
          if (latestRevision && chain.length > 1) {
            list.add(
                new VerificationOK(
                    signCert, this.getClass(), "Root certificate in final revision"));
          }
          if (list.size() == 0 && verifyRootCertificate) {
            throw new GeneralSecurityException();
          } else if (chain.length > 1)
            list.add(
                new VerificationOK(
                    signCert, this.getClass(), "Root certificate passed without checking"));
        } catch (GeneralSecurityException e) {
          throw new VerificationException(
              signCert, "Couldn't verify with CRL or OCSP or trusted anchor");
        }
      }
      result.addAll(list);
    }
    // go to the previous revision
    switchToPreviousRevision();
    return result;
  }

  /**
   * Checks the certificates in a certificate chain: are they valid on a specific date, and do they
   * chain up correctly?
   *
   * @param chain
   * @throws GeneralSecurityException
   */
  public void verifyChain(Certificate[] chain) throws GeneralSecurityException {
    // Loop over the certificates in the chain
    for (int i = 0; i < chain.length; i++) {
      X509Certificate cert = (X509Certificate) chain[i];
      // check if the certificate was/is valid
      cert.checkValidity(signDate);
      // check if the previous certificate was issued by this certificate
      if (i > 0) chain[i - 1].verify(chain[i].getPublicKey());
    }
    LOGGER.info("All certificates are valid on " + signDate.toString());
  }

  /**
   * Verifies certificates against a list of CRLs and OCSP responses.
   *
   * @param signingCert
   * @param issuerCert
   * @return a list of <code>VerificationOK</code> objects. The list will be empty if the
   *     certificate couldn't be verified.
   * @throws GeneralSecurityException
   * @throws IOException
   * @see
   *     com.itextpdf.text.pdf.security.RootStoreVerifier#verify(java.security.cert.X509Certificate,
   *     java.security.cert.X509Certificate)
   */
  public List<VerificationOK> verify(
      X509Certificate signCert, X509Certificate issuerCert, Date signDate)
      throws GeneralSecurityException, IOException {
    // we'll verify agains the rootstore (if present)
    RootStoreVerifier rootStoreVerifier = new RootStoreVerifier(verifier);
    rootStoreVerifier.setRootStore(rootStore);
    // We'll verify against a list of CRLs
    CRLVerifier crlVerifier = new CRLVerifier(rootStoreVerifier, getCRLsFromDSS());
    crlVerifier.setRootStore(rootStore);
    crlVerifier.setOnlineCheckingAllowed(latestRevision || onlineCheckingAllowed);
    // We'll verify against a list of OCSPs
    OCSPVerifier ocspVerifier = new OCSPVerifier(crlVerifier, getOCSPResponsesFromDSS());
    ocspVerifier.setRootStore(rootStore);
    ocspVerifier.setOnlineCheckingAllowed(latestRevision || onlineCheckingAllowed);
    // We verify the chain
    return ocspVerifier.verify(signCert, issuerCert, signDate);
  }

  /**
   * Switches to the previous revision.
   *
   * @throws IOException
   * @throws GeneralSecurityException
   */
  public void switchToPreviousRevision() throws IOException, GeneralSecurityException {
    LOGGER.info("Switching to previous revision.");
    latestRevision = false;
    dss = reader.getCatalog().getAsDict(PdfName.DSS);
    Calendar cal = pkcs7.getTimeStampDate();
    if (cal == null) cal = pkcs7.getSignDate();
    // TODO: get date from signature
    signDate = cal.getTime();
    List<String> names = fields.getSignatureNames();
    if (names.size() > 1) {
      signatureName = names.get(names.size() - 2);
      reader = new PdfReader(fields.extractRevision(signatureName));
      this.fields = reader.getAcroFields();
      names = fields.getSignatureNames();
      signatureName = names.get(names.size() - 1);
      pkcs7 = coversWholeDocument();
      LOGGER.info(
          String.format(
              "Checking %ssignature %s",
              pkcs7.isTsp() ? "document-level timestamp " : "", signatureName));
    } else {
      LOGGER.info("No signatures in revision");
      pkcs7 = null;
    }
  }

  /**
   * Gets a list of X509CRL objects from a Document Security Store.
   *
   * @return a list of CRLs
   * @throws GeneralSecurityException
   * @throws IOException
   */
  public List<X509CRL> getCRLsFromDSS() throws GeneralSecurityException, IOException {
    List<X509CRL> crls = new ArrayList<X509CRL>();
    if (dss == null) return crls;
    PdfArray crlarray = dss.getAsArray(PdfName.CRLS);
    if (crlarray == null) return crls;
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    for (int i = 0; i < crlarray.size(); i++) {
      PRStream stream = (PRStream) crlarray.getAsStream(i);
      X509CRL crl =
          (X509CRL) cf.generateCRL(new ByteArrayInputStream(PdfReader.getStreamBytes(stream)));
      crls.add(crl);
    }
    return crls;
  }

  /**
   * Gets OCSP responses from the Document Security Store.
   *
   * @return a list of BasicOCSPResp objects
   * @throws IOException
   * @throws GeneralSecurityException
   */
  public List<BasicOCSPResp> getOCSPResponsesFromDSS()
      throws IOException, GeneralSecurityException {
    List<BasicOCSPResp> ocsps = new ArrayList<BasicOCSPResp>();
    if (dss == null) return ocsps;
    PdfArray ocsparray = dss.getAsArray(PdfName.OCSPS);
    if (ocsparray == null) return ocsps;
    for (int i = 0; i < ocsparray.size(); i++) {
      PRStream stream = (PRStream) ocsparray.getAsStream(i);
      OCSPResp ocspResponse = new OCSPResp(PdfReader.getStreamBytes(stream));
      if (ocspResponse.getStatus() == 0)
        try {
          ocsps.add((BasicOCSPResp) ocspResponse.getResponseObject());
        } catch (OCSPException e) {
          throw new GeneralSecurityException(e);
        }
    }
    return ocsps;
  }
}
/**
 * Old iText class that allows you to convert HTML to PDF. We've completely rewritten HTML to PDF
 * conversion and we made it a separate project named XML Worker.
 *
 * @deprecated please switch to XML Worker instead (this is a separate project)
 */
public class HTMLWorker implements SimpleXMLDocHandler, DocListener {

  private static Logger LOGGER = LoggerFactory.getLogger(HTMLWorker.class);
  /**
   * DocListener that will listen to the Elements produced by parsing the HTML. This can be a
   * com.lowagie.text.Document adding the elements to a Document directly, or an HTMLWorker instance
   * strong the objects in a List
   */
  protected DocListener document;

  /**
   * The map with all the supported tags.
   *
   * @since 5.0.6
   */
  protected Map<String, HTMLTagProcessor> tags;

  /** The object defining all the styles. */
  private StyleSheet style = new StyleSheet();

  /**
   * Creates a new instance of HTMLWorker
   *
   * @param document A class that implements <CODE>DocListener</CODE>
   */
  public HTMLWorker(final DocListener document) {
    this(document, null, null);
  }

  /**
   * Creates a new instance of HTMLWorker
   *
   * @param document A class that implements <CODE>DocListener</CODE>
   * @param tags A map containing the supported tags
   * @param style A StyleSheet
   * @since 5.0.6
   */
  public HTMLWorker(
      final DocListener document,
      final Map<String, HTMLTagProcessor> tags,
      final StyleSheet style) {
    this.document = document;
    setSupportedTags(tags);
    setStyleSheet(style);
  }

  /**
   * Sets the map with supported tags.
   *
   * @param tags
   * @since 5.0.6
   */
  public void setSupportedTags(Map<String, HTMLTagProcessor> tags) {
    if (tags == null) tags = new HTMLTagProcessors();
    this.tags = tags;
  }

  /**
   * Setter for the StyleSheet
   *
   * @param style the StyleSheet
   */
  public void setStyleSheet(StyleSheet style) {
    if (style == null) style = new StyleSheet();
    this.style = style;
  }

  /**
   * Parses content read from a java.io.Reader object.
   *
   * @param reader the content
   * @throws IOException
   */
  public void parse(final Reader reader) throws IOException {
    LOGGER.info(
        "Please note, there is a more extended version of the HTMLWorker available in the iText XMLWorker");
    SimpleXMLParser.parse(this, null, reader, true);
  }

  // state machine

  /**
   * Stack with the Elements that already have been processed.
   *
   * @since iText 5.0.6 (private => protected)
   */
  protected Stack<Element> stack = new Stack<Element>();

  /**
   * Keeps the content of the current paragraph
   *
   * @since iText 5.0.6 (private => protected)
   */
  protected Paragraph currentParagraph;

  /**
   * The current hierarchy chain of tags.
   *
   * @since 5.0.6
   */
  private final ChainedProperties chain = new ChainedProperties();

  /** @see com.itextpdf.text.xml.simpleparser.SimpleXMLDocHandler#startDocument() */
  public void startDocument() {
    HashMap<String, String> attrs = new HashMap<String, String>();
    style.applyStyle(HtmlTags.BODY, attrs);
    chain.addToChain(HtmlTags.BODY, attrs);
  }

  /**
   * @see com.itextpdf.text.xml.simpleparser.SimpleXMLDocHandler#startElement(java.lang.String,
   *     java.util.Map)
   */
  public void startElement(final String tag, final Map<String, String> attrs) {
    HTMLTagProcessor htmlTag = tags.get(tag);
    if (htmlTag == null) {
      return;
    }
    // apply the styles to attrs
    style.applyStyle(tag, attrs);
    // deal with the style attribute
    StyleSheet.resolveStyleAttribute(attrs, chain);
    // process the tag
    try {
      htmlTag.startElement(this, tag, attrs);
    } catch (DocumentException e) {
      throw new ExceptionConverter(e);
    } catch (IOException e) {
      throw new ExceptionConverter(e);
    }
  }

  /** @see com.itextpdf.text.xml.simpleparser.SimpleXMLDocHandler#text(java.lang.String) */
  public void text(String content) {
    if (skipText) return;
    if (currentParagraph == null) {
      currentParagraph = createParagraph();
    }
    if (!insidePRE) {
      // newlines and carriage returns are ignored
      if (content.trim().length() == 0 && content.indexOf(' ') < 0) {
        return;
      }
      content = HtmlUtilities.eliminateWhiteSpace(content);
    }
    Chunk chunk = createChunk(content);
    currentParagraph.add(chunk);
  }

  /** @see com.itextpdf.text.xml.simpleparser.SimpleXMLDocHandler#endElement(java.lang.String) */
  public void endElement(final String tag) {
    HTMLTagProcessor htmlTag = tags.get(tag);
    if (htmlTag == null) {
      return;
    }
    // process the tag
    try {
      htmlTag.endElement(this, tag);
    } catch (DocumentException e) {
      throw new ExceptionConverter(e);
    }
  }

  /** @see com.itextpdf.text.xml.simpleparser.SimpleXMLDocHandler#endDocument() */
  public void endDocument() {
    try {
      // flush the stack
      for (int k = 0; k < stack.size(); ++k) document.add(stack.elementAt(k));
      // add current paragraph
      if (currentParagraph != null) document.add(currentParagraph);
      currentParagraph = null;
    } catch (Exception e) {
      throw new ExceptionConverter(e);
    }
  }

  // stack and current paragraph operations

  /**
   * Adds a new line to the currentParagraph.
   *
   * @since 5.0.6
   */
  public void newLine() {
    if (currentParagraph == null) {
      currentParagraph = new Paragraph();
    }
    currentParagraph.add(createChunk("\n"));
  }

  /**
   * Flushes the current paragraph, indicating that we're starting a new block. If the stack is
   * empty, the paragraph is added to the document. Otherwise the Paragraph is added to the stack.
   *
   * @since 5.0.6
   */
  public void carriageReturn() throws DocumentException {
    if (currentParagraph == null) return;
    if (stack.empty()) document.add(currentParagraph);
    else {
      Element obj = stack.pop();
      if (obj instanceof TextElementArray) {
        TextElementArray current = (TextElementArray) obj;
        current.add(currentParagraph);
      }
      stack.push(obj);
    }
    currentParagraph = null;
  }

  /**
   * Stacks the current paragraph, indicating that we're starting a new span.
   *
   * @since 5.0.6
   */
  public void flushContent() {
    pushToStack(currentParagraph);
    currentParagraph = new Paragraph();
  }

  /**
   * Pushes an element to the Stack.
   *
   * @param element
   * @since 5.0.6
   */
  public void pushToStack(final Element element) {
    if (element != null) stack.push(element);
  }

  /**
   * Updates the chain with a new tag and new attributes.
   *
   * @param tag the new tag
   * @param attrs the corresponding attributes
   * @since 5.0.6
   */
  public void updateChain(final String tag, final Map<String, String> attrs) {
    chain.addToChain(tag, attrs);
  }

  /**
   * Updates the chain by removing a tag.
   *
   * @param tag the new tag
   * @since 5.0.6
   */
  public void updateChain(final String tag) {
    chain.removeChain(tag);
  }

  // providers that help find resources such as images and fonts

  /**
   * Key used to store the image provider in the providers map.
   *
   * @since 5.0.6
   */
  public static final String IMG_PROVIDER = "img_provider";

  /**
   * Key used to store the image processor in the providers map.
   *
   * @since 5.0.6
   */
  public static final String IMG_PROCESSOR = "img_interface";

  /**
   * Key used to store the image store in the providers map.
   *
   * @since 5.0.6
   */
  public static final String IMG_STORE = "img_static";

  /**
   * Key used to store the image baseurl provider in the providers map.
   *
   * @since 5.0.6
   */
  public static final String IMG_BASEURL = "img_baseurl";

  /**
   * Key used to store the font provider in the providers map.
   *
   * @since 5.0.6
   */
  public static final String FONT_PROVIDER = "font_factory";

  /**
   * Key used to store the link provider in the providers map.
   *
   * @since 5.0.6
   */
  public static final String LINK_PROVIDER = "alink_interface";

  /**
   * Map containing providers such as a FontProvider or ImageProvider.
   *
   * @since 5.0.6 (renamed from interfaceProps)
   */
  private Map<String, Object> providers = new HashMap<String, Object>();

  /**
   * Setter for the providers. If a FontProvider is added, the ElementFactory is updated.
   *
   * @param providers a Map with different providers
   * @since 5.0.6
   */
  public void setProviders(final Map<String, Object> providers) {
    if (providers == null) return;
    this.providers = providers;
    FontProvider ff = null;
    if (providers != null) ff = (FontProvider) providers.get(FONT_PROVIDER);
    if (ff != null) factory.setFontProvider(ff);
  }

  // factory that helps create objects

  /**
   * Factory that is able to create iText Element objects.
   *
   * @since 5.0.6
   */
  private final ElementFactory factory = new ElementFactory();

  /**
   * Creates a Chunk using the factory.
   *
   * @param content the content of the chunk
   * @return a Chunk with content
   * @since 5.0.6
   */
  public Chunk createChunk(final String content) {
    return factory.createChunk(content, chain);
  }
  /**
   * Creates a Paragraph using the factory.
   *
   * @return a Paragraph without any content
   * @since 5.0.6
   */
  public Paragraph createParagraph() {
    return factory.createParagraph(chain);
  }
  /**
   * Creates a List object.
   *
   * @param tag should be "ol" or "ul"
   * @return a List object
   * @since 5.0.6
   */
  public com.itextpdf.text.List createList(final String tag) {
    return factory.createList(tag, chain);
  }
  /**
   * Creates a ListItem object.
   *
   * @return a ListItem object
   * @since 5.0.6
   */
  public ListItem createListItem() {
    return factory.createListItem(chain);
  }
  /**
   * Creates a LineSeparator object.
   *
   * @param attrs properties of the LineSeparator
   * @return a LineSeparator object
   * @since 5.0.6
   */
  public LineSeparator createLineSeparator(final Map<String, String> attrs) {
    return factory.createLineSeparator(attrs, currentParagraph.getLeading() / 2);
  }

  /**
   * Creates an Image object.
   *
   * @param attrs properties of the Image
   * @return an Image object (or null if the Image couldn't be found)
   * @throws DocumentException
   * @throws IOException
   * @since 5.0.6
   */
  public Image createImage(final Map<String, String> attrs) throws DocumentException, IOException {
    String src = attrs.get(HtmlTags.SRC);
    if (src == null) return null;
    Image img =
        factory.createImage(
            src,
            attrs,
            chain,
            document,
            (ImageProvider) providers.get(IMG_PROVIDER),
            (ImageStore) providers.get(IMG_STORE),
            (String) providers.get(IMG_BASEURL));
    return img;
  }

  /**
   * Creates a Cell.
   *
   * @param tag the tag
   * @return a CellWrapper object
   * @since 5.0.6
   */
  public CellWrapper createCell(final String tag) {
    return new CellWrapper(tag, chain);
  }

  // processing objects

  /**
   * Adds a link to the current paragraph.
   *
   * @since 5.0.6
   */
  public void processLink() {
    if (currentParagraph == null) {
      currentParagraph = new Paragraph();
    }
    // The link provider allows you to do additional processing
    LinkProcessor i = (LinkProcessor) providers.get(HTMLWorker.LINK_PROVIDER);
    if (i == null || !i.process(currentParagraph, chain)) {
      // sets an Anchor for all the Chunks in the current paragraph
      String href = chain.getProperty(HtmlTags.HREF);
      if (href != null) {
        for (Chunk ck : currentParagraph.getChunks()) {
          ck.setAnchor(href);
        }
      }
    }
    // a link should be added to the current paragraph as a phrase
    if (stack.isEmpty()) {
      // no paragraph to add too, 'a' tag is first element
      Paragraph tmp = new Paragraph(new Phrase(currentParagraph));
      currentParagraph = tmp;
    } else {
      Paragraph tmp = (Paragraph) stack.pop();
      tmp.add(new Phrase(currentParagraph));
      currentParagraph = tmp;
    }
  }

  /**
   * Fetches the List from the Stack and adds it to the TextElementArray on top of the Stack, or to
   * the Document if the Stack is empty.
   *
   * @throws DocumentException
   * @since 5.0.6
   */
  public void processList() throws DocumentException {
    if (stack.empty()) return;
    Element obj = stack.pop();
    if (!(obj instanceof com.itextpdf.text.List)) {
      stack.push(obj);
      return;
    }
    if (stack.empty()) document.add(obj);
    else ((TextElementArray) stack.peek()).add(obj);
  }

  /**
   * Looks for the List object on the Stack, and adds the ListItem to the List.
   *
   * @throws DocumentException
   * @since 5.0.6
   */
  public void processListItem() throws DocumentException {
    if (stack.empty()) return;
    Element obj = stack.pop();
    if (!(obj instanceof ListItem)) {
      stack.push(obj);
      return;
    }
    if (stack.empty()) {
      document.add(obj);
      return;
    }
    ListItem item = (ListItem) obj;
    Element list = stack.pop();
    if (!(list instanceof com.itextpdf.text.List)) {
      stack.push(list);
      return;
    }
    ((com.itextpdf.text.List) list).add(item);
    item.adjustListSymbolFont();
    stack.push(list);
  }

  /**
   * Processes an Image.
   *
   * @param img
   * @param attrs
   * @throws DocumentException
   * @since 5.0.6
   */
  public void processImage(final Image img, final Map<String, String> attrs)
      throws DocumentException {
    ImageProcessor processor = (ImageProcessor) providers.get(HTMLWorker.IMG_PROCESSOR);
    if (processor == null || !processor.process(img, attrs, chain, document)) {
      String align = attrs.get(HtmlTags.ALIGN);
      if (align != null) {
        carriageReturn();
      }
      if (currentParagraph == null) {
        currentParagraph = createParagraph();
      }
      currentParagraph.add(new Chunk(img, 0, 0, true));
      currentParagraph.setAlignment(HtmlUtilities.alignmentValue(align));
      if (align != null) {
        carriageReturn();
      }
    }
  }

  /**
   * Processes the Table.
   *
   * @throws DocumentException
   * @since 5.0.6
   */
  public void processTable() throws DocumentException {
    TableWrapper table = (TableWrapper) stack.pop();
    PdfPTable tb = table.createTable();
    tb.setSplitRows(true);
    if (stack.empty()) document.add(tb);
    else ((TextElementArray) stack.peek()).add(tb);
  }

  /**
   * Gets the TableWrapper from the Stack and adds a new row.
   *
   * @since 5.0.6
   */
  public void processRow() {
    ArrayList<PdfPCell> row = new ArrayList<PdfPCell>();
    ArrayList<Float> cellWidths = new ArrayList<Float>();
    boolean percentage = false;
    float width;
    float totalWidth = 0;
    int zeroWidth = 0;
    TableWrapper table = null;
    while (true) {
      Element obj = stack.pop();
      if (obj instanceof CellWrapper) {
        CellWrapper cell = (CellWrapper) obj;
        width = cell.getWidth();
        cellWidths.add(new Float(width));
        percentage |= cell.isPercentage();
        if (width == 0) {
          zeroWidth++;
        } else {
          totalWidth += width;
        }
        row.add(cell.getCell());
      }
      if (obj instanceof TableWrapper) {
        table = (TableWrapper) obj;
        break;
      }
    }
    table.addRow(row);
    if (cellWidths.size() > 0) {
      // cells come off the stack in reverse, naturally
      totalWidth = 100 - totalWidth;
      Collections.reverse(cellWidths);
      float[] widths = new float[cellWidths.size()];
      boolean hasZero = false;
      for (int i = 0; i < widths.length; i++) {
        widths[i] = cellWidths.get(i).floatValue();
        if (widths[i] == 0 && percentage && zeroWidth > 0) {
          widths[i] = totalWidth / zeroWidth;
        }
        if (widths[i] == 0) {
          hasZero = true;
          break;
        }
      }
      if (!hasZero) table.setColWidths(widths);
    }
    stack.push(table);
  }

  // state variables and methods

  /** Stack to keep track of table tags. */
  private final Stack<boolean[]> tableState = new Stack<boolean[]>();

  /** Boolean to keep track of TR tags. */
  private boolean pendingTR = false;

  /** Boolean to keep track of TD and TH tags */
  private boolean pendingTD = false;

  /** Boolean to keep track of LI tags */
  private boolean pendingLI = false;

  /**
   * Boolean to keep track of PRE tags
   *
   * @since 5.0.6 renamed from isPRE
   */
  private boolean insidePRE = false;

  /**
   * Indicates if text needs to be skipped.
   *
   * @since iText 5.0.6 (private => protected)
   */
  protected boolean skipText = false;

  /**
   * Pushes the values of pendingTR and pendingTD to a state stack.
   *
   * @since 5.0.6
   */
  public void pushTableState() {
    tableState.push(new boolean[] {pendingTR, pendingTD});
  }

  /**
   * Pops the values of pendingTR and pendingTD from a state stack.
   *
   * @since 5.0.6
   */
  public void popTableState() {
    boolean[] state = tableState.pop();
    pendingTR = state[0];
    pendingTD = state[1];
  }

  /**
   * @return the pendingTR
   * @since 5.0.6
   */
  public boolean isPendingTR() {
    return pendingTR;
  }

  /**
   * @param pendingTR the pendingTR to set
   * @since 5.0.6
   */
  public void setPendingTR(final boolean pendingTR) {
    this.pendingTR = pendingTR;
  }

  /**
   * @return the pendingTD
   * @since 5.0.6
   */
  public boolean isPendingTD() {
    return pendingTD;
  }

  /**
   * @param pendingTD the pendingTD to set
   * @since 5.0.6
   */
  public void setPendingTD(final boolean pendingTD) {
    this.pendingTD = pendingTD;
  }

  /**
   * @return the pendingLI
   * @since 5.0.6
   */
  public boolean isPendingLI() {
    return pendingLI;
  }

  /**
   * @param pendingLI the pendingLI to set
   * @since 5.0.6
   */
  public void setPendingLI(final boolean pendingLI) {
    this.pendingLI = pendingLI;
  }

  /**
   * @return the insidePRE
   * @since 5.0.6
   */
  public boolean isInsidePRE() {
    return insidePRE;
  }

  /**
   * @param insidePRE the insidePRE to set
   * @since 5.0.6
   */
  public void setInsidePRE(final boolean insidePRE) {
    this.insidePRE = insidePRE;
  }

  /**
   * @return the skipText
   * @since 5.0.6
   */
  public boolean isSkipText() {
    return skipText;
  }

  /**
   * @param skipText the skipText to set
   * @since 5.0.6
   */
  public void setSkipText(final boolean skipText) {
    this.skipText = skipText;
  }

  // static methods to parse HTML to a List of Element objects.

  /** The resulting list of elements. */
  protected List<Element> objectList;

  /**
   * Parses an HTML source to a List of Element objects
   *
   * @param reader the HTML source
   * @param style a StyleSheet object
   * @return a List of Element objects
   * @throws IOException
   */
  public static List<Element> parseToList(final Reader reader, final StyleSheet style)
      throws IOException {
    return parseToList(reader, style, null);
  }

  /**
   * Parses an HTML source to a List of Element objects
   *
   * @param reader the HTML source
   * @param style a StyleSheet object
   * @param providers map containing classes with extra info
   * @return a List of Element objects
   * @throws IOException
   */
  public static List<Element> parseToList(
      final Reader reader, final StyleSheet style, final HashMap<String, Object> providers)
      throws IOException {
    return parseToList(reader, style, null, providers);
  }

  /**
   * Parses an HTML source to a List of Element objects
   *
   * @param reader the HTML source
   * @param style a StyleSheet object
   * @param tags a map containing supported tags and their processors
   * @param providers map containing classes with extra info
   * @return a List of Element objects
   * @throws IOException
   * @since 5.0.6
   */
  public static List<Element> parseToList(
      final Reader reader,
      final StyleSheet style,
      final Map<String, HTMLTagProcessor> tags,
      final HashMap<String, Object> providers)
      throws IOException {
    HTMLWorker worker = new HTMLWorker(null, tags, style);
    worker.document = worker;
    worker.setProviders(providers);
    worker.objectList = new ArrayList<Element>();
    worker.parse(reader);
    return worker.objectList;
  }

  // DocListener interface

  /** @see com.itextpdf.text.ElementListener#add(com.itextpdf.text.Element) */
  public boolean add(final Element element) throws DocumentException {
    objectList.add(element);
    return true;
  }

  /** @see com.itextpdf.text.DocListener#close() */
  public void close() {}

  /** @see com.itextpdf.text.DocListener#newPage() */
  public boolean newPage() {
    return true;
  }

  /** @see com.itextpdf.text.DocListener#open() */
  public void open() {}

  /** @see com.itextpdf.text.DocListener#resetPageCount() */
  public void resetPageCount() {}

  /** @see com.itextpdf.text.DocListener#setMarginMirroring(boolean) */
  public boolean setMarginMirroring(final boolean marginMirroring) {
    return false;
  }

  /**
   * @see com.itextpdf.text.DocListener#setMarginMirroring(boolean)
   * @since 2.1.6
   */
  public boolean setMarginMirroringTopBottom(final boolean marginMirroring) {
    return false;
  }

  /** @see com.itextpdf.text.DocListener#setMargins(float, float, float, float) */
  public boolean setMargins(
      final float marginLeft,
      final float marginRight,
      final float marginTop,
      final float marginBottom) {
    return true;
  }

  /** @see com.itextpdf.text.DocListener#setPageCount(int) */
  public void setPageCount(final int pageN) {}

  /** @see com.itextpdf.text.DocListener#setPageSize(com.itextpdf.text.Rectangle) */
  public boolean setPageSize(final Rectangle pageSize) {
    return true;
  }

  // deprecated methods

  /**
   * Sets the providers.
   *
   * @deprecated use setProviders() instead
   */
  @Deprecated
  public void setInterfaceProps(final HashMap<String, Object> providers) {
    setProviders(providers);
  }
  /**
   * Gets the providers
   *
   * @deprecated use getProviders() instead
   */
  @Deprecated
  public Map<String, Object> getInterfaceProps() {
    return providers;
  }
}