reライブラリーの正規表現貪欲 (greedy) マッチに少し手を焼く

SIRIUSからWordPressへ以降するツールで、SIRIUSのファイル生成位置がデフォルトの場合、こんなURLのパターンがある。

  1. https://ドメイン名/
  2. https://ドメイン名/カテゴリー名/
  3. https://ドメイン名/カテゴリー名/記事.html
  4. https://ドメイン名/カテゴリー名/子カテゴリー名/
  5. https://ドメイン名/カテゴリー名/子カテゴリー名/記事.html

WordPressへ投稿する際、トップページ(1)以外、つまり記事ページでもカテゴリーでもslugを設定する必要がある。

そこでURLからslugに設定すべき文字列、

2なら「カテゴリー名」
3なら「記事.html」
4なら「子カテゴリー名」
5なら「記事.html」

を取得するコードをreライブラリーを使って、URLからslugにする目的の文字列を取り出すコードを書いてみた。

match = re.search("^https?://.*/(.+)", url)

これでトップページ以外はマッチするので、トップページかどうかは切り分けられる。

そして、match.group(1)に、ドメイン名以降が格納されるハズだから、

catpath = match.group(1).split('/')

と’/’で分割して、最後の部分をslugとすればいいと考えた。

が、これは2重に間違えだった。

まずカテゴリーページのURLの場合、URLの最後が’/’で終わっているので、split()を使って分割すると、分割された最後に空文字のリストが作られてしまう。
つまりslugが空文字になってしまう。

もう一つの間違いは、正規表現”^https?://.*/(.+)”は、ドメイン名までとそれ以降でマッチしているのではなく、

  • 記事ページの場合なら、ドメイン+カテゴリー名の後の’/’まで
  • 親カテゴリーページの場合なら、ドメイン名の後の’/’まで
  • 子カテゴリーページの場合なら、ドメイン+親テゴリー名の後の’/’まで

でマッチしていたのだ。
正規表現の貪欲 (greedy) マッチってやつ。

ということで、group(1)には、始めからslugにすべき文字列の部分だけが格納されている。

でも、これはこれで好都合でもあるので、理解だけ正して正規表現はこのままに。
ただカテゴリーページの場合の最後の’/’は付いたまま。

じゃ、splitは必要なくて、URLの最後に’/’があったら外せばいい、ってことで、

match = re.search("^https?://.*/(.+)/?$", url)

としたのだが、これでもカテゴリーページの場合のslug部分の文字列の最後に’/’は付いたままになってしまう。

URL最後の’/’をマッチオブジェクトのグループの範囲外にしたつもりだったのだが、これも貪欲 (greedy) マッチにハマってしまっていた。

slug部分に当てはめる「(.+)」だと、’.’が最後の’/’も含めて合致すると判断されてしまうので、グループの範囲内になってしまうわけだ。

ということで正解は、

match = re.search("^https?://.*/(.+?)/?$", url)

と、slug部分は貪欲 (greedy) マッチしないようにしてあげること。
これで最後の’/?’の部分が効いてくれるようになって、最後の’/’が外せた。

正規表現、やっぱり奥が深いな。

コメント

タイトルとURLをコピーしました