まず、これは 無効な(悪い/整形式ではない)XMLを解析する方法? の重複ではないことを述べておきます。これは、指定された無効な(または整形式でない)ためです。 )XMLファイルではなく、指定された任意のJava String
に無効なXML文字が含まれている場合と含まれていない場合があります。指定されたDocument
でText
ノードを含むDOM String
を作成し、それを次のように変換しますファイル。ファイルがDOMに解析される場合Document
最初に指定されたString
と等しいString
を取得したい。org.w3c.dom.Document#createTextNode(String data)
を使用してText
ノードを作成し、org.w3c.dom.Node#getTextContent()
。
https://stackoverflow.com/a/28152666/3882565 で確認できるように、XMLファイルのText
ノードには無効な文字がいくつかあります。実際には、Text
ノードには2つの異なるタイプの「無効な」文字があります。 "
、&
、'
、<
、>
、"
、&
、'
、<
、>
などの事前定義されたエンティティがあり、結果ファイルがDOMで解析されたときに、ファイルがAPIで解析されると、ファイルがAPIで解析されると、そのファイルがDOMによって解析されます。ここでの問題は、これが'\u0000'
や'\uffff'
などの他の無効な文字には当てはまらないことです。 '\u0000'
および'\uffff'
は無効な文字であるため、ファイルの解析時に例外が発生します。
おそらく、DOM APIに送信する前に、特定のString
のこれらの文字を独自の方法でエスケープするメソッドを実装し、後でString
を取得したときに元に戻す必要があります。これを行うより良い方法はありますか?誰かが過去にそれらまたは同様の方法を実装しましたか?
編集:この質問は JavaでXMLのテキストデータをエンコードする最良の方法? の重複としてマークされました。これですべての回答を読みましたが、どれも私の問題を解決しません。すべての回答が示唆しています:
"
、&
、'
、<
、>
などを除いて、実際には無効な文字は置き換えられません。"&#number;"
で置き換えると、ファイルの解析時に"�"
などの無効な文字の例外が発生します。"�"
などの不正な文字をサポートしないXMLエンコードメソッドでサードパーティライブラリを使用する(一部のライブラリではスキップされる)。@VGRと@kjhughesが質問の下のコメントで指摘しているように、Base64は確かに私の質問に対する可能な回答です。これで、エスケープに基づいた問題の解決策がわかりました。私は2つの関数escapeInvalidXmlCharacters(String string)
とunescapeInvalidXmlCharacters(String string)
を作成しました。これらは次のように使用できます。
_ String string = "text#text##text#0;text" + '\u0000' + "text<text&text#";
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element element = document.createElement("element");
element.appendChild(document.createTextNode(escapeInvalidXmlCharacters(string)));
document.appendChild(element);
TransformerFactory.newInstance().newTransformer().transform(new DOMSource(document), new StreamResult(new File("test.xml")));
// creates <?xml version="1.0" encoding="UTF-8" standalone="no"?><element>text##text####text##0;text#0;text<text&text##</element>
document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new File("test.xml"));
System.out.println(unescapeInvalidXmlCharacters(document.getDocumentElement().getTextContent()).equals(string));
// prints true
_
escapeInvalidXmlCharacters(String string)
およびunescapeInvalidXmlCharacters(String string)
:
_/**
* Escapes invalid XML Unicode code points in a <code>{@link String}</code>. The
* DOM API already escapes predefined entities, such as {@code "}, {@code &},
* {@code '}, {@code <} and {@code >} for
* <code>{@link org.w3c.dom.Text Text}</code> nodes. Therefore, these Unicode
* code points are ignored by this function. However, there are some other
* invalid XML Unicode code points, such as {@code '\u0000'}, which are even
* invalid in their escaped form, such as {@code "�"}.
* <p>
* This function replaces all {@code '#'} by {@code "##"} and all Unicode code
* points which are not in the ranges #x9 | #xA | #xD | [#x20-#xD7FF] |
* [#xE000-#xFFFD] | [#x10000-#x10FFFF] by the <code>{@link String}</code>
* {@code "#c;"}, where <code>c</code> is the Unicode code point.
*
* @param string the <code>{@link String}</code> to be escaped
* @return the escaped <code>{@link String}</code>
* @see <code>{@link #unescapeInvalidXmlCharacters(String)}</code>
*/
public static String escapeInvalidXmlCharacters(String string) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0, codePoint = 0; i < string.length(); i += Character.charCount(codePoint)) {
codePoint = string.codePointAt(i);
if (codePoint == '#') {
stringBuilder.append("##");
} else if (codePoint == 0x9 || codePoint == 0xA || codePoint == 0xD || codePoint >= 0x20 && codePoint <= 0xD7FF || codePoint >= 0xE000 && codePoint <= 0xFFFD || codePoint >= 0x10000 && codePoint <= 0x10FFFF) {
stringBuilder.appendCodePoint(codePoint);
} else {
stringBuilder.append("#" + codePoint + ";");
}
}
return stringBuilder.toString();
}
/**
* Unescapes invalid XML Unicode code points in a <code>{@link String}</code>.
* Makes <code>{@link #escapeInvalidXmlCharacters(String)}</code> undone.
*
* @param string the <code>{@link String}</code> to be unescaped
* @return the unescaped <code>{@link String}</code>
* @see <code>{@link #escapeInvalidXmlCharacters(String)}</code>
*/
public static String unescapeInvalidXmlCharacters(String string) {
StringBuilder stringBuilder = new StringBuilder();
boolean escaped = false;
for (int i = 0, codePoint = 0; i < string.length(); i += Character.charCount(codePoint)) {
codePoint = string.codePointAt(i);
if (escaped) {
stringBuilder.appendCodePoint(codePoint);
escaped = false;
} else if (codePoint == '#') {
StringBuilder intBuilder = new StringBuilder();
int j;
for (j = i + 1; j < string.length(); j += Character.charCount(codePoint)) {
codePoint = string.codePointAt(j);
if (codePoint == ';') {
escaped = true;
break;
}
if (codePoint >= 48 && codePoint <= 57) {
intBuilder.appendCodePoint(codePoint);
} else {
break;
}
}
if (escaped) {
try {
codePoint = Integer.parseInt(intBuilder.toString());
stringBuilder.appendCodePoint(codePoint);
escaped = false;
i = j;
} catch (IllegalArgumentException e) {
codePoint = '#';
escaped = true;
}
} else {
codePoint = '#';
escaped = true;
}
} else {
stringBuilder.appendCodePoint(codePoint);
}
}
return stringBuilder.toString();
}
_
これらの関数はおそらく非常に非効率的であり、より良い方法で書くことができることに注意してください。コメントでコードを改善するための提案を自由に投稿してください。
1つの手法は、文字列全体をBase64でエンコードされたUTF8としてエンコードすることです。
しかし、「特殊」文字がまれである場合、それは可読性とファイルサイズを大幅に犠牲にします。
別の手法は、特殊文字を処理命令として表すことです。たとえば、<?U 0000?>
(コードポイント0)。
もう1つは、バックスラッシュエスケープを使用することです。たとえば、コードポイント0には\ u0000、バックスラッシュ自体には\を使用します。これには、これを実行する既存のライブラリルーチン(JSON変換ライブラリなど)を見つけることができるという利点があります。あなたの要件がそのようなライブラリーを使用できないと言っている理由を私は想像できません。しかし、本当にそれができない場合でも、自分でコードを書くことは難しくありません。
最も簡単な解決策は、XML 1.1(org.w3c.dom
)このプリプロセッサを使用して:
<?xml version=1.1 encoding=UTF-8 standalone=yes?>
Wikipedia によると、XML 1.1の無効な文字はU + 0000、 サロゲート 、 + FFFEおよびU + FFFF のみです。
このコードスニペットを使用すると、不正な文字を省略して、常に正しいXML 1.1文字列を取得できます(まったく同じ文字列が必要な場合は、探しているものとは異なる場合があります)。
public static String escape(String orig) {
StringBuilder builder = new StringBuilder();
for (char c : orig.toCharArray()) {
if (c == 0x0 || c == 0xfffe || c == 0xffff || (c >= 0xd800 && c <= 0xdfff)) {
continue;
} else if (c == '\'') {
builder.append("'");
} else if (c == '"') {
builder.append(""");
} else if (c == '&') {
builder.append("&");
} else if (c == '<') {
builder.append("<");
} else if (c == '>') {
builder.append(">");
} else if (c <= 0x1f) {
builder.append("&#" + ((int) c) + ";");
} else {
builder.append(c);
}
}
return builder.toString();
}