C#でのXMLの操作(`XmlDocument`)

Linq の使える XDocument の方が便利らしいけどとりあえず XmlDocumentを覚えた。

XMLの読み込み操作

※いろいろと飛び交う可能性のある様々な例外を以下のコードでは処理してない

// XmlDocumentにXMLファイルを読み込む
string xmlFilepath = @"XMLファイルの場所";
XmlDocument xml = new XmlDocument();
xml.Load(xmlFilepath);

// XPathにマッチするノードを取り出す
string xpath01 = @"//hoge/piyo";
XmlNodeList nodes = xml.SelectNodes(xpath01);

foreach (XmlNode node in nodes)
{
  // XPathにマッチする単一のノードを取り出す
  string xpath02 = @"foo/bar";
  XmlNode aNode = node.SelectSingleNode(xpath02);

  // XMLの内容をテキストで取得
  string a = aNode.InnerText; // テキストノードのみ
  string b = aNode.InnerXml; // XML要素も含めたテキスト
  string c = aNode.OuterXml; // 自分自身の要素も含めたテキスト
}

XmlDocument#Load はいろんな例外投げるので注意。

XPath の構文がおかしい場合は XmlNode#SelectNodesXPathException が発生する。

もう少し丁寧に読み込む場合

InnerText プロパティは XmlNode の全ての ChildNode のテキストノードを結合した文字列を返す。

もう少し丁寧に値を取り出したい場合は、NodeType プロパティで XmlNodeType を確認して、Value プロパティでデータを取得する。

XmlDocument xml = new XmlDocument();

// 文字列を読み込む
xml.LoadXml(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<R>
    <A attr=""XYZ"">Text</A>
    <B><![CDATA[<"">]]>ccc</B>
    <C></C>
</R>"
);

Debug.WriteLine(xml.FirstChild.OuterXml); // <?xml version="1.0" encoding="utf-8" ?>
Debug.WriteLine(xml.FirstChild.InnerXml); //  (空文字)

Debug.WriteLine(xml.LastChild.InnerText); // Text<">ccc

XmlElement a = xml.LastChild.FirstChild as XmlElement;
Debug.WriteLine(a.NodeType == XmlNodeType.Element); // True
Debug.WriteLine(a.OuterXml); // <A attr="XYZ">Text</A>
Debug.WriteLine(a.ChildNodes.Count); // 1
Debug.WriteLine(a.SelectSingleNode("@attr").Value); // XYZ
Debug.WriteLine(a.SelectSingleNode("@attr").NodeType == XmlNodeType.Attribute); // True
Debug.WriteLine(a.Attributes[0].Value); // XYZ
Debug.WriteLine(a.Attributes[0].Name); // attr
Debug.WriteLine(a.FirstChild.Value); // Text
Debug.WriteLine(a.LastChild.Value); // Text : 属性は ChildNodes に含まれず、 Attributes に含まれる
Debug.WriteLine(a.FirstChild.NodeType == XmlNodeType.Text);// True

XmlElement b = xml.LastChild["B"];
Debug.WriteLine(b.OuterXml); // <B><![CDATA[<">]]></B>
Debug.WriteLine(b.ChildNodes.Count); // 1
Debug.WriteLine(b.FirstChild.NodeType == XmlNodeType.CDATA); // TRUE
Debug.WriteLine(b.FirstChild.Value); // <">
Debug.WriteLine(b.LastChild.NodeType == XmlNodeType.Text); // TRUE
Debug.WriteLine(b.LastChild.Value); // ccc

XmlElement c = xml.LastChild["C"];
Debug.WriteLine(c.ChildNodes.Count); // 0;

XmlDocument#LoadXml は 不正なXML文字列を渡すと XmlException を投げる

XMLの作成操作

XmlDocument xml = new XmlDocument();

// XML宣言の生成
XmlDeclaration declaration
    = xml.CreateXmlDeclaration(@"1.0", @"utf-8", null);

// ルートノードの生成
XmlElement root = xml.CreateElement("Hoge");

// 子ノードの生成
XmlElement child = xml.CreateElement("Piyo");

// 子ノードにテキストをセット
child.InnerText = @"FOO<&>BAR"; // <>& は自動的にエスケープされる

// 組み立てて返す
root.AppendChild(child);

xml.AppendChild(declaration);
xml.AppendChild(root);

// 出力してみる
Console.WriteLine(xml.InnerXml);

結果

<?xml version="1.0" encoding="utf-8"?><Hoge><Piyo>FOO&lt;&amp;&gt;BAR</Piyo></Hoge>

という文字列が表示される

XmlElement#CreateElement で XML要素名 として利用できない文字列を指定すると XmlException が飛ぶので注意すること! XML要素名が正しいかどうかをチェックする場合は XmlConvert.VerifyName メソッドを利用する。

XmlDocument#Load 例外投げすぎワロタ

どんだけ色んな種類の例外投げれば気が済むの…

[TestClass]
public class CreateAndLoadXmlDocumentTest
{
  XmlDocument CreateAndLoadXmlDocument(string filePath, Action<Exception> errorHandler)
  {
    XmlDocument xml = new XmlDocument();
    try
    {
      xml.Load(filePath);
      error = null;
      return xml;
    }
    catch (Exception ex)
    {
      if (ex is XmlException ||
        ex is ArgumentException ||
        ex is FileNotFoundException ||
        ex is DirectoryNotFoundException ||
        ex is PathTooLongException ||
        ex is UnauthorizedAccessException ||
        ex is SecurityException)
      {
        errorHandler(ex);
        return null;
      }
      else
      {
        throw;
      }
    }
  }

  [TestMethod]
  public void CreateAndLoadXmlDocument_正常()
  {
    string path = GetExecutingPath("valid.xml");
    if(!File.Exists(path))
    {
      Assert.Inconclusive("ValidなXMLを用意して読み込んでください");
    }

    string actualError = null;
    var actual = GetTarget().CreateAndLoadXmlDocument(path, out actualError);

    Assert.IsNotNull(actual);
    Assert.IsNull(actualError);
  }

  [TestMethod]
  public void CreateAndLoadXmlDocument_不正なXML()
  {
    string path = GetExecutingPath("invalid.xml");
    if (!File.Exists(path))
    {
      Assert.Inconclusive("InvalidなXMLを用意して読み込んでください");
    }

    string actualError = null;
    var actual = GetTarget().CreateAndLoadXmlDocument(path, out actualError);

    Assert.IsNull(actual);
    Assert.IsNotNull(actualError);
  }

  [TestMethod]
  public void CreateAndLoadXmlDocument_不正な引数()
  {
    string actualError = null;
    var actual = GetTarget().CreateAndLoadXmlDocument(null, out actualError);

    Assert.IsNull(actual);
    Assert.IsNotNull(actualError);
  }

  [TestMethod]
  public void CreateAndLoadXmlDocument_ファイルが存在しない()
  {
    string temp = Path.GetTempPath();
    string path = Path.Combine(temp, "存在しない.xml");
    if (File.Exists(path)) Assert.Inconclusive("存在しないファイルパスを指定してください");

    string actualError = null;
    var actual = GetTarget().CreateAndLoadXmlDocument(path, out actualError);

    Assert.IsNull(actual);
    Assert.IsNotNull(actualError);
  }

  [TestMethod]
  public void CreateAndLoadXmlDocument_ディレクトリが存在しない()
  {
    string path = @"C:/存/在/し/な/い/デ/ィ/レ/ク/ト/リ";
    if (Directory.Exists(path)) Assert.Inconclusive("存在しないディレクトリパスを指定してください");

    string actualError = null;
    var actual = GetTarget().CreateAndLoadXmlDocument(path, out actualError);

    Assert.IsNull(actual);
    Assert.IsNotNull(actualError);
  }

  [TestMethod]
  public void CreateAndLoadXmlDocument_長すぎるパス()
  {
    StringBuilder pathBuilder = new StringBuilder(@"c:\長すぎるパス");
    for(int i = 0; i < 1000; i++) pathBuilder.Append("x");
    string path = Path.Combine(pathBuilder.ToString(), "test.xml");

    string actualError = null;
    var actual = GetTarget().CreateAndLoadXmlDocument(path, out actualError);

    Assert.IsNull(actual);
    Assert.IsNotNull(actualError);
  }

  [TestMethod]
  public void CreateAndLoadXmlDocument_許可されないパス()
  {
    string path = null;
    if (path == null) Assert.Inconclusive("UnauthorizedAccessExceptionが発生するパスを指定してください");

    string actualError = null;
    var actual = GetTarget().CreateAndLoadXmlDocument(path, out actualError);

    Assert.IsNull(actual);
    Assert.IsNotNull(actualError);
  }

  [TestMethod]
  public void CreateAndLoadXmlDocument_アクセスを禁止されたパス()
  {
    string path = null;
    if (path == null) Assert.Inconclusive("SecurityExceptionが発生するパスを指定してください");

    string actualError = null;
    var actual = GetTarget().CreateAndLoadXmlDocument(path, out actualError);

    Assert.IsNull(actual);
    Assert.IsNotNull(actualError);
  }

  public CreateAndLoadXmlDocumentTest GetTarget()
  {
    return new CreateAndLoadXmlDocumentTest();
  }

  public string GetExecutingPath(string fileName)
  {
    string location = System.Reflection.Assembly.GetExecutingAssembly().Location;
    return Path.Combine(Path.GetDirectoryName(location), fileName);
  }
}
Share
関連記事