Featured image of post 第1回-Markdownを読み込むツールを作ろう - PerlとMooでドキュメント変換ツールを作ってみよう

第1回-Markdownを読み込むツールを作ろう - PerlとMooでドキュメント変換ツールを作ってみよう

Markdownをパースして段落要素を作成するツールを作ります。Elementクラスを使って、テキストをオブジェクトとして扱う基本を学びましょう。

@nqounetです。

今回から「PerlとMooでドキュメント変換ツールを作ってみよう」シリーズを始めます。Markdownでドキュメントを書いている方は多いと思いますが、その内容を別のフォーマットに変換したくなることはありませんか?

このシリーズでは、Markdown文書をHTMLやプレーンテキストに変換したり、単語数をカウントしたりできるツールを作っていきます。

このシリーズの対象読者

このシリーズは「Mooで覚えるオブジェクト指向プログラミング」シリーズを読了した方を対象としています。Mooによるクラス定義、has属性、メソッド作成の基本を理解していることを前提に進めます。

シリーズ全体の目次は以下をご覧ください。

まずは段落だけを表現してみる

MarkdownドキュメントをElementオブジェクトにパースする流れ

最初の一歩として、Markdownの段落(普通のテキスト行)をオブジェクトとして表現してみましょう。

ドキュメントの各部分を「要素」として扱うため、Elementクラスを作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package Element;
# 言語: perl
# バージョン: 5.36以上
# 依存: Moo

use Moo;
use experimental qw(signatures);

has content => (
    is       => 'ro',
    required => 1,
);

sub type ($self) {
    return 'element';
}

1;

これが最も基本的なドキュメント要素です。content属性にテキスト内容を保持し、typeメソッドで要素の種類を返します。

パース処理を作る

次に、Markdownテキストを読み込んでElementオブジェクトを作成するパーサーを作りましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package Parser;
# 言語: perl
# バージョン: 5.36以上
# 依存: Moo, Element

use Moo;
use experimental qw(signatures);
use Element;

sub parse ($self, $text) {
    my @elements;
    
    # 行ごとに分割
    my @lines = split /\n/, $text;
    
    for my $line (@lines) {
        # 空行はスキップ
        next if $line =~ /^\s*$/;
        
        # 段落として扱う
        push @elements, Element->new(content => $line);
    }
    
    return @elements;
}

1;

今のところシンプルに、空行以外の各行をElementとして作成しています。

注意: 今回のパーサーは学習用の最小実装です。Markdownの段落は「空行で区切られた複数行」を1つの段落として扱いますが、ここでは「1行=1段落」として扱っています。次回以降で要素が増えたら、段落の結合ロジックも拡張していく前提です。

動かしてみよう

実際にMarkdownをパースして、各要素を表示してみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/env perl
# 言語: perl
# バージョン: 5.36以上
# 依存: Parser, Element

use v5.36;
use lib '.';
use Parser;

my $markdown = <<'MARKDOWN';
これは最初の段落です。

これは二番目の段落です。
長い文章も一つの段落として扱われます。
MARKDOWN

my $parser = Parser->new();
my @elements = $parser->parse($markdown);

say "パース結果: " . scalar(@elements) . " 個の要素";
say "-" x 40;

for my $elem (@elements) {
    say "タイプ: " . $elem->type;
    say "内容: " . $elem->content;
    say "-" x 40;
}

実行結果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
パース結果: 3 個の要素
----------------------------------------
タイプ: element
内容: これは最初の段落です。
----------------------------------------
タイプ: element
内容: これは二番目の段落です。
----------------------------------------
タイプ: element
内容: 長い文章も一つの段落として扱われます。
----------------------------------------

Markdownの各行がElementオブジェクトとして作成されました。

今回のポイント

今回は以下のことを学びました。

  • ドキュメント要素を表現するElementクラスの作成
  • 基本的なパース処理の実装
  • テキストをオブジェクトとして扱うメリット

今はまだ全ての行が同じelementタイプですが、これから見出しやコードブロックなど、様々な要素を表現できるようにしていきます。

今回の完成コード

以下が今回作成したコードの完成版です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#!/usr/bin/env perl
# 言語: perl
# バージョン: 5.36以上
# 依存: Moo

use v5.36;

# === Element ===
package Element {
    use Moo;
    use experimental qw(signatures);

    has content => (
        is       => 'ro',
        required => 1,
    );

    sub type ($self) {
        return 'element';
    }
}

# === Parser ===
package Parser {
    use Moo;
    use experimental qw(signatures);

    sub parse ($self, $text) {
        my @elements;
        my @lines = split /\n/, $text;
        
        for my $line (@lines) {
            next if $line =~ /^\s*$/;
            push @elements, Element->new(content => $line);
        }
        
        return @elements;
    }
}

# === メイン処理 ===
package main {
    my $markdown = <<'MARKDOWN';
これは最初の段落です。

これは二番目の段落です。
長い文章も一つの段落として扱われます。
MARKDOWN

    my $parser = Parser->new();
    my @elements = $parser->parse($markdown);

    say "パース結果: " . scalar(@elements) . " 個の要素";
    say "-" x 40;

    for my $elem (@elements) {
        say "タイプ: " . $elem->type;
        say "内容: " . $elem->content;
        say "-" x 40;
    }
}

次回予告

次回は、見出しやコードブロックも表現できるように、Elementクラスを拡張していきます。継承を使って、様々な種類の要素を表現する方法を学びましょう。

お楽しみに!

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。