LoginSignup
3
4

More than 5 years have passed since last update.

XMLスプレッドシートの読み込み

Last updated at Posted at 2016-06-30

ExcelではCSVとXLSXの中間的なファイルフォーマットとしてXMLスプレッドシートがサポートされています。これをF#で読み取ってみます。

この記事の方法の使用例です。

XMLスプレッドシート

スプレッドシートをデータソースとして、専用ライブラリ(ClosedXMLなど)を使わないで読み込むことを考えます。Excelでよく使われるファイル形式を検討します。

  • CSV: 改行やエスケープを考慮しないケースでは処理が簡単ですが、装飾を加えることはできません。
  • XLS: 複雑なバイナリ形式のためパースが困難です。
  • XLSX: ZIPの中にXMLが入っています。XMLだけ処理する分にはあまり難しくはありませんが、ZIPから展開する手間があります。

XLSXのうちセルのデータが格納されているXMLだけを単独で抜き出せば、扱いやすさでは理想的に思えます。それに近いファイル形式がXMLスプレッドシートです。

※ スキーマ(XMLタグなど)はXLSXとは異なります。あくまでイメージです。

XMLスプレッドーシートにはオートシェイプが格納できないなどの制限がありますが、今回はオートシェイプは扱わないため不問とします。

サンプル

次のようなデータがあったとします。

社名 会長 社長 副社長 専務
A社 山田 佐藤 鈴木 小林
B社 伊藤 池田
C社 山本 高橋

これをExcelで「XML スプレッドシート 2003」形式を指定して保存します。そのままだと複雑なので、プロパティなどを削って単純化したものを示します。

Book1.xml
<?xml version="1.0"?>
<?mso-application progid="Excel.Sheet"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
 xmlns:o="urn:schemas-microsoft-com:office:office"
 xmlns:x="urn:schemas-microsoft-com:office:excel"
 xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
 xmlns:html="http://www.w3.org/TR/REC-html40">
 <Worksheet ss:Name="Sheet1">
  <Table>
   <Row>
    <Cell><Data ss:Type="String">社名</Data></Cell>
    <Cell><Data ss:Type="String">会長</Data></Cell>
    <Cell><Data ss:Type="String">社長</Data></Cell>
    <Cell><Data ss:Type="String">副社長</Data></Cell>
    <Cell><Data ss:Type="String">専務</Data></Cell>
   </Row>
   <Row>
    <Cell><Data ss:Type="String">A社</Data></Cell>
    <Cell><Data ss:Type="String">山田</Data></Cell>
    <Cell><Data ss:Type="String">佐藤</Data></Cell>
    <Cell><Data ss:Type="String">鈴木</Data></Cell>
    <Cell><Data ss:Type="String">小林</Data></Cell>
   </Row>
   <Row>
    <Cell><Data ss:Type="String">B社</Data></Cell>
    <Cell ss:Index="3"><Data ss:Type="String">伊藤</Data></Cell>
    <Cell ss:Index="5"><Data ss:Type="String">池田</Data></Cell>
   </Row>
   <Row>
    <Cell><Data ss:Type="String">C社</Data></Cell>
    <Cell><Data ss:Type="String">山本</Data></Cell>
    <Cell><Data ss:Type="String">高橋</Data></Cell>
   </Row>
  </Table>
 </Worksheet>
</Workbook>

実際のパースのときには不要なタグは無視すれば良いので、単純化したものを想定してプログラムを組んでも問題ありません。

パース

XmlReaderで読み込みます。XmlReaderはプル型パーサーで、特徴については以下の記事を参照してください。

必要なタグを頭出しして、閉じられるまで内部のタグを繰り返し読みます。<Cell>タグでは空白セルをスキップするためss:Indexが指定されていることに注意します。要素をforで処理するため、シーケンスを返します。

XmlSpreadsheetReader.fsx
#r "System.Xml"

open System
open System.IO
open System.Text
open System.Xml

let startElement (xr:XmlReader) (n:string) =
    xr.NodeType = XmlNodeType.Element && xr.Name = n

let endElement (xr:XmlReader) (n:string) =
    xr.NodeType = XmlNodeType.EndElement && xr.Name = n

let each (xr:XmlReader) (n:string) = seq {
    let parent = xr.Name
    while xr.Read() && not (endElement xr parent) do
        if startElement xr n then yield xr }

let find (xr:XmlReader) (n:string) =
    each xr n |> Seq.isEmpty |> not

let openXml (xml:string) =
    let fs = new FileStream(xml, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
    let sr = new StreamReader(fs, Encoding.UTF8)
    let xr = new XmlTextReader(sr)
    find xr "Workbook" |> ignore
    xr

let cells xr = seq {
    let col = ref 1
    for _ in each xr "Cell" do
        let i = xr.GetAttribute "ss:Index"
        if i <> null then
            let i' = Convert.ToInt32 i
            while !col < i' do
                yield ""
                col := !col + 1
        yield if find xr "Data" then xr.ReadString() else ""
        col := !col + 1 }

let rows xr = seq {
    for _ in each xr "Row" do
        yield cells xr |> Seq.toList }

let worksheets xr = seq {
    for _ in each xr "Worksheet" do
        yield xr.GetAttribute "ss:Name" }

これを使ってサンプルを読み込みます。

Test.fsx
#load "XmlSpreadsheetReader.fsx"

open XmlSpreadsheetReader

[<EntryPoint>] do
use xr = openXml "Book1.xml"
for sheet in worksheets xr do
    printfn "%s" sheet
    for row in rows xr do
        printfn "%A" row
実行結果
Sheet1
["社名"; "会長"; "社長"; "副社長"; "専務"]
["A社"; "山田"; "佐藤"; "鈴木"; "小林"]
["B社"; ""; "伊藤"; ""; "池田"]
["C社"; "山本"; "高橋"]

セルの幅を修正したり、色や罫線を加えたりしても、データはそのまま読めます。印刷用に整形を施した状態でデータを保守できるというのがミソです。

3
4
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
3
4