0

I'm implementing a simple HTML editor using JTextPane, HTMLDocument and HTMLEditorKit. The code looks as follows:

public class SimpleHTMLEditor extends JFrame {

    private static final long   serialVersionUID = 1L;

    private final JTextPane   textPane;

    private final HTMLEditorKit edtKit;

    private HTMLDocument  doc;

    public static void main(String[] args) {
        final SimpleHTMLEditor editor = new SimpleHTMLEditor();
        editor.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        editor.setVisible(true);
    }

    public SimpleHTMLEditor() {
        super("Simple HTML Editor");
        textPane = new JTextPane();
        edtKit = new HTMLEditorKit();
        textPane.setEditorKit(edtKit);
        doc = new HTMLDocument();
        textPane.setDocument(doc);

    final Container content = getContentPane();
        content.add(textPane, BorderLayout.CENTER);
        content.add(createToolBar(), BorderLayout.NORTH);
        setJMenuBar(createMenuBar());
        setSize(500, 240);
        textPane.requestFocusInWindow();
    }

    /**
     * Creates the toolbar with the combo box that allows for creation and
     * use of different conditions with their respective presentation styles.
     * @return The toolbar
     */
    private JToolBar createToolBar() {
        final JToolBar bar = new JToolBar();
        return bar;
    }

    /**
     * Creates the menu bar. It contains:
     * <li> Actions to read/write HTML file
     * <li> Action to display the HTML source in a popup window.
     * @return The menu bar
     */
    private JMenuBar createMenuBar() {
        final JMenuBar menubar = new JMenuBar();
        final JMenu mnuFile = new JMenu("File");
        menubar.add(mnuFile);
        final SaveAction actSave = new SaveAction();
        mnuFile.add(actSave);
        final LoadAction actLoad = new LoadAction();
        mnuFile.add(actLoad);
        final JMenuItem mnuPreview = new JMenuItem("Preview");
        menubar.add(mnuPreview);
        mnuPreview.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                final HTMLPreview previewer = new HTMLPreview(SimpleHTMLEditor.this,
                                                              getDocSource());
                previewer.setVisible(true);
            }
        });

    return menubar;
    }

    /**
     * Helper method to extract the HTML source code from the HTML document
     * @return The HTML source code
     */
    private String getDocSource() {
        final StringWriter sw = new StringWriter();
        try {
            edtKit.write(sw, doc, 0, doc.getLength());
        } catch (IOException | BadLocationException e1) {
            e1.printStackTrace();
        }
        try {
            sw.close();
        } catch (final IOException e1) {
            e1.printStackTrace();
        }
        return sw.toString();
    }

    class SaveAction extends AbstractAction {
        private static final long serialVersionUID = 1L;
        public SaveAction() {
            super("Save to File");
        }
        @Override
        public void actionPerformed(ActionEvent ev) {
            final JFileChooser chooser = new JFileChooser();
            if (chooser.showSaveDialog(SimpleHTMLEditor.this) != JFileChooser.APPROVE_OPTION)
                return;
            final File targetFile = chooser.getSelectedFile();
            if (targetFile == null)
                return;
            FileWriter writer = null;
            try {
                writer = new FileWriter(targetFile);
                textPane.write(writer);
            } catch (final IOException ex) {
                JOptionPane.showMessageDialog(SimpleHTMLEditor.this,
                                              "File Not Saved", "ERROR",
                                              JOptionPane.ERROR_MESSAGE);
            } finally {
                if (writer != null) {
                    try {
                        writer.close();
                    } catch (final IOException x) {
                    }
                }
            }
        }
    }

    class LoadAction extends AbstractAction {
        private static final long serialVersionUID = 1L;
        public LoadAction() {
            super("Load from File");
        }
        @Override
        public void actionPerformed(ActionEvent ev) {
            final JFileChooser chooser = new JFileChooser();
            if (chooser.showOpenDialog(SimpleHTMLEditor.this) != JFileChooser.APPROVE_OPTION)
                return;
            final File sourceFile = chooser.getSelectedFile();
            if (sourceFile == null)
                return;
            FileReader reader = null;
            try {
                reader = new FileReader(sourceFile);
                doc = (HTMLDocument)edtKit.createDefaultDocument();
                textPane.setDocument(doc);
                edtKit.read(reader,doc,0);
            } catch (final IOException ex) {
                JOptionPane.showMessageDialog(SimpleHTMLEditor.this,
                                              "File '" + sourceFile.getAbsolutePath() +
                                              "' Not Loaded", "ERROR",
                                              JOptionPane.ERROR_MESSAGE);

        } catch (final BadLocationException e) {
                e.printStackTrace();
            } finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (final IOException x) {
                    }
                }
            }
        }
    }
}

/**
 * Popup window for display of the current contents of the editor as HTML
 * source code.
 */
class HTMLPreview extends JDialog {

    private static final long serialVersionUID = 1L;

    public HTMLPreview(JFrame parent, String source) {
        super(parent, "HTML Source", true);

        final JPanel pp = new JPanel(new BorderLayout());
        pp.setBorder(new EmptyBorder(10, 10, 5, 10));

        final JTextArea srcTxtArea = new JTextArea(source, 20, 60);
        srcTxtArea.setFont(new Font("Courier", Font.PLAIN, 12));
        final JScrollPane sp = new JScrollPane(srcTxtArea);
        pp.add(sp, BorderLayout.CENTER);

        final JPanel p = new JPanel(new FlowLayout());
        final JPanel p1 = new JPanel(new GridLayout(1, 2, 10, 0));

        final JButton closeBtn = new JButton("Close");
        closeBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                dispose();
            }
        });
        p1.add(closeBtn);
        p.add(p1);
        pp.add(p, BorderLayout.SOUTH);
        getContentPane().add(pp, BorderLayout.CENTER);
        pack();
        setResizable(true);
        setLocationRelativeTo(parent);
    }
}

I noticed that when I load an HTML file that contains nested SPAN elements that the nesting is silently removed. Here's an example HTML file:

<html>
  <head>
  </head>
  <body>
    <p>
      <span title="tag:one">Outer span. <span title="tag:two">Inner span.</span> Outer span continued.</span>
    </p>
  </body>
</html>

After having loaded that file and selecting the "Preview" action in the toolbar I get a popup window that shows the HTML source code which looks as follows:

<html>
  <head>
  </head>
  <body>
    <p>
      <span title="tag:one">Outer span.</span> <span title="tag:two">Inner 
  span.</span> <span title="tag:one"> Outer span continued.</span>
    </p>
  </body>
</html>

As can be seen the outer SPAN element was silently split into two SPAN elements with the inner SPAN element placed between the two. It seems to me that the behavior shows one of the incompatibilities between the Java Swing components that realize the HTML editor and the HTML 4.x standard which as far as I understand allows for nested SPAN elements. My question now is: is there a (hopefully not too complex) way to workaround or overcome that limitation, i.e. make the HTML editor keep nested SPAN elements it encounters when reading HTML text?

Many thanks in advance, apatwork.

apatwork
  • 71
  • 1
  • 8
  • Note this [limitation](http://stackoverflow.com/a/6785121/230513). – trashgod Sep 23 '13 at 15:53
  • Thanks. Already feared that my problem stems from the missing yet support for all features of HTML 4. cannot workaround – apatwork Sep 23 '13 at 16:21
  • Thanks @trashgod. Already feared that my problem stems from the missing yet support for all features of HTML 4. My only idea until now is to somehow extend the HTMLEditorKit by a new element type, e.g. "MYSPAN". But I don't oversee how much effort that is and if it will possible at all to have nested instances of my new element type then. Could you please comment on that? And could you possibly provide some pointers to examples for that task? Thanks a lot in advance, apathome. – apatwork Sep 23 '13 at 16:44
  • Sorry, I never went very far down that path; would `Desktop#browse()` be an alternative? – trashgod Sep 23 '13 at 20:35
  • Oh, didn't know of that functionality yet. But to me it seems that this would only allow for viewing the HTML but not to make any changes in a WYSIWYG manner. So I'll now try to figure out how to add an additional user defined element and make HTMLEditorKit display it and (hopefully) allow for nesting instances of the new element. – apatwork Sep 24 '13 at 09:00
  • Not sure, but I've cited some relevant material below. – trashgod Sep 24 '13 at 09:39

1 Answers1

1

For viewing, consider leveraging the user's preferred browser via Desktop#browse(). For editing, SO contributor @stanislavl has written several relevant articles on the topic and answered extensively here. In particular, HTMLEditorKit and Custom tags in the JEditorPane/JTextPane may offer some insights on feasibility.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045