简体   繁体   English

如何设置 JavaFX WebView 的编码?

[英]How can I set the encoding of a JavaFX WebView?

I'm having an encoding issue with JavaFX's WebView .我遇到了 JavaFX 的WebView的编码问题。 When loading a UTF-8 encoded file, special characters are displayed incorrectly (eg ’ is displayed instead of ' ).加载’编码文件时,特殊字符显示不正确(例如,显示的是 ' 而不是' )。 Here's an SSCCE:这是一个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>

Output of file -bi test.html Output file -bi test.html

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

Result:结果:
WebView 编码问题

The same thing happens in Windows using Java 17 and the latest JavaFX (I used Linux and Java 8 for the demonstration). The same thing happens in Windows using Java 17 and the latest JavaFX (I used Linux and Java 8 for the demonstration).

I've tried:我试过了:

  • Declaring the charset in the HTML: <meta charset="UTF-8">在 HTML 中声明字符集: <meta charset="UTF-8">

    (works, but I'm making an editor program, so I don't have control over the HTML) (有效,但我正在制作一个编辑器程序,所以我无法控制 HTML)

  • Using the JVM argument -Dfile.encoding=UTF-8 (doesn't work)使用 JVM 参数-Dfile.encoding=UTF-8 (不起作用)

  • Setting the charset using reflection (doesn't work, and throws an exception in newer Java versions):使用反射设置字符集(不起作用,并在较新的 Java 版本中引发异常):

     System.setProperty("file.encoding","UTF-8"); Field charset = Charset.class.getDeclaredField("defaultCharset"); charset.setAccessible(true); charset.set(null,null);
  • Declaring the charset after the page loads using the DOM API (doesn't work):在页面加载后使用 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); } });
  • Using WebEngine.loadContent(String) instead of load(String) (wouldn't work; relative links would be broken)使用WebEngine.loadContent(String)而不是load(String) (不起作用;相关链接会被破坏)

It appears that WebView ignores file encodings, and uses ISO-8859-1 unless a charset is specified in the HTML. WebView似乎忽略了文件编码,并使用 ISO-8859-1,除非在 HTML 中指定了字符集。

While writing the question, I found a hacky solution:在写这个问题时,我找到了一个 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 determines the encoding from either the HTML file or the HTTP header. WebView 从 HTML 文件或 HTTP Z099FB995346F31C749F6E40DB0F93 确定编码。 This is as per the w3c specification, for information see:这是根据 w3c 规范,有关信息,请参阅:

As you already noted in your question, you can declare the character encoding in the head element within the HTML document and the WebView will pick it up:正如您在问题中已经指出的那样,您可以在 HTML 文档中的 head 元素中声明字符编码,WebView 会选择它:

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

But, you also note in your question that you don't have control over the input HTML files and whether it includes the necessary header for declaring the charset.但是,您还在问题中注意到您无法控制输入 HTML 文件以及它是否包含用于声明字符集的必要 header。

You can also have the HTTP protocol specify the encoding of the file using an appropriate header.您还可以让 HTTP 协议使用适当的 header 指定文件的编码。

 Content-Type: text/html; charset=UTF-8

If you do that, the HTML file content will be correctly UTF-8 decoded by the WebView, even if the input file does not include a charset header. If you do that, the HTML file content will be correctly UTF-8 decoded by the WebView, even if the input file does not include a charset header.

Here is an example:这是一个例子:

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

For this example to work, place the HTML test file from your question in the same location as your compiled WebViewTest.class, so that it can be loaded from there as a resource.要使此示例正常工作,请将问题中的 HTML 测试文件与已编译的 WebViewTest.class 放在同一位置,以便可以从那里作为资源加载。

To run the example as a modular app, add the following to your module-info.java (in addition to your javafx module requirements and any other app requirements you need):要将示例作为模块化应用程序运行,请将以下内容添加到您的 module-info.java(除了您的 javafx 模块要求和您需要的任何其他应用程序要求之外):

requires jdk.httpserver;

I found another simple solution using Spark Java :我找到了另一个使用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

示例图片

Result:结果:

工作 WebViewTest

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM