HTMLEditorKit Java Swing удаляет вложение элементов SPAN

Я реализую простой редактор HTML, используя JTextPane, HTMLDocumentа также HTMLEditorKit, Код выглядит следующим образом:

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);
    }
}

Я заметил, что при загрузке файла HTML, который содержит вложенные элементы SPAN, вложенность удаляется без вывода сообщений. Вот пример HTML-файла:

<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>

После загрузки этого файла и выбора действия "Предварительный просмотр" на панели инструментов я получаю всплывающее окно с исходным кодом HTML, которое выглядит следующим образом:

<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>

Как можно видеть, внешний элемент SPAN был незаметно разделен на два элемента SPAN с внутренним элементом SPAN, помещенным между ними. Мне кажется, что поведение демонстрирует одну из несовместимостей между компонентами Java Swing, которые реализуют редактор HTML и стандарт HTML 4.x, который, насколько я понимаю, допускает вложенные элементы SPAN. Теперь у меня вопрос: есть ли (надеюсь, не слишком сложный) способ обойти или преодолеть это ограничение, то есть заставить редактор HTML сохранять вложенные элементы SPAN, с которыми он сталкивается при чтении текста HTML?

Заранее большое спасибо, apatwork.

1 ответ

Для просмотра рассмотрите возможность использования предпочтительного браузера пользователя через Desktop#browse(), Для редактирования SO StanislavL написал несколько соответствующих статей по этой теме и подробно ответил здесь. В частности, HTMLEditorKit и пользовательские теги в JEditorPane/JTextPane могут предложить некоторую информацию о целесообразности.

Другие вопросы по тегам