Articles Projects Tips Downloads Contacts About

HTMLEditorKit and Custom tags in the JEditorPane/JTextPane.
By Stanislav Lapitsky

Different browsers may support a bit different versions of HTML and add new tags but extending of the HTMLEditorKit's tag list isn't simple. I found no strict rules how to add my own tag and even when I searched in the internet I found a lot of questions like "how to add custom tag?" without answers. So I tried to implement the feature myself. The article shows results of my investigations.

The screenshot below illustrate how custom tag <button> in HTMLEditorKit implementation looks like:

The HTMLEditorKit provides HTML.UnknownTag for all the tags which aren't mentioned in DTD. By default the DTD is loaded from "html 32.bdtd" binary resource and all the instance and creation methods defined in ParserDelegator aren't accessible. Private or protected static so we cannot override the DTD creation. I used a tricky way to access ParserDelegator's dtd and add my own element inside. The element is based on DIV element but we can use another source.

I extended HTMLEditorKit and override getParser() method to return my own ParserDelegator. Here is the code:

    Parser defaultParser;
    protected Parser getParser() {
        if (defaultParser==null) {
            defaultParser=new MyParserDelegator();
        }

        return defaultParser;
    }

Then in the ParserDelegator I access the DTD field and add my own tag like this:

class MyParserDelegator extends ParserDelegator {
    public MyParserDelegator() {
        try {
            Field f=javax.swing.text.html.parser.ParserDelegator.class.getDeclaredField("dtd");
            f.setAccessible(true);
            DTD dtd=(DTD)f.get(null);
            javax.swing.text.html.parser.Element div=dtd.getElement("div");
            dtd.defineElement("button", div.getType(), true, true,div.getContent(),null, null,div.getAttributes());
 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

For JDK version 1.6_26 and later where structure of original ParserDelegator class was changed access to the "dtd" field a bit different.

    public MyParserDelegator() {
        try {
            //for JDK version later than 1.6_26
            Field f=javax.swing.text.html.parser.ParserDelegator.class.getDeclaredField("DTD_KEY");
            AppContext appContext = AppContext.getAppContext();
            f.setAccessible(true);
            Object dtd_key=f.get(null);
 
            DTD dtd = (DTD) appContext.get(dtd_key);
 
            javax.swing.text.html.parser.Element div=dtd.getElement("div");
            dtd.defineElement("button", div.getType(), true, true,div.getContent(),null, null,div.getAttributes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

The next step was HTMLDocument extension. The only thing I need in the Document is to replace HTMLDocument.HTMLReader with my own class extension. That was necessary to register my Tag. Registration provides action used to process the tag but default action defines inline view for UnknownTags. So I added this code to register my <button> custom tag:

class MyHTMLDocument extends HTMLDocument {
    public MyHTMLDocument(StyleSheet styles) {
        super(styles);
    }
 
    public HTMLEditorKit.ParserCallback getReader(int pos) {
        Object desc = getProperty(Document.StreamDescriptionProperty);
        if (desc instanceof URL) {
            setBase((URL)desc);
        }
        return new MyHTMLReader(pos);
    }
 
    class MyHTMLReader extends HTMLDocument.HTMLReader {

        public MyHTMLReader(int offset) {
            super(offset);
        }
        public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
            if (t.toString().equals("button")) {
                registerTag(t, new BlockAction());
            }
            super.handleStartTag(t, a, pos);
        }

    }
}

The last step was replacing HTMLFactory with my own ViewFactory implementation to provide proper views for my custom tags. In the create() method I check Element's name and if it's "button" create a ComponentView with a button. The text of the button is defined between <button> and </button> tags. The code is below:

    class MyHTMLFactory extends HTMLFactory implements ViewFactory {
        public MyHTMLFactory() {
            super();
        }
 
        public View create(Element element) {
            HTML.Tag kind = (HTML.Tag) (element.getAttributes().getAttribute(javax.swing.text.StyleConstants.NameAttribute));
 
            if (kind instanceof HTML.UnknownTag && element.getName().equals("button")) {
 
                return new ComponentView(element) {
                    protected Component createComponent() {
                        JButton button = new JButton("Button : text unknown");
 
                        try {
                            int start=getElement().getStartOffset();
                            int end=getElement().getEndOffset();
                            String text = getElement().getDocument().getText(start, end-start);
                            button.setText(text);
                        } catch (BadLocationException e) {
                            e.printStackTrace();
                        }
 
                        return button;
                    }
                };
 
            }

            return super.create(element);
        }
    }

If you need more information about custom tags implementation the custom_tag.jar library contains full source code and runnable example.

Back to Table of Content