👀

C# - XML - CDATAセクション

2025/01/27に公開

はじめに

C# 特徴のひとつが、XML 使い勝手の良さです。
使い勝手の良さから、パッケージソフト設定情報などで、XML を多用してきました。

XML 活躍の場所として、公的機関の電子申請(e-Gov, e-Tex, eLTax、マイナポータル)申請データなどがあります。
電子公文書は、XML + XSL で、帳票フォームに基づいた表示/印刷を可能としています。

公的機関の電子申請に携わったことがあり、そこで CDATAセクションに初めて出会いました。
参考情報を踏襲した情報となりますが、CDATAセクションについて記載します。

参考情報

下記情報を参考にさせて頂きました。

テスト環境

ここに記載した情報/ソースコードは、Visual Studio Community 2022 を利用した下記プロジェクトで生成したモジュールを Windows 11 24H2 で動作確認しています。

  • Windows Forms - .NET Framework 4.8
  • Windows Forms - .NET 8
  • WPF - .NET Framework 4.8
  • WPF - .NET 8

CDATAセクション

目的

XMLでは、いくつかの特殊文字は、エスケープ記述(実体参照)が必要となります。
<&lt;
>&gt;
&&amp;
"&quot;
'&apos;

"' は、要素の内容として記述可能ですが、属性の値として記述する場合には、エスケープ記述が必要となります。

XMLを直接テキストエディタで確認するケースなどで、このようなエスケープ記述が存在すると可読性が低くなってしまうため、これらの文字をエスケープ記述とせず、そのままの文字で扱う手法が CDATAセクションです。

CDATAセクションは、<![CDATA[ という文字列で開始し、]]> という文字列で終了します。
CDATAセクション内には、基本的に XML で利用可能な文字を全て記述することが可能です。
唯一の例外は ]]> という文字列だけで、この文字列はそのまま記述することできず、エスケープ記述が必要となります。

<Foo><![CDATA[直接 < > を記述可能]]></Foo>

形態

CDATAセクション利用については、下記2形態があります。

  • 空の場合も CDATA を付与する
  • 空の場合は CDATA を付与しない

C# 実装

CDataSection は、XmlSerializer と CDATA セクション 記載内容を、そのまま利用させて頂いて、CDataSectionEmptyExclusion を、CDataSection を継承した派生クラスとして定義します。
.NET Framework と .NET の差異は、Null許容参照型の明示なので、.NET Framework ベースのコードのみを記載します。

using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
// 空の場合も CDATA を付与する
public class CDataSection : IXmlSerializable
{
  public CDataSection() { }
  public CDataSection(string text) { Text = text; }
  public string Text { get; set; }

  public virtual XmlSchema GetSchema()
  {
    return null;
  }
  public virtual void ReadXml(XmlReader reader)
  {
    Text = reader.ReadElementString();
  }
  public virtual void WriteXml(XmlWriter writer)
  {
    writer.WriteCData(Text);
  }
}

// 空の場合は CDATA を付与しない
public class CDataSectionEmptyExclusion : CDataSection
{
  public CDataSectionEmptyExclusion() : base() { }
  public CDataSectionEmptyExclusion(string text) : base (text) { }

  public override void WriteXml(XmlWriter writer)
  {
    if (string.IsNullOrEmpty(Text))
    {
        writer.WriteWhitespace("");
    }
    else
    {
        writer.WriteCData(Text);
    }
  }
}

サンプルコード

サンプルデータ、C# サンプルコードを以下に記載します。
.NET Framework と .NET の差異は、Null許容参照型の明示なので、.NET Framework ベースのサンプルコードのみを記載します。

XML

Hoge.xml
<?xml version="1.0" encoding="utf-8"?>
<XmlDataRoot>
  <Foo><![CDATA[直接 < > を記述可能]]></Foo>
  <Bar><![CDATA[直接 < > を記述可能]]></Bar>
  <Baz>直接 &lt; &gt; を記述可能</Baz>
</XmlDataRoot>
Hoge-Empty.xml
<?xml version="1.0" encoding="utf-8"?>
<XmlDataRoot>
  <Foo><![CDATA[]]></Foo>
  <Bar></Bar>
  <Baz />
</XmlDataRoot>
Hoge-Null.xml
<?xml version="1.0" encoding="utf-8"?>
<XmlDataRoot />

クラス定義

https://learn.microsoft.com/ja-jp/dotnet/standard/serialization/attributes-that-control-xml-serialization

[Serializable]
[XmlRoot]
public class XmlDataRoot
{
  [XmlElement]
  public CDataSection Foo { get; set; }
  [XmlElement]
  public CDataSectionEmptyExclusion Bar { get; set; }
  [XmlElement]
  public string Baz { get; set; }
}

ファイル入出力

https://learn.microsoft.com/ja-jp/dotnet/api/system.xml.serialization.xmlserializer

// オブジェクト → XML
private bool SaveToFile(string filePath, object obj, Type type)
{
  XmlSerializer serializer = new XmlSerializer(type);

  // XML出力形式指定
  var settings = new XmlWriterSettings();
  settings.Encoding = Encoding.GetEncoding("utf-8");
  settings.Indent = true;

  // namespace出力抑止
  var xmlnsEmpty = new XmlSerializerNamespaces();
  xmlnsEmpty.Add(String.Empty, String.Empty);

  // シリアライズ
  using (var writer = XmlWriter.Create(filePath, settings))
  {
    serializer.Serialize(writer, obj, xmlnsEmpty);
  }
  return true;
}

// XML → オブジェクト
private T LoadFromFile<T>(string filePath) where T : class
{
  T obj = null;
  XmlSerializer serializer = new XmlSerializer(typeof(T));

  // ファイル確認
  if (File.Exists(filePath))
  {
    // デシリアライズ
    using (var fs = new FileStream(filePath, FileMode.Open))
    {
      obj = serializer.Deserialize(fs) as T;
    }
  }
  return obj;
}

シリアライズ

// データ有:引数ありコンストラクタ利用
var obj = new XmlDataRoot();
obj.Foo = new CDataSection("直接 < > を記述可能");
obj.Bar = new CDataSectionEmptyExclusion("直接 < > を記述可能");
obj.Baz = "直接 < > を記述可能";
SaveToFile("Hoge.xml", obj, typeof(XmlDataRoot));

// データ空:引数なしコンストラクタ利用
obj = new XmlDataRoot();
obj.Foo = new CDataSection();
obj.Bar = new CDataSectionEmptyExclusion();
obj.Foo.Text = string.Empty;
obj.Bar.Text = string.Empty;
obj.Baz = string.Empty;
SaveToFile("Hoge-Empty.xml", obj, typeof(XmlDataRoot));

// 該当要素なし
obj = new XmlDataRoot();
obj.Foo = null;
obj.Bar = null;
obj.Baz = null;
SaveToFile("Hoge-Null.xml", obj, typeof(XmlDataRoot));

デシリアライズ

var obj = LoadFromFile<XmlDataRoot>("Hoge.xml");
string foo = obj?.Foo?.Text;
string bar = obj?.Bar?.Text;
string baz = obj?.Baz;

obj = LoadFromFile<XmlDataRoot>("Hoge-Empty.xml");
// TODO

obj = LoadFromFile<XmlDataRoot>("Hoge-Null.xml");
// TODO

出典

本記事は、2025/01/27 Qiita 投稿記事の転載です。

C# - XML - CDATAセクション

Discussion