CSVOとYAML

前回の記事(CSVOの表現力 - はじめてのフォーマット)の最後のほうで 行/列を入れ替えたCSVOデータを貼りましたが、YAMLに似ていると感じませんでしたか?

再掲 f:id:xunxiu:20200515152252p:plain

CSVOはもともと、VBAで業務ツールを作っていたときに、 設定情報をYAMLのような形式でワークシート上で管理できないか、 と考えたところから始まりました。

CSVを拡張するというアイデアはそのあとに生まれたのです。

では、下のYAMLデータをCSVO形式に変換してみましょう。 (データはYAML Ain’t Markup Language (YAML™) Version 1.2のExample 2.27.を改変したもの)

invoice: 34843
date   : 2001-01-23
bill-to:
    given  : Chris
    family : Dumars
    address:
        lines:
            - 458 Walkman Dr.
            - Suite #292
        city    : Royal Oak
        state   : MI
        postal  : 48046
product:
    - sku         : BL394D
      quantity    : 4
      description : Basketball
      price       : 450.00
    - sku         : BL4438H
      quantity    : 1
      description : Super Hoop
      price       : 2392.00
tax  : 251.42
total: 4443.52
comments:
    - Late afternoon is best.
    - Backup contact is Nancy
    - Billsmer @ 338-4338.

スペースをタブにして貼り付けます。

f:id:xunxiu:20200518124050p:plain

値を一列にまとめます。

f:id:xunxiu:20200518124602p:plain

ネスト時の改行を無くして リストのマークを*に変えて完成です。

f:id:xunxiu:20200518130236p:plain

行/列を入れ替えたものと 出力したテキストデータも貼っておきましょう。

f:id:xunxiu:20200518130720p:plain

invoice:,date:,bill-to:,,,,,,,product:,,,,,,,,tax:,total:,comments:,,
,,given:,family:,address:,,,,,*,,,,*,,,,,,*,*,*
,,,,lines:,,city:,state:,postal:,sku:,quantity:,description:,price:,sku:,quantity:,description:,price:,,,,,
,,,,*,*,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,
34843,2001-01-23,Chris,Dumars,458 Walkman Dr.,Suite #292,Royal Oak,MI,48046,BL394D,4,Basketball,450,BL4438H,1,Super Hoop,2392,251.42,4443.52,Late afternoon is best.,Backup contact is Nancy,Billsmer @ 338-4338.

CSVOの表現力

CSVOにはどのくらいの表現力があるのでしょうか。

JSONの公式サイトにあるJSONの例をCSVO形式に変換してみましょう。

例1
{
    "glossary": {
        "title": "example glossary",
        "GlossDiv": {
            "title": "S",
            "GlossList": {
                "GlossEntry": {
                    "ID": "SGML",
                    "SortAs": "SGML",
                    "GlossTerm": "Standard Generalized Markup Language",
                    "Acronym": "SGML",
                    "Abbrev": "ISO 8879:1986",
                    "GlossDef": {
                        "para": "A meta-markup language, used to create markup languages such as DocBook.",
                        "GlossSeeAlso": ["GML", "XML"]
                    },
                    "GlossSee": "markup"
                }
            }
        }
    }
}

CSVO形式に変換してみましょう(CSVOはレコードのリストを表現するデータ形式なので、例1のJSONデータを1つ目のレコードとして扱った場合の結果となります)。

glossary,,,,,,,,,,
title,GlossDiv,,,,,,,,,
,title,GlossList,,,,,,,,
,,GlossEntry,,,,,,,,
,,ID,SortAs,GlossTerm,Acronym,Abbrev,GlossDef,,,GlossSee
,,,,,,,para,GlossSeeAlso,,
,,,,,,,,*,*,
,,,,,,,,,,
example glossary,S,SGML,SGML,Standard Generalized Markup Language,SGML,ISO 8879:1986,"A meta-markup language, used to create markup languages such as DocBook.",GML,XML,markup

見にくいですね…。

エクセルで見やすくしてみましょう。(参考:エクセルでCSVOを見やすくする - はじめてのフォーマット

f:id:xunxiu:20200515140107p:plain

ほかの例も見ていきましょう。

例2
{"menu": {
  "id": "file",
  "value": "File",
  "popup": {
    "menuitem": [
      {"value": "New", "onclick": "CreateNewDoc()"},
      {"value": "Open", "onclick": "OpenDoc()"},
      {"value": "Close", "onclick": "CloseDoc()"}
    ]
  }
}}

CSVO形式

f:id:xunxiu:20200515141552p:plain

例3
{"widget": {
    "debug": "on",
    "window": {
        "title": "Sample Konfabulator Widget",
        "name": "main_window",
        "width": 500,
        "height": 500
    },
    "image": { 
        "src": "Images/Sun.png",
        "name": "sun1",
        "hOffset": 250,
        "vOffset": 250,
        "alignment": "center"
    },
    "text": {
        "data": "Click Here",
        "size": 36,
        "style": "bold",
        "name": "text1",
        "hOffset": 250,
        "vOffset": 100,
        "alignment": "center",
        "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
    }
}}  

CSVO形式

f:id:xunxiu:20200515142316p:plain

例4
{"web-app": {
  "servlet": [   
    {
      "servlet-name": "cofaxCDS",
      "servlet-class": "org.cofax.cds.CDSServlet",
      "init-param": {
        "configGlossary:installationAt": "Philadelphia, PA",
        "configGlossary:adminEmail": "ksm@pobox.com",
        "configGlossary:poweredBy": "Cofax",
        "configGlossary:poweredByIcon": "/images/cofax.gif",
        "configGlossary:staticPath": "/content/static",
        "templateProcessorClass": "org.cofax.WysiwygTemplate",
        "templateLoaderClass": "org.cofax.FilesTemplateLoader",
        "templatePath": "templates",
        "templateOverridePath": "",
        "defaultListTemplate": "listTemplate.htm",
        "defaultFileTemplate": "articleTemplate.htm",
        "useJSP": false,
        "jspListTemplate": "listTemplate.jsp",
        "jspFileTemplate": "articleTemplate.jsp",
        "cachePackageTagsTrack": 200,
        "cachePackageTagsStore": 200,
        "cachePackageTagsRefresh": 60,
        "cacheTemplatesTrack": 100,
        "cacheTemplatesStore": 50,
        "cacheTemplatesRefresh": 15,
        "cachePagesTrack": 200,
        "cachePagesStore": 100,
        "cachePagesRefresh": 10,
        "cachePagesDirtyRead": 10,
        "searchEngineListTemplate": "forSearchEnginesList.htm",
        "searchEngineFileTemplate": "forSearchEngines.htm",
        "searchEngineRobotsDb": "WEB-INF/robots.db",
        "useDataStore": true,
        "dataStoreClass": "org.cofax.SqlDataStore",
        "redirectionClass": "org.cofax.SqlRedirection",
        "dataStoreName": "cofax",
        "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver",
        "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon",
        "dataStoreUser": "sa",
        "dataStorePassword": "dataStoreTestQuery",
        "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';",
        "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log",
        "dataStoreInitConns": 10,
        "dataStoreMaxConns": 100,
        "dataStoreConnUsageLimit": 100,
        "dataStoreLogLevel": "debug",
        "maxUrlLength": 500}},
    {
      "servlet-name": "cofaxEmail",
      "servlet-class": "org.cofax.cds.EmailServlet",
      "init-param": {
      "mailHost": "mail1",
      "mailHostOverride": "mail2"}},
    {
      "servlet-name": "cofaxAdmin",
      "servlet-class": "org.cofax.cds.AdminServlet"},
 
    {
      "servlet-name": "fileServlet",
      "servlet-class": "org.cofax.cds.FileServlet"},
    {
      "servlet-name": "cofaxTools",
      "servlet-class": "org.cofax.cms.CofaxToolsServlet",
      "init-param": {
        "templatePath": "toolstemplates/",
        "log": 1,
        "logLocation": "/usr/local/tomcat/logs/CofaxTools.log",
        "logMaxSize": "",
        "dataLog": 1,
        "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log",
        "dataLogMaxSize": "",
        "removePageCache": "/content/admin/remove?cache=pages&id=",
        "removeTemplateCache": "/content/admin/remove?cache=templates&id=",
        "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder",
        "lookInContext": 1,
        "adminGroupID": 4,
        "betaServer": true}}],
  "servlet-mapping": {
    "cofaxCDS": "/",
    "cofaxEmail": "/cofaxutil/aemail/*",
    "cofaxAdmin": "/admin/*",
    "fileServlet": "/static/*",
    "cofaxTools": "/tools/*"},
 
  "taglib": {
    "taglib-uri": "cofax.tld",
    "taglib-location": "/WEB-INF/tlds/cofax.tld"}}}

CSVO形式

f:id:xunxiu:20200515144305p:plain

見えない…。

行/列を入れ替えてみましょう。

f:id:xunxiu:20200515150801p:plain

見やすくなりました。

例5
{"menu": {
    "header": "SVG Viewer",
    "items": [
        {"id": "Open"},
        {"id": "OpenNew", "label": "Open New"},
        null,
        {"id": "ZoomIn", "label": "Zoom In"},
        {"id": "ZoomOut", "label": "Zoom Out"},
        {"id": "OriginalView", "label": "Original View"},
        null,
        {"id": "Quality"},
        {"id": "Pause"},
        {"id": "Mute"},
        null,
        {"id": "Find", "label": "Find..."},
        {"id": "FindAgain", "label": "Find Again"},
        {"id": "Copy"},
        {"id": "CopyAgain", "label": "Copy Again"},
        {"id": "CopySVG", "label": "Copy SVG"},
        {"id": "ViewSVG", "label": "View SVG"},
        {"id": "ViewSource", "label": "View Source"},
        {"id": "SaveAs", "label": "Save As"},
        null,
        {"id": "Help"},
        {"id": "About", "label": "About Adobe CVG Viewer..."}
    ]
}}

CSVO形式

f:id:xunxiu:20200515151801p:plain

こちらも行/列を入れ替えてみましょう。

f:id:xunxiu:20200515152252p:plain

さまざまなJSONデータをCSVO形式に変換してみました。

エクセルでCSVOを見やすくする

こちらで作ったデータを見やすくしてみましょう。

このようなデータです。

K1,,,K2,,,
K1-1,,K1-2,*,,*,
K1-1-1,K1-1-2,,K2-1,K2-2,K2-1,K2-2
,,,,,,
R1V1,R1V2,R1V3,R1V4,R1V5,R1V6,R1V7
R2V1,R2V2,R2V3,R2V4,R2V5,R2V6,R2V7

エクセルで開きます。 f:id:xunxiu:20200510133832p:plain

項目から次の項目の手前のセルまで選択し、 f:id:xunxiu:20200510133847p:plain

結合します(結合が好きではない場合は文字の横位置を「選択範囲内で中央」にしましょう)。 f:id:xunxiu:20200510133918p:plain

他の項目や下の階層の項目も結合しましょう。 f:id:xunxiu:20200510133934p:plain

縦方向も結合してしまいましょう(結合が好きではない場合は、文字の縦位置には「選択範囲内で中央」がないので、そのままにしておきましょう)。 f:id:xunxiu:20200510134013p:plain

出来ました。 データの構造がわかりやすくなりましたね。 f:id:xunxiu:20200510134037p:plain

そのままCSV形式で保存すると整形前とまったく同じデータが出力されます。

K1,,,K2,,,
K1-1,,K1-2,*,,*,
K1-1-1,K1-1-2,,K2-1,K2-2,K2-1,K2-2
,,,,,,
R1V1,R1V2,R1V3,R1V4,R1V5,R1V6,R1V7
R2V1,R2V2,R2V3,R2V4,R2V5,R2V6,R2V7

エクセルでCSVOデータを作る

手入力でCSVOのデータを作るならエクセルを使うとかんたんです。

CSVOの書き方はこちらを見てください。

エクセルでデータを入力します。

f:id:xunxiu:20200503183846p:plain

ファイルの種類をCSVにして保存します。

f:id:xunxiu:20200503184013p:plain

保存したファイルをメモ帳などで開いてみましょう。

K1,,,K2,,,
K1-1,,K1-2,*,,*,
K1-1-1,K1-1-2,,K2-1,K2-2,K2-1,K2-2
,,,,,,
R1V1,R1V2,R1V3,R1V4,R1V5,R1V6,R1V7
R2V1,R2V2,R2V3,R2V4,R2V5,R2V6,R2V7

出来ましたね。

ネストしたデータも表現できるCSV

一般的なCSVはネストしたデータをうまく表現できません。

そこでネストしたデータを自然に表現できるようにCSVを拡張したデータ形式を考えました。

このデータ形式をCSVO(CSV for Object)と仮に名付けます。

概要を見ていきましょう。

ヘッダ

CSVではヘッダは必須ではありません。

ヘッダがある場合は1行目がヘッダであり、2行目からデータがはじまります。

こんな感じですね。
(※以降のサンプルでは見やすいように適宜空白を入れています。)

K1,   K2,   K3
R1V1, R1V2, R1V3
R2V1, R2V2, R2V3

1行目はヘッダ行です。

このデータをJSONで表すと以下のようになります。

[
    {"K1": "R1V1", "K2": "R1V2", "K3": "R1V3"},
    {"K1": "R2V1", "K2": "R2V2", "K3": "R2V3"}
]

このデータをCSVOでは以下のように書きます。

K1,   K2,   K3

R1V1, R1V2, R1V3
R2V1, R2V2, R2V3

CSVOではヘッダとデータの間には空行をはさみます。

あるいはカンマなどの区切り文字は残っていてもよいでしょう。

K1,   K2,   K3
,,
R1V1, R1V2, R1V3
R2V1, R2V2, R2V3

そしてヘッダは複数行になることができます。

そのことでネストが表現できるようになります。

ネストの表現

こんなデータから始めましょう。

K1, K2

V1, V2

JSONで表すとこうですね。
(以降ではすべてのサンプルにそのデータのJSON表現をつけます。)

[
    {
        "K1": "V1",
        "K2": "V2"
    }
]

K2をネストさせてみましょう。

ネストが始まったらヘッダ行を増やして次の行に移動します。

K1, K2  ,
,   K2-1, K2-2

V1, V2-1, V2-2
[
    {
        "K1": "V1",
        "K2": {
            "K2-1": "V2-1",
            "K2-2": "V2-2",
        }
    }
]

ネストが終わったら元の行に戻ります。

K1, K2  ,     , K3 
  , K2-1, K2-2,

V1, V2-1, V2-2, V3
[
    {
        "K1": "V1",
        "K2": {
            "K2-1": "V2-1",
            "K2-2": "V2-2",
        },
        "K3": "V3"
    }
]

配列の表現

配列も表現できるようにしましょう。

配列は「*」をキーの代わりに使います。

*,  *,  *

V1, V2, V3
[
    ["V1", "V2", "V3"]
]

こちらもネストできます。

K1  ,     ,     ,
*   ,     , *   ,
K1-1, K1-2, K1-1, K1-2

V1  , V2  , V3  , V4
[
    {
        "K1": [
            {"K1-1": "V1", "K1-2": "V2"},
            {"K1-1": "V3", "K1-2": "V4"}
        ]
    }
]

これでいろんなデータが表現できますね。