Excel MCP Server (FsOpenXmlDsl) について
Excel MCP Server (FsOpenXmlDsl)は、ローカルMCPサーバーを通じてAIエージェントがExcelブックを読み書き、変換できるようにします。ブックをF#、C#、XML、JSONに変換し、XMLやJSONから作成できるため、往復変換に対応します。
主な機能
- 01Excelブックの読み書き
- 02ブックをF#またはC#に逆変換
- 03ブックのXML・JSON変換
- 04XML・JSONからブックを作成
- 05CLIによる変換とビルド
活用シーン
- 01既存ブックをソースとして調査
- 02XMLやJSONからブックを生成
- 03スプレッドシートをF#またはC#スクリプトとして再構築
MCP 対応
ローカルMCPサーバーは、ブックの読み書き、コード生成、XML・JSON変換をMCP対応エージェント向けのツールとして公開します。同じ変換機能は`fsopenxmldsl-mcp convert`と`build`からも利用できます。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
このサーバーのパッケージやリモートエンドポイントはまだ取得できていません。プロジェクトの README の手順に従ってください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
Kookerella.FsOpenXmlDsl
Excel ブックを構築するための、型安全な F# DSL です。DocumentFormat.OpenXml SDK に対する呼び出しとして解釈されます。この DSL は単純なデータモデル(構造的等価性を持つレコード/DU)であり、インタープリター(Writer)が OOXML にコンパイルし、逆変換機能(Reader)が既存の .xlsx を解析して、同じ DSL に戻します。
どの SpreadsheetML 機能が 1 対 1 で対応し、どれが近似され、どれがまだモデル化されていないかについては、MAPPING.md をご覧ください。
このライブラリは双方向のラウンドトリップに対応しています。多くの Excel ライブラリ(EPPlus、ClosedXML、NPOI など)では対応していません。これらのライブラリでは、ブックをゼロから構築したり、既存のブックを変更したりする命令型 API は提供されていますが、既存のファイルを読み取り可能なソースに戻す方法はありません。ここでは、Reader が実際の .xlsx/.xlsm を解析して同じ DSL に戻し、さらに Workbook.generateScript(F#)/CsCodeGen.Generate(C#)を使うと、そのモデルを、同等のファイルを再構築する自己完結型スクリプトとして出力できます。これは単なるライターではなく、スプレッドシートの逆コンパイラーです。もう 2 つのインターフェイス、Xml.ofWorkbook/Xml.toWorkbook(以下の「## XML」を参照)と Json.ofWorkbook/Json.toWorkbook(以下の「## JSON」を参照)では、実際のスキーマに準拠したプレーンな XML または JSON との間で同じ変換を行います。たとえば、コードを書くよりデータを生成または利用したい場合(レポートを生成する XSLT パイプラインなど)に便利です。Kookerella.FsOpenXmlDsl.Mcp は、AI エージェント向けに 4 方向すべての変換を MCP ツール(generate_fsharp_script/generate_csharp_script/generate_xml/create_workbook_from_xml/generate_json/create_workbook_from_json)として公開し、その他のユーザー向けには fsopenxmldsl-mcp convert/build CLI コマンドとして提供します。コードは不要です。お手元のスプレッドシートでお試しください。
dotnet tool install -g Kookerella.FsOpenXmlDsl.Mcp
fsopenxmldsl-mcp convert your-file.xlsx --lang csharp
.NET がインストールされていませんか?Releases ページから、お使いのプラットフォーム向けの自己完結型ビルドをダウンロードできます。インストールは不要です。
デモ
上記の逆コンパイルしてから拡張するワークフローの、完全な実例をご覧ください。同じ請求書テンプレートを C#、F#、XSLT 変換、プレーンな JSON 生成スクリプトへとリバースエンジニアリングし、それぞれを実際のデータと、結果がスキーマに適合し続けることを証明する実際のテストに接続した例が、コンパニオンリポジトリにあります。 Kookerella.Demo.DecompileToSource
レイアウト
src/Kookerella.FsOpenXmlDsl— ライブラリです。Reference.fs—CellRefと「A1」形式のアドレス変換です。Styles.fs— セルの書式設定です。Color、FontStyle、FillStyle、BorderStyle、AlignmentStyle、NumberFormat、CellProtection、CellStyleを定義します。Validation.fs— 条件付き書式とデータの入力規則です。両方で共有されるComparisonOperator、ConditionalFormatRule、ValidationKind、ValidationAlertと、Worksheetに保存されるConditionalFormatEntry/DataValidationEntryレコードを定義します。Hyperlinks.fs—HyperlinkTarget(外部 URL/mailto:と、同じブック内の内部参照)と、Worksheetに保存されるHyperlinkEntryレコードを定義します。Comments.fs—CommentEntry(従来型のセルコメント、つまり現在の Excel でいう「メモ」です。 最新のスレッド形式コメントの未対応範囲については MAPPING.md を参照してください)。Protection.fs—Worksheetに保存されるシートレベルの保護フラグSheetProtection(セル単位のロック設定を行うCellStyle.Protectionと組み合わせて使用)と、Workbookに保存されるブックレベルの構造/ウィンドウ保護フラグWorkbookProtectionを定義します。DefinedNames.fs—DefinedNameScope/DefinedNameEntryを定義します。WorksheetではなくWorkbookに保存される、真にブックレベルの DSL 概念です。PageSetup.fs— 印刷設定です。PageOrientation、PaperSize、PrintScaling、PageMarginsと、Worksheetに保存されるPageSetupレコードを定義します。Tables.fs— Excel テーブルです。TableColumn、TableStyleと、Worksheetにリストとして 保存されるTableEntryレコードを定義します(1 つのシートに複数のテーブルを設定できます)。Sparklines.fs— セル内ミニグラフです。SparklineType、SparklineStyle、SparklineCellと、Worksheetにリストとして保存されるSparklineGroupEntryレコードを定義します (1 つのシートに、個別にスタイルを設定したグループを複数配置できます)。Charts.fs— 縦棒/横棒/折れ線/円グラフです。ChartType、ChartSeriesと、Worksheetにリストとして保存されるChartEntryレコードを定義します (1 つのシートに複数のグラフを設定できます)。Images.fs— ラスター画像です。ImageFormatと、Worksheetにリストとして保存されるImageEntryレコード(ファイルの生バイト列とセル範囲のアンカー)を定義します。PivotTables.fs—PivotAggregationと、Worksheetにリストとして保存されるPivotTableEntryレコード(ソース範囲、行/列/値フィールド、アンカーセル)を定義します。Model.fs—CellValue、Cell、Worksheet、Workbookを定義します。これにはWorkbook.VbaProject(マクロ有効ブックのvbaProject.binの生バイト列)も含まれます。 詳細はそのドキュメントコメントを参照してください。単一の不透明なフィールドで新しい型ではないため、 専用のMacros.fsはありません。Xml.fs/Xml.xsd— XML インターフェースです。Xml.toWorkbook/Xml.ofWorkbookはWorkbookをXElementツリーに変換し、またはそこから変換します。Xml.schemaSet()は、 いずれの方向の検証にも使用する対応スキーマ(アセンブリ内にリソースとして埋め込み)を読み込みます。 下記の「## XML」を参照してください。Json.fs— JSON インターフェースです。Json.toWorkbook/Json.ofWorkbookはWorkbookをSystem.Text.Json.Nodes.JsonObjectツリーに変換し、またはそこから変換します。Xml.fsと同じワークシート/ブックレベルの機能を網羅しています。スキーマ検証 (Json.schema.json)はテストスイート専用であり、公開 API ではありません。下記の 「## JSON」を参照してください。Builders.fs— 使いやすいヘルパーです。標準モデル向けの通常の関数コンストラクター(cellA1など)に加え、SheetItem/CellEntry型(それぞれ、オプションフィールドを持つ単一のシンプルな DU ケース)と、sheet畳み込み関数を提供します。これは、SpreadsheetML 自体の入れ子構造を反映した、 ツリー構造の小さな「シート構築用 AST」です(セルの行に加え、列幅、結合、条件付き書式、 データの入力規則、ハイパーリンク、コメント、オートフィルター、シート保護などのシートレベルの情報を含みます)。 実際に使用するのはSheetDslです。cell/row/autoFilter/conditionalFormat/dataValidation/hyperlink/commentの各メンバーには、実際のオプションパラメーター (?col、?style、?index、データの入力規則のアラートフィールド、?tooltip、?author)があります。 ビルダーオブジェクトや個別の「スタイル付き」関数はなく、一般的なケースでNoneを記述する必要もありません。 (スマートコンストラクターがない唯一のSheetItemケースはProtectです。SheetProtectionは通常の F# の方法で作成するプレーンなレコードです。{ SheetProtection.Default with ... }。)Interpreter/StyleRegistry.fs— フォント/塗りつぶし/罫線/数値形式を共有 OOXML スタイルシートに 登録します(内部用)。Interpreter/ChartWriter.fs/ChartReader.fs— グラフ独自の DSL ↔ DrawingML/ChartML 変換です。 他のすべてを合わせたものより、この機能の OOXML インターフェースのほうが大幅に大きいため、Writer.fs/Reader.fsから分離されています(内部用)。Interpreter/ImageWriter.fs/ImageReader.fs— 画像独自の DSL ↔ DrawingML 変換です(内部用)。Interpreter/DrawingWriter.fs/DrawingReader.fs— ワークシートにグラフや画像がある場合に作成される 1 つのDrawingsPart/<drawing>リレーションシップを管理します。両機能は個別に管理するのではなく、 同じ描画キャンバスを共有するためです(内部用)。Interpreter/PivotTableWriter.fs/PivotTableReader.fs— ピボットテーブル独自のグループ化/集計エンジンと DSL ↔ OOXML 変換(pivotCacheDefinition/pivotCacheRecords/pivotTableDefinition)です。 グラフや画像と同様に、Writer.fs/Reader.fsから分離されています(内部用)。Interpreter/Writer.fs— DSL → OOXML 変換です(内部用)。Interpreter/Reader.fs— OOXML → DSL 変換です。逆方向の変換を行います(内部用)。Interpreter/CodeGen.fs— DSL → F# ソーステキスト変換です。Workbookを、実行すると同等のファイルを 再構築する自己完結型の.fsxスクリプトとして出力します(内部用)。Api.fs— 公開エントリーポイントのWorkbook.save/saveToStream/load/loadFromStream/generateScriptを提供します。
tests/Kookerella.FsOpenXmlDsl.Tests— 機能ごとに 1 つのテストがあり、生成ファイルを OOXML スキーマ (DocumentFormat.OpenXml.Validation.OpenXmlValidator)に照らして検証し、DSL を通じて正確に往復変換されることを 確認します。各テストは、構築したブックをExamples/<test name>/output.xlsxにも書き込みます (リポジトリにコミット済み)。そのため、すべての機能に、それを実演する実際に開ける.xlsxファイルがあり、 アサーションだけでなく、閲覧できるギャラリーになっています。各シナリオにはExamples/<test name>/script.fsxもあります(下記の「F# ソースとしてのファイル再生成」を参照)。 これを実行し、コミット済みの.xlsxと照合する、より時間のかかる独立したCategory=Slowテストグループがあります。 実行にはdotnet fsiを使用します。また、Examples/<test name>/workbook.xmlにはXml.ofWorkbookを通した同じブックが含まれ、生成時にXml.xsdに照らして検証されます (下記の「## XML」を参照)。Examples/<test name>/workbook.jsonにはJson.ofWorkbookを通した同じブックが含まれ、生成時にJson.schema.jsonに照らして検証されます (下記の「## JSON」を参照)。このため、各フォルダーには常に同じサンプルの 4 つの形式、つまり実ファイル、 再構築に使用する F# ソース、そして再構築にも使用する XML/JSON がそろっています。Assets/には、Tests.fsのほかのバイナリテストデータのように base64 リテラルとしてインライン化するには 大きすぎる唯一のテストフィクスチャが含まれます。これは実際に Excel で保存したブックから抽出したvbaProject.binで、マクロのサンプルで使用されます。samples/Kookerella.FsOpenXmlDsl.Sample— ブックを構築、保存し、読み戻す小さなコンソールアプリです。src/Kookerella.CsOpenXmlDsl— F# の判別共用体/オプション型を直接扱いたくないユーザー向けの、 このライブラリをラップする、慣用的な不変の流れるような C# API です。現在、このライブラリがモデル化する ワークシート/ブックレベルのすべての機能に対応しています。対象範囲と使用例については、独自の README を参照してください。tests/Kookerella.CsOpenXmlDsl.Testsは独立した C# xUnit テストスイートで、F# のテストプロジェクトを再利用するのではなく、 実際の C# ユーザーの使い方に沿ってラッパーをテストします。src/Kookerella.FsOpenXmlDsl.Mcp— このライブラリの読み取り/書き込み/コード生成/XML/JSON 機能を、 MCP 対応の AI エージェントが直接呼び出せるツールとして公開する、ローカルの MCP(Model Context Protocol)サーバーです。 MCP クライアントを介さないユーザー向けには、同じ変換機能を通常のfsopenxmldsl-mcp convert/buildCLI コマンドでも提供します。 ツール一覧と設定方法については、独自の README を参照してください。
クイックスタート
open Kookerella.FsOpenXmlDsl
open type Kookerella.FsOpenXmlDsl.SheetDsl
let headerStyle =
{ CellStyle.Default with
Font = Some { FontStyle.Default with Bold = true }
Fill = Some { Color = Rgb(220uy, 220uy, 220uy) } }
let data =
sheet
"Sheet1"
[ row [ cell (Text "Name", style = headerStyle)
cell (Text "Amount", style = headerStyle) ]
row [ cell (Text "Widgets")
cell (Number 42.5, style = { CellStyle.Default with NumberFormat = Some TwoDecimal }) ]
Freeze(1, 0) ]
workbook [ data ] |> Workbook.save "out.xlsx"
// Reverse transform:
let roundTripped = Workbook.load "out.xlsx"
CellEntry と SheetItem の行ケースは、それぞれ省略可能フィールド(Col/Index)を持つ単一のシンプルな DU ケースであり、「スタイル付き」や「位置を明示する」ケースが別にあるわけではありません。None は「前のエントリの次の列/行」(0 から開始)を意味し、Some n はその位置へ明示的に移動し、その直後から連番が再開します。ただし、ケースを直接構築することはありません。代わりに、SheetDsl.cell/SheetDsl.row は実際の省略可能パラメーター(cell の ?col/?style、row の ?index)を持つメンバーで、一般的な用途では None を隠してくれます。F# では通常の let 関数に省略可能パラメーターを設定できないため、この DSL の一部だけは型になっています。open Kookerella.FsOpenXmlDsl に加えて open type Kookerella.FsOpenXmlDsl.SheetDsl とすると、モジュールと同様に cell/row を修飾なしで使えるようになります。列/行の位置を明示的に移動する場合も、同じ 2 つのメンバーに省略可能引数を渡します。例:cell (value, col = 2)、row (cells, index = 4)。sheet は、生成された項目リストを正規の Worksheet に解釈する唯一の fold です(Writer と OOXML の関係と同じです)。行ごとにグループ化されているのではなく、CellRef で事前に位置が指定されたセルをすでに持っている場合は、sheetOfCells を使うとフラットな Cell list から直接 Worksheet を構築できます。
Formula セルは Formula(expression, cachedValue: float option) です。このライブラリ自体は数式を評価しないため、別の処理によって計算されるまでは、cachedValue がそのセルに存在する唯一の数値です。 Excel はファイルを開くと再計算して値を上書きするため、必ず人が最初に Excel で結果を開くのであれば、None のままでも問題ありません。ただし、ヘッドレスパイプラインでは安全ではありません。たとえば、ワークブックを生成して、そのまま PDF コンバーターや別の自動読み取り処理など、実際の Excel で開くことのない処理に渡す場合です。後続の処理で正しい数値、空白、古い数値のどれが表示されるかは、その処理自体に数式エンジンがあるかどうかに完全に左右されます。数式エンジンを備えているものもあります(Aspose.Cells、Syncfusion、GemBox、COM 経由の実際の Excel)。一方、多くの軽量コンバーターやヘッドレスコンバーターにはなく、セルにすでに入っている値をそのまま表示します。数式の計算に使った数値がすでにあるのなら、先に Excel を通ることが保証されていない場合は、必ず実際の計算結果を cachedValue に渡してください。コストはかからず、問題を完全に回避できます。評価機能をまったく持たない後続の読み取り処理でも、すでに誰かが計算した正しい値を表示できるためです。
条件付き書式とデータの入力規則も SheetItem です。
[ conditionalFormat (
CellRef.ofA1 "A1",
CellRef.ofA1 "A10",
CellValueRule(GreaterThan, "100", None, { CellStyle.Default with Fill = Some { Color = Rgb(255uy, 199uy, 206uy) } })
)
dataValidation (CellRef.ofA1 "B1", CellRef.ofA1 "B10", ListValidation [ "Small"; "Medium"; "Large" ]) ]
それぞれどの種類のルールに対応しているかについては、MAPPING.md を参照してください。
定義名はワークブックレベルの要素なので、Worksheet ではなく Workbook に設定します。
workbook [ data ]
|> withDefinedNames
[ definedName "TaxRate" "Sheet1!$A$1"
sheetScopedDefinedName "Sheet1" "LocalTotal" "Sheet1!$A$2" ]
Worksheet 自体の SheetProtection とは異なるワークブックレベルの保護も、同様にワークブックレベルで設定でき、パイプでつなぎやすい形式です。
workbook [ data ]
|> withProtection { WorkbookProtection.Default with LockStructure = Some true }
withDefinedNames/withProtection は組み合わせて使用できます。同じ workbook [...] に対して、両方をパイプで適用してください。
マクロも同様にワークブックレベルの要素で、パイプでつなぎやすい形式です。withVbaProject は、既存の vbaProject.bin の生バイト列を受け取ります(すでにある .xlsm から System.IO.Compression.ZipFile などで抽出するか、Excel の VBA エディターで作成して同様に取り出します)。Core は VBA ソースを解析、生成、理解することはありません。ラスター画像に対する ImageEntry.Data と同じ「不透明なペイロード」として扱い、渡されたバイト列をそのまま埋め込み、読み戻します。
workbook [ data ]
|> withVbaProject (System.IO.File.ReadAllBytes("vbaProject.bin"))
結果は .xlsm パスで保存してください。VbaProject が存在する場合、Workbook.save/saveToStream はファイルに宣言するコンテンツタイプを Excel のマクロ有効形式に自動で切り替えますが、実際の Excel はマクロを信頼して実行する際に .xlsm 拡張子も必要とします。モデル化されていないもの(マクロソースの作成、および Core が書き込む既定のシート名/ワークブックのコード名が、マクロの元の作成者の意図と一致しない場合)については、MAPPING.md を参照してください。
印刷設定も SheetItem です。PageSetup(DU ケース)は、スマートコンストラクターではなく、通常の PageSetup レコード(型)を受け取ります。Protect/SheetProtection と同じです。
PrintArea は範囲のリストです(Excel では、シートごとに複数の離れた印刷範囲を指定できます)。内部的には実際には非表示の定義名ですが、Writer/Reader が透過的に変換するため、ほかの PageSetup フィールドと同じように読み書きできます。
[ PageSetup
{ PageSetup.Default with
Orientation = Landscape
Scaling = Some(FitToPage(1, 0)) // 1 page wide, unlimited tall
PrintArea = [ (CellRef.ofA1 "A1", CellRef.ofA1 "D10") ]
Header = Some "&C&\"Arial,Bold\"Quarterly Report"
FirstHeader = Some "&CCover Page" // shown only on page 1
EvenFooter = Some "&L&F" } ] // shown only on even pages
モデル化されていない項目(集計行のないテーブルやヘッダーのないテーブル、および印刷ページの順序など、pageSetup の細かな属性)については、MAPPING.md を参照してください。
テーブルも SheetItem です。Table(DU ケース)は、スマートコンストラクターではなく、通常の TableEntry レコード(型)を受け取ります。Protect/PageSetup と同じです。Core がヘッダー行のセルテキストを生成することはないため、通常のセルとしてあらかじめ配置しておく必要があります。これは、条件付き書式、オートフィルター、セル結合が、すでに配置済みのセルに重ねるメタデータだけを記述するのと同じです。
sheet
"Sheet1"
[ row [ cell (Text "Item"); cell (Text "Quantity") ]
row [ cell (Text "Widgets"); cell (Number 12.0) ]
Table
{ TopLeft = CellRef.ofA1 "A1"
BottomRight = CellRef.ofA1 "B2"
Name = "Inventory"
Columns = [ { Name = "Item"; CalculatedFormula = None }; { Name = "Quantity"; CalculatedFormula = None } ]
Style = TableStyle.Default } ]
構造化参照(Table1[Column])に特別な処理は必要ありません。ほかの数式と同様、Formula セル内の生の数式テキストにすぎません。モデル化されていない項目(集計行、ヘッダーのないテーブル)については、MAPPING.md を参照してください。
スパークラインも同じ形式です。SparklineGroup(DU ケース)は、通常の SparklineGroupEntry レコードを受け取ります。
[ SparklineGroup
{ Style = { SparklineStyle.Default with Type = Column; ShowNegative = true }
Sparklines =
[ { Cell = CellRef.ofA1 "E1"; DataTopLeft = CellRef.ofA1 "A1"; DataBottomRight = CellRef.ofA1 "D1" } ] } ]
スパークラインは Microsoft の拡張機能(ワークシートの extLst 内に格納)であり、SpreadsheetML のコア機能ではありません。このライブラリのほかの機能とは異なり、スキーマ検証だけでは Excel で正しく描画されることを確認できないため、少し慎重に扱い、依存する前に実際の Excel で確認してください。モデル化されていない項目(軸の設定、メイン系列の色以外の役割ごとの色)については、MAPPING.md を参照してください。
グラフも同じ形式です。EmbeddedChart(Chart そのものではありません。OOXML SDK に同名の型があるためです。Builders.fs を参照してください)は、通常の ChartEntry レコードを受け取ります。系列の Name は、その名前を示すセル(通常は列見出し)への参照です。実際の Excel グラフの系列名と同様、ライブ更新されます。静的なコピーではありません。
[ EmbeddedChart
{ Type = ChartColumn
Title = Some "Sales by Quarter"
CategoriesTopLeft = CellRef.ofA1 "A2"
CategoriesBottomRight = CellRef.ofA1 "A4"
Series = [ { Name = CellRef.ofA1 "B1"; ValuesTopLeft = CellRef.ofA1 "B2"; ValuesBottomRight = CellRef.ofA1 "B4" } ]
ShowLegend = true
TopLeftAnchor = CellRef.ofA1 "E1"
BottomRightAnchor = CellRef.ofA1 "L15" } ]
スパークラインとは異なり、グラフはコア機能であり、スキーマに完全に基づく DrawingML/ChartML です。ほかのすべての機能と同じように、拡張メカニズムではなく、型付きの OOXML SDK クラスから構築されます。モデル化されていない項目(縦棒/横棒/折れ線/円以外のグラフの種類、系列ごとのスタイル、積み上げグループ)については、MAPPING.md を参照してください。
画像も同じ方法でアンカーされます。EmbeddedImage は通常の ImageEntry レコードを受け取ります。Data は画像ファイルそのものの生バイト列です(たとえば System.IO.File.ReadAllBytes で読み込みます)。この DSL はデータのデコードも再エンコードも行わず、渡されたデータをそのまま埋め込み、返します。
[ EmbeddedImage
{ Data = System.IO.File.ReadAllBytes("logo.png")
Format = Png
TopLeftAnchor = CellRef.ofA1 "A1"
BottomRightAnchor = CellRef.ofA1 "C6" } ]
ワークシートのグラフと画像は、内部で同じ描画キャンバスを共有します(Excel ではシートごとにキャンバスは 1 つだけです)。呼び出し側からはこの点を意識する必要はありません。同じシートに両方の種類の SheetItem を追加するだけです。モデル化されていない項目(PNG/JPEG/GIF/BMP 以外の形式、自由な位置指定、トリミング、埋め込みではなくリンクされた画像)については、MAPPING.md を参照してください。
ピボットテーブルも SheetItem です。EmbeddedPivotTable(EmbeddedChart/EmbeddedImage と命名の一貫性を保つため、PivotTable そのものではありません)は、通常の PivotTableEntry レコードを受け取ります。ほかのすべての機能とは異なり、これは単なる変換ではなく、書き込み時に実際の処理を行います。ソース範囲を RowField(指定されている場合は ColumnField も)でグループ化し、ValueField を集計して、実際の Excel ピボットキャッシュと、計算済みセルのグリッドの両方を書き込みます。
[ EmbeddedPivotTable
{ SourceSheet = None // defaults to this sheet; can name another
SourceTopLeft = CellRef.ofA1 "A1"
SourceBottomRight = CellRef.ofA1 "C5"
RowField = "Region"
ColumnField = Some "Quarter"
ValueField = "Sales"
Aggregation = PivotSum
ValueCaption = Some "Total Sales"
TopLeftAnchor = CellRef.ofA1 "E1" } ]
ソース範囲の最初の行には、各フィールド名を示す通常の Text ヘッダーセルが必要です。これは、各軸に単一のフィールドを指定する場合に表現できる範囲に意図的に限定されています。行フィールドは 1 つ、列フィールドは最大 1 つ、値フィールドは 1 つ、レイアウトは表形式、総計のみです。より高度なピボットテーブル(ネストされたフィールド、複数の値フィールド、ページフィルター)で必要となる内容や、この設計の理由については、MAPPING.md を参照してください。
F# ソースとしてファイルを再生成する
Workbook(通常は既存のファイルから直前に Workbook.load したもの)を指定すると、Workbook.generateScript はそれを自己完結型の .fsx スクリプトとして出力します。実行すると同等のファイルを再構築します。これは Workbook.load に対応するコード生成機能であり、逆変換をさらに一段進めたものです。データの代わりに、DSL の ソーステキスト を取得できます。スクリプトで FsOpenXmlDsl アセンブリをどのように参照するかは指定しないため、#r 行は自分で用意してください。
let wb = Workbook.load "input.xlsx"
let referenceLines =
[ "#r \"path/to/Kookerella.FsOpenXmlDsl.dll\""
"#r \"path/to/DocumentFormat.OpenXml.dll\"" ]
let script = Workbook.generateScript referenceLines "output.xlsx" wb
System.IO.File.WriteAllText("regenerate.fsx", script)
dotnet fsi regenerate.fsx を実行すると output.xlsx が生成されます。元のファイルとバイト単位で同一にはなりません(ZIP のメタデータやタイムスタンプが異なります)が、このリポジトリの他のすべてのテストで使われている同じラウンドトリップの観点では、構造的に同等です。生成コードが言及するのは、CellStyle.Default/BorderStyle.None などと異なるフィールドだけです。また、元のデータに間隔がある場合に限り、行やセルに明示的な index/col を指定します。詳しくは Interpreter/CodeGen.fs を参照してください。tests/Kookerella.FsOpenXmlDsl.Tests/Examples/ 以下の各シナリオには、この方法で生成された script.fsx がコミットされています。Category=Slow テストグループでは、実際にそれぞれを dotnet fsi で実行し、コミット済みの .xlsx が再現されることを確認しています。
XML
Xml.toWorkbook/Xml.ofWorkbook(Xml.fs 内)は、F#/C# を直接記述する方法やコード生成に加えて、DSL を出入りする 3 つ目の方法です。実際のスキーマ(アセンブリに埋め込まれた Xml.xsd)に準拠したプレーンな XML を使います。これは、コードをまったく書かずにデータを生成または取り込むことを希望する呼び出し元のための機能です。具体的な用途は次のとおりです。
- 変換エンジンがすでに生成する XML から
.xlsxを構築する - XSLT パイプライン(または XML を出力するテンプレート処理)は、OOXML スキーマやこのライブラリ独自の API を把握しなくても、Excel を直接出力先にできます。 - 既存の
.xlsxをバージョン管理用に XML に変換する -.xlsxはバイナリ ZIP なので、git diffを使っても意味がありません。先に XML に変換することで、人が読める実際の差分を確認できます。Xml.ofWorkbookの出力は、基になるWorkbookのリストの並び順に関係なく、決定的な順序になります(セル位置順、定義名の場合は名前順)。そのため、実際の内容変更では小さく独立した差分が生成され、実行ごとに行やルールが並べ替えられたことによる不要な差分は発生しません。
open System.Xml.Linq
// XML -> Workbook -> .xlsx
let wb = XElement.Load "report.xml" |> Xml.toWorkbook
Workbook.save "report.xlsx" wb
// .xlsx -> Workbook -> XML
let xml = Workbook.load "report.xlsx" |> Xml.ofWorkbook
xml.Save "report.xml"
判別共用体のケースは、独自のデータを持つ場合はケース名(camelCase)を名前とする XML 要素になり、パラメーターを持たない複数の選択肢のうちの 1 つである場合は属性 値(こちらも camelCase)になります。たとえば、セルの値は次のようになります。
<cell ref="B2">
<number>42.5</number>
<style>
<numberFormat kind="currency" />
</style>
</cell>
より詳しい例として、ValidationKind の 6 つのケースも同じ規則に従います。また、ValidationAlert のフィールドは入れ子にせず、<dataValidation> 自体の属性として記述されます。
<dataValidation topLeft="A2" bottomRight="A2" errorTitle="Invalid quantity"
errorMessage="Quantity must be a positive whole number.">
<wholeNumberValidation operator="greaterThan" formula1="0" />
</dataValidation>
ConditionalFormatRule の 7 つのケースも同じ規則に従い、ルールで必要な場合は CellStyle 全体を入れ子にします。<fill> は <rgb>/<indexed>/<theme> を直接含み、間にラッパー要素はありません。
<conditionalFormat topLeft="A1" bottomRight="A3">
<cellValueRule operator="greaterThan" formula1="100">
<style>
<fill>
<rgb r="255" g="199" b="206" />
</fill>
</style>
</cellValueRule>
</conditionalFormat>
Chart の Series リストには、各項目の要素名(<s>)とは別に、独自のラッパー要素(<series>)が必要です。これは、XML にはあって JSON にはない実際の曖昧さを避けるためです。XML のリストには、JSON 配列のような独自の形がありません。そのため、リストの開始位置を読み取れるように、コンテナーと各項目には異なる名前が必要です。
<chart type="column" title="Sales by Quarter" showLegend="true"
anchorTopLeft="E1" anchorBottomRight="L15">
<categories topLeft="A2" bottomRight="A4" />
<series>
<s name="B1" valuesTopLeft="B2" valuesBottomRight="B4" />
<s name="C1" valuesTopLeft="C2" valuesBottomRight="C4" />
</series>
</chart>
Excel の Table では、同じラッパーと項目の分け方をより一般的な形で示しています。columns にはすでに自然な単数形(column)があるため、<s> のような回避策は必要ありません。
<table topLeft="A1" bottomRight="B4" name="Calc">
<columns>
<column name="Qty" />
<column name="Doubled" calculatedFormula="Calc[Qty]*2" />
</columns>
<style name="TableStyleLight9" showFirstColumn="true" showLastColumn="true"
showColumnStripes="true" />
</table>
SparklineGroup の Color フィールドは、独自の <color> 子要素でラップされます。これは CellStyle の font/fill と同じ規則です。
<sparklineGroup>
<style type="column" lineWeight="1.5" showNegative="true">
<color>
<rgb r="0" g="112" b="192" />
</color>
</style>
<sparklines>
<sparkline cell="E1" dataTopLeft="A1" dataBottomRight="D1" />
</sparklines>
</sparklineGroup>
PivotTable は、ここで扱う中で最もフラットな構造です。属性だけで構成され、入れ子の要素はありません。ただし、ここで保持されるのは 説明 のみです。このスキーマが扱うその他の要素とは異なり、Xml.toWorkbook で読み込んでも集計は再実行されません。
<pivotTable sourceSheet="Data" sourceTopLeft="A1" sourceBottomRight="C9"
rowField="Region" columnField="Quarter" valueField="Sales"
aggregation="average" valueCaption="Avg Sales" anchorTopLeft="F1" />
Image の生バイト列は、要素自身の base64 テキストコンテンツになります。これは後述の vbaProject と同じ規則です。
<image format="gif" topLeft="A1" bottomRight="D6">R0lGODlhAQABAIAAAAAAAP...</image>
Hyperlink の Target は、ValidationKind/ConditionalFormatRule と同じ方法でネストします。
<hyperlink topLeft="A1" bottomRight="A1" tooltip="Visit site">
<externalHyperlink>https://example.com</externalHyperlink>
</hyperlink>
<hyperlink topLeft="A2" bottomRight="B3" display="Go to top">
<internalHyperlink>Sheet1!A1</internalHyperlink>
</hyperlink>
Comment のテキストも属性ではなく要素自体のコンテンツです。空の場合、author は author="" として記述せず、省略します。
<comment cell="A1" author="Alex">Check this figure</comment>
<comment cell="A2">Unnamed author</comment>
シートとワークブックの保護は、どちらもフラットな属性の集まりです。SheetProtection/WorkbookProtection のフィールドに構造化データがないため、ネストされた要素は必要ありません。
<protection password="hunter2" sheet="true" formatCells="true" sort="true" autoFilter="true" />
<workbook>
<sheets>...</sheets>
<protection password="hunter2" lockStructure="true" />
</workbook>
PageSetup でも、混合 DU の規則を確認できます。PaperSize の名前付きケースは kind 属性になり、セルのスタイルで NumberFormat が使うのと同じエスケープハッチ形式になります。
<pageSetup orientation="landscape">
<paperSize kind="a4" />
<margins left="0.5" right="0.5" top="1" bottom="1" header="0.2" footer="0.2" />
</pageSetup>
PrintScaling の 2 つのケース、PaperSize のエスケープハッチ(名前を付けるほどではない数十種類の用紙コードに対応する other)、PrintArea の範囲リスト、ヘッダー/フッターのテキストをすべてまとめた例です。
<pageSetup orientation="portrait">
<paperSize other="9" />
<scaling fitWidth="1" fitHeight="0" />
<margins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3" />
<printArea>
<range topLeft="A1" bottomRight="D10" />
</printArea>
<header>&C&"Arial,Bold"Report</header>
<footer>&LPage &P of &N</footer>
</pageSetup>
マクロ有効ワークブックの VbaProject のバイト列は、個々のシート内ではなく、sheets と並んでワークブックレベルに配置されます。
<workbook>
<sheets>...</sheets>
<vbaProject>AQIDBA==</vbaProject>
</workbook>
DefinedNameScope の 2 つのケースは、PaperSize/NumberFormat の「kind 属性」という手法とは異なる形式です。WorkbookScope 自体はデータを持ちませんが、データを持つ SheetScope と並んで <choice> に含まれるため、属性値ではなく、それ自体が空の要素になります。
<definedNames>
<definedName name="LocalTotal" formula="Sheet1!$A$2" hidden="true">
<sheetScope sheetName="Sheet1" />
</definedName>
<definedName name="TaxRate" formula="0.075">
<workbookScope />
</definedName>
</definedNames>
範囲を表す小さなフィールド(MergedRange、FreezePane、AutoFilter、ColumnProps、RowProps)は、いずれも単純な属性の集まり、またはそのリストです。
<mergedRanges>
<mergedRange topLeft="A1" bottomRight="C1" />
</mergedRanges>
<freezePane rows="1" columns="0" />
<autoFilter topLeft="A1" bottomRight="D11" />
<columnProps>
<columnProp index="0" width="20" />
</columnProps>
<rowProps>
<rowProp index="0" height="30" />
</rowProps>
Xml.schemaSet() は、どちらの方向でも自分で検証できるように、コンパイル済みスキーマを読み込みます(XDocument.Validate)。tests/Kookerella.FsOpenXmlDsl.Tests/Examples/ にある各シナリオでは、生成と同じテストの一部として、スキーマに対して検証済みの workbook.xml がコミットされています。そのため、スキーマと Xml.fs の内容がいつの間にか食い違うことはありません。Xml.fs は、このライブラリの他の部分や C# ラッパーと同じワークシート/ワークブックレベルの機能セットをカバーしています。セル値、スタイル、結合範囲、ウィンドウ枠の固定、オートフィルター、列/行のサイズ設定、VBA(base64)、定義済みの名前、ハイパーリンク、コメント、シート/ワークブックの保護、印刷設定、画像(base64)、Excel テーブル、スパークライン、グラフ、ピボットテーブル(ここでの説明は内容のみです。他のすべての機能と異なり、読み込み時に集計は再実行されません)、条件付き書式、データの入力規則に対応しています。
Kookerella.FsOpenXmlDsl.Mcp では、F# をまったく書かずに両方向を利用できます。AI エージェント向けの MCP ツール generate_xml/create_workbook_from_xml と、その他のユーザー向けの CLI コマンド fsopenxmldsl-mcp convert --lang xml/build を提供しています。詳細は、このプロジェクト独自の README を参照してください。
JSON
Json.toWorkbook/Json.ofWorkbook(Json.fs 内)は、F#/C# を直接記述する方法、コード生成、XML に加えて、DSL との変換に使える 4 つ目の方法です。XML ではなく JSON を扱うツールを使用する呼び出し元向けに、プレーンな JSON を使います。XML に適用される次の 2 つの具体的な用途は、JSON にも当てはまります。
- 変換/生成パイプラインがすでに出力している JSON から
.xlsxを作成する - OOXML スキーマや、このライブラリ独自の API を学ぶ必要はありません。 - 既存の
.xlsxをバージョン管理用の JSON に変換する -Xml.ofWorkbookと同じ決定性がJson.ofWorkbookの出力にも適用されます(セル位置順、または定義済みの名前順にソート)。そのため、リストの並び替わりによる見せかけの差分ではなく、実際の内容の変更が小さく独立した差分になります。
open System.Text.Json.Nodes
// JSON -> Workbook -> .xlsx
let wb = JsonNode.Parse(File.ReadAllText "report.json").AsObject() |> Json.toWorkbook
Workbook.save "report.xlsx" wb
// .xlsx -> Workbook -> JSON
let json = Workbook.load "report.xlsx" |> Json.ofWorkbook
File.WriteAllText("report.json", json.ToJsonString())
判別共用体のケースが独自のデータを持つ場合は、ケース名(キャメルケース)をキーとする単一キーの JSON オブジェクトになります。パラメーターのない複数の選択肢の 1 つである場合は、単独の JSON 文字列(同じくキャメルケース)になります。たとえば、セルの値は次のようになります。
{
"ref": "B2",
"number": 42.5,
"style": { "numberFormat": "currency" }
}
上記と同じ DataValidation の例を JSON で表したものです。XML 形式では ValidationAlert のフィールドを <dataValidation> 自体にフラット化しますが、JSON では kind と alert をそれぞれ独自のオブジェクトとしてネストします。この形式では、こちらの方が自然な構造です。
{
"topLeft": "A2",
"bottomRight": "A2",
"kind": { "wholeNumberValidation": { "operator": "greaterThan", "formula1": "0" } },
"alert": {
"errorTitle": "Invalid quantity",
"errorMessage": "Quantity must be a positive whole number."
}
}
上記の ConditionalFormat の例を JSON で表したものです。rule は上記の kind と同じように、7 つのケースのうちの 1 つをネストします。また、XML の何もラップしない <fill> とは異なり、fill は常に明示的なキーの下に color をラップします。
{
"topLeft": "A1",
"bottomRight": "A3",
"rule": {
"cellValueRule": {
"operator": "greaterThan",
"formula1": "100",
"style": { "fill": { "color": { "rgb": { "r": 255, "g": 199, "b": 206 } } } }
}
}
}
上記の Chart の例を JSON で表したものです。series は通常の配列です。JSON 配列は自己区切り形式なので、XML の <series>/<s> にあるラッパー名と項目名を使い分ける仕組みは必要ありません。
{
"type": "column",
"title": "Sales by Quarter",
"showLegend": true,
"anchorTopLeft": "E1",
"anchorBottomRight": "L15",
"categories": { "topLeft": "A2", "bottomRight": "A4" },
"series": [
{ "name": "B1", "valuesTopLeft": "B2", "valuesBottomRight": "B4" },
{ "name": "C1", "valuesTopLeft": "C2", "valuesBottomRight": "C4" }
]
}
上記の Table の例を JSON で表したものです。columns は series と同様に、通常の配列です。
{
"topLeft": "A1",
"bottomRight": "B4",
"name": "Calc",
"columns": [
{ "name": "Qty" },
{ "name": "Doubled", "calculatedFormula": "Calc[Qty]*2" }
],
"style": {
"name": "TableStyleLight9",
"showFirstColumn": true,
"showLastColumn": true,
"showColumnStripes": true
}
}
上記の SparklineGroup の例を JSON で表したものです。color は、CellStyle 内の fill と同じように、スタイルのほかのフィールドと並ぶ通常のネストされたキーです。
{
"style": {
"type": "column",
"lineWeight": 1.5,
"showNegative": true,
"color": { "rgb": { "r": 0, "g": 112, "b": 192 } }
},
"sparklines": [
{ "cell": "E1", "dataTopLeft": "A1", "dataBottomRight": "D1" }
]
}
上記の PivotTable の例を JSON で表したものです。リストやネストされた構造がないため、どちらの場合もフラットなオブジェクトです。
{
"sourceSheet": "Data",
"sourceTopLeft": "A1",
"sourceBottomRight": "C9",
"rowField": "Region",
"columnField": "Quarter",
"valueField": "Sales",
"aggregation": "average",
"valueCaption": "Avg Sales",
"anchorTopLeft": "F1"
}
Image のバイト列は、下記の vbaProject と同様に、base64 文字列値です。
{ "format": "gif", "topLeft": "A1", "bottomRight": "D6", "data": "R0lGODlhAQABAIAAAAAAAP..." }
JSON で表した Hyperlink です。
{
"topLeft": "A1",
"bottomRight": "A1",
"target": { "externalHyperlink": "https://example.com" },
"tooltip": "Visit site"
}
{
"topLeft": "A2",
"bottomRight": "B3",
"target": { "internalHyperlink": "Sheet1!A1" },
"display": "Go to top"
}
Comment です。author は通常の省略可能なフィールドで、空文字列ではなく省略されます。
{ "cell": "A1", "author": "Alex", "text": "Check this figure" }
{ "cell": "A2", "text": "Unnamed author" }
JSON で表したシートとブックの保護です。XML と同じく、フラットなオブジェクトです。
{ "password": "hunter2", "sheet": true, "formatCells": true, "sort": true, "autoFilter": true }
{ "sheets": [ { "name": "Sheet1" } ], "protection": { "password": "hunter2", "lockStructure": true } }
PageSetup です。PaperSize の名前付きケースは文字列そのもので、混在 DU 規約を使う NumberFormat と同様です。
{
"orientation": "landscape",
"paperSize": "a4",
"margins": { "left": 0.5, "right": 0.5, "top": 1, "bottom": 1, "header": 0.2, "footer": 0.2 }
}
上記の、より豊富な例を JSON で表したものです。PaperSize の拡張用ケースは {"other": 9} です。PrintScaling の 2 つのケースは、ほかのすべてのケースと同様に単一キーのオブジェクトです。また、printArea は通常の配列です。
{
"orientation": "portrait",
"paperSize": { "other": 9 },
"scaling": { "fitToPage": { "width": 1, "height": 0 } },
"margins": { "left": 0.7, "right": 0.7, "top": 0.75, "bottom": 0.75, "header": 0.3, "footer": 0.3 },
"printArea": [ { "topLeft": "A1", "bottomRight": "D10" } ],
"header": "&C&\"Arial,Bold\"Report",
"footer": "&LPage &P of &N"
}
知っておくと便利です:System.Text.Json の既定のエンコーダーは、文字列値内の &、<、>、" をすべて \uXXXX シーケンスとしてエスケープします(HTML に埋め込まれる可能性のある JSON に対する保守的な選択です)。そのため、上記のヘッダーについて generate_json が実際に出力する内容では、読みやすさのためにここで示したようなプレーンテキストではなく、これらの文字がすべてその形式に置き換えられます。これは見た目の違いであり、データ損失のバグではありません(この例をそのまま往復変換して確認済みで、Json.toWorkbook はエスケープ形式から元の文字列を正確に復元します)。ただし、generate_json の結果が破損していると思い込む前に知っておくと便利です。特に、Excel のヘッダー/フッターコード(&C/&L/&R/&P/&N/...)はすべて & で始まるため、実際のヘッダー/フッターのテキストは必ずこの形式で出力されます。
ブックレベルの VbaProject です。
{ "sheets": [ { "name": "Sheet1" } ], "vbaProject": "AQIDBA==" }
DefinedNameScope の 2 つのケースは、XML の <workbookScope />/<sheetScope> の使い分けとは異なり、標準的な JSON 規約に沿っています。WorkbookScope は単なる文字列で、パラメーターのないほかのケースと同じ扱いです。
{
"definedNames": [
{ "name": "LocalTotal", "formula": "Sheet1!$A$2", "scope": { "sheetScope": "Sheet1" }, "hidden": true },
{ "name": "TaxRate", "formula": "0.075", "scope": "workbookScope" }
]
}
JSON で表した、範囲形式の小さなフィールドです。
{
"mergedRanges": [ { "topLeft": "A1", "bottomRight": "C1" } ],
"freezePane": { "rows": 1, "columns": 0 },
"autoFilter": { "topLeft": "A1", "bottomRight": "D11" },
"columnProps": [ { "index": 0, "width": 20 } ],
"rowProps": [ { "index": 0, "height": 30 } ]
}
XML と異なり、.NET には XML 用の System.Xml.Schema のような組み込みの JSON Schema バリデーターがありません。そのため、Json.schema.json(リポジトリ内にあり、この形式に一致します)は、公開の Json.schemaSet() のような API として提供されるのではなく、テスト専用の JsonSchema.Net 依存関係を使って、このリポジトリ独自のテストスイートからのみ検証されます。tests/Kookerella.FsOpenXmlDsl.Tests/Examples/ 配下のすべてのシナリオにはコミット済みの workbook.json があり、workbook.xml が Xml.xsd に対して検証されるのと同様に、この方法で検証されます。そのため、スキーマと Json.fs が互いに気付かれないまま乖離することもありません。Json.fs は Xml.fs と同じワークシート/ブックレベルの機能をカバーします。セル値、スタイル、結合範囲、ウィンドウ枠の固定、オートフィルター、列/行のサイズ設定、VBA(base64)、定義済みの名前、ハイパーリンク、コメント、シート/ブックの保護、印刷設定、画像(base64)、Excel テーブル、スパークライン、チャート、ピボットテーブル(ここで説明しているのは説明情報のみで、ほかのすべての機能と異なり、読み込み時に集計が再実行されることはありません)、条件付き書式、データの入力規則です。
Kookerella.FsOpenXmlDsl.Mcp は、F# を一切書かずに両方向の操作を提供します。AI エージェント向けの generate_json/create_workbook_from_json MCP ツールと、それ以外の方向けの fsopenxmldsl-mcp convert --lang json/build CLI コマンドです。詳しくは、このプロジェクト独自の README を参照してください。
ビルドとテスト
dotnet build
dotnet test --filter "Category!=Slow"
dotnet run --project samples/Kookerella.FsOpenXmlDsl.Sample
上記のデフォルトのループでは、時間のかかる Category=Slow テストはスキップされます。このテストでは、生成されたすべての Examples/*/script.fsx に対して実際に dotnet fsi を呼び出します(プロセスの起動に毎回数秒かかるため、合計で約30~60秒かかります)。生成されたソースの構文解析を確認するだけではありません。まず高速なテストスイートを実行して .fsx ファイルを一度以上生成してから、これらのテストを明示的に実行してください。
dotnet test --filter "Category=Slow"
フィルターなしの dotnet test では、両方のグループが実行されます。
スポンサーシップ
このプロジェクトがお役に立った場合は、スポンサーになっていただくことで、継続的な開発を支援できます。スポンサーシップはプロジェクトを支援するものであり、サポートの SLA、機能の保証、優先対応時間は含まれません。このソフトウェアは、スポンサーシップの有無にかかわらず、MIT ライセンスのもとで現状のまま提供されます。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。