JEditorPaneHTMLEditorKitParserTree树结构处理

wen java案例 2

本文目录导读:

JEditorPaneHTMLEditorKitParserTree树结构处理

  1. 核心问题定位
  2. 获取 HTMLDocument 中的 Element 树
  3. 可视化显示为 JTree
  4. 进阶:使用 SAX 解析器构建精确的 DOM 树
  5. 常见问题与解决方案
  6. 完整示例:交互式 HTML 树查看器
  7. 最佳实践建议

针对 JEditorPane 结合 HTMLEditorKit 解析 HTML 并构建文档树结构的问题,以下是深度分析与解决方案:

核心问题定位

JEditorPane 使用 HTMLEditorKit 解析 HTML 时,生成的内部文档结构是 HTMLDocument,其底层基于 Swing 的 View 体系(不是标准的 DOM 树),要获取树形结构,需要:

  • 解析阶段:获取 HTMLDocument 中的 Element 层次
  • 展示阶段:将 Element 树转换为可视化树控件(如 JTree

获取 HTMLDocument 中的 Element 树

import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.html.*;
import javax.swing.tree.*;
public class HTMLTreeBuilder {
    public static DefaultMutableTreeNode buildTreeFromHTML(String html) {
        JEditorPane editorPane = new JEditorPane();
        editorPane.setContentType("text/html");
        editorPane.setText(html);
        HTMLDocument doc = (HTMLDocument) editorPane.getDocument();
        Element rootElement = doc.getDefaultRootElement();
        return convertElementToTreeNode(rootElement);
    }
    private static DefaultMutableTreeNode convertElementToTreeNode(Element element) {
        String elementInfo = getElementInfo(element);
        DefaultMutableTreeNode node = new DefaultMutableTreeNode(elementInfo);
        for (int i = 0; i < element.getElementCount(); i++) {
            Element child = element.getElement(i);
            node.add(convertElementToTreeNode(child));
        }
        return node;
    }
    private static String getElementInfo(Element element) {
        AttributeSet attrs = element.getAttributes();
        Object name = attrs.getAttribute(StyleConstants.NameAttribute);
        String tagName = (name != null) ? name.toString() : "unknown";
        // 提取 ID/Class 等关键属性
        String id = (String) attrs.getAttribute(HTML.Attribute.ID);
        String cls = (String) attrs.getAttribute(HTML.Attribute.CLASS);
        StringBuilder sb = new StringBuilder(tagName);
        if (id != null) sb.append(" #").append(id);
        if (cls != null) sb.append(" .").append(cls);
        // 对于文本节点,显示前20个字符
        if (element instanceof AbstractDocument.AbstractElement) {
            try {
                String text = element.getDocument().getText(
                    element.getStartOffset(), 
                    Math.min(20, element.getEndOffset() - element.getStartOffset())
                );
                if (!text.trim().isEmpty()) {
                    sb.append(" [").append(text.trim()).append("...]");
                }
            } catch (BadLocationException e) {}
        }
        return sb.toString();
    }
}

可视化显示为 JTree

public class HTMLTreeViewer extends JFrame {
    public HTMLTreeViewer(String html) {
        setTitle("HTML Element Tree");
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        setSize(600, 400);
        DefaultMutableTreeNode root = HTMLTreeBuilder.buildTreeFromHTML(html);
        JTree tree = new JTree(root);
        tree.setRootVisible(true);
        // 自定义单元格渲染器,显示更友好
        tree.setCellRenderer(new DefaultTreeCellRenderer() {
            @Override
            public java.awt.Component getTreeCellRendererComponent(
                    JTree tree, Object value, boolean sel, boolean expanded,
                    boolean leaf, int row, boolean hasFocus) {
                super.getTreeCellRendererComponent(tree, value, sel, expanded, 
                                                  leaf, row, hasFocus);
                if (value instanceof DefaultMutableTreeNode) {
                    String text = value.toString();
                    // 根据标签类型设置不同图标(可选)
                    if (text.startsWith("html")) {
                        setIcon(UIManager.getIcon("FileView.computerIcon"));
                    } else if (text.startsWith("body")) {
                        setIcon(UIManager.getIcon("FileView.directoryIcon"));
                    } else if (text.startsWith("img")) {
                        setIcon(UIManager.getIcon("FileView.floppyDriveIcon"));
                    }
                }
                return this;
            }
        });
        add(new JScrollPane(tree));
        setLocationRelativeTo(null);
    }
    // 使用示例
    public static void main(String[] args) {
        String html = "<html><body><h1 id='title'>Hello</h1>" +
                      "<p class='content'>World</p>" +
                      "<div><span>Nested</span></div></body></html>";
        SwingUtilities.invokeLater(() -> {
            new HTMLTreeViewer(html).setVisible(true);
        });
    }
}

进阶:使用 SAX 解析器构建精确的 DOM 树

如果默认的 HTMLDocument 解析不够精确(例如缺少某些标签),可以改用 Swing HTML 解析器 的 SAX 风格 API:

import javax.swing.text.html.parser.*;
import javax.swing.text.html.HTMLEditorKit;
import java.io.*;
public class SAXHTMLTreeBuilder {
    public static DefaultMutableTreeNode buildPreciseTree(String html) throws Exception {
        // 使用 HTMLEditorKit 的解析器
        HTMLEditorKit kit = new HTMLEditorKit();
        Parser parser = new ParserDelegator();
        final DefaultMutableTreeNode root = new DefaultMutableTreeNode("HTML Root");
        final DefaultMutableTreeNode[] current = {root};
        parser.parse(new StringReader(html), new HTMLEditorKit.ParserCallback() {
            @Override
            public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
                DefaultMutableTreeNode node = new DefaultMutableTreeNode(
                    t.toString() + getAttributeString(a));
                current[0].add(node);
                current[0] = node; // 进入子节点
            }
            @Override
            public void handleEndTag(HTML.Tag t, int pos) {
                if (current[0].getParent() != null) {
                    current[0] = (DefaultMutableTreeNode) current[0].getParent();
                }
            }
            @Override
            public void handleText(char[] data, int pos) {
                String text = new String(data).trim();
                if (!text.isEmpty()) {
                    DefaultMutableTreeNode textNode = new DefaultMutableTreeNode(
                        "text: " + text.substring(0, Math.min(30, text.length())));
                    current[0].add(textNode);
                }
            }
            @Override
            public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
                DefaultMutableTreeNode node = new DefaultMutableTreeNode(
                    t.toString() + getAttributeString(a) + " (self-closing)");
                current[0].add(node);
            }
            private String getAttributeString(MutableAttributeSet a) {
                StringBuilder sb = new StringBuilder();
                Enumeration<?> names = a.getAttributeNames();
                while (names.hasMoreElements()) {
                    Object key = names.nextElement();
                    String value = a.getAttribute(key).toString();
                    sb.append(" ").append(key).append("=\"").append(value).append("\"");
                }
                return sb.toString();
            }
        }, true); // true 表示严格解析
        return root;
    }
}

常见问题与解决方案

1 中文乱码

// 设置字符编码
editorPane.setDocument(new HTMLDocument() {
    @Override
    public String getProperty(Object key) {
        if (key.equals("charset")) return "UTF-8";
        return super.getProperty(key);
    }
});

2 样式表丢失

// 自定义 StyleSheet 添加样式支持
StyleSheet ss = new StyleSheet();
ss.addRule("body { font-family: sans-serif; }");
HTMLDocument doc = new HTMLDocument(ss);
editorPane.setDocument(doc);

3 大文档性能优化

  • 使用 SwingWorker 在后台解析 HTML
  • 只在展开节点时加载子节点(延迟加载)
  • 限制显示深度(例如最多显示5层)

完整示例:交互式 HTML 树查看器

import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
public class InteractiveHTMLTreeViewer extends JFrame {
    private JEditorPane previewPane;
    private JTree tree;
    private JTextArea htmlInput;
    public InteractiveHTMLTreeViewer() {
        setTitle("Interactive HTML Tree Viewer");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(900, 600);
        setLayout(new BorderLayout());
        // 输入区域
        htmlInput = new JTextArea(10, 40);
        htmlInput.setText("<html><body><h1>Test</h1><p>Hello World</p></body></html>");
        // 按钮
        JButton parseBtn = new JButton("Parse & Build Tree");
        parseBtn.addActionListener(e -> buildTree());
        // 左侧:输入 + 控制
        JPanel leftPanel = new JPanel(new BorderLayout());
        leftPanel.add(new JScrollPane(htmlInput), BorderLayout.CENTER);
        leftPanel.add(parseBtn, BorderLayout.SOUTH);
        // 中间:树结构
        tree = new JTree();
        tree.getSelectionModel().addTreeSelectionListener(e -> {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) 
                tree.getLastSelectedPathComponent();
            if (node != null) {
                // 点击节点时在预览区显示对应 HTML 片段
                highlightElement(node);
            }
        });
        // 右侧:HTML 预览
        previewPane = new JEditorPane();
        previewPane.setContentType("text/html");
        previewPane.setEditable(false);
        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
            new JScrollPane(tree), new JScrollPane(previewPane));
        splitPane.setDividerLocation(300);
        add(leftPanel, BorderLayout.WEST);
        add(splitPane, BorderLayout.CENTER);
    }
    private void buildTree() {
        String html = htmlInput.getText();
        try {
            DefaultMutableTreeNode root = HTMLTreeBuilder.buildTreeFromHTML(html);
            tree.setModel(new DefaultTreeModel(root));
            previewPane.setText(html);
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(this, "Parse error: " + ex.getMessage());
        }
    }
    private void highlightElement(DefaultMutableTreeNode node) {
        // 可以根据节点信息在预览区高亮显示
        // 实际实现需要映射元素范围
    }
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            new InteractiveHTMLTreeViewer().setVisible(true);
        });
    }
}

最佳实践建议

  1. 区分解析模型HTMLDocument 的 Element 树 ≠ 标准 DOM 树,它合并了相邻文本节点
  2. 错误处理:对于不完整的 HTML,HTMLEditorKit 会自动修复,但可能导致树结构不一致
  3. 性能考量:对于超大型文档,考虑分段解析或使用虚拟树节点
  4. 属性访问:使用 HTML.Attribute 常量访问属性,避免硬编码字符串

通过以上方法,你可以有效地将 JEditorPane 解析的 HTML 转换为可视化树结构,用于调试、学习或文档结构分析场景。

抱歉,评论功能暂时关闭!