Add write opml test

This commit is contained in:
Shinokuni 2020-08-08 22:40:23 +02:00
parent 5fe10a848c
commit ab86bcbcc2
6 changed files with 83 additions and 19 deletions

View File

@ -54,6 +54,7 @@ dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.squareup.retrofit2:retrofit:2.7.1'

View File

@ -1,7 +1,7 @@
<opml version="2.0">
<body>
<outline text="Folder 1" title="Folder 1">
<outline text="Subfolder 1" title="SubFolder 1">
<outline text="Subfolder 1" title="Subfolder 1">
<outline title="The Verge" xmlUrl='http://www.theverge.com/rss/index.xml' htmlUrl="http://www.theverge.com" />
<outline title="TechCrunch" xmlUrl='https://techcrunch.com/feed/' htmlUrl="https://techcrunch.com/" />
<outline xmlUrl='http://feeds.mashable.com/Mashable' />

View File

@ -1,22 +1,36 @@
package com.readrops.api
import android.Manifest
import android.content.Context
import android.os.Environment
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import com.readrops.api.opml.OPMLParser
import com.readrops.api.utils.ParseException
import com.readrops.db.entities.Feed
import com.readrops.db.entities.Folder
import io.reactivex.CompletableObserver
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
@RunWith(AndroidJUnit4::class)
class OPMLParserTest {
private val context: Context = InstrumentationRegistry.getInstrumentation().context
@get:Rule
val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
@Test
fun readOpmlTest() {
val stream = context.resources.assets.open("subscriptions.opml")
@ -53,6 +67,34 @@ class OPMLParserTest {
@Test
fun writeOpmlTest() {
val filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath
val file = File(filePath, "subscriptions.opml")
val outputStream: OutputStream = FileOutputStream(file)
val foldersAndFeeds: Map<Folder?, List<Feed>> = HashMap<Folder?, List<Feed>>().apply {
put(null, listOf(Feed("Feed1", "", "https://feed1.com"),
Feed("Feed2", "", "https://feed2.com")))
put(Folder("Folder1"), listOf())
put(Folder("Folder2"), listOf(Feed("Feed3", "", "https://feed3.com"),
Feed("Feed4", "", "https://feed4.com")))
}
OPMLParser.write(foldersAndFeeds, outputStream)
.subscribeOn(Schedulers.trampoline())
.subscribe()
outputStream.flush()
outputStream.close()
val inputStream = file.inputStream()
var foldersAndFeeds2: Map<Folder?, List<Feed>>? = null
OPMLParser.read(inputStream).subscribe { result -> foldersAndFeeds2 = result }
assertEquals(foldersAndFeeds.size, foldersAndFeeds2?.size)
assertEquals(foldersAndFeeds[Folder("Folder1")]?.size, foldersAndFeeds2?.get(Folder("Folder1"))?.size)
assertEquals(foldersAndFeeds[Folder("Folder2")]?.size, foldersAndFeeds2?.get(Folder("Folder2"))?.size)
assertEquals(foldersAndFeeds[null]?.size, foldersAndFeeds2?.get(null)?.size)
inputStream.close()
}
}

View File

@ -0,0 +1,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.readrops.api">
<!-- for tests only -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:requestLegacyExternalStorage="true" />
</manifest>

View File

@ -40,7 +40,7 @@ object OPMLParser {
val opml: OPML = serializer.read(OPML::class.java, fileString)
emitter.onSuccess(opmltoFoldersAndFeeds(opml))
emitter.onSuccess(opmlToFoldersAndFeeds(opml))
} catch (e: Exception) {
Log.d(TAG, e.message, e)
emitter.onError(e)
@ -49,7 +49,7 @@ object OPMLParser {
}
@JvmStatic
fun write(foldersAndFeeds: Map<Folder, List<Feed>>, outputStream: OutputStream): Completable {
fun write(foldersAndFeeds: Map<Folder?, List<Feed>>, outputStream: OutputStream): Completable {
return Completable.create { emitter ->
val serializer: Serializer = Persister()
serializer.write(foldersAndFeedsToOPML(foldersAndFeeds), outputStream)
@ -58,7 +58,7 @@ object OPMLParser {
}
}
private fun opmltoFoldersAndFeeds(opml: OPML): Map<Folder?, List<Feed>> {
private fun opmlToFoldersAndFeeds(opml: OPML): Map<Folder?, List<Feed>> {
if (opml.version != "2.0")
throw ParseException("Only 2.0 OPML specification is supported")
@ -84,7 +84,9 @@ object OPMLParser {
// The outline is a folder/category
if ((outline.outlines != null && !outline.outlines?.isEmpty()!!) || outline.xmlUrl.isNullOrEmpty()) {
val folder = Folder(outline.text)
// if the outline doesn't have text or title value but contains sub outlines,
// those sub outlines will be considered as not belonging to any folder and join the others at the top level of the hierarchy
val folder = if (outline.name != null) Folder(outline.name) else null
outline.outlines?.forEach {
val recursiveFeedsFolders = parseOutline(it)
@ -100,7 +102,7 @@ object OPMLParser {
} else { // the outline is a feed
if (!outline.xmlUrl.isNullOrEmpty()) {
val feed = Feed().apply {
name = outline.title
name = outline.name
url = outline.xmlUrl
siteUrl = outline.htmlUrl
}
@ -112,20 +114,27 @@ object OPMLParser {
return foldersAndFeeds
}
private fun foldersAndFeedsToOPML(foldersAndFeeds: Map<Folder, List<Feed>>): OPML {
private fun foldersAndFeedsToOPML(foldersAndFeeds: Map<Folder?, List<Feed>>): OPML {
val outlines = arrayListOf<Outline>()
for (folderAndFeeds in foldersAndFeeds) {
val outline = Outline(folderAndFeeds.key.name)
if (folderAndFeeds.key != null) {
val outline = Outline(folderAndFeeds.key?.name)
val feedOutlines = arrayListOf<Outline>()
folderAndFeeds.value.forEach { feed ->
val feedOutline = Outline(feed.name, feed.url, feed.siteUrl)
val feedOutlines = arrayListOf<Outline>()
for (feed in folderAndFeeds.value) {
val feedOutline = Outline(feed.name, feed.url, feed.siteUrl)
feedOutlines += feedOutline
feedOutlines += feedOutline
}
outline.outlines = feedOutlines
outlines += outline
} else {
for (feed in folderAndFeeds.value) {
outlines += Outline(feed.name, feed.url, feed.siteUrl)
}
}
outline.outlines = feedOutlines
outlines += outline
}
val head = Head("Subscriptions")

View File

@ -5,8 +5,8 @@ import org.simpleframework.xml.ElementList
import org.simpleframework.xml.Root
@Root(name = "outline", strict = false)
data class Outline(@field:Attribute(required = false) var title: String?,
@field:Attribute(required = false) var text: String?,
data class Outline(@field:Attribute(required = false) private var title: String?,
@field:Attribute(required = false) private var text: String?,
@field:Attribute(required = false) var type: String?,
@field:Attribute(required = false) var xmlUrl: String?,
@field:Attribute(required = false) var htmlUrl: String?,
@ -23,7 +23,10 @@ data class Outline(@field:Attribute(required = false) var title: String?,
null,
null)
constructor(title: String) : this(title, null, null, null, null, null)
constructor(title: String?) : this(title, title, null, null, null, null)
constructor(title: String, xmlUrl: String, htmlUrl: String) : this(title, title, "rss", xmlUrl, htmlUrl, null)
constructor(title: String?, xmlUrl: String, htmlUrl: String?) : this(title, title, "rss", xmlUrl, htmlUrl, null)
val name: String?
get() = title ?: text
}