/** Add Table of content */ private Document toc() { // anything to do? if (toc.isEmpty()) return this; Element old = cursor; // pop back to flow pop("flow", "can't create TOC without enclosing flow"); // add block for toc AS FIRST child push("block", "", cursor.getFirstChild()); // <block> // Table of Contents // <block> // Title 1<leader/><page-number-citation/> // </block> // ... // </block> // add toc header push("block", formatSectionLarger); text(RESOURCES.getString("toc"), ""); pop(); // add toc entries for (Iterator it = toc.iterator(); it.hasNext(); ) { push( "block", "start-indent=1cm,end-indent=1cm,text-indent=0cm,text-align-last=justify,text-align=justify"); TOCEntry entry = (TOCEntry) it.next(); addLink(entry.text, entry.id); push("leader", "leader-pattern=dots").pop(); push("page-number-citation", "ref-id=" + entry.id).pop(); pop(); } // done cursor = old; return this; }
/** * An abstract layer above docbook handling and transformations. * * <p>Some methods have an attributes parameter that allows additional control over the formatting. * The attributes refer to the underlying XSL Format representation, from which various formats of * document can be generated, such as PDF and HTML. Using them effectively require knowledge of XSL * FO (see below) and the internals of the method. * * <p>Some useful resources about XSL FO and the Apache FOP we use to process it are: * * <ul> * <li><a href="http://www.w3.org/TR/xsl11/">The W3C specification of XSL FO</a> * <li><a href="http://xmlgraphics.apache.org/fop/resources.html">The Apache FOP Resources * page</a> * <li><a ref="http://www.renderx.com/demos/src_examples.html">Samples at renderx.com</a> * </ul> */ public class Document { /** * Symbolic constant for font size for sections. * * @see #setSectionSizes * @see #startSection(String, String, int) */ public static final int FONT_XX_SMALL = 0; /** Symbolic constant for font size for sections. */ public static final int FONT_X_SMALL = 1; /** Symbolic constant for font size for sections. */ public static final int FONT_SMALL = 2; /** Symbolic constant for font size for sections. */ public static final int FONT_MEDIUM = 3; /** Symbolic constant for font size for sections. */ public static final int FONT_LARGE = 4; /** Symbolic constant for font size for sections. */ public static final int FONT_X_LARGE = 5; /** Symbolic constant for font size for sections. */ public static final int FONT_XX_LARGE = 6; private static final Resources RESOURCES = Resources.get(Document.class); /** matching a=b,c-d=e,f:g=h,x=y(m,n,o),z=1 */ protected static final Pattern REGEX_ATTR = Pattern.compile("([^,]+)=([^,\\(]*(\\(.*?\\))?)"); /** xsl fo namespace URI */ private static final String NS_XSLFO = "http://www.w3.org/1999/XSL/Format", NS_GENJ = "http://genj.sourceforge.net/XSL/Format"; private org.w3c.dom.Document doc; private Element cursor; private String title; private boolean needsTOC = false; private Map file2elements = new HashMap(); private List toc = new ArrayList(); private String formatSection = "font-weight=bold,space-before=0.5cm,space-after=0.2cm,keep-with-next.within-page=always"; private String formatSectionLarger = "font-size=larger," + formatSection; private static final String[] fontSizes = new String[] {"xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large"}; private int minSectionFontSize; private int maxSectionFontSize; private Map index2primary2secondary2elements = new TreeMap(); private int idSequence = 0; private boolean containsCSV = false; /** Constructor */ public Document(String title) { // remember title this.title = title; // section size range setSectionSizes(FONT_MEDIUM, FONT_XX_LARGE); // create a dom document try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); doc = dbf.newDocumentBuilder().newDocument(); } catch (Throwable t) { throw new RuntimeException(t); } // boilerplate // <root> // <layout-master-set> // <simple-page-master> // <region-body/> // <region-end/> // </simple-page-master> // </layout-master-set> // <page-sequence> // <title> // <flow> // <block/> // </flow> // </page-sequence> cursor = (Element) doc.appendChild(doc.createElementNS(NS_XSLFO, "root")); cursor.setAttribute("xmlns", NS_XSLFO); cursor.setAttribute("xmlns:genj", NS_GENJ); // FOP crashes when a title element is present so we use an extension to pass it to our fo2html // stylesheet // @see http://issues.apache.org/bugzilla/show_bug.cgi?id=38710 cursor.setAttributeNS(NS_GENJ, "genj:title", title); push("layout-master-set"); // Tip: see also http://www.dpawson.co.uk/xsl/sect3/N8565.html for a minimal page master. push( "simple-page-master", "master-name=master,margin-top=1cm,margin-bottom=1cm,margin-left=1cm,margin-right=1cm"); push("region-body", "margin-bottom=1cm").pop(); push("region-after", "extent=0.8cm").pop(); pop().pop().push("page-sequence", "master-reference=master"); /* Paul Grosso offers this suggestion for left-center-right header formatting at http://www.dpawson.co.uk/xsl/sect3/headers.html#d13432e123: <fo:static-content flow-name="xsl-region-before"> <!-- header-width is the width of the full header in picas --> <xsl:variable name="header-width" select="36"/> <xsl:variable name="header-field-width"> <xsl:value-of select="$header-width * 0.3333"/><xsl:text>pc</xsl:text> </xsl:variable> <fo:list-block font-size="8pt" provisional-label-separation="0pt"> <xsl:attribute name="provisional-distance-between-starts"> <xsl:value-of select="$header-field-width"/> </xsl:attribute> <fo:list-item> <fo:list-item-label end-indent="label-end()"> <fo:block text-align="left"> <xsl:text>The left header field</xsl:text> </fo:block> </fo:list-item-label> <fo:list-item-body start-indent="body-start()"> <fo:list-block provisional-label-separation="0pt"> <xsl:attribute name="provisional-distance-between-starts"> <xsl:value-of select="$header-field-width"/> </xsl:attribute> <fo:list-item> <fo:list-item-label end-indent="label-end()"> <fo:block text-align="center"> <fo:page-number/> </fo:block> </fo:list-item-label> <fo:list-item-body start-indent="body-start()"> <fo:block text-align="right"> <xsl:text>The right header field</xsl:text> </fo:block> </fo:list-item-body> </fo:list-item> </fo:list-block> </fo:list-item-body> </fo:list-item> </fo:list-block> </fo:static-content> */ push("static-content", "flow-name=xsl-region-after"); push("block", "text-align=center"); // text("p. ", ""); // todo bk better w/o text, to avoid language-dependency, but with title push("page-number").pop(); pop(); // </block> pop(); // </static-content> // don't use title - see above // push("title").text(getTitle(), "").pop(); push("flow", "flow-name=xsl-region-body"); push("block"); // done - cursor points to first block } /** * Sets the range of logical font sizes to be used for section headings. The outermost section * (depth 1) has size {@link #FONT_XX_LARGE} by default, with each nested section having a smaller * font, until minSize (default {@link #FONT_MEDIUM}) is reached, after which all section headings * appear the same. * * @param minSize Smallest size for nested section headings * @param maxSize Largest size for outermost section headings * @see #startSection(String, String, int) */ public void setSectionSizes(int minSize, int maxSize) { if (minSize < 0 || minSize > maxSize || maxSize > fontSizes.length - 1) throw new IllegalArgumentException("setSectionSizes(" + minSize + "," + maxSize + ")"); minSectionFontSize = minSize; maxSectionFontSize = maxSize; } /** Check if there's any CSV in this document */ protected boolean containsCSV() { return containsCSV; } /** String representation - the title */ public String toString() { return getTitle(); } /** Closes the document finalizing output */ protected void close() { // closed already? if (cursor == null) return; // generate indexes indexes(); // generate TOC if (needsTOC) toc(); // done cursor = null; } /** Title access */ public String getTitle() { return title; } /** Access to DOM source */ /*package*/ DOMSource getDOMSource() { return new DOMSource(doc); } /** Add Table of Content */ public Document addTOC() { needsTOC = true; // done return this; } /** Add a Table of Content entry for current cursor position */ public Document addTOCEntry(String title) { addTOC(); // add anchor String id = "toc" + toc.size(); addAnchor(id); // remember entry toc.add(new TOCEntry(id, title)); // done return this; } /** * Outermost section with largest font size is 1. * * @param sectionDepth * @return XSL-FO font-size parameter */ private String getFontSize(int sectionDepth) { int i = maxSectionFontSize + 1 - sectionDepth; if (i < minSectionFontSize) i = minSectionFontSize; return fontSizes[i]; } /** * Add section at specified depth. The depth is used to determine the font size and should in the * future be used for numbering in X.Y.Z format. 1 is the usual outermost section and maps to * {@link #FONT_XX_LARGE} by default. <a * href="http://www.w3.org/TR/REC-CSS2/fonts.html#font-styling">http://www.w3.org/TR/REC-CSS2/fonts.html#font-styling</a> * describes the meaning of logical font sizes in XSL/FO. * * @see #setSectionSizes */ public Document startSection(String title, String id, int sectionDepth) { // check if if (id != null && id.startsWith("_")) throw new IllegalArgumentException("underscore is reserved for internal IDs"); // return to the last block in flow pop("flow", "addSection() is not applicable outside document flow"); cursor = (Element) cursor.getLastChild(); // generate an id if necessary if (id == null || id.length() == 0) id = "toc" + toc.size(); // start a new block String fontSize = getFontSize(sectionDepth); pop().push("block", "font-size=" + fontSize + "," + formatSection + ",id=" + id); // remember toc.add(new TOCEntry(id, title)); // add the title addText(title); // create the following block nextParagraph(); // done return this; } /** Add section at depth 1. */ public Document startSection(String title, String id) { return startSection(title, id, 1); } /** Add section at specified depth with reference to a GEDCOM entity. */ public Document startSection(String title, Entity entity, int sectionDepth) { return startSection(title, entity.getTag() + "_" + entity.getId(), sectionDepth); } /** Add section at depth. */ public Document startSection(String title, Entity entity) { return startSection(title, entity.getTag() + "_" + entity.getId()); } /** Add section at specified depth. */ public Document startSection(String title, int sectionDepth) { return startSection(title, "", sectionDepth); } /** Add section at depth 1. */ public Document startSection(String title) { return startSection(title, ""); } /** Add an index entry */ public Document addIndexTerm(String index, String primary) { return addIndexTerm(index, primary, ""); } /** Add an index entry */ public Document addIndexTerm(String index, String primary, String secondary) { // check index if (index == null) throw new IllegalArgumentException("addIndexTerm() requires name of index"); index = index.trim(); if (index.length() == 0) throw new IllegalArgumentException("addIndexTerm() name of index can't be empty"); // check primary - ignore indexterm if empty primary = trimIndexTerm(primary); if (primary.length() == 0) return this; // check secondary secondary = trimIndexTerm(secondary); // remember Map primary2secondary2elements = (Map) index2primary2secondary2elements.get(index); if (primary2secondary2elements == null) { primary2secondary2elements = new TreeMap(); index2primary2secondary2elements.put(index, primary2secondary2elements); } Map secondary2elements = (Map) primary2secondary2elements.get(primary); if (secondary2elements == null) { secondary2elements = new TreeMap(); primary2secondary2elements.put(primary, secondary2elements); } List elements = (List) secondary2elements.get(secondary); if (elements == null) { elements = new ArrayList(); secondary2elements.put(secondary, elements); } // add anchor - normally that would be a fo:inline // push("inline", "id=_"+(++idSequence)); // but FOP doesn't support IDs on those elements // so instead we attach an id to the surrounding block String id = cursor.getAttribute("id"); if (id.length() == 0) { id = "" + (++idSequence); cursor.setAttribute("id", id); } // remember the element for primary+secondary if the element isn't in there already if (!elements.contains(cursor)) elements.add(cursor); return this; } private String trimIndexTerm(String term) { // null? if (term == null) return ""; // remove anything after ( int bracket = term.indexOf('('); if (bracket >= 0) term = term.substring(0, bracket); // remove anything after , int comma = term.indexOf('('); if (comma >= 0) term = term.substring(0, comma); // trim return term.trim(); } /** Add text */ public Document addText(String text) { return addText(text, ""); } /** * Add text with given CSS styling. See <a * href="http://www.w3.org/TR/REC-CSS2/fonts.html#font-styling">http://www.w3.org/TR/REC-CSS2/fonts.html#font-styling</a> */ public Document addText(String text, String atts) { text(text, atts); return this; } /** * Add image file reference to the document * * @param file the file pointing to the image * @param atts fo attributes for the image */ public Document addImage(File file, String atts) { // anything we care about? if (file == null || !file.exists()) return this; // check dimension - let's not make this bigger than 1x1 inch Dimension2D dim = new ImageSniffer(file).getDimensionInInches(); if (dim == null) return this; if (dim.getWidth() > dim.getHeight()) { if (dim.getWidth() > 1) atts = "width=1in,content-width=scale-to-fit," + atts; // can be overriden } else { if (dim.getHeight() > 1) atts = "height=1in,content-height=scale-to-fit," + atts; // can be overriden } // <fo:external-graphic src="file"/> push("external-graphic", "src=" + file.getAbsolutePath() + "," + atts); // remember file in case a formatter wants to resolve file location later List elements = (List) file2elements.get(file); if (elements == null) { elements = new ArrayList(3); file2elements.put(file, elements); } elements.add(cursor); // back to enclosing block pop(); // add opportunity to line break addText(" "); // done return this; } /** Access to external image files */ protected File[] getImages() { Set files = file2elements.keySet(); return (File[]) files.toArray(new File[files.size()]); } /** Replace referenced image file with a calculated value */ protected void setImage(File file, String value) { List nodes = (List) file2elements.get(file); for (int i = 0; i < nodes.size(); i++) { Element external = (Element) nodes.get(i); external.setAttribute("src", value); } } /** Add a paragraph */ public Document nextParagraph() { return nextParagraph(""); } /** Add a paragraph */ public Document nextParagraph(String format) { // start a new block if the current is not-empty if (cursor.getFirstChild() != null) pop().push("block", format); else attributes(cursor, format); return this; } /** Start a list */ public Document startList() { return startList(""); } /** Start a list */ public Document startList(String format) { // <list-block> pop(); push( "list-block", "provisional-distance-between-starts=0.6em, provisional-label-separation=0pt," + format); nextListItem(); return this; } /** Add a list item */ public Document nextListItem() { return nextListItem(""); } /** Add a list item */ public Document nextListItem(String format) { // <list-block> // <list-item> // <list-item-label end-indent="label-end()"><block>•</block></list-item-label> // <list-item-body start-indent="body-start()"> // <block/> // </list-item-body> // </list-item> // check containing list-block Element list = peek("list-block", "nextListItem() is not applicable outside list block"); // a list with only one item containing an empty block? if (list.getChildNodes().getLength() == 1 && cursor.getFirstChild() == null && cursor.getPreviousSibling() == null && cursor.getParentNode().getLocalName().equals("list-item-body")) { // delete list-item and start over list.removeChild(list.getFirstChild()); } // continue with list cursor = list; // find out what 'bullet' to use String label = attribute("genj:label", format); if (label != null) { // check provisional-distance-between-starts - we assume a certain 'em' per label character String dist = list.getAttribute("provisional-distance-between-starts"); if (dist.endsWith("em")) { float len = label.length() * 0.6F; if (Float.parseFloat(dist.substring(0, dist.length() - 2)) < len) list.setAttribute("provisional-distance-between-starts", len + "em"); } } else { label = "\u2219"; // • /u2219 works in JEditPane, • \u2022 doesn't } // add new item push("list-item"); push("list-item-label", "end-indent=label-end()"); push("block"); text(label, ""); pop(); pop(); push("list-item-body", "start-indent=body-start()"); push("block"); return this; } /** End a list */ public Document endList() { // * // <list-block> pop("list-block", "endList() is not applicable outside list-block").pop(); push("block", ""); return this; } /** Start a table */ public Document startTable() { return startTable("width=100%,border=0.5pt solid black"); } public Document startTable(String format) { // patch format - FOP only supports table-layout=fixed format = "table-layout=fixed," + format; // <table> // <table-header> // <table-row> // <table-cell> // <block> // ... // </table-header> // <table-body> // <table-row> // <table-cell> // <block> // ... push("table", format); Element table = cursor; // mark as cvs if applicable - non fo namespace attributes won't be picked up by push() and // attributes() if ("true".equals(attribute("genj:csv", format))) { containsCSV = true; cursor.setAttributeNS(NS_GENJ, "genj:csv", "true"); String prefix = attribute("genj:csvprefix", format); if (prefix != null) cursor.setAttributeNS(NS_GENJ, "genj:csvprefix", prefix); } // head/body & row if (format.indexOf("genj:header=true") >= 0) { push("table-header"); push("table-row", "color=#ffffff,background-color=#c0c0c0,font-weight=bold"); } else { push("table-body"); push("table-row"); } // cell and done push("table-cell", "border=" + table.getAttribute("border")); push("block"); return this; } /** Add a column to the table (this is not necessary) */ public Document addTableColumn(String atts) { // <table> // <table-column/> Element save = cursor; // find the enclosing table pop("table", "addTableColumn() is not applicable outside enclosing table"); // find last table definition Node before = cursor.getFirstChild(); while (before != null && before.getNodeName().equals("table-column")) before = before.getNextSibling(); push("table-column", atts, before); // done for now cursor = save; return this; } /** Jump to next cell in table */ public Document nextTableCell() { return nextTableCell(""); } public Document nextTableCell(String atts) { // peek at current cell - stay with it IF // + it's the first cell in the row // + the current cursor points at the first child (a block) // + the block pointed by cursor is empty Element cell = peek("table-cell", "nextTableCell() is not applicable outside enclosing table"); if (cell.getPreviousSibling() == null && cursor == cell.getFirstChild() && !cursor.hasChildNodes()) { attributes(cell, atts); // add empty content to block so another call to nextTableCell() willl actually move forward push("inline", "").pop(); return this; } // peek at row Element row = peek("table-row", "nextTableCell() is not applicable outside enclosing table row"); int cells = row.getElementsByTagName("table-cell").getLength(); // peek at table - add new row if we have all columns already Element table = peek("table", "nextTableCell() is not applicable outside enclosing table"); int cols = table.getElementsByTagName("table-column").getLength(); if (cols > 0 && cells == cols) return nextTableRow(); // pop to row pop("table-row", "nextTableCell() is not applicable outside enclosing table row"); // 20060215 wanted to use border=inherit here but that would require table-row // and table-body to have border=inherit as well. table-body can't have a // border property in a table with border-collapse=separate (which is the only // model FOP supports). // So we're simply doing our own 'inherit' here :) Alternative would be to do // us border=from-nearest-specified-value() on each cell or border=inherit // on the table-columns and then border=from-table-column() on the cells. // add now push("table-cell", "border=" + table.getAttribute("border") + "," + atts); push("block"); // done return this; } /** Jump to next row in table */ public Document nextTableRow() { return nextTableRow(""); } public Document nextTableRow(String atts) { // peek at current cell - stay with it IF // + it's the first cell in the row // + the current cursor points at the first child (a block) // + the block pointed by cursor is empty Element cell = peek("table-cell", "nextTableRow() is not applicable outside enclosing table"); if (cell.getPreviousSibling() == null && cursor == cell.getFirstChild() && !cursor.hasChildNodes()) { attributes((Element) cell.getParentNode(), atts); return this; } // pop to table pop("table", "nextTableRow() is not applicable outside enclosing table"); Element table = cursor; // last child is already table-body? if (table.getLastChild().getNodeName().equals("table-body")) { cursor = (Element) table.getLastChild(); } else { push("table-body"); } // add row push("table-row", atts); // add cell push("table-cell", "border=" + table.getAttribute("border")); push("block"); // done return this; } /** Jump to next cell in table */ public Document endTable() { // leave table pop("table", "endTable() is not applicable outside enclosing table").pop(); // done return this; } /** Force a page break */ public Document nextPage() { pop(); push("block", "page-break-before=always"); return this; } /** Add an anchor */ public Document addAnchor(String id) { if (id.startsWith("_")) throw new IllegalArgumentException("underscore is reserved for internal IDs"); // normally I'd use fo:inline for anchors but FOP can't handle IDs on those elements // so i have to use block here - since the EditorPane uses extra space even for // empty blocks i'm trying to reuse the current block here IF it doesn't have an ID // already if (cursor.getAttribute("id").length() == 0) cursor.setAttribute("id", id); else push("block", "id=" + id).pop(); return this; } /** Add an anchor */ public Document addAnchor(Entity entity) { return addAnchor(entity.getTag() + "_" + entity.getId()); } /** Add a link */ public Document addExternalLink(String text, String id) { // <basic-link external-destination="...">text</basic-link> push("basic-link", "external-destination=" + id); text(text, ""); pop(); // done return this; } /** Add a link */ public Document addLink(String text, String id) { // <basic-link>text</basic-link> push("basic-link", "internal-destination=" + id); text(text, ""); pop(); // done return this; } /** Add a link */ public Document addLink(String text, Entity entity) { addLink(text, entity.getTag() + "_" + entity.getId()); return this; } /** Add a link */ public Document addLink(Entity entity) { return addLink(entity.toString(), entity); } /** Add indexes */ private Document indexes() { // loop over indexes for (Iterator indexes = index2primary2secondary2elements.keySet().iterator(); indexes.hasNext(); ) { String index = (String) indexes.next(); Map primary2secondary2elements = (Map) index2primary2secondary2elements.get(index); // add section nextPage(); startSection(index); push("block", "start-indent=1cm"); // loop over primaries for (Iterator primaries = primary2secondary2elements.keySet().iterator(); primaries.hasNext(); ) { String primary = (String) primaries.next(); Map secondary2elements = (Map) primary2secondary2elements.get(primary); // add block and primary push("block", ""); text(primary + " ", ""); // loop over secondaries for (Iterator secondaries = secondary2elements.keySet().iterator(); secondaries.hasNext(); ) { String secondary = (String) secondaries.next(); List elements = (List) secondary2elements.get(secondary); if (secondary.length() > 0) { push("block", "start-indent=2cm"); // start-indent? text(secondary + " ", ""); } // loop over elements for (int e = 0; e < elements.size(); e++) { if (e > 0) text(", ", ""); Element element = (Element) elements.get(e); String id = element.getAttribute("id"); push("basic-link", "internal-destination=" + id); push("page-number-citation", "ref-id=" + id); cursor.setAttributeNS(NS_GENJ, "genj:citation", Integer.toString(e + 1)); pop(); pop(); } if (secondary.length() > 0) pop(); // next } // next pop(); } // next pop(); } // done return this; } /** Add Table of content */ private Document toc() { // anything to do? if (toc.isEmpty()) return this; Element old = cursor; // pop back to flow pop("flow", "can't create TOC without enclosing flow"); // add block for toc AS FIRST child push("block", "", cursor.getFirstChild()); // <block> // Table of Contents // <block> // Title 1<leader/><page-number-citation/> // </block> // ... // </block> // add toc header push("block", formatSectionLarger); text(RESOURCES.getString("toc"), ""); pop(); // add toc entries for (Iterator it = toc.iterator(); it.hasNext(); ) { push( "block", "start-indent=1cm,end-indent=1cm,text-indent=0cm,text-align-last=justify,text-align=justify"); TOCEntry entry = (TOCEntry) it.next(); addLink(entry.text, entry.id); push("leader", "leader-pattern=dots").pop(); push("page-number-citation", "ref-id=" + entry.id).pop(); pop(); } // done cursor = old; return this; } /** Add qualified element to parent */ private Document push(String name) { return push(name, ""); } /** Add qualified element to parent */ private Document push(String name, String attributes) { return push(name, attributes, null); } /** Add qualified element to parent */ private Document push(String name, String attributes, Node before) { // create it, set attributes and hook it up Element elem = doc.createElementNS(NS_XSLFO, name); if (before != null) cursor.insertBefore(elem, before); else cursor.appendChild(elem); cursor = elem; // attribute it and done return attributes(elem, attributes); } /** Set attributes on current element */ private Document attributes(Element elem, String format) { // parse attributes Matcher m = REGEX_ATTR.matcher(format); while (m.find()) { // accept only fo attributes here String key = m.group(1).trim(); if (key.indexOf(':') < 0) { String val = m.group(2).trim(); elem.setAttribute(key, val); } } // done return this; } /** find an attribute in format */ private String attribute(String key, String format) { // parse attributes Matcher m = REGEX_ATTR.matcher(format); while (m.find()) { if (m.group(1).trim().equals(key)) return m.group(2).trim(); } // not found return null; } /** Add text element */ private Document text(String text, String atts) { // ignore empties if (text.length() == 0) return this; // create a text node for it Node txt = doc.createTextNode(text); if (atts.length() > 0) { push("inline", atts); cursor.appendChild(txt); pop(); } else { cursor.appendChild(txt); } return this; } /** pop element from stack */ private Document pop() { cursor = (Element) cursor.getParentNode(); return this; } /** pop element from stack */ private Document pop(String qname, String error) { cursor = peek(qname, error); return this; } /** find element in current stack upwards */ private Element peek(String qname, String error) { Node loop = cursor; while (loop instanceof Element) { if (loop.getLocalName().equals(qname)) return (Element) loop; loop = loop.getParentNode(); } throw new IllegalArgumentException(error); } /** A table of content entry */ private class TOCEntry { String id; String text; private TOCEntry(String id, String text) { this.id = id; this.text = text; } } /** A test main */ public static void main(String[] args) { try { Document doc = new Document("Testing FO"); doc.addText("A paragraph"); doc.nextParagraph("start-indent=10pt"); doc.addText( "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. "); doc.nextParagraph("text-decoration=underline"); doc.addText("this paragraph is underlined"); doc.nextParagraph(); doc.addText("this line contains "); doc.addText("underlined", "text-decoration=underline"); doc.addText(" text"); doc.startList(); doc.nextListItem("genj:label=a)"); doc.addText("A foo'd bullet"); doc.nextListItem("genj:label=b)"); doc.addText("A foo'd bullet"); doc.nextListItem(); doc.addText("A normal bullet"); doc.addTOC(); doc.startSection("Section 1"); doc.addText("here comes a ") .addText("table", "font-weight=bold, color=rgb(255,0,0)") .addText(" for you:"); doc.addImage( new File( "C:/Documents and Settings/Nils/My Documents/Java/Workspace/GenJ/gedcom/meiern.jpg"), "vertical-align=middle"); doc.addImage( new File("C:/Documents and Settings/Nils/My Documents/My Pictures/usamap.gif"), "vertical-align=middle"); // doc.startTable("width=100%,border=0.5pt solid black,genj:csv=true"); // doc.addTableColumn("column-width=10%"); // doc.addTableColumn("column-width=10%"); // doc.addTableColumn("column-width=80%"); // doc.nextTableCell("color=red"); // doc.addText("AA"); // doc.nextTableCell(); // doc.addText("AB"); // doc.nextTableCell(); // //doc.addText("AC"); // doc.nextTableCell(); // doc.addText("BA"); // next row // doc.nextTableCell("number-columns-spanned=2"); // doc.addText("BB+BC"); // doc.nextTableRow(); // doc.addText("CA"); // doc.nextTableCell(); // doc.addText("CB"); // doc.nextTableCell(); // doc.addText("CC"); // doc.endTable(); // // doc.startList(); // doc.nextListItem(); // doc.addText("Item 1"); // doc.addText(" with text talking about"); // doc.addIndexTerm("Animals", "Mammals"); // doc.addText(" elephants and "); // doc.addIndexTerm("Animals", "Mammals", "Horse"); // doc.addText(" horses as well as "); // doc.addIndexTerm("Animals", "Mammals", "Horse"); // doc.addText(" ponys and even "); // doc.addIndexTerm("Animals", "Fish", ""); // doc.addText(" fish"); // doc.nextParagraph(); // doc.addText("and a newline"); // doc.nextListItem(); // doc.addText("Item 2"); // doc.startList(); // doc.addText("Item 2.1"); // doc.nextListItem(); // doc.addText("Item 2.2"); // doc.endList(); // doc.endList(); // doc.addText("Text"); // doc.startSection("Section 2"); doc.addText("Text and a page break"); // doc.nextPage(); doc.addTOCEntry("Foo"); doc.startSection("Section 3"); doc.addText("Text"); Format format; if (args.length > 0) format = Format.getFormat(args[0]); else format = new PDFFormat(); File file = null; String ext = format.getFileExtension(); if (ext != null) { file = new File("c:/temp/foo." + ext); } format.format(doc, file); if (file != null) Runtime.getRuntime() .exec( "c:/Program Files/Internet Explorer/iexplore.exe \"" + file.getAbsolutePath() + "\""); } catch (IOException e) { e.printStackTrace(); } } }