/* * Created on May 31, 2003 * * To change this generated comment go to * Window>Preferences>Java>Code Generation>Code Template */ package com.idega.block.pdf; /** * <p>Title: idegaWeb</p> * <p>Description: </p> * <p>Copyright: Copyright (c) 2003</p> * <p>Company: idega Software</p> * @author aron * @version 1.0 */ import java.util.ArrayList; import java.util.Collections; import java.util.EmptyStackException; import java.util.Iterator; import java.util.Properties; import java.util.Stack; import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.Attributes; import com.lowagie.text.*; /** * The <CODE>Tags</CODE>-class maps several XHTML-tags to iText-objects. */ public class SAXiTextHandler extends DefaultHandler { /** This is the resulting document. */ protected DocListener document; /** This is a <CODE>Stack</CODE> of objects, waiting to be added to the document. */ protected Stack stack; /** Counts the number of chapters in this document. */ protected int chapters = 0; /** This is the current chunk to which characters can be added. */ protected Chunk currentChunk = null; /** This is the current chunk to which characters can be added. */ protected boolean ignore = false; /** This is a flag that can be set, if you want to open and close the Document-object yourself. */ protected boolean controlOpenClose = true; /** * Constructs a new SAXiTextHandler that will translate all the events * triggered by the parser to actions on the <CODE>Document</CODE>-object. * * @paramdocumentthis is the document on which events must be triggered */ public SAXiTextHandler(DocListener document) { super(); this.document = document; this.stack = new Stack(); } /** * Sets the parameter that allows you to enable/disable the control over the Document.open() and Document.close() method. * <P> * If you set this parameter to true (= default), the parser will open the Document object when the start-root-tag is encounterd * and close it when the end-root-tag is met. If you set it to false, you have to open and close the Document object * yourself. * * @param controlOpenClose set this to false if you plan to open/close the Document yourself */ public void setControlOpenClose(boolean controlOpenClose) { this.controlOpenClose = controlOpenClose; } /** * This method gets called when a start tag is encountered. * * @paramnamethe name of the tag that is encountered * @paramattrsthe list of attributes */ public void startElement(String uri, String lname, String name, Attributes attrs) { Properties attributes = new Properties(); if (attrs != null) { for (int i = 0; i < attrs.getLength(); i++) { String attribute = attrs.getQName(i); attributes.setProperty(attribute, attrs.getValue(i)); } } handleStartingTags(name, attributes); } /** * This method deals with the starting tags. * * @param name the name of the tag * @param attributes the list of attributes */ public void handleStartingTags(String name, Properties attributes) { //System.err.println("Start: " + name); if (this.ignore || ElementTags.IGNORE.equals(name)) { this.ignore = true; return; } // maybe there is some meaningful data that wasn't between tags if (this.currentChunk != null) { TextElementArray current; try { current = (TextElementArray) this.stack.pop(); } catch(EmptyStackException ese) { current = new Paragraph(); } current.add(this.currentChunk); this.stack.push(current); this.currentChunk = null; } // chunks if (Chunk.isTag(name)) { this.currentChunk = new Chunk(attributes); return; } // symbols if (Entities.isTag(name)) { Font f = new Font(); if (this.currentChunk != null) { handleEndingTags(ElementTags.CHUNK); f = this.currentChunk.font(); } this.currentChunk = Entities.get(attributes.getProperty(ElementTags.ID), f); return; } // phrases if (Phrase.isTag(name)) { this.stack.push(new Phrase(attributes)); return; } // anchors if (Anchor.isTag(name)) { this.stack.push(new Anchor(attributes)); return; } // paragraphs and titles if (Paragraph.isTag(name) || Section.isTitle(name)) { this.stack.push(new Paragraph(attributes)); return; } // lists if (List.isTag(name)) { this.stack.push(new List(attributes)); return; } // listitems if (ListItem.isTag(name)) { this.stack.push(new ListItem(attributes)); return; } // cells if (Cell.isTag(name)) { this.stack.push(new Cell(attributes)); return; } // tables if (Table.isTag(name)) { Table table = new Table(attributes); float widths[] = table.getProportionalWidths(); for (int i = 0; i < widths.length; i++) { if (widths[i] == 0) { widths[i] = 100.0f / widths.length; } } try { table.setWidths(widths); } catch(BadElementException bee) { // this shouldn't happen throw new ExceptionConverter(bee); } this.stack.push(table); return; } // sections if (Section.isTag(name)) { Element previous = (Element) this.stack.pop(); Section section; try { section = ((Section)previous).addSection(attributes); } catch(ClassCastException cce) { throw new ExceptionConverter(cce); } this.stack.push(previous); this.stack.push(section); return; } // chapters if (Chapter.isTag(name)) { String value; // changed after a suggestion by Serge S. Vasiljev if ((value = (String)attributes.remove(ElementTags.NUMBER)) != null){ this.chapters = Integer.parseInt(value); } else { this.chapters++; } Chapter chapter = new Chapter(attributes,this.chapters); this.stack.push(chapter); return; } // images if (Image.isTag(name)) { try { Image img = Image.getInstance(attributes); Object current; try { // if there is an element on the stack... current = this.stack.pop(); // ...and it's a Chapter or a Section, the Image can be added directly if (current instanceof Chapter || current instanceof Section || current instanceof Cell) { ((TextElementArray)current).add(img); this.stack.push(current); return; } // ...if not, the Image is wrapped in a Chunk before it's added else { Stack newStack = new Stack(); try { while (! (current instanceof Chapter || current instanceof Section || current instanceof Cell)) { newStack.push(current); if (current instanceof Anchor) { img.setAnnotation(new Annotation(0, 0, 0, 0, ((Anchor)current).reference())); } current = this.stack.pop(); } ((TextElementArray)current).add(img); this.stack.push(current); } catch(EmptyStackException ese) { this.document.add(img); } while (!newStack.empty()) { this.stack.push(newStack.pop()); } return; } } catch(EmptyStackException ese) { // if there is no element on the stack, the Image is added to the document try { this.document.add(img); } catch(DocumentException de) { throw new ExceptionConverter(de); } return; } } catch(Exception e) { throw new ExceptionConverter(e); } } // annotations if (Annotation.isTag(name)) { Annotation annotation = new Annotation(attributes); TextElementArray current; try { try { current = (TextElementArray) this.stack.pop(); try { current.add(annotation); } catch(Exception e) { this.document.add(annotation); } this.stack.push(current); } catch(EmptyStackException ese) { this.document.add(annotation); } return; } catch(DocumentException de) { throw new ExceptionConverter(de); } } // newlines if (isNewline(name)) { TextElementArray current; try { current = (TextElementArray) this.stack.pop(); current.add(Chunk.NEWLINE); this.stack.push(current); } catch(EmptyStackException ese) { if (this.currentChunk == null) { try { this.document.add(Chunk.NEWLINE); } catch(DocumentException de) { throw new ExceptionConverter(de); } } else { this.currentChunk.append("\n"); } } return; } // newpage if (isNewpage(name)) { TextElementArray current; try { current = (TextElementArray) this.stack.pop(); Chunk newPage = new Chunk(""); newPage.setNewPage(); current.add(newPage); this.stack.push(current); } catch(EmptyStackException ese) { try { this.document.newPage(); } catch(DocumentException de) { throw new ExceptionConverter(de); } } return; } // newpage if (ElementTags.HORIZONTALRULE.equals(name)) { TextElementArray current; Graphic hr = new Graphic(); hr.setHorizontalLine(1.0f, 100.0f); try { current = (TextElementArray) this.stack.pop(); current.add(hr); this.stack.push(current); } catch(EmptyStackException ese) { try { this.document.add(hr); } catch(DocumentException de) { throw new ExceptionConverter(de); } } return; } // documentroot if (isDocumentRoot(name)) { String key; String value; for (Iterator i = attributes.keySet().iterator(); i.hasNext(); ) { key = (String) i.next(); value = attributes.getProperty(key); try { this.document.add(new Meta(key, value)); } catch(DocumentException de) { throw new ExceptionConverter(de); } } if (this.controlOpenClose) { this.document.open(); } } } /** * This method gets called when ignorable white space encountered. * * @paramchan array of characters * @paramstartthe start position in the array * @paramlengththe number of characters to read from the array */ public void ignorableWhitespace(char[] ch, int start, int length) { // do nothing: we handle white space ourselves in the characters method } /** * This method gets called when characters are encountered. * * @paramchan array of characters * @paramstartthe start position in the array * @paramlengththe number of characters to read from the array */ public void characters(char[] ch, int start, int length) { if (this.ignore) { return; } String content = new String(ch, start, length); //System.err.println("'" + content + "'"); if (content.trim().length() == 0) { return; } StringBuffer buf = new StringBuffer(); int len = content.length(); char character; boolean newline = false; for (int i = 0; i < len; i++) { switch(character = content.charAt(i)) { case ' ': if (!newline) { buf.append(character); } break; case '\n': if (i > 0) { newline = true; buf.append(' '); } break; case '\r': break; case '\t': break; default: newline = false; buf.append(character); } } if (this.currentChunk == null) { this.currentChunk = new Chunk(buf.toString()); } else { this.currentChunk.append(buf.toString()); } } /** * This method gets called when an end tag is encountered. * * @paramnamethe name of the tag that ends */ public void endElement(String uri, String lname, String name) { handleEndingTags(name); } /** * This method deals with the starting tags. * * @param name the name of the tag */ public void handleEndingTags(String name) { //System.err.println("Stop: " + name); if (ElementTags.IGNORE.equals(name)) { this.ignore = false; return; } if (this.ignore) { return; } // tags that don't have any content if (isNewpage(name) || Annotation.isTag(name) || Image.isTag(name) || isNewline(name)) { return; } try { // titles of sections and chapters if (Section.isTitle(name)) { Paragraph current = (Paragraph) this.stack.pop(); if (this.currentChunk != null) { current.add(this.currentChunk); this.currentChunk = null; } Section previous = (Section) this.stack.pop(); previous.setTitle(current); this.stack.push(previous); return; } // all other endtags if (this.currentChunk != null) { TextElementArray current; try { current = (TextElementArray) this.stack.pop(); } catch(EmptyStackException ese) { current = new Paragraph(); } current.add(this.currentChunk); this.stack.push(current); this.currentChunk = null; } // chunks if (Chunk.isTag(name)) { return; } // phrases, anchors, lists, tables if (Phrase.isTag(name) || Anchor.isTag(name) || List.isTag(name) || Paragraph.isTag(name)) { Element current = (Element) this.stack.pop(); try { TextElementArray previous = (TextElementArray) this.stack.pop(); previous.add(current); this.stack.push(previous); } catch(EmptyStackException ese) { this.document.add(current); } return; } // listitems if (ListItem.isTag(name)) { ListItem listItem = (ListItem) this.stack.pop(); List list = (List) this.stack.pop(); list.add(listItem); this.stack.push(list); } // tables if (Table.isTag(name)) { Table table = (Table) this.stack.pop(); try { TextElementArray previous = (TextElementArray) this.stack.pop(); previous.add(table); this.stack.push(previous); } catch(EmptyStackException ese) { this.document.add(table); } return; } // rows if (Row.isTag(name)) { ArrayList cells = new ArrayList(); int columns = 0; Table table; Cell cell; while (true) { Element element = (Element) this.stack.pop(); if (element.type() == Element.CELL) { cell = (Cell) element; columns += cell.colspan(); cells.add(cell); } else { table = (Table) element; break; } } if (table.columns() < columns) { table.addColumns(columns - table.columns()); } Collections.reverse(cells); String width; float[] cellWidths = new float[columns]; boolean[] cellNulls = new boolean[columns]; for (int i = 0; i < columns; i++) { cellWidths[i] = 0; cellNulls[i] = true; } float total = 0; int j = 0; for (Iterator i = cells.iterator(); i.hasNext(); ) { cell = (Cell) i.next(); if ((width = cell.cellWidth()) == null) { if (cell.colspan() == 1 && cellWidths[j] == 0) { try { cellWidths[j] = 100f / columns; total += cellWidths[j]; } catch(Exception e) { // empty on purpose } } else if (cell.colspan() == 1) { cellNulls[j] = false; } } else if (cell.colspan() == 1 && width.endsWith("%")) { try { cellWidths[j] = Float.valueOf(width.substring(0, width.length() - 1) + "f").floatValue(); total += cellWidths[j]; } catch(Exception e) { // empty on purpose } } j += cell.colspan(); table.addCell(cell); } float widths[] = table.getProportionalWidths(); if (widths.length == columns) { float left = 0.0f; for (int i = 0; i < columns; i++) { if (cellNulls[i] && widths[i] != 0) { left += widths[i]; cellWidths[i] = widths[i]; } } if (100.0 >= total) { for (int i = 0; i < widths.length; i++) { if (cellWidths[i] == 0 && widths[i] != 0) { cellWidths[i] = (widths[i] / left) * (100.0f - total); } } } table.setWidths(cellWidths); } this.stack.push(table); } // cells if (Cell.isTag(name)) { return; } // sections if (Section.isTag(name)) { this.stack.pop(); return; } // chapters if (Chapter.isTag(name)) { this.document.add((Element) this.stack.pop()); return; } // the documentroot if (isDocumentRoot(name)) { try { while (true) { Element element = (Element) this.stack.pop(); try { TextElementArray previous = (TextElementArray) this.stack.pop(); previous.add(element); this.stack.push(previous); } catch(EmptyStackException es) { this.document.add(element); } } } catch(EmptyStackException ese) { // empty on purpose } if (this.controlOpenClose) { this.document.close(); } return; } } catch(DocumentException de) { throw new ExceptionConverter(de); } } /** * Checks if a certain tag corresponds with the newpage-tag. * * @paramtaga presumed tagname * @return<CODE>true</CODE> or <CODE>false</CODE> */ private boolean isNewpage(String tag) { return ElementTags.NEWPAGE.equals(tag); } /** * Checks if a certain tag corresponds with the newpage-tag. * * @paramtaga presumed tagname * @return<CODE>true</CODE> or <CODE>false</CODE> */ private boolean isNewline(String tag) { return ElementTags.NEWLINE.equals(tag); } /** * Checks if a certain tag corresponds with the roottag. * * @paramtaga presumed tagname * @return<CODE>true</CODE> if <VAR>tag</VAR> equals <CODE>itext</CODE>, <CODE>false</CODE> otherwise. */ protected boolean isDocumentRoot(String tag) { return ElementTags.ITEXT.equals(tag); } }