Il modo migliore per codificare i dati di testo per XML in Java?


93

Molto simile a questa domanda , ad eccezione di Java.

Qual è il modo consigliato per codificare le stringhe per un output XML in Java. Le stringhe potrebbero contenere caratteri come "&", "<" e così via.

Risposte:


40

Molto semplicemente: usa una libreria XML. In questo modo sarà effettivamente giusto invece di richiedere una conoscenza dettagliata dei bit delle specifiche XML.


25
Puoi consigliare una biblioteca del genere? (Trovo sorprendente che questa non sia una parte standard dell'edizione 5 di Java ... un'attività così comune).
Tim Cooper,

4
XML fa parte del framework Java standard: guarda in org.w3c.sax e org.w3c.dom. Tuttavia, ci sono anche alcuni framework più facili da usare, come JDom. Si noti che potrebbe non esserci un metodo di "codifica delle stringhe per l'output XML": raccomandavo di più che l'intera attività XML dovrebbe essere eseguita con una libreria piuttosto che eseguire solo bit alla volta con la manipolazione delle stringhe.
Jon Skeet

1
Questo non è un consiglio così utile quando si esegue l'output di XHTML - FlyingSaucer richiede XML, ma non è possibile che io stia creando modelli tramite una libreria XML :). Per fortuna StringTemplate mi consente di sfuggire rapidamente a tutti gli oggetti String.
Stephen

4
@mice: la domanda è contrassegnata da Java e Java ha molte librerie XML. Infatti, ci sono le API XML cotto in Java, quindi non ci sarebbe bisogno di aggiungere niente altro ... ma anche se avete fatto, a poche centinaia di K è raramente un problema al di fuori di telefonia mobile in questi giorni. Anche se non fosse Java, sarei molto cauto nello sviluppare su una piattaforma che non avesse API XML ...
Jon Skeet

2
@mice: l'API DOM è perfettamente in grado di generare XML. Oppure ci sono librerie di terze parti abbastanza piccole. (Ad esempio, il file jar di JDom è 114K.) L' utilizzo di un'API XML è ancora il modo consigliato per creare XML.
Jon Skeet

123

Come altri hanno già detto, l'utilizzo di una libreria XML è il modo più semplice. Se vuoi scappare da te stesso, puoi cercare StringEscapeUtilsdalla libreria Apache Commons Lang .


Questa potrebbe essere la strada da percorrere se non ti interessa la correttezza assoluta, ad esempio se stai mettendo insieme un prototipo.
Chase Seibert

2
Usa StringEscapeUtils.escapeXml(str)da commons-lang. Lo uso nell'applicazione App Engine: funziona a meraviglia. Ecco il Java Doc per questa funzione:
Oleg K

Il metodo escapeXml di StringEscapeUtils sembra essere un po 'costoso. Esiste un metodo più efficiente che opera su StringBuffer invece che su String?
CKing

Questo metodo funziona sia per il contenuto XML che per gli attributi? A me sembra che non funzioni per gli attributi. Non sembra scappare \t, \ne \r.
Lii

@Lii e \t, \no \rdeve essere evitato?
Betlista

20

Basta usare.

<![CDATA[ your text here ]]>

Ciò consentirà qualsiasi carattere tranne il finale

]]>

Quindi puoi includere caratteri illegali come & e>. Per esempio.

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

Tuttavia, sarà necessario eseguire l'escape degli attributi poiché i blocchi CDATA non possono essere utilizzati per essi.


11
Nella maggior parte dei casi, non è quello che dovresti fare. Troppe persone abusano dei tag CDATA. L'intento del CDATA è di dire al processore di non elaborarlo come XML e di passarlo semplicemente attraverso. Se stai cercando di creare un file XML, dovresti creare XML, non solo passare byte attraverso un elemento di wrapping.
Mads Hansen

2
@Mads, utilizzando CDATA si ottiene un file XML valido, quindi va bene quanto farlo nel "modo giusto". Se non ti piace, analizzalo in seguito, l'identità lo trasforma e lo stampa.
Thorbjørn Ravn Andersen

24
Se metti a capo il testo in un elemento CDATA devi sfuggire al marcatore di chiusura CDATA: "]]>" ... tranne che non puoi sfuggire a questo. Quindi invece devi suddividere il tuo codice in parti in cui metti metà dei dati in un elemento CDATA e l'altra metà in un secondo: <! [CDATA [Questi dati contengono un marcatore di chiusura CDATA: "]]]]> <! [CDATA [> "questo è il motivo per cui doveva essere diviso.]]> ... Alla fine potrebbe essere molto più semplice sfuggire a" <","> "e" & ". Ovviamente molte app ignorano il potenziale problema con i marcatori di chiusura CDATA nei dati. L'ignoranza è una gioia, immagino. :)
Stijn de Witt

3
@StijndeWitt è assolutamente corretto. CDATA non è una panacea per sfuggire ai caratteri speciali.
dnault

Questa è una cattiva idea. CDATA non consente alcun carattere al di fuori della codifica XML.
Florian F,

14

Questo ha funzionato bene per me per fornire una versione con escape di una stringa di testo:

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 + ";"); Questo non funzionerà per i caratteri multibyte. Mi sto imbattendo in questo in questo momento con un carattere emoji, sequenza UTF8 F0 9F 98 8D.
Kylar

14

Prova questo:

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
Hai almeno due bug che posso vedere. Uno è sottile, l'altro no. Non avrei un tale bug, perché non reinventerei la ruota in primo luogo.
Jon Skeet

1
E l'iterazione attraverso le stringhe Unicode è un po 'più complicata. Vedi qui: stackoverflow.com/q/1527856/402322
ceving

1
Non sono sicuro che sia sottile, ma è meglio considerare il caso in cui t==null.
Myobis

1
@ user1003916: l'escape XML è progettato per convertire qualsiasi & occorrenza in & amp; quindi è così che deve funzionare. Se esegui l'escape di una stringa già sfuggita, è colpa tua.
Pointer Null

3
Sono contento della versione finale. Java SE è compatto, veloce ed efficiente. Fare solo quello che deve essere fatto piuttosto che scaricare altri 100 MB di bloatware è sempre meglio nel mio libro.
Roger F. Gay

11

Questa domanda ha otto anni e non è ancora una risposta del tutto corretta! No, non dovresti importare un'intera API di terze parti per eseguire questa semplice operazione. Cattivo consiglio.

Il metodo seguente:

  • gestire correttamente i caratteri al di fuori del piano multilingue di base
  • caratteri di escape richiesti in XML
  • sfuggire a qualsiasi carattere non ASCII, che è opzionale ma comune
  • sostituire i caratteri illegali in XML 1.0 con il carattere di sostituzione Unicode. Non esiste un'opzione migliore qui: rimuoverli è altrettanto valido.

Ho cercato di ottimizzare per il caso più comune, assicurandomi comunque di poter eseguire il pipe / dev / random attraverso questo e ottenere una stringa valida in 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();
}

Modifica: per coloro che continuano a insistere che è sciocco scrivere il proprio codice per questo quando ci sono API Java perfettamente buone per gestire XML, potresti voler sapere che l'API StAX inclusa con Oracle Java 8 (non ne ho testate altre ) non riesce a codificare correttamente il contenuto CDATA: non sfugge]]> sequenze nel contenuto. Una libreria di terze parti, anche una che fa parte del core Java, non è sempre l'opzione migliore.


+1 per il codice autonomo. Sto solo confrontando il tuo codice con l' implementazione di guava , mi chiedo cosa mi dici di '\ t', '\ n', '\ r'? Vedi anche note su guava docs
jschnasse

2
Non è necessario eseguire l'escape \ n, \ r e \ t, sono validi, anche se rendono la formattazione un po 'brutta. Ho modificato il codice per mostrare come evitarli se è quello che vuoi.
Mike B

1
Non c'è modo di "uscire]]>" in CDATA.
kmkaplan

1
Quindi dovrebbe rifiutare il contenuto lanciando un'eccezione IllegalArgumentException. In nessuna circostanza dovrebbe pretendere di avere successo ma continuare a produrre XML non valido.
Mike B

Invece di sostituire i caratteri illegali in XML 1.0 con il carattere di sostituzione Unicode, puoi usare i miei metodi qui stackoverflow.com/a/59475093/3882565 .
stonar96

8

StringEscapeUtils.escapeXml()non sfugge ai caratteri di controllo (<0x20). XML 1.1 consente caratteri di controllo; XML 1.0 non lo fa. Ad esempio, XStream.toXML()serializzerà felicemente i caratteri di controllo di un oggetto Java in XML, che un parser XML 1.0 rifiuterà.

Per sfuggire ai personaggi di controllo con Apache commons-lang, usa

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
Il concatenamento delle replaceAllchiamate è molto inefficiente, soprattutto per le stringhe di grandi dimensioni. Ogni chiamata risulta nella creazione di un nuovo oggetto String, che rimarrà in sospeso fino alla raccolta dei rifiuti. Inoltre, ogni chiamata richiede di ripetere il ciclo attraverso la stringa. Questo potrebbe essere consolidato in un unico ciclo manuale con confronti con ogni carattere di destinazione in ogni iterazione.
daiscog

Questa dovrebbe essere la risposta accettata, anche se inefficiente. Risolve il problema in una sola riga.
Stimpson Cat

E ha molti bug. Vedi questo commento sopra
David Balažic

Per correggere questi bug puoi anche utilizzare il mio metodo qui stackoverflow.com/a/59475093/3882565 . Nota che questo non è un sostituto ma può essere utilizzato in aggiunta.
stonar96

6

Mentre l'idealismo dice di usare una libreria XML, IMHO se hai un'idea di base di XML, il buon senso e le prestazioni dicono che il modello è tutto. È probabilmente anche più leggibile. Anche se l'uso delle routine di escape di una libreria è probabilmente una buona idea.

Considerate questo: XML è stato pensato per essere scritto da esseri umani.

Usa le librerie per generare XML quando il tuo XML come "oggetto" modella meglio il tuo problema. Ad esempio, se i moduli collegabili partecipano al processo di creazione di questo XML.

Modifica: per quanto riguarda come sfuggire effettivamente a XML nei modelli, l'uso di CDATA o escapeXml(string)da JSTL sono due buone soluzioni, escapeXml(string)possono essere utilizzate in questo modo:

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

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

6

Il comportamento di StringEscapeUtils.escapeXml () è stato modificato da Commons Lang 2.5 a 3.0. Ora non esegue più l'escape dei caratteri Unicode maggiori di 0x7f.

Questa è una buona cosa, il vecchio metodo doveva essere un po 'impaziente di sfuggire a entità che potevano essere semplicemente inserite in un documento utf8.

Anche i nuovi escapers da includere in Google Guava 11.0 sembrano promettenti: http://code.google.com/p/guava-libraries/issues/detail?id=799


1
Ecco l'escaper XML di Guava: code.google.com/p/guava-libraries/source/browse/guava/src/com/… . In generale, ho trovato Guava meglio progettato di Apache Commons.
jhclark


6

Per coloro che cercano la soluzione più veloce da scrivere: utilizzare i metodi di apache commons-lang :

Ricorda di includere la dipendenza:

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

5

Nota: la tua domanda riguarda l' escape , non la codifica . L'escape sta usando <, ecc. Per consentire al parser di distinguere tra "questo è un comando XML" e "questo è un testo". La codifica è ciò che specifichi nell'intestazione XML (UTF-8, ISO-8859-1, ecc.).

Prima di tutto, come hanno detto tutti gli altri, usa una libreria XML. XML sembra semplice ma la codifica + le cose di escape sono dark voodoo (che noterai non appena incontrerai dieresi e giapponese e altre cose strane come " cifre a larghezza intera " (& # FF11; è 1)). Mantenere XML leggibile dagli umani è un compito di Sisyphus.

Suggerisco di non provare mai ad essere intelligente sulla codifica del testo e sull'escape in XML. Ma non lasciare che questo ti impedisca di provare; ricorda solo quando ti morde (e lo farà).

Detto questo, se usi solo UTF-8, per rendere le cose più leggibili puoi considerare questa strategia:

  • Se il testo contiene "<", ">" o "&", racchiudilo <![CDATA[ ... ]]>
  • Se il testo non contiene questi tre caratteri, non deformarlo.

Lo sto usando in un editor SQL e consente agli sviluppatori di tagliare e incollare SQL da uno strumento SQL di terze parti nell'XML senza doversi preoccupare dell'escape. Questo funziona perché l'SQL non può contenere dieresi nel nostro caso, quindi sono al sicuro.


5

Anche se in linea di principio sono d'accordo con Jon Skeet, a volte non ho la possibilità di utilizzare una libreria XML esterna. E trovo peculiare che le due funzioni per eseguire l'escape / unescape di un valore semplice (attributo o tag, documento non completo) non siano disponibili nelle librerie XML standard incluse in Java.

Di conseguenza e in base alle diverse risposte che ho visto pubblicate qui e altrove, ecco la soluzione che ho finito per creare (niente ha funzionato come un semplice copia / incolla):

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

Quanto sopra ospita diverse cose:

  1. evita di usare la logica basata sui caratteri fino a quando non è assolutamente necessario - migliora la compatibilità unicode
  2. cerca di essere il più efficiente possibile dato che la probabilità è la seconda condizione "se" è probabilmente il percorso più utilizzato
  3. è una funzione pura; ie è thread-safe
  4. si ottimizza bene con il garbage collector restituendo solo il contenuto di StringBuilder se qualcosa è effettivamente cambiato, altrimenti viene restituita la stringa originale

Ad un certo punto scriverò l'inversione di questa funzione, toUnescaped (). Non ho tempo per farlo oggi. Quando lo farò, verrò ad aggiornare questa risposta con il codice. :)


Mi sembra abbastanza buono. Non desidero aggiungere un altro jar al mio progetto per un solo metodo. Se concedi l'autorizzazione, posso copiare e incollare il tuo codice nel mio?
RuntimeException

1
@ SatishMotwani Ovviamente puoi prendere il codice sopra e farlo come preferisci. A quanto mi risulta, si presume che qualsiasi codice pubblicato su StackOverflow sia privo di copyright (non è coperto come opera nella sua totalità). D'altro canto, sarebbe estremamente difficile per qualcuno spingere qualsiasi tipo di rivendicazione sul copyright e aspettarsi una sorta di risultato per se stesso.
caotic3quilibrium

1
Grazie per il permesso :-) Lo userò.
RuntimeException

Hai dimenticato di gestire i caratteri NUL. E forse anche altre cose.
David Balažic


3

Se stai cercando una libreria per portare a termine il lavoro, prova:

  1. Guava 26.0 documentato qui

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

    Nota: esiste anche un file xmlAttributeEscaper()

  2. Apache Commons Text 1.4 documentato qui

    StringEscapeUtils.escapeXml11(text)

    Nota: esiste anche un escapeXml10()metodo


1

Ecco una soluzione semplice ed è ottima anche per codificare i caratteri accentati!

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

Uscite

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

Il "31" nella prima riga di "if" non dovrebbe essere "32"; cioè meno del carattere spazio? E se "31" deve rimanere, allora non dovrebbe essere corretto per leggere "if (c <= 31 || ..." (segno di uguale aggiuntivo dopo il segno di minore di)?
caotico3quilibrio


1

Basta sostituire

 & with &amp;

E per altri personaggi:

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

0

Usa JAXP e dimentica la gestione del testo, sarà fatto automaticamente.


Il tuo link è in spagnolo, il che non è così utile per la maggior parte di noi. È meglio questo .
Vivit

0

Prova a codificare l'XML usando il serializzatore XML di Apache

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

Ecco cosa ho trovato dopo aver cercato ovunque una soluzione:

Ottieni la libreria Jsoup:

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

Poi:

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

Spero che questo aiuti qualcuno

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.