如何设置 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进行演示) .
我试过:
在 HTML 中声明字符集:<meta charset="UTF-8">
(有效,但我正在制作一个编辑器程序,所以我无法控制 HTML)
使用 JVM 参数 -Dfile.encoding=UTF-8
(不起作用)
Setting the charset using reflection(不起作用,并在较新的 Java 版本中抛出异常):
System.setProperty("file.encoding","UTF-8");
Field charset = Charset.class.getDeclaredField("defaultCharset");
charset.setAccessible(true);
charset.set(null,null);
在页面加载后使用 DOM API 声明字符集(不起作用):
webView.getEngine().getLoadWorker().stateProperty().addListener((o, oldState, newState) -> {
if(newState == Worker.State.SUCCEEDED) {
Document document = webView.getEngine().getDocument();
Element meta = document.createElement("meta");
meta.setAttribute("charset", "UTF-8");
document.getElementsByTagName("html").item(0).appendChild(meta);
}
});
使用 WebEngine.loadContent(String)
而不是 load(String)
(不起作用;相关链接会被破坏)
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
结果:
我在使用 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进行演示) .
我试过:
在 HTML 中声明字符集:
<meta charset="UTF-8">
(有效,但我正在制作一个编辑器程序,所以我无法控制 HTML)
使用 JVM 参数
-Dfile.encoding=UTF-8
(不起作用)Setting the charset using reflection(不起作用,并在较新的 Java 版本中抛出异常):
System.setProperty("file.encoding","UTF-8"); Field charset = Charset.class.getDeclaredField("defaultCharset"); charset.setAccessible(true); charset.set(null,null);
在页面加载后使用 DOM API 声明字符集(不起作用):
webView.getEngine().getLoadWorker().stateProperty().addListener((o, oldState, newState) -> { if(newState == Worker.State.SUCCEEDED) { Document document = webView.getEngine().getDocument(); Element meta = document.createElement("meta"); meta.setAttribute("charset", "UTF-8"); document.getElementsByTagName("html").item(0).appendChild(meta); } });
使用
WebEngine.loadContent(String)
而不是load(String)
(不起作用;相关链接会被破坏)
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
结果: