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.