背景
我正在尝试使用Java获取HTML数据风格的剪贴板数据.因此我将它们从浏览器复制到剪贴板中.然后我使用java.awt.datatransfer.Clipboard来获取它们.
这在Windows系统中正常工作.但在Ubuntu中存在一些奇怪的问题.最糟糕的是从Firefox浏览器将数据复制到剪贴板.
再现行为的示例
Java代码:
import java.io.*;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
public class WorkingWithClipboadData {
static void doSomethingWithBytesFromClipboard(byte[] dataBytes, String paramCharset, int number) throws Exception {
String fileName = "Result " + number + " " + paramCharset + ".txt";
OutputStream fileOut = new FileOutputStream(fileName);
fileOut.write(dataBytes, 0, dataBytes.length);
fileOut.close();
}
public static void main(String[] args) throws Exception {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
int count = 0;
for (DataFlavor dataFlavor : clipboard.getAvailableDataFlavors()) {
System.out.println(dataFlavor);
String mimeType = dataFlavor.getHumanPresentableName();
if ("text/html".equalsIgnoreCase(mimeType)) {
String paramClass = dataFlavor.getParameter("class");
if ("java.io.InputStream".equals(paramClass)) {
String paramCharset = dataFlavor.getParameter("charset");
if (paramCharset != null && paramCharset.startsWith("UTF")) {
System.out.println("============================================");
System.out.println(paramCharset);
System.out.println("============================================");
InputStream inputStream = (InputStream)clipboard.getData(dataFlavor);
ByteArrayOutputStream data = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length = -1;
while ((length = inputStream.read(buffer)) != -1) {
data.write(buffer, 0, length);
}
data.flush();
inputStream.close();
byte[] dataBytes = data.toByteArray();
data.close();
doSomethingWithBytesFromClipboard(dataBytes, paramCharset, ++count);
}
}
}
}
}
}
问题描述
我正在做的是,在Firefox中打开URL https://en.wikipedia.org/wiki/Germanic_umlaut.然后我在那里选择"letters:ä"并将其复制到剪贴板中.然后我运行我的Java程序.之后,生成的文件(仅其中一些作为示例)如下所示:
axel@arichter:~/Dokumente/JAVA/poi/poi-3.17$ xxd "./Result 1 UTF-16.txt"
00000000: feff fffd fffd 006c 0000 0065 0000 0074 .......l...e...t
00000010: 0000 0074 0000 0065 0000 0072 0000 0073 ...t...e...r...s
00000020: 0000 003a 0000 0020 0000 003c 0000 0069 ...:... ...<...i
00000030: 0000 003e 0000 fffd 0000 003c 0000 002f ...>.......<.../
00000040: 0000 0069 0000 003e 0000 ...i...>..
好的FEFF
,开头看起来像一个UTF-16BE
字节顺序标记.但那是什么FFFD
?为什么0000
单个字母之间有那些字节?UTF-16
的编码l
是006C
仅仅.似乎所有字母都以32位编码.但这是错误的UTF-16
.并且所有非ASCII字符都被编码,FFFD 0000
因此丢失.
axel@arichter:~/Dokumente/JAVA/poi/poi-3.17$ xxd "./Result 4 UTF-8.txt"
00000000: efbf bdef bfbd 6c00 6500 7400 7400 6500 ......l.e.t.t.e.
00000010: 7200 7300 3a00 2000 3c00 6900 3e00 efbf r.s.:. .<.i.>...
00000020: bd00 3c00 2f00 6900 3e00 ..<./.i.>.
这里EFBF BDEF BFBD
看起来不像任何已知的字节顺序标记.并且所有字母似乎都以16位编码,这是所需位的两倍UTF-8
.所以使用的位似乎总是需要的双重计数.参见UTF-16
上面的例子.并且所有非ASCII字母都被编码为EFBFBD
,因此也丢失了.
axel@arichter:~/Dokumente/JAVA/poi/poi-3.17$ xxd "./Result 7 UTF-16BE.txt"
00000000: fffd fffd 006c 0000 0065 0000 0074 0000 .....l...e...t..
00000010: 0074 0000 0065 0000 0072 0000 0073 0000 .t...e...r...s..
00000020: 003a 0000 0020 0000 003c 0000 0069 0000 .:... ...<...i..
00000030: 003e 0000 fffd 0000 003c 0000 002f 0000 .>.......<.../..
00000040: 0069 0000 003e 0000 .i...>..
与上面的例子相同.所有字母都使用32位编码.UTF-16
除了使用代理对的补充字符外,只能使用16位.并且所有非ASCII字母都被编码,FFFD 0000
因此丢失.
axel@arichter:~/Dokumente/JAVA/poi/poi-3.17$ xxd "./Result 10 UTF-16LE.txt"
00000000: fdff fdff 6c00 0000 6500 0000 7400 0000 ....l...e...t...
00000010: 7400 0000 6500 0000 7200 0000 7300 0000 t...e...r...s...
00000020: 3a00 0000 2000 0000 3c00 0000 6900 0000 :... ...<...i...
00000030: 3e00 0000 fdff 0000 3c00 0000 2f00 0000 >.......<.../...
00000040: 6900 0000 3e00 0000 i...>...
只是为了完成.与上面相同的图片.
所以结论是Ubuntu剪贴板在从Firefox复制到它之后完全搞砸了.至少对于HTML数据风格以及使用Java读取剪贴板时.
其他浏览器使用
当我使用Chromium浏览器作为数据源做同样的事情时,问题就会变小.
所以我在Chromium 打开网址https://en.wikipedia.org/wiki/Germanic_umlaut.然后我在那里选择"letters:ä"并将其复制到剪贴板中.然后我运行我的Java程序.
结果如下:
axel@arichter:~/Dokumente/JAVA/poi/poi-3.17$ xxd "./Result 1 UTF-16.txt"
00000000: feff 003c 006d 0065 0074 0061 0020 0068 ...<.m.e.t.a. .h
...
00000800: 0061 006c 003b 0022 003e 00e4 003c 002f .a.l.;.".>...<./
00000810: 0069 003e 0000 .i.>..
Chromium在剪贴板中的HTML数据风格中选择了更多的HTML.但编码看起来很合适.也适用于非ASCII ä
= 00E4
.但是也存在一个小问题,最后还有其他字节0000
不应该存在.最后UTF-16
有2个额外的00
字节.
axel@arichter:~/Dokumente/JAVA/poi/poi-3.17$ xxd "./Result 4 UTF-8.txt"
00000000: 3c6d 6574 6120 6874 7470 2d65 7175 6976 ...
与上述相同.编码看起来很合适UTF-8
.但是这里还有一个额外的00
字节,不应该存在.
环境
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.4 LTS"
Mozilla Firefox 61.0.1 (64-Bit)
java version "1.8.0_101"
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)
问题
我在代码中做错了吗?
有人可以建议如何避免剪贴板中的混乱内容?由于非ASCII字符丢失,至少从Firefox复制时,我认为我们不能修复此内容.
这是一个已知的问题吗?有人可以确认相同的行为吗?如果是这样,Firefox中是否已经有关于此的错误报告?
或者这是一个只有在Java代码读取剪贴板内容时才会出现的问题?似乎好像.因为如果我从Firefox复制内容并将其粘贴到Libreoffice Writer中,则Unicode会正确显示.如果我然后将内容从Writer复制到剪贴板并使用我的Java程序读取它,那么UTF
编码是正确的,除了最后的附加00
字节.因此,从Writer复制的剪贴板内容的行为类似于从Chromium浏览器复制的内容.
新的见解
字节0xFFFD
似乎是Unicode字符'REPLACEMENT CHARACTER'(U + FFFD).所以这0xFDFF
是这个的小端表示,并且0xEFBFBD
是这个的UTF-8编码.因此,所有结果似乎都是错误解码和重新编码Unicode的结果.
似乎它的剪贴板中的内容从Firefox未来是UTF-16LE
有BOM
永远.但后来Java
得到了这个UTF-8
.因此,2字节BOM成为两个混乱的字符,用0xEFBFBD替换,每个附加0x00
序列成为它们自己的NUL
字符,所有不正确的UTF-8
字节序列的字节序列变成搞乱的字符,用0xEFBFBD替换.然后这个伪UTF-8将被重新编码.现在垃圾完成了.
例:
a?aüa
具有BOM的UTF-16LE 序列将是
0xFFFE 6100 5B02 6100 FC00 6100
.
这被视为UTF-8(0xEFBFBD =不是正确的UTF-8字节序列)= 0xEFBFBD 0xEFBFBD a
NUL
[
STX
a
NUL
0xEFBFBD NUL
a
NUL
.
重新编码为UTF-16LE的伪ASCII将是:
0xFDFF FDFF 6100 0000 5B00 0200 6100 0000 FDFF 0000 6100 0000
这个伪编码为UTF-8的伪ASCII将是
0xEFBF BDEF BFBD 6100 5B02 6100 EFBF BD00 6100
这正是发生的事情.
其他例子:
Â
= 0x00C2 = C200
UTF-16LE =伪UTF-8中的0xEFBFBD00
?
= 0x80C2 = C280
伪UTF-8中的UTF-16LE = 0xC280
所以我认为Firefox
这不应该归咎于这个Ubuntu
或者Java
是运行时环境.因为从Firefox到Writer的复制/粘贴在Ubuntu中工作,我认为Java
运行时环境无法正确处理Ubuntu
剪贴板中的Firefox数据风格.
新见解:
我比较了flavormap.properties
我Windows 10
和我的文件,Ubuntu
并且有区别.在Ubuntu
的本地名称text/html
是UTF8_STRING
,而在Windows
它HTML Format
.所以我认为这可能是问题所在.所以我添加了这条线
HTML\ Format=text/html;charset=utf-8;eoln="\n";terminators=0
到我的flavormap.properties
文件中Ubuntu
.
之后:
Map nativesForFlavors = SystemFlavorMap.getDefaultFlavorMap().getNativesForFlavors(
new DataFlavor[]{
new DataFlavor("text/html;charset=UTF-16LE")
});
System.out.println(nativesForFlavors);
版画
{java.awt.datatransfer.DataFlavor[mimetype=text/html;representatiOnclass=java.io.InputStream;charset=UTF-16LE]=HTML Format}
但是,当Java读取时,Ubuntu剪贴板内容的结果没有变化.
1> Michael Powe..:
看了这个之后,看起来这是Java的长期存在的错误(这里甚至是较旧的报告).
看起来X11 Java组件看起来希望剪贴板数据始终采用UTF-8编码,Firefox使用UTF-16编码数据.由于Java的假设,它通过强制将UTF-16解析为UTF-8来破坏文本.我试过但找不到绕过这个问题的好方法."text/html"的"text"部分似乎向Java表明从剪贴板接收的字节应始终首先解释为文本,然后以各种方式提供.我找不到任何直接从X11访问预转换字节数组的方法.