@nqounetです。
今回から「PerlとMooでドキュメント変換ツールを作ってみよう」シリーズを始めます。Markdownでドキュメントを書いている方は多いと思いますが、その内容を別のフォーマットに変換したくなることはありませんか?
このシリーズでは、Markdown文書をHTMLやプレーンテキストに変換したり、単語数をカウントしたりできるツールを作っていきます。
このシリーズの対象読者
このシリーズは「Mooで覚えるオブジェクト指向プログラミング」シリーズを読了した方を対象としています。Mooによるクラス定義、has属性、メソッド作成の基本を理解していることを前提に進めます。
シリーズ全体の目次は以下をご覧ください。
まずは段落だけを表現してみる

最初の一歩として、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クラスを拡張していきます。継承を使って、様々な種類の要素を表現する方法を学びましょう。
お楽しみに!