PerlでMarkdownからPowerPointを作るツールを作りたい
背景と作りたいツールの概要
そもそもですが、ブラウザベースのプレゼンツールではなかなか表現に乏しいところがあり微妙だなーというのが問題意識です。チャートとかオブジェクト的なものが作りづらいってことですね。(Cacooとかで画像生成すれば別ですが、それはそれでガッチャンコがめんどいっす。)Google DocsがそのうちMarkdown読み込みの、Cacooを超えるスライド作成ツールに進化する気もしますが、とわいえ、やっぱり、Windowsユーザーとしては、パワーポイントつかってかっこいいチャート/オブジェクトをスライドの中に作りこみたいのです。こんな願望が芽生えたのです。
実装への道のり
そんな妄想持ってボーと考えていると、2010年のPerl Adventカレンダーで『スライドの下準備にWin32::PowerPointを使う』って記事があったのを思い出したんですね。以下の3つのモジュールが結びついて、「これはいける!」と思いました。
- Win32::PowerPoint(参考にしながらWin32::OLEを使って独自実装か?)
- Text::Markdown
- Web::Scraper(今は、HTML::TokeParser系じゃないとダメな気がしている)
で、「ふむふむ、こんなインターフェースだとうれしいな」という妄想に移って行きました。
Perlのインターフェースなどの設計的な
で、今思っているインターフェースはこんな感じです。
Win32::PowerPoint::Markdown->new(
draft => './readme.md',#必須
draft_encode => 'utf8',#任意 デフォルトはcp932
#format => 'document',#予め用意されているはずの*::Format::Documentの設定を使う場合
#format => 'presen',#予め用意されているはずの*::Format::Presenの設定を使う場合
format => +{#独自にオーバーライドする場合
h1 => ,
h2 => ,
ul => ,
ol => ,
p => ,
pre => ,
code => ,
blockquote => ,
cite => ,
table => ,
dl => ,
},
template => './base.ppt',#任意,いつも使っているpptの書式を使いまわせるようにしたい
);
個人的には、templateのところが重要ですね。いつも使っているスライドマスターをそのまま使いまわしたいし、途中までこれでオッケーというスライドも使いまわしたいですよね。formatのところが一番悩ましいので詳しく言及します。
フォーマットについて(format引数について)
- ハッシュリファレンスだったら、上書き読み込み クラスだったら、その設定をロードして読み込む
- タイトルとか、リード文とか、オブジェクトとか属性つける?
- タグの引数としては、Win32::PowerPointのadd_textのオプションを入れるようにする
- baseフォーマットとか、*::Format::Documentの設定をもとに上書きしていくこともできるといいかも
サンプル
Win32::PowerPoint::Markdown->new(
path => './readme.md',
base_format => 'presen',
format => +{
h2 => +{
bold => 1,
size => 36,
align => 'center',
},
p => +{
size => 20,
align => 'left',
},
},
template => './base.ppt',#いつも使っているpptの書式を使いまわせるようにしたい
);
当初の想定クラス
- Win32::PowerPoint::Converter
- Win32::PowerPoint::Converter::Format::Presen
- Win32::PowerPoint::Converter::Format::Document
初期のコード
まぁ、実際、Win32::PowerPoint使ったことないし、いろいろやりたいことをどう実装すればいいか調査する必要がありますね。で、初期のコーディングに入っていきます。まぁ、プロトタイプのような、とりあえず、動くものを作ろうって感じですね。
Hachioji.pmのYairc(仮)の話で、「disられるのを恐れて汚いコードを晒すのに怯え続けると、初心者がなかなかPerlに入ってきづらい」という話があったから、汚いコードを晒しておきます。
ちなみに、Hachioji.pmのLTの資料はこれを使いつつ、ppt整形後に修正して使ったので、これで私のニーズの6割は満たせてはいます。
あと、Win32::PowerPointの中身を多少いじる( PACKAGE->mk_ro_accessors → PACKAGE->mk_accessors )という良くないこともしているのを忘れずに。
use strict;
use warnings;
use utf8;
use Encode;
use Data::Dumper;
use Try::Tiny;
use Web::Scraper;
use Path::Class qw/file dir/;
use Text::Markdown qw/markdown/;
use Win32::OLE;
use Win32::PowerPoint;
$Win32::OLE::Warn = 3;#重要
my $conf = +{
h2 => +{
left => 0,
top => 10,
width => 720,
height => 300,
size => 36,
align => 'center',
},
p => +{
left => 0,
top => 10+36+50,
width => 720,
height => 500,
size => 24,
align => 'left',
},
ul => +{
left => 0,
top => 10+36+50,
width => 720,
height => 500,
size => 24,
align => 'left',
},
};
my $ppt = Win32::PowerPoint->new;
$ppt->presentation(
$ppt->application->Presentations->Open({ FileName => "C://Users/akabane/Desktop/ppt/script/base.ppt", ReadOnly => 0, WithWindow => 0})# WithWindow => 0が重要
);
my $data = decode('utf8',file('./content.md')->slurp(iomode => '<:encoding(UTF-8)'));
my $html = markdown($data);
warn $html;
my @htmls = split("<hr />",$html);
for my $html_part (@htmls) {
$ppt->new_slide;
my $scraper = scraper {
#process 'h1', 'h1' => 'TEXT';
process 'h2', 'h2' => 'TEXT';
#process 'h3', 'h3' => 'TEXT';
#process 'h4', 'h4' => 'TEXT';
#process 'h5', 'h5' => 'TEXT';
#process 'h6', 'h6' => 'TEXT';
process 'p', 'p[]' => 'TEXT';
process 'ul li', 'ul[]' => 'TEXT';
#process 'ol', 'ol' => 'TEXT';
#process 'blockquate', 'blockquote' => 'TEXT';
#process 'cite', 'cite' => 'TEXT';
#process 'table', 'table' => 'TEXT';
#process 'dl', 'dl' => 'TEXT';
#process 'pre', 'pre' => 'TEXT';
#process 'code', 'code' => 'TEXT';
};
my $texts = $scraper->scrape($html_part);
$ppt->add_text(encode('cp932',$texts->{h2}),$conf->{h2});
for my $tag (qw/p ul/) {
my %temp = %{$conf->{$tag}};
for my $text (@{$texts->{$tag}}) {
$ppt->add_text(encode('cp932',$text),\%temp);
my $num_of_boxes = $ppt->slide->Shapes->Count;
my $last = $num_of_boxes ? $ppt->slide->Shapes($num_of_boxes) : undef;
$temp{top} = $last->Top + $last->Height + 20;
}
}
}
$ppt->save_presentation('foo.ppt');
$ppt->close_presentation;
Win32::OLE系のモジュールは使ったことがなかったんだけど、使ったことないモジュールを使うときにゴニョゴニョしていくと私はだいたいこんな感じになります。メチャクチャハードコードされていて、へたれ具合全開ですが、こんなもんでしょうw
課題/妄想
一旦、プロトタイプのコードを書いていると、いろいろ課題とかさらなる妄想とかが出てきますよね?それが、以下です。
- 最初は、Win32::PowerPoint::Converter::Syntax::MarkdownとかWin32::PowerPoint::Converter::Syntax::MultiMarkdownって広げようとしたけど、Active Perl環境でどこまで対応できるか疑問に思ってやめた
- Markdownが吐き出したHTMLのParseは上から順に解釈が必要なので、TreeBuilder系統ではなく、TokeParser系統でスクレーピングしないといけないだろう?
- 現状は、変なスクリプトまでできた。タグの対応がいまいち。
- Wordにも対応させて、Win32::DocConverterとかって名前にする?
- PowerPoint Object Modelが壮大でなかなかドキュメント読むのがつらい
- 実際、Win32::OLEに依存したくないと思ったけど、それはopen xml系でなんとかしたほうがいいとHachioji.pmでツッコミもらって、なるほど雑念は捨て今回はWin32::OLEで行こうと思った
- Win32::PowerPointの中身を多少いじる( __PACKAGE__->mk_ro_accessors → __PACKAGE__->mk_accessors )があって、悩ましい
- ul>liのネスト的なのってどうやってスクレーピングすれば楽かな?
- テストどうすればいいんだろ?
- 最終的にCPANあげたりするとかっこいいんだけど、実際は、ppmパッケージにしないと万一使い人もうれしくない・・・んだけど、よく知らない。。。
- デフォルトのテキストの文字コードは、cp932 ? utf8 ?
今後は、こんな感じで、「プロタイプをいじる⇔課題/妄想を考える」を繰り返しつつ、どこかのタイミングで内部設計して、「コードを整える⇔課題/妄想を考える」に移っていくんだと思います。
徒然と
最近のコードの書き方も合わせてまとめたわけですが、まぁ、仕事でもこんなもんでしょう。仕事の場合は、データ構造とかどんなアルゴリズムで処理するかとか、よくわかっていながらも必死に考えるのが設計ペースで入ってくると思いますが。設計力を高めるために、オブジェクトデザインパターンを意識してもっとコード書いていきたいと思っているんですが、なかなかオブジェクトデザインパターンの理解も乏しく。。。やっぱり、私もまだまだプログラマーとしては初心者だなーと思うわけです。
とりあえず、GW明けにリファクタリングしたコードをGitにあげることを目指しますよ!ツッコミ等あればどしどしお願いします!