Как я могу преобразовать строку 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 chars (известный как суррогаты).

Я не уверен, что такое "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 ''";
Другие вопросы по тегам