0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C# - XML - CDATAセクション

Last updated at Posted at 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>

パッケージソフト設定情報として XML を利用する際は、設定ツールを用意するので、テキストエディタでの可視性は重要視せず、CDATAセクションを自発的に利用することはありませんでした。

形態

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 />

クラス定義

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

ファイル入出力

// オブジェクト → 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
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?