C# Tips | 文字列処理:XML圧縮

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

はじめに 「XML圧縮」は“意味はそのまま、見た目だけ最小化する”

ここでいう「XML圧縮」は、GZip などのバイナリ圧縮ではなく、

改行・インデント・余計な空白をできるだけ取り除いて、XML文字列を短くする

いわゆる XMLのミニファイ(minify) の話です。

例えば、整形済みのXMLがこうだとして:

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

これを圧縮すると、こうなります。

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

意味(要素・属性・値)は一切変えずに、「見た目だけ」コンパクトにするイメージです。


基本方針 「空白を消す」のではなく「XMLとして書き直す」

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

初心者がやりがちなのは、XML文字列に対して

  • 改行を全部削除する
  • タブやスペースを全部削除する

といった「文字列置換ベース」の圧縮です。

これは危険です。

  • テキストノードの中のスペース(例: <name>山田 太郎</name> の空白)
  • 属性値の中のスペース(例: display="inline block"
  • CDATA セクションの中の改行やスペース

など、「消してはいけない空白」まで消してしまう可能性が高いからです。

正しいやり方は、XML整形と同じく、

  1. XML文字列を一度パースして「構造」として理解する
  2. インデントなし・余計な改行なしで書き直す

という二段構えです。

C#では、XDocumentXmlWriter を使うのが素直です。


実務で使える基本実装 XDocument+XmlWriterSettings 版

完成形のユーティリティ

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

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

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

            var settings = new XmlWriterSettings
            {
                Indent = false,
                NewLineHandling = NewLineHandling.None,
                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#

この1メソッドで、「整形済みXML → 1行XML」にする“XML圧縮ユーティリティ”として、ほぼそのまま業務で使えます。
ここから、重要なポイントを分解していきます。


重要ポイント1 XDocument.Parse で“構造として”読み直す

var doc = XDocument.Parse(xml, LoadOptions.PreserveWhitespace);
C#

ここでやっているのは、

  • XML文字列をパースして
  • ルート要素・子要素・属性・コメントなどを「構造」として扱えるようにする

という処理です。

LoadOptions.PreserveWhitespace を付けているのは、「空白も一応ノードとして保持する」ためです。
ただし、最終的には XmlWriterSettings のルールで書き直されるので、「元の空白を完全に再現する」わけではありません。

もし XML が壊れている場合(タグ閉じ忘れ、ルートが複数など)、XmlException が投げられます。
今回のユーティリティでは、

catch (XmlException)
{
    return xml;
}
C#

として、「圧縮はあきらめて元の文字列を返す」方針にしています。
ログやデバッグ用途では、「壊れていてもとりあえず見たい」ことが多いので、この挙動はかなり実務的です。


重要ポイント2 XmlWriterSettings で“コンパクト出力モード”にする

var settings = new XmlWriterSettings
{
    Indent = false,
    NewLineHandling = NewLineHandling.None,
    OmitXmlDeclaration = false
};
C#

ここが「圧縮モード」の肝です。

Indent = false
インデントを付けない設定です。
これにより、要素の入れ子に応じたスペースやタブが一切出力されなくなります。

NewLineHandling = NewLineHandling.None
改行をそのまま出力するかどうかの指定です。
None にすることで、「XMLライターが勝手に改行を入れない」方向に寄せています。
結果として、ほぼ1行に詰まったXMLになります。

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

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


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

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

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

ここで大事なのは、

  • タグの入れ子
  • 属性の位置
  • コメントやテキストノード

など、XMLとしての構造を理解したうえで、「余計な改行・インデントを付けずに」出力している、という点です。

単純な文字列置換ではなく、「構造ベースで書き直す」からこそ、安全に圧縮できます。


動作例で違いを確認する

整形済みXMLを圧縮する

string pretty = @"<?xml version=""1.0"" encoding=""utf-8""?>
<root>
  <item id=""1"">
    <name>Taro</name>
  </item>
  <item id=""2"">
    <name>Jiro</name>
  </item>
</root>";

string compressed = XmlCompressUtil.CompressXml(pretty);

Console.WriteLine(compressed);
C#

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

<?xml version="1.0" encoding="utf-16"?><root><item id="1"><name>Taro</name></item><item id="2"><name>Jiro</name></item></root>
  • 改行がなくなり、1行に詰まっている
  • インデントも消えている
  • 要素・属性・値の関係はそのまま

という状態になっています(encoding 表示は StringWriter の実装に依存します)。

もともと1行XMLを渡した場合

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

string compressed = XmlCompressUtil.CompressXml(raw);

Console.WriteLine(compressed); // ほぼ同じ1行XML
C#

この場合は、ほとんど変化しません。
「整形済みでも、1行でも、とりあえず“正規化されたコンパクトXML”にする」イメージです。


XmlDocument を使った別パターン(レガシー寄り)

既存の .NET Framework 系プロジェクトでは、XmlDocument が使われていることも多いです。
同じような圧縮ユーティリティは、次のように書けます。

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

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

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

            var settings = new XmlWriterSettings
            {
                Indent = false,
                NewLineHandling = NewLineHandling.None,
                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 版と同じで、「パースしてから、インデントなし・改行なしで書き直す」という流れです。


業務ユーティリティとしてのまとめ方

「整形」と「圧縮」をセットで持っておくと強い

XML整形とXML圧縮は、ペアで持っておくとかなり便利です。

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

public static class XmlMini
{
    public static string Compress(string? xml)
        => XmlCompressUtil.CompressXml(xml);
}
C#

これで、

  • デバッグ・ログ出力 → XmlPretty.Format(xml)
  • 送信前・保存前にサイズを減らしたい → XmlMini.Compress(xml)

というように、用途に応じて簡単に切り替えられます。

null や不正XMLの扱いを“ユーティリティ側で決めておく”

今回の実装では、

  • null / 空 / 空白だけ → 空文字を返す
  • 不正XML → 元の文字列をそのまま返す

というルールにしています。

これにより、呼び出し側は

  • いちいち null チェックを書かなくてよい
  • 例外処理を毎回書かなくてよい

というメリットがあります。

プロジェクトによっては、

  • 不正XMLなら例外を投げる
  • null は null のまま返す

といったポリシーもあり得ますが、
大事なのは「ユーティリティ側で統一ルールを決める」ことです。


まとめ 「XML圧縮ユーティリティ」は“通信・保存を少しだけ賢くする小さな最適化”

XML圧縮は、見た目の読みやすさを犠牲にしてでも、

  • 通信量を少しでも減らしたい
  • 保存サイズを少しでも減らしたい
  • 整形済みXMLを“送信用の素の形”に戻したい

といった場面で効いてくる、小さな最適化です。

押さえておきたいポイントは、

  • 改行削除・空白削除のような“生の文字列いじり”ではなく、必ずXMLとしてパースしてから書き直すこと。
  • XDocument.ParseXmlWriterSettings(Indent = false, NewLineHandling = None)doc.Save(XmlWriter) という流れを覚えておくこと。
  • 不正XMLへの扱い(例外にするか、元の文字列を返すか)をユーティリティ側で決めておくと、呼び出し側がシンプルになること。
  • XML整形ユーティリティとセットで持っておくと、デバッグと本番の両方で使い分けやすいこと。

ここまで理解できれば、「なんとなく改行を消している」段階から一歩進んで、
“構造を意識した、安全で実務的なXML圧縮ユーティリティ”を、自分のC#コードに自然に組み込めるようになっていきます。

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