- はじめに 「XML圧縮」は“意味はそのまま、見た目だけ最小化する”
- 基本方針 「空白を消す」のではなく「XMLとして書き直す」
- 実務で使える基本実装 XDocument+XmlWriterSettings 版
- 重要ポイント1 XDocument.Parse で“構造として”読み直す
- 重要ポイント2 XmlWriterSettings で“コンパクト出力モード”にする
- 重要ポイント3 XmlWriter で“構造に従って書き直す”
- 動作例で違いを確認する
- XmlDocument を使った別パターン(レガシー寄り)
- 業務ユーティリティとしてのまとめ方
- まとめ 「XML圧縮ユーティリティ」は“通信・保存を少しだけ賢くする小さな最適化”
はじめに 「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整形と同じく、
- XML文字列を一度パースして「構造」として理解する
- インデントなし・余計な改行なしで書き直す
という二段構えです。
C#では、XDocument+XmlWriter を使うのが素直です。
実務で使える基本実装 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.Parse+XmlWriterSettings(Indent = false, NewLineHandling = None)+doc.Save(XmlWriter)という流れを覚えておくこと。- 不正XMLへの扱い(例外にするか、元の文字列を返すか)をユーティリティ側で決めておくと、呼び出し側がシンプルになること。
- XML整形ユーティリティとセットで持っておくと、デバッグと本番の両方で使い分けやすいこと。
ここまで理解できれば、「なんとなく改行を消している」段階から一歩進んで、
“構造を意識した、安全で実務的なXML圧縮ユーティリティ”を、自分のC#コードに自然に組み込めるようになっていきます。
