[Linq to XML] `XDocument` や `XElement` の使い方・サンプルコード

Linq to XML のお勉強。

XObject

全ての派生型の親クラス。 とりあえず継承ツリーだけ覚えとく。

XObject
XAttribute : XAttributeはノードじゃないよ
XNode
  XComment
  XDocumentType
  XContainer
   XElement
   XDocument
  XText
   XCData
  XProcessingInstruction

ファイルや文字列から XElementXDocument を生成

//
// ファイルから読み込み
//
XDocument xdoc = XDocument.Load("c:/ファイルパス.xml");
XElement xelement = XElement.Load("同上。Streamなども使える");

//
// テキストを読み込み
//
XDocument doc = XDocument.Parse("XML文字列");
XElement ele = XElement.Parse("同上", LoadOptions.PreserveWhitespace);

XMLが不正な場合は XmlException が投げられる

XDocument でXMLを生成する

//
// 簡潔にルートノードだけ
//
XNamespace xmlns = "http://fernweh.jp/";
XName xname = xmlns + "MyRoot";

XDocument xdoc = new XDocument(
  new XDeclaration(version: "1.0", encoding: "utf-8", standalone: null),
  new XElement(xmlns + "MyRoot"));

// XML宣言部分とXML部分を一緒に取得する方法はあるのだろうか
Trace.WriteLine(xdoc.Declaration); // <?xml version="1.0" encoding="utf-8"?>
Trace.WriteLine(xdoc); //<MyRoot xmlns="http://fernweh.jp/" />
//
// 属性やテキストノード, CDATA
//
var xdoc = new XDocument(
  new XElement("A",
    new XAttribute("b", "c"),
    new XElement("D",
  new XText("text"),
  new XCData("cdata")
)));

Trace.Write(xdoc); // <A b="c">\n<D>text<![CDATA[cdata]]></D>\n</A>

//
// ファイルに書き出すときは
//
xdoc.Save("ココにStreamやファイルパス, TextWriter, XmlWriterを指定");

要素の操作

//
// 追加や削除や値の変更, 取得
//
var a = new XElement("a");
var b = new XElement("b");
var c = new XElement("c");

/*
 Add系 : これらは全て可変長引数で受け取れる
   Add
   AddFirst
   AddAfterSelf
   AddBeforeSelf
*/
a.Add(b);
a.Add(c);
Trace.WriteLine(a); // <a><b /><c /><c /></a>

/*
 各種プロパティ
   NodeType
   Name - string ではなく XName型
   Value
   Parent
   PreviousNode
   NextNode
   FirstNode
   FirstAttribute
   LastNode
   LastAttribute
   IsEmpty
     空文字のテキストノードのみを持つ要素は空と判定されない
     属性は関係ない(<a b="c" /> は 空と判定される)
   EmptySequence(.NET 4.5~)
   HasAttributes
   HasElements
   */
Trace.WriteLine(a.Name); // a
Trace.WriteLine(a.Value); // (空文字)
Trace.WriteLine(a.Parent); // (null)
Trace.WriteLine(a.NodeType
    == XmlNodeType.Element); // True
Trace.WriteLine(a.PreviousNode); // (null)
Trace.WriteLine(a.NextNode); // (null)
Trace.WriteLine(a.FirstNode); // <b />
Trace.WriteLine(a.LastNode); // <c />
Trace.WriteLine(a.HasAttributes); // False
Trace.WriteLine(a.HasElements); // True
Trace.WriteLine(a.IsEmpty); // False

Trace.WriteLine(b.Parent); //  <a><b /><c /></a>
Trace.WriteLine(b.PreviousNode); // (null)
Trace.WriteLine(b.NextNode); // <c />
Trace.WriteLine(b.FirstNode); // (null)
Trace.WriteLine(b.HasElements); // False
Trace.WriteLine(b.IsEmpty); // True

Trace.WriteLine(c.PreviousNode); // <b />
Trace.WriteLine(c.NextNode); // (null)

/*
 Set系
   SetValue - nullを入れると ArgumentNullException
   SetElementValue - null をセットすると要素が削除される!
   SetAttribute - null をセットすると属性が削除される!
 */
a.Element("c").SetValue("m");
Trace.WriteLine(a); // <a><b /><c>m</c></a>
Trace.WriteLine(c.IsEmpty); // False

c.Remove();
Trace.WriteLine(a); // <a><b /></a>
a.SetElementValue("c", string.Empty); /* 要素が存在しない場合は作成 */
Trace.WriteLine(a); // <a><b /><c></c></a>
a.SetElementValue("c", null); /* null をセットすると削除 */
Trace.WriteLine(a); // <a><b /></a>

b.SetValue("x");
b.SetAttributeValue("y", "z");
Trace.WriteLine(a); // <a><b y="z">x</b></a>
Trace.WriteLine(b.FirstNode); // x
Trace.WriteLine(b.FirstNode.NodeType
    == XmlNodeType.Text); // True

b.SetAttributeValue("y", "zzz");
Trace.WriteLine(a); // <a><b y="zzz">x</b></a>

/*
 属性の取得
     Attribute("属性名")
     Attributes()
     FirstAttribute
     LastAttribute
 属性の削除
     RemoveAttributes()
     SetAttributeValue("属性名", null)
 */
b.Attribute("y").SetValue("zzzzzzz");
Trace.WriteLine(a); // <a><b y="zzzzzzz">x</b></a>

b.SetAttributeValue("m", "n"); /* 属性が存在しなければ作成 */
Trace.WriteLine(a); // <a><b y="zzzzzzz" m="n">x</b></a>

b.SetAttributeValue("m", null); /* null をセットすると削除 */
Trace.WriteLine(a); // <a><b y="zzzzzzz">x</b></a>

b.RemoveAttributes();
Trace.WriteLine(a); // <a><b>x</b></a>

b.SetValue(string.Empty); /* 空文字いれてもテキストノードは残る */
b.RemoveAttributes();
b.AddBeforeSelf(c);
Trace.WriteLine(b.IsEmpty); // False
Trace.WriteLine(a); // <a><c>m</c><b></b></a>
Trace.WriteLine(b.FirstNode == null ? "null" : ""); // null

/*
 Remove系
     Remove() - 自身を親ノードから削除
     RemoveAll() - 自身の持つ子要素を全て削除
     RemoveNodes() - 自身の持つ子ノードを全て削除
      ※SetElementValue("要素名", null) でも削除可能
 */
b.RemoveAll(); /* ここは RemoveNodes とやっても結果は同じ */
Trace.WriteLine(a); // <a><c>m</c><b /></a>

b.Add(c);
c.SetValue("xyz"); /* もう一方の c要素 には影響なし */
Trace.WriteLine(a); // <a><c>xyz</c><b><c>m</c></b></a>

名前空間

//
// 名前空間
//

XNamespace ns = "http://fernweh.jp/";

XElement xele;
/*
 下の結果は
<ROOT xmlns="http://fernweh.jp/">
  <A />
</ROOT>
*/
xele = new XElement(
  ns + "ROOT",
  new XElement(xmlns + "A"));
Trace.WriteLine(xele);

/*
 下の結果は
<ROOT xmlns="http://fernweh.jp/">
  <A xmlns="" />
</ROOT>
*/
xele = new XElement(
  ns + "ROOT",
  new XElement("A"));
Trace.WriteLine(xele);

/*
 下の結果は
<ROOT xmlns="http://fernweh.jp/">
  <A xmlns="" />
  <A />
</ROOT>
*/
xele = new XElement(
  ns + "ROOT",
  new XElement("A"),
  new XElement(ns + "A"));
Trace.WriteLine(xele);

/*
 下の結果は
<名前空間:ROOT xmlns:名前空間="http://fernweh.jp/">
  <名前空間:A />
</名前空間:ROOT>
*/
xele = new XElement(
  ns + "ROOT",
  new XAttribute(XNamespace.Xmlns + "名前空間", ns),
  new XElement(xmlns + "A"));
Trace.WriteLine(xele);

/*
 下の結果は
<名前空間:ROOT xmlns:名前空間="http://fernweh.jp/">
  <A />
</名前空間:ROOT>
*/
xele = new XElement(
  ns + "ROOT",
  new XAttribute(XNamespace.Xmlns + "名前空間", ns),
  new XElement("A"));
Trace.WriteLine(xele);

/*
 下の結果は
<名前空間:ROOT xmlns:名前空間="http://fernweh.jp/">
  <A />
  <名前空間:A />
</名前空間:ROOT>
*/
xele = new XElement(
  ns + "ROOT",
  new XAttribute(XNamespace.Xmlns + "名前空間", ns),
  new XElement("A"),
  new XElement(ns + "A"));
Trace.WriteLine(xele);
//
// 名前空間名の取得
// 名前空間名付きの要素を選択
//
XNamespace ns = "http://fernweh.jp/";
XElement xele = new XElement(
  ns + "ROOT",
  new XAttribute(XNamespace.Xmlns + "名前空間", ns),
  new XElement("A", "first"),
  new XElement(ns + "A", "second"));

XName xname = xele.Name;
XNamespace xnamespace = xname.Namespace;

Trace.WriteLine(xnamespace); // http://fernweh.jp
Trace.WriteLine(xele.Element("A").Value); // first
Trace.WriteLine(xele.Element(ns + "A").Value); // second
//
// 名前空間付きの属性を選択
//
XNamespace ns = "http://fernweh.jp/";
XElement xele = new XElement(
  ns + "ROOT",
  new XAttribute(XNamespace.Xmlns + "W", ns),
  new XAttribute("a", "1st"),
  new XAttribute(ns + "a", "2nd"));

Trace.WriteLine(xele); // <W:ROOT xmlns:W="http://fernweh.jp/" a="1st" W:a="2nd" />

Trace.WriteLine(xele.Attribute("a").Value); // 1st
Trace.WriteLine(xele.Attribute(ns + "a").Value); // 2nd

クローンとアタッチ

参考: Linq入門記-61 (LINQ to XML, 要素のクローンとアタッチ) - いろいろ備忘録日記

//
// 同じのをAddすると
// コピーされたものが後ろに追加される…
//
var a = new XElement("a");
var b = new XElement("b", "v");

a.Add(b);
a.Add(b);
a.Add(b);
(a.FirstNode as XElement).SetAttributeValue("id", "0");
(a.FirstNode.NextNode as XElement).SetAttributeValue("id", "1");
(a.LastNode as XElement).SetAttributeValue("id", "2");

b.Remove();
Trace.WriteLine(a); // <a><b id="1">v</b><b id="2">v</b></a>

XElement b1 = a.FirstNode as XElement;
b1.RemoveNodes();
Trace.WriteLine(a); // <a><b id="1" /><b id="2">v</b></a>

try
{
  b.Remove(); // もう既にRemoveされているので…
}
catch (InvalidOperationException)
{
  Trace.WriteLine("ココ通る");
}

DescendantsAnscestors

  • Descendants : 子孫要素を取得
  • DescendantsAndSelf : 子孫要素 + 自身 を取得
  • DescendantNodes : 子孫のノードを取得
  • DescendantNodesAndSelf : 子孫のノード + 自身 を取得
  • Ancestors : 先祖要素を取得
  • AncestorsAndSelf : 先祖要素 + 自身 を取得

    public static void Main()
    {
    string xml = @"<?xml version=""1.0"" encoding=""utf-8""?>
    <ROOT>
    <A />
    <B />
    <B>
    <C>
    <D>text-d</D>
    <E>text-e</E>
    <E><N x=""y"" /></E>
    </C>
    <F><G /></F>
    <H>text-h</H>
    テキスト
    </B>
    </ROOT>";
    
    var doc = XDocument.Parse(xml);
    XElement c = doc.XPathSelectElement("ROOT/B/C");
    
    //
    // Descendants で子Elementを取得(兄弟Elementは対象外)
    // 引数に null を与えると 空のIEnumerableが返ってくる
    // 属性名はヒットしない
    //
    WriteElementNames(c.Descendants()); // D, E, E, N
    WriteElementNames(c.Descendants("C")); // empty
    WriteElementNames(c.Descendants("D")); // D
    WriteElementNames(c.Descendants("E")); // E, E
    WriteElementNames(c.Descendants("N")); // N
    WriteElementNames(c.Descendants("X")); // empty
    WriteElementNames(c.Descendants(null)); // empty
    WriteElementNames(c.Descendants("x")); // empty
    
    //
    // DescendantsAndSelf は Descendants と違い自身を含む
    //
    WriteElementNames(c.DescendantsAndSelf()); // C, D, E, E, N
    WriteElementNames(c.DescendantsAndSelf("C")); // C
    WriteElementNames(c.DescendantsAndSelf("D")); // D
    WriteElementNames(c.DescendantsAndSelf("E")); // E, E
    WriteElementNames(c.DescendantsAndSelf("N")); // N
    WriteElementNames(c.DescendantsAndSelf("X")); // empty
    WriteElementNames(c.DescendantsAndSelf(null)); // empty
    
    //
    // Ancestors で子Elementを取得(兄弟Elementは対象外)
    // 引数に null を与えると 空のIEnumerableが返ってくる
    //
    WriteElementNames(c.Ancestors()); // B, ROOT
    WriteElementNames(c.Ancestors("C")); // empty
    WriteElementNames(c.Ancestors("B")); // B
    WriteElementNames(c.Ancestors("ROOT")); // ROOT
    WriteElementNames(c.Ancestors("A")); // empty
    WriteElementNames(c.Ancestors(null)); // empty
    
    //
    // AncestorsAndSelf は Ancestors と違い自身を含む
    //
    WriteElementNames(c.AncestorsAndSelf()); // C, B, ROOT
    WriteElementNames(c.AncestorsAndSelf("C")); // C
    WriteElementNames(c.AncestorsAndSelf("B")); // B
    WriteElementNames(c.AncestorsAndSelf("ROOT")); // ROOT
    WriteElementNames(c.AncestorsAndSelf("A")); // empty
    WriteElementNames(c.AncestorsAndSelf(null)); // empty
    
    //
    // DescendantNodes, DescendantNodesAndSelf で要素ではなくノードを列挙
    // 引数は無し。
    //
    // AncestorNodesは…もちろん存在しない。
    // あっても Ancestors と同じだろうし。。
    //
    WriteNodes(c.DescendantNodes()); // D, text-d, E, text-e, E, N
    WriteNodes(c.DescendantNodesAndSelf()); // C, D, text-d, E, text-e, E, N
    }
    
    private static void WriteElementNames(IEnumerable<XElement> elements)
    {
    if (elements.Count() == 0)
    {
    Trace.WriteLine("empty");
    return;
    }
    
    var builder = new StringBuilder();
    foreach (var xele in elements)
    {
    builder.Append(xele.Name + ", ");
    }
    builder.Length = builder.Length - ", ".Length;
    Trace.WriteLine(builder);
    }
    
    private static void WriteNodes(IEnumerable<XNode> nodes)
    {
    if (nodes.Count() == 0)
    {
    Trace.WriteLine("empty");
    return;
    }
    
    var builder = new StringBuilder();
    foreach (var xnode in nodes)
    {
    dynamic aNode = xnode;
    switch (xnode.NodeType)
    {
      case XmlNodeType.Element:
        builder.Append(aNode.Name + ", ");
        break;
    
      case XmlNodeType.Text:
        builder.Append(aNode.Value + ", ");
        break;
    
      default:
        throw new NotSupportedException();
        break;
    }
    }
    builder.Length = builder.Length - ", ".Length;
    Trace.WriteLine(builder);
    }
    

Element, Elements, ElementsBeforeSelf, ElementsAfterSelf

public class Sample
{
  public static void Main()
  {
    string xml = @"<?xml version=""1.0"" encoding=""utf-8""?>
<ROOT>
<A />
<B />
<B>
  <C>
    <D>text-d</D>
    <E>text-e</E>
    <E><N x=""y"" /></E>
  </C>
  <F><G /></F>
  <H>text-h</H>
  テキスト
</B>
</ROOT>";

    var doc = XDocument.Parse(xml);
    XElement c = doc.XPathSelectElement("ROOT/B/C");

    //
    // Elementメソッドで引数に指定した最初の子要素を取得
    //
    Trace.WriteLine(c.Element("A") == null); // True - 親はヒットしない
    Trace.WriteLine(c.Element("C") == null); // True - 自身はヒットしない
    Trace.WriteLine(c.Element("E").Value); // text-e
    Trace.WriteLine(c.Element("N") == null); // True - 孫はヒットしない

    //
    // Elements で(引数に指定した)全ての子要素を取得
    //
    WriteElementNames(c.Elements()); // D, E, E
    WriteElementNames(c.Elements("D")); // D
    WriteElementNames(c.Elements("E")); // E, E

    //
    // ElementsBeforeSelf, ElementsAfterSelf で兄弟要素を取得
    // 子孫親は含まれない。
    //
    WriteElementNames(c.ElementsBeforeSelf()); // empty

    WriteElementNames(c.ElementsAfterSelf()); // F, H
    WriteElementNames(c.ElementsAfterSelf("F")); // F
    WriteElementNames(c.ElementsAfterSelf("G")); // empty
    WriteElementNames(c.ElementsAfterSelf("H")); // H

    //
    // NodesBeforeSelf, NodesAfterSelf で兄弟ノードを取得
    // 引数は取らない
    //
    WriteNodes(c.NodesBeforeSelf()); // empty

    WriteNodes(c.NodesAfterSelf()); // F, H, テキスト
  }

  private static void WriteElementNames(IEnumerable<XElement> elements)
  {
    if (elements.Count() == 0)
    {
      Trace.WriteLine("empty");
      return;
    }

    var builder = new StringBuilder();
    foreach (var xele in elements)
    {
      builder.Append(xele.Name + ", ");
    }
    builder.Length = builder.Length - ", ".Length;
    Trace.WriteLine(builder);
  }

  private static void WriteNodes(IEnumerable<XNode> nodes)
  {
    if (nodes.Count() == 0)
    {
      Trace.WriteLine("empty");
      return;
    }

    var builder = new StringBuilder();
    foreach (var xnode in nodes)
    {
      dynamic aNode = xnode;
      switch (xnode.NodeType)
      {
        case XmlNodeType.Element:
          builder.Append(aNode.Name + ", ");
          break;

        case XmlNodeType.Text:
          builder.Append(aNode.Value + ", ");
          break;

        default:
          throw new NotSupportedException();
          break;
      }
    }
    builder.Length = builder.Length - ", ".Length;
    Trace.WriteLine(builder);
  }
}

XPath による要素の選択

//
// System.Xml.XPath
// の拡張メソッドが必要
//
var c = new XElement("c", "xxx");
var b = new XElement("b", c);
var a = new XElement("a", b);

Trace.WriteLine(a); // <a><b><c>xxx</c></b></a>

//
// XPathSelectElement でマッチする最初の要素を取得
// XPathSelectElements はマッチする全ての要素をIEnumerable<XElement> で返す
//
Trace.WriteLine(a.XPathSelectElement("b/c")); // <c>xxx</c>
Trace.WriteLine(a.XPathSelectElement("/b/c")); // <c>xxx</c>
Trace.WriteLine(a.XPathSelectElement("/a/b/c")); // (null)  この辺り注意
Trace.WriteLine(a.XPathSelectElement("a/b/c")); // (null)

Trace.WriteLine(b.XPathSelectElement("b/c")); // (null)
Trace.WriteLine(b.XPathSelectElement("/b/c")); // <c>xxx</c>
Trace.WriteLine(b.XPathSelectElement("/a/b/c")); // (null)
Trace.WriteLine(b.XPathSelectElement("a/b/c")); // (null)

// XDeclarationはあってもなくても結果は同じだけど…
var doc = new XDocument(new XDeclaration("1.0", "utf-8", null), a);
Trace.WriteLine(doc); // <a><b><c>xxx</c></b></a>
Trace.WriteLine(a.XPathSelectElement("b/c")); // <c>xxx</c>
Trace.WriteLine(a.XPathSelectElement("/b/c")); //  (null)
Trace.WriteLine(a.XPathSelectElement("/a/b/c")); // <c>xxx</c>
Trace.WriteLine(a.XPathSelectElement("a/b/c")); //  (null)

Trace.WriteLine(b.XPathSelectElement("c")); // <c>xxx</c>
Trace.WriteLine(b.XPathSelectElement("/c")); //  (null)
Trace.WriteLine(b.XPathSelectElement("/a/b/c")); // <c>xxx</c>

//
// XPathEvaluate は XPathSelectElements とほぼ同じ?
// 戻り値の型は object
//
IEnumerable result = b.XPathEvaluate("c") as IEnumerable;
Trace.WriteLine(result.GetType().Name); // <EvaluateIterator>d__0`1
foreach (var x in result)
{
  Trace.WriteLine(x); // <c>xxx</c>
}

XNode#IsAfterXNode#IsTrue

var root = new XElement("ROOT");
var a =  new XElement("A");
var b = new XElement("B");
var c = new XElement("C");
root.Add(a, b, c);

Trace.WriteLine(a.IsAfter(a)); // False
Trace.WriteLine(a.IsAfter(b)); // False
Trace.WriteLine(a.IsAfter(c)); // False
Trace.WriteLine(a.IsBefore(a)); // False
Trace.WriteLine(a.IsBefore(b)); // True
Trace.WriteLine(a.IsBefore(c)); // True

Trace.WriteLine(b.IsAfter(a)); // True
Trace.WriteLine(b.IsAfter(b)); // False
Trace.WriteLine(b.IsAfter(c)); // False
Trace.WriteLine(b.IsBefore(a)); // False
Trace.WriteLine(b.IsBefore(b)); // False
Trace.WriteLine(b.IsBefore(c)); // True

try
{
  Trace.WriteLine(c.IsAfter(new XElement("d")));
}
catch (InvalidOperationException ex)
{
  Trace.WriteLine(ex.Message); // 共通の先祖がありません。
}

Replace

null を渡した時の挙動が嫌な感じ。。

var a = new XElement("A",
  new XAttribute("x", "y"),
  new XText("text"));
var r = new XElement("ROOT", a);
Trace.WriteLine(r); // <ROOT><A x="y">text</A></ROOT>
Trace.WriteLine(a.Parent.Name); // ROOT

//
// ReplaceNodes で 子ノードを置換
//
// 属性は置き換わらない。
// XAttribute を渡すと属性が追加されてノードが消える
//
a.ReplaceNodes(new XElement("B"));
Trace.WriteLine(a); // <A x="y"><B /></A>

a.ReplaceNodes(new XAttribute("z", "0"));
Trace.WriteLine(a); // <A x="y" z="0" />

try
{
  a.ReplaceNodes(new XAttribute("z", "1"));
}
catch (InvalidOperationException ex)
{
  Trace.WriteLine(ex.Message); // 属性が重複しています。
};

try
{
  a.ReplaceNodes(new XAttribute("n", null));
}
catch (ArgumentNullException ex)
{
  Trace.WriteLine(ex.Message); // 値を Null にすることはできません。 パラメーター名: value
}

a.Add(new XElement("G"));
a.ReplaceAttributes(new XAttribute("n", "m"));
Trace.WriteLine(a); // <A n="m"><G /></A>

//
// RelpaceAll は ReplaceNodes と違って属性も含めて置換
// で、ReplaceNodesと違って null を受け取れる。その場合は子要素を全削除
//
a.ReplaceAll(new XElement("D"));
Trace.WriteLine(a); // <A><D /><A>

a.ReplaceAll(null);
Trace.WriteLine(a); // <A />

//
// ReplaceWith で 自身を別要素に置換
// null を引数に渡すと自分自身を親からRemove(親がない場合はInvalidOperationException)
//
a.SetAttributeValue("c", "d");
Trace.WriteLine(a); // <A c="d" />
a.ReplaceWith(new XElement("B"), new XElement("C"));
Trace.WriteLine(r); // <ROOT><B /><C /></ROOT>
Trace.WriteLine(a.Parent == null ? "null" : ""); // null

r.ReplaceAll(a);
Trace.WriteLine(r); // <ROOT><A c="d"></ROOT>
a.ReplaceWith(null);
Trace.WriteLine(r); // <ROOT />

try
{
  a.ReplaceWith(null);
}
catch (InvalidOperationException ex)
{
  Trace.WriteLine(ex.Message); // 親がありません。
}

キャストで値を取得

参考: Linq入門記-87 (LINQ to XML, Tips, XElementとXAttributeをキャストして値取得) - いろいろ備忘録日記

キャスト演算子がオーバーライドされてて、Valueプロパティから取得した値を変換せずとも、キャストで直接変換可能。

var xele = new XElement("a", "true");
Trace.WriteLine((bool)xele); // True
Trace.WriteLine((string)xele); // true
try
{
  Trace.WriteLine((int)xele);
}
catch (FormatException ex)
{
  Trace.WriteLine(ex.Message); // 文字列入力の形式が正しくありません。
}

その他

Share
関連記事