···4848 {:ok,
4949 %{
5050 state
5151- | entries: [%Entry{} | state.entries],
5151+ | entries: [%Entry{categories: []} | state.entries],
5252 stack: ["entry" | state.stack],
5353 current_text: []
5454 }}
···5757 def handle_event(:start_element, {"link", attrs}, state) do
5858 state = apply_link(state, List.first(state.stack), attrs)
5959 {:ok, %{state | stack: ["link" | state.stack], current_text: []}}
6060+ end
6161+6262+ def handle_event(:start_element, {"category", attrs}, state) do
6363+ state = apply_category(state, List.first(state.stack), attrs)
6464+ {:ok, %{state | stack: ["category" | state.stack], current_text: []}}
6065 end
61666267 def handle_event(:start_element, {name, _attrs}, state) do
···116121 end
117122 end
118123124124+ defp apply_category(state, "entry", attrs) do
125125+ case Map.new(attrs) do
126126+ %{"term" => term} ->
127127+ [current | entries] = state.entries
128128+ %{state | entries: [%{current | categories: [term | current.categories]} | entries]}
129129+130130+ _ ->
131131+ state
132132+ end
133133+ end
134134+135135+ defp apply_category(state, _parent, _attrs), do: state
136136+119137 defp apply_entry_field(entry, "title", text), do: %{entry | title: text}
120138 defp apply_entry_field(entry, "id", text), do: %{entry | id: text}
121139 defp apply_entry_field(entry, "summary", text), do: %{entry | summary: text}
···132150 defp apply_feed_field(feed, "subtitle", text), do: %{feed | description: text}
133151 defp apply_feed_field(feed, _name, _text), do: feed
134152135135- defp finalize_entry(%Entry{id: nil, url: url} = entry) when is_binary(url) do
136136- %{entry | id: url}
137137- end
153153+ defp finalize_entry(%Entry{} = entry) do
154154+ id =
155155+ if is_nil(entry.id) and is_binary(entry.url) do
156156+ entry.url
157157+ else
158158+ entry.id
159159+ end
138160139139- defp finalize_entry(entry), do: entry
161161+ %{entry | id: id, categories: Enum.reverse(entry.categories)}
162162+ end
140163141164 defp text(parts) do
142165 result =
+1
test/annot_at/feeds/atom_test.exs
···2727 assert "<p>The full <strong>content</strong>\n of the first post.</p>" == first.content
2828 assert %DateTime{} = first.published_at
2929 assert ~U[2024-10-02 13:00:00Z] == first.published_at
3030+ assert ["category-1", "category-2"] = first.categories
3031 end
31323233 test "falls back to URL as ID when id is missing" do
+2
test/support/fixtures/feeds/atom_sample.xml
···1717 of the first post.</p></content>
1818 <published>2024-10-02T13:00:00Z</published>
1919 <updated>2024-10-03T09:00:00Z</updated>
2020+ <category term="category-1" />
2121+ <category term="category-2" />
2022 </entry>
2123 <entry>
2224 <title>Second Post</title>