C# Tips | 文字列処理:XML整形

C# C#
スポンサーリンク

はじめに 「XML整形」は“タグの森に道をつける”作業

XMLは、設定ファイル、外部システム連携、古めのWebサービスなどで今でもよく使われますが、そのまま1行でベタッと出てくると、とても読みにくいです。
XML整形(Pretty Print)は、そのぐちゃっとしたXMLに改行とインデントを付けて、「人間が読める形」にする処理です。

C#では、XMLを「文字列として置換する」のではなく、「一度パースしてから、整形オプション付きで書き出す」のが正しいやり方です。
ここをきちんと押さえておくと、ログ、デバッグ、ツール作成などでかなり役に立つユーティリティになります。


基本方針 「文字列いじり」ではなく「XMLとしてパースしてから整形」

なぜ単純な文字列置換ではダメなのか

初心者がやりがちなのは、XML文字列に対して直接、改行やスペースを入れたり消したりする方法です。
例えば、「>< の間に改行を入れる」「> の後に改行を入れる」といったやり方です。

これはすぐに破綻します。
タグの中の属性値に >< が出てくる場合、CDATAセクションの中に記号が出てくる場合、コメントや処理命令の中に特殊な文字が出てくる場合など、「XMLの構造」と「ただの文字」を区別できないからです。

正しいやり方は、XMLパーサに一度読ませて「構造」として理解させ、その構造を「インデント付きで書き出す」ことです。
C#では、System.Xml.Linq.XDocumentSystem.Xml.XmlDocument を使うのが定番です。


実務で使いやすい基本実装 XDocument を使った整形

一番シンプルで実務投入しやすいユーティリティ

まずは、XDocument を使った XML 整形ユーティリティの完成形を見てみます。

using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;

public static class XmlFormatUtil
{
    public static string FormatXml(string? xml)
    {
        if (string.IsNullOrWhiteSpace(xml))
        {
            return string.Empty;
        }

        try
        {
            var doc = XDocument.Parse(xml, LoadOptions.PreserveWhitespace);

            var settings = new XmlWriterSettings
            {
                Indent = true,
                IndentChars = "  ",
                NewLineChars = "\n",
                NewLineHandling = NewLineHandling.Replace,
                OmitXmlDeclaration = false
            };

            using var sw = new StringWriter();
            using (var xw = XmlWriter.Create(sw, settings))
            {
                doc.Save(xw);
            }

            return sw.ToString();
        }
        catch (XmlException)
        {
            return xml;
        }
    }
}
C#

このメソッドだけで、「ぐちゃぐちゃXMLをきれいに整形する」ことができます。
ここから、重要なポイントを一つずつかみ砕いていきます。


重要ポイント1 XDocument.Parse で“XMLとして正しいか”をチェックする

XDocument.Parse(xml, LoadOptions.PreserveWhitespace); は、文字列をXMLとしてパースする処理です。
ここで XML の構造が解析され、ルート要素、子要素、属性、コメントなどがオブジェクトとして扱えるようになります。

LoadOptions.PreserveWhitespace を付けているのは、「元の空白も一応保持したい」場合に使うオプションです。
ただし、整形時には XmlWriterSettings 側のルールでインデントや改行が付け直されるので、「空白を完全にそのまま残す」わけではありません。
ここでは、「パーサにとって意味のある空白(テキストノードとしての空白)」を落とさないための保険程度に考えておけば大丈夫です。

もし XML が壊れている場合、XDocument.ParseXmlException を投げます。
今回のユーティリティでは、キャッチして「元の文字列をそのまま返す」方針にしています。
これにより、「整形できないけれど、とりあえず中身は見たい」というログ・デバッグ用途でも使いやすくなります。


重要ポイント2 XmlWriterSettings で“整形ルール”を決める

XmlWriterSettings は、「XMLを書き出すときのルール」をまとめて指定するクラスです。
ここが XML 整形ユーティリティの“性格”を決める重要な部分です。

Indent = true は、「インデントを付ける」指定です。
これを true にしないと、1行に詰まったXMLが出力されます。

IndentChars = " " は、インデントに使う文字列です。
ここでは半角スペース2つにしていますが、タブ "\t" にしたり、スペース4つにしたり、プロジェクトのコーディングスタイルに合わせて変えられます。

NewLineChars = "\n" は、改行コードを指定しています。
Windows標準は "\r\n" ですが、ログやツールで扱いやすいように "\n" に統一することもよくあります。
ここをどうするかは、「どこで使うXMLか」によって決めるとよいです。

NewLineHandling = NewLineHandling.Replace は、「元の改行を指定した改行コードに置き換える」指定です。
これにより、元のXMLが \r\n でも \n でも、出力は常に NewLineChars に揃います。

OmitXmlDeclaration = false は、「XML宣言(<?xml version="1.0" encoding="utf-8"?>)を出力するかどうか」です。
false にしておくと、宣言が付いた状態で出力されます。
宣言を付けたくない場合は true にします。

この設定をユーティリティ側で固定しておくことで、「プロジェクト内のXML整形ルール」が統一されます。


重要ポイント3 XmlWriter で“構造に従って書き直す”

XmlWriter.Create(sw, settings) で、先ほどの設定を使った XML ライターを作っています。
doc.Save(xw); で、XDocument の内容をそのライターに書き出します。

ここで大事なのは、「文字列をいじっているのではなく、構造をもとに書き直している」という点です。
タグの入れ子、属性の位置、コメント、テキストノードなど、XMLとしての構造を理解したうえで、インデントと改行を付けて出力してくれます。

結果として、元のXMLが

<root><item id="1"><name>Taro</name></item><item id="2"><name>Jiro</name></item></root>

のような1行でも、整形後は

<?xml version="1.0" encoding="utf-16"?>
<root>
  <item id="1">
    <name>Taro</name>
  </item>
  <item id="2">
    <name>Jiro</name>
  </item>
</root>

のように、読みやすい形になります(encodingStringWriter の実装に依存します)。


動作例でイメージを固める

ぐちゃぐちゃXMLを整形する例

string raw = "<root><item id=\"1\"><name>Taro</name></item><item id=\"2\"><name>Jiro</name></item></root>";

string formatted = XmlFormatUtil.FormatXml(raw);

Console.WriteLine(formatted);
C#

出力イメージは次のようになります。

<?xml version="1.0" encoding="utf-16"?>
<root>
  <item id="1">
    <name>Taro</name>
  </item>
  <item id="2">
    <name>Jiro</name>
  </item>
</root>

タグの入れ子が一目で分かるようになり、「どこに何があるか」を目で追いやすくなります。

すでに整形済みのXMLを渡した場合

すでにインデント付きのXMLを渡しても、一度パースしてから書き直すので、インデントスタイルや改行コードがユーティリティ側のルールに揃います。
「バラバラなスタイルのXMLを、プロジェクト標準のスタイルに揃える」という用途にも使えます。


エラーや不正XMLへの向き合い方

XMLは、タグの閉じ忘れ、属性の引用符抜け、ルート要素が複数ある、などの理由で簡単に壊れます。
XDocument.Parse は厳密なので、こうした不正XMLに対しては XmlException を投げます。

今回のユーティリティでは、catch (XmlException) で例外を受け取り、そのまま元の文字列を返すようにしています。
これにより、「整形はできないが、とりあえずログに出して中身を見たい」というケースでも、アプリ全体が落ちることなく扱えます。

もし「不正XMLは絶対に許さない」という要件なら、ここで例外を再スローする実装に変えればOKです。
大事なのは、「ユーティリティ側で方針を決めておく」ことです。


XmlDocument を使った別パターンも知っておく

既存のコードベースでは、XmlDocument を使っていることも多いです。
同じような整形ユーティリティは、次のように書けます。

using System;
using System.IO;
using System.Text;
using System.Xml;

public static class XmlFormatUtilLegacy
{
    public static string FormatXml(string? xml)
    {
        if (string.IsNullOrWhiteSpace(xml))
        {
            return string.Empty;
        }

        try
        {
            var doc = new XmlDocument();
            doc.LoadXml(xml);

            var settings = new XmlWriterSettings
            {
                Indent = true,
                IndentChars = "  ",
                NewLineChars = "\n",
                NewLineHandling = NewLineHandling.Replace,
                OmitXmlDeclaration = false
            };

            using var sw = new StringWriter();
            using (var xw = XmlWriter.Create(sw, settings))
            {
                doc.Save(xw);
            }

            return sw.ToString();
        }
        catch (XmlException)
        {
            return xml;
        }
    }
}
C#

考え方は XDocument 版と同じで、「パースしてから XmlWriter で整形して書き出す」という流れです。
新規開発なら XDocument を使うほうが扱いやすいことが多いですが、既存コードに合わせて XmlDocument 版も書けると便利です。


業務ユーティリティとしてどうまとめるか

XML整形は、ログやデバッグで特に威力を発揮します。
例えば、次のようなラッパーを用意しておくと、呼び出し側がとても楽になります。

public static class XmlPretty
{
    public static string Format(string? xml)
        => XmlFormatUtil.FormatXml(xml);
}
C#

これで、ログ出力時に

logger.LogInformation(XmlPretty.Format(responseXml));
C#

のように一行で「整形してから出す」が書けます。

また、null や空文字の扱いもユーティリティ側で決めておくと、呼び出し側で毎回 null チェックを書く必要がなくなります。
今回の実装では、null や空白だけの文字列は空文字を返すようにしており、「とりあえず何かしら文字列が返ってくる」前提で扱えるようにしています。


まとめ 「XML整形ユーティリティ」は“XMLの中身を理解するための必需品”

XML整形は、単なる見た目の問題ではなく、「構造を理解しやすくするための道具」です。
業務でXMLを扱うなら、ほぼ確実に欲しくなるユーティリティです。

押さえておきたいポイントは、次の通りです。
文字列置換ではなく、必ず XML としてパースしてから整形すること。
XDocument.ParseXmlWriterSettings(Indent = true)doc.Save(XmlWriter) という流れを覚えておくこと。
インデント文字、改行コード、XML宣言の有無などを XmlWriterSettings で統一しておくと、プロジェクト全体のスタイルが揃うこと。
不正XMLへの扱い(例外にするか、元の文字列を返すか)をユーティリティ側で決めておくと、呼び出し側がシンプルになること。

ここまで理解できれば、「なんとなくXMLを眺めている」段階から一歩進んで、
“読みやすさとデバッグ効率を一気に上げるXML整形ユーティリティ”を、自分のC#コードに自然に組み込めるようになっていきます。

タイトルとURLをコピーしました