Perlのちょっとしたテキスト処理で生活を豊かにする

こちらは [Perl Advent Calendar 2022](https://qiita.com/advent-calendar/2022/perl) の23日目の記事になります。
昨日は　@doikojiさんによる 「[高機能軽量マークアップ言語をサポートするperl modulino：perlプログラマに向けての補遺](https://qiita.com/doikoji/items/9ca50bfa9eb645292c74)」でした。

# はじめに
1年に1回のこの時期くらいは何かPerlのネタを投稿したいと思ったのですが、
Perlと言えばやっぱりテキスト処理・正規表現が最強に使いやすいということで、
日常で不便な問題をPerlのテキスト処理でカジュアルに改善しようというネタで少し前に遭遇したことを紹介します。

---

# Perlとテキスト処理

[初めてのPerl](https://www.oreilly.co.jp/books/9784873118246/
) には以下の記述があります。

> Perl は、3 分間で書き上げるようなやっつけ仕事のプログラムに適しています。また、12 人の
プログラマが3 年間がかりで完成させるような、大規模で本格的なプログラムにも適しています。
> Perl は、テキスト処理が90％、それ以外の処理が10％で構成される問題に向けて最適化されて
います。

「Perlとテキスト処理」というと、ふと思い出したのは今から10年前頃に以下の記事が話題になりました。

https://perl-users.jp/articles/advent-calendar/2012/hacker/12

yappoさんによる、最強のケンオールを求めて - Parse::JapanesePostalCode
という名記事です。
当時、日本郵便による郵便番号データ用のCSV,いわゆる `KEN_ALL.CSV` ブームが賑わっていたように思います。
詳しく上記の記事を読んで頂くとわかるのですが、
複雑怪奇なルールから成るCSVを、Perlの正規表現を駆使してゴリゴリ処理して自由に郵便番号や住所をいい感じにパースするというものでした。
余談ですが ケンオール.CSV はこういった複雑なCSVのため、今では以下のような企業による郵便番号検索APIも出来ています。

https://kenall.jp/features/postalcode

KEN_ALL.CSVほどではありませんが、先月少しだけ似たような状況に遭遇したので軽いネタとして紹介します。

---

# 全国旅行支援とワーケーション

今年10月から、全国旅行支援というのが観光振興事業が開始されました。

https://ja.wikipedia.org/wiki/%E5%85%A8%E5%9B%BD%E6%97%85%E8%A1%8C%E6%94%AF%E6%8F%B4

GoToトラベルの時は国主導で進められたのですが、
今回の全国旅行支援では日本の都道府県単位で事務局が設けられています。

全国旅行支援はリモートワークメインのエンジニアにも非常に相性が良く、
自分も仕事と観光振興を兼ねてワーケーション目的で何県かお世話になったのですが、
先月訪問した沖縄県での事でした。

宿泊した際に地域クーポン券がもらえるのですが、
都道府県によっては専用のアプリなどでデジタルクーポンや使える店がスマホで地図検索を行う事が可能です。

今回の沖縄の場合はクーポンが紙のみとなっていました。
そこまではいいのですが、沖縄の場合は（本日時点では）クーポン利用可能なお店の地図がありません。

公式ページを見てみると、

https://okinawasaihakkennext.com/coupon.html

> 地域クーポン 登録件数 1646 件

とあり、一応エリアの絞り込みと検索フォームも付いているのですが、実際に目の前の使える店を調べてみるも1件もヒットせず。
おかしいと思って検索フォームの上を見てみると、PDFのリンクがありました。
https://okinawasaihakkennext.com/uploads/coupon_list.pdf

こちらのPDFにはちゃんとお店が載っていました。
一番下まで辿ってみると 2603件（12月20日付） とあり、検索フォームと約1000件近くの差異があることが判明しました。

ここの検索フォームは（多分）更新されていなそうで使えそうにないので、PDFの検索で我慢するか、と思うも今度は住所だけ見てもどこがどこなのかが非常に分かりにくいため困りました。。
観光客としては、住所だと分からないので自分の近くのお店を地図で見たい…！

https://twitter.com/teckl/status/1595012058260668420

ということでクーポン一覧のPDFからテキストデータを引っこ抜いてGoogleMapに描画する事を考えました。

---

# PDFからテキストの抽出
metacpanでPDFを検索すると、PDFからテキストを抽出できそうなモジュールがいくつか見つかるのですが、
やっつけ仕事なのでググって出てきたPDFテキスト抽出ツールを使用することに。

で、出てきたテキストファイルを眺めてみるとこんな感じになっていました。

```text
1 Sushi Restaurant 一目良膳
飲食
沖縄県石垣市美崎町2-8 ホテルイーストチャイナシー内3F 0980-82-7733
https://ichimokuryozen.com/
2 ホテルモントレ沖縄 スパ＆リゾート
飲食
沖縄県国頭郡恩納村字冨着1550番地１
098-993-7111
https://www.hotelmonterey.co.jp/okinawa/restaurant/
3 アクアベル オールデイダイニング
飲食
沖縄県国頭郡恩納村字瀬良垣2260番地
098-966-1302
https://www.anaintercontinental-manza.jp/restaurants/aqua-belle/
```

ぱっと見だと4行毎に規則的なパターンがあるようにも見えますが、
電話番号が住所の行に入っていたりいなかったりで実際には5行のパターンが多いようです。
また、

```
39 アグー専門店 南国亭
飲食店
沖縄県島尻郡八重瀬町字仲座1075-2
098-998-1739
https://nangoku-agoo.business.site/?utm_source=gmb&utm_medium=referral 40 まぐろ問屋 やざえもん 沖縄新都心あっぷるタウン店
飲食店
沖縄県那覇市おもろまち3-3-1 あっぷるタウン2F
098-941-8080
http://www.neo-emotion.jp
```

この5行目を見てみると、URLの行の後ろに次の別の店舗情報が入っていたりするようですね。。

さらに後半になってくると、

```
2597 mic21沖縄那覇店
体験・アクティビティ、スポーツ・イベント、お土産 沖縄県那覇市前島２－２１－１３ ふそうビル1階
098-943-2422
https://www.mic21.com/shop/show/shop/9.html 2598 ケラマカヤックセンター
体験・アクティビティ、スポーツ・イベント、お土産、クラフトショップ、工芸品（やちむん、琉球ガラスなど） 沖縄県島尻郡座間味村字座間味125-2
098-896-4677
http://keramakayak.jp/
2599 うたくなー石垣島 -豊かな自然が織りなす静寂へ-
体験・アクティビティ、スポーツ・イベント、クラフトショップ 沖縄県石垣市平得417-4 グランデHOSHINO 303号室 090-1947-5898
https://www.utakuna-ishigakijima.com/
```

のように、カテゴリ、住所、電話番号が一緒の行に入っていたり入っていなかったりと、なかなかアグレッシブ。
また、中には電話番号の手動入力によるものと思われる、ハイフン入りの電話番号で10桁のものなどもあるようです。（ハイフン込みの場合、電話番号は本来11桁か12桁になるはず）

KEN_ALL.CSV からするとこんなのは全然余裕かもしれませんが、手動で整形するにはちょっと件数が多いのでしんどいのと、
リストは1週間に1回程度更新されているようなので、手動で改行などを入れていくのは敗北した気分になります。
そこでPerlの正規表現を使って、やっつけ仕事をしてみることにしました。

---

# パーサーを作ってみる

KEN_ALL.CSV のパーサーモジュール

https://metacpan.org/pod/Parse::JapanesePostalCode

を大いに参考にして作業を開始してみたものの、
元々のソースがCSVか複数行のテキストかというところでデータ構造自体が大きく違うのと、
ケンオールと違って中身を置換して表記ゆれを適宜修正したりといった目的も特になかったため、
結局大半のコードが不要となってしまいましたｗ

# 成果物

https://github.com/teckl/p5-Parse-OkinawaCouponListText

（なお、実際には旅先ではモジュール化などはせずに1枚っぺらのやっつけPerlスクリプトを作ってやっていました。今回この記事でネタにするために KEN_ALL.CSV のモジュールをインスパイアさせていただいたものになります）

# 動作イメージ

```perl
$ cat ./make_okinawa_coupon_list_csv.pl
#!/usr/bin/env perl
use strict;
use warnings;
use v5.12;
use lib qw(./lib);

use Encode;
use Parse::OkinawaCouponListText;
use Text::CSV_XS;

my $parser = Parse::OkinawaCouponListText->new( file => './coupon_list_20221220.txt' );
my $csv = Text::CSV_XS->new({binary => 1});
while (my $obj = $parser->fetch_obj) {
    $csv->combine($obj->number, $obj->name, $obj->category, $obj->address, $obj->tel, $obj->url);
    say encode_utf8($csv->string);
}
```

```bash
$ ./make_okinawa_coupon_list_csv.pl | head
1,"Sushi Restaurant 一目良膳","飲食","沖縄県石垣市美崎町2-8 ホテルイーストチャイナシー内3F",0980-82-7733,https://ichimokuryozen.com/
2,"ホテルモントレ沖縄 スパ＆リゾート","飲食","沖縄県国頭郡恩納村字冨着1550番地１",098-993-7111,https://www.hotelmonterey.co.jp/okinawa/restaurant/
3,"アクアベル オールデイダイニング","飲食","沖縄県国頭郡恩納村字瀬良垣2260番地",098-966-1302,https://www.anaintercontinental-manza.jp/restaurants/aqua-belle/
4,"日本料理 雲海","飲食","沖縄県国頭郡恩納村字瀬良垣2260番地",098-966-1302,https://www.anaintercontinental-manza.jp/restaurants/unkai-japanese-cuisine/
5,"オーシャンカフェ","飲食","沖縄県国頭郡恩納村字瀬良垣2260番地",098-966-1302,https://www.anaintercontinental-manza.jp/restaurants/ocean-cafe/
〜
```

ということでなんとかあまり正規化されていない複数行テキストをCSVに変換することが出来ました。

---

# CSVからGoogleマップにインポート

CSVが出来たらあとはGoogleの [マイマップ](https://www.google.com/maps/d/u/0/
) に作ってみたCSVをインポートするだけ。
ということでようやくGoogleマップ上でクーポンが使用出来るお店を探せるようになりました。。

参考までに本日時点の最新のCSVを入れた地図を共有しておきます。
沖縄に旅行される方は是非ご活用ください。
[おきなわ彩発見NEXT_クーポンマイマップ](https://www.google.com/maps/d/edit?mid=1etJBiq9jOuiMpTzB2qAipEh6I7M4I_8&usp=sharing)

全国旅行支援のセカンドシーズンが2023年の年明けから開始されるようですので、もう少し使えるかもしれませんｗ

https://okinawasaihakkennext.com/uploads/jisshigaiyou.pdf

---

# おわりに

今回は日頃生活をしている中で、Perlの強みであるテキスト処理と正規表現を使って面倒な作業を少しでも楽をしてみようということで、旅先で起こったテキスト処理の例を紹介させていただきました。

プログラマ以外の人でも、簡単な正規表現を覚えるとちょっとした時にテキスト処理を行うと便利になったりする事もあるかもしれません。
人生を豊かにするためにも、Perlと正規表現を覚えておくと楽しくなると思います。


明日はbayashiさんによる、「[Perl Navigatorについて](https://bayashi.net/diary/2022/1224)」です。
お楽しみに！