Найкращий спосіб кодування текстових даних для 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.