summaryrefslogtreecommitdiff
path: root/app/src/main/java
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-08-22 00:54:33 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-08-22 00:54:33 +0000
commit5c5ba596a50274aa4d99dc682fb34d43bb7f7326 (patch)
treeae833ef641f80e5ea7dabf4286b33ff7dca9b901 /app/src/main/java
parent44b0aa44e065822818e8473bb8e49b0ca45c43c8 (diff)
Fix Atom feeds crashing the whole parse on xhtml content/summary
<content type="xhtml">/<summary type="xhtml"> wrap real nested markup, which XmlPullParser's nextText() can't handle (it requires a text-only element) -- it threw and aborted parsing of the entire feed over one xhtml-typed field. Walks and reconstructs the inner markup manually instead, tracking depth against the parser's own getDepth().
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/org/terst/feedreader/data/FeedParser.kt47
1 files changed, 45 insertions, 2 deletions
diff --git a/app/src/main/java/org/terst/feedreader/data/FeedParser.kt b/app/src/main/java/org/terst/feedreader/data/FeedParser.kt
index 8b37e55..7a3ce93 100644
--- a/app/src/main/java/org/terst/feedreader/data/FeedParser.kt
+++ b/app/src/main/java/org/terst/feedreader/data/FeedParser.kt
@@ -43,6 +43,37 @@ object FeedParser {
private fun decodeTitleEntities(text: String): String =
Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY).toString()
+ // Atom's <content type="xhtml">/<summary type="xhtml"> wrap real nested markup
+ // (spec-legal: <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
+ // ...actual HTML...</div></content>) instead of a plain text leaf. XmlPullParser's
+ // nextText() requires the element to be text-only and throws XmlPullParserException
+ // the instant it hits that nested <div> -- uncaught, that aborted parsing of the
+ // ENTIRE feed over one xhtml-typed field, not just that one entry. Manually walks and
+ // reconstructs the inner markup as an HTML string instead, tracking depth against the
+ // parser's own getDepth() (not a separately-maintained counter) so it can't drift out
+ // of sync with what nextText() would have consumed.
+ private fun readElementAsHtml(parser: XmlPullParser): String {
+ val startDepth = parser.depth
+ val sb = StringBuilder()
+ var event = parser.next()
+ while (!(event == XmlPullParser.END_TAG && parser.depth == startDepth)) {
+ when (event) {
+ XmlPullParser.START_TAG -> {
+ sb.append('<').append(parser.name)
+ for (i in 0 until parser.attributeCount) {
+ sb.append(' ').append(parser.getAttributeName(i)).append("=\"")
+ .append(parser.getAttributeValue(i).replace("\"", "&quot;")).append('"')
+ }
+ sb.append('>')
+ }
+ XmlPullParser.END_TAG -> sb.append("</").append(parser.name).append('>')
+ XmlPullParser.TEXT, XmlPullParser.CDSECT -> sb.append(parser.text)
+ }
+ event = parser.next()
+ }
+ return sb.toString().trim()
+ }
+
fun parse(input: InputStream): ParsedFeed {
val parser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
@@ -187,8 +218,20 @@ object FeedParser {
"name" -> if (inEntry && entryAuthor == null) entryAuthor = parser.nextText()
"updated" -> if (inEntry) entryDate = parseIso8601Date(parser.nextText())
"published" -> if (inEntry) entryDate = parseIso8601Date(parser.nextText()) ?: entryDate
- "content" -> if (inEntry) entryContent = parser.nextText()
- "summary" -> if (inEntry) entrySummary = parser.nextText()
+ "content" -> if (inEntry) {
+ entryContent = if (parser.getAttributeValue(null, "type") == "xhtml") {
+ readElementAsHtml(parser)
+ } else {
+ parser.nextText()
+ }
+ }
+ "summary" -> if (inEntry) {
+ entrySummary = if (parser.getAttributeValue(null, "type") == "xhtml") {
+ readElementAsHtml(parser)
+ } else {
+ parser.nextText()
+ }
+ }
"media:thumbnail" -> if (inEntry && entryImageUrl == null) {
entryImageUrl = parser.getAttributeValue(null, "url")
}