Как я могу преобразовать строку Java в сущности xml для версий Unicode выше 3.0?
Чтобы преобразовать символы Java в объекты XML, я могу сделать следующее для каждого символа в строке:
buf.append("&#x"+ Integer.toHexString(c | 0x10000).substring(1) +";");
Однако, в соответствии с другими вопросами, касающимися стека, это работает только для Unicode 3.0.
Если я использую UTF-8 Reader для чтения в строке, то предположительно, что строка содержит символы в формате, который работает через Unicode 6.0 (потому что Java 7 поддерживает Unicode 6.0 в соответствии с javadoc).
Как только я получу эту строку, как я могу записать ее в виде объектов XML? В идеале я бы использовал API, который продолжал бы работать, когда выходят новые версии Unicode.
2 ответа
Либо вы не используете правильную терминологию, либо здесь много путаницы.
&#x
обозначение ссылки на символ просто указывает числовую кодовую точку; он не зависит от версии Unicode, используемой любым читателем или анализатором.
Ваш код на самом деле совместим только с Unicode 1.x, потому что он предполагает, что числовое значение символа меньше 216. Начиная с Unicode 2.0 это неверное предположение. Некоторые символы представлены одной Java char
в то время как другие символы представлены двумя Java char
s (известный как суррогаты).
Я не уверен, что такое "UTF-8 Reader". Читатель только читает char
значений, и не знает о UTF-8 или любой другой кодировке, за исключением InputStreamReader, который использует CharsetDecoder для преобразования байтов в символы с использованием кодировки UTF-8 (или любой другой кодировки, используемой конкретным CharsetDecoder).
В любом случае, никакой Читатель не будет анализировать XML &#x
символьная ссылка. Вы должны использовать синтаксический анализатор XML для этого.
Версия Unicode, известная Java, не влияет на Reader или XML-парсер, поскольку ни один Reader или XML-парсер не обращается к базе данных Unicode каким-либо образом. Символы просто обрабатываются как числовые значения при их разборе. Соответствуют ли они назначенным кодовым точкам в любой версии Unicode, никогда не рассматривается.
Наконец, чтобы записать String как XML, вы можете использовать Formatter:
static String toXML(String s) {
Formatter formatter = new Formatter();
int len = s.length();
for (int i = 0; i < len; i = s.offsetByCodePoints(i, 1)) {
int c = s.codePointAt(i);
if (c < 32 || c > 126 || c == '&' || c == '<' || c == '>') {
formatter.format("&#x%x;", c);
} else {
formatter.format("%c", c);
}
}
return formatter.toString();
}
Как видите, нет кода, который зависит от версии Unicode, потому что символы - это просто числовые значения. Является ли каждое числовое значение назначенной кодовой точкой Unicode, не имеет значения.
(Моим первым стремлением было использовать класс XMLStreamWriter, но оказывается, что XMLStreamWriter, использующий не-Unicode-кодировку, такую как ISO-8859-1 или US-ASCII, неправильно выводит суррогатные пары в виде одно-символьных объектов, как в Java 1.8.0_05.)
Первоначально Java поддерживала Unicode 1.0, делая тип char длиной 16 битов, но Unicode 2.0 представил механизм суррогатных символов для поддержки большего количества символов, чем допустимое число в 16 битах, поэтому строки Java стали кодированными в UTF-16; это означает, что некоторым символам для представления нужны два символа Java, они называются старшим суррогатным символом и младшим суррогатным символом.
Чтобы узнать, какие символы в String на самом деле являются суррогатными парами high/low, вы можете использовать служебные методы в Character
:
Character.isHighSurrogate(myChar); // returns true if myChar is a high surrogate
Character.isLowSurrogate(myChar); // same for low surrogate
Character.isSurrogate(myChar); // just to know if myChar is a surrogate
Как только вы узнаете, какие символы являются высокими или низкими суррогатами, вам нужно преобразовать каждую пару в кодировку Unicode с помощью этого метода:
int codePoint = Character.toCodePoint(highSurrogate, lowSurrogate);
Поскольку фрагмент кода стоит тысячи слов, это пример метода замены ссылок на символы xml без символов us-ascii внутри строки:
public static String replaceToCharEntities(String str) {
StringBuilder result = new StringBuilder(str.length());
char surrogate = 0;
for(char c: str.toCharArray()) {
// if char is a high surrogate, keep it to match it
// against the next char (low surrogate)
if(Character.isHighSurrogate(c)) {
surrogate = c;
continue;
}
// get codePoint
int codePoint;
if(surrogate != 0) {
codePoint = Character.toCodePoint(surrogate, c);
surrogate = 0;
} else {
codePoint = c;
}
// decide wether using just a char or a character reference
if(codePoint < 0x20 || codePoint > 0x7E || codePoint == '<'
|| codePoint == '>' || codePoint == '&' || codePoint == '"'
|| codePoint == '\'') {
result.append(String.format("&#x%x;", codePoint));
} else {
result.append(c);
}
}
return result.toString();
}
Следующий пример строки - хороший пример для тестирования, так как он содержит не-ascii символ, который может быть представлен 16-битным значением, а также символ с суррогатной парой high/low:
String myString = "text with some non-US chars: 'Ñ' and ''";