From 5c5ba596a50274aa4d99dc682fb34d43bb7f7326 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Sat, 22 Aug 2026 00:54:33 +0000 Subject: Fix Atom feeds crashing the whole parse on xhtml content/summary / 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(). --- .../java/org/terst/feedreader/data/FeedParser.kt | 47 +++++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) (limited to 'app/src/main/java/org/terst/feedreader/data') 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 / wrap real nested markup + // (spec-legal:
+ // ...actual HTML...
) 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
-- 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("\"", """)).append('"') + } + sb.append('>') + } + XmlPullParser.END_TAG -> sb.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") } -- cgit v1.2.3