Skip to content

Commit

Permalink
Merge branch 'release/2.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
prof18 committed Jan 4, 2020
2 parents e81b46e + 8abd156 commit c800826
Show file tree
Hide file tree
Showing 24 changed files with 383 additions and 108 deletions.
37 changes: 20 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ Versions 1.4 and 1.4.1 have been deleted due to a critical dependency error. Ple

## About

This is an Android library to parse a RSS Feed. You can retrive the following information about an article:
This is an Android library to parse a RSS Feed. You can retrieve the following information about an RSS channel:
<ul>
<li> Title
<li> Description
<li> Link
<li> Articles
<li> Image (Title, Url and Link)
</ul>

Articles can have following attributes:
<ul>
<li> Title
<li> Author
Expand All @@ -27,7 +36,7 @@ This is an Android library to parse a RSS Feed. You can retrive the following in
The library is uploaded in jCenter, so you can easily add the dependency:
```Gradle
dependencies {
compile 'com.prof.rssparser:rssparser:2.0.4'
compile 'com.prof.rssparser:rssparser:2.1.0'
}
```
#### Use:
Expand All @@ -46,8 +55,8 @@ private val url = "https://www.androidauthority.com/feed"
coroutineScope.launch(Dispatchers.Main) {
try {
val parser = Parser()
val articleList = parser.getArticles(url)
// The list contains all article's data. For example you can use it for your adapter.
val articleList = parser.getChannel(url)
// Show the channel data
} catch (e: Exception) {
// Handle the exception
}
Expand All @@ -67,8 +76,8 @@ parser.onFinish(new OnTaskCompleted() {

//what to do when the parsing is done
@Override
public void onTaskCompleted(List<Article> list) {
// The list contains all article's data. For example you can use it for your adapter.
public void onTaskCompleted(Channel channel) {
// Use the channel info
}

//what to do in case of error
Expand All @@ -94,9 +103,8 @@ Parser parser = new Parser();
parser.onFinish(new Parser.OnTaskCompleted() {

@Override
public void onTaskCompleted(ArrayList<Article> list) {
public void onTaskCompleted(Channel channel) {
//what to do when the parsing is done
//the Array List contains all article's data. For example you can use it for your adapter.
}

@Override
Expand All @@ -114,17 +122,12 @@ I wrote a simple app that shows articles from Android Authority. If in the artic
The sample is written both in Kotlin and Java. You can browse the Kotlin code [here](https://github.com/prof18/RSS-Parser/tree/master/samplekotlin) and the Java code [here](https://github.com/prof18/RSS-Parser/tree/master/samplejava)
You can also download the <a href="https://github.com/prof18/RSS-Parser/blob/master/RSS%20Parser.apk"> apk file</a> to try it!

Please use the issues tracker only to report issues. If you have any kind of question you can ask it on [the blog post that I wrote](https://medium.com/@marcogomiero/how-to-easily-handle-rss-feeds-on-android-with-rss-parser-8acc98e8926f)
Please use the issues tracker only to report issues. If you have any kind of question you can ask it on [the blog post that I wrote](https://www.marcogomiero.com/posts/2017/rss-parser-library/)

## Changelog
- 28 September 2019 - Add the possibility to provide a custom OkHttpClient (Issue #43). Add handling of encoding (Issue #45). Add the possibility to stop the fetching and parsing process (Issue #30). Migration to Gradle 5. - Version 2.0.5
- 18 May 2019 - Fix parsing image url from enclosure tag (PR #35). Support parsing time tag (PR #34). Add parsing of guid (Issue #31) - Version 2.0.4
- 24 January 2019 - Now the date of an article is saved as String, to extend the compatibility with different formats - Version 2.0.3
- 11 January 2019 - Moved the Java parsing on a background thread - Version 2.0.2
- 2 January 2019 - Fixed an error on Date parsing - Version 2.0.1
- 22 December 2018 - Rewrote library with Kotlin - Version 2.0.0
- 8 December 2018 - Include thrown exception in onError() callback (PR #22) - Version 1.4.5
- 7 September 2018 - Added more sources for the featured image. Removed unused resources and improved the parsing of the image. Fixed dependency errors. - Version 1.4.4

From version 1.4.4 and above, the changelog is available in the [release section.](https://github.com/prof18/RSS-Parser/releases)

- 14 December 2017 - Little fixes on Error Management - Version 1.3.1
- 13 December 2017 - Improved Error Management - Version 1.3
- 10 December 2017 - Added support for the categories - Version 1.2
Expand Down
10 changes: 5 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ apply plugin: "com.github.ben-manes.versions"

buildscript {
ext.versions = [
'libVersionCode' : 20005,
'libVersionName' : '2.0.5',
'libVersionCode' : 21000,
'libVersionName' : '2.1.0',
'compileSdk' : 29,
'minSdk' : 14,
'targetSdk' : 29,
'buildTools' : '29.0.0',
'okhttp' : '3.12.0',
'coroutines' : '1.3.2',
'kotlin' : '1.3.50',
'kotlin' : '1.3.61',
'appCompat' : '1.1.0',
'constraintLayout' : '1.1.3',
'material' : '1.0.0',
Expand All @@ -35,11 +35,11 @@ buildscript {
}

dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.android.tools.build:gradle:3.5.3'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:${versions.kotlin}"
classpath "com.github.ben-manes:gradle-versions-plugin:0.25.0"
classpath "com.github.ben-manes:gradle-versions-plugin:0.27.0"
}
}

Expand Down
6 changes: 3 additions & 3 deletions rssparser/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ android {
defaultConfig {
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
versionCode 20003
versionName "2.0.3"
versionCode 21000
versionName "2.1.0"
}

buildTypes {
Expand All @@ -35,7 +35,7 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutines}"

testImplementation "junit:junit:${versions.junit}"
testImplementation "org.robolectric:robolectric:4.2"
testImplementation "org.robolectric:robolectric:4.3.1"
}


Expand Down
9 changes: 9 additions & 0 deletions rssparser/src/main/java/com/prof/rssparser/Channel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.prof.rssparser

data class Channel(
val title: String? = null,
val link: String? = null,
val description: String? = null,
val image: Image? = null,
val articles: MutableList<Article> = mutableListOf()
)
7 changes: 7 additions & 0 deletions rssparser/src/main/java/com/prof/rssparser/Image.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.prof.rssparser

data class Image(
var title: String? = null,
var url: String? = null,
var link: String? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
package com.prof.rssparser

interface OnTaskCompleted {
fun onTaskCompleted(list: MutableList<Article>)
fun onTaskCompleted(channel: Channel)
fun onError(e: Exception)
}
42 changes: 22 additions & 20 deletions rssparser/src/main/java/com/prof/rssparser/Parser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,21 @@ package com.prof.rssparser
import com.prof.rssparser.engine.XMLFetcher
import com.prof.rssparser.engine.XMLParser
import com.prof.rssparser.enginecoroutine.CoroutineEngine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
import okhttp3.OkHttpClient
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import kotlin.Exception
import kotlin.coroutines.CoroutineContext

class Parser(private val okHttpClient: OkHttpClient? = null) {

private lateinit var onComplete: OnTaskCompleted
private lateinit var service: ExecutorService

private val parserJob = Job()
private val coroutineContext: CoroutineContext
get() = parserJob + Dispatchers.Default

fun onFinish(onComplete: OnTaskCompleted) {
this.onComplete = onComplete
}
Expand All @@ -55,30 +57,30 @@ class Parser(private val okHttpClient: OkHttpClient? = null) {

/**
* Cancel the execution of the fetching and the parsing.
*
* N.B. this method works only if the parsing is performed with [execute], i.e. with the Java
* implementation. If the parsing is performed with [getArticles], i.e. with the Kotlin
* implementation, you have to stop the parsing using your coroutines Job.
* For example, [https://github.com/prof18/RSS-Parser/blob/753d297aa6b792c8da7e472d315cdec54f56abb6/samplekotlin/src/main/java/com/prof/rssparser/sample/kotlin/MainViewModel.kt#L61]
*
*/
fun cancel() {
try {
if (::service.isInitialized && !service.isShutdown) {
service.shutdownNow()
if (::service.isInitialized) {
// The client is using Java!
try {
if (!service.isShutdown) {
service.shutdownNow()
}
} catch (e: Exception) {
onComplete.onError(e)
}
} else {
// The client is using Kotlin and coroutines
if (coroutineContext.isActive) {
coroutineContext.cancel()
}
} catch (e: Exception) {
onComplete.onError(e)
}
}

@Throws(Exception::class)
suspend fun getArticles(url: String) =
withContext(Dispatchers.IO) {
val xml = async { CoroutineEngine.fetchXML(url, okHttpClient) }
suspend fun getChannel(url: String) =
withContext(coroutineContext) {
val xml = CoroutineEngine.fetchXML(url, okHttpClient)
return@withContext CoroutineEngine.parseXML(xml)
}


}

53 changes: 45 additions & 8 deletions rssparser/src/main/java/com/prof/rssparser/core/CoreXMLParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@

package com.prof.rssparser.core

import android.util.Log
import com.prof.rssparser.Article
import com.prof.rssparser.Channel
import com.prof.rssparser.Image
import com.prof.rssparser.utils.RSSKeywords
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
Expand All @@ -29,8 +32,12 @@ import java.util.regex.Pattern
object CoreXMLParser {

@Throws(XmlPullParserException::class, IOException::class)
fun parseXML(xml: String): MutableList<Article> {
fun parseXML(xml: String): Channel {

var channelTitle: String? = null
var channelLink: String? = null
var channelDescription: String? = null
var channelImage: Image? = null
val articleList = mutableListOf<Article>()
var currentArticle = Article()

Expand All @@ -43,6 +50,8 @@ object CoreXMLParser {

// A flag just to be sure of the correct parsing
var insideItem = false
var insideChannel = false
var insideChannelImage = false

var eventType = xmlPullParser.eventType

Expand All @@ -51,17 +60,34 @@ object CoreXMLParser {

// Start parsing the item
if (eventType == XmlPullParser.START_TAG) {
if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM, ignoreCase = true)) {
if (xmlPullParser.name.equals(RSSKeywords.RSS_CHANNEL, ignoreCase = true)) {
insideChannel = true
insideItem = false
insideChannelImage = false

} else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM, ignoreCase = true)) {
insideItem = true
insideChannel = false
insideChannelImage = false

} else if (xmlPullParser.name.equals(RSSKeywords.RSS_CHANNEL_IMAGE, ignoreCase = true)) {
insideItem = false
insideChannel = false
insideChannelImage = true
channelImage = Image()

} else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_TITLE, ignoreCase = true)) {
if (insideItem) {
currentArticle.title = xmlPullParser.nextText().trim()
when {
insideChannel -> channelTitle = xmlPullParser.nextText().trim()
insideChannelImage -> channelImage?.title = xmlPullParser.nextText().trim()
insideItem -> currentArticle.title = xmlPullParser.nextText().trim()
}

} else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_LINK, ignoreCase = true)) {
if (insideItem) {
currentArticle.link = xmlPullParser.nextText().trim()
when {
insideChannel -> channelLink = xmlPullParser.nextText().trim()
insideChannelImage -> channelImage?.link = xmlPullParser.nextText().trim()
insideItem -> currentArticle.link = xmlPullParser.nextText().trim()
}

} else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_AUTHOR, ignoreCase = true)) {
Expand All @@ -79,6 +105,12 @@ object CoreXMLParser {
currentArticle.image = xmlPullParser.getAttributeValue(null, RSSKeywords.RSS_ITEM_URL)
}

} else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_URL, ignoreCase = true)) {
if (insideChannelImage) {
channelImage?.url = xmlPullParser.nextText().trim()
Log.d("PARSER", "")
}

} else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_ENCLOSURE, ignoreCase = true)) {
if (insideItem) {
val type = xmlPullParser.getAttributeValue(null, RSSKeywords.RSS_ITEM_TYPE)
Expand All @@ -88,7 +120,9 @@ object CoreXMLParser {
}

} else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_DESCRIPTION, ignoreCase = true)) {
if (insideItem) {
if (insideChannel) {
channelDescription = xmlPullParser.nextText().trim()
} else if (insideItem) {
val description = xmlPullParser.nextText()
currentArticle.description = description.trim()
if (currentArticle.image == null) {
Expand All @@ -114,15 +148,18 @@ object CoreXMLParser {
// Skip to be able to find date inside 'tag' tag
continue
}

} else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_TIME, ignoreCase = true)) {
if (insideItem) {
currentArticle.pubDate = xmlPullParser.nextText()
}

} else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_GUID, ignoreCase = true)) {
if (insideItem) {
currentArticle.guid = xmlPullParser.nextText().trim()
}
}

} else if (eventType == XmlPullParser.END_TAG && xmlPullParser.name.equals("item", ignoreCase = true)) {
// The item is correctly parsed
insideItem = false
Expand All @@ -131,7 +168,7 @@ object CoreXMLParser {
}
eventType = xmlPullParser.next()
}
return articleList
return Channel(channelTitle, channelLink, channelDescription, channelImage, articleList)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@

package com.prof.rssparser.engine

import com.prof.rssparser.Article
import com.prof.rssparser.Channel
import com.prof.rssparser.core.CoreXMLParser
import java.lang.Exception
import java.util.concurrent.Callable

class XMLParser(var xml: String) : Callable<MutableList<Article>> {
class XMLParser(var xml: String) : Callable<Channel> {

@Throws(Exception::class)
override fun call(): MutableList<Article> {
override fun call(): Channel {
return CoreXMLParser.parseXML(xml)
}
}
Loading

0 comments on commit c800826

Please sign in to comment.