如何设置 JavaFX WebView 的编码?

How can I set the encoding of a JavaFX WebView?

我在使用 JavaFX 的 WebView 时遇到编码问题。加载 UTF-8 编码文件时,特殊字符显示不正确(例如显示 ’ 而不是 )。这是一个 SSCCE:

WebViewTest.java

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import javafx.stage.Stage;

public class WebViewTest extends Application {

    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage stage) {
        WebView webView = new WebView();
        webView.getEngine().load(getClass().getResource("/test.html").toExternalForm());

        Scene scene = new Scene(webView, 500, 500);
        stage.setScene(scene);
        stage.setTitle("WebView Test");
        stage.show();
    }

}

test.html

<!DOCTYPE html>
<html>
  <body>
      <p>RIGHT SINGLE QUOTATION MARK: ’</p>
  </body>
</html>

file -bi test.html

的输出
src:$ file -bi test.html
text/plain; charset=utf-8

结果:

同样的事情发生在Windows使用Java17和最新的JavaFX(我使用Linux和Java8进行演示) .

我试过:

WebView 似乎忽略了文件编码,并使用 ISO-8859-1,除非在 HTML 中指定了字符集。

在写问题的时候,我发现了一个hacky解决方案:

webView.getEngine().getLoadWorker().stateProperty().addListener((o, oldState, newState) -> {
    if(newState == Worker.State.SUCCEEDED) {
        try {
            String newContent = new String(Files.readAllBytes(Paths.get(new URI(getClass().getResource("/test.html").toExternalForm()))), "UTF-8");
            webView.getEngine().executeScript("document.documentElement.innerHTML = '" + newContent.replace("'", "\'").replace("\n", "\n") + "'");
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
});

WebView 根据 HTML 文件或 HTTP header 确定编码。这是根据 w3c 规范,有关信息,请参阅:

正如您在问题中已经指出的那样,您可以在 HTML 文档中的 head 元素中声明字符编码,WebView 将拾取它:

<!DOCTYPE html>
<html lang="en"> 
<head>
<meta charset="utf-8"/>
...

但是,您在问题中还注意到您无法控制输入 HTML 文件以及它是否包含声明字符集所需的 header。

您还可以让 HTTP 协议使用适当的 header.

指定文件的编码
 Content-Type: text/html; charset=UTF-8

如果这样做,HTML 文件内容将由 WebView 正确地进行 UTF-8 解码,即使输入文件不包含字符集 header.

这是一个例子:

import com.sun.net.httpserver.*;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import javafx.stage.Stage;

import java.io.*;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Collectors;

public class WebViewTest extends Application {

    private static final String TEST_HTML = "test.html";

    private HttpServer server;

    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void init() throws Exception {
        server = HttpServer.create(new InetSocketAddress(8000), 0);
        server.createContext("/", new MyHandler());
        server.setExecutor(null); // creates a default executor
        server.start();
    }

    @Override
    public void start(Stage stage) {
        WebView webView = new WebView();
        webView.getEngine().load("http://localhost:8000/" + TEST_HTML);

        Scene scene = new Scene(webView, 500, 500);
        stage.setScene(scene);
        stage.setTitle("WebView Test");
        stage.show();
    }

    @Override
    public void stop() throws Exception {
        server.stop(0);
    }

    static class MyHandler implements HttpHandler {
        public void handle(HttpExchange httpExchange) {
            try {
                String path = httpExchange.getRequestURI().getPath().substring(1);  // strips leading slash from path, so resource lookup will be relative to this class, not the root.
                String testString = resourceAsString(path);
                System.out.println("testString = " + testString);
                if (testString != null) {
                    httpExchange.getResponseHeaders().put("Content-Type", List.of("text/html; charset=UTF-8"));
                    httpExchange.sendResponseHeaders(200, testString.getBytes(StandardCharsets.UTF_8).length);
                    try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(httpExchange.getResponseBody()))) {
                        writer.write(testString);
                        writer.flush();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } else {
                    System.out.println("Unable to find resource: " + path);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private String resourceAsString(String fileName) throws IOException {
            try (InputStream is = WebViewTest.class.getResourceAsStream(fileName)) {
                if (is == null) return null;
                try (InputStreamReader isr = new InputStreamReader(is);
                     BufferedReader reader = new BufferedReader(isr)) {
                    return reader.lines().collect(Collectors.joining(System.lineSeparator()));
                }
            }
        }
    }
}

为了让这个例子起作用,请将问题中的 HTML 测试文件放在 与已编译的 WebViewTest.class 相同的位置,这样它就可以作为资源从那里加载。

为了 运行 作为模块化应用程序的示例,将以下内容添加到您的 module-info.java(除了您的 javafx 模块要求和任何其他您需要的应​​用程序要求):

requires jdk.httpserver;

我使用 Spark Java 找到了另一个简单的解决方案:

WebViewTest.java

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import spark.Spark;
import spark.staticfiles.StaticFilesConfiguration;

public class WebViewTest extends Application {

    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage stage) {

        Spark.port(8000);

        StaticFilesConfiguration staticHandler = new StaticFilesConfiguration();
        staticHandler.configure("/");
        Spark.before((req, res) -> {
            if(req.url().endsWith(".html")) staticHandler.putCustomHeader("Content-Type", "text/html; charset=UTF-8");
            else staticHandler.putCustomHeader("Content-Type", null);
            staticHandler.consume(req.raw(), res.raw());
        });

        Spark.init();

        WebView webView = new WebView();

        webView.getEngine().load("http://localhost:8000/test.html");

        Scene scene = new Scene(webView, 500, 500);
        stage.setScene(scene);
        stage.setTitle("WebView Test");
        stage.show();
    }

}

test.html

<!DOCTYPE html>
<html>
    <body>
        <p>RIGHT SINGLE QUOTATION MARK: ’</p>
        <p>Image:</p>
        <img src="image.png">
    </body>
</html>

image.png



结果: