Лучший способ кодировать текстовые данные для XML в Java?


95

Очень похоже на этот вопрос , за исключением Java.

Каков рекомендуемый способ кодирования строк для вывода XML в Java. Строки могут содержать такие символы, как «&», «<» и т. Д.

Ответы:


41

Очень просто: используйте библиотеку XML. Таким образом, это действительно будет правильно, вместо того, чтобы требовать подробных знаний отдельных частей спецификации XML.


25
Вы можете порекомендовать такую ​​библиотеку? (Мне кажется удивительным, что это не стандартная часть Java версии 5 ... такая обычная задача).
Тим Купер

4
XML является частью стандартной среды Java - посмотрите org.w3c.sax и org.w3c.dom. Однако есть и более простые в использовании фреймворки, например JDom. Обратите внимание, что может не быть метода «кодирования строк для вывода XML» - я больше рекомендовал, чтобы вся XML-задача выполнялась с библиотекой, а не просто выполняла отдельные биты с манипуляциями со строками.
Джон Скит

1
Это не такой уж полезный совет при выводе XHTML - FlyingSaucer требует XML, но я не могу создавать шаблоны с помощью библиотеки XML :). К счастью, StringTemplate позволяет мне быстро избегать всех объектов String.
Стивен

4
@mice: Вопрос помечен как Java, а в Java есть множество XML-библиотек. Действительно, есть XML API, встроенные в Java, так что не было бы необходимости добавлять что - либо еще ... но даже если бы вы это сделали, несколько сотен тысяч килобайт в наши дни редко являются проблемой вне мобильных устройств. Даже если бы это была не Java, я бы очень осторожно подходил к разработке на платформе, которая не имеет никаких XML API ...
Джон Скит

2
@mice: DOM API прекрасно способен генерировать XML. Или есть довольно небольшие сторонние библиотеки. (Например, размер jar-файла JDom составляет 114 КБ.) Использование XML API по-прежнему является рекомендуемым способом создания XML.
Джон Скит

125

Как уже упоминалось, использование библиотеки XML - самый простой способ. Если вы хотите , чтобы избежать себя, вы можете посмотреть в StringEscapeUtilsиз Apache Commons Lang библиотеки.


Это может быть подходящим вариантом, если вас не волнует абсолютная правильность, например, если вы собираете прототип.
Чейз Зайберт,

2
Используйте StringEscapeUtils.escapeXml(str)из commons-lang. Я использую его в приложении App Engine - работает как шарм. Вот Java Doc для этой функции:
Олег К.

Метод escapeXml StringEscapeUtils кажется немного дорогостоящим. Есть ли более эффективный метод, который работает с StringBuffer вместо String?
CKing

Работает ли этот метод как для содержимого, так и для атрибутов XML? Мне кажется, что это не работает с атрибутами. Кажется, не уйти \t, \nи \r.
Lii

@Lii и \t, \nили \rнужно убежать ?
Betlista

20

Просто используйте.

<![CDATA[ your text here ]]>

Это позволит использовать любые символы, кроме окончания

]]>

Таким образом, вы можете включать запрещенные символы, такие как & и>. Например.

<element><![CDATA[ characters such as & and > are allowed ]]></element>

Однако атрибуты необходимо экранировать, поскольку для них нельзя использовать блоки CDATA.


11
В большинстве случаев это не то, что вам следует делать. Слишком много людей злоупотребляют тегами CDATA. Назначение CDATA - сказать процессору не обрабатывать его как XML, а просто передать его. Если вы пытаетесь создать файл XML, вам следует создавать XML, а не просто передавать байты через какой-либо элемент оболочки.
Мэдс Хансен

2
@Mads, использование CDATA приводит к правильному XML-файлу, так что это так же хорошо, как и делать это «правильно». Если вам это не нравится, затем проанализируйте его, измените идентичность и распечатайте.
Торбьёрн Равн Андерсен

24
Если вы помещаете текст в элемент CDATA, вам нужно избежать закрывающего маркера CDATA: "]]>" ... кроме того, что вы не можете этого избежать. Поэтому вместо этого вам нужно разбить свой код на части, где вы помещаете половину данных в один элемент CDATA, а другую половину - в секунду: <! [CDATA [Эти данные содержат закрывающий маркер CDATA: "]]]]> <! [CDATA [> "вот почему его пришлось разделить.]]> ... В конце концов, может быть намного проще вместо этого просто избежать '<', '>' и '&'. Конечно, многие приложения игнорируют потенциальную проблему с закрывающими маркерами CDATA в данных. Думаю, невежество - это блаженство. :)
Stijn de Witt

3
@StijndeWitt абсолютно прав. CDATA не является панацеей для экранирования специальных символов.
dnault 05

Это плохая идея. CDATA не допускает никаких символов вне кодировки XML.
Флориан Ф

14

Это хорошо помогло мне предоставить экранированную версию текстовой строки:

public class XMLHelper {

/**
 * Returns the string where all non-ascii and <, &, > are encoded as numeric entities. I.e. "&lt;A &amp; B &gt;"
 * .... (insert result here). The result is safe to include anywhere in a text field in an XML-string. If there was
 * no characters to protect, the original string is returned.
 * 
 * @param originalUnprotectedString
 *            original string which may contain characters either reserved in XML or with different representation
 *            in different encodings (like 8859-1 and UFT-8)
 * @return
 */
public static String protectSpecialCharacters(String originalUnprotectedString) {
    if (originalUnprotectedString == null) {
        return null;
    }
    boolean anyCharactersProtected = false;

    StringBuffer stringBuffer = new StringBuffer();
    for (int i = 0; i < originalUnprotectedString.length(); i++) {
        char ch = originalUnprotectedString.charAt(i);

        boolean controlCharacter = ch < 32;
        boolean unicodeButNotAscii = ch > 126;
        boolean characterWithSpecialMeaningInXML = ch == '<' || ch == '&' || ch == '>';

        if (characterWithSpecialMeaningInXML || unicodeButNotAscii || controlCharacter) {
            stringBuffer.append("&#" + (int) ch + ";");
            anyCharactersProtected = true;
        } else {
            stringBuffer.append(ch);
        }
    }
    if (anyCharactersProtected == false) {
        return originalUnprotectedString;
    }

    return stringBuffer.toString();
}

}

1
stringBuffer.append ("& #" + (int) ch + ";"); Это не сработает для многобайтовых символов. Я сейчас сталкиваюсь с этим с символом эмодзи, последовательностью UTF8 F0 9F 98 8D.
Кайлар

14

Попробуй это:

String xmlEscapeText(String t) {
   StringBuilder sb = new StringBuilder();
   for(int i = 0; i < t.length(); i++){
      char c = t.charAt(i);
      switch(c){
      case '<': sb.append("&lt;"); break;
      case '>': sb.append("&gt;"); break;
      case '\"': sb.append("&quot;"); break;
      case '&': sb.append("&amp;"); break;
      case '\'': sb.append("&apos;"); break;
      default:
         if(c>0x7e) {
            sb.append("&#"+((int)c)+";");
         }else
            sb.append(c);
      }
   }
   return sb.toString();
}

8
Я вижу как минимум две ошибки. Один тонкий, другой нет. У меня не было бы такого бага, потому что я бы вообще не изобретал велосипед.
Джон Скит

1
А перебирать строки Unicode немного сложнее. См. Здесь: stackoverflow.com/q/1527856/402322
2012,

1
Не уверен, что это незаметно, но лучше рассмотреть случай, когда t==null.
Myobis

1
@ user1003916: экранирование XML предназначено для преобразования любого вхождения & в & amp; так вот как это должно работать. Если вы исключаете уже экранированную строку, это ваша вина.
Pointer Null

3
Я доволен финальной версией. Java SE компактный, быстрый и эффективный. В моей книге всегда лучше делать то, что нужно, чем загружать еще 100 МБ вредоносного ПО.
Roger F. Gay

11

Этому вопросу восемь лет, и он все еще не является полностью правильным! Нет, вам не нужно импортировать весь сторонний API для выполнения этой простой задачи. Плохой совет.

Следующий метод:

  • правильно обрабатывать символы вне основной многоязычной плоскости
  • escape-символы, необходимые в XML
  • избегать любых символов, отличных от ASCII, что является необязательным, но распространенным
  • заменить недопустимые символы в XML 1.0 символом подстановки Unicode. Здесь нет лучшего варианта - их удаление также возможно.

Я попытался оптимизировать для наиболее распространенного случая, но при этом убедился, что вы можете пропустить через него / dev / random и получить действительную строку в XML.

public static String encodeXML(CharSequence s) {
    StringBuilder sb = new StringBuilder();
    int len = s.length();
    for (int i=0;i<len;i++) {
        int c = s.charAt(i);
        if (c >= 0xd800 && c <= 0xdbff && i + 1 < len) {
            c = ((c-0xd7c0)<<10) | (s.charAt(++i)&0x3ff);    // UTF16 decode
        }
        if (c < 0x80) {      // ASCII range: test most common case first
            if (c < 0x20 && (c != '\t' && c != '\r' && c != '\n')) {
                // Illegal XML character, even encoded. Skip or substitute
                sb.append("&#xfffd;");   // Unicode replacement character
            } else {
                switch(c) {
                  case '&':  sb.append("&amp;"); break;
                  case '>':  sb.append("&gt;"); break;
                  case '<':  sb.append("&lt;"); break;
                  // Uncomment next two if encoding for an XML attribute
//                  case '\''  sb.append("&apos;"); break;
//                  case '\"'  sb.append("&quot;"); break;
                  // Uncomment next three if you prefer, but not required
//                  case '\n'  sb.append("&#10;"); break;
//                  case '\r'  sb.append("&#13;"); break;
//                  case '\t'  sb.append("&#9;"); break;

                  default:   sb.append((char)c);
                }
            }
        } else if ((c >= 0xd800 && c <= 0xdfff) || c == 0xfffe || c == 0xffff) {
            // Illegal XML character, even encoded. Skip or substitute
            sb.append("&#xfffd;");   // Unicode replacement character
        } else {
            sb.append("&#x");
            sb.append(Integer.toHexString(c));
            sb.append(';');
        }
    }
    return sb.toString();
}

Изменить: для тех, кто продолжает настаивать на глупости написания собственного кода для этого, когда есть совершенно хорошие Java API для работы с XML, вам может быть интересно узнать, что StAX API включен в Oracle Java 8 (я не тестировал другие ) не может правильно кодировать содержимое CDATA: он не экранирует]]> последовательности содержимого. Сторонняя библиотека, даже если она является частью ядра Java, не всегда лучший вариант.


+1 за автономный код. Просто сравнивая ваш код с реализацией гуавы , мне интересно, а как насчет '\ t', '\ n', '\ r'? См. Также примечания в документации
guava

2
Нет необходимости экранировать \ n, \ r и \ t, они действительны, хотя они делают форматирование немного некрасивым. Я изменил код, чтобы показать, как их убрать, если вы этого хотите.
Mike B

1
В CDATA нет возможности «сбежать]]>».
kmkaplan

1
Затем он должен отклонить содержимое, вызвав исключение IllegalArgumentException. Ни при каких обстоятельствах он не должен заявлять об успешном выполнении, но при этом выводить неверный XML.
Майк Би

Вместо замены недопустимых символов в XML 1.0 символом подстановки Unicode вы можете использовать мои методы здесь stackoverflow.com/a/59475093/3882565 .
stonar96

8

StringEscapeUtils.escapeXml()не экранирует управляющие символы (<0x20). XML 1.1 допускает управляющие символы; XML 1.0 этого не делает. Например,XStream.toXML() успешно сериализует управляющие символы объекта Java в XML, который синтаксический анализатор XML 1.0 отклонит.

Чтобы экранировать управляющие символы с помощью Apache commons-lang, используйте

NumericEntityEscaper.below(0x20).translate(StringEscapeUtils.escapeXml(str))

7
public String escapeXml(String s) {
    return s.replaceAll("&", "&amp;").replaceAll(">", "&gt;").replaceAll("<", "&lt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
}

5
Объединение replaceAllвызовов очень неэффективно, особенно для больших строк. Каждый вызов приводит к созданию нового объекта String, который будет висеть до тех пор, пока не будет собран мусор. Кроме того, каждый вызов требует повторного прохождения строки. Это можно объединить в один ручной цикл со сравнениями с каждым целевым символом на каждой итерации.
daiscog

Это должен быть принятый ответ, даже если он неэффективен. Решает проблему в одной строке.
Stimpson Cat

И в нем много ошибок. См. Этот комментарий выше
Давид Балажич

Чтобы исправить эти ошибки, вы можете дополнительно использовать мой метод здесь stackoverflow.com/a/59475093/3882565 . Обратите внимание, что это не замена, но может использоваться дополнительно.
stonar96

6

В то время как идеализм говорит, что используйте XML-библиотеку, ИМХО, если у вас есть базовое представление об XML, тогда здравый смысл и производительность полностью говорят о шаблоне. Возможно, это и более читабельно. Хотя использование функций экранирования библиотеки, вероятно, является хорошей идеей.

Подумайте об этом: XML был предназначен для написания людьми.

Используйте библиотеки для генерации XML, когда ваш XML как «объект» лучше моделирует вашу проблему. Например, если подключаемые модули участвуют в процессе построения этого XML.

Изменить: что касается того, как на самом деле избежать XML в шаблонах, использование CDATA или escapeXml(string)из JSTL - два хороших решения, escapeXml(string)которые можно использовать следующим образом:

<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>

<item>${fn:escapeXml(value)}</item>

6

Поведение StringEscapeUtils.escapeXml () изменилось с Commons Lang 2.5 на 3.0. Теперь он больше не экранирует символы Unicode больше 0x7f.

Это хорошо, старый метод должен был немного сбежать от сущностей, которые можно было просто вставить в документ utf8.

Новые средства защиты, которые будут включены в Google Guava 11.0, также кажутся многообещающими: http://code.google.com/p/guava-libraries/issues/detail?id=799


1
Вот XML-эскейпер Guava: code.google.com/p/guava-libraries/source/browse/guava/src/com/… . В общем, я считаю, что Guava лучше спроектирован, чем Apache Commons.
jhclark


6

Для тех, кто ищет наиболее быстрое решение: используйте методы из apache commons-lang :

Не забудьте включить зависимость:

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.5</version> <!--check current version! -->
</dependency>

5

Примечание. Ваш вопрос касается экранирования , а не кодирования. . Для экранирования используется <и т. Д., Чтобы синтаксический анализатор мог различать «это команда XML» и «это некоторый текст». Кодировка - это то, что вы указываете в заголовке XML (UTF-8, ISO-8859-1 и т. Д.).

Прежде всего, как все говорили, используйте библиотеку XML. XML выглядит простым, но кодирование + экранирование - это темное вуду (которое вы заметите, как только встретите умляуты, японский язык и другие странные вещи, такие как « цифры полной ширины » (& # FF11; равно 1)). Обеспечение удобочитаемости XML - это задача Сизифа.

Я предлагаю никогда не хитрить в кодировке текста и экранировании в XML. Но не позволяйте этому останавливать вас от попыток; просто помните, когда он вас укусит (и будет).

Тем не менее, если вы используете только UTF-8, чтобы сделать вещи более читаемыми, вы можете рассмотреть эту стратегию:

  • Если текст действительно содержит '<', '>' или '&', оберните его <![CDATA[ ... ]]>
  • Если текст не содержит этих трех символов, не деформируйте его.

Я использую это в редакторе SQL, и это позволяет разработчикам вырезать и вставлять SQL из стороннего инструмента SQL в XML, не беспокоясь об экранировании. Это работает, потому что в нашем случае SQL не может содержать умляутов, так что я в безопасности.


5

Хотя я в принципе согласен с Джоном Скитом, иногда у меня нет возможности использовать внешнюю библиотеку XML. И мне кажется странным, что две функции для экранирования / отмены экранирования простого значения (атрибута или тега, а не полного документа) недоступны в стандартных библиотеках XML, включенных в Java.

В результате и на основе различных ответов, которые я видел здесь и в других местах, вот решение, которое я создал (ничто не работало как простая копия / вставка):

  public final static String ESCAPE_CHARS = "<>&\"\'";
  public final static List<String> ESCAPE_STRINGS = Collections.unmodifiableList(Arrays.asList(new String[] {
      "&lt;"
    , "&gt;"
    , "&amp;"
    , "&quot;"
    , "&apos;"
  }));

  private static String UNICODE_NULL = "" + ((char)0x00); //null
  private static String UNICODE_LOW =  "" + ((char)0x20); //space
  private static String UNICODE_HIGH = "" + ((char)0x7f);

  //should only be used for the content of an attribute or tag      
  public static String toEscaped(String content) {
    String result = content;
    
    if ((content != null) && (content.length() > 0)) {
      boolean modified = false;
      StringBuilder stringBuilder = new StringBuilder(content.length());
      for (int i = 0, count = content.length(); i < count; ++i) {
        String character = content.substring(i, i + 1);
        int pos = ESCAPE_CHARS.indexOf(character);
        if (pos > -1) {
          stringBuilder.append(ESCAPE_STRINGS.get(pos));
          modified = true;
        }
        else {
          if (    (character.compareTo(UNICODE_LOW) > -1)
               && (character.compareTo(UNICODE_HIGH) < 1)
             ) {
            stringBuilder.append(character);
          }
          else {
            //Per URL reference below, Unicode null character is always restricted from XML
            //URL: https://en.wikipedia.org/wiki/Valid_characters_in_XML
            if (character.compareTo(UNICODE_NULL) != 0) {
              stringBuilder.append("&#" + ((int)character.charAt(0)) + ";");
            }
            modified = true;
          }
        }
      }
      if (modified) {
        result = stringBuilder.toString();
      }
    }
    
    return result;
  }

Вышеупомянутое включает несколько разных вещей:

  1. избегает использования логики на основе символов до тех пор, пока это не будет абсолютно необходимо - улучшает совместимость с Unicode
  2. пытается быть как можно более эффективным, учитывая вероятность того, что второе условие «если» является наиболее часто используемым путем
  3. чистая функция; т.е. является потокобезопасным
  4. прекрасно оптимизируется со сборщиком мусора, возвращая содержимое StringBuilder только в том случае, если что-то действительно изменилось - в противном случае возвращается исходная строка

В какой-то момент я напишу инверсию этой функции toUnescaped (). У меня просто нет на это времени сегодня. Когда я это сделаю, я обновлю этот ответ кодом. :)


Выглядит довольно хорошо для меня. Я не хочу добавлять в свой проект еще одну банку только для одного метода. Если вы дадите разрешение, могу я скопировать и вставить ваш код в свой?
RuntimeException

1
@SatishMotwani Конечно, вы можете взять приведенный выше код и делать с ним, как хотите. Насколько я понимаю, любой код, опубликованный на StackOverflow, считается свободным от авторских прав (не рассматривается как произведение в целом). С другой стороны, кому-то было бы чрезвычайно сложно выдвинуть какое-либо заявление об авторском праве и ожидать для себя какого-то результата.
chaotic3quilibrium

1
Спасибо, что разрешили :-) Воспользуюсь.
RuntimeException

Вы забыли обрабатывать символы NUL. И, может быть, и другие вещи.
Давид Балажич

@ DavidBalažic Хорошо, объясните, пожалуйста, поподробнее, что я мог пропустить? Пожалуйста, прочтите код более внимательно. Я обработал КАЖДЫЙ ОДИНОЧНЫЙ символ Unicode (из 1111998), включая nullсимвол. Можете ли вы объяснить определение двух значений UNICODE_LOWи UNICODE_HIGH? Пожалуйста, перечитайте, ifчто использует эти два значения. Уведомление null( \u0000которое есть (int)0) не находится между этими двумя значениями. Читайте о том, как она становится правильно «убежала» так же , как существующие вне ALL Unicode символов UNICODE_LOWи UNICODE_HIGHдиапазона, используя &#технику.
chaotic3quilibrium

3

Чтобы избежать символов XML, проще всего использовать проект Apache Commons Lang, JAR загружаемый с: http://commons.apache.org/lang/

Это класс: org.apache.commons.lang3.StringEscapeUtils;

У него есть метод с именем "escapeXml", который возвращает строку с соответствующим экранированием.


Обновление: escapeXml устарел - используйте escapeXml10. Ссылка commons.apache.org/proper/commons-lang/javadocs/api-3.3/org/…
Даниил

3

Если вы ищете библиотеку для работы, попробуйте:

  1. Guava 26.0 задокументирован здесь

    return XmlEscapers.xmlContentEscaper().escape(text);

    Примечание: есть также xmlAttributeEscaper()

  2. Документированный здесь Apache Commons Text 1.4

    StringEscapeUtils.escapeXml11(text)

    Примечание: существует также escapeXml10()метод


1

Вот простое решение, и оно отлично подходит для кодирования акцентированных символов!

String in = "Hi Lârry & Môe!";

StringBuilder out = new StringBuilder();
for(int i = 0; i < in.length(); i++) {
    char c = in.charAt(i);
    if(c < 31 || c > 126 || "<>\"'\\&".indexOf(c) >= 0) {
        out.append("&#" + (int) c + ";");
    } else {
        out.append(c);
    }
}

System.out.printf("%s%n", out);

Выходы

Hi L&#226;rry &#38; M&#244;e!

Разве «31» в первой строке «if» не должно быть «32»; т.е. меньше пробела? И если "31" должно остаться, то не следует ли его исправить, чтобы читать "if (c <= 31 || ..." (дополнительный знак равенства после знака меньше)?
chaotic3quilibrium



0

Используйте JAXP и забудьте об обработке текста, это будет сделано за вас автоматически.


Ваша ссылка на испанском языке, что не так полезно для большинства из нас. Лучше это .
Вивит

0

Попробуйте закодировать XML с помощью сериализатора Apache XML

//Serialize DOM
OutputFormat format    = new OutputFormat (doc); 
// as a String
StringWriter stringOut = new StringWriter ();    
XMLSerializer serial   = new XMLSerializer (stringOut, 
                                          format);
serial.serialize(doc);
// Display the XML
System.out.println(stringOut.toString());

0

Вот что я нашел после повсеместных поисков решения:

Получите библиотеку Jsoup:

<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.12.1</version>
</dependency>

Затем:

import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Entities
import org.jsoup.parser.Parser

String xml = '''<?xml version = "1.0"?>
<SOAP-ENV:Envelope
   xmlns:SOAP-ENV = "http://www.w3.org/2001/12/soap-envelope"
   SOAP-ENV:encodingStyle = "http://www.w3.org/2001/12/soap-encoding">

   <SOAP-ENV:Body xmlns:m = "http://www.example.org/quotations">
      <m:GetQuotation>
         <m:QuotationsName> MiscroSoft@G>>gle.com </m:QuotationsName>
      </m:GetQuotation>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>'''



Document doc = Jsoup.parse(new ByteArrayInputStream(xml.getBytes("UTF-8")), "UTF-8", "", Parser.xmlParser())
doc.outputSettings().charset("UTF-8")
doc.outputSettings().escapeMode(Entities.EscapeMode.base)

println doc.toString()

Надеюсь, это кому-то поможет


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.