gem の html-pipeline を使って、独自のマークダウンフィルターを追加しているとき、ビジネスロジック上、かなり変化球なフィルターを作りたくなるときがあると思います。もしくは、コード上は入り組んでるけど諸事情があってそういった仕様にしなきゃいけないときとか。
今回、結局使わなかったけど、なかなかイレギュラーな xpath の書き方だなと思ったので、備忘録的にここにメモしておきます。
(こういう備忘録的なこと書くのってやっぱ個人のブログの方が良いんだよね…?ブログ作ろ…)
特定のテキストノードが a タグの後にある場合とは
html-pipeline の HTML::Pipeline::MarkdownFilter
を使うときの依存 gem である Commonmarker のそもそもの設計思想として、「マークダウンはHTMLを書くためのものじゃないよ」ってのがあります。そのため、変換できない要素とか属性とかが結構あったりします。
たとえば、 <div>
とか、 target="_blank"
とか。
なので、これらをもしマークダウンで書けるようにしたいのであれば、独自のフィルターを作るしかない。うん作ろう。
リンクに target="_blank"
をつけるための記法を決める
[]():blank
のように、リンクの末尾に :blank
がある場合は、 target="_blank"
をつけようみたいな仕様を考えたとします。
[これは target="_blank" のリンクです。](https://example.com) → target="_blank" つかない
[これは target="_blank" のリンクです。](https://example.com):blank → target="_blank" つく
このとき、HTML::Pipeline::MarkdownFilter
の内部で動いてる nokogiri は以下のようにパースしてくれています。
(Element:0x3fc248dead9c {
name = "p",
children = [
#(Element:0x3fc248df7330 {
name = "a",
attributes = [
#(Attr:0x3fc248f308dc { name = "href", value = "https://example.com" })],
children = [ #(Text "これは target=\"_blank\" のリンクです")]
}),
#(Text ":blank")]
})
このように、 Element
の後に Text
が続いてるのが分かると思います。つまり、 []():blank
のように書く場合はおおよそ上記の構造になると考えて差し支えなさそう。
nokogiri で target="_blank"
を付与する
詳細は html-pipeline の仕様とも絡むので詳細は割愛しますが、 HTML::Pipeline
を初期化するときに引数に独自のフィルターを渡すと、内部の call
メソッド内で doc
メソッド経由で Nokogiri::HTML::DocumentFragment
にアクセスすることができます。このインスタンスにパースされる HTML 要素が全部詰まっています。
class HogeFilter < HTML::Pipeline::Filter
def call
# your custom filter goes here
# ex) doc.css('a').each { |a| a[:rel] = "nofollow" }
# doc.search('.youtube-movie').wrap("<div class='wrapper'>")
end
end
ここで、 直後に :blank
というテキストを持つ a タグのみを抽出する方法を xpath で書きたいと思います。
doc.xpath('.//a[contains(following-sibling::text(), ":blank")]')
ところで xpath の記法については クローラ作成に必須!XPATHの記法まとめ がおすすめです!
ということで、これバカみてーに一日試行錯誤してこういう記法に落ち着きましたが、他にもっと短い(もしくは xpath を使わない)記法がありましたら教えていただけると幸いです。 RuboCop に怒られる確率も減ることでしょう…。
参考文献
https://qiita.com/rllllho/items/cb1187cec0fb17fc650a
https://github.com/jch/html-pipeline
https://github.com/sparklemotion/nokogiri
https://gogodiet.net/z/xml/7.htm
https://www.techscore.com/tech/XML/XPath/XPath3/xpath03.html/