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

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

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

20 ответов

Решение

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

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

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

<![CDATA[ your text here ]]>

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

]]>

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

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

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

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

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

  • правильно обрабатывать символы вне базовой многоязычной плоскости
  • в 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;
                  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, не всегда является лучшим вариантом.

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

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();
}

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

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();
}

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

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))

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

  • StringEscapeUtils.escapeXml10()для xml 1.0
  • StringEscapeUtils.escapeXml11() для XML 1.1
  • StringEscapeUtils.escapeXml() в настоящее время устарела, но обычно использовалась в прошлом

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

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

Хотя я в принципе согласен с Джоном Скитом, иногда у меня нет возможности использовать внешнюю библиотеку 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_LOW =  "" + ((char)0x20); //space
  private static String UNICODE_HIGH = "" + ((char)0x7f);

  //should only use 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 {
            stringBuilder.append("&#" + ((int)character.charAt(0)) + ";");
            modified = true;
          }
        }
      }
      if (modified) {
        result = stringBuilder.toString();
      }
    }

    return result;
  }

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

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

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

Поведение 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

В то время как идеализм говорит, что используйте библиотеку 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>

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

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

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

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

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

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

Если вы ищете библиотеку, чтобы выполнить работу, попробуйте:

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

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

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

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

    StringEscapeUtils.escapeXml11(text)

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

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

Класс это: org.apache.commons.lang3.StringEscapeUtils;

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

Вы можете использовать библиотеку Enterprise Security API (ESAPI), которая предоставляет такие методы, как encodeForXML а также encodeForXMLAttribute, Посмотрите на документацию интерфейса Encoder; он также содержит примеры того, как создать экземпляр DefaultEncoder.

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

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!

Просто замени

 & with &amp;

И для других персонажей:

> with &gt;
< with &lt;
\" with &quot;
' with &apos;

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

Попробуйте закодировать 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());

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

Получите библиотеку 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()

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

Я создал здесь свою оболочку, надеюсь, она мне очень поможет. Нажмите здесь. Вы можете изменить в зависимости от ваших требований.

Другие вопросы по тегам