Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,8 @@ internal fun CoroutineScope.extractAtomContent(
rel != AtomKeyword.LINK_REL_ENCLOSURE.value
) {
when {
insideItem -> channelFactory.articleBuilder.link(link)
else -> channelFactory.channelBuilder.link(link)
insideItem -> channelFactory.articleBuilder.link(link, rel)
!insideItem -> channelFactory.channelBuilder.link(link)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ internal class AtomFeedHandler(
rel != AtomKeyword.LINK_REL_REPLIES.value
) {
when {
isInsideItem -> channelFactory.articleBuilder.link(link)
isInsideItem -> channelFactory.articleBuilder.link(link, rel)
else -> channelFactory.channelBuilder.link(link)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,20 @@ public data class RssItem(
private var youtubeItemData: YoutubeItemData? = null,
private var rawEnclosure: RawEnclosure? = null,
) {
private var linkPriority: Int = LINK_PRIORITY_NONE

fun guid(guid: String?) = apply { this.guid = guid }
fun title(title: String?) = apply { this.title = title }
fun author(author: String?) = apply { this.author = author }
fun link(link: String?) = apply { this.link = link }

fun link(link: String?, rel: String? = null) = apply {
val candidate = link?.takeIf { it.isNotBlank() } ?: return@apply
val priority = linkPriorityFor(rel)
if (priority > linkPriority) {
this.link = candidate
linkPriority = priority
}
}
fun pubDate(pubDate: String?) = apply {
this.pubDate = pubDate
}
Expand Down Expand Up @@ -152,4 +162,22 @@ public data class RssItem(
)
}
}

private companion object {
private const val LINK_PRIORITY_NONE = -1
private const val LINK_PRIORITY_RELATED = 0
private const val LINK_PRIORITY_OTHER = 1
private const val LINK_PRIORITY_MISSING_REL = 2
private const val LINK_PRIORITY_ALTERNATE = 3

private fun linkPriorityFor(rel: String?): Int {
val normalized = rel?.lowercase()
return when {
normalized == "alternate" -> LINK_PRIORITY_ALTERNATE
normalized.isNullOrBlank() -> LINK_PRIORITY_MISSING_REL
normalized == "related" -> LINK_PRIORITY_RELATED
else -> LINK_PRIORITY_OTHER
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.prof18.rssparser.atom

import com.prof18.rssparser.XmlParserTestExecutor
import com.prof18.rssparser.model.RssChannel
import com.prof18.rssparser.model.RssItem
import com.prof18.rssparser.parseFeed
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals

class XmlParserAtomEntryRelatedLinkTest : XmlParserTestExecutor() {

private val expectedChannel = RssChannel(
title = "Visual Studio Code - Code Editing. Redefined.",
link = "https://code.visualstudio.com/",
description = null,
image = null,
lastBuildDate = "2026-02-04T17:00:00.000Z",
updatePeriod = null,
itunesChannelData = null,
youtubeChannelData = null,
items = listOf(
RssItem(
guid = "https://code.visualstudio.com/updates/v1_109",
title = "January 2026 Insiders (version 1.109)",
author = null,
link = "https://code.visualstudio.com/updates/v1_109",
pubDate = "2026-02-04T17:00:00.000Z",
description = null,
content = "Learn what is new.",
image = null,
audio = null,
video = null,
sourceName = null,
sourceUrl = null,
categories = listOf(),
itunesItemData = null,
commentsUrl = null,
youtubeItemData = null,
rawEnclosure = null,
)
)
)

@Test
fun channelIsParsedCorrectly() = runTest {
val channel = parseFeed("atom-feed-entry-related-link.xml")
assertEquals(expectedChannel, channel)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Visual Studio Code - Code Editing. Redefined.</title>
<link rel="self" type="application/atom+xml" href="https://code.visualstudio.com/feed.xml" />
<link rel="alternate" type="text/html" href="https://code.visualstudio.com/" />
<updated>2026-02-04T17:00:00.000Z</updated>
<id>https://code.visualstudio.com/</id>

<entry>
<title>January 2026 Insiders (version 1.109)</title>
<link rel="related" href="https://code.visualstudio.com/assets/updates/1_109/vscode-insiders-header.webp" />
<link href="https://code.visualstudio.com/updates/v1_109" />
<updated>2026-02-04T17:00:00.000Z</updated>
<id>https://code.visualstudio.com/updates/v1_109</id>
<content type="html">Learn what is new.</content>
</entry>
</feed>
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ internal class AtomFeedHandler(
href
}
when {
isInsideItem -> channelFactory.articleBuilder.link(link)
isInsideItem -> channelFactory.articleBuilder.link(link, rel)
else -> channelFactory.channelBuilder.link(link)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ private fun AtomFeedEntity.toRssChannel(baseFeedUrl: String?): RssChannel {
link.rel != AtomKeyword.LINK_REL_ENCLOSURE.value &&
link.rel != AtomKeyword.LINK_REL_REPLIES.value
) {
link(link.generateLink(baseFeedUrl))
link(link.generateLink(baseFeedUrl), link.rel)
}
}
pubDate(entry.published)
Expand Down
Loading