剪贴板内容在从 Firefox 复制并在 Ubuntu 中使用 Java 读取时会弄乱
背景
我正在尝试使用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。然后,我会在那里选择“字母:ä”并将其复制到剪贴板中。然后我运行我的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...>..
好的,开头看起来像一个字节顺序标记。但什么是?为什么单个字母之间有这些字节? 的编码仅为。似乎所有字母都以32位编码。但这是错误的。所有非 ASCII 字符都使用编码,因此会丢失。FEFF
UTF-16BE
FFFD
0000
UTF-16
l
006C
UTF-16
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.>.
这里的 看起来不像任何已知的字节顺序标记。而且所有字母似乎都编码为16位,这是 中所需位的双倍。因此,使用的位似乎始终是根据需要进行重复计数。请参阅上面的示例。所有不是ASCII的字母都被编码为,因此也会丢失。EFBF BDEF BFBD
UTF-8
UTF-16
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 位进行编码。除了使用代理项对的增补字符外,只能使用 16 位。所有不是ASCII的字母都被编码,因此会丢失。UTF-16
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中打开URL https://en.wikipedia.org/wiki/Germanic_umlaut。然后,我会在那里选择“字母:ä”并将其复制到剪贴板中。然后我运行我的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 = 。但也有一个小问题,末尾有额外的字节不应该在那里。在末尾有2个额外的字节。ä
00E4
0000
UTF-16
00
axel@arichter:~/Dokumente/JAVA/poi/poi-3.17$ xxd "./Result 4 UTF-8.txt"
00000000: 3c6d 6574 6120 6874 7470 2d65 7175 6976 <meta http-equiv
...
000003f0: 696f 6e2d 636f 6c6f 723a 2069 6e69 7469 ion-color: initi
00000400: 616c 3b22 3ec3 a43c 2f69 3e00 al;">..</i>.
同上。编码看起来正确。但是这里最后还有一个额外的字节,不应该在那里。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程序进行读取,那么除了末尾的其他字节外,编码是正确的。因此,从 Writer 复制的剪贴板内容的行为类似于从 Chromium 浏览器复制的内容。UTF
00
新见解
字节似乎是 Unicode 字符“替换字符”(U+FFFD)。所以这是这个的小端表示,这是 UTF-8 编码。因此,所有结果似乎都是错误解码和重新编码Unicode的结果。0xFFFD
0xFDFF
0xEFBFBD
似乎来自Firefox的剪贴板内容总是与。但随后将其作为.因此,2字节BOM变成两个混乱的字符,它们被0xEFBFBD替换,每个额外的序列成为它们自己的字符,所有不是正确的字节序列的字节序列变成混乱的字符,这些字符被替换为0xEFBFBD。然后这个伪 UTF-8 将被重新编码。现在垃圾已经完成。UTF-16LE
BOM
Java
UTF-8
0x00
NUL
UTF-8
例:
带有 BOM 的 UTF-16LE 中的序列将为 。aɛaüa
0xFFFE 6100 5B02 6100 FC00 6100
这被视为 UTF-8(0xEFBFBD = 不是正确的 UTF-8 字节序列)= 0xEFBFBD 0xEFBFBD 0xEFBFBD 。a
NUL
[
STX
a
NUL
NUL
a
NUL
这个重新编码为 UTF-16LE 的伪 ASCII 将是:0xFDFF FDFF 6100 0000 5B00 0200 6100 0000 FDFF 0000 6100 0000
此伪 ASCII 重新编码为 UTF-8 将是0xEFBF BDEF BFBD 6100 5B02 6100 EFBF BD00 6100
这正是所发生的事情。
其他示例:
Â
= 0x00C2 = 以 UTF-16LE 为单位 = 以伪 UTF-8 为单位0xEFBFBD00C200
胂
= 0x80C2 = UTF-16LE = 伪 UTF-8 中的 0xC280C280
所以我认为这不应该归咎于此,而是应该归咎于或的运行时环境。而且由于从 Firefox 到 Writer 的复制/粘贴在 Ubuntu 中工作,我认为 的运行时环境无法正确处理剪贴板中的 Firefox 数据风格。Firefox
Ubuntu
Java
Java
Ubuntu
新见解:
我已经比较了我和我的文件,有区别。在 的本地名称中是 ,而在它是 .所以我认为这可能是问题所在。所以我添加了一行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<DataFlavor,String> 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 剪贴板内容时,结果没有变化。