System.Xml.XmlWriterSettingsクラスを用いると、XmlWriterクラスでのXML文書の出力時に使用する文字コードインデント改行などのフォーマットを細かく指定することができます。 また、テキストノード内の改行文字をエンティティ化して改行文字の種類を維持するかどうか、XML宣言を出力するかどうかといった出力時の動作も指定することができます。

デフォルト設定のXmlWriterでは、UTF-8を用いてXMLを出力する際にBOM(Byte Order Mark)を付与しますが、XmlWriterSettingsでBOMを出力しないEncodingを設定することによってXmlWriterにBOMなしのXMLを出力させるようにすることができます。

§1 XmlWriterとXmlWriterSettings

XDocument.SaveメソッドXmlDocument.Saveメソッドでは、直接ファイル名を指定して保存するほかにも、XmlWriterを用いて保存することができるようになっています。

XmlWriterSettingsで出力時のオプションを設定し、その設定に従って動作するXmlWriterをSaveメソッドに指定することで、出力時のフォーマットや動作を制御することができます。

XmlWriterを用いたXMLツリーの出力
using System;
using System.Text;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    // 出力したいXMLツリー
    var doc = new XDocument(
      new XElement("root",
        new XElement("foo", 
          new XElement("bar", "baz")
        ),
        new XElement("ほげ", "もげ")
      )
    );

    // XmlWriterの設定
    var settings = new XmlWriterSettings();

    // 文字コードにShift_JISを用いる
    settings.Encoding = Encoding.GetEncoding("Shift_JIS");

    // インデントにタブ1文字を用いる
    settings.Indent = true;
    settings.IndentChars = "\t";

    // ファイル"output.xml"に出力するXmlWriterを作成する
    using (var writer = XmlWriter.Create("output.xml", settings)) {
      // 作成したXmlWriterを使ってXMLツリーを出力する
      doc.Save(writer);
    }
  }
}
output.xmlに出力される内容
<?xml version="1.0" encoding="shift_jis"?>
<root>
	<foo>
		<bar>baz</bar>
	</foo>
	<ほげ>もげ</ほげ>
</root>


§2 XmlWriterの作成・出力先の指定

XmlWriterSettingsからXmlWriterを作成するにはXmlWriter.Createメソッドを使います。 第一引数には出力先となるファイル名を指定することができるほか、Stream派生クラスやStreamWriterなどのTextWriter派生クラスを出力先として指定することもできます。 第二引数には出力時のオプションが設定されたXmlWriterSettingsを指定します。

XmlWriter.Createメソッドを使ってXmlWriterを作成する
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    // 出力したいXMLツリー
    var doc = new XDocument(
      new XElement("root",
        new XElement("foo", 
          new XElement("bar", "baz")
        ),
        new XElement("ほげ", "もげ")
      )
    );

    // 出力時の設定
    var settings = new XmlWriterSettings();

    /* オプションの設定は省略 */

    // ファイル"test.xml"に出力するXmlWriterを作成する
    using (var writer = XmlWriter.Create("test.xml", settings)) {
      doc.Save(writer); // 上記のXmlWriterを使って出力
    }

    using (var stream = new MemoryStream()) {
      // MemoryStreamに出力するXmlWriterを作成する
      using (var writer = XmlWriter.Create(stream, settings)) {
        doc.Save(writer); // 上記のXmlWriterを使って出力
      }
    }
  }
}

このようにMemoryStreamを用いればXMLツリーをメモリ上に展開することもできます。

§2.1 XmlWriterを使ったXMLツリーの文字列化

XmlWriter.CreateメソッドでStringWriterを出力先に指定すれば、出力結果を文字列として取得することができます。 StringWriterを出力先とすることで、XMLツリーを文字列化する際にXmlWriterSettingsによって出力時のフォーマットを細かく指定することができます。

XmlWriterとStringWriterを使ってXMLツリーを文字列として取得する
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    // 出力したいXMLツリー
    var doc = new XDocument(
      new XElement("root",
        new XElement("foo", 
          new XElement("bar", "baz")
        ),
        new XElement("ほげ", "もげ")
      )
    );

    // 出力先となるStringWriter
    using (var sw = new StringWriter()) {
      // 出力時の設定
      var settings = new XmlWriterSettings() {
        Indent = true, // インデントする
        CloseOutput = false, // XmlWriterを閉じる際にベースとなるStringWriterを閉じない
      };

      // StringWriterに出力するXmlWriterを作成する
      using (var writer = XmlWriter.Create(sw, settings)) {
        // XmlWriterを使ってXMLツリーを出力する
        doc.Save(writer);
      }

      // 出力された内容を表示する
      Console.WriteLine(sw.ToString());
    }
  }
}
実行結果
<?xml version="1.0" encoding="utf-16"?>
<root>
  <foo>
    <bar>baz</bar>
  </foo>
  <ほげ>もげ</ほげ>
</root>

この例ではXmlWriterSettings.CloseOutputfalseを指定しています。 これにより、XmlWriterを閉じる際に出力先となるStringWriterを閉じないようになります。 このようにすることで、XmlWriterを使い終わったあともStringWriterを使い続けることができるようにしています。

逆にXmlWriterSettings.CloseOutputにtrueを指定すると、XmlWriterが閉じられる際に元になったStream/TextWriterも同時に閉じるようになります。

§3 XmlWriterSettingsで設定できる動作

ここまでで触れてきたような内容の他にも、XmlWriterSettingsでは以下のような動作を設定することができます。

XmlWriterSettingsで設定できるXmlWriterの動作
XmlWriterSettingsのプロパティ 動作 解説
出力フォーマットに関するプロパティ XmlWriterSettings.Encoding XmlWriterでの出力時に用いるエンコーディング(System.Text.Encoding)を指定する。 特に指定しなかった場合はデフォルトでUTF-8(BOM付き)が用いられる。
(どちらの場合もXDeclarationXmlDeclarationによるエンコーディングの指定が上書きされる)
§.エンコーディング (XmlWriterSettings.Encoding)
XmlWriterSettings.Indent trueを指定した場合、要素のインデントと要素間の改行を行う。
インデント文字にはXmlWriterSettings.IndentCharsで指定された文字列を用いる。
改行文字にはXmlWriterSettings.NewLineCharsで指定された文字列を用いる。
falseを指定した場合、インデントと改行は行われない。
(デフォルトはfalse: インデントしない)
§.インデント (XmlWriterSettings.Indent, IndentChars)
XmlWriterSettings.IndentChars Indentがtrueの場合に要素のインデントに用いる空白文字を指定する。
(デフォルトは" ": U+0020 'SPACE' 半角空白が2つ)
XmlWriterSettings.NewLineChars Indentがtrueの場合に要素間の改行に用いる改行文字を指定する。
NewLineOnAttributesがtrueの場合は、こプロパティに指定した文字列が属性間の改行にも用いられる。
(デフォルトは"\n": U+000A 'LINE FEED' ラインフィードが1つ)
§.要素間・属性間の改行 (XmlWriterSettings.NewLineChars, NewLineOnAttributes)
XmlWriterSettings.NewLineOnAttributes trueを指定した場合(かつIndentもtrueの場合)、属性間の改行を行い、各属性が一行ごとに記述される。
falseを指定した場合、属性は要素と同じ行で一行に記述される。
(デフォルトはfalse: 属性間の改行を行わない)
XmlWriterSettings.NewLineHandling NewLineHandling.Entitizeを指定した場合、テキストノードの"\r"(U+000D 'CARRIAGE RETURN')がエンティティ化(="&#xD;")された上で出力される。
NewLineHandling.Replaceを指定した場合、テキストノードの"\r"および"\n"(U+000A 'LINE FEED')がNewLineCharsで設定されている改行文字に統一された上で出力される。
この指定は属性値における改行文字・タブ文字にも影響する。
(デフォルトはNewLineHandling.None: そのままにする)
§.テキストノード・属性値の改行の処理 (XmlWriterSettings.NewLineHandling)
XmlWriterSettings.OmitXmlDeclaration trueを指定した場合、XML宣言の出力を省略する。
falseを指定した場合、XML宣言の出力を省略しない。
(デフォルトはfalse: XML宣言を出力する)
§.XML宣言の省略 (XmlWriterSetings.OmitXmlDeclaration)
XmlWriterSettings.NamespaceHandling NamespaceHandling.OmitDuplicatesを指定した場合、XMLツリー内に重複する名前空間宣言が存在する場合はそれを省略して出力する。
NamespaceHandling.Defaultを指定した場合、重複する名前空間宣言が存在していてもそのまま出力する。
(デフォルトはNamespaceHandling.Default: 重複する名前空間宣言を省略しない)
§.重複する名前空間宣言の処理 (XmlWriterSettings.NamespaceHandling)
出力時の検証に関するプロパティ XmlWriterSettings.CheckCharacters trueを指定した場合、CRLFを除くC0制御文字などのXMLとして不正な文字が含まれているかチェックし、含まれている場合は例外をスローする。
falseを指定した場合、XMLとして不正な文字が含まれている場合はエンティティ化して出力し、例外はスローしない。
(デフォルトはtrue: チェックする)
§.不正な文字の処理 (XmlWriterSettings.CheckCharacters)
XmlWriterSettings.ConformanceLevel ConformanceLevel.Documentを指定すれば、完全なXMLドキュメントとして適合するかどうかの検証が行われる。
ConformanceLevel.Fragmentを指定すれば、XMLフラグメント(不完全なXMLの断片)として適合するかどうかの検証が行われる。
いずれの場合も問題があればXmlExceptionがスローされる。
(デフォルトはConformanceLevel.Document: 完全なXMLドキュメントとして検証する)
XmlWriterの出力先に関するプロパティ XmlWriterSettings.CloseOutput trueを指定した場合、XmlWriterを閉じる際に元になったStream/TextWriterも同時に閉じる。
falseを指定した場合、XmlWriterを閉じても元になったStream/TextWriterは開いたままにする。 この場合、XmlWriterを使用し終わったあともStream/TextWriterを継続して用いることができる。
(デフォルトはfalse: 元になったStream/TextWriterを開いたままにする)
§.XmlWriterを使ったXMLツリーの文字列化

個々のプロパティを指定した場合の動作については以降で詳しく解説します。

§3.1 エンコーディング (XmlWriterSettings.Encoding)

XmlWriterSettings.EncodingではXmlWriterで出力する際のエンコーディングを指定することができます。 デフォルトの状態ではUTF-8(BOM付き)が用いられます。 XmlWriterSettings.Encodingにnullを指定することはできません。

XmlWriterSettings.Encodingを指定した場合、XML宣言にはそのエンコーディングが設定されます。 XMLツリーにXML宣言(XDeclarationXmlDeclaration)が含まれていてencoding属性でなんらかのエンコーディングを指定していても、XmlWriterSettings.Encodingの指定が優先され、XML宣言のencoding属性が上書きされます

XDeclarationの指定を上書きしてXmlWriterSettings.Encodingに指定したエンコーディングで出力する
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    var doc = new XDocument(
      // XML宣言でエンコーディングにutf-8を指定する
      new XDeclaration(version: "1.0", encoding: "utf-8", standalone: "yes"),
      new XElement("root", "日本語")
    );

    // XmlWriterSettingsでエンコーディングにShift_JISを指定する
    var settings = new XmlWriterSettings() {
      Encoding = Encoding.GetEncoding("Shift_JIS"),
    };

    using (var writer = XmlWriter.Create("output.xml", settings)) {
      // Shift_JISでファイル"output.xml"に出力される
      doc.Save(writer);
    }
  }
}
output.xmlに出力される内容
<?xml version="1.0" encoding="shift_jis" standalone="yes"?><root>日本語</root>

§3.1.1 BOM出力の制御

XDocument.Saveメソッドなどを用いて保存する場合、出力される内容の先頭にはBOM(Byte Order Mark)が付けられます。 また、XmlWriterSettings.EncodingにEncoding.UTF8を指定した場合や、XmlWriterSettings.Encodingをデフォルト値のままにした場合にもBOMが付けられます

BOM無しのXMLを出力したい場合は、コンストラクタのパラメータencoderShouldEmitUTF8Identifierfalseを指定したUTF8Encodingを作成し、XmlWriterSettings.Encodingに指定します。

UTF-8 BOM付きでXMLを出力する
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    var doc = new XDocument(
      new XElement("root")
    );

    using (var stream = new MemoryStream()) {

      // エンコーディングにEncoding.UTF8を指定する
      var settings = new XmlWriterSettings() {
        Encoding = Encoding.UTF8,
      };

      using (var writer = XmlWriter.Create(stream, settings)) {
        // BOM付きのUTF-8でstreamに出力される
        doc.Save(writer);
      }

      // 出力された内容をバイト表現・文字列で表示
      Console.WriteLine(BitConverter.ToString(stream.ToArray()));
      Console.WriteLine(Encoding.UTF8.GetString(stream.ToArray()));
    }
  }
}
実行結果
EF-BB-BF-3C-3F-78-6D-6C-20-76-65-72-73-69-6F-6E-3D-22-31-2E-30-22-20-65-6E-63-
6F-64-69-6E-67-3D-22-75-74-66-2D-38-22-3F-3E-3C-72-6F-6F-74-20-2F-3E
<?xml version="1.0" encoding="utf-8"?><root />

(BOM"EF-BB-BF"が先頭3バイトに出力されている)

UTF-8 BOMなしでXMLを出力する
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    var doc = new XDocument(
      new XElement("root")
    );

    using (var stream = new MemoryStream()) {
      // エンコーディングにencoderShouldEmitUTF8Identifier=falseの
      // UTF8Encodingを指定する
      var settings = new XmlWriterSettings() {
        Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false),
      };

      using (var writer = XmlWriter.Create(stream, settings)) {
        // BOMなしのUTF-8でstreamに出力される
        doc.Save(writer);
      }

      // 出力された内容をバイト表現・文字列で表示
      Console.WriteLine(BitConverter.ToString(stream.ToArray()));
      Console.WriteLine(Encoding.UTF8.GetString(stream.ToArray()));
    }
  }
}
実行結果
3C-3F-78-6D-6C-20-76-65-72-73-69-6F-6E-3D-22-31-2E-30-22-20-65-6E-63-
6F-64-69-6E-67-3D-22-75-74-66-2D-38-22-3F-3E-3C-72-6F-6F-74-20-2F-3E
<?xml version="1.0" encoding="utf-8"?><root />

(BOM"EF-BB-BF"が出力されていない)

UnicodeEncoding(UTF-16)やUTF32Encoding(UTF-32)の場合も同様にコンストラクタの引数byteOrderMarkによってBOMの有無を指定することができます。

System.Text.EncodingとBOMの関連や、他のクラスにおけるBOM出力の制御についてはSystem.Text.EncodingのBOMありなしの制御を参照してください。

§3.2 XML宣言の省略 (XmlWriterSetings.OmitXmlDeclaration)

XmlWriterSettings.OmitXmlDeclarationtrueを指定すると、XML文書内にXDeclarationXmlDeclarationが存在していてもXML宣言の出力を省略するようになります。

XmlWriterSetings.OmitXmlDeclarationにtrueを指定してXML宣言の出力を省略する
using System;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    var doc = new XDocument(
      // XDocumentにXML宣言(XDeclaration)を含める
      new XDeclaration(version: "1.0", encoding: "utf-8", standalone: "yes"),
      new XElement("root",
        new XElement("foo", "foo"),
        new XElement("bar")
      )
    );

    var settings = new XmlWriterSettings() {
      // XmlWriterでの出力時にXML宣言の出力を省略する
      OmitXmlDeclaration = true,
    };

    using (var writer = XmlWriter.Create("output.xml", settings)) {
      // XML宣言のないXML文書が"output.xml"に保存される
      doc.Save(writer);
    }
  }
}
output.xmlに出力される内容
<root><foo>foo</foo><bar /></root>

なお、XDocument.ToStringメソッドでは、文字列化に際してXML宣言が省略されます。

XDocument.ToStringメソッドを使ってXML宣言を省略した文字列を取得する
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    var doc = new XDocument(
      // XDocumentにXML宣言を含める
      new XDeclaration(version: "1.0", encoding: "utf-8", standalone: "yes"),
      new XElement("root",
        new XElement("foo", "foo"),
        new XElement("bar")
      )
    );

    // ToStringメソッドを使ってXDocumentを文字列化する
    // (この場合、XML宣言は出力されない)
    Console.WriteLine(doc.ToString());
  }
}
実行結果
<root>
  <foo>foo</foo>
  <bar />
</root>

§3.3 インデント (XmlWriterSettings.Indent, IndentChars)

XmlWriterSettings.Indenttrueを指定すると要素のインデントと要素間の改行を行った上で出力するようになります。

インデントに用いる文字はXmlWriterSettings.IndentCharsで指定します。 IndentCharsにはタブ("\t")や半角空白(" ")、全角空白(" ")、改行文字など、ホワイトスペースとして扱われる文字(Char.IsWhiteSpace = true)からなる文字列を指定します。 それ以外の文字列を指定することもできますが、その場合は不正なXMLが出力されます。

XmlWriterSettings.IndentCharsを使ってインデント文字を変えてXMLを出力する
using System;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    var doc = new XDocument(
      new XElement("root",
        new XElement("foo", 
          new XElement("bar", "baz")
        ),
        new XElement("ほげ", "もげ")
      )
    );

    var settings = new XmlWriterSettings() {
      Indent = true, // 要素のインデントと要素間の改行を行う
    };

    // 半角空白4つ単位でインデントする
    settings.IndentChars = new String(' ', 4);

    using (var writer = XmlWriter.Create("output1.xml", settings)) {
      doc.Save(writer);
    }

    // 全角空白1つ単位でインデントする
    settings.IndentChars = " ";

    using (var writer = XmlWriter.Create("output2.xml", settings)) {
      doc.Save(writer);
    }

    // タブ1つ単位でインデントする
    settings.IndentChars = "\t";

    using (var writer = XmlWriter.Create("output3.xml", settings)) {
      doc.Save(writer);
    }
  }
}
output1.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?>
<root>
    <foo>
        <bar>baz</bar>
    </foo>
    <ほげ>もげ</ほげ>
</root>
output2.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?>
<root>
 <foo>
  <bar>baz</bar>
 </foo>
 <ほげ>もげ</ほげ>
</root>
output3.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?>
<root>
	<foo>
		<bar>baz</bar>
	</foo>
	<ほげ>もげ</ほげ>
</root>

改行文字の指定はXmlWriterSettings.NewLineCharsで行うことができます。


XmlWriterSettingsではインデント文字(空白やタブ)とインデント幅とを個別に指定することはできません。 あらかじめ目的のインデント幅を持った文字列をXmlWriterSettings.IndentCharsに指定する必要があります。

インデント文字とインデント幅によるXmlWriterSettings.IndentCharsの指定
using System;
using System.Linq;
using System.Xml;

class Sample {
  static void Main()
  {
    var settings = new XmlWriterSettings();

    // インデント文字に半角空白、インデント幅4でインデントする
    settings.IndentChars = "    ";
    // これは以下のようにすることもできる
    settings.IndentChars = new string(' ', 4);
    settings.IndentChars = string.Concat(Enumerable.Repeat(" ", 4));

    // タブ2文字でインデントする
    settings.IndentChars = "\t\t";
  }
}

繰り返した文字列を作成する方法については文字列の加工・編集 §.乗算演算子(*, *=)・繰り返した文字列の生成を参照してください。

§3.4 要素間・属性間の改行 (XmlWriterSettings.NewLineChars, NewLineOnAttributes)

XmlWriterSettings.Indenttrueを指定した上でXmlWriterSettings.NewLineCharsを指定すると、出力時に用いる改行文字を変更することができます。

IndentCharsと同様、NewLineCharsには改行文字以外にも空白などのホワイトスペースとして扱われる文字(Char.IsWhiteSpace = trueとなる文字)からなる文字列を指定することもできます。

XmlWriterSettings.NewLineCharsを使って改行文字を変えてXMLを出力する
using System;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    var doc = new XDocument(
      new XElement("root",
        new XElement("foo", 
          new XElement("bar", "baz")
        ),
        new XElement("ほげ", "もげ")
      )
    );

    var settings = new XmlWriterSettings() {
      Indent = true, // 要素のインデントと要素間の改行を行う
    };

    // 改行文字にCRLFを用いる
    settings.NewLineChars = "\r\n";

    using (var writer = XmlWriter.Create("output1.xml", settings)) {
      doc.Save(writer);
    }

    // 改行文字に半角空白を用いる
    settings.NewLineChars = " ";

    using (var writer = XmlWriter.Create("output2.xml", settings)) {
      doc.Save(writer);
    }
  }
}
output1.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?>
<root>
  <foo>
    <bar>baz</bar>
  </foo>
  <ほげ>もげ</ほげ>
</root>
output2.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?> <root>   <foo>     <bar>baz</bar>   </foo>   <ほげ>もげ</ほげ> </root>

XmlWriterSettings.NewLineOnAttributestrueを指定すると要素間だけでなく属性間にも改行を行うようにすることができます。 属性のインデントにはIndentCharsに指定されている文字列、改行にはNewLineCharsに指定されている文字列が用いられます。

XmlWriterSettings.NewLineOnAttributesを使って属性間を改行してXMLを出力する
using System;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    var doc = new XDocument(
      new XElement("root",
        new XAttribute("foo", "ほげ"),
        new XAttribute("bar", "もげ"),
        new XElement("foo")
      )
    );

    var settings = new XmlWriterSettings() {
      Indent = true, // 要素のインデントと要素間の改行を行う
    };

    // 各属性間に改行を入れない
    settings.NewLineOnAttributes = false;

    using (var writer = XmlWriter.Create("output1.xml", settings)) {
      doc.Save(writer);
    }

    // 各属性間にも改行を入れる
    settings.NewLineOnAttributes = true;

    using (var writer = XmlWriter.Create("output2.xml", settings)) {
      doc.Save(writer);
    }
  }
}
output1.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?>
<root foo="ほげ" bar="もげ">
  <foo />
</root>
output2.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?>
<root
  foo="ほげ"
  bar="もげ">
  <foo />
</root>

属性値が長くなる場合や、要素に付与する属性の数が多数になる場合は、XmlWriterSettings.NewLineOnAttributesをtrueにして属性間を改行することによって視認性を上げることができます。

§3.5 テキストノード・属性値の改行の処理 (XmlWriterSettings.NewLineHandling)

XmlWriterSettings.NewLineHandlingプロパティではテキストノード・属性値に含まれる改行文字をどのように扱うかを指定することができます。

§3.5.1 改行文字のエンティティ化 (NewLineHandling.Entitize)

NewLineHandling.Entitizeを指定した場合は、テキストノード内における"\r"(U+000D 'CARRIAGE RETURN')がエンティティ化され、"&#xD;"として出力されます。 "\n"(U+000A 'LINE FEED')はエンティティ化されません。 テキストノード内の各改行文字がそれぞれどのようにエンティティ化されるかを表にすると次のようになります。

XmlWriterSettings.NewLineHandlingの値とエンティティ化される改行文字
XmlWriterSettings.NewLineHandlingの値 テキストノード内の改行文字
\r \n \r\n
NewLineHandling.None \r \n \r\n
NewLineHandling.Entitize &#xD; \n &#xD;\n

NewLineHandling.Entitizeを指定して改行文字をエンティティ化しておくことには次のような利点があります。 例えば、出力したXML文書が他のXMLプロセッサによって読み込まれる際に、テキストノード・属性値内の改行文字が空白文字として扱われることによって消失したり変わってしまうことを防ぐことができます。 この指定は、テキストノード・属性値内における改行文字が重要な意味を持つ場合に特に有用です。

XmlWriterSettings.NewLineHandlingにNewLineHandling.Entitizeを指定してテキストノード内のCRをエンティティ化する
using System;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    var doc = new XDocument(
      new XElement("root", "line1\rline2\nline3\r\nline4")
    );

    var settings = new XmlWriterSettings();

    // テキストノード内の改行文字をそのままにする
    settings.NewLineHandling = NewLineHandling.None;

    using (var writer = XmlWriter.Create("output-none.xml", settings)) {
      doc.Save(writer);
    }

    // テキストノード内の"\r"(CR)をエンティティ化する
    settings.NewLineHandling = NewLineHandling.Entitize;

    using (var writer = XmlWriter.Create("output-entitize.xml", settings)) {
      doc.Save(writer);
    }
  }
}
output-none.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?><root>line1
line2
line3
line4</root>
output-entitize.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?><root>line1&#xD;line2
line3&#xD;
line4</root>

NewLineHandling.Entitizeを指定した場合は属性値も同様にエンティティ化されます。 属性値の場合、"\r""\n"(U+000A 'LINE FEED')の両方に加えて"\t"(U+0009 'CHARACTER TABULATION')がエンティティ化されます。 属性値内の各制御文字がそれぞれどのようにエンティティ化されるかを表にすると次のようになります。

XmlWriterSettings.NewLineHandlingの値とエンティティ化される制御文字
XmlWriterSettings.NewLineHandlingの値 属性値内の制御文字
\r \n \r\n \t
NewLineHandling.None \r \n \r\n \t
NewLineHandling.Entitize &#xD; &#xA; &#xD;&#xA; &#x9;
XmlWriterSettings.NewLineHandlingにNewLineHandling.Entitizeを指定して属性値内の制御文字をエンティティ化する
using System;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    var doc = new XDocument(
      new XElement("root",
        new XAttribute("attr", "line1\rline2\nline3\r\nline\t4")
      )
    );

    var settings = new XmlWriterSettings();

    // 属性値内の制御文字をそのままにする
    settings.NewLineHandling = NewLineHandling.None;

    using (var writer = XmlWriter.Create("output-none.xml", settings)) {
      doc.Save(writer);
    }

    // 属性値内の"\r"(CR)・"\n"(LF)・"\t"(TAB)をエンティティ化する
    settings.NewLineHandling = NewLineHandling.Entitize;

    using (var writer = XmlWriter.Create("output-entitize.xml", settings)) {
      doc.Save(writer);
    }
  }
}
output-none.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?><root attr="line1
line2
line3
line	4" />
output-entitize.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?><root attr="line1&#xD;line2&#xA;line3&#xD;&#xA;line&#x9;4" />

§3.5.2 改行文字の置換 (NewLineHandling.Replace)

NewLineHandling.Replaceを指定した場合は、テキストノード内における"\r"(U+000D 'CARRIAGE RETURN')・"\n"(U+000A 'LINE FEED')および"\r\n"のシーケンスがXmlWriterSettings.NewLineCharsで設定されている値へと置換されます。 一方、属性値内における改行文字に対しては置換が行われず、NewLineHandling.Entitizeを指定した場合と同様のエンティティ化が行われます。

テキストノード内と属性値内の各改行文字がそれぞれどのように置換されるかを表にすると次のようになります。

XmlWriterSettings.NewLineHandlingの値と置換される改行文字
XmlWriterSettings.NewLineHandlingの値 テキストノード内の改行文字 属性値内の制御文字
\r \n \r\n \r \n \r\n \t
NewLineHandling.None \r \n \r\n \r \n \r\n \t
NewLineHandling.Replace NewLineChars NewLineChars NewLineChars &#xD; &#xA; &#xD;&#xA; &#x9;
(XmlWriterSettings.NewLineCharsに置換される) (NewLineHandling.Entitizeを指定した場合と同様にエンティティ化される)

このように、NewLineHandling.Replaceを用いるとテキストノード内の改行文字を統一することができます。

XmlWriterSettings.NewLineHandlingにNewLineHandling.Replaceを指定してテキストノード内の改行文字を統一する
using System;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    var doc = new XDocument(
      new XElement("root", "line1\rline2\nline3\r\nline4")
    );

    var settings = new XmlWriterSettings();

    // テキストノード内の改行文字をそのままにする
    settings.NewLineHandling = NewLineHandling.None;

    using (var writer = XmlWriter.Create("output-none.xml", settings)) {
      doc.Save(writer);
    }

    // テキストノード内の改行文字を"\n"(LF)に統一する
    settings.NewLineHandling = NewLineHandling.Replace;
    settings.NewLineChars = "\n";

    using (var writer = XmlWriter.Create("output-replace-tolf.xml", settings)) {
      doc.Save(writer);
    }

    // テキストノード内の改行文字を"\r\n"(CRLF)に統一する
    settings.NewLineHandling = NewLineHandling.Replace;
    settings.NewLineChars = "\r\n";

    using (var writer = XmlWriter.Create("output-replace-tocrlf.xml", settings)) {
      doc.Save(writer);
    }
  }
}
output-none.xmlの出力内容(odコマンドによるバイナリダンプ)
$ od -vtx1 -a output-none.xml 
0000000  ef  bb  bf  3c  3f  78  6d  6c  20  76  65  72  73  69  6f  6e
          o   ;   ?   <   ?   x   m   l  sp   v   e   r   s   i   o   n
0000020  3d  22  31  2e  30  22  20  65  6e  63  6f  64  69  6e  67  3d
          =   "   1   .   0   "  sp   e   n   c   o   d   i   n   g   =
0000040  22  75  74  66  2d  38  22  3f  3e  3c  72  6f  6f  74  3e  6c
          "   u   t   f   -   8   "   ?   >   <   r   o   o   t   >   l
0000060  69  6e  65  31  0d  6c  69  6e  65  32  0a  6c  69  6e  65  33
          i   n   e   1  cr   l   i   n   e   2  nl   l   i   n   e   3
0000100  0d  0a  6c  69  6e  65  34  3c  2f  72  6f  6f  74  3e
         cr  nl   l   i   n   e   4   <   /   r   o   o   t   >
0000116
output-replace-tolf.xmlの出力内容(odコマンドによるバイナリダンプ)
$ od -vtx1 -a output-replace-tolf.xml 
0000000  ef  bb  bf  3c  3f  78  6d  6c  20  76  65  72  73  69  6f  6e
          o   ;   ?   <   ?   x   m   l  sp   v   e   r   s   i   o   n
0000020  3d  22  31  2e  30  22  20  65  6e  63  6f  64  69  6e  67  3d
          =   "   1   .   0   "  sp   e   n   c   o   d   i   n   g   =
0000040  22  75  74  66  2d  38  22  3f  3e  3c  72  6f  6f  74  3e  6c
          "   u   t   f   -   8   "   ?   >   <   r   o   o   t   >   l
0000060  69  6e  65  31  0a  6c  69  6e  65  32  0a  6c  69  6e  65  33
          i   n   e   1  nl   l   i   n   e   2  nl   l   i   n   e   3
0000100  0a  6c  69  6e  65  34  3c  2f  72  6f  6f  74  3e
         nl   l   i   n   e   4   <   /   r   o   o   t   >
0000115
output-replace-tocrlf.xmlの出力内容(odコマンドによるバイナリダンプ)
$ od -vtx1 -a output-replace-tocrlf.xml 
0000000  ef  bb  bf  3c  3f  78  6d  6c  20  76  65  72  73  69  6f  6e
          o   ;   ?   <   ?   x   m   l  sp   v   e   r   s   i   o   n
0000020  3d  22  31  2e  30  22  20  65  6e  63  6f  64  69  6e  67  3d
          =   "   1   .   0   "  sp   e   n   c   o   d   i   n   g   =
0000040  22  75  74  66  2d  38  22  3f  3e  3c  72  6f  6f  74  3e  6c
          "   u   t   f   -   8   "   ?   >   <   r   o   o   t   >   l
0000060  69  6e  65  31  0d  0a  6c  69  6e  65  32  0d  0a  6c  69  6e
          i   n   e   1  cr  nl   l   i   n   e   2  cr  nl   l   i   n
0000100  65  33  0d  0a  6c  69  6e  65  34  3c  2f  72  6f  6f  74  3e
          e   3  cr  nl   l   i   n   e   4   <   /   r   o   o   t   >
0000120

(ここでcr = CR, nl = new line = LF)

NewLineHandling.Replaceを指定しても属性値の改行文字がNewLineCharsに置換されることはなく、NewLineHandling.Entitizeを指定した場合と同様にすべてエンティティ化されます。 改行文字だけでなく、タブ文字もエンティティ化される点に注意してください。

XmlWriterSettings.NewLineHandlingにNewLineHandling.Replaceを指定すると属性値内の改行文字はエンティティ化される
using System;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    var doc = new XDocument(
      new XElement("root",
        new XAttribute("attr", "line1\rline2\nline3\r\nline\t4")
      )
    );

    var settings = new XmlWriterSettings();

    // 属性値内の改行をそのままにする
    settings.NewLineHandling = NewLineHandling.None;

    using (var writer = XmlWriter.Create("output-none.xml", settings)) {
      doc.Save(writer);
    }

    // 属性値内の改行文字をエンティティ化する("\n"には置換されない)
    settings.NewLineHandling = NewLineHandling.Replace;
    settings.NewLineChars = "\n";

    using (var writer = XmlWriter.Create("output-replace-tolf.xml", settings)) {
      doc.Save(writer);
    }

    // 属性値内の改行文字をエンティティ化する("\r\n"には置換されない)
    settings.NewLineHandling = NewLineHandling.Replace;
    settings.NewLineChars = "\r\n";

    using (var writer = XmlWriter.Create("output-replace-tocrlf.xml", settings)) {
      doc.Save(writer);
    }
  }
}
output-none.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?><root attr="line1
line2
line3
line	4" />
output-replace-tolf.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?><root attr="line1&#xD;line2&#xA;line3&#xD;&#xA;line&#x9;4" />
output-replace-tocrlf.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?><root attr="line1&#xD;line2&#xA;line3&#xD;&#xA;line&#x9;4" />

§3.6 重複する名前空間宣言の処理 (XmlWriterSettings.NamespaceHandling)

XmlWriterSettings.NamespaceHandlingプロパティでは、XMLツリー内に重複する名前空間宣言(xmlns属性)が存在する場合にそれらをどのように扱うかを指定することができます。

NamespaceHandling.OmitDuplicatesを指定した場合は、重複する名前空間宣言を削除し、最も上位にある名前空間宣言だけを残します。 この際、名前空間とプレフィックスの両方が一致する名前空間宣言のみが削除されます。 プレフィックスが異なる名前空間宣言は別のものとして扱われ、省略されることはありません。

NamespaceHandling.Defaultを指定した場合は、名前空間宣言が重複していてもそのままにします。

XmlWriterSettings.NamespaceHandlingを指定して重複する名前空間宣言を省略する
using System;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    var nsAtom = (XNamespace)"http://www.w3.org/2005/Atom";

    var doc = new XDocument(
      new XElement(nsAtom + "feed",
        new XAttribute(XNamespace.Xmlns + "atom", nsAtom.NamespaceName),
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        new XElement(nsAtom + "title", "更新履歴 (Atom 1.0)"),
        new XElement(nsAtom + "entry",
          // 上と重複する名前空間の宣言
          new XAttribute(XNamespace.Xmlns + "atom", nsAtom.NamespaceName),
          // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          new XElement(nsAtom + "title",
            new XAttribute("type", "text"),
            "entry 1"
          )
        )
      )
    );

    var settings = new XmlWriterSettings() {
      Indent = true,
    };

    // 重複する名前空間の宣言を省略する
    settings.NamespaceHandling = NamespaceHandling.OmitDuplicates;

    using (var writer = XmlWriter.Create("output-omitduplicates.xml", settings)) {
      doc.Save(writer);
    }

    // 重複する名前空間の宣言をそのままにする
    settings.NamespaceHandling = NamespaceHandling.Default;

    using (var writer = XmlWriter.Create("output-default.xml", settings)) {
      doc.Save(writer);
    }
  }
}
output-omitduplicates.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?>
<atom:feed xmlns:atom="http://www.w3.org/2005/Atom">
  <atom:title>更新履歴 (Atom 1.0)</atom:title>
  <atom:entry>
    <atom:title type="text">entry 1</atom:title>
  </atom:entry>
</atom:feed>
output-default.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?>
<atom:feed xmlns:atom="http://www.w3.org/2005/Atom">
  <atom:title>更新履歴 (Atom 1.0)</atom:title>
  <atom:entry xmlns:atom="http://www.w3.org/2005/Atom">
    <atom:title type="text">entry 1</atom:title>
  </atom:entry>
</atom:feed>

次の例のように、名前空間が同一でもプレフィックスが異なっている場合はそれぞれ個別の名前空間宣言とみなされ、NamespaceHandling.OmitDuplicatesを指定しても名前空間宣言の省略は行われません。

プレフィックスが異なる場合はNamespaceHandling.OmitDuplicatesを指定しても同一名前空間の宣言は省略されない
using System;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    var nsAtom = (XNamespace)"http://www.w3.org/2005/Atom";

    var doc = new XDocument(
      new XElement(nsAtom + "feed",
        new XAttribute(XNamespace.Xmlns + "atom", nsAtom.NamespaceName),
        //                                ~~~~~~  ~~~~~~
        new XElement(nsAtom + "title", "更新履歴 (Atom 1.0)"),
        new XElement(nsAtom + "entry",
          // 上の名前空間宣言と同一名前空間でもプレフィックスは異なるので
          // NamespaceHandling.OmitDuplicatesを指定しても省略されない
          new XAttribute(XNamespace.Xmlns + "a", nsAtom.NamespaceName),
          //                                ~~~  ~~~~~~
          new XElement(nsAtom + "title",
            new XAttribute("type", "text"),
            "entry 1"
          )
        )
      )
    );

    var settings = new XmlWriterSettings() {
      Indent = true,
    };

    // 重複する名前空間の宣言を省略する
    settings.NamespaceHandling = NamespaceHandling.OmitDuplicates;

    using (var writer = XmlWriter.Create("output-omitduplicates.xml", settings)) {
      doc.Save(writer);
    }

    // 重複する名前空間の宣言をそのままにする
    settings.NamespaceHandling = NamespaceHandling.Default;

    using (var writer = XmlWriter.Create("output-default.xml", settings)) {
      doc.Save(writer);
    }
  }
}
output-omitduplicates.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?>
<atom:feed xmlns:atom="http://www.w3.org/2005/Atom">
  <atom:title>更新履歴 (Atom 1.0)</atom:title>
  <a:entry xmlns:a="http://www.w3.org/2005/Atom">
    <a:title type="text">entry 1</a:title>
  </a:entry>
</atom:feed>
output-default.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?>
<atom:feed xmlns:atom="http://www.w3.org/2005/Atom">
  <atom:title>更新履歴 (Atom 1.0)</atom:title>
  <a:entry xmlns:a="http://www.w3.org/2005/Atom">
    <a:title type="text">entry 1</a:title>
  </a:entry>
</atom:feed>

XDocument.Saveメソッドを用いる場合は、SaveOptions.OmitDuplicateNamespacesを使うことでもこのプロパティと同じ結果を得ることができます。

§3.7 不正な文字の処理 (XmlWriterSettings.CheckCharacters)

XmlWriterSettings.CheckCharactersプロパティでは、XML内にC0制御文字(CR・LFを除く)など、XMLとして不正な文字が含まれているかをチェックするかどうかを指定することができます。

trueを指定してチェックするようにした場合、不正な文字が含まれている際には出力時に例外ArgumentExceptionをスローします。

falseを指定してチェックしないようにした場合、不正な文字が含まれている際にはそれをエンティティ化して出力します。 例外はスローしません。

XmlWriterSettings.CheckCharactersを指定して出力時に不正な文字が含まれていないかチェックする
using System;
using System.Xml;
using System.Xml.Linq;

class Sample {
  static void Main()
  {
    var doc = new XDocument(
      new XElement("root",
        // U+0000 'NULL'で終端されているテキストノード
        new XText("text\0")
      )
    );

    var settings = new XmlWriterSettings();

    // XMLとして不正な文字列が含まれているかチェックしないで出力する
    // (不正な文字がある場合はエンティティ化して出力する)
    settings.CheckCharacters = false;

    Console.WriteLine("[CheckCharacters = false]");

    using (var writer = XmlWriter.Create("output-unchecked.xml", settings)) {
      doc.Save(writer);
    }

    Console.WriteLine("done");

    // XMLとして不正な文字列が含まれているかチェックして出力する
    // (不正な文字がある場合は例外をスローする)
    settings.CheckCharacters = true;

    Console.WriteLine("[CheckCharacters = true]");

    using (var writer = XmlWriter.Create("output-checked.xml", settings)) {
      doc.Save(writer);
    }

    Console.WriteLine("done");
  }
}
実行結果
[CheckCharacters = false]
done
[CheckCharacters = true]

ハンドルされていない例外: System.ArgumentException: '.' (16 進数値 0x00) は無効な文字です。
   場所 System.Xml.XmlUtf8RawTextWriter.InvalidXmlChar(Int32 ch, Byte* pDst, Boolean entitize)
   場所 System.Xml.XmlUtf8RawTextWriter.WriteElementTextBlock(Char* pSrc, Char* pSrcEnd)
   場所 System.Xml.XmlUtf8RawTextWriter.WriteString(String text)
   場所 System.Xml.XmlWellFormedWriter.WriteString(String text)
   場所 System.Xml.Linq.ElementWriter.WriteElement(XElement e)
   場所 System.Xml.Linq.XElement.WriteTo(XmlWriter writer)
   場所 System.Xml.Linq.XContainer.WriteContentTo(XmlWriter writer)
   場所 System.Xml.Linq.XDocument.WriteTo(XmlWriter writer)
   場所 Sample.Main()
output-unchecked.xmlの出力内容
<?xml version="1.0" encoding="utf-8"?><root>text&#x0;</root>