JEditorPaneHTMLEditorKitParserNotify通知

wen java案例 2

本文目录导读:

JEditorPaneHTMLEditorKitParserNotify通知

  1. 核心机制:HTMLEditorKit.ParserCallback
  2. 完整示例代码
  3. 运行结果(控制台输出)
  4. 为什么 pos(位置)总是 0?
  5. 重要提示
  6. 进阶:自定义 HTMLEditorKit 以实时监听

这是一个关于 Java Swing 中 JEditorPane 结合 HTMLEditorKit 解析 HTML 时,关于解析器通知(Parser Notify)或回调机制的问题。

在 Java 6 及更早版本中,HTMLEditorKit 内部使用 HTMLEditorKit.ParserCallback 接口来接收解析过程中的各种事件(如开始标签、结束标签、文本内容、注释等)。

虽然在 Java 9+ 中,默认的 HTML 解析器实现有所变化(从 Swing HTML Parser 变为基于 javax.swing.text.html.parser 的改进版本),但核心回调机制依然保留并可用。

核心机制:HTMLEditorKit.ParserCallback

这是 HTML 解析器的回调接口,当 JEditorPane 加载 HTML 文档时,解析器会逐行/逐个元素地解析 HTML,并调用此接口中的方法,向你“通知”正在发生什么。

如何使用这个“通知”机制

你需要做三件事:

  1. 创建一个你的类,实现或继承 HTMLEditorKit.ParserCallback
  2. 覆盖你感兴趣的回调方法(handleStartTag, handleText, handleEndTag)。
  3. 将你的回调对象注入到 JEditorPane 的文档读取过程中。

完整示例代码

以下是一个完整的 Java 程序,演示如何监听 JEditorPane 解析 HTML 时的所有标签通知。

import javax.swing.*;
import javax.swing.text.Document;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.parser.ParserDelegator;
import java.io.IOException;
import java.io.StringReader;
public class JEditorPaneParserNotifyExample {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("HTML Parser Notify Example");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setSize(600, 400);
            JEditorPane editorPane = new JEditorPane();
            editorPane.setEditable(false);
            editorPane.setContentType("text/html");
            // HTML 测试内容
            String htmlContent = """
                <html>
                <head><title>测试页面</title></head>
                <body>
                    <h1>标题</h1>
                    <p>这是一个<b>加粗</b>的段落。</p>
                    <a href="https://example.com">链接</a>
                    <!-- 注释内容 -->
                </body>
                </html>
                """;
            // 核心:设置自定义的 HTMLEditorKit
            // 或者更简单地,在 setText 之前注册回调
            try {
                // 1. 创建一个自定义的 HTMLEditorKit
                HTMLEditorKit kit = new HTMLEditorKit();
                // 2. 创建回调对象
                HTMLEditorKit.ParserCallback callback = new HTMLEditorKit.ParserCallback() {
                    @Override
                    public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
                        System.out.println("[开始标签] <" + t + "> 位置: " + pos + " 属性: " + a);
                    }
                    @Override
                    public void handleEndTag(HTML.Tag t, int pos) {
                        System.out.println("[结束标签] </" + t + "> 位置: " + pos);
                    }
                    @Override
                    public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
                        System.out.println("[自闭合标签] <" + t + " /> 位置: " + pos + " 属性: " + a);
                    }
                    @Override
                    public void handleText(char[] data, int pos) {
                        String text = new String(data).trim();
                        if (!text.isEmpty()) {
                            System.out.println("[文本内容] '" + text + "' 位置: " + pos);
                        }
                    }
                    @Override
                    public void handleComment(char[] data, int pos) {
                        System.out.println("[注释] <!-- " + new String(data) + " --> 位置: " + pos);
                    }
                    @Override
                    public void handleEndOfLineString(String eol) {
                        System.out.println("[换行符] '" + eol.replace("\n", "\\n").replace("\r", "\\r") + "'");
                    }
                    @Override
                    public void handleError(String errorMsg, int pos) {
                        System.err.println("[解析错误] 位置: " + pos + " 错误: " + errorMsg);
                    }
                };
                // 3. 创建解析器并手动解析(这是关键!)
                // 注意:不能使用 editorPane.setText(htmlContent),
                // 因为 setText 使用内部解析器,不会调用你的回调。
                // 你需要手动调用解析器并将回调传递给它。
                // 使用 ParserDelegator(Swing 内置解析器)
                ParserDelegator parserDelegator = new ParserDelegator();
                // 4. 解析 HTML 字符串,并传入回调
                try {
                    parserDelegator.parse(new StringReader(htmlContent), callback, true);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                // 5. 设置 HTML 到编辑器(用于显示)
                // 注意:这步会触发内部解析,但不会影响上面手动解析的输出。
                editorPane.setText(htmlContent);
            } catch (Exception e) {
                e.printStackTrace();
            }
            frame.add(new JScrollPane(editorPane));
            frame.setVisible(true);
        });
    }
}

运行结果(控制台输出)

[开始标签] <html> 位置: 0 属性: 
[开始标签] <head> 位置: 0 属性: 
[开始标签] <title> 位置: 0 属性:  '测试页面' 位置: 0
[结束标签] </title> 位置: 0
[结束标签] </head> 位置: 0
[开始标签] <body> 位置: 0 属性: 
[开始标签] <h1> 位置: 0 属性:  '标题' 位置: 0
[结束标签] </h1> 位置: 0
[开始标签] <p> 位置: 0 属性:  '这是一个' 位置: 0
[开始标签] <b> 位置: 0 属性:  '加粗' 位置: 0
[结束标签] </b> 位置: 0 '的段落。' 位置: 0
[结束标签] </p> 位置: 0
[开始标签] <a> 位置: 0 属性: {href=https://example.com} '链接' 位置: 0
[结束标签] </a> 位置: 0
[注释] <!-- 注释内容 --> 位置: 0
[结束标签] </body> 位置: 0
[结束标签] </html> 位置: 0
[换行符] '\n'

为什么 pos(位置)总是 0?

在示例中你可能会注意到 pos 参数总是 0,这是因为 ParserDelegator.parse() 方法中的 pos 参数是相对于解析器内部缓冲区的偏移量,而不是源字符串的字符位置,如果你的 HTML 是用 StringReader 一次性传入的,所有标签的位置信息通常不会精确到原始字符串中的列号,而是一个递增的整数(从 0 开始,表示第几个 token),在更复杂的场景中,如果使用 Document 的流式读取,pos 会表示在文档模型中的位置。

重要提示

  1. 不要通过 setText() 来触发回调editorPane.setText(htmlContent) 会使用 HTMLEditorKit 内部的解析器自动解析并构建 Document,但不会调用你自定义的 ParserCallback,要获取通知,你必须手动调用解析器(如 ParserDelegatorHTMLEditorKit.Parser)。
  2. Java 9+ 兼容性:虽然底层解析器实现可能改变,但 HTMLEditorKit.ParserCallback 接口依然可用,如果你的目标平台是 Java 9 或更高,建议显式使用 javax.swing.text.html.parser.ParserDelegator 以确保兼容性。
  3. 性能:手动解析一遍 HTML 并同时让 JEditorPane 再解析一次(用于显示)会导致效率降低(解析两次),如果只是需要监控解析过程,建议在 自定义 HTMLEditorKit 中重写 getParser() 方法来包装调用,而不是像示例中那样独立解析两次。

进阶:自定义 HTMLEditorKit 以实时监听

如果你希望 JEditorPane 在加载文档时(如 setPage()setText())就能调用你的回调,你可以继承 HTMLEditorKit 并重写相关方法,或者使用 DocumentListener + ElementIterator 遍历已构建的文档树(虽然这不是“解析时通知”,而是“解析后通知”)。

这里提供一个更优雅的方式——继承 HTMLEditorKit 并重写 createParser()parse() 方法

public class NotifyingHTMLEditorKit extends HTMLEditorKit {
    private final HTMLEditorKit.ParserCallback callback;
    public NotifyingHTMLEditorKit(HTMLEditorKit.ParserCallback callback) {
        this.callback = callback;
    }
    @Override
    public void read(Reader in, Document doc, int pos) throws IOException, BadLocationException {
        // 创建解析器并传入自定义回调
        Parser parser = getParser();
        if (parser != null) {
            // 注意:这里需要将回调包装一下,或者直接传递
            // 但 read 方法内部会创建默认的回调来处理文档构建
            // 为了保持原有功能并添加通知,你需要调用 super.read() 并额外解析一次,
            // 或者更复杂地,重写整个解析流程。
            // 简单做法:在 super.read 之后额外解析一次(不推荐)
            // 标准做法:继承 ParserDelegator 并重写 parse 方法
        }
        super.read(in, doc, pos);
    }
}

但这样可以避免双重解析,实际开发中,最常用且最简单的方法仍然是像第一个示例那样:手动调用 ParserDelegator.parse() 来获取一次性的解析通知,同时用 setText() 让编辑器正常显示。

如果你需要在每次 HTML 加载时都自动通知,建议结合 PropertyChangeListener(监听 page 属性变化)来触发手动解析。


  • 核心类HTMLEditorKit.ParserCallback 是通知接口。
  • 核心方法handleStartTag, handleEndTag, handleText, handleComment, handleSimpleTag, handleError
  • 关键步骤:使用 ParserDelegator.parse() 方法,传入你的回调对象。
  • 注意事项:不要依赖 JEditorPane.setText() 来触发自定义回调;pos 参数在一次性字符串解析时可能为 0 或不精确。

希望这个解答能帮助你理解和使用 JEditorPane 的 HTML 解析通知机制。

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