前回は、コードを書く前に「仕様書(file-format.md)」を作った話を書きました。.kasane(XML テンプレート)と .dat(INI 形式データ)の二層構造、レイヤーの設計思想——これらを明文化することで、AI(Claude Code)との共通理解の土台ができました。
仕様が固まったら、いよいよ実装です。今回は Phase 1: コアライブラリの実装 の話。そして前回予告した通り、ここで一つ技術的な選択がありました。XML を読むのに、C# 標準の XmlSerializer を使うか、それとも XLinq で手書きパースするか。
結論から言うと、手書きパース(XLinq)を選びました。なぜか。業務系プログラマの方なら、きっと「あるある」と感じてもらえる理由です。
コアライブラリ(Kasane.Core)の役割
まず、Kasane.Core が何をする部分かを説明します。
ひとことで言えば、「ファイルを読み込んで、メモリ上の帳票データに変換する心臓部」 です。
.kasane(XML)─┐
┝ Kasane.Core → メモリ上の帳票データ(Form オブジェクト)
.dat(INI) ─┘
この後のフェーズ(画面表示、印刷、PDF 出力)は、すべてこの「メモリ上の帳票データ」を使います。だから、ここの設計がプロジェクト全体の土台になります。一番慎重に作るべき部分です。
C# で XML を読む方法は、大きく2つあります。
方法1: XmlSerializer(自動マッピング)
XML の構造とクラスを対応させておくと、自動で変換してくれる仕組みです。
[XmlRoot("Form")]
public class Form
{
[XmlElement("Page")]
public Page Page { get; set; }
// ...
}属性を付けるだけで、XML ⇔ オブジェクトの変換を勝手にやってくれる。手間が少なく、楽です。
方法2: XLinq(手書きパース)
XDocument で XML を読み込み、自分で1つずつ要素を取り出してオブジェクトを組み立てます。
var doc = XDocument.Load(path);
var pageElement = doc.Root.Element(ns + "Page");
var size = pageElement.Attribute("size")?.Value ?? "A4";
// ...一つずつ手で読む手間はかかります。でも、読み込みの過程を完全にコントロールできる。
なぜ「手書きパース」を選んだか
Claude Code と相談する中で、最初は「XmlSerializer の方が楽では?」という話も出ました。ですが、業務帳票の仕様を考えると、手書きパースでないと困る場面が3つありました。
理由1: 既定値の処理
仕様書では、多くの属性を「省略可能」にしました。例えば線の太さ(thickness)は、書かなければ既定値 0.25mmです。
XmlSerializer だと、「属性が省略された時に既定値を入れる」処理が、意外と素直に書けません。手書きパースなら
var thickness = element.Attribute("thickness")?.Value;
double value = thickness != null ? double.Parse(thickness) : 0.25; // 省略なら 0.25このように、「省略されたら既定値」を明示的に書ける。業務帳票では「ほとんどの属性は省略可能で、書いた時だけ効く」という設計が多いので、これが重要でした。
理由2: 色の検証
色は #RRGGBB または #RRGGBBAA の形式で書く仕様にしました。でも、ユーザーが手で .kasane を編集して、#GGGGGG(不正な値)や red(名前)を書いてしまうかもしれません。
手書きパースなら、読み込む瞬間に検証できます。
// #RRGGBB / #RRGGBBAA の形式かチェック
if (!IsValidHexColor(colorText))
throw new KasaneFormatException($"不正な色指定: {colorText}");XmlSerializer だと、こういう「読み込み時の細かい検証」を挟むのが難しい面があります。業務システムでは「おかしなデータは、早い段階で弾く」のが鉄則です。
読み込み時に検証できるのは大きな利点でした。
理由3: ポリモーフィックな要素
これが一番の理由でした。Kasane のレイヤーには、いろいろな種類の要素(Line / Rectangle / Text / Field / …)が混在します。
<Layer name="Base">
<Text x="15" y="15">請求書</Text>
<Line x1="15" y1="22" x2="195" y2="22" />
<Field x="40" y="30" key="customer_name" />
<Rectangle x="140" y="25" width="40" height="20" />
</Layer>これらを「1つの要素リスト」として、種類を見ながら適切なクラスに振り分ける必要があります。XmlSerializer でポリモーフィックな要素を扱うのは、設定が複雑になりがちです。
手書きパースなら、素直に書けます。
foreach (var el in layerElement.Elements())
{
Element element = el.Name.LocalName switch
{
"Line" => ParseLine(el),
"Rectangle" => ParseRectangle(el),
"Text" => ParseText(el),
"Field" => ParseField(el),
// ...
};
}要素名を見て、対応するパーサーを呼ぶ。直感的で、種類が増えても拡張しやすい。実際、後のフェーズで要素の種類(円・寸法線・画像・明細表)が増えていきましたが、この方式のおかげで素直に追加できました。
「楽な方」より「コントロールできる方」
まとめると、XmlSerializer は楽ですが、業務帳票が必要とする「既定値処理・検証・ポリモーフィズム」を素直に書くには、手書きパース(XLinq)の方が向いていました。
手間は増えます。でも、土台となるコアライブラリは、手間をかけてでもコントロールできる方がいい。これは業務システム開発で何度も学んだ教訓です。
後から「ここに検証を入れたい」「この既定値を変えたい」となった時、自分で書いたパーサーなら自由に手を入れられます。
Claude Code は、この判断を踏まえて手書きパースで実装してくれました。「楽だから XmlSerializer」ではなく、「プロジェクトの性質に合うから XLinq」。
AI に丸投げするのではなく、「設計の方針を人間が決めて、実装を任せる」。この役割分担が、いい開発リズムになりました。
データバインドの設計: 寛容であること
コアライブラリのもう一つの肝が、Field(データを表示する要素)の仕組みです。
Field は .dat の値を引いて表示します。ここで一つ、設計判断をしました。キーに対応する値がなかったら、どうするか。
- エラーにする?
- 空文字として表示する?
私は 「空文字として表示する(エラーにしない)」 を選びました。業務帳票では「一部のデータが欠けていても、とりあえず帳票は出したい」という場面が多いからです。1つのフィールドが空でも、帳票全体は成立させたい。
この「寛容な設計」は、後のフェーズで面白い展開を生むのですが……それはまた別の回で。(実は、デザイナー機能を作った時に、この「空なら見えない」性質が、ちょっとした課題になりました)
エラーと警告の使い分け
業務システムらしい話として、「何をエラーにし、何を警告にするか」も整理しました。
| 種類 | 例 | 扱い |
|---|---|---|
| エラー(処理を止める) | レイヤー名の重複、不正な色形式、ページサイズが不正 | 例外を投げる |
| 警告(処理は続ける) | Field のキーがデータにない、座標が負の値 | 警告を記録、処理は継続 |
「直さないと帳票が壊れるもの」はエラー、「気をつけた方がいいが、動きはするもの」は警告。この線引きは、業務システムの設計でいつも考えることですね。
テストで土台を固める
コアライブラリは全体の土台なので、テストをしっかり書きました(xUnit)。
- XML を読み込んで、正しくオブジェクトになるか
- 既定値が正しく入るか
- 不正な値でちゃんとエラーになるか
- レイヤーの表示判定が仕様通りか
AI が書いたコードでも(むしろ AI が書いたからこそ)、テストで「本当に仕様通りか」を検証するのは欠かせません。テストが通っていれば、後のフェーズで土台をいじっても「壊れていないこと」がすぐ分かる。この安心感は、開発を加速させてくれました。
Phase 1 の段階で、テストは50件ほど。これが、後のフェーズでどんどん増えていきます(最終的には数百件に)。
次回予告
コアライブラリができて、.kasane と .dat を読み込んで、メモリ上の帳票データを組み立てられるようになりました。でも、まだ画面には何も映りません。データ構造があるだけです。
次回は Phase 2: WPF プレビューア。作ったデータ構造を、いよいよ画面に描画します。WPF の Canvas を使って、mm 単位の座標を画面のピクセルに変換し、帳票を表示する。「初めて帳票が画面に映った瞬間」の話を書きます。
それでは、また次回。
