その理由は、XSLTの内部表現で"
は、がとまったく同じ文字であるため"
です。これらは両方ともASCIIコードポイント0x34を表します。XslCompiledTransformがその出力を生成するとき、それはそれが"
そうすることが合法であるところを使用するように思われるでしょう。"
それでも属性値の中に出力されると思います。
出力のように"
生成されるのはあなたにとって問題ですか?"
任意の入力ファイルを使用して、VisualStudioで次のXSLTを実行しました。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/*">
<xml>
<xsl:variable name="chars">"'<>&</xsl:variable>
<node a='{$chars}' b="{$chars}">
<xsl:value-of select="$chars"/>
</node>
</xml>
</xsl:template>
</xsl:stylesheet>
出力は次のとおりです。
<xml>
<node a=""'<>&" b=""'<>&">"'<>&</node>
</xml>
ご覧のとおり、元々5文字すべてがエンティティとして表されていたとしても、アポストロフィは'
どこでも生成され、引用符は"
テキストノードのように生成されます。さらに、区切り文字が含まれa
ている属性は、出力で'
区切り文字を使用"
します。私が言ったように、XSLTが気にする限り、引用符は単なる引用符であり、属性は単なる属性です。それらが出力でどのように生成されるかは、XSLTプロセッサー次第です。
編集:この動作の根本的な原因は、XmlWriterクラスの動作にあるようです。よりカスタマイズされたエスケープが必要な場合の一般的な提案は、XmlTextWriter
クラスを拡張することのようです。 このページには、かなり有望に見える実装があります。
public class KeepEntityXmlTextWriter : XmlTextWriter
{
private static readonly string[] ENTITY_SUBS = new string[] { "'", """ };
private static readonly char[] REPLACE_CHARS = new char[] { '\'', '"' };
public KeepEntityXmlTextWriter(string filename) : base(filename, null) { ; }
private void WriteStringWithReplace(string text)
{
string[] textSegments = text.Split(KeepEntityXmlTextWriter.REPLACE_CHARS);
if (textSegments.Length > 1)
{
for (int pos = -1, i = 0; i < textSegments.Length; ++i)
{
base.WriteString(textSegments[i]);
pos += textSegments[i].Length + 1;
// Assertion: Replace the following if-else when the number of
// replacement characters and substitute entities has grown
// greater than 2.
Debug.Assert(2 == KeepEntityXmlTextWriter.REPLACE_CHARS.Length);
if (pos != text.Length)
{
if (text[pos] == KeepEntityXmlTextWriter.REPLACE_CHARS[0])
base.WriteRaw(KeepEntityXmlTextWriter.ENTITY_SUBS[0]);
else
base.WriteRaw(KeepEntityXmlTextWriter.ENTITY_SUBS[1]);
}
}
}
else base.WriteString(text);
}
public override void WriteString( string text)
{
this.WriteStringWithReplace(text);
}
}
一方、MSDNのドキュメントXmlWriter.Create()
では、XmlTextWritersを直接インスタンス化するのではなく、を使用することを推奨しています。
.NET Framework 2.0リリースでは、XmlWriter.CreateメソッドとXmlWriterSettingsクラスを使用してXmlWriterインスタンスを作成することをお勧めします。これにより、このリリースで導入されたすべての新機能を最大限に活用できます。詳細については、「XMLライターの作成」を参照してください。
これを回避する1つの方法は、上記と同じロジックを使用することですが、それをラップするクラスに配置しますXmlWriter
。 このページには、XmlWrappingWriterの既製の実装があり、必要に応じて変更できます。
上記のコードをで使用するにはXmlWrappingWriter
、次のようにラッピングライターをサブクラス化します。
public class KeepEntityWrapper : XmlWrappingWriter
{
public KeepEntityWrapper(XmlWriter baseWriter)
: base(baseWriter)
{
}
private static readonly string[] ENTITY_SUBS = new string[] { "'", """ };
private static readonly char[] REPLACE_CHARS = new char[] { '\'', '"' };
private void WriteStringWithReplace(string text)
{
string[] textSegments = text.Split(REPLACE_CHARS);
if (textSegments.Length > 1)
{
for (int pos = -1, i = 0; i < textSegments.Length; ++i)
{
base.WriteString(textSegments[i]);
pos += textSegments[i].Length + 1;
// Assertion: Replace the following if-else when the number of
// replacement characters and substitute entities has grown
// greater than 2.
Debug.Assert(2 == REPLACE_CHARS.Length);
if (pos != text.Length)
{
if (text[pos] == REPLACE_CHARS[0])
base.WriteRaw(ENTITY_SUBS[0]);
else
base.WriteRaw(ENTITY_SUBS[1]);
}
}
}
else base.WriteString(text);
}
public override void WriteString(string text)
{
this.WriteStringWithReplace(text);
}
}
これは基本的にと同じコードですKeepEntityXmlTextWriter
が、基本クラスとしてXmlWrappingWriterを使用し、コンストラクターが異なることに注意してください。
Guard
コードが2つの場所で使用されていることを認識していませんXmlWrappingWriter
が、コードを自分で消費することを考えると、このような行を削除するのはかなり安全です。null値がコンストラクターまたは(上記の場合はアクセスできない)BaseWriter
プロパティに渡されないようにするだけです。
Guard.ArgumentNotNull(baseWriter, "baseWriter");
のインスタンスを作成するにはXmlWrappingWriter
、必要に応じてXmlWriterを作成し、次を使用します。
KeepEntityWrapper wrap = new KeepEntityWrapper(writer);
次に、このwrap
変数をXSL変換に渡すXmlWriterとして使用します。