Search code examples
javacoding-stylejtextpane

How to output JTextPane styled contents to HTML, including custom style?


I currently use a JTextPane to allow users to add/edit text. It allows bold/italic/underline (and I plan to allow links in the future). It also allows users to drop buttons in, which are inserted as custom styles. The panel looks like:

< < image deleted > >

I would like to be able to save/load content as HTML - the content will be incorporated in a Flash swf. I am able to get content as HTML like so:

     public String getHTMLText(){

     ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
try{
   HTMLEditorKit hk = new HTMLEditorKit();
   hk.write(baos, this.getStyledDocument(), 0, this.getDocument().getLength());
   
      } catch (IOException e) {
       e.printStackTrace();
      } catch (BadLocationException e) {
       e.printStackTrace();
      }
      return baos.toString();
     }

This works fine if the JTextPane includes only bold/italic/underlined text. The output is overly complicated though. I want to be able to output my custom style as well, but when I try I'm getting this error:

 Exception occurred during event dispatching:
    java.lang.NullPointerException
 at javax.swing.text.html.MinimalHTMLWriter.writeAttributes(MinimalHTMLWriter.java:151)
 at javax.swing.text.html.MinimalHTMLWriter.writeStyles(MinimalHTMLWriter.java:256)
 at javax.swing.text.html.MinimalHTMLWriter.writeHeader(MinimalHTMLWriter.java:220)
 at javax.swing.text.html.MinimalHTMLWriter.write(MinimalHTMLWriter.java:122)
 at javax.swing.text.html.HTMLEditorKit.write(HTMLEditorKit.java:293)
 at javax.swing.text.DefaultEditorKit.write(DefaultEditorKit.java:152)
 at numeracy.referencetextpanel.NRefButtonTextArea.getHTMLText(NRefButtonTextArea.java:328)
 at numeracy.referencetextpanel.NInputPanelRefTextButton.getReferencedText(NInputPanelRefTextButton.java:59)
 at numeracy.referencetextpanel.NInputRefText.actionPerformed(NInputRefText.java:106)
 at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1995)

My custom style is inserted like so (cID is a string like "{0-0}"):

StyledDocument doc = this.getStyledDocument();

 NRefButton b = this.createRefButton(cID);

 Style style = doc.addStyle(cID, null); //prepare a style
 StyleConstants.setComponent(style, b);  

 doc.insertString(doc.getLength(), b.toString(), style); //insert button at index

The function createRefButton(String cID):

 private NRefButton createRefButton(String cID) {

  NRefButton b = new NRefButton(_equationButtons.get(cID).getText(), cID, _equationButtons.get(cID).isStruck()); //prepare a button

  return b;
 }

NRefButton overrides toString, which returns "{"+cID+"}".

What I would like to know: should I modify the way I insert the "Style" to get this error?

Is there a different/better way for me to get HTML from this JTextPane? All I want is HTML tags around bold/italic/underlined text, not overly complicated as if it is I'll then have to strip out the unnecessary HTML, and for the "style" to come out as button.toString().

Or should I implement my own toHTML() method, wrapping bold/italic/underlined text with the required tags? I don't mind doing this (in some ways I'd prefer it), but I don't know how to get the styles for the given JTextPane document. I suppose if I were able to get these styles, I could iterate through them, wrapping styled text in the appropriate tags?

Ideally, the pictured JTextPane content would be output as:

<html><p>This is some <b>styled</b> text. It is <u>incredible</u>.
<br/>
<br/>
Here we have a button that has been dropped in: {0-0}. These buttons are a <b><i>required part of this project.</i></b>

I want to be able to read the output HTML into the JTextPane as well - again I don't mind writing my own fromHTML() method for this, but I need to be able to get HTML out first.

Thank you for taking the time to read my question.


Solution

  • After reading:

    I wrote my own HTML exporter:

      package com.HTMLExport;
    
        import javax.swing.text.AbstractDocument;
        import javax.swing.text.AttributeSet;
        import javax.swing.text.BadLocationException;
        import javax.swing.text.Element;
        import javax.swing.text.ElementIterator;
        import javax.swing.text.StyleConstants;
        import javax.swing.text.StyledDocument;
    
        public class NHTMLWriter {
    
         private StyledDocument _sd;
         private ElementIterator _it;
    
         protected static final char NEWLINE = '\n';
    
         public NHTMLWriter(StyledDocument doc) {
          _sd = doc;
          _it = new ElementIterator(doc.getDefaultRootElement());
         }
    
         public String getHTML(){
          return "<html>" + this.getBody() + "</html>";
         }
    
         protected String getBody() {
          /*
               This will be a section element for a styled document.
               We represent this element in HTML as the body tags.
                  Therefore we ignore it.
           */
          _it.current();
    
          Element next = null;
    
          String body = "<body>";
    
          while((next = _it.next()) != null) {
           if (this.isText(next)) {
            body += writeContent(next);
           }
           else if(next.getName().equals("component")){
            body += getText(next);  //this is where the custom component is output. 
           }
          }
          body += "</body>";
    
          return body;
         }
         /**
          * Returns true if the element is a text element.
          */
         protected boolean isText(Element elem) {
          return (elem.getName() == AbstractDocument.ContentElementName);
         }
         protected String writeContent(Element elem){
    
          AttributeSet attr = elem.getAttributes();
    
      String startTags = this.getStartTag(attr);
    
      String content = startTags + this.getText(elem) + this.getEndTag(startTags);
    
      return content;
         }
         /**
          * Writes out text
          */
         protected String text(Element elem){
          String contentStr = getText(elem);
          if ((contentStr.length() > 0) && (contentStr.charAt(contentStr.length()-1) == NEWLINE)) {
           contentStr = contentStr.substring(0, contentStr.length()-1) + "<br/>";
          }
          if (contentStr.length() > 0) {
           return contentStr;
          }
          return contentStr;
         }
    
         protected String getText(Element elem){
          try {
           return _sd.getText(elem.getStartOffset(), elem.getEndOffset() - elem.getStartOffset()).replaceAll(NEWLINE+"", "<br/>");
          } catch (BadLocationException e) {
           e.printStackTrace();
          }
          return "";
         }
         private String getEndTag(String startTags) {
    
      String[] startOrder = startTags.split("<");
    
      String tags = "";
    
      for(String s : startOrder){
       tags = "</" + s + tags;
      }
    
      return tags;
        }
         private String getStartTag(AttributeSet attr) {
    
          String tag = "";
    
          if(StyleConstants.isBold(attr)){
           tag += "<b>";
          }
          if(StyleConstants.isItalic(attr)){
           tag += "<i>";
          }
          if(StyleConstants.isUnderline(attr)){
           tag += "<u>";
          }
    
          return tag;
         }
        }
    

    Now I need to write code so that it can do the reverse: converts the output HTML to a StyledDocument.

    First, coffee.