mirror of https://github.com/readrops/Readrops.git
Fallback to current date time if a RSS2 item doesn't have any date
This commit is contained in:
parent
e41ab29264
commit
10a7b99e59
|
@ -6,6 +6,7 @@ import androidx.test.platform.app.InstrumentationRegistry
|
|||
import com.readrops.api.utils.DateUtils
|
||||
import com.readrops.api.utils.ParseException
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertNotNull
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
@ -37,7 +38,7 @@ class RSS2ItemsAdapterTest {
|
|||
@Test
|
||||
fun otherNamespacesTest() {
|
||||
val stream = context.resources.assets.open("localfeed/rss2/rss_items_other_namespaces.xml")
|
||||
val item = adapter.fromXml(stream)[0]
|
||||
val item = adapter.fromXml(stream).first()
|
||||
|
||||
assertEquals(item.guid, "guid")
|
||||
assertEquals(item.author, "creator 1, creator 2, creator 3, creator 4")
|
||||
|
@ -45,28 +46,30 @@ class RSS2ItemsAdapterTest {
|
|||
assertEquals(item.content, "content:encoded")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noDateTest() {
|
||||
val stream = context.resources.assets.open("localfeed/rss2/rss_items_no_date.xml")
|
||||
val item = adapter.fromXml(stream).first()
|
||||
|
||||
assertNotNull(item.pubDate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noTitleTest() {
|
||||
val stream = context.resources.assets.open("localfeed/rss2/rss_items_no_title.xml")
|
||||
Assert.assertThrows("Item title is required", ParseException::class.java) { adapter.fromXml(stream) }
|
||||
Assert.assertThrows(ParseException::class.java) { adapter.fromXml(stream) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noLinkTest() {
|
||||
val stream = context.resources.assets.open("localfeed/rss2/rss_items_no_link.xml")
|
||||
Assert.assertThrows("Item link is required", ParseException::class.java) { adapter.fromXml(stream) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noDateTest() {
|
||||
val stream = context.resources.assets.open("localfeed/rss2/rss_items_no_date.xml")
|
||||
Assert.assertThrows("Item date is required", ParseException::class.java) { adapter.fromXml(stream) }
|
||||
Assert.assertThrows(ParseException::class.java) { adapter.fromXml(stream) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun enclosureTest() {
|
||||
val stream = context.resources.assets.open("localfeed/rss2/rss_items_enclosure.xml")
|
||||
val item = adapter.fromXml(stream)[0]
|
||||
val item = adapter.fromXml(stream).first()
|
||||
|
||||
assertEquals(item.imageLink, "https://image1.jpg")
|
||||
}
|
||||
|
@ -74,7 +77,7 @@ class RSS2ItemsAdapterTest {
|
|||
@Test
|
||||
fun mediaContentTest() {
|
||||
val stream = context.resources.assets.open("localfeed/rss2/rss_items_media_content.xml")
|
||||
val item = adapter.fromXml(stream)[0]
|
||||
val item = adapter.fromXml(stream).first()
|
||||
|
||||
assertEquals(item.imageLink, "https://image2.jpg")
|
||||
}
|
||||
|
|
|
@ -5,20 +5,19 @@ import com.readrops.api.localfeed.XmlAdapter
|
|||
import com.readrops.api.localfeed.XmlAdapter.Companion.AUTHORS_MAX
|
||||
import com.readrops.api.utils.*
|
||||
import com.readrops.db.entities.Item
|
||||
import org.joda.time.LocalDateTime
|
||||
import java.io.InputStream
|
||||
|
||||
class RSS2ItemsAdapter : XmlAdapter<List<Item>> {
|
||||
|
||||
override fun fromXml(inputStream: InputStream): List<Item> {
|
||||
val konsume = inputStream.konsumeXml()
|
||||
val konsumer = inputStream.konsumeXml()
|
||||
val items = mutableListOf<Item>()
|
||||
|
||||
return try {
|
||||
konsume.child("rss") {
|
||||
konsumer.child("rss") {
|
||||
child("channel") {
|
||||
allChildrenAutoIgnore("item") {
|
||||
val enclosures = arrayListOf<String>()
|
||||
val mediaContents = arrayListOf<String>()
|
||||
val creators = arrayListOf<String?>()
|
||||
|
||||
val item = Item().apply {
|
||||
|
@ -28,67 +27,71 @@ class RSS2ItemsAdapter : XmlAdapter<List<Item>> {
|
|||
"link" -> link = nonNullText()
|
||||
"author" -> author = nullableText()
|
||||
"dc:creator" -> creators += nullableText()
|
||||
"pubDate" -> pubDate = DateUtils.parse(nonNullText())
|
||||
"dc:date" -> pubDate = DateUtils.parse(nonNullText())
|
||||
"pubDate" -> pubDate = DateUtils.parse(nullableText())
|
||||
"dc:date" -> pubDate = DateUtils.parse(nullableText())
|
||||
"guid" -> guid = nullableText()
|
||||
"description" -> description = nullableTextRecursively()
|
||||
"content:encoded" -> content = nullableTextRecursively()
|
||||
"enclosure" -> parseEnclosure(this, enclosures)
|
||||
"media:content" -> parseMediaContent(this, mediaContents)
|
||||
"media:group" -> parseMediaGroup(this, mediaContents)
|
||||
"enclosure" -> parseEnclosure(this, item = this@apply)
|
||||
"media:content" -> parseMediaContent(this, item = this@apply)
|
||||
"media:group" -> parseMediaGroup(this, item = this@apply)
|
||||
else -> skipContents() // for example media:description
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validateItem(item)
|
||||
if (item.guid == null) item.guid = item.link
|
||||
if (item.author == null && creators.filterNotNull().isNotEmpty())
|
||||
item.author = creators.filterNotNull().joinToString(limit = AUTHORS_MAX)
|
||||
|
||||
if (enclosures.isNotEmpty()) item.imageLink = enclosures.first()
|
||||
else if (mediaContents.isNotEmpty()) item.imageLink = mediaContents.first()
|
||||
finalizeItem(item, creators)
|
||||
|
||||
items += item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
konsume.close()
|
||||
konsumer.close()
|
||||
items
|
||||
} catch (e: KonsumerException) {
|
||||
throw ParseException(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseEnclosure(konsume: Konsumer, enclosures: MutableList<String>) {
|
||||
if (konsume.attributes.getValueOpt("type") != null
|
||||
&& LibUtils.isMimeImage(konsume.attributes["type"]))
|
||||
enclosures += konsume.attributes["url"]
|
||||
private fun parseEnclosure(konsumer: Konsumer, item: Item) {
|
||||
if (konsumer.attributes.getValueOpt("type") != null
|
||||
&& LibUtils.isMimeImage(konsumer.attributes["type"]) && item.imageLink == null)
|
||||
item.imageLink = konsumer.attributes.getValueOpt("url")
|
||||
}
|
||||
|
||||
private fun parseMediaContent(konsume: Konsumer, mediaContents: MutableList<String>) {
|
||||
if (konsume.attributes.getValueOpt("medium") != null
|
||||
&& LibUtils.isMimeImage(konsume.attributes["medium"]))
|
||||
mediaContents += konsume.attributes["url"]
|
||||
private fun parseMediaContent(konsumer: Konsumer, item: Item) {
|
||||
if (konsumer.attributes.getValueOpt("medium") != null
|
||||
&& LibUtils.isMimeImage(konsumer.attributes["medium"]) && item.imageLink == null)
|
||||
item.imageLink = konsumer.attributes.getValueOpt("url")
|
||||
|
||||
konsume.skipContents() // ignore media content sub elements
|
||||
konsumer.skipContents() // ignore media content sub elements
|
||||
}
|
||||
|
||||
private fun parseMediaGroup(konsume: Konsumer, mediaContents: MutableList<String>) {
|
||||
konsume.allChildrenAutoIgnore("content") {
|
||||
private fun parseMediaGroup(konsumer: Konsumer, item: Item) {
|
||||
konsumer.allChildrenAutoIgnore("content") {
|
||||
when (tagName) {
|
||||
"media:content" -> parseMediaContent(this, mediaContents)
|
||||
"media:content" -> parseMediaContent(this, item)
|
||||
else -> skipContents()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun finalizeItem(item: Item, creators: List<String?>) {
|
||||
item.apply {
|
||||
validateItem(this)
|
||||
|
||||
if (pubDate == null) pubDate = LocalDateTime.now()
|
||||
if (guid == null) guid = link
|
||||
if (author == null && creators.filterNotNull().isNotEmpty())
|
||||
author = creators.filterNotNull().joinToString(limit = AUTHORS_MAX)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateItem(item: Item) {
|
||||
when {
|
||||
item.title == null -> throw ParseException("Item title is required")
|
||||
item.link == null -> throw ParseException("Item link is required")
|
||||
item.pubDate == null -> throw ParseException("Item date is required")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package com.readrops.api.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.joda.time.LocalDateTime;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
|
@ -9,6 +13,8 @@ import java.util.Locale;
|
|||
|
||||
public final class DateUtils {
|
||||
|
||||
private static final String TAG = DateUtils.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Base of common RSS 2 date formats.
|
||||
* Examples :
|
||||
|
@ -30,20 +36,30 @@ public final class DateUtils {
|
|||
*/
|
||||
private static final String ATOM_JSON_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
|
||||
|
||||
@Nullable
|
||||
public static LocalDateTime parse(String value) {
|
||||
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
|
||||
.appendOptional(DateTimeFormat.forPattern(RSS_2_BASE_PATTERN + " ").getParser()) // with timezone
|
||||
.appendOptional(DateTimeFormat.forPattern(RSS_2_BASE_PATTERN).getParser()) // no timezone, important order here
|
||||
.appendOptional(DateTimeFormat.forPattern(ATOM_JSON_DATE_FORMAT).getParser())
|
||||
.appendOptional(DateTimeFormat.forPattern(GMT_PATTERN).getParser())
|
||||
.appendOptional(DateTimeFormat.forPattern(OFFSET_PATTERN).getParser())
|
||||
.appendOptional(DateTimeFormat.forPattern(ISO_PATTERN).getParser())
|
||||
.appendOptional(DateTimeFormat.forPattern(EDT_PATTERN).getParser())
|
||||
.toFormatter()
|
||||
.withLocale(Locale.ENGLISH)
|
||||
.withOffsetParsed();
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatter.parseLocalDateTime(value);
|
||||
try {
|
||||
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
|
||||
.appendOptional(DateTimeFormat.forPattern(RSS_2_BASE_PATTERN + " ").getParser()) // with timezone
|
||||
.appendOptional(DateTimeFormat.forPattern(RSS_2_BASE_PATTERN).getParser()) // no timezone, important order here
|
||||
.appendOptional(DateTimeFormat.forPattern(ATOM_JSON_DATE_FORMAT).getParser())
|
||||
.appendOptional(DateTimeFormat.forPattern(GMT_PATTERN).getParser())
|
||||
.appendOptional(DateTimeFormat.forPattern(OFFSET_PATTERN).getParser())
|
||||
.appendOptional(DateTimeFormat.forPattern(ISO_PATTERN).getParser())
|
||||
.appendOptional(DateTimeFormat.forPattern(EDT_PATTERN).getParser())
|
||||
.toFormatter()
|
||||
.withLocale(Locale.ENGLISH)
|
||||
.withOffsetParsed();
|
||||
|
||||
return formatter.parseLocalDateTime(value);
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String formattedDateByLocal(LocalDateTime dateTime) {
|
||||
|
|
Loading…
Reference in New Issue