この文書では、System.Xml.Linq名前空間のクラス郡を使ってXML文書を構築する方法、またファイルなどに出力する方法ついて扱います。 XDocumentクラスやXElementクラスを使ったXMLの構築では、System.Xml.XmlDocumentクラスを使った場合と比べるとXMLの構造にごく近い形で記述でき、コードが非常に分かりやすくなります。 インターフェイスもシンプルで把握しやすく、目的とするコードを手早く記述することができます。
System.Xml.Linq名前空間のクラスはアセンブリSystem.Xml.Linq.dllに含まれています。 使用する際はSystem.Xml.Linq.dllを参照に追加します。
この文書では、XMLの読み込みと加工、XMLからのデータの読み出し、またLINQ to XMLの機能については扱いません。
概略
System.Xml.Linq名前空間のクラス郡を用いてXML文書の構築を行う場合は、XDocumentクラスやXElementクラスなどを使います。 これらのクラスを入れ子にしていくことで文書構造を構成します。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
new XElement("html", // <html>要素
new XAttribute("lang", "ja"), // lang属性、属性の値に"ja"を指定(lang="ja")
new XElement("head", // <head>要素
new XElement("title", "タイトル") // <title>要素、要素のテキストとして"タイトル"を指定
),
new XElement("body", // <body>要素
new XComment("本文"), // コメント、<!--本文-->を作成
new XElement("p", "段落"), // <p>要素、要素のテキストとして"段落"を指定
new XElement("p", 1234567) // <p>要素、要素のテキストとして値1234567を指定
)
)
);
Console.WriteLine(doc);
}
}
<html lang="ja"> <head> <title>タイトル</title> </head> <body> <!--本文--> <p>段落</p> <p>1234567</p> </body> </html>
XDocument
がXML文書、XElement
が要素を表すクラスとなります。 XElementのコンストラクタでは、new XElement(要素名, 子ノード, 子ノード, ...)
のように引数を指定します。 これにより、要素に任意個の子ノードを含めることができます。
子ノードとしてXElementを指定すれば子要素となり、これを入れ子にしていくことで文書構造を定義していくことができます。 XElementではなく文字列を子ノードに指定すれば、テキストノード、つまり要素内のテキストを指定することができます。 また、子ノードとしてXAttributeやXCommentを含めれば、要素に属性やコメントを付与することができます。
生成したXML文書をファイルなどに保存する場合はXDocument.Saveメソッドを使います。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
new XElement("html",
new XAttribute("lang", "ja"),
new XElement("head",
new XElement("title", "タイトル")
),
new XElement("body",
new XElement("p", "本文")
)
)
);
// XDocumentの内容をtest.xmlに保存する
doc.Save("test.xml");
}
}
<?xml version="1.0" encoding="utf-8"?>
<html lang="ja">
<head>
<title>タイトル</title>
</head>
<body>
<p>本文</p>
</body>
</html>
Saveメソッドでの保存時に指定できるオプションについては§.Saveメソッドを用いた出力を参照してください。
インデントの有無・インデント幅の変更、文字コードの扱いなど、出力時の動作を変更する方法については後述の§.XmlWriterを用いた出力を参照してください。
XMLツリーの構築
XMLツリーを構築する際には、System.Xml.Linq名前空間のクラスを組み合わせて構成します。 以下はその主なクラスです。
ノードの種類・構成要素 | 該当するクラス | 解説 |
---|---|---|
XML文書 | XDocument | - |
要素 | XElement | §.要素・属性・テキストノード (XElement・XAttribute・XText) |
属性 | XAttribute | §.要素・属性・テキストノード (XElement・XAttribute・XText) |
テキスト | XText | §.要素・属性・テキストノード (XElement・XAttribute・XText) |
CDATAセクション | XCData | §.CDATAセクション (XCData) |
コメント | XComment | §.コメントノード (XComment) |
XML宣言 | XDeclaration | §.XML宣言 (XDeclaration) |
名前空間 | XNamespace | §.名前空間 (XNamespace) |
要素・属性・テキストノード (XElement・XAttribute・XText)
要素を作成するにはXElementクラス、属性を作成するにはXAttributeクラスを使います。
XElementコンストラクタ・XAttributeコンストラクタの第一引数は要素・属性の名前を指定します。 XElementコンストラクタでは、第二引数以降には子要素・要素のテキスト・属性を指定することができ、それぞれ指定した順序で要素に追加されます。 第二引数以降を指定しなければ空の要素(<element/>
)が作成されます。 XAttributeコンストラクタでは、第二引数に属性の値を指定します。
要素の作成 |
new XElement(要素名, 子ノード, 子ノード, ...)
|
空の要素の作成 |
new XElement(要素名)
|
属性の作成 |
new XAttribute(属性名, 属性値)
|
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var element = new XElement("element", // 名前"element"で要素を作成
new XAttribute("attr1", "value1"), // 名前"attr1"・値"value1"の属性を追加
new XElement("child1"), // 名前"child1"の要素を追加
"text1", // テキスト"text1"を追加
new XAttribute("attr2", "value2"), // 名前"attr1"・値"value1"の属性を追加
"text2", // テキスト"text2"を追加
new XElement("child2") // 名前"child2"の要素を追加
);
Console.WriteLine(element);
}
}
<element attr1="value1" attr2="value2"> <child1 />text1text2<child2 /></element>
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var attr1 = new XAttribute("attr1", "value1");
var child1 = new XElement("child1");
var text1 = "text1";
var attr2 = new XAttribute("attr2", "value2");
var text2 = "text2";
var child2 = new XElement("child2");
var element = new XElement("element",
attr1,
child1,
text1,
attr2,
text2,
child2
);
Console.WriteLine(element);
}
}
XDocumentは子要素(XElement)を一つだけ持つことができます。 XMLではルート要素(他の要素に格納されない最上位の要素)は常に1つだけです。 複数のルート要素を持たせることはできません。 XDocumentのルート要素はXDocument.Rootプロパティを使って取得することができます。
XElementコンストラクタの第二引数以降に文字列を指定すると、それが要素のテキストノードとして格納されます。 一方、テキストノードを作成するためにXTextクラスを用いることもできます。 XElementコンストラクタの引数に文字列を指定した場合は、それが自動的にXTextへと変換され、XElementの子ノードとして追加されます。
文字列を使った作成 |
new XElement(要素名, "テキスト", "テキスト", ...)
|
XTextを使った作成 |
new XElement(要素名, new XText("テキスト"), new XText("テキスト"), ...)
|
したがって、以下のコードはどちらも同じXMLツリーを生成します。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var element = new XElement("element",
new XElement("child1", "text1" /*テキストノード*/),
new XElement("child2", "text2" /*テキストノード*/),
"text3" /*テキストノード*/
);
Console.WriteLine(element);
}
}
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var element = new XElement("element",
new XElement("child1", new XText("text1")),
new XElement("child2", new XText("text2")),
new XText("text3")
);
Console.WriteLine(element);
}
}
<element> <child1>text1</child1> <child2>text2</child2>text3</element>
このように、テキストノードを単に文字列で記述した場合とXTextクラスを用いて記述した場合では結果に違いはありません。 また、XTextクラスを用いてテキストノードを記述するとコードの記述量が増えるというデメリットはありますが、一方で単に文字列で記述した場合と比べるとそれがテキストノードであることを分かりやすくすることができるというメリットもあります。
CDATAセクションを用いてテキストノードを記述したい場合はXCDataクラスを用います。
詳細は後述の§.要素のテキスト・属性の値で解説しますが、XElementでは数値など文字列以外の値を指定した場合はそれが自動的にテキストノードとして追加されます。 一方、XTextコンストラクタでは文字列のみが指定できるため、数値などを指定することはできません。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
// 数値をテキストノードとして追加する
var element = new XElement("element",
new XElement("value", 0),
new XElement("value", 1.23)
);
Console.WriteLine(element);
}
}
<element> <value>0</value> <value>1.23</value> </element>
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
// XTextコンストラクタは文字列に限られるため、以下のように記述することはできない
var element = new XElement("element",
new XElement("value", new XText(0)),
// error CS1503: 引数 1: 'int' から 'string' に変換できません
new XElement("value", new XText(1.23))
// error CS1503: 引数 1: 'double' から 'string' に変換できません
);
Console.WriteLine(element);
}
}
要素のテキスト・属性の値
XElement・XAttributeコンストラクタの第二引数以降はObject型になっていて、要素のテキスト・属性値として文字列だけでなく数値やDateTimeOffset型、Uriクラスなどの値を直接指定することができます。
コンストラクタで要素のテキスト・属性値として指定した値は、基本的にはToStringメソッドによって文字列化した場合と同じフォーマットで出力されますが、日時型(DateTime型・DateTimeOffset型)を指定した場合はW3C-DTF形式(ISO8601形式)でフォーマットされます。 それ以外の書式で出力したい場合はToStringメソッドで目的の書式を指定して文字列化する必要があります。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
new XElement("html", // XML文書のルート要素として<html>を作成
new XElement("body", // <html>要素の子要素として<body>を作成
new XElement("a", // <body>要素の子要素として<a>を作成
new XAttribute("href", // <a>要素のhref属性の値にUriクラスのインスタンスを指定
new Uri("http://example.com/")),
"リンク" // <a>要素のテキストとして文字列"リンク"を指定
),
new XElement("time", // <body>要素の子要素として<time>を作成
new XAttribute("datetime", // <time>要素のdatetime属性の値にDateTimeOffsetの値を指定
DateTimeOffset.Now),
DateTimeOffset.Now.ToString("D") // <time>要素のテキストとして、ToStringで文字列化したDateTimeOffsetの値を指定
),
new XElement("span", // <body>要素の子要素として<span>を作成
0, // <span>要素のテキストとして数値・文字列を指定する
"文字列",
1.23)
)
)
);
Console.WriteLine(doc);
}
}
<html> <body> <a href="http://example.com/">リンク</a> <time datetime="2016-02-22T12:34:56.789012+09:00">2016年2月22日</time> <span>0文字列1.23</span> </body> </html>
XElementコンストラクタで連続する複数のテキストを指定した場合は、上記のように一つの連結したテキストとして出力されます。 間に空白や区切り文字などが挟まれることはありません。
DateTimeOffsetを文字列化する際の書式については書式指定子 §.日付と時刻の書式指定子を参照してください。
TimeSpan型の値も日時と同様にISO8601形式の時間長(duration)として出力されます。 これ以外の書式で出力したい場合はToStringメソッドで目的の書式を指定して文字列化する必要があります。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
new XElement("root",
new XElement("e", TimeSpan.FromSeconds(1.0)), // 1秒
new XElement("e", TimeSpan.FromMinutes(1.0)), // 1分
new XElement("e", TimeSpan.FromHours(1.0)), // 1時間
new XElement("e", new TimeSpan(1, 2, 34, 56, 789)), // 1日と2時間34分56.789秒
new XElement("e", TimeSpan.FromDays(0.9)), // 0.9日
new XElement("e", TimeSpan.FromDays(1.0)), // 1.0日
new XElement("e", TimeSpan.FromDays(1.1)) // 1.1日
)
);
Console.WriteLine(doc.ToString());
}
}
<root> <e>PT1S</e> <e>PT1M</e> <e>PT1H</e> <e>P1DT2H34M56.789S</e> <e>PT21H36M</e> <e>P1D</e> <e>P1DT2H24M</e> </root>
出力される時間長の書式は次のようになっています。
記号 | 意味 |
---|---|
P
|
以降の文字列が時間長で表される期間(Period)であることを表す |
nD
|
日数(Days)を表す |
T
|
以降の文字列が時間長の時刻部分であることを表す ( P<日数>T<時間> ) |
nH
|
時間数(Hours)を表す |
nM
|
分数(Minutes)を表す |
nS
n.mS
|
秒数(Seconds)を表す 端数(ミリ秒)がある場合は小数点数となる |
TimeSpanを文字列化する際の書式については書式指定子 §.時間間隔の書式指定子を参照してください。
子ノードの追加 (XElement.Add)
XDocumentやXElementでは、インスタンス作成時にコンストラクタで子ノードを格納するほかにも、インスタンス作成後にAddメソッドなどを使って子ノードを追加していくこともできます。
Addメソッドの引数もコンストラクタと同様にObject型となっているため、§.要素のテキスト・属性の値と同じように要素に対してテキストノードや属性を追加する場合にもこのメソッドを使うことができます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
// <p>要素
var p = new XElement("p");
p.Add("本文"); // <p>要素にテキストノード"本文"を追加する
// <body>要素
var body = new XElement("body");
body.Add(p); // <p>要素を<body>要素の子ノードとして追加する
// <head>要素
var head = new XElement("head",
new XElement("title", "タイトル"));
// <html>要素
var html = new XElement("html");
html.Add(head, body); // <head>要素と<body>要素を<html>要素の子ノードとして追加する
// 属性lang="ja"を<html>要素に追加する
html.Add(new XAttribute("lang", "ja"));
// XML文書
var doc = new XDocument();
doc.Add(html); // <html>要素をドキュメントノード(ルートノード)として追加する
Console.WriteLine(doc);
}
}
<html lang="ja"> <head> <title>タイトル</title> </head> <body> <p>本文</p> </body> </html>
Addメソッドのほかにも、ノードを追加したい位置に応じて次のメソッドを使うことができます。
メソッド | 動作 |
---|---|
Add(node) | nodeを子ノードの末尾に追加する |
AddFirst(node) | nodeを子ノードの先頭に追加する |
AddBeforeSelf(node) | nodeを自身の直前に追加する(兄ノードとして追加する) |
AddAfterSelf(node) | nodeを自身の直後に追加する(弟ノードとして追加する) |
これらのメソッドはXContainerクラスによって提供され、このクラスを継承しているXDocumentやXElementでは共通してこのメソッドを使うことができます。
これらのメソッドでノードが追加される位置を実際のコードを使って比較すると次のようになります。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var self = new XElement("self",
new XElement("child"));
var root = new XElement("root", self);
// 追加前の状態を表示
Console.WriteLine(root);
Console.WriteLine();
// AddXXXメソッドを使ってノードを追加
self.Add(new XElement("last"));
self.AddFirst(new XElement("first"));
self.AddBeforeSelf(new XElement("before"));
self.AddAfterSelf(new XElement("after"));
// 追加後の状態を表示
Console.WriteLine(root);
}
}
<root> <self> <child /> </self> </root> <root> <before /> <self> <first /> <child /> <last /> </self> <after /> </root>
名前空間 (XNamespace)
この項での名前空間とは、プログラム要素としての名前空間(System.Xml.Linq名前空間など)ではなく、XML要素の名前空間(xmlns
)のことです。 混同しないように注意してください。
名前空間付きの要素を作成したい場合は、XNamespaceクラスを使います。 XElementコンストラクタの第一引数に名前空間なしの要素名を指定するかわりに、次のようにXNamespace + 要素名
と指定することによって要素に名前空間を与えた上でXMLツリーに追加することができます。
名前空間のない要素の作成 |
new XElement(要素名)
|
名前空間付きの要素の作成 |
new XElement(XNamespace + 要素名)
|
XNamespace + 要素名
という式からはXNameクラスのインスタンスが作成されます。 XNameクラスについて詳しくは§.名前 (XName)で解説します。
XNamespaceのインスタンスは、名前空間名の文字列を型変換することで作成します。 コンストラクタを使った作成はできない点に注意してください。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
// 名前空間
var nsAtom = (XNamespace)"http://www.w3.org/2005/Atom";
var nsSy = (XNamespace)"http://purl.org/rss/1.0/modules/syndication/";
var doc = new XDocument(
// 名前空間付きの要素
new XElement(nsAtom + "feed",
new XElement(nsAtom + "title", "更新履歴 (Atom 1.0)"),
new XElement(nsAtom + "updated", DateTimeOffset.Now),
new XElement(nsSy + "updatePeriod", "daily")
)
);
Console.WriteLine(doc);
}
}
<feed xmlns="http://www.w3.org/2005/Atom"> <title>更新履歴 (Atom 1.0)</title> <updated>2016-02-22T12:34:56.789012+09:00</updated> <updatePeriod xmlns="http://purl.org/rss/1.0/modules/syndication/">daily</updatePeriod> </feed>
上記のように、要素に与えた各名前空間はプレフィックスなしのデフォルト名前空間として出力されます。 同一スコープ内にある同一名前空間の要素は自動的に弁別されるため、すべての要素に名前空間を指定しても、そのすべてにxmlns
属性が付与されることはありません。
名前空間付きの要素を作成する場合、XElementコンストラクタの第一引数に"{名前空間}要素名
"のような展開された名前を指定することもできます。 ただし、このような指定は名前空間と要素名の解析処理が必要となり非効率であるため、推奨される方法ではありません。 特別な理由がない限りはXNamespaceクラスを使用するようにしてください。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
// '展開された'名前を使って名前空間付きの要素を作成する
new XElement("{http://www.w3.org/2005/Atom}feed",
new XElement("{http://www.w3.org/2005/Atom}title", "更新履歴 (Atom 1.0)"),
new XElement("{http://www.w3.org/2005/Atom}updated", DateTimeOffset.Now),
new XElement("{http://purl.org/rss/1.0/modules/syndication/}updatePeriod", "daily")
)
);
Console.WriteLine(doc);
}
}
<feed xmlns="http://www.w3.org/2005/Atom"> <title>更新履歴 (Atom 1.0)</title> <updated>2016-02-22T12:34:56.789012+09:00</updated> <updatePeriod xmlns="http://purl.org/rss/1.0/modules/syndication/">daily</updatePeriod> </feed>
名前空間のプレフィックス
名前空間を指定した要素を、名前空間プレフィックス(接頭辞)付きの要素名(qualified name)で出力したい場合は、XAttributeを使って要素にxmlns
属性を付与して名前空間とプレフィックスの宣言をします。 例えばnew XAttribute(XNamespace.Xmlns + "ex", "http://example.com/")
とすれば、xmlns:ex="http://example.com/"というxmlns
属性を表すXAttributeインスタンスが作成されます。
名前空間宣言となるxmlns属性 | 名前空間宣言を作成するためのXAttributeインスタンス |
---|---|
xmlns:ex="http://example.com/" |
new XAttribute(XNamespace.Xmlns + "ex", "http://example.com/")
|
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" |
new XAttribute(XNamespace.Xmlns + "xsl", "http://www.w3.org/1999/XSL/Transform")
|
xmlns:atom="http://www.w3.org/2005/Atom" |
new XAttribute(XNamespace.Xmlns + "atom", "http://www.w3.org/2005/Atom")
|
このような属性をもつ要素では、その要素と、要素内にある同一名前空間の要素すべてに対して自動的にプレフィックスが付与されます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var nsAtom = (XNamespace)"http://www.w3.org/2005/Atom";
var nsSy = (XNamespace)"http://purl.org/rss/1.0/modules/syndication/";
var doc = new XDocument(
new XElement(nsAtom + "feed",
// Atomの名前空間に属する要素にはプレフィックス"atom"を付ける
new XAttribute(XNamespace.Xmlns + "atom", nsAtom.NamespaceName),
// Syndicationの名前空間に属する要素にはプレフィックス"sy"を付ける
new XAttribute(XNamespace.Xmlns + "sy", nsSy.NamespaceName),
new XElement(nsAtom + "title", "更新履歴 (Atom 1.0)"),
new XElement(nsAtom + "updated", DateTimeOffset.Now),
new XElement(nsSy + "updatePeriod", "daily")
)
);
Console.WriteLine(doc);
}
}
<atom:feed xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"> <atom:title>更新履歴 (Atom 1.0)</atom:title> <atom:updated>2016-02-22T12:34:56.789012+09:00</atom:updated> <sy:updatePeriod>daily</sy:updatePeriod> </atom:feed>
上記の例にあるように、名前空間名はXNamespace.NamespaceNameプロパティより参照することができるため、何度も同じ名前空間名を文字列で記述する必要がなくなります。
プレフィックス付きの属性名を作成する場合も要素と同様に行うことができます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var nsRss = (XNamespace)"http://purl.org/rss/1.0/";
var nsRdf = (XNamespace)"http://www.w3.org/1999/02/22-rdf-syntax-ns#";
var doc = new XDocument(
new XElement(nsRdf + "RDF",
// RSS1.0の名前空間に属する要素・属性にはプレフィックス"rss"を付ける
new XAttribute(XNamespace.Xmlns + "rss", nsRss.NamespaceName),
// RDFの名前空間に属する要素・属性にはプレフィックス"rdf"を付ける
new XAttribute(XNamespace.Xmlns + "rdf", nsRdf.NamespaceName),
new XElement(nsRss + "channel",
new XAttribute(nsRdf + "about", "http://example.com/rss10.rdf"),
new XElement(nsRss + "title", "更新履歴 (RSS 1.0)"),
new XElement(nsRss + "items",
new XElement(nsRdf + "Seq",
new XElement(nsRdf + "li", new XAttribute(nsRdf + "resource", "tag:example.com,2016-02-22:update1")),
new XElement(nsRdf + "li", new XAttribute(nsRdf + "resource", "tag:example.com,2016-02-22:update2")),
new XElement(nsRdf + "li", new XAttribute(nsRdf + "resource", "tag:example.com,2016-02-22:update3"))
)
)
)
)
);
Console.WriteLine(doc);
}
}
<rdf:RDF xmlns:rss="http://purl.org/rss/1.0/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rss:channel rdf:about="http://example.com/rss10.rdf"> <rss:title>更新履歴 (RSS 1.0)</rss:title> <rss:items> <rdf:Seq> <rdf:li rdf:resource="tag:example.com,2016-02-22:update1" /> <rdf:li rdf:resource="tag:example.com,2016-02-22:update2" /> <rdf:li rdf:resource="tag:example.com,2016-02-22:update3" /> </rdf:Seq> </rss:items> </rss:channel> </rdf:RDF>
プレフィックスとデフォルト名前空間の併用
特定の名前空間にプレフィックスを付けたくない場合、必要な要素・属性だけにプレフィックスを付けたい場合は、デフォルト名前空間と併用することができます。 例えばルート要素と同じ名前空間の要素にはプレフィックスを付与したくないといった場合には、プレフィックスの宣言をしないことでデフォルト名前空間として出力させることができます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var nsAtom = (XNamespace)"http://www.w3.org/2005/Atom";
var nsSy = (XNamespace)"http://purl.org/rss/1.0/modules/syndication/";
var doc = new XDocument(
new XElement(nsAtom + "feed",
// Atomの名前空間に属する要素にはプレフィックスを付けない
// new XAttribute(XNamespace.Xmlns + "atom", nsAtom.NamespaceName),
// Syndicationの名前空間に属する要素にはプレフィックスを付ける
new XAttribute(XNamespace.Xmlns + "sy", nsSy.NamespaceName),
new XElement(nsAtom + "title", "更新履歴 (Atom 1.0)"),
new XElement(nsAtom + "updated", DateTimeOffset.Now),
new XElement(nsSy + "updatePeriod", "daily")
)
);
Console.WriteLine(doc);
}
}
<feed xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns="http://www.w3.org/2005/Atom"> <title>更新履歴 (Atom 1.0)</title> <updated>2016-02-22T12:34:56.789012+09:00</updated> <sy:updatePeriod>daily</sy:updatePeriod> </feed>
名前 (XName)
XNameクラスは要素や属性の名前を扱うクラスで、名前空間名とローカル名(名前のうち、名前空間によって修飾される部分)をセットで保持します。 XElementやXAttributeのコンストラクタの第一引数で指定した要素・属性の名前はXNameに変換され、各クラスで保持されます。 名前空間のない要素名・属性名の場合もXNameとして保持されます。
次の例のように、要素名・属性名となるXNameのインスタンスを事前に作成しておき、要素・属性を作成する際にXElementとXAttributeに与える名前として直接XNameを指定することができます。 特に、名前空間を多く扱うXML文書ではXNamespace + "要素名・属性名"
を記述することが多くなりますが、そのような場合にXNameを使うと記述を簡略化できます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var nsAtom = (XNamespace)"http://www.w3.org/2005/Atom";
// 名前空間で修飾された要素名となるXNameを作成
XName elemFeed = nsAtom + "feed";
XName elemTitle = nsAtom + "title";
var doc = new XDocument(
// XNameを使って名前空間で修飾された名前を持つ要素を作成する
new XElement(elemFeed,
new XElement(elemTitle, "更新履歴 (Atom 1.0)")
)
);
Console.WriteLine(doc);
}
}
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var nsAtom = (XNamespace)"http://www.w3.org/2005/Atom";
var doc = new XDocument(
// XNameを使わずに名前空間で修飾された名前を持つ要素を作成する
new XElement(nsAtom + "feed",
new XElement(nsAtom + "title", "更新履歴 (Atom 1.0)")
)
);
Console.WriteLine(doc);
}
}
比較のためXNameを用いた場合とそうでない場合を併記していますが、上記のいずれも同じXMLツリーを生成します。
<feed xmlns="http://www.w3.org/2005/Atom"> <title>更新履歴 (Atom 1.0)</title> </feed>
このようにXNamespace + "ローカル名"
とすれば名前空間付きの名前を表すXNameが作成されます。 名前空間なしの名前の場合は、ローカル名の文字列を直接XNameにキャストするか、名前空間が無いことを表すXNamespace.Noneプロパティとローカル名を連結して作成します。
XNameを作成する構文 | 作成されるXNameの展開名 | |
---|---|---|
名前空間付き |
(XNamespace)"http://example.com/ns" + "localname"
|
{http://example.com/ns}localname |
名前空間なし |
(XName)"localname"
|
localname |
XNamespace.None + "localname"
|
localname |
要素名・属性名はそれぞれXElement.NameプロパティとXAttribute.NameプロパティにXNameの形で格納されます。 XName.Namespaceプロパティを参照すれば名前空間名、XName.LocalNameプロパティを参照すればローカル名を取得することができます。 XName.ToStringメソッドなどによって文字列化した場合は、名前空間が展開された名前を取得することができます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var nsAtom = (XNamespace)"http://www.w3.org/2005/Atom";
var feed = new XElement(nsAtom + "feed"); // 名前空間付きの要素
// 要素の名前を取得して表示
Console.WriteLine("Name = {0}", feed.Name); // 展開名を取得して表示
Console.WriteLine("Namespace = {0}", feed.Name.Namespace); // 名前空間名を取得して表示
Console.WriteLine("LocalName = {0}", feed.Name.LocalName); // ローカル名を取得して表示
Console.WriteLine("------------");
var rss = new XElement(XNamespace.None + "rss"); // 名前空間のない要素
Console.WriteLine("Name = {0}", rss.Name);
Console.WriteLine("Namespace = {0}", rss.Name.Namespace);
Console.WriteLine("LocalName = {0}", rss.Name.LocalName);
}
}
Name = {http://www.w3.org/2005/Atom}feed Namespace = http://www.w3.org/2005/Atom LocalName = feed ------------ Name = rss Namespace = LocalName = rss
XNameクラスではインスタンスがキャッシュされ、同一名前空間かつ同一ローカル名の名前を持つXNameを作成すると常に同一のインスタンスが返されます。 例えば次のように要素ごとに名前を指定してXNameを作成した場合でも、あらかじめ作成したXNameを用いた場合でも、それらはすべて同一のインスタンスが用いられます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
// 要素毎にXNameを作成した場合
var e1 = new XElement((XNamespace)"http://example.com/ns" + "localname");
var e2 = new XElement((XNamespace)"http://example.com/ns" + "localname");
Console.WriteLine("e1.Name == e2.Name : {0}", Object.ReferenceEquals(e1.Name, e2.Name));
// 要素でXNameを共用した使用した場合
var name = (XNamespace)"http://example.com/ns" + "localname";
var e3 = new XElement(name);
var e4 = new XElement(name);
Console.WriteLine("e3.Name == e4.Name : {0}", Object.ReferenceEquals(e3.Name, e4.Name));
Console.WriteLine("e1.Name == e3.Name : {0}", Object.ReferenceEquals(e1.Name, e3.Name));
}
}
e1.Name == e2.Name : True e3.Name == e4.Name : True e1.Name == e3.Name : True
特殊なノード
XML宣言 (XDeclaration)
XML宣言を明示的に設定したい場合は、XDeclarationクラスを使って指定します。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
// XML宣言
new XDeclaration(version: "1.0", encoding: "utf-8", standalone: "yes"),
new XElement("rss",
new XAttribute("version", "2.0"),
new XElement("channel",
new XElement("title", "更新履歴 (RSS 2.0)")
)
)
);
// ファイルtest.xmlに保存
doc.Save("test.xml");
// 以下の呼び出しではXML宣言は出力されない
//Console.WriteLine(doc);
//Console.WriteLine(doc.ToString());
}
}
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0">
<channel>
<title>更新履歴 (RSS 2.0)</title>
</channel>
</rss>
Saveメソッドなどによって出力する場合、XDeclarationが無くてもXML宣言が出力されます。 一方、ToStringメソッドによって文字列化した場合は、XDeclarationの有無に関わらずXML宣言は出力されません。
Saveメソッドによる出力時オプションについては§.Saveメソッドを用いた出力、またXML宣言と出力時の文字コードの扱いなどについてはXmlWriterSettings §.エンコーディング (XmlWriterSettings.Encoding)を参照してください。
XML宣言はXDocument.Declarationプロパティを使うことによっても取得・設定することができます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
new XElement("rss",
new XAttribute("version", "2.0"),
new XElement("channel",
new XElement("title", "更新履歴 (RSS 2.0)")
)
)
);
// プロパティを使ってXML宣言を設定する
doc.Declaration = new XDeclaration(version: "1.0", encoding: "utf-8", standalone: "yes");
// ファイルtest.xmlに保存
doc.Save("test.xml");
}
}
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0">
<channel>
<title>更新履歴 (RSS 2.0)</title>
</channel>
</rss>
XDocumentに複数のXML宣言を持たせることはできません。 XDocumentに複数のXDeclarationを持たせようとした場合、ArgumentExceptionがスローされます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
// 1つ目のXDeclaration
new XDeclaration(version: "1.0", encoding: "utf-8", standalone: "yes"),
// 2つ目のXDeclaration
new XDeclaration(version: "1.0", encoding: "utf-8", standalone: "yes")
);
doc.Save("test.xml");
}
}
ハンドルされていない例外: System.ArgumentException: 空白以外の文字をコンテンツに追加できません。 場所 System.Xml.Linq.XDocument.ValidateString(String s) 場所 System.Xml.Linq.XContainer.AddStringSkipNotify(String s) 場所 System.Xml.Linq.XContainer.AddContentSkipNotify(Object content) 場所 Sample.Main()
DOCTYPE宣言 (XDocumentType)
DOCTYPE宣言(ドキュメントタイプ)を記述する場合は、XDocumentTypeクラスを使います。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
// DOCTYPE宣言
new XDocumentType("html", null, null, null),
new XElement("html",
new XElement("head",
new XElement("title", "タイトル")
),
new XElement("body",
new XElement("p", "段落")
)
)
);
Console.WriteLine(doc);
}
}
<!DOCTYPE html > <html> <head> <title>タイトル</title> </head> <body> <p>段落</p> </body> </html>
XML宣言と同様、XDocument.DocumentTypeプロパティを使ってDOCTYPE宣言を参照することはできますが、このプロパティは読み取り専用プロパティであるため設定することはできません。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
new XElement("html",
new XElement("head",
new XElement("title", "タイトル")
),
new XElement("body",
new XElement("p", "段落")
)
)
);
// プロパティを使ってDOCTYPE宣言を設定することはできない
doc.DocumentType = new XDocumentType("html", null, null, null);
// error CS0200: プロパティまたはインデクサー 'System.Xml.Linq.XDocument.DocumentType' は読み取り専用なので、割り当てることはできません
Console.WriteLine(doc);
}
}
XDocumentに複数のDOCTYPE宣言を持たせることはできません。 XDocumentに複数のXDocumentTypeを持たせようとした場合、InvalidOperationExceptionがスローされます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
// 1つ目のXDocumentType
new XDocumentType("html", null, null, null),
// 2つ目のXDocumentType
new XDocumentType("html", null, null, null)
);
doc.Save("test.xml");
}
}
ハンドルされていない例外: System.InvalidOperationException: この操作を実行すると構造の正しくないドキュメントが作成されます。 場所 System.Xml.Linq.XDocument.ValidateDocument(XNode previous, XmlNodeType allowBefore, XmlNodeType allowAfter) 場所 System.Xml.Linq.XContainer.AddNodeSkipNotify(XNode n) 場所 System.Xml.Linq.XContainer.AddContentSkipNotify(Object content) 場所 Sample.Main()
XML処理命令 (XProcessingInstruction)
XML処理命令(Processing Instruction)を記述する場合は、XProcessingInstructionクラスを使います。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
// XML処理命令
new XProcessingInstruction("xml-stylesheet", "type=\"text/css\" href=\"feed.css\""),
new XElement("rss",
new XAttribute("version", "2.0"),
new XElement("channel",
new XElement("title", "更新履歴 (RSS 2.0)")
)
)
);
Console.WriteLine(doc);
}
}
<?xml-stylesheet type="text/css" href="feed.css"?> <rss version="2.0"> <channel> <title>更新履歴 (RSS 2.0)</title> </channel> </rss>
コメントノード (XComment)
XMLツリー内にコメントを記述する場合は、XCommentクラスを使います。 XElementクラス等とは異なり、XCommentコンストラクタの引数に指定できるのは常にコメントとなる単一の文字列だけです。 複数の文字列や数値を指定したりすることはできないため、あらかじめString.FormatメソッドやString.Joinメソッドなどによってフォーマット・結合しておく必要があります。
コメントノードはルート要素の前後を含む任意の位置に追加することができます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
new XComment("コメント"),
new XElement("root", new XComment("コメント")),
new XComment("コメント")
// XCommentではXElementのように複数の引数を与えることはできない
//new XComment("コメント", "コメント", "コメント")
);
Console.WriteLine(doc);
}
}
<!--コメント--> <root> <!--コメント--> </root> <!--コメント-->
XCommentクラスでは、"<!--
"や"-->
"などのコメント開始・終了のマークを含む文字列もコメントとして指定することができます。 この場合、ArgumentExceptionなどの例外はスローされず、ハイフン"-
"が2つ以上連続している個所に対して自動的に空白が挿入された上でコメントノードが作成されます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
new XComment("XXX<!--"),
new XComment("-->XXX"),
new XComment("--XXX"),
new XComment("XXX--"),
new XComment("--XXX----XXX--")
);
Console.WriteLine(doc);
}
}
<!--XXX<!- - --> <!--- ->XXX--> <!--- -XXX--> <!--XXX- - --> <!--- -XXX- - - -XXX- - -->
なお、System.Xml.XmlDocument.CreateCommentメソッドでは、このようなコメントノードを追加しようとした場合にはArgumentExceptionがスローされます。
using System;
using System.Xml;
class Sample {
static void Main()
{
var doc = new XmlDocument();
// 以下のいずれの呼び出しもArgumentExceptionをスローする
doc.AppendChild(doc.CreateComment("XXX<!--"));
doc.AppendChild(doc.CreateComment("-->XXX"));
doc.AppendChild(doc.CreateComment("--XXX"));
doc.AppendChild(doc.CreateComment("XXX--"));
doc.AppendChild(doc.CreateComment("--XXX----XXX--"));
}
}
XmlDocumentとXDocumentを並行して扱う場合や実装の書き換えを行う際には動作が異なる点に注意してください。
CDATAセクション (XCData)
テキストノード内にCDATAセクションを記述する場合は、XCDataクラスを使います。 CDATAセクションを用いると、テキストノード内で<
、>
、"
といった記号を文字参照形式ではなくそのまま記述することができます。 XCommentコンストラクタと同様、XCDataコンストラクタも引数に指定できるのは常にCDATAセクションとなる単一の文字列だけです。 複数の文字列や数値を指定したりすることはできないため、あらかじめ結合しておく必要があります。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
new XElement("root",
new XElement("text",
// CDATAセクションを用いてC#コードを記述する
new XCData("var list = new List<string>() {\"foo\", \"bar\", \"baz\"};")
),
new XElement("text",
// CDATAセクションを用いてXMLコードを記述する
new XCData(@"<?xml version=""1.0"" encoding=""utf-8"" standalone=""yes""?>
<rss version=""2.0"">
<channel>
<title>更新履歴 (RSS 2.0)</title>
</channel>
</rss>
")
)
)
);
Console.WriteLine(doc);
}
}
<root> <text><![CDATA[var list = new List<string>() {"foo", "bar", "baz"};]]></text> <text><![CDATA[<?xml version="1.0" encoding="utf-8" standalone="yes"?> <rss version="2.0"> <channel> <title>更新履歴 (RSS 2.0)</title> </channel> </rss> ]]></text> </root>
一つの要素内に通常のテキストとCDATAセクションを混在させることもできます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
new XElement("root",
"foo",
new XCData("bar"),
"baz"
)
);
Console.WriteLine(doc);
}
}
<root>foo<![CDATA[bar]]>baz</root>
このようにして出力されたXMLが読み込まれる際には、テキストノードとCDATAセクション内のテキストが連結された状態で読み込まれます。 つまり、上記のXML文書における<root>
要素はテキスト"foobarbaz"
を持っている状態となります。
XCDataクラスでは、CDATAセクションの開始・終了のマーク("<![CDATA[
"および"]]>
")を含む文字列もCDATAセクションの文字列として指定することができます。 この場合、ArgumentExceptionなどの例外はスローされません。 また、終了マーク"]]>
"に関しては、"]]
"と">
"がそれぞれ個別のCDATAセクションに分割された状態で作成されます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
new XElement("root",
new XElement("text", new XCData("<![CDATA[")), // CDATAセクションの開始マーク
new XElement("text", new XCData("]]>")) // CDATAセクションの終了マーク
)
);
Console.WriteLine(doc);
}
}
<root> <text><![CDATA[<![CDATA[]]></text> <text><![CDATA[]]]]><![CDATA[>]]></text> </root>
XMLフラグメント
XMLフラグメントの構築
次のようにXElementだけを用いればXMLフラグメント(不完全なXML文書の断片)を構築することもできます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var fragment = new XElement("body",
new XComment("本文"),
new XElement("p", "段落"),
new XElement("p", "段落")
);
Console.WriteLine(fragment);
}
}
<body> <!--本文--> <p>段落</p> <p>段落</p> </body>
XElementクラスもXDocumentクラス同様のSaveメソッドを持っているため、XMLフラグメントをファイルなどに保存することができます。 この際、デフォルトではXML宣言を含んだ状態で保存されます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var fragment = new XElement("body",
new XComment("本文"),
new XElement("p", "段落"),
new XElement("p", "段落")
);
fragment.Save("test.xml");
}
}
<?xml version="1.0" encoding="utf-8"?>
<body>
<!--本文-->
<p>段落</p>
<p>段落</p>
</body>
XML宣言を含まずに保存したい場合はXmlWriterを使います。 詳しくはXmlWriterSettings §.XML宣言の省略 (XmlWriterSetings.OmitXmlDeclaration)を参照してください。
XMLフラグメントのインポート (XElement.Parse)
XElement.Parseメソッドを用いると、文字列で記述されたXMLフラグメントを読み込んでXElementをルートとするXMLツリーを構築することができます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
new XElement("root")
);
// 文字列からXMLフラグメントを生成する
var fragment = XElement.Parse(@"<fragment>
<foo>foo</foo>
<bar>bar</bar>
</fragment>");
// XMLフラグメントのツリーをルート要素に追加する
doc.Root.Add(fragment);
Console.WriteLine(doc);
}
}
<root> <fragment> <foo>foo</foo> <bar>bar</bar> </fragment> </root>
XDocumentクラスにもXDocument.Parseメソッドが用意されているため、XDocumentクラスでも文字列からXML文書を構築することができます。
XElement.Parseメソッドで読み込めるのは一つの要素をルートとするXMLフラグメントのみです。 ルートに複数の要素があるようなXMLフラグメントを読み込むことはできません。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
new XElement("root")
);
// ルートに2つの要素が含まれるXMLフラグメントを読み込もうとする
// (実際にはSystem.Xml.XmlExceptionがスローされる)
var fragment = XElement.Parse(@"<foo>foo</foo>
<bar>bar</bar>");
doc.Root.Add(fragment);
Console.WriteLine(doc);
}
}
ハンドルされていない例外: System.Xml.XmlException: ルート要素が複数あります。 行 2、位置 2。 場所 System.Xml.XmlTextReaderImpl.Throw(Exception e) 場所 System.Xml.XmlTextReaderImpl.ParseDocumentContent() 場所 System.Xml.Linq.XElement.Load(XmlReader reader, LoadOptions options) 場所 System.Xml.Linq.XElement.Parse(String text, LoadOptions options) 場所 Sample.Main()
ルートに複数の要素を含むXMLフラグメントを読み込みたい場合は、以下のように仮のルート要素で包んだ上で読み込むようにします。 その後、XElement.Elements()メソッドを使って読み込んだルート要素内に含まれる子要素だけを取り出します。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
new XElement("root")
);
// ルートに2つの要素が含まれるXMLフラグメントを読み込みたい
string fragment = @"<foo>foo</foo>
<bar>bar</bar>";
// XMLフラグメントを仮のルート要素<wrapper>で包んだ上で読み込み、
// Elementsメソッドを使って<wrapper>の子要素だけを取り出す
var elements = XElement.Parse("<wrapper>" + fragment + "</wrapper>").Elements();
// 取り出した要素をルート要素に追加する
doc.Root.Add(elements);
Console.WriteLine(doc);
}
}
<root> <foo>foo</foo> <bar>bar</bar> </root>
名前空間を持つXMLフラグメントをインポートしてXML文書を組み立てる場合、各XMLフラグメントで宣言している名前空間が重複する場合があります。 例えば次のようなコードでXML文書を構築した場合、同じ名前空間の宣言が複数箇所に存在するようになります。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
// csprojファイルのテンプレートとなるXDocument
var doc = XDocument.Parse(@"<?xml version=""1.0"" encoding=""utf-8""?>
<Project DefaultTargets=""Build"" ToolsVersion=""4.0"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<PropertyGroup>
<AssemblyName>Sample</AssemblyName>
<OutputType>exe</OutputType>
<OutputPath>.\bin\</OutputPath>
<IntermediateOutputPath>.\obj\</IntermediateOutputPath>
</PropertyGroup>
<Import Project=""$(MSBuildBinPath)\Microsoft.CSharp.targets"" />
</Project>
");
// コンパイルするファイルを定義するItemGroupのXMLフラグメント
var elementItemGroupCompile = XElement.Parse(@"
<ItemGroup xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<Compile Include=""Properties\AssemblyInfo.cs"" />
<Compile Include=""Main.cs"" />
<Compile Include=""Class1.cs"" />
<Compile Include=""Class2.cs"" />
<Compile Include=""Class3.cs"" />
</ItemGroup>
");
// 参照するアセンブリを定義するItemGroupのXMLフラグメント
var elementItemGroupReference = XElement.Parse(@"
<ItemGroup xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<Reference Include=""System"" />
<Reference Include=""System.Core"" />
</ItemGroup>
");
// 読み込んだXMLフラグメントをXML文書に追加する
doc.Root.Add(elementItemGroupCompile);
doc.Root.Add(elementItemGroupReference);
// 構築したXML文書をファイルに保存する
doc.Save("test.csproj");
}
}
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<AssemblyName>Sample</AssemblyName>
<OutputType>exe</OutputType>
<OutputPath>.\bin\</OutputPath>
<IntermediateOutputPath>.\obj\</IntermediateOutputPath>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Main.cs" />
<Compile Include="Class1.cs" />
<Compile Include="Class2.cs" />
<Compile Include="Class3.cs" />
</ItemGroup>
<ItemGroup xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
</Project>
このような場合はSaveメソッドの引数でSaveOptions.OmitDuplicateNamespacesを指定することにより、保存する際に重複する名前空間宣言を省略させることができます。 詳しくは§.SaveOptions.OmitDuplicateNamespacesを参照してください。
csprojファイル(プロジェクトファイル)のフォーマット等についてはプロジェクトファイルを参照してください。
XMLの出力
ToStringメソッドによる文字列化
XDocumentやXElementに対してToStringメソッドを呼びだせばXMLツリーを文字列化して取得することができます。 ToStringメソッドで文字列化した場合、XML宣言は省略されます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var root = new XElement("root",
new XElement("child1", "foo"),
new XElement("child2", "bar"));
Console.WriteLine("[XElement.ToString]");
// XElementに含まれるXMLツリーを文字列化
Console.WriteLine(root.ToString());
// 単に以下のようにした場合は自動的にToStringメソッドによる
// XMLツリーの文字列化が行われる
//Console.WriteLine(root);
var doc = new XDocument(
new XDeclaration(version: "1.0", encoding: "utf-8", standalone: "yes"),
root
);
Console.WriteLine("[XDocument.ToString]");
// XDocumentに含まれるXMLツリーを文字列化
Console.WriteLine(doc.ToString());
}
}
[XElement.ToString] <root> <child1>foo</child1> <child2>bar</child2> </root> [XDocument.ToString] <root> <child1>foo</child1> <child2>bar</child2> </root>
インデントの有無やインデント幅を指定するなど、フォーマットを細かく指定して文字列化したい場合は、XmlWriterとXmlWriterSettingsを使う必要があります。 これについてはXmlWriterSettings §.XmlWriterを使ったXMLツリーの文字列化で解説しています。
Saveメソッドを用いた出力
XDocument.Saveメソッド・XElement.Saveメソッドを用いればファイルやストリームへの出力を行うこともできます。 Saveメソッドを使って出力した場合、XElement(=XMLフラグメント)の場合でもXML宣言が付与された状態で出力されます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var root = new XElement("root",
new XElement("child1", "foo"),
new XElement("child2", "bar"));
// XElementに含まれるXMLツリーをroot.xmlに保存
root.Save("root.xml");
var doc = new XDocument(
new XDeclaration(version: "1.0", encoding: "utf-8", standalone: "yes"),
root
);
// XDocumentに含まれるXMLツリーをdoc.xmlに保存
doc.Save("doc.xml");
}
}
<?xml version="1.0" encoding="utf-8"?>
<root>
<child1>foo</child1>
<child2>bar</child2>
</root>
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<root>
<child1>foo</child1>
<child2>bar</child2>
</root>
Saveメソッドでの出力時の動作を変更するにはSaveOptionsを指定します。 また、インデント幅や文字コードの指定、XML宣言の省略など、フォーマットを細かく制御したい場合はXmlWriterとXmlWriterSettingsを使って出力します。
Saveメソッドでは、文字コードにUTF-8などを用いて出力する場合にはBOM(Byte Order Mark)が付けられます。 BOMなしで出力したい場合についてはXmlWriterSettings §.BOM出力の制御を参照してください。
SaveOptions
XDocumentやXElementのToStringメソッド、Saveメソッドでは、出力時の動作を変えるオプションとしてSaveOptions列挙体の値を指定することができます。 SaveOptionsでは、次のいずれかの値を指定することができます。
SaveOptionsの値 | 出力時の動作 | 解説 |
---|---|---|
SaveOptions.None | SaveOptionsを指定しなかった場合のデフォルトと同じ。 | - |
SaveOptions.DisableFormatting | フォーマットを行わずに出力する。 インデントや改行など、(XML文書としての)意味を持たない要素間のホワイトスペースなどが省略された状態で出力する。 |
解説へ |
SaveOptions.OmitDuplicateNamespaces | 重複する名前空間宣言(xxx:xmlns="yyy" )がある場合は、それを削除した上で出力する。 |
解説へ |
SaveOptions.DisableFormatting
SaveOptions.DisableFormattingを指定した場合、出力に際して要素のインデントや要素間の改行は行われなくなります。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
var doc = new XDocument(
new XDeclaration(version: "1.0", encoding: "utf-8", standalone: "yes"),
new XElement("root",
new XElement("child1", "foo"),
new XElement("child2", "bar")
)
);
doc.Save("doc-none.xml", SaveOptions.None);
doc.Save("doc-df.xml", SaveOptions.DisableFormatting);
}
}
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<root>
<child1>foo</child1>
<child2>bar</child2>
</root>
<?xml version="1.0" encoding="utf-8" standalone="yes"?><root><child1>foo</child1><child2>bar</child2></root>
XDocument.LoadメソッドやXDocument.Parseメソッドを使ってXML文書を読み込む際にLoadOptions.PreserveWhitespace
を指定した場合、SaveOptions.None
やSaveOptions.DisableFormatting
を指定してもその指定に関わらず元のホワイトスペースが維持された状態で出力されます。
using System;
using System.Xml.Linq;
class Sample {
static void Main()
{
// LoadOptions.PreserveWhitespaceを指定してXMLを読み込む
var doc = XDocument.Parse(@"<?xml version=""1.0""?>
<root>
<foo>
foo
</foo>
<bar> bar </bar>
<baz>baz
</baz>
</root>
", LoadOptions.PreserveWhitespace);
doc.Save("doc-none.xml", SaveOptions.None);
doc.Save("doc-df.xml", SaveOptions.DisableFormatting);
}
}
<?xml version="1.0" encoding="utf-8"?>
<root>
<foo>
foo
</foo>
<bar> bar </bar>
<baz>baz
</baz>
</root>
<?xml version="1.0" encoding="utf-8"?>
<root>
<foo>
foo
</foo>
<bar> bar </bar>
<baz>baz
</baz>
</root>
SaveOptions.OmitDuplicateNamespaces
SaveOptions.OmitDuplicateNamespacesを指定した場合、出力に際して重複する名前空間宣言が削除されます。
using System;
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"
)
)
)
);
doc.Save("doc.xml", SaveOptions.None);
doc.Save("doc-odn.xml", SaveOptions.OmitDuplicateNamespaces);
}
}
<?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>
<?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>
これは例えば、XML文書を構築する際に、その一部として他のメソッドやライブラリが生成するXMLツリーを組み込むような場合に有効になる場合があります。 個々のXMLツリーが名前空間宣言を含んでいても、それを組み込む親要素の側で名前空間宣言をしていれば、SaveOptions.OmitDuplicateNamespaces
を指定することによって同一の名前空間宣言を省略させるようにすることができます。
SaveOptions.OmitDuplicateNamespaces
で省略されるのは名前空間とそのプレフィックスが一致する場合に限られます。 次のように同一名前空間でも異なるプレフィックスを用いている場合は、SaveOptions.OmitDuplicateNamespaces
を指定していても名前空間宣言は省略されません。
using System;
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",
// 上の名前空間宣言と同一名前空間でもプレフィックスは異なるので
// OmitDuplicateNamespacesを指定しても省略されない
new XAttribute(XNamespace.Xmlns + "a", nsAtom.NamespaceName),
// ~~~ ~~~~~~
new XElement(nsAtom + "title",
new XAttribute("type", "text"),
"entry 1"
)
)
)
);
doc.Save("doc.xml", SaveOptions.None);
doc.Save("doc-odn.xml", SaveOptions.OmitDuplicateNamespaces);
}
}
<?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>
<?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>
XmlWriterを使った出力の場合、XmlWriterSettings.NamespaceHandlingプロパティにNamespaceHandling.OmitDuplicatesを指定すると、SaveOptions.OmitDuplicateNamespacesを指定した場合と同じ動作になります。
XmlWriterを用いた出力
XDocument.SaveメソッドおよびXElement.Saveメソッドでは、XmlWriterを使った保存もサポートしています。 XmlWriterSettingsと組み合わせて使うことで、出力時のフォーマットや動作を制御することができます。
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);
}
}
}
<?xml version="1.0" encoding="shift_jis"?>
<root>
<foo>
<bar>baz</bar>
</foo>
<ほげ>もげ</ほげ>
</root>
XmlWriterおよびXmlWriterSettingsは名前空間System.Xmlのクラスで、アセンブリSystem.Xml.dll
を参照に追加することによって使うことができます。 XDocumentなどが定義されているSystem.Xml.Linq.dll
とは別アセンブリのクラスである点に注意してください。
XmlWriterの作成はXmlWriter.Createメソッドを使います。 第一引数には出力先となるファイル名を指定できるほか、StreamやTextWriterを指定することもできるため、例えばMemoryStreamを用いればXMLツリーをメモリ上に展開することもできます。 StringWriterを出力先とすれば、出力結果を文字列として取得することもできます。
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を閉じる際にベースとなるStream/TextWriterを閉じない
};
// 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>
XmlWriterを使ったXMLの出力と、XmlWriterSettingsで設定可能な内容についてはXmlWriterSettingsでより詳しく解説します。