C# XmlDocumentの使い方を基礎から解説|XMLの読み込み・検索・編集・保存までわかる完全ガイド

はじめに

C#でXMLを扱うときによく使われるクラスのひとつがXmlDocumentです。XMLファイルを読み込んで特定の値を取得したり、要素や属性を検索したり、内容を編集して保存したりできます。

XMLは、設定ファイル、外部システムとのデータ連携、古い業務システムのデータ形式、APIレスポンス、帳票データなどで今でも使われています。JSONが主流になった現在でも、XMLを扱う場面は少なくありません。

この記事では、C#のXmlDocumentの使い方を基礎から解説します。XMLの読み込み、ノードの取得、XPath検索、編集、保存、よくあるエラーまで、実務で使いやすい形で整理します。

1. C#のXmlDocumentとは

1-1. XmlDocumentの概要

XmlDocumentは、C#でXMLドキュメントをメモリ上に読み込み、要素や属性を操作するためのクラスです。System.Xml名前空間に含まれており、XML全体をツリー構造として扱えます。

Microsoftの公式ドキュメントでは、XmlDocumentはXMLドキュメントのメモリ内表現であり、W3C DOMに基づくクラスとして説明されています。XMLを読み込み、読み取り、変更、削除できる点が特徴です。Microsoft Learn

たとえば、次のようなXMLがあるとします。

XML
<?xml version="1.0" encoding="utf-8"?>
<books>
<book id="1">
<title>C#入門</title>
<price>3000</price>
</book>
<book id="2">
<title>XML基礎</title>
<price>2500</price>
</book>
</books>

XmlDocumentを使うと、このXMLからtitle要素の値を取得したり、priceの値を変更したり、新しいbook要素を追加したりできます。

1-2. XmlDocumentでできること

XmlDocumentでは、主に次のような操作ができます。

C#
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

XmlNode? titleNode = doc.SelectSingleNode("/books/book[@id='1']/title");

if (titleNode != null)
{
Console.WriteLine(titleNode.InnerText);
}

このコードでは、books.xmlを読み込み、id属性が1book要素に含まれるtitleを取得しています。

XmlDocumentでできる主な処理は次のとおりです。

  • XMLファイルの読み込み

  • XML文字列の読み込み

  • 要素の取得

  • 属性の取得

  • XPathによる検索

  • 要素や属性の追加

  • 要素や属性の変更

  • ノードの削除

  • XMLファイルへの保存

特に、既存のXMLファイルを読み込んで一部を書き換える処理では、XmlDocumentが使いやすい場面があります。

1-3. XmlDocumentが使われる主な場面

XmlDocumentは、次のような場面でよく使われます。

設定ファイルを読み込む場合、XML形式の設定から接続先URL、ファイルパス、フラグ値などを取得できます。

外部システムとXMLで連携する場合、受け取ったXMLデータを解析し、必要な値を抽出できます。

古い業務システムを保守する場合、既存コードでXmlDocumentが使われていることがあります。特に.NET Framework時代から続くシステムでは、XmlDocumentを使ったXML操作が多く見られます。

XMLテンプレートを編集する場合、既存のXMLに要素や属性を追加し、別ファイルとして保存するような処理にも向いています。

1-4. XDocumentとの違い

C#でXMLを扱うクラスには、XmlDocumentのほかにXDocumentもあります。

XmlDocumentはDOMベースのXML操作クラスです。一方、XDocumentはLINQ to XMLで使われるクラスで、LINQを使ってXMLを直感的に検索・加工できます。Microsoft公式ドキュメントでも、LINQ to XMLはXMLをメモリに読み込み、クエリや変更、保存ができるXMLプログラミングインターフェイスとして説明されています。Microsoft Learn

簡単に比較すると、次のようになります。

項目XmlDocumentXDocument
名前空間System.XmlSystem.Xml.Linq
操作方法DOMベースLINQベース
XPath使いやすい追加対応は可能だが基本はLINQ
既存システムとの相性高い新しいコードで使いやすい
コードの書きやすさやや冗長比較的シンプル
向いている場面既存XML操作、XPath検索LINQでの柔軟な検索・加工

たとえば、既存コードがXmlDocumentで書かれている場合や、XPathを多用する場合はXmlDocumentが扱いやすいです。新規開発でLINQを活用したい場合は、XDocumentも有力な選択肢です。

1-5. XmlDocumentを使うメリット・デメリット

XmlDocumentのメリットは、XML全体をツリー構造として直感的に扱えることです。ノード単位で取得、変更、追加、削除ができるため、XMLの一部を細かく操作できます。

また、XPathとの相性がよく、条件を指定してノードを検索しやすい点もメリットです。

一方で、XML全体をメモリに読み込むため、大容量XMLには注意が必要です。数百MB以上のXMLを扱う場合は、XmlReaderのようなストリーム型の読み取り方法を検討したほうがよい場合があります。

また、コードがやや冗長になりやすい点もデメリットです。単純な読み取りや加工であれば、XDocumentのほうが短く書けることもあります。

2. XmlDocumentを使うための準備

2-1. 必要な名前空間

XmlDocumentを使うには、次の名前空間を指定します。

C#
using System;
using System.Xml;

XmlDocumentXmlNodeXmlElementXmlAttributeXmlNodeListXmlNamespaceManagerなどは、基本的にSystem.Xml名前空間に含まれています。

.NET 6、.NET 7、.NET 8以降のプロジェクトでも、System.Xmlを使ってXmlDocumentを利用できます。Microsoft公式ドキュメントでも、XmlDocumentは.NETのAPIとして掲載されています。Microsoft Learn

2-2. サンプルXMLの用意

この記事では、次のXMLをサンプルとして使います。

XML
<?xml version="1.0" encoding="utf-8"?>
<books>
<book id="1" category="programming">
<title>C#入門</title>
<author>山田太郎</author>
<price>3000</price>
</book>
<book id="2" category="xml">
<title>XML基礎</title>
<author>佐藤花子</author>
<price>2500</price>
</book>
</books>

この内容をbooks.xmlという名前で保存しておくと、以降のサンプルコードを試しやすくなります。

2-3. XmlDocumentオブジェクトの作成

XmlDocumentを使うには、まずインスタンスを作成します。

C#
XmlDocument doc = new XmlDocument();

XMLファイルを読み込む場合は、次のようにLoadメソッドを使います。

C#
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

XML文字列を読み込む場合は、LoadXmlメソッドを使います。

C#
string xml = "<books><book><title>C#入門</title></book></books>";

XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);

ファイルから読み込むならLoad、文字列から読み込むならLoadXmlと覚えるとわかりやすいです。

2-4. XML処理でよく使う主要クラス

XmlDocumentを使うときは、関連するクラスも一緒に理解しておくと便利です。

XmlDocumentはXML文書全体を表します。

C#
XmlDocument doc = new XmlDocument();

XmlNodeはXML内のノードを表します。要素、テキスト、コメントなど、XMLを構成するさまざまな単位を扱えます。

C#
XmlNode? node = doc.SelectSingleNode("/books/book/title");

XmlElementはXML要素を表します。新しい要素を作成するときによく使います。

C#
XmlElement newBook = doc.CreateElement("book");

XmlAttributeは属性を表します。

C#
XmlAttribute attr = doc.CreateAttribute("id");
attr.Value = "3";

XmlNodeListは複数のノードをまとめたコレクションです。

C#
XmlNodeList nodes = doc.SelectNodes("/books/book")!;

XmlNamespaceManagerは、名前空間付きXMLをXPathで検索するときに使います。

C#
XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("x", "http://example.com/books");

3. XmlDocumentでXMLを読み込む方法

3-1. XMLファイルを読み込む

XMLファイルを読み込むには、Loadメソッドを使います。

C#
using System;
using System.Xml;

class Program
{
static void Main()
{
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

Console.WriteLine("XMLを読み込みました。");
}
}

Loadには、ファイルパスを指定します。相対パスを指定した場合、実行時のカレントディレクトリが基準になります。

確実に読み込みたい場合は、絶対パスを使うか、アプリケーションの実行フォルダからの相対パスを明確にしましょう。

C#
string path = @"C:\XmlSample\books.xml";

XmlDocument doc = new XmlDocument();
doc.Load(path);

3-2. XML文字列を読み込む

文字列として持っているXMLを読み込む場合は、LoadXmlを使います。

C#
string xml = @"
<books>
<book id='1'>
<title>C#入門</title>
</book>
</books>";

XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);

Console.WriteLine(doc.DocumentElement?.Name);

この例では、XML文字列をXmlDocumentに読み込み、ルート要素名を表示しています。

LoadXmlは、外部APIから受け取ったXML文字列や、プログラム内で生成したXMLを解析するときに便利です。

3-3. XMLのルート要素を取得する

XMLのルート要素は、DocumentElementプロパティで取得できます。

C#
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

XmlElement? root = doc.DocumentElement;

if (root != null)
{
Console.WriteLine(root.Name);
}

サンプルXMLの場合、ルート要素はbooksなので、次のように表示されます。

books

DocumentElementはXML文書の最上位要素を表します。XMLが正しく読み込まれていない場合や、空のドキュメントの場合はnullになる可能性があります。

3-4. 読み込み時の例外処理

XMLファイルを読み込むときは、ファイルが存在しない、XMLの形式が正しくない、アクセス権限がない、といった理由で例外が発生することがあります。

そのため、実務ではtry-catchで例外処理を書くことが重要です。

C#
using System;
using System.IO;
using System.Xml;

class Program
{
static void Main()
{
try
{
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

Console.WriteLine("XMLを読み込みました。");
}
catch (FileNotFoundException)
{
Console.WriteLine("XMLファイルが見つかりません。");
}
catch (XmlException ex)
{
Console.WriteLine("XMLの形式が正しくありません。");
Console.WriteLine(ex.Message);
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("ファイルにアクセスする権限がありません。");
}
catch (Exception ex)
{
Console.WriteLine("予期しないエラーが発生しました。");
Console.WriteLine(ex.Message);
}
}
}

XmlExceptionは、XMLの構文が壊れている場合に発生しやすい例外です。閉じタグがない、属性のクォートが不足している、ルート要素が複数ある、といったケースで発生します。

3-5. ファイルパス指定時の注意点

doc.Load("books.xml")のように相対パスを指定した場合、ソースコードの場所ではなく、実行時の作業ディレクトリが基準になります。

Visual Studioで実行している場合、一般的にはbin\Debug\net8.0などの出力フォルダが基準になることがあります。

ファイルが読み込めない場合は、次のコードでカレントディレクトリを確認できます。

C#
Console.WriteLine(Environment.CurrentDirectory);

また、アプリケーションの実行フォルダを基準にパスを組み立てる場合は、次のように書けます。

C#
string path = Path.Combine(AppContext.BaseDirectory, "books.xml");

XmlDocument doc = new XmlDocument();
doc.Load(path);

XMLファイルをプロジェクトに追加している場合は、ファイルのプロパティで「出力ディレクトリにコピー」を設定しておくと、実行時にファイルを見つけやすくなります。

4. XmlDocumentでXMLノードを取得する方法

4-1. DocumentElementでルートノードを取得する

DocumentElementを使うと、XMLのルート要素を取得できます。

C#
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

XmlElement? root = doc.DocumentElement;

if (root != null)
{
Console.WriteLine($"ルート要素名: {root.Name}");
}

サンプルXMLでは、booksがルート要素です。

DocumentElementから子ノードをたどっていくことで、XML全体を順番に処理できます。

4-2. ChildNodesで子ノードを取得する

子ノードを取得するには、ChildNodesプロパティを使います。

C#
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

XmlElement? root = doc.DocumentElement;

if (root != null)
{
foreach (XmlNode node in root.ChildNodes)
{
Console.WriteLine(node.Name);
}
}

ただし、XML内に改行や空白がある場合、それらがテキストノードとして扱われることがあります。

要素ノードだけを処理したい場合は、NodeTypeを確認します。

C#
foreach (XmlNode node in root.ChildNodes)
{
if (node.NodeType == XmlNodeType.Element)
{
Console.WriteLine(node.Name);
}
}

このようにすると、改行やインデントの空白を除外して、要素だけを処理できます。

4-3. SelectSingleNodeで単一ノードを検索する

SelectSingleNodeは、XPathに一致する最初のノードを取得するメソッドです。

C#
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

XmlNode? titleNode = doc.SelectSingleNode("/books/book/title");

if (titleNode != null)
{
Console.WriteLine(titleNode.InnerText);
}

この例では、最初のbook要素のtitleを取得します。

SelectSingleNodeは、1件だけ取得したい場合に便利です。Microsoft公式ドキュメントでも、SelectSingleNodeはXPath式に一致する最初のXmlNodeを選択するメソッドとして説明されています。Microsoft Learn

4-4. SelectNodesで複数ノードを検索する

複数のノードを取得したい場合は、SelectNodesを使います。

C#
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

XmlNodeList? bookNodes = doc.SelectNodes("/books/book");

if (bookNodes != null)
{
foreach (XmlNode bookNode in bookNodes)
{
XmlNode? titleNode = bookNode.SelectSingleNode("title");

if (titleNode != null)
{
Console.WriteLine(titleNode.InnerText);
}
}
}

このコードでは、すべてのbook要素を取得し、それぞれのtitleを表示しています。

SelectNodesは、一覧データを処理するときに非常によく使います。

4-5. ノードの値を取得する

ノードの値を取得するには、主にInnerTextを使います。

C#
XmlNode? priceNode = doc.SelectSingleNode("/books/book[@id='1']/price");

if (priceNode != null)
{
string price = priceNode.InnerText;
Console.WriteLine(price);
}

InnerTextは、要素内のテキストを取得します。

たとえば、次のXMLがある場合、

XML
<title>C#入門</title>

titleNode.InnerTextC#入門になります。

なお、InnerXmlを使うと、子要素を含むXML文字列を取得できます。

C#
Console.WriteLine(bookNode.InnerXml);

単純に値だけが欲しい場合はInnerText、XML構造ごと取得したい場合はInnerXmlを使います。

4-6. 属性値を取得する

属性値を取得するには、Attributesプロパティを使います。

C#
XmlNode? bookNode = doc.SelectSingleNode("/books/book[@id='1']");

if (bookNode != null)
{
XmlAttribute? categoryAttr = bookNode.Attributes?["category"];

if (categoryAttr != null)
{
Console.WriteLine(categoryAttr.Value);
}
}

また、XmlElementとして扱える場合は、GetAttributeメソッドも使えます。

C#
XmlElement? bookElement = doc.SelectSingleNode("/books/book[@id='1']") as XmlElement;

if (bookElement != null)
{
string category = bookElement.GetAttribute("category");
Console.WriteLine(category);
}

GetAttributeは、属性が存在しない場合に空文字列を返します。そのため、属性の有無を厳密に判定したい場合は、HasAttributeを併用します。

C#
if (bookElement != null && bookElement.HasAttribute("category"))
{
Console.WriteLine(bookElement.GetAttribute("category"));
}

5. XPathを使ったXML検索

5-1. XPathとは

XPathは、XML内の要素や属性を指定するための記法です。XMLの階層構造をパスのように表現し、目的のノードを検索できます。

たとえば、次のXPathは、books要素の中にあるbook要素の中のtitle要素を表します。

/books/book/title

属性条件も指定できます。

/books/book[@id='1']

このXPathは、id属性が1book要素を検索します。

XmlDocumentでは、SelectSingleNodeSelectNodesでXPathを使えます。

5-2. XPathで要素名を指定して検索する

要素名を指定して検索する基本形は次のとおりです。

C#
XmlNode? titleNode = doc.SelectSingleNode("/books/book/title");

if (titleNode != null)
{
Console.WriteLine(titleNode.InnerText);
}

先頭の/は、XMLドキュメントのルートから検索することを意味します。

すべてのtitle要素を取得したい場合は、次のように書けます。

C#
XmlNodeList? titleNodes = doc.SelectNodes("//title");

if (titleNodes != null)
{
foreach (XmlNode node in titleNodes)
{
Console.WriteLine(node.InnerText);
}
}

//titleは、階層を問わずすべてのtitle要素を検索します。

5-3. XPathで属性を条件に検索する

属性を条件に検索するには、[@属性名='値']を使います。

C#
XmlNode? bookNode = doc.SelectSingleNode("/books/book[@id='2']");

if (bookNode != null)
{
XmlNode? titleNode = bookNode.SelectSingleNode("title");

if (titleNode != null)
{
Console.WriteLine(titleNode.InnerText);
}
}

この例では、id属性が2book要素を検索し、その中のtitleを表示しています。

属性値が文字列の場合は、シングルクォートまたはダブルクォートで囲みます。

C#
XmlNode? node = doc.SelectSingleNode("/books/book[@category='xml']");

複数条件を指定する場合は、andを使えます。

C#
XmlNode? node = doc.SelectSingleNode("/books/book[@id='2' and @category='xml']");

5-4. XPathで階層を指定して検索する

XPathでは、階層を明確に指定できます。

C#
XmlNode? node = doc.SelectSingleNode("/books/book/title");

これは、books直下のbook直下にあるtitleを検索します。

一方、階層を問わず検索したい場合は、//を使います。

C#
XmlNode? node = doc.SelectSingleNode("//title");

ただし、//はXML全体を広く検索するため、XMLが大きい場合や同名要素が多い場合は、意図しないノードが取得されることがあります。

できるだけ階層を具体的に指定したほうが、読みやすく安全なコードになります。

C#
XmlNode? node = doc.SelectSingleNode("/books/book[@id='1']/title");

5-5. XPath検索で見つからないときの原因

XPathでノードが取得できないときは、まずXPathがXML構造と一致しているか確認しましょう。

よくある原因は次のとおりです。

ルート要素名が間違っている場合、/books/bookと書いているのに実際のルートがbookListであれば取得できません。

大文字・小文字が違う場合、XMLの要素名は大文字・小文字を区別します。Titletitleは別の要素です。

属性値が違う場合、[@id='1']と指定しているのに、実際の属性値が001であれば一致しません。

名前空間がある場合、XPathに名前空間を指定しないと取得できないことがあります。

また、相対パスと絶対パスの違いにも注意が必要です。

C#
bookNode.SelectSingleNode("title");

これはbookNodeを基準にした相対パスです。

C#
doc.SelectSingleNode("/books/book/title");

これはドキュメントのルートからの絶対パスです。

5-6. 名前空間付きXMLをXPathで検索する方法

名前空間付きXMLをXPathで検索する場合は、XmlNamespaceManagerを使います。

たとえば、次のXMLがあるとします。

XML
<?xml version="1.0" encoding="utf-8"?>
<books xmlns="http://example.com/books">
<book id="1">
<title>C#入門</title>
</book>
</books>

このXMLでは、既定の名前空間が指定されています。この場合、次のようなXPathでは取得できません。

C#
XmlNode? node = doc.SelectSingleNode("/books/book/title");

名前空間を使う場合は、任意のプレフィックスを登録して検索します。

C#
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("b", "http://example.com/books");

XmlNode? titleNode = doc.SelectSingleNode("/b:books/b:book/b:title", ns);

if (titleNode != null)
{
Console.WriteLine(titleNode.InnerText);
}

ポイントは、XML側にプレフィックスがなくても、XPathではプレフィックスを付けて指定する必要があることです。公式ドキュメントでも、既定の名前空間を含むXMLでは、XmlNamespaceManagerを使い、プレフィックスと名前空間URIを追加する必要があると説明されています。Microsoft Learn

6. XmlDocumentでXMLを編集する方法

6-1. 既存ノードの値を変更する

既存ノードの値を変更するには、対象ノードを取得し、InnerTextを書き換えます。

C#
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

XmlNode? priceNode = doc.SelectSingleNode("/books/book[@id='1']/price");

if (priceNode != null)
{
priceNode.InnerText = "3500";
}

doc.Save("books.xml");

このコードでは、id1の本の価格を3500に変更して保存しています。

要素の中身が単純なテキストであれば、InnerTextを書き換えるのが簡単です。

6-2. 属性値を変更する

属性値を変更するには、Attributesから対象属性を取得してValueを変更します。

C#
XmlNode? bookNode = doc.SelectSingleNode("/books/book[@id='1']");

if (bookNode?.Attributes?["category"] != null)
{
bookNode.Attributes["category"]!.Value = "csharp";
}

doc.Save("books.xml");

XmlElementとして扱う場合は、SetAttributeも使えます。

C#
XmlElement? bookElement = doc.SelectSingleNode("/books/book[@id='1']") as XmlElement;

if (bookElement != null)
{
bookElement.SetAttribute("category", "csharp");
}

doc.Save("books.xml");

SetAttributeは、属性が存在すれば更新し、存在しなければ追加します。

6-3. 新しい要素を追加する

新しい要素を追加するには、CreateElementで要素を作成し、AppendChildで追加します。

C#
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

XmlElement newBook = doc.CreateElement("book");
newBook.SetAttribute("id", "3");
newBook.SetAttribute("category", "database");

XmlElement title = doc.CreateElement("title");
title.InnerText = "SQL入門";

XmlElement author = doc.CreateElement("author");
author.InnerText = "鈴木一郎";

XmlElement price = doc.CreateElement("price");
price.InnerText = "2800";

newBook.AppendChild(title);
newBook.AppendChild(author);
newBook.AppendChild(price);

doc.DocumentElement?.AppendChild(newBook);

doc.Save("books.xml");

このコードでは、新しいbook要素を作成し、その中にtitleauthorpriceを追加しています。

CreateElementで作成しただけではXMLには追加されません。必ずAppendChildなどで親ノードに追加する必要があります。

6-4. 新しい属性を追加する

属性を追加する方法としては、SetAttributeが簡単です。

C#
XmlElement? bookElement = doc.SelectSingleNode("/books/book[@id='2']") as XmlElement;

if (bookElement != null)
{
bookElement.SetAttribute("stock", "10");
}

doc.Save("books.xml");

CreateAttributeを使って属性を作成する方法もあります。

C#
XmlNode? bookNode = doc.SelectSingleNode("/books/book[@id='2']");

if (bookNode != null)
{
XmlAttribute stockAttr = doc.CreateAttribute("stock");
stockAttr.Value = "10";

bookNode.Attributes?.Append(stockAttr);
}

doc.Save("books.xml");

単純に属性を追加・更新するだけならSetAttributeのほうが短く書けます。

6-5. ノードを削除する

ノードを削除するには、親ノードのRemoveChildを使います。

C#
XmlNode? bookNode = doc.SelectSingleNode("/books/book[@id='2']");

if (bookNode?.ParentNode != null)
{
bookNode.ParentNode.RemoveChild(bookNode);
}

doc.Save("books.xml");

RemoveChildは、削除対象の親ノードから呼び出す必要があります。

属性を削除する場合は、RemoveAttributeを使えます。

C#
XmlElement? bookElement = doc.SelectSingleNode("/books/book[@id='1']") as XmlElement;

if (bookElement != null)
{
bookElement.RemoveAttribute("category");
}

doc.Save("books.xml");

6-6. ノードをコピー・移動する

ノードをコピーするには、CloneNodeを使います。

C#
XmlNode? bookNode = doc.SelectSingleNode("/books/book[@id='1']");

if (bookNode != null)
{
XmlNode copiedNode = bookNode.CloneNode(true);
doc.DocumentElement?.AppendChild(copiedNode);
}

doc.Save("books.xml");

CloneNode(true)は、子ノードを含めて深くコピーします。CloneNode(false)にすると、子ノードを含めずにコピーします。

ノードを移動する場合は、一度削除して別の場所に追加するイメージです。

C#
XmlNode? bookNode = doc.SelectSingleNode("/books/book[@id='1']");

if (bookNode?.ParentNode != null && doc.DocumentElement != null)
{
bookNode.ParentNode.RemoveChild(bookNode);
doc.DocumentElement.AppendChild(bookNode);
}

doc.Save("books.xml");

同じドキュメント内であれば、このようにノードを移動できます。

別のXmlDocumentにノードをコピーする場合は、ImportNodeを使います。

C#
XmlDocument sourceDoc = new XmlDocument();
sourceDoc.Load("source.xml");

XmlDocument targetDoc = new XmlDocument();
targetDoc.Load("target.xml");

XmlNode? sourceNode = sourceDoc.SelectSingleNode("/books/book");

if (sourceNode != null && targetDoc.DocumentElement != null)
{
XmlNode importedNode = targetDoc.ImportNode(sourceNode, true);
targetDoc.DocumentElement.AppendChild(importedNode);
}

targetDoc.Save("target.xml");

7. XmlDocumentでXMLを保存する方法

7-1. XMLファイルに保存する

XMLを保存するには、Saveメソッドを使います。

C#
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

XmlNode? priceNode = doc.SelectSingleNode("/books/book[@id='1']/price");

if (priceNode != null)
{
priceNode.InnerText = "3500";
}

doc.Save("books.xml");

このコードでは、読み込んだファイルに上書き保存しています。

別ファイルとして保存したい場合は、別のパスを指定します。

C#
doc.Save("books_updated.xml");

Microsoft公式ドキュメントでも、XmlDocumentにはXMLドキュメントを保存するSaveメソッドが用意されています。Microsoft Learn

7-2. 文字コードを指定して保存する

文字コードを指定して保存したい場合は、XmlWriterSettingsXmlWriterを使います。

C#
using System.Text;
using System.Xml;

XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

XmlWriterSettings settings = new XmlWriterSettings
{
Encoding = new UTF8Encoding(false),
Indent = true
};

using XmlWriter writer = XmlWriter.Create("books_utf8.xml", settings);
doc.Save(writer);

この例では、BOMなしUTF-8で保存しています。

BOM付きUTF-8で保存したい場合は、次のようにします。

C#
Encoding = new UTF8Encoding(true)

文字コードを明示したい場合は、単純にdoc.Save("file.xml")とするより、XmlWriterを使ったほうが制御しやすくなります。

7-3. インデント付きで整形して保存する

インデント付きで見やすく保存するには、XmlWriterSettingsIndenttrueにします。

C#
XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true,
IndentChars = " ",
NewLineChars = Environment.NewLine,
Encoding = new UTF8Encoding(false)
};

using XmlWriter writer = XmlWriter.Create("books_pretty.xml", settings);
doc.Save(writer);

これにより、XMLが改行とインデント付きで保存されます。

人が直接読むXMLファイルや設定ファイルでは、インデント付き保存が便利です。

7-4. XML宣言を付けて保存する

XML宣言を付けたい場合は、CreateXmlDeclarationを使います。

C#
XmlDocument doc = new XmlDocument();

XmlDeclaration declaration = doc.CreateXmlDeclaration("1.0", "utf-8", null);
doc.AppendChild(declaration);

XmlElement root = doc.CreateElement("books");
doc.AppendChild(root);

doc.Save("books.xml");

出力されるXMLは次のようになります。

XML
<?xml version="1.0" encoding="utf-8"?>
<books />

既存のXMLに宣言がある場合は、そのまま保存されることが多いですが、新しくXMLを作成する場合は、必要に応じてXML宣言を追加します。

7-5. 保存時の上書き・権限エラーへの対処

保存時には、ファイルが使用中、アクセス権限がない、保存先フォルダが存在しない、といった理由でエラーになることがあります。

C#
try
{
doc.Save("books.xml");
Console.WriteLine("保存しました。");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("保存先に書き込む権限がありません。");
}
catch (DirectoryNotFoundException)
{
Console.WriteLine("保存先のフォルダが存在しません。");
}
catch (IOException ex)
{
Console.WriteLine("ファイル入出力エラーが発生しました。");
Console.WriteLine(ex.Message);
}

上書きしたくない場合は、保存前にファイルの存在を確認します。

C#
string path = "books_updated.xml";

if (!File.Exists(path))
{
doc.Save(path);
}
else
{
Console.WriteLine("ファイルが既に存在します。");
}

実務では、元ファイルを直接上書きする前にバックアップを作成することも重要です。

C#
File.Copy("books.xml", "books_backup.xml", overwrite: true);
doc.Save("books.xml");

8. XmlDocumentの実践サンプル

8-1. 設定ファイルを読み込むサンプル

次のような設定ファイルを考えます。

XML
<?xml version="1.0" encoding="utf-8"?>
<settings>
<database>
<host>localhost</host>
<port>5432</port>
<user>admin</user>
</database>
</settings>

このXMLから接続情報を取得します。

C#
XmlDocument doc = new XmlDocument();
doc.Load("settings.xml");

string host = doc.SelectSingleNode("/settings/database/host")?.InnerText ?? "";
string port = doc.SelectSingleNode("/settings/database/port")?.InnerText ?? "";
string user = doc.SelectSingleNode("/settings/database/user")?.InnerText ?? "";

Console.WriteLine($"Host: {host}");
Console.WriteLine($"Port: {port}");
Console.WriteLine($"User: {user}");

?.InnerText ?? ""を使うことで、ノードが存在しない場合でもNullReferenceExceptionを防げます。

ただし、必須項目の場合は空文字で処理を続けるのではなく、エラーとして扱ったほうがよいこともあります。

C#
XmlNode? hostNode = doc.SelectSingleNode("/settings/database/host");

if (hostNode == null)
{
throw new InvalidOperationException("hostが設定されていません。");
}

string hostValue = hostNode.InnerText;

8-2. 特定の要素を検索して値を取得するサンプル

book要素の中から、特定のIDを持つ本のタイトルを取得します。

C#
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

string id = "2";
XmlNode? titleNode = doc.SelectSingleNode($"/books/book[@id='{id}']/title");

if (titleNode != null)
{
Console.WriteLine(titleNode.InnerText);
}
else
{
Console.WriteLine("指定された本が見つかりません。");
}

このコードはシンプルですが、外部入力をそのままXPath文字列に埋め込む場合は注意が必要です。値にクォートが含まれるとXPathが壊れる可能性があります。

安全性を高めたい場合は、属性値の形式を事前に検証しましょう。

C#
if (!int.TryParse(id, out _))
{
throw new ArgumentException("IDの形式が正しくありません。");
}

IDが数値だけであると決まっている場合は、このように検証してからXPathに使うと安全です。

8-3. XMLに新しいデータを追加するサンプル

新しい本のデータをXMLに追加するサンプルです。

C#
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

XmlElement book = doc.CreateElement("book");
book.SetAttribute("id", "3");
book.SetAttribute("category", "network");

XmlElement title = doc.CreateElement("title");
title.InnerText = "ネットワーク入門";

XmlElement author = doc.CreateElement("author");
author.InnerText = "田中次郎";

XmlElement price = doc.CreateElement("price");
price.InnerText = "3200";

book.AppendChild(title);
book.AppendChild(author);
book.AppendChild(price);

doc.DocumentElement?.AppendChild(book);

doc.Save("books.xml");

追加後のXMLは、次のような形になります。

XML
<book id="3" category="network">
<title>ネットワーク入門</title>
<author>田中次郎</author>
<price>3200</price>
</book>

新しい要素を作成するときは、必ず同じXmlDocumentCreateElementで作成します。別のXmlDocumentで作成したノードをそのまま追加しようとするとエラーになることがあります。

8-4. XMLの値を更新して保存するサンプル

指定した本の価格を更新して保存するサンプルです。

C#
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

string targetId = "1";
string newPrice = "3600";

XmlNode? priceNode = doc.SelectSingleNode($"/books/book[@id='{targetId}']/price");

if (priceNode != null)
{
priceNode.InnerText = newPrice;
doc.Save("books.xml");

Console.WriteLine("価格を更新しました。");
}
else
{
Console.WriteLine("更新対象の価格が見つかりません。");
}

数値として扱う値であれば、保存前に入力値をチェックすると安全です。

C#
if (!int.TryParse(newPrice, out int price) || price < 0)
{
throw new ArgumentException("価格の形式が正しくありません。");
}

XMLはテキスト形式なので、値を書き込むこと自体は簡単です。しかし、値の意味まで自動的に検証してくれるわけではありません。必要なチェックはC#側で行いましょう。

8-5. 属性を条件にノードを検索・編集するサンプル

属性を条件に検索し、該当ノードを編集するサンプルです。

C#
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");

XmlNodeList? xmlBooks = doc.SelectNodes("/books/book[@category='xml']");

if (xmlBooks != null)
{
foreach (XmlNode book in xmlBooks)
{
XmlNode? priceNode = book.SelectSingleNode("price");

if (priceNode != null)
{
priceNode.InnerText = "2000";
}
}
}

doc.Save("books.xml");

このコードでは、category属性がxmlbook要素をすべて取得し、価格を2000に変更しています。

複数条件を使う場合は、次のように書けます。

C#
XmlNode? book = doc.SelectSingleNode("/books/book[@category='xml' and @id='2']");

属性条件を使うと、IDやカテゴリなどを基準に目的のノードを簡単に検索できます。

9. XmlDocumentでよくあるエラーと対処法

9-1. NullReferenceExceptionが発生する原因

XmlDocumentでよくあるエラーがNullReferenceExceptionです。

たとえば、次のコードは危険です。

C#
string title = doc.SelectSingleNode("/books/book[@id='999']/title").InnerText;

指定したノードが存在しない場合、SelectSingleNodenullを返します。その状態でInnerTextにアクセスすると、NullReferenceExceptionが発生します。

安全に書くには、nullチェックを行います。

C#
XmlNode? titleNode = doc.SelectSingleNode("/books/book[@id='999']/title");

if (titleNode != null)
{
Console.WriteLine(titleNode.InnerText);
}
else
{
Console.WriteLine("ノードが見つかりません。");
}

また、null条件演算子を使って短く書くこともできます。

C#
string title = doc.SelectSingleNode("/books/book[@id='999']/title")?.InnerText ?? "";

ただし、必須データが見つからない場合に空文字として処理を続けると、後続処理で原因がわかりにくくなることがあります。重要な項目は明示的にエラーにするのがおすすめです。

9-2. XPathでノードが取得できない原因

XPathでノードが取得できない場合は、次の点を確認します。

まず、XMLのルート要素名が正しいか確認します。

XML
<books>

であれば、XPathは次のようになります。

/books

次に、要素名の大文字・小文字が一致しているか確認します。

XML
<Title>C#入門</Title>

この場合、//titleでは取得できません。正しくは//Titleです。

属性条件も確認しましょう。

C#
doc.SelectSingleNode("/books/book[@id='1']")

このXPathは、id属性が正確に1である場合だけ一致します。0011とは一致しません。

また、相対XPathと絶対XPathの違いも重要です。

C#
XmlNode? bookNode = doc.SelectSingleNode("/books/book[@id='1']");
XmlNode? titleNode = bookNode?.SelectSingleNode("title");

この場合、titlebookNodeから見た相対パスです。先頭に/を付けると、ドキュメントルートからの検索になります。

9-3. 名前空間付きXMLが検索できない原因

名前空間付きXMLでノードが取得できない問題は、非常によくあります。

次のXMLを考えます。

XML
<books xmlns="http://example.com/books">
<book>
<title>C#入門</title>
</book>
</books>

見た目はbooksbooktitleですが、実際には名前空間に属しています。そのため、次のXPathでは取得できません。

C#
doc.SelectSingleNode("/books/book/title");

正しくは、XmlNamespaceManagerを使います。

C#
XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("b", "http://example.com/books");

XmlNode? node = doc.SelectSingleNode("/b:books/b:book/b:title", ns);

既定の名前空間があるXMLでは、XPath側にプレフィックスを付ける必要があります。XML側にプレフィックスがないからといって、XPathでもプレフィックスなしで検索できるわけではありません。

9-4. XMLの形式エラーへの対処

XMLの形式が正しくない場合、LoadLoadXmlXmlExceptionが発生します。

よくある形式エラーは次のようなものです。

閉じタグがない場合です。

XML
<title>C#入門

属性値のクォートがない場合です。

XML
<book id=1>

ルート要素が複数ある場合です。

XML
<book></book>
<book></book>

XMLでは、ルート要素は1つだけでなければなりません。

例外処理では、XmlExceptionを捕捉します。

C#
try
{
XmlDocument doc = new XmlDocument();
doc.Load("books.xml");
}
catch (XmlException ex)
{
Console.WriteLine("XMLの形式が正しくありません。");
Console.WriteLine(ex.Message);
}

XMLの形式エラーが発生した場合は、エラーメッセージに行番号や位置情報が含まれることがあります。該当箇所を確認して修正しましょう。

9-5. ファイルが読み込めない・保存できない原因

XMLファイルが読み込めない場合は、まずファイルパスを確認します。

C#
Console.WriteLine(Path.GetFullPath("books.xml"));

このコードで、実際に参照しているパスを確認できます。

ファイルが存在するか確認するには、File.Existsを使います。

C#
string path = "books.xml";

if (!File.Exists(path))
{
Console.WriteLine("ファイルが存在しません。");
return;
}

XmlDocument doc = new XmlDocument();
doc.Load(path);

保存できない場合は、保存先フォルダの存在や権限を確認します。

C#
string outputPath = @"C:\Output\books.xml";

string? directory = Path.GetDirectoryName(outputPath);

if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}

doc.Save(outputPath);

また、ファイルが他のアプリケーションで開かれている場合、保存に失敗することがあります。Excel、エディタ、別プロセスなどでファイルを使用していないか確認しましょう。

10. XmlDocumentを使うときの注意点

10-1. 大容量XMLを扱うときの注意点

XmlDocumentはXML全体をメモリに読み込むため、大容量XMLを扱う場合はメモリ使用量に注意が必要です。

小〜中規模のXMLであれば扱いやすいですが、数百MB以上のXMLや大量のノードを含むXMLでは、読み込みに時間がかかったり、メモリ不足になったりする可能性があります。

大容量XMLを先頭から順番に読み取るだけでよい場合は、XmlReaderの使用を検討しましょう。

C#
using XmlReader reader = XmlReader.Create("large.xml");

while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "book")
{
Console.WriteLine("book要素を検出しました。");
}
}

XmlReaderはストリーム型で読み取るため、XML全体をメモリに保持しません。

一方で、XMLの一部を自由に行き来しながら編集したい場合は、XmlDocumentのほうが扱いやすいです。

10-2. セキュリティ面で注意すべきこと

外部から受け取ったXMLを処理する場合は、セキュリティにも注意が必要です。

特に、信頼できないXMLをそのまま読み込む場合、外部エンティティやDTDの扱いに注意します。必要がなければ、DTD処理を禁止する設定を使うことを検討します。

C#
XmlReaderSettings settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit
};

using XmlReader reader = XmlReader.Create("input.xml", settings);

XmlDocument doc = new XmlDocument();
doc.Load(reader);

また、外部入力をXPathに直接埋め込む場合も注意が必要です。

C#
string id = userInput;
XmlNode? node = doc.SelectSingleNode($"/books/book[@id='{id}']");

このようなコードでは、入力値にクォートなどが含まれるとXPathが壊れる可能性があります。IDが数値であるべきなら、数値として検証してから使いましょう。

C#
if (!int.TryParse(userInput, out int id))
{
throw new ArgumentException("IDが不正です。");
}

10-3. XmlReader・XmlWriterとの使い分け

XmlDocumentXmlReaderXmlWriterは、それぞれ用途が異なります。

XmlDocumentは、XML全体をメモリ上でツリーとして扱いたい場合に向いています。ノードを検索、編集、削除、追加する処理に便利です。

XmlReaderは、XMLを前から順番に高速に読み取りたい場合に向いています。大容量XMLを読み込む場合に適しています。

XmlWriterは、XMLを効率よく書き出したい場合に向いています。新しいXMLを順番に生成する処理でよく使います。

たとえば、既存XMLの一部を変更したいならXmlDocument、巨大なXMLから特定情報だけを読み取りたいならXmlReader、大量データをXMLとして出力したいならXmlWriterが候補になります。

10-4. XDocumentを選ぶべきケース

新規開発でXMLを扱う場合、XDocumentを選んだほうがよいケースもあります。

たとえば、LINQを使って要素を検索したい場合です。

C#
XDocument doc = XDocument.Load("books.xml");

var titles = doc.Descendants("book")
.Where(x => (string?)x.Attribute("category") == "xml")
.Select(x => (string?)x.Element("title"));

foreach (var title in titles)
{
Console.WriteLine(title);
}

LINQに慣れている場合、XDocumentのほうが読みやすく書けることがあります。

一方で、既存コードがXmlDocumentで書かれている場合や、XPathを中心に検索したい場合は、XmlDocumentを使い続けるほうが保守しやすい場合もあります。

10-5. 保守しやすいXML操作コードを書くコツ

XmlDocumentを使うコードは、XPath文字列が増えると読みにくくなりがちです。保守しやすくするには、XPathを定数化すると便利です。

C#
private const string BookByIdXPath = "/books/book[@id='{0}']/title";

ただし、文字列連結や文字列補間でXPathを作る場合は、入力値の検証を忘れないようにします。

また、ノード取得処理をメソッド化するのも有効です。

C#
static string GetNodeText(XmlDocument doc, string xpath)
{
XmlNode? node = doc.SelectSingleNode(xpath);

if (node == null)
{
throw new InvalidOperationException($"ノードが見つかりません: {xpath}");
}

return node.InnerText;
}

このようにしておくと、nullチェックを毎回書かずに済みます。

さらに、XMLの構造が変わる可能性がある場合は、要素名や属性名をコード内に散らばらせないことが重要です。定数や設定値としてまとめておくと、修正範囲を小さくできます。

11. XmlDocumentに関するよくある質問

11-1. XmlDocumentとXDocumentはどちらを使うべきか

既存システムでXmlDocumentが使われている場合や、XPath検索を多用する場合はXmlDocumentが向いています。

新規開発で、LINQを使ってXMLを簡潔に操作したい場合はXDocumentが向いています。

判断の目安は次のとおりです。

既存コードとの互換性を重視するならXmlDocument

XPathを中心に検索したいならXmlDocument

LINQで読みやすく書きたいならXDocument

新規開発でシンプルにXMLを扱いたいならXDocument

どちらもXMLをメモリに読み込んで操作する点は似ていますが、書き方や思想が異なります。チームのスキルや既存コードに合わせて選ぶとよいでしょう。

11-2. XmlDocumentでJSONを扱えるか

XmlDocumentはXMLを扱うためのクラスです。JSONを直接扱うためのクラスではありません。

JSONを扱う場合は、System.Text.JsonNewtonsoft.Jsonなどを使います。

C#
using System.Text.Json;

var data = JsonSerializer.Deserialize<MyClass>(jsonString);

XMLとJSONを変換したい場合は、別途変換処理が必要です。ただし、XMLとJSONは構造や表現方法が異なるため、単純に変換できないケースもあります。

11-3. XPathで複数条件を指定できるか

XPathでは複数条件を指定できます。

andを使う例です。

C#
XmlNode? node = doc.SelectSingleNode("/books/book[@id='2' and @category='xml']");

orを使う例です。

C#
XmlNodeList? nodes = doc.SelectNodes("/books/book[@category='xml' or @category='programming']");

要素の値を条件にすることもできます。

C#
XmlNode? node = doc.SelectSingleNode("/books/book[price='2500']");

部分一致にはcontainsを使えます。

C#
XmlNodeList? nodes = doc.SelectNodes("/books/book[contains(title, '入門')]");

XPathを使うと、属性、要素名、階層、値などを組み合わせて柔軟に検索できます。

11-4. XMLの一部だけを保存できるか

XmlDocument.Saveは、基本的にドキュメント全体を保存します。

XMLの一部だけを文字列として取り出したい場合は、対象ノードのOuterXmlを使います。

C#
XmlNode? bookNode = doc.SelectSingleNode("/books/book[@id='1']");

if (bookNode != null)
{
string xmlPart = bookNode.OuterXml;
File.WriteAllText("book_part.xml", xmlPart);
}

OuterXmlは対象ノード自身を含むXMLを取得します。

一方、InnerXmlは対象ノードの内側だけを取得します。

C#
string inner = bookNode.InnerXml;

一部だけを独立したXMLファイルとして保存する場合は、XML宣言やルート要素の有無にも注意しましょう。

11-5. .NET Coreや.NET 6以降でもXmlDocumentは使えるか

XmlDocumentは、.NET Coreや.NET 6以降でも利用できます。System.Xml名前空間に含まれる標準的なXML操作クラスです。

.NET Framework時代の古いクラスという印象を持たれることもありますが、現在の.NETでもXMLを扱うための選択肢として利用できます。

ただし、新規開発ではXDocument、大容量XMLではXmlReader、新規XML生成ではXmlWriterなど、用途に応じて使い分けることが大切です。

まとめ

C#のXmlDocumentは、XMLをメモリ上に読み込み、ノード単位で検索・編集・保存できる便利なクラスです。

XMLファイルを読み込む場合はLoad、XML文字列を読み込む場合はLoadXmlを使います。ルート要素はDocumentElementで取得でき、特定のノードを探す場合はSelectSingleNodeSelectNodesを使います。

XPathを使えば、要素名、属性、階層、複数条件を指定して柔軟にXMLを検索できます。ただし、名前空間付きXMLではXmlNamespaceManagerが必要になる点に注意しましょう。

XMLを編集する場合は、InnerTextで値を変更し、SetAttributeで属性を追加・更新し、CreateElementAppendChildで新しい要素を追加できます。編集後はSaveでファイルに保存できます。

一方で、XmlDocumentはXML全体をメモリに読み込むため、大容量XMLには向かない場合があります。大量データを順番に読むならXmlReader、LINQで簡潔に操作したいならXDocumentも検討しましょう。

XmlDocumentは、既存システムの保守、設定ファイルの読み書き、XPathを使ったXML検索、XMLの一部編集などで今でも役立つクラスです。基本的な読み込み、検索、編集、保存の流れを押さえておけば、C#でのXML操作を安定して実装できます。