Фон
Я пытаюсь получить данные буфера обмена в контексте HTML-данных, используя Java. Таким образом, я копирую их в буфер обмена из браузеров. Затем я использую 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);
}
}
}
}
}
}
Описание проблемы
Я делаю это, открывая URL https://en.wikipedia.org/wiki/Germanic_umlaut в Firefox. Затем я выбираю "буквы: ä" и копирую их в буфер обмена. Затем я запускаю свою программу на Java. После этого результирующие файлы (только некоторые из них в качестве примеров) выглядят следующим образом:
[email protected]r:~/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...>..
OK, FEFF
в начале выглядит как байт-знак UTF-16BE
. Но что такое FFFD
? И почему существуют эти 0000
байт между единственными буквами? UTF-16
кодирование l
только 006C
. Кажется, что все буквы кодируются в 32 бит. Но это неправильно для UTF-16
. И все символы без ASCII закодированы с FFFD 0000
и поэтому теряются.
[email protected]:~/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
и поэтому также теряются.
[email protected]:~/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
и поэтому теряются.
[email protected]:~/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 в качестве источника данных, проблемы становятся меньше.
Поэтому я открываю URL https://en.wikipedia.org/wiki/Germanic_umlaut в Chromium. Затем я выбираю "буквы: ä" и копирую их в буфер обмена. Затем я запускаю свою программу на Java.
Результат выглядит так:
[email protected]:~/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
байта в конце.
[email protected]:~/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-программы, то UTF
верны, за исключением дополнительных 00
байтов в конце. Таким образом, содержимое буфера обмена, скопированное из Writer, ведет себя как контент, скопированный из браузера Chromium.
Новые идеи
Байты 0xFFFD
кажутся символами Unicode "ЗАМЕНА ХАРАКТЕРА" (U + FFFD). Таким образом, 0xFDFF
- это малое 0xFDFF
представление этого, а 0xEFBFBD
- кодировка UTF-8. Таким образом, все результаты, как представляется, являются результатом неправильного декодирования и повторного кодирования Unicode.
Похоже, что содержимое буфера обмена, поступающее из Firefox, всегда есть UTF-16LE
с BOM
. Но тогда Java
получает это как UTF-8
. Таким образом, 2-байтовая спецификация становится двумя перепутанными символами, которые заменяются 0xEFBFBD, каждая дополнительная последовательность 0x00
становится их собственными символами NUL
а все последовательности байтов, которые не являются правильными байтовыми последовательностями UTF-8
, становятся испорченными символами, которые заменяются 0xEFBFBD. Затем этот псевдо UTF-8 будет закодирован. Теперь мусор завершен.
Пример:
Последовательность aɛaüa
в UTF-16LE с спецификацией будет 0xFFFE 6100 5B02 6100 FC00 6100
.
Это принято как UTF-8 (0xEFBFBD = не правильная последовательность байтов UTF-8) = 0xEFBFBD 0xEFBFBD a
NUL
[
STX
a
NUL
0xEFBFBD NUL
a
NUL
.
Этот псевдо-ASCII- 0xFDFF FDFF 6100 0000 5B00 0200 6100 0000 FDFF 0000 6100 0000
кодированный в UTF-16LE, будет: 0xFDFF FDFF 6100 0000 5B00 0200 6100 0000 FDFF 0000 6100 0000
Этот псевдо-ASCII- 0xEFBF BDEF BFBD 6100 5B02 6100 EFBF BD00 6100
закодированный в UTF-8, будет 0xEFBF BDEF BFBD 6100 5B02 6100 EFBF BD00 6100
И это именно то, что происходит.
Другие примеры:
Â
= 0x00C2 = C200
в UTF-16LE = 0xEFBFBD00 в псевдо UTF-8
胂
= 0x80C2 = C280
в UTF-16LE = 0xC280 в псевдо UTF-8
Поэтому я думаю, что Firefox
не виноват в этом, кроме среды Ubuntu
или Java
. И поскольку копирование/вставка из Firefox в Writer работает в Ubuntu, я думаю, что среда выполнения Java
не обрабатывает флаги данных Firefox в буфере обмена 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}
Но никаких изменений в результатах содержимого буфера обмена Ubuntu при чтении Java.