Найкращий спосіб кодування текстових даних для XML на Java?


93

Дуже схоже на це питання , за винятком 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 lib :). На щастя, StringTemplate дозволяє мені швидко уникнути всіх об’єктів String.
Стівен

4
@mice: Питання помічено Java, і Java має безліч бібліотек XML. Справді, у Java є API, запечені в XML, тому не потрібно буде додавати що- небудь ще ... але навіть якщо ви це зробили, кілька сотень К сьогодні рідко є проблемою поза мобільними пристроями. Навіть якби це не була Java, я би дуже насторожився щодо розробки на платформі, яка не мала жодного API XML ...
Джон Скіт,

2
@mice: DOM API чудово здатний генерувати XML. Або є досить маленькі сторонні бібліотеки. (Наприклад, Jar-файл Jar становить 114 тис.). Використання XML API як і раніше є рекомендованим способом створення XML.
Джон Скіт,

124

Як зазначали інші, використання бібліотеки XML - це найпростіший спосіб. Якщо ви хочете , щоб уникнути себе, ви можете подивитися в StringEscapeUtilsз Apache Commons Lang бібліотеки.


Це може бути таким шляхом, якщо ви не дбаєте про абсолютну коректність, наприклад, якщо збираєте прототип.
Chase Seibert

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

Метод escapeXml StringEscapeUtils здається трохи дорогим. Чи існує більш ефективний метод, який працює на StringBuffer замість String?
CKing

Чи працює цей метод як для вмісту XML, так і для атрибутів? Мені здається, що це не працює для атрибутів. Здається, це не втече \t, \nі \r.
Lii

@Lii та \t, \nабо \rпотрібно уникнути?
Бетліста

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-файлу, тому це так само добре, як робити це "правильно". Якщо вам це не подобається, потім проаналізуйте його, перетворіть ідентифікаційне зображення та роздрукуйте.
Thorbjørn Ravn Andersen

24
Якщо ви обертаєте текст елементом CDATA, вам потрібно уникнути маркера закриття CDATA: "]]>" ... за винятком того, що ви не можете уникнути цього. Отже, натомість вам доведеться розбити свій код на частини, куди ви помістите половину даних в один елемент CDATA, а другу половину - в секунду: <! [CDATA [Ці дані містять маркер закриття CDATA: "]]]]> <! [CDATA [> "саме тому його потрібно було розділити.]]> ... Зрештою, можливо, набагато простіше просто уникнути" <","> "та" & "замість цього. Звичайно, багато програм ігнорують потенційну проблему із маркерами закриття CDATA у даних. Невігластво - це блаженство, мабуть. :)
Stijn de Witt

3
@StijndeWitt абсолютно правильний. CDATA не є панацеєю для втечі від спеціальних символів.
dnault

Це погана ідея. 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.
Kylar

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
ceving

1
Не впевнений, що це тонко, але краще розглянути випадок де t==null.
Myobis

1
@ user1003916: Екранування XML призначене для перетворення будь-яких & випадків у & amp; так ось як це має працювати. Якщо ви втекли з уже втеченого рядка, це ваша вина.
Нуль покажчика

3
Я задоволений остаточною версією. Java SE є компактною, швидкою та ефективною. У моїй книзі завжди краще робити саме те, що потрібно, а не завантажувати ще 100 МБ вірусів.
Роджер Ф. Гей,

11

Цьому питанню вісім років, і він все ще не цілком правильна відповідь! Ні, вам не потрібно імпортувати цілий сторонній 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;
                  // 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();
}

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


+1 для автономного коду. Просто порівнюючи ваш код із реалізацією гуави , мені цікаво, як щодо '\ t', '\ n', '\ r'? Див. Також примітки в документах гуави
jschnasse

2
Немає необхідності втікати \ n, \ r та \ t, вони дійсні, хоча і роблять форматування трохи потворним. Я змінив код, щоб показати, як їх уникнути, якщо це те, що ви хочете.
Mike B

1
Там немає НЕ спосіб «втекти]]>» в CDATA.
kmkaplan

1
Потім він повинен відхилити вміст, викинувши IllegalArgumentException. Він ні за яких обставин не повинен претендувати на успіх, але все одно виводить недійсний XML.
Mike B

Замість заміни незаконних символів у 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

Це має бути прийнятою відповіддю, навіть якщо вона неефективна. Це вирішує проблему одним рядком.
Кішка Стімпсона

І в ньому багато помилок. Дивіться цей коментар вище
Девід Балажич

Щоб виправити ці помилки, ви можете додатково використовувати мій метод тут stackoverflow.com/a/59475093/3882565 . Зверніть увагу, що це не заміна, але її можна використовувати додатково.
stonar96

6

Хоча ідеалізм говорить про використання бібліотеки XML, IMHO, якщо ви маєте базове уявлення про 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/… . Загалом, я вважаю, що Гуава має кращу архітектуру, ніж 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

Примітка. Ваше питання стосується екранування , а не кодування . Escaping використовує <тощо, щоб парсер міг розрізнити "це команда XML" і "це якийсь текст". Кодування - це матеріал, який ви вказуєте в заголовку XML (UTF-8, ISO-8859-1 тощо).

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

Я пропоную ніколи не намагатися бути розумним щодо кодування тексту та екранування в XML. Але нехай це не заважає вам намагатися; просто пам’ятайте, коли це вас вкусить (і так буде).

Тим не менш, якщо ви використовуєте лише UTF-8, щоб зробити речі більш читабельними, ви можете розглянути цю стратегію:

  • Якщо текст містить "<", ">" або "&", оберніть його <![CDATA[ ... ]]>
  • Якщо текст не містить цих трьох символів, не викривляйте його.

Я використовую це в редакторі SQL, і це дозволяє розробникам вирізати та вставити SQL із сторонніх інструментів SQL у XML, не турбуючись про те, щоб уникнути. Це працює, оскільки SQL не може містити umlauts у нашому випадку, тому я в безпеці.


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, якщо щось насправді змінилося - інакше повертається вихідний рядок

У якийсь момент я напишу інверсію цієї функції доUnescaped (). Я просто не встигаю цього зробити сьогодні. Після цього я прийду оновити цю відповідь кодом. :)


Мені це виглядає досить добре. Я не хочу додавати ще одну банку до свого проекту лише за одним методом. Якщо ви надаєте дозвіл, чи можу я скопіювати вставте ваш код у свій?
RuntimeException

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

1
Дякуємо, що дозволили :-) Я буду ним користуватися.
RuntimeException

Ви забули обробляти символи NUL. І, можливо, інші речі теж.
Девід Балажич

@ DavidBalažic Гаразд, будь ласка, поясніть детальніше, що я міг пропустити? Будь ласка, уважніше прочитайте код. Я обробляв КОЖНИЙ ОДИНИЙ символ Unicode (з 1111 998), включаючи nullперсонажа. Чи можете ви пояснити визначення двох значень, UNICODE_LOWі UNICODE_HIGH? Перечитайте, ifщо використовує ці два значення. Зверніть увагу null( \u0000що є (int)0) не потрапляє між цими двома значеннями. Прочитайте, як він стає належним чином "захищеним" так само, як ВСІ символи Unicode, що існують поза діапазоном UNICODE_LOWта UNICODE_HIGHдіапазоном, за допомогою цієї &#техніки.
хаотична рівновага

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. Гуава 26.0 задокументована тут

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

    Примітка: Існує також xmlAttributeEscaper()

  2. Текст 1.4 Apache Commons документально зафіксований тут

    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" у першому рядку "якщо" бути "32"; тобто менше, ніж простір символу? І якщо "31" повинен залишитися, то чи не слід його виправляти, читаючи "if (c <= 31 || ..." (додатковий знак рівності, що
стоїть


1

Просто замініть

 & with &amp;

А щодо інших персонажів:

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

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.