XMLのテキストデータをエンコードする最適な方法
質問
Xml要素または属性で使用する文字列をエンコードするための.Netの汎用メソッドを探していましたが、すぐに見つからなかったときは驚きました。それでは、さらに先に進む前に、組み込み関数が欠落している可能性がありますか?
実際には存在しないと仮定して、私は独自の一般的なEncodeForXml(string data)
メソッドを作成し、これを行うための最良の方法を考えています。
この全体を促した使用データには、<!> amp;、<!> lt;、<!> quot;などの不適切な文字が含まれている可能性があります。 <!> amp; amp;、<!> amp; lt ;、および<!> amp; quot;。これは、CDATAセクションを使用するだけでは最良のアイデアではないことを意味します。とにかく不器用そうです。むしろ、xmlで直接使用できる素敵な文字列値になります。
過去に正規表現を使用して不正なアンパサンドをキャッチしましたが、この場合と最初のステップでそれらをキャッチし、他の文字の単純な置換を行うことを考えています。
それで、複雑になりすぎることなく、これをさらに最適化できますか? :
Function EncodeForXml(ByVal data As String) As String
Static badAmpersand As new Regex("&(?![a-zA-Z]{2,6};|#[0-9]{2,4};)")
data = badAmpersand.Replace(data, "&")
return data.Replace("<", "<").Replace("""", """).Replace(">", "gt;")
End Function
C#のみの皆さん-ごめんなさい。どの言語を使用するかはあまり気にしませんが、Regexを静的にしたかったので、メソッド外で宣言せずにC#でそれを行うことはできません。 、これはVB.Netになります
最後に、私はまだ仕事をしている.Net 2.0を使用していますが、最終製品を取得して文字列クラスの拡張メソッドに変換できれば、それもかなりクールです。
更新最初のいくつかの応答は、.Netに実際にこれを行う組み込み方法があることを示しています。しかし、今は始めたので、楽しみのためにEncodeForXml()メソッドを終了したいので、まだ改善のためのアイデアを探しています。特に:エンティティ(おそらくリスト/マップに保存される)としてエンコードされるべき文字の完全なリスト、および不変文字列をシリアルで.Replace()するよりもパフォーマンスが向上するもの。
解決
System.XMLがエンコードを処理するため、このようなメソッドは必要ありません。
他のヒント
入力についてどれだけ知っているかに応じて、ではないことを考慮する必要があります。すべてのUnicode文字は有効なXML文字です。
Server.HtmlEncode と System.Security.SecurityElement.Escape の両方が不正なXML文字を無視しているように見えますが、 System.XML.XmlWriter.WriteString 不正な文字に遭遇すると ArgumentException をスローします(そのチェックを無効にしない限り、無視します)。ライブラリ関数の概要は、こちら。
2011/8/14の編集:過去数年間で少なくとも数人がこの回答を参考にしていることがわかったため、元のコードを完全に書き直すことにしました。 a href = "https://stackoverflow.com/questions/1049947/should-utf-16-be-considered-harmful">恐ろしくUTF-16を誤って処理する。
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
/// <summary>
/// Encodes data so that it can be safely embedded as text in XML documents.
/// </summary>
public class XmlTextEncoder : TextReader {
public static string Encode(string s) {
using (var stream = new StringReader(s))
using (var encoder = new XmlTextEncoder(stream)) {
return encoder.ReadToEnd();
}
}
/// <param name="source">The data to be encoded in UTF-16 format.</param>
/// <param name="filterIllegalChars">It is illegal to encode certain
/// characters in XML. If true, silently omit these characters from the
/// output; if false, throw an error when encountered.</param>
public XmlTextEncoder(TextReader source, bool filterIllegalChars=true) {
_source = source;
_filterIllegalChars = filterIllegalChars;
}
readonly Queue<char> _buf = new Queue<char>();
readonly bool _filterIllegalChars;
readonly TextReader _source;
public override int Peek() {
PopulateBuffer();
if (_buf.Count == 0) return -1;
return _buf.Peek();
}
public override int Read() {
PopulateBuffer();
if (_buf.Count == 0) return -1;
return _buf.Dequeue();
}
void PopulateBuffer() {
const int endSentinel = -1;
while (_buf.Count == 0 && _source.Peek() != endSentinel) {
// Strings in .NET are assumed to be UTF-16 encoded [1].
var c = (char) _source.Read();
if (Entities.ContainsKey(c)) {
// Encode all entities defined in the XML spec [2].
foreach (var i in Entities[c]) _buf.Enqueue(i);
} else if (!(0x0 <= c && c <= 0x8) &&
!new[] { 0xB, 0xC }.Contains(c) &&
!(0xE <= c && c <= 0x1F) &&
!(0x7F <= c && c <= 0x84) &&
!(0x86 <= c && c <= 0x9F) &&
!(0xD800 <= c && c <= 0xDFFF) &&
!new[] { 0xFFFE, 0xFFFF }.Contains(c)) {
// Allow if the Unicode codepoint is legal in XML [3].
_buf.Enqueue(c);
} else if (char.IsHighSurrogate(c) &&
_source.Peek() != endSentinel &&
char.IsLowSurrogate((char) _source.Peek())) {
// Allow well-formed surrogate pairs [1].
_buf.Enqueue(c);
_buf.Enqueue((char) _source.Read());
} else if (!_filterIllegalChars) {
// Note that we cannot encode illegal characters as entity
// references due to the "Legal Character" constraint of
// XML [4]. Nor are they allowed in CDATA sections [5].
throw new ArgumentException(
String.Format("Illegal character: '{0:X}'", (int) c));
}
}
}
static readonly Dictionary<char,string> Entities =
new Dictionary<char,string> {
{ '"', """ }, { '&', "&"}, { '\'', "'" },
{ '<', "<" }, { '>', ">" },
};
// References:
// [1] http://en.wikipedia.org/wiki/UTF-16/UCS-2
// [2] http://www.w3.org/TR/xml11/#sec-predefined-ent
// [3] http://www.w3.org/TR/xml11/#charsets
// [4] http://www.w3.org/TR/xml11/#sec-references
// [5] http://www.w3.org/TR/xml11/#sec-cdata-sect
}
ユニットテストと完全なコードは、こちらにあります。 。
SecurityElement.Escape
ドキュメントこちら
過去にHttpUtility.HtmlEncodeを使用してxmlのテキストをエンコードしました。本当に同じタスクを実行します。私はまだそれに関する問題に遭遇していませんが、それは私が将来にしないと言うことではありません。名前が示すように、XMLではなくHTML用に作成されました。
おそらく既に読んでいますが、こちらxmlエンコードおよびデコードに関する記事。
編集:もちろん、xmlwriterまたは新しいXElementクラスの1つを使用する場合、このエンコードは自動的に行われます。実際、テキストを取得して新しいXElementインスタンスに配置し、要素の文字列(.tostring)バージョンを返すことができます。 SecurityElement.Escape はユーティリティメソッドと同じタスクを実行しますが、それについて詳しく読んだり使用したりしません。
EDIT2:まだ2.0を使用しているため、XElementに関する私のコメントは無視してください
Microsoftの AntiXssライブラリ AntiXssEncoderクラス。 dllにはこのためのメソッドがあります:
AntiXss.XmlEncode(string s)
AntiXss.XmlAttributeEncode(string s)
HTMLもあります:
AntiXss.HtmlEncode(string s)
AntiXss.HtmlAttributeEncode(string s)
.net 3.5以降
new XText("I <want> to & encode this for XML").ToString();
あなたに与えます:
I <want> to & encode this for XML
このメソッドは、引用符などの必要なものをエンコードしないことを確認します。
SecurityElement.Escape
( workmad3の回答)はこれでより良い仕事をしているようで、以前のバージョンのに含まれています。ネット。
サードパーティのコードを気にせず、違法文字がXMLに含まれないようにする場合は、 Michael Kropat'sをお勧めします回答。
XmlTextWriter.WriteString()
はエスケープを行います。
これがASP.NETアプリの場合、Server.HtmlEncode()を使用しないのはなぜですか?
WriteCDataメソッドを使用することでメリットが得られる場合があります。
public override void WriteCData(string text)
Member of System.Xml.XmlTextWriter
Summary:
Writes out a <![CDATA[...]]> block containing the specified text.
Parameters:
text: Text to place inside the CDATA block.
簡単な例は次のようになります。
writer.WriteStartElement("name");
writer.WriteCData("<unsafe characters>");
writer.WriteFullEndElement();
結果は次のようになります。
<name><![CDATA[<unsafe characters>]]></name>
ノード値を読み取るとき、XMLReaderは自動的にインナーテキストのCData部分を取り除きますので、心配する必要はありません。唯一の問題は、データをinnerText値としてXMLノードに保存する必要があることです。つまり、CDataコンテンツを属性値に挿入することはできません。
素晴らしい!それだけです。
更新されたコードのVBバリアント(クラスではなく、単なる関数)は、XMLをクリーンアップし、サニタイズします
Function cXML(ByVal _buf As String) As String
Dim textOut As New StringBuilder
Dim c As Char
If _buf.Trim Is Nothing OrElse _buf = String.Empty Then Return String.Empty
For i As Integer = 0 To _buf.Length - 1
c = _buf(i)
If Entities.ContainsKey(c) Then
textOut.Append(Entities.Item(c))
ElseIf (AscW(c) = &H9 OrElse AscW(c) = &HA OrElse AscW(c) = &HD) OrElse ((AscW(c) >= &H20) AndAlso (AscW(c) <= &HD7FF)) _
OrElse ((AscW(c) >= &HE000) AndAlso (AscW(c) <= &HFFFD)) OrElse ((AscW(c) >= &H10000) AndAlso (AscW(c) <= &H10FFFF)) Then
textOut.Append(c)
End If
Next
Return textOut.ToString
End Function
Shared ReadOnly Entities As New Dictionary(Of Char, String)() From {{""""c, """}, {"&"c, "&"}, {"'"c, "'"}, {"<"c, "<"}, {">"c, ">"}}
組み込みクラス XAttribute 。エンコードを自動的に処理します。
using System.Xml.Linq;
XDocument doc = new XDocument();
List<XAttribute> attributes = new List<XAttribute>();
attributes.Add(new XAttribute("key1", "val1&val11"));
attributes.Add(new XAttribute("key2", "val2"));
XElement elem = new XElement("test", attributes.ToArray());
doc.Add(elem);
string xmlStr = doc.ToString();
XElementsを使用した1行のソリューションです。非常に小さなツールで使用します。二度目は必要ないので、このように保ちます。 (それは汚いダグ)
StrVal = (<x a=<%= StrVal %>>END</x>).ToString().Replace("<x a=""", "").Replace(">END</x>", "")
ああ、C#ではなくVBでのみ動作します
無効な文字(少数の<!> quot; html <!> quot;だけでなく)のすべての処理に真剣で、System.Xml
にアクセスできる場合、 値データの適切なXmlエンコーディングを行う最も簡単な方法は次のとおりです。
string theTextToEscape = "Something \x1d else \x1D <script>alert('123');</script>";
var x = new XmlDocument();
x.LoadXml("<r/>"); // simple, empty root element
x.DocumentElement.InnerText = theTextToEscape; // put in raw string
string escapedText = x.DocumentElement.InnerXml; // Returns: Something  else  <script>alert('123');</script>
// Repeat the last 2 lines to escape additional strings.
XmlConvert.EncodeName()
は適切ではないことを知っておくことが重要です。これは、値ではなくエンティティ/タグ名のためのものだからです。これを使用すると、Htmlエンコードが必要なときにUrlエンコードのようになります。