JavaでXMLのテキストデータをエンコードする最良の方法は?


93

Javaを除いて、この質問とよく似ています。

JavaでXML出力の文字列をエンコードする推奨方法は何ですか。文字列には、「&」、「<」などの文字が含まれる場合があります。

回答:


40

非常に簡単です。XMLライブラリを使用します。そうすれば、XML仕様のビットに関する詳細な知識を必要とするのではなく、実際に正しくなります。


25
そのようなライブラリをお勧めできますか?(これがJavaエディション5の標準的な部分ではないのは驚くべきことです...そのような一般的なタスクです)。
Tim Cooper、

4
XML 標準のJavaフレームワークの一部です-org.w3c.saxとorg.w3c.domを調べてください。ただし、JDomなど、使いやすいフレームワークもいくつかあります。「XML出力用に文字列をエンコードする」方法がない可能性があることに注意してください。文字列操作で一度にビットを実行するだけでなく、XMLタスク全体をライブラリで実行することをお勧めしました。
Jon Skeet、

1
XHTMLを出力する場合、これはそれほど有用なアドバイスではありません-FlyingSaucerにはXMLが必要ですが、XML libを介してテンプレートを作成する方法はありません。ありがたいことに、StringTemplateを使用すると、すべてのStringオブジェクトをすばやくエスケープできます。
Stephen

4
@mice:質問のタグはJavaで、Javaには多数のXMLライブラリあります。実際、XML APIはJavaに組み込まれているので、他にも追加する必要はありません...しかし、追加したとしても、最近のモバイル以外では、数百Kの問題はめったにありません。Javaでなくても、XML APIを持たないプラットフォームでの開発には非常に注意が必要です...
Jon Skeet

2
@mice:DOM APIは完全にXMLを生成できます。または、かなり小さなサードパーティライブラリがあります。(例えば、JDomのjarファイルは114Kです。)XML APIの使用は、XMLを作成するための推奨方法です。
Jon Skeet、2012

123

他の人が述べたように、XMLライブラリを使用するのが最も簡単な方法です。脱出したい場合StringEscapeUtilsは、Apache Commons Langライブラリから調べることができます。


これは、たとえばプロトタイプを作成する場合など、絶対的な正確さを気にしない場合に適した方法です。
チェイスセイバート

2
StringEscapeUtils.escapeXml(str)から使用してくださいcommons-lang。App Engineアプリケーションで使用します-魅力のように動作します。ここでJavaのドキュメントは、この機能のために:
オレグK

StringEscapeUtilsのescapeXmlメソッドは少しコストがかかるようです。Stringの代わりにStringBufferを操作するより効率的なメソッドはありますか?
CK

このメソッドは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を作成する必要があります。
Mads Hansen、

2
@ Mads、CDATAを使用すると、有効なXMLファイルが生成されるため、「正しい方法」で行うのと同じくらい問題ありません。気に入らなかった場合は、後で解析し、アイデンティティを変換して印刷します。
するThorbjörnRavnアンデルセン

24
CDATA要素でテキストをラップする場合は、CDATA終了マーカーをエスケープする必要があります: "]]>" ...それをエスケープすることはできません。したがって、代わりにコードを分割して、データの半分を1つのCDATA要素に、残りの半分を1秒に配置する必要があります。<![CDATA [このデータにはCDATA終了マーカーが含まれています: "]]]]> <! [CDATA [> "そのため、分割する必要がありました。]]> ...最後に、「<」、「>」、「&」をエスケープするだけの方がはるかに簡単な場合があります。もちろん、多くのアプリはデータのCDATA終了マーカーの潜在的な問題を無視します。無知は至福だと思います。:)
Stijn de Witt 2010

3
@StijndeWittは完全に正しいです。CDATAは、特殊文字をエスケープするための万能薬ではありません。
ドナウ14

これは悪い考えです。CDATAは、XMLのエンコーディング以外の文字を許可しません。
フロリアンF

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
私が見ることができる少なくとも2つのバグがあります。1つは微妙で、もう1つはそうではありません。そもそもそのようなバグはありません-そもそもホイールを再発明しないからです。
Jon Skeet

1
また、Unicode文字列の反復処理は少し複雑です。ここを参照してください:stackoverflow.com/q/1527856/402322
ceving

1
微妙かどうかはわかりませんが、の場合を検討することをお勧めしますt==null
Myobis 2013

1
@ user1003916:XMLエスケープは、すべての&出現を&amp;に変換するように設計されています これが動作する方法です。すでにエスケープされた文字列を除外する場合、それはあなたの責任です。
Pointer Null '19

3
最終バージョンに満足しています。Java SEはコンパクト、高速、そして効率的です。私の本では、別の100 MBのブロートウェアをダウンロードするのではなく、必要なことだけを行う方が常に優れています。
ロジャーF.ゲイ

11

この質問は8歳ですが、まだ完全な正解ではありません。いいえ、この単純なタスクを実行するためにサードパーティのAPI全体をインポートする必要はありません。悪いアドバイス。

次のメソッドは:

  • 基本的な多言語面外の文字を正しく処理する
  • XMLで必要なエスケープ文字
  • オプションですが一般的な非ASCII文字をエスケープします
  • 置き換え違法 Unicodeの置換文字とXML 1.0の文字を。ここに最良のオプションはありません-それらを削除することも同様に有効です。

私は最も一般的なケースに合わせて最適化しようとしましたが、/ 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を処理するための完全に優れたJava APIがある場合に独自のコードを書くのは愚かだと主張し続ける人にとって、Oracle Java 8に含まれているStAX API(私は他のものをテストしていません)を知りたいかもしれません)CDATAコンテンツを正しくエンコードできません。コンテンツの]]>シーケンスをエスケープしません。サードパーティのライブラリは、Javaコアの一部であっても、常に最良のオプションであるとは限りません。


スタンドアロンコードの場合は+1。コードをグアバ実装と比較するだけで、「\ t」、「\ n」、「\ r」はどうなのでしょうか。
guava

2
\ n、\ r、および\ tをエスケープする必要はありません。書式設定が少し見苦しくなりますが、有効です。必要に応じてそれらをエスケープする方法を示すようにコードを変更しました。
マイクB

1
CDATAで「]]>をエスケープする方法はありません
kmkaplan

1
次に、IllegalArgumentExceptionをスローしてコンテンツを拒否します。どのような状況でも成功すると主張するべきですが、それでも無効なXMLを出力します。
マイクB

代わりにUnicodeの置換文字とXML 1.0に不正な文字を置き換えるので、あなたはここで私の方法を使用することができますstackoverflow.com/a/59475093/3882565
stonar96

8

StringEscapeUtils.escapeXml()制御文字をエスケープしません(<0x20)。XML 1.1では制御文字を使用できます。XML 1.0ではサポートされていません。たとえばXStream.toXML()、Javaオブジェクトの制御文字をXMLに喜んでシリアライズします。XML1.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 2015年

これは非効率的であるとしても、受け入れられた答えであるべきです。問題を1行で解決します。
Stimpson Cat、

そして、それは多くのバグを持っています。上記のこのコメントを
DavidBalažic18年8

これらのバグを修正するには、ここで私の方法を追加で使用できます。stackoverflow.com/a/59475093/3882565。これは置き換えではありませんが、追加で使用できます。
stonar96

6

理想主義はXMLライブラリを使用すると言いますが、IMHOがXMLの基本的な考え方を持っている場合、常識とパフォーマンスはそれをすべてテンプレート化すると言います。それは間違いなくもっと読みやすいです。ライブラリのエスケープルーチンを使用することは、おそらく良い考えです。

これを考慮してください:XML 人間によって書かれることを意図されていました。

XMLを「オブジェクト」として使用する場合は、ライブラリを使用してXMLを生成し、問題をより適切にモデル化します。たとえば、プラグイン可能なモジュールがこのXMLの構築プロセスに参加している場合。

編集:実際にテンプレートでXMLをエスケープする方法については、CDATAの使用またはescapeXml(string)JSTLからの使用が2つの優れたソリューションであり、次の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に変更されました。0x7fより大きいUnicode文字をエスケープしなくなりました。

これは良いことです。古い方法は、utf8ドキュメントに挿入するだけのエンティティをエスケープしたいと少し思っていました。

Google Guava 11.0に含まれる新しいエスケーパーも期待できるようです:http ://code.google.com/p/guava-libraries/issues/detail?id=799


1
これがGuavaのXMLエスケーパです:code.google.com/p/guava-libraries/source/browse/guava/src/com/…。一般的に、Apache CommonsよりもGuavaの方がアーキテクチャが優れていることがわかりました。
jhclark 2012年


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[ ... ]]>
  • テキストにこれらの3つの文字が含まれていない場合は、ワープしないでください。

私はこれをSQLエディターで使用しています。開発者は、エスケープを心配することなく、サードパーティのSQLツールからXMLにSQLを切り取って貼り付けることができます。私たちの場合、SQLにウムラウトを含めることができないため、これは機能し、安全です。


5

私は基本的にJon Skeetに同意しますが、外部XMLライブラリを使用するオプションがない場合があります。そして、単純な値(属性またはタグ、完全なドキュメントではない)をエスケープ/アンエスケープする2つの関数は、Javaに含まれている標準XMLライブラリでは使用できないのが特徴です。

その結果、私がここや他の場所に投稿したさまざまな回答に基づいて、私が作成したソリューションは次のとおりです(単純なコピー/貼り付けとして機能するものはありません)。

  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. 確率が2番目の「if」条件が最もよく使用される経路である可能性がある場合、可能な限り効率的になる試み
  3. 純粋な関数です。つまり、スレッドセーフです
  4. 何かが実際に変更された場合にのみStringBuilderのコンテンツを返すことにより、ガベージコレクターで適切に最適化します。それ以外の場合は、元の文字列が返されます

ある時点で、この関数の逆であるtoUnescaped()を記述します。今日はそれをする時間がありません。私がそうしたら、この答えをコードで更新します。:)


私にはかなりよさそうだ。1つの方法でプロジェクトに別のjarを追加したくありません。許可を与えていただければ、コードをコピーして私の中に貼り付けてもいいですか?
RuntimeException 2014年

1
@SatishMotwaniもちろん、上記のコードを好きなように使用することができます。StackOverflowで公開されたコードはすべて著作権フリーであると想定されていると私は理解しています(全体として著作物として扱われていません)。逆に言えば、誰かが著作権の主張を押し付けて、自分自身に何らかの結果を期待することは非常に困難です。
chaotic3quilibrium 2014年

1
許可してくれてありがとう:-)私はそれを使います。
RuntimeException 2014年

NUL文字を処理するのを忘れました。そして多分他のものも。
デビッドBalažic18年

3

XML文字をエスケープするための最も簡単な方法は、http//commons.apache.org/lang/からダウンロード可能なJARであるApache Commons Langプロジェクトを使用することです。

クラスはこれです:org.apache.commons.lang3.StringEscapeUtils;

「escapeXml」という名前のメソッドがあり、適切にエスケープされた文字列を返します。


更新:escapeXmlは非推奨になりました-escapeXml10を使用してください。参考commons.apache.org/proper/commons-lang/javadocs/api-3.3/org/...
ダニエル


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!

「if」の最初の行の「31」が「32」であってはなりません。つまり、スペース文字よりも小さいですか?そして、「31」が残っている必要がある場合は、「if(c <= 31 || ...」(より小記号に続く追加の等号)を読み取るように修正すべきではありませんか?
chaotic3quilibrium


1

交換するだけ

 & with &amp;

そして他のキャラクターのために:

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


0

Apache XMLシリアライザーを使用して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()

これが誰かを助けることを願っています

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.