ScrapboxのページをObsidianに取り込む

文脈

最近はプライベートなノートアプリとしてObsidianを使っている。気に入ってきたので、以前使っていたScrapboxのページをインポートしたくなった。ObsidianはMarkdownがベースで、内部リンクの固有の記法があるなどの違いがある。ScrapboxMarkdownでエクスポートする機能がないので、Scrapboxで書き出したjson形式のデータをmarkdownに変換したい。

obsidian.md

scrapbox.io

やったこと

ページごとにMarkdown風テキストファイルにするスクリプトを書いた。Scrapboxの記法を正規表現で置換することを試みた。一部ではうまく置換でき、一部では置換できていない。時間はないし、インポートしないよりはマシなので、そのスクリプトで変換してObsidianにインポートした。ObsidianのインポートはただMarkdownのファイルを置くだけ。

やってみた感想

正規表現をがんばるより、パーサーを書いてみたい。その方が面白そう。

Obsidianはインラインのlatexに対応していないなど、変換できないところもあった。

Scrapboxjsonでエクスポートできる。しかしエクスポート可能であっても、記法を変換するため他アプリへでインポートするには障壁がある。

use strict;
use warnings;
use utf8;

use File::Slurp;

use JSON::XS qw(decode_json encode_json);

sub main {
    print "start\n";
    # open json file
    my $text = read_file( 'data/scrapbox.json' );
    my $data = decode_json($text);
    
    # print data info
    print($data->{name} . "\n");
    print($data->{displayName} . "\n");
    print($data->{exported} . "\n");

    my $pages = $data->{pages};

    foreach my $page (@$pages) {
        convert_page($page)
    }

}

sub convert_page {
    my ($page) = @_;
    
    my $file_name = "output/" . $page->{title} . ".md";

    my $str = "";
    my $lines = $page->{lines};
    foreach my $line (@$lines) {
        $str = $str . convert_line($line) . "\n";
    }

    open(my $fh, "> :encoding(UTF-8)", $file_name);
    $fh->print($str);

    print "saved " . $file_name . "\n";
}

sub convert_line {
    my ($line) = @_;
    
    # bold
    $line =~ s/([\w|\s]+)\[\*\* (\w+)\]/$1**$2**/g;
    # header 2
    $line =~ s/^\[\*\* (.+)\]/## $1/;
    # header 3
    $line =~ s/^\[\* (.+)\]/### $1/;
    # internal link
    $line =~ s/(.?)\[([\w|\s\.|]+)\](.?)/$1\[\[$2\]\]$3/g;
    # latex
    $line =~ s/^(.?)\[\$(.+)\](.?)/$1\$\$$2\$\$$3/;

    return $line;
}

main();
use strict;
use warnings;
use utf8;
use Test2::V0;


# bold
sub test_1 {
    my $s = "foobar [** hoge]";
    my $expected = "foobar **hoge**";
    $s =~ s/([\w|\s]+)\[\*\* (\w+)\]/$1**$2**/g;
    my $got = $s;
    is $got, $expected, "bold :  in the middle of a line";
}

test_1;


# header 2
sub test_2 {
    my $s = "[** foo bar]";
    my $expected = "## foo bar";
    $s =~ s/^\[\*\* (.+)\]/## $1/;
    my $got = $s;
    is $got, $expected, "header 2 : with space";
}

test_2;

# header 3
sub test_header_3 {
    my $s = "[* foo bar]";
    my $expected = "### foo bar";
    $s =~ s/^\[\* (.+)\]/### $1/;
    my $got = $s;
    is $got, $expected, "header 1 : with space";
}

test_header_3;


# internal link
sub test_internal_link {
    my $s = "hoge [foo bar.hoge]fuga[foobar]";
    my $expected = "hoge [[foo bar.hoge]]fuga[[foobar]]";
    $s =~ s/(.?)\[([\w|\s\.|]+)\](.?)/$1\[\[$2\]\]$3/g;
    my $got = $s;
    is $got, $expected, "internal link: in the middle of a line";
}

test_internal_link;

# latex(except inline latex)
sub test_latex {
    my $s = "[\$ \Gamma \vdash \mathit{unit}: \mathit{Unit} \phantom{-} \textbf{(Unit)}]";
    my $expected = "\$\$ \Gamma \vdash \mathit{unit}: \mathit{Unit} \phantom{-} \textbf{(Unit)}\$\$";
    $s =~ s/^(.?)\[\$(.+)\](.?)/$1\$\$$2\$\$$3/;
    my $got = $s;
    is $got, $expected, "latex(except inline latex)";
}

test_latex;

done_testing;