dorapon2000’s diary

忘備録的な。セキュリティとかネットワークすきです。

BoostNoteからMarkdownを抽出する

タイトルのとおりです。 最近Typoraというマークダウンエディタを発見しまして、非常に使い勝手がいいので、今まで使っていたBoostNoteからそちらに移行しました。 その際に、BoosteNoteのデータを全部マークダウンとしてエクスポートしたいのですが、1個づつでしかできません。 本来BoostNoteからしかデータへはアクセスできず、ディスク上に保存されるファイルは乱数のような名前です。 中身もマークダウンではなくcsonと呼ばれるJSONCoffeeScriptが合体したようなデータでした。

ということで、マークダウンを抽出するスクリプトを書きました。


使い方

コードgithub上に置いてありますのでcloneしていただくか、コピペして依存モジュールを手動でインストールしてください。

git clone git@github.com:dorapon2000/boostnote2markdown.git
pip install -r requirements.txt
python boostnote_to_markdown.py

フォルダ選択のダイアログが開くので、BoostNoteのワーキングディレクトリを指定してください。ワーキングディレクトリ内にmarkdownというディレクトリができて、その中にフォルダ構造を維持したままマークダウンがエクスポートできています。

コード

github.com

github上にコードがあります。

BoostNoteは./boostenote.jsonにフォルダ情報、./notes/*.csonにノートが保存されています。*.csonには親フォルダ名とデータがcson形式で保存されています。初めて聞いた形式だったのですが、以下のサイトに簡単な説明があります。

blog.kazu69.net

csonを読むモジュールはないかなと探したら、pycsonというドンピシャなものがあったのでこちらで読み取りました。使い方もjsonモジュールとほぼ同じで助かりました。

*.csonからデータ部分だけ取り出したら、BoostNote上のフォルダ構造を再現しながらマークダウンファイルに変換するだけです。 *.csonにかかれている親フォルダ情報(088f99dbeb2ee050cc8bみたいな)は記号なので、boostenote.jsonから記号に対応するフォルダ名を取得します。

注意点

作成するマークダウンの名前はBoostNote上のタイトルとなりますが、特殊記号が使われている場合適当な文字に変換しています。

特殊文字というのは「 \/:,*?<>|」で、Windowsでファイル名に使えないものです。 私の場合、論文名をそのままタイトルにしたり日付を入れたものがあるので、「:」や「/」が引っかかりました。 記号は[colon]や[slash]というふうに置換して、とりあえずマークダウンは作成してもらうようにしています。 削除するなり適宜あとで直してください。

import os
import re
import json
import cson
import tkinter
import tkinter.filedialog


def convert_to_name(dir_key, conf):
    """Convert from directory key  to directory name
    :param  str dir_key:    key in boostnote.json/folders/key
    :param  dict    conf:   dictionary converted from boostnote.json
    :rtype: str
    :return: directory name
    """

    return [meta['name'] for meta in conf['folders'] if meta['key'] == dir_key]


def sanitize(str_):
    """Sanitize string for Windows
    target: \\/:,*?<>|
    :param  str str_:   string you want to sanitize
    :rtype: string
    :return:    sanitized string
    """

    if re.search('[\\/:,*?<>|]', str_) is None:
        return str_

    str_ = str_.replace('\\', '[backslash]')
    str_ = str_.replace('/', '[spash]')
    str_ = str_.replace(':', '[colon]')
    str_ = str_.replace(',', '[hyphen]')
    str_ = str_.replace('*', '[star]')
    str_ = str_.replace('?', '[question]')
    str_ = str_.replace('<', '[less]')
    str_ = str_.replace('>', '[greater]')

    print(f'"{str_}" is sanitized')

    return str_


def extract_md_from_BoostNote():
    """Extract Markdown from BoostNote
    """

    cnt_success = 0
    cnt_skip = 0

    root = tkinter.Tk()
    root.withdraw()
    msg = 'Select your BoostNote working directory'
    boostnote = tkinter.filedialog.askdirectory(title=msg)
    conf_json = os.path.join(boostnote, 'boostnote.json')
    with open(conf_json) as f:
        conf = json.load(f)

    notes = os.path.join(boostnote, 'notes')
    for file in os.listdir(notes):
        with open(os.path.join(notes, file)) as f:
            note = cson.load(f)

            if note['type'] != 'MARKDOWN_NOTE':
                cnt_skip += 1
                continue

            key = note['folder']
            folder = convert_to_name(key, conf)
            title = note['title']
            content = note['content']

            if note['isTrashed']:
                folder = 'Trash'

        folder = sanitize(folder)
        title = sanitize(title)

        output_dir = os.path.join(boostnote, 'markdown', folder)
        os.makedirs(output_dir, exist_ok=True)
        output_file_name = title + '.md'
        output_file = os.path.join(output_dir, output_file_name)

        with open(output_file, 'w') as f:
            f.write(content)

        cnt_success += 1

    print('=============================================')
    print('Converting BoostNote to Markdown is Success!!')
    print(f'success: \t{cnt_success}')
    print(f'skip:    \t{cnt_skip}')


if __name__ == '__main__':
    extract_md_from_BoostNote()