com.concursive.connect.web.modules.wiki.utils.WikiPDFUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.concursive.connect.web.modules.wiki.utils.WikiPDFUtils.java

Source

/*
 * ConcourseConnect
 * Copyright 2009 Concursive Corporation
 * http://www.concursive.com
 *
 * This file is part of ConcourseConnect, an open source social business
 * software and community platform.
 *
 * Concursive ConcourseConnect is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, version 3 of the License.
 *
 * Under the terms of the GNU Affero General Public License you must release the
 * complete source code for any application that uses any part of ConcourseConnect
 * (system header files and libraries used by the operating system are excluded).
 * These terms must be included in any work that has ConcourseConnect components.
 * If you are developing and distributing open source applications under the
 * GNU Affero General Public License, then you are free to use ConcourseConnect
 * under the GNU Affero General Public License.
 *
 * If you are deploying a web site in which users interact with any portion of
 * ConcourseConnect over a network, the complete source code changes must be made
 * available.  For example, include a link to the source archive directly from
 * your web site.
 *
 * For OEMs, ISVs, SIs and VARs who distribute ConcourseConnect with their
 * products, and do not license and distribute their source code under the GNU
 * Affero General Public License, Concursive provides a flexible commercial
 * license.
 *
 * To anyone in doubt, we recommend the commercial license. Our commercial license
 * is competitively priced and will eliminate any confusion about how
 * ConcourseConnect can be used and distributed.
 *
 * ConcourseConnect is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with ConcourseConnect.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Attribution Notice: ConcourseConnect is an Original Work of software created
 * by Concursive Corporation
 */

package com.concursive.connect.web.modules.wiki.utils;

import com.concursive.commons.date.DateUtils;
import com.concursive.commons.db.DatabaseUtils;
import com.concursive.commons.phone.PhoneNumberBean;
import com.concursive.commons.phone.PhoneNumberUtils;
import com.concursive.commons.text.StringUtils;
import com.concursive.connect.Constants;
import com.concursive.connect.web.modules.documents.dao.FileItem;
import com.concursive.connect.web.modules.documents.dao.FileItemList;
import com.concursive.connect.web.modules.documents.dao.ImageInfo;
import com.concursive.connect.web.modules.profile.dao.Project;
import com.concursive.connect.web.modules.wiki.beans.WikiExportBean;
import com.concursive.connect.web.modules.wiki.dao.*;
import com.lowagie.text.*;
import com.lowagie.text.Font;
import com.lowagie.text.Image;
import com.lowagie.text.List;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.awt.*;
import java.io.*;
import java.net.URL;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Class to manipulate wiki objects
 *
 * @author matt rajkowski
 * @version $Id$
 * @created September 26, 2006
 */
public class WikiPDFUtils {

    private static final Log LOG = LogFactory.getLog(WikiPDFUtils.class);

    public static String CRLF = "\n";
    public final static String fs = System.getProperty("file.separator");
    // Define styles
    public final static Font titleFont = FontFactory.getFont(FontFactory.TIMES, 24, Font.NORMAL,
            new Color(0, 0, 0));
    public final static Font titleSmallFont = FontFactory.getFont(FontFactory.TIMES, 12, Font.NORMAL,
            new Color(0, 0, 0));
    public final static Font wikiFont = FontFactory.getFont(FontFactory.HELVETICA, 24, Font.ITALIC,
            new Color(0, 0, 0));
    public final static Font sectionFont = FontFactory.getFont(FontFactory.HELVETICA, 20, Font.NORMAL,
            new Color(0, 0, 0));
    public final static Font section2Font = FontFactory.getFont(FontFactory.HELVETICA, 18, Font.NORMAL,
            new Color(0, 0, 0));
    public final static Font section3Font = FontFactory.getFont(FontFactory.HELVETICA, 16, Font.NORMAL,
            new Color(0, 0, 0));
    public final static Font codeFont = FontFactory.getFont(FontFactory.COURIER, 10, Font.NORMAL,
            new Color(0, 0, 0));

    public static boolean functionsToUse(Project project, Wiki wiki, File file, HashMap imageList)
            throws Exception {
        // remove this.
        Document document = new Document();

        // Parse each line to see if paragraph, table, section heading, list
        // Parse within the line to see if bold, italic, links, code fragment, images

        // Define styles
        Font exampleFont = FontFactory.getFont(FontFactory.HELVETICA, 24, Font.NORMAL, new Color(255, 0, 0));

        // Add title and paragraphs

        // TODO: Construct a title page... with logo
        Paragraph title = new Paragraph(wiki.getSubject(), exampleFont);

        //Chunk title = new Chunk(wiki.getSubject());
        //title.setBackground(new Color(0xFF, 0xDE, 0xAD));
        ////title.setUnderline(new Color(0xFF, 0x00, 0x00), 3.0f, 0.0f, -4.0f, 0.0f, PdfContentByte.LINE_CAP_ROUND);
        //title.setUnderline(0.2f, -2f);
        LOG.debug("document.add(title)");
        document.add(title);

        // Link from
        // a paragraph with a local goto
        //Paragraph p1 = new Paragraph("We will do something special with this paragraph. If you click on ", FontFactory.getFont(FontFactory.HELVETICA, 12));
        //p1.add(new Chunk("this word", FontFactory.getFont(FontFactory.HELVETICA, 12, Font.NORMAL, new Color(0, 0, 255))).setLocalGoto("test"));
        //p1.add(" you will automatically jump to another location in this document.");

        // a paragraph with a local destination
        //Paragraph p3 = new Paragraph("This paragraph contains a ");
        //p3.add(new Chunk("local destination", FontFactory.getFont(FontFactory.HELVETICA, 12, Font.NORMAL, new Color(0, 255, 0))).setLocalDestination("test"));

        // Ext A Href
        //Paragraph paragraph = new Paragraph("Please visit my ");
        //Anchor anchor1 = new Anchor("website (external reference)", FontFactory.getFont(FontFactory.HELVETICA, 12, Font.UNDERLINE, new Color(0, 0, 255)));
        //anchor1.setReference("http://www.lowagie.com/iText/");
        //anchor1.setName("top");
        //paragraph.add(anchor1);

        //Image jpg = Image.getInstance("otsoe.jpg");
        //document.add(jpg);

        return false;
    }

    public static boolean exportToFile(WikiPDFContext context, Connection db) throws Exception {

        LOG.debug("exportToFile-> begin");

        // Context Objects
        Wiki wiki = context.getWiki();
        Project project = context.getProject();
        File file = context.getFile();
        WikiExportBean exportBean = context.getExportBean();

        // Determine the content to parse
        String content = wiki.getContent();
        if (content == null) {
            return false;
        }

        // Create a pdf
        Document document = new Document(PageSize.LETTER);
        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(file));

        // Meta data
        document.addTitle(project.getTitle());
        document.addSubject(wiki.getSubject());
        document.addCreator("Concursive ConcourseConnect");
        document.addAuthor("Wiki Contributor");
        //writer.setPageEvent(new PageNumbersWatermark());

        if (!exportBean.getIncludeTitle()) {
            boolean hasTitle = StringUtils.hasText(wiki.getSubject());
            HeaderFooter pageFooter = new HeaderFooter(
                    new Phrase(project.getTitle() + (hasTitle ? ": " + wiki.getSubject() : "") + " - page "),
                    new Phrase(""));
            pageFooter.setAlignment(Element.ALIGN_CENTER);
            document.setFooter(pageFooter);
        }

        document.open();

        if (exportBean.getIncludeTitle()) {
            //HeaderFooter pageHeader = new HeaderFooter(new Phrase(project.getTitle()), false);
            //document.setHeader(pageHeader);
            boolean hasTitle = (StringUtils.hasText(wiki.getSubject()));
            HeaderFooter pageFooter = new HeaderFooter(
                    new Phrase(project.getTitle() + (hasTitle ? ": " + wiki.getSubject() : "") + " - page "),
                    new Phrase(""));
            pageFooter.setAlignment(Element.ALIGN_CENTER);
            document.setFooter(pageFooter);

            // Draw a title page
            Rectangle rectangle = new Rectangle(600, 30);
            rectangle.setBackgroundColor(new Color(100, 100, 100));
            LOG.debug("document.add(rectangle)");
            document.add(rectangle);

            document.add(new Paragraph(project.getTitle(), titleFont));
            if (!"".equals(wiki.getSubject())) {
                document.add(new Paragraph(wiki.getSubject(), titleFont));
            }
            document.add(Chunk.NEWLINE);
            document.add(new Paragraph("Last Modified: " + wiki.getModified(), titleSmallFont));
            document.newPage();
        }

        ArrayList<Integer> wikiListDone = new ArrayList<Integer>();

        appendWiki(context, context.getWiki(), document, db, wikiListDone);
        // Close everything
        document.close();
        writer.close();
        LOG.debug("exportToFile-> finished");
        return true;
    }

    private static void appendWiki(WikiPDFContext context, Wiki currentWiki, Document document, Connection db,
            ArrayList<Integer> wikiListDone) throws SQLException {

        LOG.debug("appendWiki-> " + currentWiki.getSubject());

        // Context Objects
        Project project = context.getProject();
        WikiExportBean exportBean = context.getExportBean();

        // Track the wikis to get appended to the output
        ArrayList<Integer> wikiListTodo = new ArrayList<Integer>();

        try {
            // Output the name of the Wiki
            boolean hasTitle = StringUtils.hasText(currentWiki.getSubject());
            if (hasTitle) {
                Anchor wikiAnchor = new Anchor(currentWiki.getSubject(), wikiFont);
                wikiAnchor.setName(currentWiki.getSubject().toLowerCase());
                LOG.debug("Add anchor: " + currentWiki.getSubject().toLowerCase());
                document.add(wikiAnchor);
                LOG.debug("document.add(wikiAnchor)");
            }

            // Output the wiki content
            parseContent(context, currentWiki, currentWiki.getContent(), document, null, db, wikiListTodo,
                    wikiListDone, 0f);
            wikiListDone.add(currentWiki.getId());

            // See if any linked wikis should be appended
            if (exportBean.getFollowLinks() && wikiListTodo.size() > 0) {
                Iterator i = wikiListTodo.iterator();
                while (i.hasNext()) {
                    Integer id = (Integer) i.next();
                    if (id > -1 && !wikiListDone.contains(id)) {
                        Wiki subwiki = new Wiki(db, id);
                        document.add(Chunk.NEXTPAGE);
                        appendWiki(context, subwiki, document, db, wikiListDone);
                    }
                    //i.remove();
                }
            }
        } catch (Exception e) {
            LOG.error("appendWiki", e);
        }
    }

    private static boolean parseContent(WikiPDFContext context, Wiki wiki, String content, Document document,
            PdfPCell cell, Connection db, ArrayList<Integer> wikiListTodo, ArrayList<Integer> wikiListDone,
            float cellWidth) throws Exception {

        LOG.debug("PARSING CONTENT: " + content);

        // Parse the wiki page
        int lastIndent = 0;
        boolean preTag = false;
        boolean pre = false;
        boolean code = false;
        boolean header = true;

        try {

            BufferedReader in = new BufferedReader(new StringReader(content));
            String line = null;

            ArrayList unorderedParents = null;
            List thisList = null;
            Paragraph codeParagraph = null;

            while ((line = in.readLine()) != null) {
                // Tables
                if (line.startsWith("|")) {
                    // @todo Close all ordered lists, unordered lists, and paragraphs

                    // Parse the table
                    line = parseTable(context, wiki, line, document, db, wikiListTodo, wikiListDone, in);

                    if (line == null) {
                        continue;
                    }
                }

                // Forms
                if (line.startsWith("[{form")) {
                    // @todo close any lists or paragraphs

                    // parseForm operates over all the lines that make up the form,
                    // it will have to look forward so it returns an unparsed line
                    parseForm(context, db, in, line, document, cell);
                    continue;
                }

                // Handle code blocks
                // @todo chunk the content similar to WikiToHTMLUtils otherwise inaccurate
                if (line.startsWith("<pre>") || line.startsWith("<code>")) {
                    if (!code && !pre) {
                        if (line.startsWith("<pre>")) {
                            preTag = true;
                            pre = true;
                        } else if (line.startsWith("<code>")) {
                            code = true;
                        }
                        codeParagraph = new Paragraph("", codeFont);
                        codeParagraph.setSpacingBefore(10);

                        if (pre && line.length() > ("<pre>").length()) {
                            int endOfLine = line.length();
                            if (line.endsWith("</pre>")) {
                                endOfLine = line.indexOf("</pre>");
                            }
                            // This line has some extra content that needs to be added
                            codeParagraph.add(new Chunk(
                                    line.substring(line.indexOf("<pre>") + 5, endOfLine) + Chunk.NEWLINE));
                        }
                        if (code && line.length() > ("<code>").length()) {
                            int endOfLine = line.length();
                            if (line.endsWith("</code>")) {
                                endOfLine = line.indexOf("</code>");
                            }
                            // This line has some extra content that needs to be added
                            codeParagraph.add(new Chunk(
                                    line.substring(line.indexOf("<code>") + 6, endOfLine) + Chunk.NEWLINE));
                        }
                        // See if this is a single line block
                        if (preTag && line.endsWith("</pre>")) {
                            // This is a single line block, so finish processing it
                        } else if (code && line.endsWith("</code>")) {
                            // This is a single line block, so finish processing it
                        } else {
                            // There are more lines to process, so do that
                            continue;
                        }
                    }
                }
                if (line.startsWith("</code>") || line.endsWith("</code>")) {
                    if (code) {
                        code = false;
                        if (line.indexOf("</code>") > 0 && !line.startsWith("<code>")) {
                            // This line has some extra content that needs to be added
                            codeParagraph
                                    .add(new Chunk(line.substring(0, line.indexOf("</code>")) + Chunk.NEWLINE));
                        }
                        // Draw the final content
                        PdfPTable codeTable = new PdfPTable(1);
                        codeTable.setWidthPercentage(100);
                        codeTable.setSpacingBefore(10);
                        PdfPCell codeCell = new PdfPCell(codeParagraph);
                        codeCell.setPadding(20);
                        codeCell.setBorderColor(new Color(100, 100, 100));
                        codeCell.setBackgroundColor(new Color(200, 200, 200));
                        //            codeCell.setNoWrap(true);
                        codeTable.addCell(codeCell);
                        LOG.debug("document.add(codeTable)");
                        document.add(codeTable);
                        continue;
                    }
                }
                if (line.startsWith("</pre>") || line.endsWith("</pre>")) {
                    if (pre) {
                        preTag = false;
                        pre = false;
                        if (line.indexOf("</pre>") > 0 && !line.startsWith("<pre>")) {
                            // This line has some extra content that needs to be added
                            codeParagraph.add(new Chunk(line.substring(0, line.indexOf("</pre>")) + Chunk.NEWLINE));
                        }
                        // Draw the final content
                        PdfPTable codeTable = new PdfPTable(1);
                        codeTable.setWidthPercentage(100);
                        codeTable.setSpacingBefore(10);
                        PdfPCell codeCell = new PdfPCell(codeParagraph);
                        codeCell.setPadding(20);
                        codeCell.setBorderColor(new Color(100, 100, 100));
                        codeCell.setBackgroundColor(new Color(200, 200, 200));
                        //            codeCell.setNoWrap(true);
                        codeTable.addCell(codeCell);
                        LOG.debug("document.add(codeTable)");
                        document.add(codeTable);
                        continue;
                    }
                }
                if (code || preTag) {
                    // Append the chunk
                    codeParagraph.add(new Chunk(line + Chunk.NEWLINE));
                    continue;
                }

                // Section
                if (line.startsWith("=") && line.endsWith("=")) {
                    // @todo close any open lists or paragraphs

                    int hCount = parseHCount(line, "=");
                    if (hCount > 6) {
                        hCount = 6;
                    }
                    String section = line.substring(line.indexOf("=") + hCount, line.lastIndexOf("=") - hCount + 1);
                    header = true;
                    context.foundHeader(hCount);
                    String headerAnchor = null;

                    // Store the h2's with anchors for table of contents or index
                    if (hCount == 2) {
                        headerAnchor = StringUtils.toHtmlValue(section).replace(" ", "_");
                        context.getHeaderAnchors().put(headerAnchor, section);
                    }
                    if (hCount == 3) {
                        Paragraph title = new Paragraph(section.trim(), section2Font);
                        title.setSpacingBefore(10);
                        if (cell != null) {
                            LOG.debug("phrase.add(title)");
                            cell.addElement(title);
                        } else {
                            LOG.debug("document.add(title)");
                            document.add(title);
                        }
                    } else if (hCount > 3) {
                        Paragraph title = new Paragraph(section.trim(), section3Font);
                        title.setSpacingBefore(10);
                        if (cell != null) {
                            LOG.debug("phrase.add(title)");
                            cell.addElement(title);
                        } else {
                            LOG.debug("document.add(title)");
                            document.add(title);
                        }
                    } else {
                        Paragraph title = new Paragraph(section.trim(), sectionFont);
                        title.setSpacingBefore(10);
                        if (cell != null) {
                            LOG.debug("phrase.add(title)");
                            cell.addElement(title);
                        } else {
                            LOG.debug("document.add(title)");
                            document.add(title);
                        }
                    }
                    continue;
                }
                if (header) {
                    header = false;
                    if (line.trim().equals("")) {
                        // remove the extra space a user may leave after a header
                        continue;
                    }
                }

                // Determine if this is a bulleted list
                if (line.startsWith("*") || line.startsWith("#")) {
                    // Initialize the list array
                    if (unorderedParents == null) {
                        unorderedParents = new ArrayList();
                        //            if (phrase != null) {
                        //              LOG.debug("phrase.add(new Paragraph(Chunk.NEWLINE))");
                        //              phrase.add(new Paragraph(Chunk.NEWLINE));
                        //            } else {
                        //              LOG.debug("document.add(new Paragraph(Chunk.NEWLINE))");
                        //              document.add(new Paragraph(Chunk.NEWLINE));
                        //            }
                    }
                    // Get the indent level
                    boolean ol = line.startsWith("#");
                    int hCount = WikiPDFUtils.parseHCount(line, ol ? "#" : "*");
                    // Determine a shift in the tree
                    if (lastIndent == 0) {
                        if (ol) {
                            thisList = new List(ol, 20);
                        } else {
                            thisList = new List(ol, 10);
                            thisList.setListSymbol(
                                    new Chunk("\u2022", FontFactory.getFont(FontFactory.HELVETICA, 12)));
                        }
                        thisList.setIndentationLeft(36);
                        thisList.setIndentationRight(36);
                        unorderedParents.add(thisList);
                    } else {
                        if (hCount > lastIndent) {
                            if (ol) {
                                thisList = new List(ol, 20);
                            } else {
                                thisList = new List(ol, 10);
                                thisList.setListSymbol(
                                        new Chunk("\u2022", FontFactory.getFont(FontFactory.HELVETICA, 12)));
                            }
                            thisList.setIndentationLeft(36);
                            thisList.setIndentationRight(36);
                            ((List) unorderedParents.get(unorderedParents.size() - 1)).add(thisList);
                            unorderedParents.add(thisList);
                        } else if (hCount < lastIndent) {
                            unorderedParents.remove(unorderedParents.size() - 1);
                            thisList = (List) unorderedParents.get(unorderedParents.size() - 1);
                        }
                    }
                    lastIndent = hCount;
                    // Append the item...
                    Paragraph thisItem = new Paragraph();
                    parseLine(context, line.substring(hCount).trim(), thisItem, db, wikiListTodo, cellWidth, cell);
                    thisList.add(new ListItem(thisItem));
                    continue;
                }
                // List is finished, so append it to the document before working on
                // other paragraphs
                if (unorderedParents != null) {
                    if (cell != null) {
                        LOG.debug("phrase.add((List) unorderedParents.get(0))");
                        cell.addElement((List) unorderedParents.get(0));
                    } else {
                        LOG.debug("document.add((List) unorderedParents.get(0))");
                        document.add((List) unorderedParents.get(0));
                    }
                    unorderedParents = null;
                    thisList = null;
                    lastIndent = 0;
                }

                // Otherwise a simple paragraph
                Paragraph paragraph = new Paragraph();
                parseLine(context, line, paragraph, db, wikiListTodo, cellWidth, cell);
                if (cell != null) {
                    LOG.debug("phrase.add(paragraph)");
                    if (cell.getHorizontalAlignment() == Element.ALIGN_CENTER) {
                        paragraph.setAlignment(Element.ALIGN_CENTER);
                    }
                    paragraph.setSpacingBefore(5);
                    cell.addElement(paragraph);
                } else {
                    LOG.debug("document.add(paragraph)");
                    paragraph.setSpacingBefore(5);
                    document.add(paragraph);
                }
            }

            // Cleanup now that the lines are finished
            if (pre || code) {
                PdfPTable codeTable = new PdfPTable(1);
                codeTable.setWidthPercentage(100);
                codeTable.setSpacingBefore(10);
                PdfPCell codeCell = new PdfPCell(codeParagraph);
                codeCell.setPadding(20);
                codeCell.setBorderColor(new Color(100, 100, 100));
                codeCell.setBackgroundColor(new Color(200, 200, 200));
                //        codeCell.setNoWrap(true);
                codeTable.addCell(codeCell);
                LOG.debug("document.add(codeTable)");
                document.add(codeTable);
            }
            if (unorderedParents != null) {
                if (cell != null) {
                    LOG.debug("phrase.add((List) unorderedParents.get(0))");
                    cell.addElement((List) unorderedParents.get(0));
                } else {
                    LOG.debug("document.add((List) unorderedParents.get(0))");
                    document.add((List) unorderedParents.get(0));
                }
            }
            in.close();
        } catch (Exception e) {
            LOG.error("parseContent", e);
        }
        return true;
    }

    protected static int parseHCount(String line, String id) {
        int count = 0;
        for (int i = 0; i < line.length(); i++) {
            char c = line.charAt(i);
            if (id.equals(String.valueOf(c))) {
                ++count;
            } else {
                return count;
            }
        }
        return 1;
    }

    private static String parseTable(WikiPDFContext context, Wiki wiki, String line, Document document,
            Connection db, ArrayList<Integer> wikiListTodo, ArrayList<Integer> wikiListDone, BufferedReader in)
            throws Exception {
        if (line == null) {
            return null;
        }
        PdfPTable pdfTable = null;
        int columnCount = 0;
        int rowCount = 0;

        // Keep track of the table's custom styles
        HashMap<Integer, String> cStyle = new HashMap<Integer, String>();

        while (line != null && (line.startsWith("|") || line.startsWith("!"))) {

            // Build a complete line
            String lineToParse = line;
            while (!line.endsWith("|")) {
                line = in.readLine();
                if (line == null) {
                    // there is an error in the line to process
                    return null;
                }
                if (line.startsWith("!")) {
                    lineToParse += CRLF + line.substring(1);
                }
            }
            line = lineToParse;

            // Determine if the row can output
            boolean canOutput = true;

            ++rowCount;

            String cellType = null;
            Scanner sc = null;
            if (line.startsWith("||") && line.endsWith("||")) {
                cellType = "th";
                sc = new Scanner(line).useDelimiter("[|][|]");
                //        sc = new Scanner(line.substring(2, line.length() - 2)).useDelimiter("[|][|]");
            } else if (line.startsWith("|")) {
                cellType = "td";
                sc = new Scanner(line.substring(1, line.length() - 1)).useDelimiter("\\|(?=[^\\]]*(?:\\[|$))");
            }

            if (sc != null) {

                if (rowCount == 1) {
                    // Count the columns, get the specified widths too...
                    while (sc.hasNext()) {
                        ++columnCount;
                        sc.next();
                    }
                    // Reset the scanner now that the columns have been counted
                    if (line.startsWith("||") && line.endsWith("||")) {
                        sc = new Scanner(line).useDelimiter("[|][|]");
                    } else if (line.startsWith("|")) {
                        sc = new Scanner(line.substring(1, line.length() - 1))
                                .useDelimiter("\\|(?=[^\\]]*(?:\\[|$))");
                    }

                    // Start the table
                    pdfTable = new PdfPTable(columnCount);
                    //pdfTable.setWidthPercentage(100);
                    pdfTable.setHorizontalAlignment(Element.ALIGN_LEFT);
                    pdfTable.setSpacingBefore(10);
                    pdfTable.setWidthPercentage(100);
                    pdfTable.setKeepTogether(true);
                }

                // Determine the column span
                int colSpan = 1;
                // Determine the cell being output
                int cellCount = 0;

                while (sc.hasNext()) {
                    String cellData = sc.next();
                    if (cellData.length() == 0) {
                        ++colSpan;
                        continue;
                    }

                    // Track the cell count being output
                    ++cellCount;

                    if (rowCount == 1) {
                        // Parse and validate the style input
                        LOG.debug("Checking style value: " + cellData);
                        if (cellData.startsWith("{") && cellData.endsWith("}")) {
                            String[] style = cellData.substring(1, cellData.length() - 1).split(":");
                            String attribute = style[0].trim();
                            String value = style[1].trim();
                            // Determine the width of each column and store it
                            if ("width".equals(attribute)) {
                                // Validate the width style
                                if (StringUtils.hasAllowedOnly("0123456789%.", value)) {
                                    cStyle.put(cellCount, attribute + ": " + value + ";");
                                }
                            } else {
                                LOG.debug("Unsupported style: " + cellData);
                            }
                            canOutput = false;
                        }
                    }

                    // Output the header
                    if (canOutput) {

                        PdfPCell cell = new PdfPCell();
                        cell.setPadding(10);
                        cell.setBorderColor(new Color(100, 100, 100));
                        if ("th".equals(cellType)) {
                            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
                            cell.setBackgroundColor(new Color(0xC0, 0xC0, 0xC0));
                        }
                        if (colSpan > 1) {
                            cell.setColspan(colSpan);
                        }

                        // Output the data
                        if (" ".equals(cellData) || "".equals(cellData)) {
                            // Put a blank space in blank cells for output consistency
                            cell.addElement(new Chunk(" "));
                            LOG.debug("   OUTPUTTING A BLANK");
                        } else {
                            // Output the cell as a complete wiki
                            float cellWidth = (100.0f / columnCount);
                            parseContent(context, wiki, cellData, document, cell, db, wikiListTodo, wikiListDone,
                                    cellWidth);
                            LOG.debug("   OUTPUTTING CONTENT");
                        }
                        pdfTable.addCell(cell);
                    }
                }
            }
            // read another line to see if it's part of the table
            line = in.readLine();
        }
        if (pdfTable != null) {
            LOG.debug("document.add(pdfTable)");
            document.add(pdfTable);
            //          document.add(Chunk.NEWLINE);
        }
        return line;
    }

    protected static String parseForm(WikiPDFContext context, Connection db, BufferedReader in, String line,
            Document document, PdfPCell cell) throws Exception {
        if (line == null) {
            return line;
        }
        CustomForm form = WikiToHTMLUtils.retrieveForm(in, line);
        LOG.debug("parseForm");
        for (CustomFormGroup group : form) {
            LOG.debug(" group...");
            // Start the table

            PdfPTable pdfTable = new PdfPTable(2);
            pdfTable.setHorizontalAlignment(Element.ALIGN_LEFT);
            pdfTable.setSpacingBefore(10);
            //      pdfTable.setWidthPercentage(100);
            pdfTable.setKeepTogether(true);

            if (group.getDisplay() && StringUtils.hasText(group.getName())) {
                // output the 1st row with a colspan of 2
                if (StringUtils.hasText(group.getName())) {
                    Paragraph groupParagraph = new Paragraph(group.getName());
                    PdfPCell groupCell = new PdfPCell(groupParagraph);
                    groupCell.setHorizontalAlignment(Element.ALIGN_CENTER);
                    groupCell.setColspan(2);
                    groupCell.setPadding(20);
                    groupCell.setBorderColor(new Color(100, 100, 100));
                    groupCell.setBackgroundColor(new Color(200, 200, 200));
                    groupCell.setNoWrap(true);
                    pdfTable.addCell(groupCell);
                }
            }
            for (CustomFormField field : group) {
                LOG.debug("  field...");
                if (field.hasValue()) {
                    // output the row (2 columns: label, value)
                    Paragraph fieldLabelParagraph = new Paragraph(field.getLabel());
                    PdfPCell fieldLabelCell = new PdfPCell(fieldLabelParagraph);
                    fieldLabelCell.setPadding(20);
                    fieldLabelCell.setBorderColor(new Color(100, 100, 100));
                    //          fieldLabelCell.setNoWrap(true);
                    pdfTable.addCell(fieldLabelCell);

                    Paragraph fieldValueParagraph = new Paragraph(getFieldValue(context, field));
                    PdfPCell fieldValueCell = new PdfPCell(fieldValueParagraph);
                    fieldValueCell.setPadding(20);
                    fieldValueCell.setBorderColor(new Color(100, 100, 100));
                    //          fieldValueCell.setNoWrap(true);
                    pdfTable.addCell(fieldValueCell);
                }
            }
            LOG.debug("document.add(pdfTable)");
            document.add(pdfTable);

        }
        return null;
    }

    private static boolean parseLine(WikiPDFContext context, String line, Paragraph main, Connection db,
            ArrayList<Integer> wikiListTodo, float cellWidth, PdfPCell cell) throws Exception {

        LOG.debug("PARSING LINE: " + line);

        // Context Objects
        Project project = context.getProject();
        WikiExportBean exportBean = context.getExportBean();
        HashMap<String, ImageInfo> imageList = context.getImageList();
        String fileLibrary = context.getFileLibrary();

        boolean needsCRLF = true;
        boolean bold = false;
        boolean italic = false;
        boolean bolditalic = false;
        StringBuffer subject = new StringBuffer();
        StringBuffer data = new StringBuffer();
        int linkL = 0;
        int linkR = 0;
        int attr = 0;
        int underlineAttr = 0;

        // parse characters
        for (int i = 0; i < line.length(); i++) {
            char c1 = line.charAt(i);
            String c = String.valueOf(c1);
            // False attr/links
            if (!"'".equals(c) && attr == 1) {
                data.append("'").append(c);
                attr = 0;
                continue;
            }
            if (!"_".equals(c) && underlineAttr == 1) {
                data.append("_").append(c);
                underlineAttr = 0;
                continue;
            }
            if (!"[".equals(c) && linkL == 1) {
                data.append("[").append(c);
                linkL = 0;
                continue;
            }
            if (!"]".equals(c) && linkR == 1) {
                data.append("]").append(c);
                linkR = 0;
                continue;
            }
            // Links
            if ("[".equals(c)) {
                ++linkL;
                continue;
            }
            if ("]".equals(c)) {
                ++linkR;
                if (linkL == 2 && linkR == 2) {
                    LOG.debug("main.add(new Chunk(data.toString()))");
                    main.add(new Chunk(data.toString()));
                    data.setLength(0);
                    // Different type of links...
                    String link = subject.toString().trim();
                    if (link.startsWith("Image:") || link.startsWith("image:")) {
                        // @note From WikiImageLink.java
                        String image = link.substring(6);
                        String title = null;
                        int frame = -1;
                        int thumbnail = -1;
                        int left = -1;
                        int right = -1;
                        int center = -1;
                        int none = -1;
                        int imageLink = -1;
                        int alt = -1;
                        if (image.indexOf("|") > 0) {
                            // the image is first
                            image = image.substring(0, image.indexOf("|"));
                            // any directives are next
                            frame = link.indexOf("|frame");
                            thumbnail = link.indexOf("|thumb");
                            left = link.indexOf("|left");
                            right = link.indexOf("|right");
                            center = link.indexOf("|center");
                            none = link.indexOf("|none");
                            imageLink = link.indexOf("|link=");
                            alt = link.indexOf("|alt=");
                            // the optional caption is last
                            int last = link.lastIndexOf("|");
                            if (last > frame && last > thumbnail && last > left && last > right && last > center
                                    && last > none && last > imageLink && last > alt) {
                                title = link.substring(last + 1);
                            }
                        }

                        //A picture, including alternate text:
                        //[[Image:Wiki.png|The logo for this Wiki]]

                        //You can put the image in a frame with a caption:
                        //[[Image:Wiki.png|frame|The logo for this Wiki]]

                        // Access some image details
                        int width = 0;
                        int height = 0;
                        ImageInfo imageInfo = imageList.get(image + (thumbnail > -1 ? "-TH" : ""));
                        if (imageInfo != null) {
                            width = imageInfo.getWidth();
                            height = imageInfo.getHeight();
                        }

                        // Alt
                        String altText = null;
                        if (alt > -1) {
                            int startIndex = alt + 4;
                            int endIndex = link.indexOf("|", startIndex);
                            if (endIndex == -1) {
                                endIndex = link.length();
                            }
                            altText = link.substring(startIndex, endIndex);
                        }

                        // Looks like the image needs a link (which is always last)
                        String url = null;
                        if (imageLink > -1) {
                            // Get the entered link
                            int startIndex = imageLink + 6;
                            int endIndex = link.length();
                            String href = link.substring(startIndex, endIndex);

                            // Treat as a wikiLink to validate and to create a proper url
                            WikiLink wikiLink = new WikiLink(project.getId(),
                                    (altText != null ? href + " " + altText : href));
                            url = wikiLink.getUrl("");
                            if (!url.startsWith("http://") && !url.startsWith("https://")) {
                                // @todo Use a local link
                                // @todo Use an external link
                            }
                        }

                        // Determine if local or external image
                        if ((image.startsWith("http://") || image.startsWith("https://"))) {
                            // retrieve external image
                            String imageUrl = null;
                            try {
                                URL imageURL = new URL(image);
                                imageUrl = image;
                            } catch (Exception e) {

                            }
                        } else {
                            // local image
                            try {
                                // @todo image alignment and links
                                Image thisImage = Image.getInstance(
                                        getImageFilename(db, fileLibrary, project, image, (thumbnail > -1)));

                                LOG.debug("Drawing image for area: " + cellWidth);

                                if (cellWidth > 0f) {
                                    LOG.debug(" Image is embedded in cell");
                                    // Guess the size of the cell
                                    float cellPixels = (500f * (cellWidth / 100f));
                                    if (cellPixels > 0f && (float) width > cellPixels) {
                                        // Shrink image to fit the cell
                                        LOG.debug(" Scaling to fit");
                                        thisImage.scaleToFit(cellPixels, 500f);
                                    } else {
                                        // Align image to left instead of scaling it to fit
                                        thisImage.setAlignment(Image.LEFT);
                                    }
                                    LOG.debug("cell.addElement(thisImage)");
                                    cell.addElement(thisImage);
                                } else {
                                    LOG.debug(" Image is inline");
                                    if (width > 500) {
                                        LOG.debug(" Scaling to fit");
                                        thisImage.scaleToFit(500f, 500f);
                                    }
                                    LOG.debug("main.add(thisImage)");
                                    main.add(thisImage);
                                }

                                //                thisImage.setAlignment();
                                //                thisImage.Alignment = Image.TEXTWRAP | Image.ALIGN_RIGHT;

                                //                main.add(thisImage);
                            } catch (FileNotFoundException fnfe) {
                                LOG.warn("WikiPDFUtils-> Image was not found in the FileLibrary ("
                                        + getImageFilename(db, fileLibrary, project, image, (thumbnail > -1))
                                        + ")... will continue.");
                            }
                        }

                        /*
                        if (frame > -1 || thumbnail > -1) {
                          sb.append("</div>");
                          sb.append("<div id=\"caption\" style=\"margin-bottom: 5px; margin-left: 5px; margin-right: 5px; text-align: left;\">");
                        }
                        if (thumbnail > -1) {
                          sb.append("<div style=\"float:right\"><a target=\"_blank\" href=\"ProjectManagementWiki.do?command=Img&pid=" + wiki.getProjectId() + "&subject=" + StringUtils.replace(StringUtils.jsEscape(image), "%20", "+") + "\"><img src=\"images/magnify-clip.png\" width=\"15\" height=\"11\" alt=\"Enlarge\" border=\"0\" /></a></div>");
                        }
                        if (frame > -1 || thumbnail > -1) {
                          if (title != null) {
                            sb.append(StringUtils.toHtml(title));
                          }
                          sb.append(
                              "  </div>\n" +
                                  "</div>");
                        }
                        */
                        /*
                        if (none > -1) {
                          sb.append("<br clear=\"all\">");
                        }
                        */
                        if (i + 1 == line.length() && (right > -1 || left > -1) || none > -1) {
                            needsCRLF = false;
                        }
                    } else {
                        // This is most likely a Wiki link
                        String title = subject.toString().trim();
                        if (link.indexOf("|") > 0) {
                            link = link.substring(0, link.indexOf("|")).trim();
                            title = title.substring(title.indexOf("|") + 1);
                        }
                        if (link.indexOf("http://") > -1 || link.indexOf("https://") > -1) {
                            String label = link;
                            if (link.indexOf(" ") > 0) {
                                label = link.substring(link.indexOf(" ") + 1);
                                link = link.substring(0, link.indexOf(" "));
                            }
                            Anchor anchor1 = new Anchor(label, FontFactory.getFont(FontFactory.HELVETICA, 12,
                                    Font.UNDERLINE, new Color(0, 0, 255)));
                            anchor1.setReference(link);
                            anchor1.setName("top");
                            main.add(anchor1);
                        } else {
                            // Place a wiki link
                            if (exportBean.getFollowLinks()) {
                                // See if target link exists before creating a link to it
                                int linkedWikiId = -1;
                                if (StringUtils.hasText(title) && !title.startsWith("|")) {
                                    Wiki subwiki = WikiList.queryBySubject(db, title, project.getId());
                                    if (subwiki.getId() > -1) {
                                        linkedWikiId = subwiki.getId();
                                    }
                                }
                                // Display the linked item
                                if (linkedWikiId > -1) {
                                    // Display as an anchor
                                    Anchor linkToWiki = new Anchor(title, FontFactory.getFont(FontFactory.HELVETICA,
                                            12, Font.NORMAL, new Color(0, 0, 255)));
                                    linkToWiki.setReference("#" + title.toLowerCase());
                                    LOG.debug("Link to: #" + title.toLowerCase());
                                    main.add(linkToWiki);
                                    LOG.debug(" main.add(linkToWiki)");
                                    //                  main.add(new Chunk(title, FontFactory.getFont(FontFactory.HELVETICA, 12, Font.NORMAL, new Color(0, 0, 255))).setLocalGoto(link));
                                    // Add this wiki to the to do list...
                                    if (!wikiListTodo.contains(linkedWikiId)) {
                                        wikiListTodo.add(linkedWikiId);
                                    }
                                } else {
                                    // Display without the link
                                    main.add(new Chunk(title, FontFactory.getFont(FontFactory.HELVETICA, 12,
                                            Font.NORMAL, new Color(0, 0, 255))));
                                }
                            } else {
                                // Not following links, so display... perhaps as an external link someday
                                main.add(new Chunk(title, FontFactory.getFont(FontFactory.HELVETICA, 12,
                                        Font.NORMAL, new Color(0, 0, 255))));
                            }
                        }
                    }
                    subject.setLength(0);
                    linkL = 0;
                    linkR = 0;
                }
                continue;
            }
            if (!"[".equals(c) && linkL == 2 && !"]".equals(c)) {
                subject.append(c);
                continue;
            }
            // Attribute properties
            if ("'".equals(c)) {
                ++attr;
                continue;
            }
            if (!"'".equals(c) && attr > 1) {
                if (attr == 2) {
                    if (!italic) {
                        main.add(new Chunk(data.toString()));
                        data.setLength(0);
                        data.append(c);
                        italic = true;
                    } else {
                        data.append(c);
                        main.add(new Chunk(data.toString(),
                                FontFactory.getFont(FontFactory.HELVETICA, 12, Font.ITALIC, new Color(0, 0, 0))));
                        data.setLength(0);
                        italic = false;
                    }
                    attr = 0;
                    continue;
                }
                if (attr == 3) {
                    if (!bold) {
                        main.add(new Chunk(data.toString()));
                        data.setLength(0);
                        data.append(c);
                        bold = true;
                    } else {
                        data.append(c);
                        main.add(new Chunk(data.toString(),
                                FontFactory.getFont(FontFactory.HELVETICA, 12, Font.BOLD, new Color(0, 0, 0))));
                        data.setLength(0);
                        bold = false;
                    }
                    attr = 0;
                    continue;
                }
                if (attr == 5) {
                    if (!bolditalic) {
                        main.add(new Chunk(data.toString()));
                        data.setLength(0);
                        data.append(c);
                        bolditalic = true;
                    } else {
                        data.append(c);
                        main.add(new Chunk(data.toString(), FontFactory.getFont(FontFactory.HELVETICA, 12,
                                Font.BOLDITALIC, new Color(0, 0, 0))));
                        data.setLength(0);
                        bolditalic = false;
                    }
                    attr = 0;
                    continue;
                }
            }
            data.append(c);
        }
        for (int x = 0; x < linkR; x++) {
            data.append("]");
        }
        for (int x = 0; x < linkL; x++) {
            data.append("[");
        }
        if (attr == 1) {
            data.append("'");
        }
        if (italic) {
            main.add(new Chunk(data.toString(),
                    FontFactory.getFont(FontFactory.HELVETICA, 12, Font.ITALIC, new Color(0, 0, 0))));
        } else if (bold) {
            main.add(new Chunk(data.toString(),
                    FontFactory.getFont(FontFactory.HELVETICA, 12, Font.BOLD, new Color(0, 0, 0))));
        } else if (bolditalic) {
            main.add(new Chunk(data.toString(),
                    FontFactory.getFont(FontFactory.HELVETICA, 12, Font.BOLDITALIC, new Color(0, 0, 0))));
        } else {
            main.add(new Chunk(data.toString()));
        }
        data.setLength(0);
        return needsCRLF;
    }

    public static String getFieldValue(WikiPDFContext context, CustomFormField field) {
        // Return output based on type
        switch (field.getType()) {
        case CustomFormField.TEXTAREA:
            return StringUtils.replace(field.getValue(), "^", CRLF);
        case CustomFormField.SELECT:
            return field.getValue();
        case CustomFormField.CHECKBOX:
            if ("true".equals(field.getValue())) {
                return "Yes";
            } else {
                return "No";
            }
        case CustomFormField.CALENDAR:
            String calendarValue = field.getValue();
            if (StringUtils.hasText(calendarValue)) {
                try {
                    String convertedDate = DateUtils.getUserToServerDateTimeString(null, DateFormat.SHORT,
                            DateFormat.LONG, field.getValue());
                    Timestamp timestamp = DatabaseUtils.parseTimestamp(convertedDate);
                    Locale locale = Locale.getDefault();
                    int dateFormat = DateFormat.SHORT;
                    SimpleDateFormat dateFormatter = (SimpleDateFormat) SimpleDateFormat.getDateInstance(dateFormat,
                            locale);
                    calendarValue = dateFormatter.format(timestamp);
                } catch (Exception e) {
                    LOG.error(e);
                }
            }
            return calendarValue;
        case CustomFormField.PERCENT:
            return field.getValue() + "%";
        case CustomFormField.INTEGER:
            // Determine the value to display in the field
            String integerValue = StringUtils.toHtmlValue(field.getValue());
            if (StringUtils.hasText(integerValue)) {
                try {
                    NumberFormat formatter = NumberFormat.getInstance();
                    integerValue = formatter.format(StringUtils.getIntegerNumber(field.getValue()));
                } catch (Exception e) {
                    LOG.warn("Could not integer format: " + field.getValue());
                }
            }
            return integerValue;
        case CustomFormField.FLOAT:
            // Determine the value to display in the field
            String decimalValue = field.getValue();
            if (StringUtils.hasText(decimalValue)) {
                try {
                    NumberFormat formatter = NumberFormat.getInstance();
                    decimalValue = formatter.format(StringUtils.getDoubleNumber(field.getValue()));
                } catch (Exception e) {
                    LOG.warn("Could not decimal format: " + field.getValue());
                }
            }
            return decimalValue;
        case CustomFormField.CURRENCY:
            // Use a currency for formatting
            String currencyCode = field.getValueCurrency();
            if (currencyCode == null) {
                currencyCode = field.getCurrency();
            }
            if (!StringUtils.hasText(currencyCode)) {
                currencyCode = "USD";
            }
            try {
                NumberFormat formatter = NumberFormat.getCurrencyInstance();
                if (currencyCode != null) {
                    Currency currency = Currency.getInstance(currencyCode);
                    formatter.setCurrency(currency);
                }
                return (formatter.format(StringUtils.getDoubleNumber(field.getValue())));
            } catch (Exception e) {
                LOG.error(e.getMessage());
            }
            return field.getValue();
        case CustomFormField.EMAIL:
            return field.getValue();
        case CustomFormField.PHONE:
            PhoneNumberBean phone = new PhoneNumberBean();
            phone.setNumber(field.getValue());
            PhoneNumberUtils.format(phone, Locale.getDefault());
            return phone.toString();
        case CustomFormField.URL:
            String value = StringUtils.toHtmlValue(field.getValue());
            if (StringUtils.hasText(value)) {
                if (!value.contains("://")) {
                    value = "http://" + value;
                }
                if (value.contains("://")) {
                    return value;
                }
            }
            return value;
        case CustomFormField.IMAGE:
            String image = field.getValue();
            if (StringUtils.hasText(image)) {
                return "WikiImage:" + image;
            }
            return image;
        default:
            return field.getValue();
        }
    }

    private static String getImageFilename(Connection db, String fileLibrary, Project project, String subject,
            boolean showThumbnail) throws SQLException {
        // Load the file for download
        FileItemList fileItemList = new FileItemList();
        fileItemList.setLinkModuleId(Constants.PROJECT_WIKI_FILES);
        fileItemList.setLinkItemId(project.getId());
        fileItemList.setFilename(subject);
        fileItemList.buildList(db);
        FileItem fileItem = null;
        if (fileItemList.size() > 0) {
            fileItem = (FileItem) fileItemList.get(0);
            return fileLibrary + "1" + fs + "projects" + fs + getDatePath(fileItem.getModified())
                    + (showThumbnail ? fileItem.getThumbnailFilename() : fileItem.getFilename());
        }
        return null;
    }

    protected static String getDatePath(java.sql.Timestamp fileDate) {
        SimpleDateFormat formatter1 = new SimpleDateFormat("yyyy");
        String datePathToUse1 = formatter1.format(fileDate);
        SimpleDateFormat formatter2 = new SimpleDateFormat("MMdd");
        String datePathToUse2 = formatter2.format(fileDate);
        return datePathToUse1 + fs + datePathToUse2 + fs;
    }

}