FeedjiraでRSSの独自タグ拡張

## FeedjiraでRSSの独自タグ拡張

feedjira gemを利用した独自タグ拡張の方法について説明します。

Feedjiraには `add_common_feed_entry_element` というメソッドがあります。
https://www.rubydoc.info/github/feedjira/feedjira/master/Feedjira/Feed#add_common_feed_entry_element-class_method

ドキュメントがなくコードを読んでも
https://github.com/feedjira/feedjira/blob/master/lib/feedjira/feed.rb#L18

だいぶ深追いしないと把握できないのでとりあえずどういう動きをするのかを説明します。

### シンプルな構造の場合（ネストしていない）

以下のような `my:tag` という独自タグが含まれるRSSがあったとします

```
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
  xmlns:media="http://search.yahoo.com/mrss/"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  >
  <channel>
    <title>サイトタイトル</title>
    <link>https://example.com/</link>
    <description>サイト説明</description>
    <pubDate>Mon, 19 Jun 2020 09:00:00 7JunJST</pubDate>
    <language>ja</language>
    <copyright>誰か</copyright>
    <item>
      <title><![CDATA[タイトル]]></title>
      <link>https://example.com/item/1</link>
      <guid>https://example.com/item/1</guid>
      <description><![CDATA[ディスクリプション]]></description>
      <pubDate>Mon, 19 Jun 2020 02:55:52 2JunJST</pubDate>
      <content:encoded><![CDATA[
        コンテンツ本文
      ]]></content:encoded>
      <!-- 独自タグ -->
      <my:tag link="https://example.com/" title="example">独自タグ</my:tag>
      <!-- /独自タグ -->
    </item>
  </channel>
</rss>
```

独自タグ部分をparseしてFeedjiraのentryオブジェクトに追加したい場合

```
<my:tag link="https://example.com/" title="example">独自タグ</my:tag>
```

普通に`Feedjira::parse`しただけでは取得できません。

そこで `add_common_feed_entry_element` の登場です。

```
Feedjira::Feed.add_common_feed_entry_element("my:tag", as: :my_tag)
```

このように書くことでFeedjiraのentryオブジェクトにmy_tagという名前で追加されタグのinner textを取得することが出来ます。

```
Feedjira::Feed.add_common_feed_entry_element("my:tag", :as => :my_tag)
resp = Faraday.get('http://example.com/feed')
feed = Feedjira.parse(resp.body)
feed.entries.each do |entry|
  puts entry.my_tag
  # stdout: 独自タグ
end
```

`link` アトリビュートや `title` アトリビュートが必要な場合は `value` にアトリビュート名を設定します。

```
Feedjira::Feed.add_common_feed_entry_element("my:tag", as: :my_tag)
Feedjira::Feed.add_common_feed_entry_element("my:tag", as: :my_tag_title, value: :title)
Feedjira::Feed.add_common_feed_entry_element("my:tag", as: :my_tag_link, value: :link)
resp = Faraday.get('http://example.com/feed')
feed = Feedjira.parse(resp.body)
feed.entries.each do |entry|
  puts entry.my_tag
  # stdout: 独自タグ
  puts entry.my_tag_title
  # stdout: example
  puts entry.my_tag_link
  # stdout: https://example.com/
end
```

ネストしていないシンプルな構造の独自タグの場合は `add_common_feed_entry_element` で簡単に取得することが出来ます。

### 複雑な構造の場合（ネストしている）

以下のようなRSSがあったとします。

```
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
  xmlns:media="http://search.yahoo.com/mrss/"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  >
  <channel>
    <title>サイトタイトル</title>
    <link>https://example.com/</link>
    <description>サイト説明</description>
    <pubDate>Mon, 19 Jun 2020 09:00:00 7JunJST</pubDate>
    <language>ja</language>
    <copyright>誰か</copyright>
    <item>
      <title><![CDATA[タイトル]]></title>
      <link>https://example.com/item/1</link>
      <guid>https://example.com/item/1</guid>
      <description><![CDATA[ディスクリプション]]></description>
      <pubDate>Mon, 19 Jun 2020 02:55:52 2JunJST</pubDate>
      <content:encoded><![CDATA[
        コンテンツ本文
      ]]></content:encoded>
      <!-- ここから独自拡張 -->
      <my:nested_tag>
        <my:child link="https://exmaple.com/1" title="example1" />
        <my:child link="https://exmaple.com/2" title="example2" />
        <my:child link="https://exmaple.com/3" title="example3" />
      </my:nested_tag>
      <!-- /ここまで独自拡張 -->
    </item>
  </channel>
</rss>
```

このような場合は `add_common_feed_entry_element` を普通に使用しただけでは思った通りに取得が出来ません。

```
Feedjira::Feed.add_common_feed_entry_element("my:nested_tag", as: :nested_tag)
# valueを指定しないとinner textを返すのでブランクになる

Feedjira::Feed.add_common_feed_entry_element("my:child", as: :sponsored_link, value: :link)
# my:childから取得を試みた場合は3件のうち1件目のみが取得できる
```

#### どうするか？

`add_common_feed_entry_element` には `class` オプションにparserを渡せる仕組みがあります。
ドキュメントにはありませんが以下のコードから `class` を渡せるということがわかります。

https://github.com/feedjira/feedjira/blob/master/lib/feedjira/parser/rss.rb#L20


##### 公式のコードを参考にして独自parserクラスを定義

```
module Feedjira
  module Parser
    class MyNestedTag
      include SAXMachine

      elements :"my:child", as: :link, value: :link
      elements :"my:child", as: :title, value: :title
    end
  end
end
```

##### classに独自parserクラスを指定してみる

```
Feedjira::Feed.add_common_feed_entry_element("my:nested_tag", as: :nested_tag, class: Feedjira::Parser::MyNestedTag)
resp = Faraday.get('http://example.com/feed')
feed = Feedjira.parse(resp.body)
feed.entries.each do |entry|
  entry.nested_tag.each do |ads|
    puts ads.link
    puts ads.title
  end
  # stdout:
  #   https://exmaple.com/ads/1
  #   example1
  #   https://exmaple.com/ads/2
  #   example2
  #   https://exmaple.com/ads/3
  #   example3
end
```

意図した通りに動きました。
parserクラスの書き方自体は公式のparserが参考になるのでそちらを参考にすると良いと思います

公式parser: https://github.com/feedjira/feedjira/tree/master/lib/feedjira/parser