2
1

More than 1 year has passed since last update.

OuDia形式ファイルのパース

Last updated at Posted at 2020-09-11

OuDia形式とは

鉄道やバスの時刻表を記述するファイルフォーマット。主にOuDia系のソフトウェアで使用されている。

OuDia形式の構文

OuDia形式のファイルは文字列で記述されているので、テキストエディタ等で開くことができる。
OuDia形式のファイルは以下の特徴を持つ。

  • ドット.から次のドット.までを1階層とした、階層構造になっている。
  • ドット.による階層構造のほかに、key=value形式でプロパティが定義できる。
  • プロパティ(=の左側)は複数回登場することがある。

構文の一例
oudiaの構文例
FileType=OuDia.1.02
Rosen.
Rosenmei=〇〇線
Eki.
Ekimei=A
Ekijikokukeisiki=Jikokukeisiki_NoboriChaku
Ekikibo=Ekikibo_Ippan
.
Eki.
Ekimei=B
Ekijikokukeisiki=Jikokukeisiki_Hatsu
Ekikibo=Ekikibo_Ippan
.
Ressyasyubetsu.
Syubetsumei=普通
JikokuhyouMojiColor=00000000
.
Dia.
DiaName=diaName
Kudari.
Ressya.
Houkou=Kudari
Syubetsu=0
EkiJikoku=1,1;001,1;010/
.
.
Nobori.
Ressya.
Houkou=Nobori
Syubetsu=0
EkiJikoku=1;001/,2,1;300/300
.
.
.
.
DispProp.
JikokuhyouFont=PointTextHeight=9;Facename=MS ゴシック
JikokuhyouFont=PointTextHeight=9;Facename=MS ゴシック;Bold=1
JikokuhyouFont=PointTextHeight=9;Facename=MS ゴシック;Itaric=1
JikokuhyouFont=PointTextHeight=9;Facename=MS ゴシック;Bold=1;Itaric=1
.
FileTypeAppComment=OuDia Ver. 1.02.05

これだけだと分かりにくいので、インデントを付けて見やすくしてみる。

oudiaの構文例
FileType=OuDia.1.02
Rosen.
 ├ Rosenmei=〇〇線
 ├ Eki.
 │  ├ Ekimei=A
 │  ├ Ekijikokukeisiki=Jikokukeisiki_NoboriChaku
 │  ├ Ekikibo=Ekikibo_Ippan
 ├ .
 ├ Eki.
 │  ├ Ekimei=B
 │  ├ Ekijikokukeisiki=Jikokukeisiki_Hatsu
 │  ├ Ekikibo=Ekikibo_Ippan
 ├ .
 ├ Ressyasyubetsu.
 │  ├ Syubetsumei=普通
 │  ├ JikokuhyouMojiColor=00000000
 ├ .
 ├ Dia.
 │  ├ DiaName=diaName
 │  ├ Kudari.
 │  │  ├ Ressya.
 │  │  │  ├ Houkou=Kudari
 │  │  │  ├ Syubetsu=0
 │  │  │  ├ EkiJikoku=1,1;001,1;010/
 │  │  ├ .
 │  ├ .
 │  ├ Nobori.
 │  │  ├ Ressya.
 │  │  │  ├ Houkou=Nobori
 │  │  │  ├ Syubetsu=0
 │  │  │  ├ EkiJikoku=1;001/,2,1;300/300
 │  │  ├ .
 │  ├ .
 ├ .
.
DispProp.
 ├ JikokuhyouFont=PointTextHeight=9;Facename=MS ゴシック
 ├ JikokuhyouFont=PointTextHeight=9;Facename=MS ゴシック;Bold=1
 ├ JikokuhyouFont=PointTextHeight=9;Facename=MS ゴシック;Itaric=1
 ├ JikokuhyouFont=PointTextHeight=9;Facename=MS ゴシック;Bold=1;Itaric=1
.
FileTypeAppComment=OuDia Ver. 1.02.05

OuDia形式のパース/文字列化

OuDia形式のデータはただのテキストなので、そのままだとプログラムで扱いにくい。
JSON.parse/stringifyよろしく、OuDia.parse/stringifyを自作して、ObjectとArrayの組み合わせに変換してみる。

方針

  • 基本はObjectの入れ子
  • 同じプロパティ名が複数回登場する可能性があるので、値は配列で保持する。
  • key=value形式のプロパティはkeyの先頭に$$を追加して、.区切りの階層名と区別する

実装

const OuDia = {
    parse (text) {
        let current = {}
        for (const lineRaw of text.split(/\r\n|\n/)) {
            const line = lineRaw.trim()
            if (line==='') {
                continue
            } else if (line==='.') {
                const parent = current.parent
                delete current.parent
                current = parent
            } else if (line.endsWith('.')) {
                const type = line.slice(0, -1)
                const newCurrent = {parent: current}
                if (!current[type]) {current[type] = []}
                current[type].push(newCurrent)
                current = newCurrent
            } else if (line.includes('=')) {
                let [key, ...value] = line.split('=')
                key = `$$${key}`
                value = value.join('=')
                if (value.includes('=')) {
                    value = Object.fromEntries(value.split(';').map(v=>v.split('=').map(v=>v.trim())))
                }
                if (!current[key]) {current[key] = []}
                current[key].push(value)
            }
        }
        return current
    },
    stringify (obj, isReturnText=true) {
        const isObject = obj => Object.getPrototypeOf(JSON.parse(JSON.stringify(obj)))===Object.prototype
        let result = []
        for (const [key, values] of Object.entries(obj)) {
            for (let value of values) {
                if (value==null) {continue}
                if (key.startsWith('$$')) {
                    if (isObject(value)) {
                        value = Object.entries(value).map(v=>v.join('=')).join(';')
                    }
                    result.push(`${key.slice(2)}=${value}`)
                } else {
                    result.push(`${key}.`, ...this.stringify(value, false), '.')
                }
            }
        }
        if (isReturnText) {
            return result.join('\n')
        } else {
            return result
        }
    }
}

コメント書いてません、すいません。

使用例

const oudData = OuDia.parse(oudText) //=> {$$FileType: ["OuDiaSecond.1.09"], $$FileTypeAppComment: ["OuDiaSecondV2 Ver. 2.04.04"], DispProp: [{…}], Rosen: [{…}]}
const oudText = OuDia.stringify(oudData) //=> FileType=OuDiaSecond.1.09\nRosen.\nRosenmei=...
console.log(oudData.Rosen[0].$$Rosenmei[0]) //=> 〇〇線

値が配列に入っているでので、\$\$Rosenmei[0]のように毎回[0]で取りに行く必要がある。

OuDia.stringify()内でObjectとStringを判別する必要があったので、自作のisObject関数を作成した。

const isObject = obj => Object.getPrototypeOf(JSON.parse(JSON.stringify(obj)))===Object.prototype

自作クラスなどが渡された時の挙動が不安だったので、一旦JSONを経由することで簡易的に型チェックしている。

2
1
1

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
2
1