Ruben Laguna's blog

Nov 15, 2009 - 4 minute read - block flying flyingsaucer java replacedelementfactory sauce xhtmlrenderer

How to handle custom tags using Flying Saucer

If you have a customized xhtml variant, like evernote ENML format and you want to render it with Flying Saucer R8 you must first make sure that the extra element (in ENML case, en-media) is defined as a block-level element. To do that you create your own NamespaceHandler and you make sure that you return "display: block;" for en-media in the implementation of NamespaceHandler.getNonCssStyling. (See ENMLNamespaceHandler.java below)

        ....
        XHTMLPanel panel = new XHTMLPanel();
        panel.setDocument(doc,"",new ENMLNamespaceHandler(new XhtmlNamespaceHandler()));
        ....
        class ENMLNamespaceHandler implements NamespaceHandler {
        ....
           public String getNonCssStyling(Element e) {
               String toReturn = delegate.getNonCssStyling(e);
               if ("en-media".equalsIgnoreCase(e.getNodeName())) {
                  toReturn = "display: block;";
               }
               return toReturn;
           }
        ....
        }

With that you ensure that xhtmlrenderer will call ReplacedElementFactory.createReplacedElement for en-media. Now you must supply a ReplacedElementFactory that it’s able to process en-media. Usually the implementation of the createReplacedElement() involves creating a Swing JComponent, wrapping it in a SwingReplacedElement and adding it to the LayoutContext.getCanvas().

        ....
       ReplacedElementFactory cef = new ENMLReplacedElementFactory(new SwingReplacedElementFactory());
       XHTMLPanel panel = new XHTMLPanel();
       panel.getSharedContext().setReplacedElementFactory(cef);
       panel.setDocument(doc,"",new ENMLNamespaceHandler(new XhtmlNamespaceHandler()));
        ....

       public class ENMLReplacedElementFactory implements ReplacedElementFactory {
       ...
          public ReplacedElement createReplacedElement(LayoutContext context, BlockBox box,
                                                                                      UserAgentCallback uac, int cssWidth, int cssHeight) 
          {

             if ("en-media".equals(box.getElement().getNodeName())) {
                    JTextArea cc = new JTextArea();
                   cc.setText("Missing implementation for en-media");
                   cc.setSize(cc.getPreferredSize());

                   context.getCanvas().add(cc);

                   ReplacedElement toReturn = new SwingReplacedElement(cc) {
                     public boolean isRequiresInteractivePaint() {
                         return false;
                     }
                  };

                 return toReturn;
         }

        ReplacedElement toReturn = delegate.createReplacedElement(context, box, uac, cssWidth, cssHeight);
        return toReturn;
    }
....
}

References:

  1. Experiment: embedding Flash in Flying Saucer
  2. NamespaceHandler
  3. ReplacedElementFactory
  4. Re: custom layout support
  5. http://wiki.java.net/bin/view/Javadesktop/TheFlyingSaucerInProcessFAQ
ENMLNamespaceHandler.java:
package com.rubenlaguna.en4j.NoteContentViewModule;

import java.util.logging.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xhtmlrenderer.css.extend.StylesheetFactory;
import org.xhtmlrenderer.css.sheet.StylesheetInfo;
import org.xhtmlrenderer.extend.NamespaceHandler;

/**
 *
 * @author ecerulm
 */
class ENMLNamespaceHandler implements NamespaceHandler {
    private final NamespaceHandler delegate;

    public ENMLNamespaceHandler(NamespaceHandler h) {
        this.delegate = h;
    }

    public boolean isImageElement(Element e) {
        return delegate.isImageElement(e);
    }

    public boolean isFormElement(Element e) {
        return delegate.isFormElement(e);
    }

    public StylesheetInfo[] getStylesheets(Document doc) {
        return delegate.getStylesheets(doc);
    }

    public String getNonCssStyling(Element e) {
        String toReturn = delegate.getNonCssStyling(e);
        if ("en-media".equalsIgnoreCase(e.getNodeName())) {
            toReturn = "display: block;";
        }
        Logger.getLogger(ENMLNamespaceHandler.class.getName()).info("style for ("+e.getNodeName()+") is ("+toReturn+")");
        return toReturn;
    }

    public String getNamespace() {
        return delegate.getNamespace();
    }

    public String getLinkUri(Element e) {
        return delegate.getLinkUri(e);
    }

    public String getLang(Element e) {
        return delegate.getLang(e);
    }

    public String getImageSourceURI(Element e) {
        return delegate.getImageSourceURI(e);
    }

    public String getID(Element e) {
        return delegate.getID(e);
    }

    public String getElementStyling(Element e) {
        return delegate.getElementStyling(e);
    }

    public String getDocumentTitle(Document doc) {
        return delegate.getDocumentTitle(doc);
    }

    public StylesheetInfo getDefaultStylesheet(StylesheetFactory factory) {
        return delegate.getDefaultStylesheet(factory);
    }

    public String getClass(Element e) {
        return delegate.getClass(e);
    }

    public String getAttributeValue(Element e, String namespaceURI, String attrName) {
        return delegate.getAttributeValue(e, namespaceURI, attrName);
    }

    public String getAttributeValue(Element e, String attrName) {
        return delegate.getAttributeValue(e, attrName);
    }

    public String getAnchorName(Element e) {
        return delegate.getAnchorName(e);
    }

}

ENMLReplacedElementFactory.java:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.rubenlaguna.en4j.NoteContentViewModule;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import org.w3c.dom.Element;
import org.xhtmlrenderer.extend.ReplacedElement;
import org.xhtmlrenderer.extend.ReplacedElementFactory;
import org.xhtmlrenderer.extend.UserAgentCallback;
import org.xhtmlrenderer.layout.LayoutContext;
import org.xhtmlrenderer.render.BlockBox;
import org.xhtmlrenderer.simple.extend.FormSubmissionListener;
import org.xhtmlrenderer.swing.EmptyReplacedElement;
import org.xhtmlrenderer.swing.ImageReplacedElement;
import org.xhtmlrenderer.swing.SwingReplacedElement;
import org.xhtmlrenderer.swing.SwingReplacedElementFactory;
import org.xhtmlrenderer.util.ImageUtil;

/**
 *
 * @author ecerulm
 */
class ENMLReplacedElementFactory implements ReplacedElementFactory {

    private final SwingReplacedElementFactory delegate;
    private final Logger LOG = Logger.getLogger(ENMLReplacedElementFactory.class.getName());

    public ENMLReplacedElementFactory(SwingReplacedElementFactory delegate) {
        this.delegate = delegate;
    }

    public void setFormSubmissionListener(FormSubmissionListener fsl) {
        delegate.setFormSubmissionListener(fsl);
    }

    public void reset() {
        delegate.reset();
    }

    public void remove(Element e) {
        delegate.remove(e);
    }

    public ReplacedElement createReplacedElement(LayoutContext context, BlockBox box, UserAgentCallback uac, int cssWidth, int cssHeight) {
        ReplacedElement toReturn = null;


        Logger.getLogger(ENMLReplacedElementFactory.class.getName()).log(Level.INFO, "Element:" + box.getElement().getNodeName());
        Logger.getLogger(ENMLReplacedElementFactory.class.getName()).log(Level.INFO, "Element content:" + box.getElement().getNodeValue());

        if ("en-media".equals(box.getElement().getNodeName())) {
            //if("image/jpeg".equalsIgnoreCase(box.getElement().getAttribute("type"))){
            //
            //    toReturn = loadImage()
            //            return toReturn;
            //}
            toReturn = brokenImage(context, 100, 100);
        }

        if (null == toReturn) {
            toReturn = delegate.createReplacedElement(context, box, uac, cssWidth, cssHeight);
        }

        return toReturn;
    }

    private ReplacedElement brokenImage(LayoutContext context, int cssWidth, int cssHeight) {

        //TODO: add a real implementation that returns an image
        ReplacedElement toReturn = null;

        JTextArea cc = new JTextArea();
        cc.setText("Missing implementation for en-media");
        //cc.setPreferredSize(new Dimension(cssWidth, cssHeight));
        cc.setSize(cc.getPreferredSize());

        context.getCanvas().add(cc);

        toReturn = new SwingReplacedElement(cc) {

            public boolean isRequiresInteractivePaint() {
                return false;
            }
        };

        return toReturn;
    }
}