diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-07-22 08:08:58 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-07-22 08:08:58 +0000 |
| commit | d8ce4b3c0f383e844b125d2d3abd618715bb3c89 (patch) | |
| tree | b586ff9b42392f2c44645773c2a2f2aadb9db406 /app/src/main/java/org/terst/feedreader/opml/Opml.kt | |
Initial commit: Feeds, an Android RSS reader
Feed subscriptions (RSS 2.0 + Atom, hand-rolled parser), OPML
import/export, WorkManager periodic fetch with conditional GET,
Room-backed local storage (feeds/articles/filter rules), keyword
mute/highlight filtering.
Reader mode: on-device extraction via Mozilla's Readability.js
(vendored from github.com/mozilla/readability, pinned to release
0.6.0) run in a hidden WebView, for feed articles whose provided
content is too thin to be the real article body.
Save: explicit action (from a feed article, or the Android share-sheet
target for any URL) archives to Stash via POST /artifacts?sync=true,
while a local on-device copy (Readability-extracted) is always kept so
saved articles read offline regardless of Stash's reachability.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
Diffstat (limited to 'app/src/main/java/org/terst/feedreader/opml/Opml.kt')
| -rw-r--r-- | app/src/main/java/org/terst/feedreader/opml/Opml.kt | 71 |
1 files changed, 71 insertions, 0 deletions
diff --git a/app/src/main/java/org/terst/feedreader/opml/Opml.kt b/app/src/main/java/org/terst/feedreader/opml/Opml.kt new file mode 100644 index 0000000..65510fb --- /dev/null +++ b/app/src/main/java/org/terst/feedreader/opml/Opml.kt @@ -0,0 +1,71 @@ +package org.terst.feedreader.opml + +import android.util.Xml +import org.terst.feedreader.data.FeedEntity +import org.xmlpull.v1.XmlPullParser +import java.io.InputStream +import java.io.OutputStream + +data class OpmlFeed(val title: String, val xmlUrl: String, val htmlUrl: String?) + +/** + * OPML import/export. Any folder nesting (<outline> containing other + * <outline> elements instead of an xmlUrl) is flattened -- this app has no + * folder concept, per the "simple design" scope, so a folder's contents + * are just imported as a flat list of feeds. + */ +object Opml { + + fun parse(input: InputStream): List<OpmlFeed> { + val parser = Xml.newPullParser() + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false) + parser.setInput(input, null) + + val feeds = mutableListOf<OpmlFeed>() + var eventType = parser.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG && parser.name.equals("outline", ignoreCase = true)) { + val xmlUrl = parser.getAttributeValue(null, "xmlUrl") + if (xmlUrl != null) { + val title = parser.getAttributeValue(null, "title") + ?: parser.getAttributeValue(null, "text") + ?: xmlUrl + val htmlUrl = parser.getAttributeValue(null, "htmlUrl") + feeds += OpmlFeed(title, xmlUrl, htmlUrl) + } + } + eventType = parser.next() + } + return feeds + } + + fun write(feeds: List<FeedEntity>, output: OutputStream) { + val serializer = Xml.newSerializer() + serializer.setOutput(output, "UTF-8") + serializer.startDocument("UTF-8", true) + serializer.startTag(null, "opml") + serializer.attribute(null, "version", "2.0") + + serializer.startTag(null, "head") + serializer.startTag(null, "title") + serializer.text("Feeds subscriptions") + serializer.endTag(null, "title") + serializer.endTag(null, "head") + + serializer.startTag(null, "body") + for (feed in feeds) { + serializer.startTag(null, "outline") + serializer.attribute(null, "text", feed.title) + serializer.attribute(null, "title", feed.title) + serializer.attribute(null, "type", "rss") + serializer.attribute(null, "xmlUrl", feed.id) + feed.siteUrl?.let { serializer.attribute(null, "htmlUrl", it) } + serializer.endTag(null, "outline") + } + serializer.endTag(null, "body") + + serializer.endTag(null, "opml") + serializer.endDocument() + serializer.flush() + } +} |
