From 42b3754aa0dad90055a7d91a914579291354d998 Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 15 Jan 2021 13:51:01 +0800 Subject: [PATCH] Add pure Kotlin automix parser and tests. (#65) --- .../parser/AndroidAutoMixParser.kt | 43 +-- .../kotlin/extension/ModelExtensions.kt | 46 +++ .../kotlin/parser/AutoMixParser.kt | 287 ++++++++++++++++++ .../ktrssreader/kotlin/parser/GoogleParser.kt | 32 +- .../ktrssreader/kotlin/parser/ITunesParser.kt | 36 +-- .../ktrssreader/kotlin/parser/ParserBase.kt | 148 ++++++--- .../kotlin/parser/RssStandardParser.kt | 42 +-- .../kotlin/AutoMixParserLocalTest.kt | 89 ++++++ .../kotlin/RssStandardParserLocalTest.kt | 6 +- 9 files changed, 568 insertions(+), 161 deletions(-) create mode 100644 kotlin/src/main/java/tw/ktrssreader/kotlin/extension/ModelExtensions.kt create mode 100644 kotlin/src/main/java/tw/ktrssreader/kotlin/parser/AutoMixParser.kt create mode 100644 kotlin/src/test/java/tw/ktrssreader/kotlin/AutoMixParserLocalTest.kt diff --git a/android/src/main/java/tw/ktrssreader/parser/AndroidAutoMixParser.kt b/android/src/main/java/tw/ktrssreader/parser/AndroidAutoMixParser.kt index cd53463..21fb6ec 100644 --- a/android/src/main/java/tw/ktrssreader/parser/AndroidAutoMixParser.kt +++ b/android/src/main/java/tw/ktrssreader/parser/AndroidAutoMixParser.kt @@ -18,10 +18,10 @@ package tw.ktrssreader.parser import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParserException -import tw.ktrssreader.kotlin.constant.ParserConst import tw.ktrssreader.kotlin.constant.ParserConst.AUTHOR import tw.ktrssreader.kotlin.constant.ParserConst.BLOCK import tw.ktrssreader.kotlin.constant.ParserConst.CATEGORY +import tw.ktrssreader.kotlin.constant.ParserConst.CHANNEL import tw.ktrssreader.kotlin.constant.ParserConst.CLOUD import tw.ktrssreader.kotlin.constant.ParserConst.COMMENTS import tw.ktrssreader.kotlin.constant.ParserConst.COPYRIGHT @@ -79,6 +79,8 @@ import tw.ktrssreader.kotlin.constant.ParserConst.TYPE import tw.ktrssreader.kotlin.constant.ParserConst.URL import tw.ktrssreader.kotlin.constant.ParserConst.WEB_MASTER import tw.ktrssreader.kotlin.constant.ParserConst.WIDTH +import tw.ktrssreader.kotlin.extension.hrefToImage +import tw.ktrssreader.kotlin.extension.replaceInvalidUrlByPriority import tw.ktrssreader.kotlin.model.channel.* import tw.ktrssreader.kotlin.model.item.* import tw.ktrssreader.utils.logD @@ -93,6 +95,10 @@ class AndroidAutoMixParser : AndroidParserBase() { override val logTag: String = this::class.java.simpleName override fun parse(xml: String): AutoMixChannelData { + rssStandardMap.clear() + iTunesMap.clear() + googlePlayMap.clear() + parseTag(xml) return AutoMixChannelData( @@ -139,7 +145,7 @@ class AndroidAutoMixParser : AndroidParserBase() { @Throws(IOException::class, XmlPullParserException::class) private fun parseTag(xml: String) = parseChannel(xml = xml) { - require(XmlPullParser.START_TAG, null, ParserConst.CHANNEL) + require(XmlPullParser.START_TAG, null, CHANNEL) var title: String? = null var description: String? = null @@ -232,7 +238,7 @@ class AndroidAutoMixParser : AndroidParserBase() { else -> skip() } } - require(XmlPullParser.END_TAG, null, ParserConst.CHANNEL) + require(XmlPullParser.END_TAG, null, CHANNEL) rssStandardMap.run { put(TITLE, title) @@ -257,7 +263,7 @@ class AndroidAutoMixParser : AndroidParserBase() { put(ITEM, items.takeIf { it.isNotEmpty() }) } iTunesMap.run { - put(IMAGE, hrefToImage(iTunesImageHref)) + put(IMAGE, iTunesImageHref.hrefToImage()) put(CATEGORY, iTunesCategories?.takeIf { it.isNotEmpty() }) put(ITUNES_TITLE, iTunesSimpleTitle) put(EXPLICIT, iTunesExplicit) @@ -271,7 +277,7 @@ class AndroidAutoMixParser : AndroidParserBase() { } googlePlayMap.run { put(DESCRIPTION, googleDescription) - put(IMAGE, hrefToImage(googleImageHref)) + put(IMAGE, googleImageHref.hrefToImage()) put(CATEGORY, googleCategories?.takeIf { it.isNotEmpty() }) put(EXPLICIT, googleExplicit) put(EMAIL, googleEmail) @@ -473,31 +479,4 @@ class AndroidAutoMixParser : AndroidParserBase() { block = iTunesBlock ?: googleBlock ) } - - private fun Image?.replaceInvalidUrlByPriority(vararg priorityHref: String?): Image? { - if (this == null || url != null) return this - val href = priorityHref.firstOrNull { null != it } ?: return this - - return Image( - link = link, - title = title, - url = href, - description = description, - height = height, - width = width - ) - } - - private fun hrefToImage(href: String?): Image? { - href ?: return null - - return Image( - link = null, - title = null, - url = href, - description = null, - height = null, - width = null - ) - } } \ No newline at end of file diff --git a/kotlin/src/main/java/tw/ktrssreader/kotlin/extension/ModelExtensions.kt b/kotlin/src/main/java/tw/ktrssreader/kotlin/extension/ModelExtensions.kt new file mode 100644 index 0000000..779a90b --- /dev/null +++ b/kotlin/src/main/java/tw/ktrssreader/kotlin/extension/ModelExtensions.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2020 Feng Hsien Hsu, Siao Syuan Yang, Wei-Qi Wang, Ya-Han Tsai, Yu Hao Wu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tw.ktrssreader.kotlin.extension + +import tw.ktrssreader.kotlin.model.channel.Image + +fun Image?.replaceInvalidUrlByPriority(vararg priorityHref: String?): Image? { + if (this == null || url != null) return this + val href = priorityHref.firstOrNull { null != it } ?: return this + + return Image( + link = link, + title = title, + url = href, + description = description, + height = height, + width = width + ) +} + +fun String?.hrefToImage(): Image? { + this ?: return null + + return Image( + link = null, + title = null, + url = this, + description = null, + height = null, + width = null + ) +} \ No newline at end of file diff --git a/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/AutoMixParser.kt b/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/AutoMixParser.kt new file mode 100644 index 0000000..5770afe --- /dev/null +++ b/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/AutoMixParser.kt @@ -0,0 +1,287 @@ +/* + * Copyright 2020 Feng Hsien Hsu, Siao Syuan Yang, Wei-Qi Wang, Ya-Han Tsai, Yu Hao Wu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tw.ktrssreader.kotlin.parser + +import org.w3c.dom.Element +import tw.ktrssreader.kotlin.constant.ParserConst.AUTHOR +import tw.ktrssreader.kotlin.constant.ParserConst.BLOCK +import tw.ktrssreader.kotlin.constant.ParserConst.CATEGORY +import tw.ktrssreader.kotlin.constant.ParserConst.CHANNEL +import tw.ktrssreader.kotlin.constant.ParserConst.CLOUD +import tw.ktrssreader.kotlin.constant.ParserConst.COMMENTS +import tw.ktrssreader.kotlin.constant.ParserConst.COPYRIGHT +import tw.ktrssreader.kotlin.constant.ParserConst.DESCRIPTION +import tw.ktrssreader.kotlin.constant.ParserConst.DOCS +import tw.ktrssreader.kotlin.constant.ParserConst.EMAIL +import tw.ktrssreader.kotlin.constant.ParserConst.EXPLICIT +import tw.ktrssreader.kotlin.constant.ParserConst.GENERATOR +import tw.ktrssreader.kotlin.constant.ParserConst.GOOGLE_AUTHOR +import tw.ktrssreader.kotlin.constant.ParserConst.GOOGLE_BLOCK +import tw.ktrssreader.kotlin.constant.ParserConst.GOOGLE_CATEGORY +import tw.ktrssreader.kotlin.constant.ParserConst.GOOGLE_DESCRIPTION +import tw.ktrssreader.kotlin.constant.ParserConst.GOOGLE_EMAIL +import tw.ktrssreader.kotlin.constant.ParserConst.GOOGLE_EXPLICIT +import tw.ktrssreader.kotlin.constant.ParserConst.GOOGLE_IMAGE +import tw.ktrssreader.kotlin.constant.ParserConst.HREF +import tw.ktrssreader.kotlin.constant.ParserConst.IMAGE +import tw.ktrssreader.kotlin.constant.ParserConst.ITEM +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_AUTHOR +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_BLOCK +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_CATEGORY +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_COMPLETE +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_DURATION +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_EMAIL +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_EPISODE +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_EPISODE_TYPE +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_EXPLICIT +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_IMAGE +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_NEW_FEED_URL +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_SEASON +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_TITLE +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_TYPE +import tw.ktrssreader.kotlin.constant.ParserConst.LANGUAGE +import tw.ktrssreader.kotlin.constant.ParserConst.LAST_BUILD_DATE +import tw.ktrssreader.kotlin.constant.ParserConst.LINK +import tw.ktrssreader.kotlin.constant.ParserConst.MANAGING_EDITOR +import tw.ktrssreader.kotlin.constant.ParserConst.OWNER +import tw.ktrssreader.kotlin.constant.ParserConst.PUB_DATE +import tw.ktrssreader.kotlin.constant.ParserConst.RATING +import tw.ktrssreader.kotlin.constant.ParserConst.SKIP_DAYS +import tw.ktrssreader.kotlin.constant.ParserConst.SKIP_HOURS +import tw.ktrssreader.kotlin.constant.ParserConst.TEXT_INPUT +import tw.ktrssreader.kotlin.constant.ParserConst.TITLE +import tw.ktrssreader.kotlin.constant.ParserConst.TTL +import tw.ktrssreader.kotlin.constant.ParserConst.TYPE +import tw.ktrssreader.kotlin.constant.ParserConst.WEB_MASTER +import tw.ktrssreader.kotlin.extension.hrefToImage +import tw.ktrssreader.kotlin.extension.replaceInvalidUrlByPriority +import tw.ktrssreader.kotlin.model.channel.* +import tw.ktrssreader.kotlin.model.item.* + +class AutoMixParser : ParserBase() { + + private val rssStandardMap = HashMap(20) + private val iTunesMap = HashMap(11) + private val googlePlayMap = HashMap(8) + + override fun parse(xml: String): AutoMixChannel { + return parseChannel(xml) { + rssStandardMap.clear() + iTunesMap.clear() + googlePlayMap.clear() + parseChannelTags() + + AutoMixChannelData( + title = priorityMapValueOf(TITLE), + description = priorityMapValueOf(DESCRIPTION), + image = priorityMapValueOf(IMAGE), + language = priorityMapValueOf(LANGUAGE), + categories = priorityMapValueOf(CATEGORY), + link = priorityMapValueOf(LINK), + copyright = priorityMapValueOf(COPYRIGHT), + managingEditor = priorityMapValueOf(MANAGING_EDITOR), + webMaster = priorityMapValueOf(WEB_MASTER), + pubDate = priorityMapValueOf(PUB_DATE), + lastBuildDate = priorityMapValueOf(LAST_BUILD_DATE), + generator = priorityMapValueOf(GENERATOR), + docs = priorityMapValueOf(DOCS), + cloud = priorityMapValueOf(CLOUD), + ttl = priorityMapValueOf(TTL), + rating = priorityMapValueOf(RATING), + textInput = priorityMapValueOf(TEXT_INPUT), + skipHours = priorityMapValueOf(SKIP_HOURS), + skipDays = priorityMapValueOf(SKIP_DAYS), + items = priorityMapValueOf(ITEM), + simpleTitle = priorityMapValueOf(ITUNES_TITLE), + explicit = priorityMapValueOf(EXPLICIT), + email = priorityMapValueOf(EMAIL), + author = priorityMapValueOf(AUTHOR), + owner = priorityMapValueOf(OWNER), + type = priorityMapValueOf(TYPE), + newFeedUrl = priorityMapValueOf(ITUNES_NEW_FEED_URL), + block = priorityMapValueOf(BLOCK), + complete = priorityMapValueOf(ITUNES_COMPLETE), + ) + } + } + + /** + * The priority of reading tags as the following sequence: + * RssStandard -> iTunes -> GooglePlay + */ + private fun priorityMapValueOf(key: String): R? { + @Suppress("UNCHECKED_CAST") + return (rssStandardMap[key] ?: iTunesMap[key] ?: googlePlayMap[key]) as? R + } + + private fun Element.parseChannelTags() { + val title: String? = readString(TITLE) + val description: String? = readString(name = DESCRIPTION, parentTag = CHANNEL) + val image: Image? = readImage() + val language: String? = readString(LANGUAGE) + val categories = readCategories(CHANNEL) + val link: String? = readString(LINK) + val copyright: String? = readString(COPYRIGHT) + val managingEditor: String? = readString(MANAGING_EDITOR) + val webMaster: String? = readString(WEB_MASTER) + val pubDate: String? = readString(PUB_DATE) + val lastBuildDate: String? = readString(LAST_BUILD_DATE) + val generator: String? = readString(GENERATOR) + val docs: String? = readString(DOCS) + val cloud: Cloud? = readCloud() + val ttl: Int? = readString(TTL)?.toIntOrNull() + val rating: String? = readString(RATING) + val textInput: TextInput? = readTextInput() + val skipHours: List? = readSkipHours() + val skipDays: List? = readSkipDays() + val items = readItems() + + val iTunesImageHref: String? = getElementByTag(ITUNES_IMAGE)?.getAttributeOrNull(HREF) + val iTunesCategories: List? = readCategories( + parentTag = CHANNEL, + tagName = ITUNES_CATEGORY + ) + val iTunesSimpleTitle: String? = readString(ITUNES_TITLE) + val iTunesExplicit: Boolean? = readString(ITUNES_EXPLICIT)?.toBoolean() + val iTunesEmail: String? = readString(name = ITUNES_EMAIL, parentTag = CHANNEL) + val iTunesAuthor: String? = readString(ITUNES_AUTHOR) + val iTunesOwner: Owner? = readITunesOwner() + val iTunesType: String? = readString(ITUNES_TYPE) + val iTunesNewFeedUrl: String? = readString(ITUNES_NEW_FEED_URL) + val iTunesBlock: Boolean? = readString(ITUNES_BLOCK)?.toBoolOrNull() + val iTunesComplete: Boolean? = readString(ITUNES_COMPLETE)?.toBoolOrNull() + + val googleDescription: String? = readString(GOOGLE_DESCRIPTION) + val googleImageHref: String? = getElementByTag(GOOGLE_IMAGE)?.getAttributeOrNull(HREF) + val googleCategories: List? = + readCategories(parentTag = CHANNEL, tagName = GOOGLE_CATEGORY) + val googleExplicit: Boolean? = readString(GOOGLE_EXPLICIT)?.toBoolOrNull() + val googleEmail: String? = readString(name = GOOGLE_EMAIL, parentTag = CHANNEL) + val googleAuthor: String? = readString(GOOGLE_AUTHOR) + val googleOwner: Owner? = readGoogleOwner() + val googleBlock: Boolean? = readString(GOOGLE_BLOCK)?.toBoolOrNull() + + rssStandardMap.run { + put(TITLE, title) + put(DESCRIPTION, description) + put( + IMAGE, + image.replaceInvalidUrlByPriority(iTunesImageHref, googleImageHref) + ) + put(LANGUAGE, language) + put(CATEGORY, categories.takeIf { it.isNotEmpty() }) + put(LINK, link) + put(COPYRIGHT, copyright) + put(MANAGING_EDITOR, managingEditor) + put(WEB_MASTER, webMaster) + put(PUB_DATE, pubDate) + put(LAST_BUILD_DATE, lastBuildDate) + put(GENERATOR, generator) + put(DOCS, docs) + put(CLOUD, cloud) + put(TTL, ttl) + put(RATING, rating) + put(TEXT_INPUT, textInput) + put(SKIP_HOURS, skipHours) + put(SKIP_DAYS, skipDays) + put(ITEM, items.takeIf { it.isNotEmpty() }) + } + iTunesMap.run { + put(IMAGE, iTunesImageHref.hrefToImage()) + put(CATEGORY, iTunesCategories?.takeIf { it.isNotEmpty() }) + put(ITUNES_TITLE, iTunesSimpleTitle) + put(EXPLICIT, iTunesExplicit) + put(EMAIL, iTunesEmail) + put(AUTHOR, iTunesAuthor) + put(OWNER, iTunesOwner) + put(TYPE, iTunesType) + put(ITUNES_NEW_FEED_URL, iTunesNewFeedUrl) + put(BLOCK, iTunesBlock) + put(ITUNES_COMPLETE, iTunesComplete) + } + googlePlayMap.run { + put(DESCRIPTION, googleDescription) + put(IMAGE, googleImageHref.hrefToImage()) + put(CATEGORY, googleCategories?.takeIf { it.isNotEmpty() }) + put(EXPLICIT, googleExplicit) + put(EMAIL, googleEmail) + put(AUTHOR, googleAuthor) + put(OWNER, googleOwner) + put(BLOCK, googleBlock) + } + } + + private fun Element.readItems(): List { + val result = mutableListOf() + val nodeList = getElementsByTagName(ITEM) ?: return result + + for (i in 0 until nodeList.length) { + val element = nodeList.item(i) as? Element ?: continue + + element.run { + val title: String? = readString(name = TITLE, parentTag = ITEM) + val enclosure: Enclosure? = readEnclosure() + val guid: Guid? = readGuid() + val pubDate: String? = readString(PUB_DATE) + val description: String? = readString(name = DESCRIPTION, parentTag = ITEM) + val link: String? = readString(LINK, parentTag = ITEM) + val author: String? = readString(AUTHOR) + val categories = readCategories(ITEM) + val comments: String? = readString(COMMENTS) + val source: Source? = readSource() + + val iTunesAuthor: String? = readString(ITUNES_AUTHOR) + val iTunesSimpleTitle: String? = readString(ITUNES_TITLE) + val iTunesDuration: String? = readString(ITUNES_DURATION) + val iTunesImageHref: String? = getElementByTag(ITUNES_IMAGE)?.getAttributeOrNull(HREF) + val iTunesExplicit: Boolean? = readString(ITUNES_EXPLICIT)?.toBoolOrNull() + val iTunesEpisode: Int? = readString(ITUNES_EPISODE)?.toIntOrNull() + val iTunesSeason: Int? = readString(ITUNES_SEASON)?.toIntOrNull() + val iTunesEpisodeType: String? = readString(ITUNES_EPISODE_TYPE) + val iTunesBlock: Boolean? = readString(ITUNES_BLOCK)?.toBoolOrNull() + + val googleDescription: String? = readString(GOOGLE_DESCRIPTION) + val googleExplicit: Boolean? = readString(GOOGLE_EXPLICIT)?.toBoolOrNull() + val googleBlock: Boolean? = readString(GOOGLE_BLOCK)?.toBoolOrNull() + + result.add( + AutoMixItemData( + title = title, + enclosure = enclosure, + guid = guid, + pubDate = pubDate, + description = description ?: googleDescription, + link = link, + author = author ?: iTunesAuthor, + categories = categories.takeIf { it.isNotEmpty() }, + comments = comments, + source = source, + simpleTitle = iTunesSimpleTitle, + duration = iTunesDuration, + image = iTunesImageHref, + explicit = iTunesExplicit ?: googleExplicit, + episode = iTunesEpisode, + season = iTunesSeason, + episodeType = iTunesEpisodeType, + block = iTunesBlock ?: googleBlock + ) + ) + } + } + return result + } +} \ No newline at end of file diff --git a/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/GoogleParser.kt b/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/GoogleParser.kt index 459296f..40bb3fd 100644 --- a/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/GoogleParser.kt +++ b/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/GoogleParser.kt @@ -16,7 +16,6 @@ package tw.ktrssreader.kotlin.parser -import com.sun.org.apache.xerces.internal.dom.DeferredElementImpl import org.w3c.dom.Element import tw.ktrssreader.kotlin.constant.ParserConst.AUTHOR import tw.ktrssreader.kotlin.constant.ParserConst.CHANNEL @@ -31,7 +30,6 @@ import tw.ktrssreader.kotlin.constant.ParserConst.GOOGLE_DESCRIPTION import tw.ktrssreader.kotlin.constant.ParserConst.GOOGLE_EMAIL import tw.ktrssreader.kotlin.constant.ParserConst.GOOGLE_EXPLICIT import tw.ktrssreader.kotlin.constant.ParserConst.GOOGLE_IMAGE -import tw.ktrssreader.kotlin.constant.ParserConst.GOOGLE_OWNER import tw.ktrssreader.kotlin.constant.ParserConst.HREF import tw.ktrssreader.kotlin.constant.ParserConst.ITEM import tw.ktrssreader.kotlin.constant.ParserConst.LANGUAGE @@ -40,14 +38,11 @@ import tw.ktrssreader.kotlin.constant.ParserConst.LINK import tw.ktrssreader.kotlin.constant.ParserConst.MANAGING_EDITOR import tw.ktrssreader.kotlin.constant.ParserConst.PUB_DATE import tw.ktrssreader.kotlin.constant.ParserConst.RATING -import tw.ktrssreader.kotlin.constant.ParserConst.TEXT import tw.ktrssreader.kotlin.constant.ParserConst.TITLE import tw.ktrssreader.kotlin.constant.ParserConst.TTL import tw.ktrssreader.kotlin.constant.ParserConst.WEB_MASTER import tw.ktrssreader.kotlin.model.channel.GoogleChannelData import tw.ktrssreader.kotlin.model.channel.Image -import tw.ktrssreader.kotlin.model.channel.Owner -import tw.ktrssreader.kotlin.model.item.Category import tw.ktrssreader.kotlin.model.item.GoogleItemData class GoogleParser : ParserBase() { @@ -75,7 +70,8 @@ class GoogleParser : ParserBase() { val description = readString(name = GOOGLE_DESCRIPTION, parentTag = CHANNEL) val image = getElementByTag(GOOGLE_IMAGE)?.readGoogleImage() val explicit = readString(GOOGLE_EXPLICIT)?.toBoolOrNull() - val categories = readGoogleCategories(parentTag = CHANNEL) ?: listOf() + val categories = + readCategories(parentTag = CHANNEL, tagName = GOOGLE_CATEGORY) ?: listOf() val author = readString(GOOGLE_AUTHOR) val owner = readGoogleOwner() val block = readString(GOOGLE_BLOCK)?.toBoolOrNull() @@ -112,7 +108,7 @@ class GoogleParser : ParserBase() { } private fun Element.readGoogleImage(): Image { - val href = getAttribute(HREF) + val href = getAttributeOrNull(HREF) return Image( link = null, title = null, @@ -123,28 +119,6 @@ class GoogleParser : ParserBase() { ) } - private fun Element.readGoogleOwner(): Owner? { - val nodeList = getElementsByTagName(GOOGLE_OWNER) - if (nodeList.length == 0) return null - - return Owner(name = null, email = readString(GOOGLE_OWNER)) - } - - private fun Element.readGoogleCategories(parentTag: String): List { - val result = mutableListOf() - val nodeList = getElementsByTagName(GOOGLE_CATEGORY) ?: return result - - for (i in 0 until nodeList.length) { - val e = nodeList.item(i) as? Element ?: continue - val parent = e.parentNode as? DeferredElementImpl - if (parent?.tagName == parentTag || parent?.tagName == GOOGLE_CATEGORY) { - val name: String? = e.getAttributeOrNull(TEXT) - result.add(Category(name = name, domain = null)) - } - } - return result - } - private fun Element.readItems(): List { val result = mutableListOf() val nodeList = getElementsByTagName(ITEM) ?: return result diff --git a/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/ITunesParser.kt b/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/ITunesParser.kt index 88ec297..4358fc0 100644 --- a/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/ITunesParser.kt +++ b/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/ITunesParser.kt @@ -16,7 +16,6 @@ package tw.ktrssreader.kotlin.parser -import com.sun.org.apache.xerces.internal.dom.DeferredElementImpl import org.w3c.dom.Element import tw.ktrssreader.kotlin.constant.ParserConst.CHANNEL import tw.ktrssreader.kotlin.constant.ParserConst.COMMENTS @@ -31,14 +30,11 @@ import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_BLOCK import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_CATEGORY import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_COMPLETE import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_DURATION -import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_EMAIL import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_EPISODE import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_EPISODE_TYPE import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_EXPLICIT import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_IMAGE -import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_NAME import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_NEW_FEED_URL -import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_OWNER import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_SEASON import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_TITLE import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_TYPE @@ -48,7 +44,6 @@ import tw.ktrssreader.kotlin.constant.ParserConst.LINK import tw.ktrssreader.kotlin.constant.ParserConst.MANAGING_EDITOR import tw.ktrssreader.kotlin.constant.ParserConst.PUB_DATE import tw.ktrssreader.kotlin.constant.ParserConst.RATING -import tw.ktrssreader.kotlin.constant.ParserConst.TEXT import tw.ktrssreader.kotlin.constant.ParserConst.TITLE import tw.ktrssreader.kotlin.constant.ParserConst.TTL import tw.ktrssreader.kotlin.constant.ParserConst.WEB_MASTER @@ -82,7 +77,8 @@ class ITunesParser : ParserBase() { val image: Image? = readITunesImage() val explicit: Boolean? = readString(ITUNES_EXPLICIT)?.toBoolean() - val categories: List? = readITunesCategories(parentTag = CHANNEL) + val categories: List? = + readCategories(parentTag = CHANNEL, tagName = ITUNES_CATEGORY) val author: String? = readString(ITUNES_AUTHOR) val owner: Owner? = readITunesOwner() val simpleTitle: String? = readString(ITUNES_TITLE) @@ -127,7 +123,7 @@ class ITunesParser : ParserBase() { private fun Element.readITunesImage(): Image? { val element = getElementByTag(ITUNES_IMAGE) ?: return null - val href = element.getAttribute(HREF) + val href = element.getAttributeOrNull(HREF) return Image( link = null, title = null, @@ -138,30 +134,6 @@ class ITunesParser : ParserBase() { ) } - private fun Element.readITunesCategories(parentTag: String): List? { - val result = mutableListOf() - val nodeList = getElementsByTagName(ITUNES_CATEGORY) ?: return null - - for (i in 0 until nodeList.length) { - val e = nodeList.item(i) as? Element ?: continue - val parent = e.parentNode as? DeferredElementImpl - if (parent?.tagName == parentTag || parent?.tagName == ITUNES_CATEGORY) { - result.add(Category(name = e.getAttributeOrNull(TEXT), domain = null)) - } - } - return result - } - - private fun Element.readITunesOwner(): Owner? { - val nodeList = getElementsByTagName(ITUNES_OWNER) ?: return null - if (nodeList.length == 0) return null - - val element = getElementByTag(ITUNES_OWNER) - val name = element?.readString(ITUNES_NAME) - val email = element?.readString(ITUNES_EMAIL) - return Owner(name = name, email = email) - } - private fun Element.readItems(): List { val result = mutableListOf() val nodeList = getElementsByTagName(ITEM) ?: return result @@ -181,7 +153,7 @@ class ITunesParser : ParserBase() { val simpleTitle: String? = element.readString(ITUNES_TITLE) val duration: String? = element.readString(ITUNES_DURATION) - val image: String? = element.getElementByTag(ITUNES_IMAGE)?.getAttribute(HREF) + val image: String? = element.getElementByTag(ITUNES_IMAGE)?.getAttributeOrNull(HREF) val explicit: Boolean? = element.readString(ITUNES_EXPLICIT)?.toBoolOrNull() val episode: Int? = element.readString(ITUNES_EPISODE)?.toIntOrNull() val season: Int? = element.readString(ITUNES_SEASON)?.toIntOrNull() diff --git a/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/ParserBase.kt b/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/ParserBase.kt index c4f3d0b..fecd2f6 100644 --- a/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/ParserBase.kt +++ b/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/ParserBase.kt @@ -18,11 +18,39 @@ package tw.ktrssreader.kotlin.parser import com.sun.org.apache.xerces.internal.dom.DeferredElementImpl import org.w3c.dom.Element -import tw.ktrssreader.kotlin.constant.ParserConst -import tw.ktrssreader.kotlin.model.channel.Cloud -import tw.ktrssreader.kotlin.model.channel.Image -import tw.ktrssreader.kotlin.model.channel.RssStandardChannel -import tw.ktrssreader.kotlin.model.channel.TextInput +import tw.ktrssreader.kotlin.constant.ParserConst.CATEGORY +import tw.ktrssreader.kotlin.constant.ParserConst.CHANNEL +import tw.ktrssreader.kotlin.constant.ParserConst.CLOUD +import tw.ktrssreader.kotlin.constant.ParserConst.DAY +import tw.ktrssreader.kotlin.constant.ParserConst.DESCRIPTION +import tw.ktrssreader.kotlin.constant.ParserConst.DOMAIN +import tw.ktrssreader.kotlin.constant.ParserConst.ENCLOSURE +import tw.ktrssreader.kotlin.constant.ParserConst.GOOGLE_OWNER +import tw.ktrssreader.kotlin.constant.ParserConst.GUID +import tw.ktrssreader.kotlin.constant.ParserConst.HEIGHT +import tw.ktrssreader.kotlin.constant.ParserConst.HOUR +import tw.ktrssreader.kotlin.constant.ParserConst.IMAGE +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_EMAIL +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_NAME +import tw.ktrssreader.kotlin.constant.ParserConst.ITUNES_OWNER +import tw.ktrssreader.kotlin.constant.ParserConst.LENGTH +import tw.ktrssreader.kotlin.constant.ParserConst.LINK +import tw.ktrssreader.kotlin.constant.ParserConst.NAME +import tw.ktrssreader.kotlin.constant.ParserConst.PATH +import tw.ktrssreader.kotlin.constant.ParserConst.PERMALINK +import tw.ktrssreader.kotlin.constant.ParserConst.PORT +import tw.ktrssreader.kotlin.constant.ParserConst.PROTOCOL +import tw.ktrssreader.kotlin.constant.ParserConst.REGISTER_PROCEDURE +import tw.ktrssreader.kotlin.constant.ParserConst.SKIP_DAYS +import tw.ktrssreader.kotlin.constant.ParserConst.SKIP_HOURS +import tw.ktrssreader.kotlin.constant.ParserConst.SOURCE +import tw.ktrssreader.kotlin.constant.ParserConst.TEXT +import tw.ktrssreader.kotlin.constant.ParserConst.TEXT_INPUT +import tw.ktrssreader.kotlin.constant.ParserConst.TITLE +import tw.ktrssreader.kotlin.constant.ParserConst.TYPE +import tw.ktrssreader.kotlin.constant.ParserConst.URL +import tw.ktrssreader.kotlin.constant.ParserConst.WIDTH +import tw.ktrssreader.kotlin.model.channel.* import tw.ktrssreader.kotlin.model.item.Category import tw.ktrssreader.kotlin.model.item.Enclosure import tw.ktrssreader.kotlin.model.item.Guid @@ -35,7 +63,7 @@ abstract class ParserBase : Parser { val builder = DocumentBuilderFactory.newInstance().newDocumentBuilder() val document = builder.parse(xml.byteInputStream()) document.documentElement.normalize() - val nodeList = document.getElementsByTagName(ParserConst.CHANNEL) + val nodeList = document.getElementsByTagName(CHANNEL) var result: T? = null // It can only have a channel. @@ -48,30 +76,80 @@ abstract class ParserBase : Parser { return result ?: throw IllegalArgumentException("No valid channel tag in the RSS feed.") } + protected fun Element.readImage(): Image? { + val element = getElementByTag(IMAGE) ?: return null + + val link = element.readString(LINK) + val title = element.readString(TITLE) + val url = element.readString(URL) + val description = element.readString(DESCRIPTION) + val height = element.readString(HEIGHT)?.toIntOrNull() + val width = element.readString(WIDTH)?.toIntOrNull() + return Image( + link = link, + title = title, + url = url, + description = description, + height = height, + width = width + ) + } + protected fun Element.readCategories(parentTag: String): List { val result = mutableListOf() - val nodeList = getElementsByTagName(ParserConst.CATEGORY) ?: return result + val nodeList = getElementsByTagName(CATEGORY) ?: return result for (i in 0 until nodeList.length) { val e = nodeList.item(i) as? Element ?: continue val parent = e.parentNode as? DeferredElementImpl if (parent?.tagName != parentTag) continue - val domain: String? = e.getAttributeOrNull(ParserConst.DOMAIN) - val name: String? = e.textContent + val domain: String? = e.getAttributeOrNull(DOMAIN) + val name: String? = e.textContent?.takeIf { it.isNotEmpty() } result.add(Category(name = name, domain = domain)) } return result } + protected fun Element.readCategories(parentTag: String, tagName: String): List? { + val result = mutableListOf() + val nodeList = getElementsByTagName(tagName) ?: return null + + for (i in 0 until nodeList.length) { + val e = nodeList.item(i) as? Element ?: continue + val parent = e.parentNode as? DeferredElementImpl + if (parent?.tagName == parentTag || parent?.tagName == tagName) { + result.add(Category(name = e.getAttributeOrNull(TEXT), domain = null)) + } + } + return result + } + + protected fun Element.readITunesOwner(): Owner? { + val nodeList = getElementsByTagName(ITUNES_OWNER) ?: return null + if (nodeList.length == 0) return null + + val element = getElementByTag(ITUNES_OWNER) + val name = element?.readString(ITUNES_NAME) + val email = element?.readString(ITUNES_EMAIL) + return Owner(name = name, email = email) + } + + protected fun Element.readGoogleOwner(): Owner? { + val nodeList = getElementsByTagName(GOOGLE_OWNER) + if (nodeList.length == 0) return null + + return Owner(name = null, email = readString(GOOGLE_OWNER)) + } + protected fun Element.readCloud(): Cloud? { - val element = getElementByTag(ParserConst.CLOUD) ?: return null + val element = getElementByTag(CLOUD) ?: return null - val domain: String? = element.getAttributeOrNull(ParserConst.DOMAIN) - val port: Int? = element.getAttributeOrNull(ParserConst.PORT)?.toIntOrNull() - val path: String? = element.getAttributeOrNull(ParserConst.PATH) - val registerProcedure: String? = element.getAttributeOrNull(ParserConst.REGISTER_PROCEDURE) - val protocol: String? = element.getAttributeOrNull(ParserConst.PROTOCOL) + val domain: String? = element.getAttributeOrNull(DOMAIN) + val port: Int? = element.getAttributeOrNull(PORT)?.toIntOrNull() + val path: String? = element.getAttributeOrNull(PATH) + val registerProcedure: String? = element.getAttributeOrNull(REGISTER_PROCEDURE) + val protocol: String? = element.getAttributeOrNull(PROTOCOL) return Cloud( domain = domain, @@ -83,20 +161,20 @@ abstract class ParserBase : Parser { } protected fun Element.readTextInput(): TextInput? { - val element = getElementByTag(ParserConst.TEXT_INPUT) ?: return null + val element = getElementByTag(TEXT_INPUT) ?: return null - val title: String? = element.readString(ParserConst.TITLE) - val description: String? = element.readString(name = ParserConst.DESCRIPTION, parentTag = ParserConst.TEXT_INPUT) - val name: String? = element.readString(ParserConst.NAME) - val link: String? = element.readString(ParserConst.LINK) + val title: String? = element.readString(TITLE) + val description: String? = element.readString(name = DESCRIPTION, parentTag = TEXT_INPUT) + val name: String? = element.readString(NAME) + val link: String? = element.readString(LINK) return TextInput(title = title, description = description, name = name, link = link) } protected fun Element.readSkipHours(): List? { - val element = getElementByTag(ParserConst.SKIP_HOURS) ?: return null + val element = getElementByTag(SKIP_HOURS) ?: return null val hours = mutableListOf() - val nodes = element.getElementsByTagName(ParserConst.HOUR) + val nodes = element.getElementsByTagName(HOUR) for (i in 0 until nodes.length) { val e = nodes.item(i) as? Element e?.textContent?.toIntOrNull()?.let { hours.add(it) } @@ -105,10 +183,10 @@ abstract class ParserBase : Parser { } protected fun Element.readSkipDays(): List? { - val element = getElementByTag(ParserConst.SKIP_DAYS) ?: return null + val element = getElementByTag(SKIP_DAYS) ?: return null val days = mutableListOf() - val nodes = element.getElementsByTagName(ParserConst.DAY) + val nodes = element.getElementsByTagName(DAY) for (i in 0 until nodes.length) { val e = nodes.item(i) as? Element e?.textContent?.let { days.add(it) } @@ -117,27 +195,27 @@ abstract class ParserBase : Parser { } protected fun Element.readEnclosure(): Enclosure? { - val element = getElementByTag(ParserConst.ENCLOSURE) ?: return null + val element = getElementByTag(ENCLOSURE) ?: return null - val url = element.getAttributeOrNull(ParserConst.URL) - val length = element.getAttributeOrNull(ParserConst.LENGTH)?.toLongOrNull() - val type = element.getAttributeOrNull(ParserConst.TYPE) + val url = element.getAttributeOrNull(URL) + val length = element.getAttributeOrNull(LENGTH)?.toLongOrNull() + val type = element.getAttributeOrNull(TYPE) return Enclosure(url = url, length = length, type = type) } protected fun Element.readGuid(): Guid? { - val element = getElementByTag(ParserConst.GUID) ?: return null + val element = getElementByTag(GUID) ?: return null - val isPermaLink = element.getAttributeOrNull(ParserConst.PERMALINK)?.toBoolean() - val value = readString(ParserConst.GUID) + val isPermaLink = element.getAttributeOrNull(PERMALINK)?.toBoolean() + val value = readString(GUID) return Guid(value = value, isPermaLink = isPermaLink) } protected fun Element.readSource(): Source? { - val element = getElementByTag(ParserConst.SOURCE) ?: return null + val element = getElementByTag(SOURCE) ?: return null - val url = element.getAttributeOrNull(ParserConst.URL) - val title = readString(ParserConst.SOURCE) + val url = element.getAttributeOrNull(URL) + val title = readString(SOURCE) return Source(title = title, url = url) } @@ -158,7 +236,7 @@ abstract class ParserBase : Parser { } protected fun Element.getAttributeOrNull(tag: String): String? { - val attr = getAttribute(tag) + val attr = getAttribute(tag) ?: return null return if (attr.isEmpty() || attr.isBlank()) null else attr } diff --git a/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/RssStandardParser.kt b/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/RssStandardParser.kt index a2ea2f2..bb8707d 100644 --- a/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/RssStandardParser.kt +++ b/kotlin/src/main/java/tw/ktrssreader/kotlin/parser/RssStandardParser.kt @@ -17,12 +17,14 @@ package tw.ktrssreader.kotlin.parser import org.w3c.dom.Element -import tw.ktrssreader.kotlin.constant.ParserConst +import tw.ktrssreader.kotlin.constant.ParserConst.AUTHOR import tw.ktrssreader.kotlin.constant.ParserConst.CHANNEL +import tw.ktrssreader.kotlin.constant.ParserConst.COMMENTS import tw.ktrssreader.kotlin.constant.ParserConst.COPYRIGHT import tw.ktrssreader.kotlin.constant.ParserConst.DESCRIPTION import tw.ktrssreader.kotlin.constant.ParserConst.DOCS import tw.ktrssreader.kotlin.constant.ParserConst.GENERATOR +import tw.ktrssreader.kotlin.constant.ParserConst.ITEM import tw.ktrssreader.kotlin.constant.ParserConst.LANGUAGE import tw.ktrssreader.kotlin.constant.ParserConst.LAST_BUILD_DATE import tw.ktrssreader.kotlin.constant.ParserConst.LINK @@ -32,7 +34,6 @@ import tw.ktrssreader.kotlin.constant.ParserConst.RATING import tw.ktrssreader.kotlin.constant.ParserConst.TITLE import tw.ktrssreader.kotlin.constant.ParserConst.TTL import tw.ktrssreader.kotlin.constant.ParserConst.WEB_MASTER -import tw.ktrssreader.kotlin.model.channel.Image import tw.ktrssreader.kotlin.model.channel.RssStandardChannel import tw.ktrssreader.kotlin.model.channel.RssStandardChannelData import tw.ktrssreader.kotlin.model.item.RssStandardItemData @@ -60,7 +61,7 @@ class RssStandardParser : ParserBase() { val textInput = readTextInput() val skipHours = readSkipHours() val skipDays = readSkipDays() - val items = readItems() ?: listOf() + val items = readItems() RssStandardChannelData( title = title, @@ -89,20 +90,20 @@ class RssStandardParser : ParserBase() { private fun Element.readItems(): List { val result = mutableListOf() - val nodeList = getElementsByTagName(ParserConst.ITEM) ?: return result + val nodeList = getElementsByTagName(ITEM) ?: return result for (i in 0 until nodeList.length) { val element = nodeList.item(i) as? Element ?: continue - val title = element.readString(ParserConst.TITLE) + val title = element.readString(TITLE) val enclosure = element.readEnclosure() val guid = element.readGuid() - val pubDate = element.readString(ParserConst.PUB_DATE) - val description = element.readString(ParserConst.DESCRIPTION) - val link = element.readString(ParserConst.LINK) - val author = element.readString(ParserConst.AUTHOR) - val categories = element.readCategories(ParserConst.ITEM) - val comments = element.readString(ParserConst.COMMENTS) + val pubDate = element.readString(PUB_DATE) + val description = element.readString(DESCRIPTION) + val link = element.readString(LINK) + val author = element.readString(AUTHOR) + val categories = element.readCategories(ITEM) + val comments = element.readString(COMMENTS) val source = element.readSource() result.add( RssStandardItemData( @@ -121,23 +122,4 @@ class RssStandardParser : ParserBase() { } return result } - - private fun Element.readImage(): Image? { - val element = getElementByTag(ParserConst.IMAGE) ?: return null - - val link = element.readString(ParserConst.LINK) - val title = element.readString(ParserConst.TITLE) - val url = element.readString(ParserConst.URL) - val description = element.readString(ParserConst.DESCRIPTION) - val height = element.readString(ParserConst.HEIGHT)?.toIntOrNull() - val width = element.readString(ParserConst.WIDTH)?.toIntOrNull() - return Image( - link = link, - title = title, - url = url, - description = description, - height = height, - width = width - ) - } } diff --git a/kotlin/src/test/java/tw/ktrssreader/kotlin/AutoMixParserLocalTest.kt b/kotlin/src/test/java/tw/ktrssreader/kotlin/AutoMixParserLocalTest.kt new file mode 100644 index 0000000..3d73dcd --- /dev/null +++ b/kotlin/src/test/java/tw/ktrssreader/kotlin/AutoMixParserLocalTest.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2020 Feng Hsien Hsu, Siao Syuan Yang, Wei-Qi Wang, Ya-Han Tsai, Yu Hao Wu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tw.ktrssreader.kotlin + +import org.junit.Test +import org.junit.experimental.runners.Enclosed +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.xml.sax.SAXParseException +import tw.ktrssreader.kotlin.model.channel.AutoMixChannelData +import tw.ktrssreader.kotlin.parser.AutoMixParser +import tw.ktrssreader.test.common.ChannelItemTestData +import tw.ktrssreader.test.common.XmlFileReader +import tw.ktrssreader.test.common.base.ErrorTagParserBaseTest +import tw.ktrssreader.test.common.shouldBe + +@RunWith(Enclosed::class) +class AutoMixParserLocalTest { + + @RunWith(Parameterized::class) + class AutoMixParserParseFunctionTest( + private val rssFilePath: String, + private val expectedChannel: AutoMixChannelData + ) { + companion object { + @JvmStatic + @Parameterized.Parameters + fun getTestingData() = listOf( + arrayOf( + "${ChannelItemTestData.AUTOMIX_FOLDER}/rss_v2_itunes_google_full.xml", + ChannelItemTestData.FULL_AUTOMIX_CHANNEL + ), + arrayOf( + "${ChannelItemTestData.AUTOMIX_FOLDER}/rss_v2_itunes_google_category_no_ordering.xml", + ChannelItemTestData.CATEGORY_NO_ORDERING_AUTOMIX_CHANNEL + ), + arrayOf( + "${ChannelItemTestData.AUTOMIX_FOLDER}/rss_v2_itunes_google_nested_itunes_category.xml", + ChannelItemTestData.NESTED_ITUNES_CATEGORY_AUTOMIX_CHANNEL + ), + arrayOf( + "${ChannelItemTestData.AUTOMIX_FOLDER}/rss_v2_itunes_google_without_items.xml", + ChannelItemTestData.PARTIAL_AUTOMIX_CHANNEL + ), + arrayOf( + "${ChannelItemTestData.AUTOMIX_FOLDER}/rss_v2_itunes_google_without_itunes_attrs.xml", + ChannelItemTestData.PARTIAL_AUTOMIX_CHANNEL_2 + ), + ) + } + + private val parser = AutoMixParser() + + @Test + fun parse() { + val xml = XmlFileReader.readFile(rssFilePath) + val actualChannel = parser.parse(xml) + + actualChannel shouldBe expectedChannel + } + } + + @RunWith(Parameterized::class) + class AutoMixErrorTagParserErrorTagTest(private val rssFilePath: String) : + ErrorTagParserBaseTest() { + + @Test(expected = SAXParseException::class) + fun parse() { + val parser = AutoMixParser() + val xml = XmlFileReader.readFile(rssFilePath) + + parser.parse(xml) + } + } +} \ No newline at end of file diff --git a/kotlin/src/test/java/tw/ktrssreader/kotlin/RssStandardParserLocalTest.kt b/kotlin/src/test/java/tw/ktrssreader/kotlin/RssStandardParserLocalTest.kt index beb80c5..77e36df 100644 --- a/kotlin/src/test/java/tw/ktrssreader/kotlin/RssStandardParserLocalTest.kt +++ b/kotlin/src/test/java/tw/ktrssreader/kotlin/RssStandardParserLocalTest.kt @@ -20,19 +20,19 @@ import org.junit.Test import org.junit.experimental.runners.Enclosed import org.junit.runner.RunWith import org.junit.runners.Parameterized +import tw.ktrssreader.kotlin.model.channel.RssStandardChannelData +import tw.ktrssreader.kotlin.parser.RssStandardParser import tw.ktrssreader.test.common.ChannelItemTestData.FULL_RSS_CHANNEL import tw.ktrssreader.test.common.ChannelItemTestData.PARTIAL_RSS_CHANNEL import tw.ktrssreader.test.common.ChannelItemTestData.RSS_CHANNEL_PARTIAL_CLOUD import tw.ktrssreader.test.common.ChannelItemTestData.RSS_CHANNEL_PARTIAL_IMAGE import tw.ktrssreader.test.common.ChannelItemTestData.RSS_CHANNEL_PARTIAL_TEXT_IMAGE import tw.ktrssreader.test.common.ChannelItemTestData.RSS_FOLDER -import tw.ktrssreader.kotlin.model.channel.RssStandardChannelData -import tw.ktrssreader.kotlin.parser.RssStandardParser import tw.ktrssreader.test.common.XmlFileReader import tw.ktrssreader.test.common.shouldBe @RunWith(Enclosed::class) -class RssStandardParserTest { +class RssStandardParserLocalTest { @RunWith(Parameterized::class) class RssStandardParserParseFunctionTest(