はじめに
CHASE!、nikkieです。
OGP(Open Graph protocol)に関するSphinxネタです。
発表スライドはSphinx拡張で作ってGitHub Pagesで公開している1ので、Sphinxで作るHTMLのOGP用のタグ設定はそれなりに経験があるつもりでした。
この週末、今回紹介するやり方を知って衝撃を受けました。
目次
- はじめに
- 目次
- 結論:metaディレクティブでproperty属性を持ったmetaタグを作る方法
- Open Graph protocolとは(簡単に)
- SphinxでHTMLにメタデータを指定する
- SphinxでHTMLにOGP用のmetaタグを設定できる!
- IMO:SphinxでOGP用のタグを設定する方法
- :property=og:locale:と書いて属性込みで指定もできるのはどういう仕組み?
- 終わりに
結論:metaディレクティブでproperty属性を持ったmetaタグを作る方法
.. meta:: :property=og:title: The Rock
:property=og:title:
という指定により、ビルドしたHTML中のmetaタグのproperty属性がog:title
と指定されます!
Open Graph protocolとは(簡単に)
SNS(FacebookやTwitter)にWebページのURLを貼ると、そのSNS内のコンテンツのように展開されますよね。
この仕組みがOpen Graph protocol(略してOGP)という仕組みなのです!
ドキュメントの「Basic Metadata」に例があります。
HTMLのheadタグの中に、property属性とcontent属性を指定したmetaタグを書けばいいのです。
<head> <meta property="og:title" content="The Rock" /> </head>
HTMLをWebに公開するとき、headタグにOGP用のmetaタグを書いておけば、そのページがSNSでシェアされるときの見た目がいい感じになります。
SphinxでHTMLにメタデータを指定する
さて、ドキュメント変換ツールSphinxは、原稿をHTMLに変換する際、メタデータ(headタグの中のmetaタグたち)を指定できます。
https://www.sphinx-doc.org/ja/master/usage/restructuredtext/basics.html#html-metadata
meta
ディレクティブを使います。
.. meta:: :description: The Sphinx documentation builder :keywords: Sphinx, documentation, builder
これでname="description"
やname="keywords"
というmetaタグを指定できます。
<meta name="description" content="The Sphinx documentation builder"> <meta name="keywords" content="Sphinx, documentation, builder">
SphinxでHTMLにOGP用のmetaタグを設定できる!
OGPではname属性ではなくproperty属性が必要ですが、name属性は指定せずにproperty属性を指定するという書き方ができるんです!
MyST-Parser2のドキュメントを見ていて気付きました。
https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html#setting-html-metadata
.. meta:: :property=og:locale: en_US
:property=og:locale:
!!
こんな指定の仕方が可能なんですね!
これにより、metaタグは、name属性の代わりにproperty属性を持ちます!
<meta content="en_US" property="og:locale" />
metaディレクティブでOGP用のmetaタグを指定した例
Sphinxで作ったランディングページ3に早速採用しました。
これまではSphinxのテンプレートを使ってOGP用のmetaタグを直に書いていました。
今回知った書き方をもとに、metaディレクティブでOGPの設定をすることにしました。
メタデータの設定が1箇所のmetaディレクティブに集まるのがいいなと感じています。
Future works: 完全にmetaディレクティブに置き換えきれず
metaディレクティブを使ったときに、以下のmetaタグがまだ作れていません。
<meta name="twitter:site" content="@pyconjapan">
content属性の値の@
がHTMLエンティティになりました(エスケープ処理と理解しました)。
.. meta:: :twitter:site: @pyconjapan
<meta content="@pyconjapan" name="twitter:site" />
すぐに解決できなさそうだったので、現在はテンプレートとmetaディレクティブの併用となっています。
IMO:SphinxでOGP用のタグを設定する方法
ここでmetaディレクティブを使った例を紹介しましたが、常にオススメとは考えていません。
Sphinxで作ったランディングページは1枚の小さいWebページであり、ライブラリの依存を増やすよりはmetaディレクティブの使いこなしでできそうなので採用しました。
Webページが増えてきたら(例:私の発表スライドのGitHub Pages)、sphinxext-opengraph
というライブラリがよさそうに思います。
私はこれから素振りするところですが、Sphinx-Users.jpで採用されています。
Twitter/Facebookへのページシェアでコンテンツを埋め込む(OGP) — Python製ドキュメンテーションビルダー、Sphinxの日本ユーザ会
Sphinx-users.jp 自体のOGP生成も独自実装(本ページで紹介しているコード)から、sphinxext-opengraphに切り替えました。
:property=og:locale:
と書いて属性込みで指定もできるのはどういう仕組み?
なんでこの書き方ができるんだろう? この書き方をしたときにmetaタグにname属性ができないのはなぜだろう? と気になってしまい、ソースコードを覗きました。
Sphinxのドキュメントでmeta
ディレクティブが紹介されていますが、実装はdocutilsにあります4(Sphinxは使っているだけという理解です)。
※時間を区切って調べたので、読み間違えている可能性もあります。お気づきの点があれば@ftnextまでお知らせいただけると大変助かります
ランディングページの開発環境のソースコードを読みました。
docutils 0.17.1 Sphinx 4.5.0
reSTに書いたmetaディレクティブをdocutilsのnodeにパースするのに使われるのが、docutils.parsers.rst.directives.html.MetaBody
クラスかなと思います。
関連すると判断した箇所を抜粋します。
class MetaBody(states.SpecializedBody): def field_marker(self, match, context, next_state): node, blank_finish = self.parsemeta(match) # 省略 def parsemeta(self, match): name = self.parse_field_marker(match) # 省略 tokens = name.split() try: attname, val = utils.extract_name_value(tokens[0])[0] node[attname.lower()] = val # 省略
parse_field_marker
メソッドはSpecializedBody
クラスのベースクラス、docutils.parsers.rst.states.Body
に定義されています。
MetaBody <- SpecializedBody <- Body
実装を引用します。
def parse_field_marker(self, match): """Extract & return field name from a field marker match.""" field = match.group()[1:] # strip off leading ':' field = field[:field.rfind(':')] # strip off trailing ':' etc. return field
先頭と末尾のコロンを外すので、":property=og:locale:"
という文字列は"property=og:locale"
となりますね。
そしてextract_name_value
関数が、key=valueという形式の文字列を(key, value)
に変換します。
docutils/utils/__init__.py
に定義されており、以下に引用します。
def extract_name_value(line): """ Return a list of (name, value) from a line of the form "name=value ...".
"property=og:locale"
という文字列は("property", "og:locale")
となりますね。
MetaBody
の実装に戻ると、node[attname.lower()] = val
でnode
の"property"
に"og:locale"
が設定されます。
なお、extract_name_value
がNameValueError
を送出すると(key=value
という形式ではないときですかね)、MetaBody
はnode
の"name"
に値を設定します。
以上、metaタグがname属性の代わりにproperty属性を持つ実装の読み解きでした!
終わりに
metaディレクティブだけでOGP設定ができたという衝撃をアウトプットしました。
私は仕組みを知ることが好き、かつ、どうしても必要なものでない限りは開発の依存関係は増やしたくないという立場なので、metaディレクティブだけで設定できるというのはかなり好感触です。
一方、複数のHTMLからなるWebサイトも扱っているわけで、そちらではsphinxext-opengraph
がなかなかよさそうに思われます(素振りだ!)。
metaディレクティブの使い方に詳しくなったことで、どこまでmetaディレクティブを使い、どこからはサードパーティの拡張(巨人)の肩に乗るか、指針が持てた気がします!