AJAX to PDF Java howto p.2 (Server) – Mea Cup O' Jo
Skip to content


AJAX to PDF Java howto p.2 (Server)

Access part 1 here

Server processing

From this point on – this tutorial is specific to Java and Java tools I’m listing in “References” section
I choose to use Java servlet to process the request since there no markup that needs to be generated and no page to display. Hopefully outcome of the action is a file-save pop-up. You will need to provide missing dependencies – most importantly Apache FOP and css2xslfo jar. These in turn use XML parsers (Xalan, Xerces) and some other dependent packages so, hopefully you are using Maven or Ivy to manage your project.
I ended up using POST and prohibiting GET method so I can pass substantial number of chars without worrying that part of it will get truncated.
Here’s entire post method (this is proof-of-concept quality code)

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String content = request.getParameter("content");
        content = URLDecoder.decode(content, "UTF-8");
        String[] css = request.getParameter("css").split(",");
        String xhtml = buildXHTML(content, css, request.getRequestURL().toString());
        response.setContentType(MimeConstants.MIME_PDF);
        ByteArrayOutputStream outPdf = new ByteArrayOutputStream();
        try {
            generatePDF(xhtml, outPdf);
        } catch (Exception e) {
            logger.error("PDF generation failed", e);
        }
        byte[] file = outPdf.toByteArray();
        response.setContentLength(file.length);
        response.setHeader("Content-disposition", "attachment; filename=Speakerships.pdf");
        OutputStream out = response.getOutputStream();
        out.write(file);
        out.flush();
        out.close();
    }

Step-by-step:

  1. Extract parameters and decode content back into valid HTML
  2. Massage HTML into valid XHTML (JTidy)
  3. Prepare the request
  4. Run the conversion (PDF is generated here)
  5. Write response out

Preparing XHTML

  • Convert paths into URLs e.g. /foo/css/pdf.css into http://blah.com/foo/css/pdf.css. This is also needs to be done with images. I had not have much luck with providing a baseUrl setting but I agree that would be a better way
  • Run JTidy as a library to produce a valid XHTML
    private static final String XHTML_HEADER = "<html><head>";
    private static final String XHTML_FOOTER = "</body></html>";
    private static final String CSS_TEMPLATE = "<link type=\"text/css\" href=\"?\" rel=\"stylesheet\" />";

    private String buildXHTML(String content, String[] css, String path) throws IOException {
        StringBuilder sb = new StringBuilder(XHTML_HEADER);
        for (String link : css) {
            if (StringUtils.isNotEmpty(link)) {
                link = buildUrl(path, link);
                sb.append(CSS_TEMPLATE.replace("?", link)).append('\n');
            }
        }
        sb.append("").append(content).append(XHTML_FOOTER);
        // convert any relative/absolute paths to full URL
        String raw = sb.toString();
        String url = path.substring(0, path.indexOf('/', "http://".length() + 1) + 1);
        raw = raw.replaceAll("src=\"/", "src=\"" + url);
        // now tidy it up
        Tidy tidy = new Tidy();
        tidy.setXHTML(true);
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        tidy.parse(IOUtils.toInputStream(raw), os);
        String out = new String(os.toByteArray());
        return out;
    }

    /**
     * With given request URL and absolute or relative link constructs URL
     *
     * @param path
     * @param link
     * @return
     */
    private String buildUrl(String path, String link) {
        if (link.startsWith("/")) {
            // absolute location
            return path.substring(0, path.indexOf('/', "http://".length() + 1)) + link;
        } else if (link.startsWith("http://"))
            return link;
        } else {/* extend to handle relative URLs */}
        return null;
    }

PDF conversion

The most important thing to remember about final PDF conversion is that Apache FOP does not support floating elements. Here’s a conversion routine

    private void generatePDF(String xhtml, OutputStream os) throws CSSToXSLFOException,
            SAXException, IOException {
        convert(new ByteArrayInputStream(xhtml.getBytes()), os, MimeConstants.MIME_PDF);
    }

    /**
     * Uses css2xslfo and Apache FOP to generate PDF output. Do not pass in markup that contains
     * floated elements, FOP currently does not support these
     *
     * @param in
     * @param out
     * @param format
     * @throws CSSToXSLFOException
     * @throws SAXException
     * @throws IOException
     */
    private void convert(ByteArrayInputStream in, OutputStream out, String format)
            throws CSSToXSLFOException, SAXException, IOException {
        XMLReader parser = be.re.xml.sax.Util.getParser(null, false);
        XMLFilter parent = new ProtectEventHandlerFilter(true, true, parser);
        XMLFilter filter = new CSSToXSLFOFilter(null, null, Collections.EMPTY_MAP, parent, false);
        InputSource source = new InputSource(in);
        FopFactory factory = FopFactory.newInstance();
        factory.setStrictValidation(false);
        factory.setBreakIndentInheritanceOnReferenceAreaBoundary(true);
        FOUserAgent agent = factory.newFOUserAgent();
        filter.setContentHandler(factory.newFop(format, agent, out).getDefaultHandler());
        filter.parse(source);
    }

Css2XSLFLO actually provides 5 separate jars based on various XSL-FO packages. I picked css2fopnew.jar since it uses latest (and free) version of Apache FOP. This way I can avoid using a commercial product but get limited XSL-FO implementation (no floats).
convert() method is extracted from CSS2FOPNew#convert static method to add ability to turn FOP validation off/on

References

Posted in Web stuff.


2 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. bjp says

    Is anyone else finding that the code samples wont so much as compile?

Continuing the Discussion

  1. Mea Cup O’ Jo » AJAX to PDF Java howto linked to this post on January 27, 2009

    [...] Part 2 – process server request [...]



Some HTML is OK

or, reply to this post via trackback.