haven-apps/havenopml
A pure Swift package for importing and exporting OPML (Outline Processor Markup Language) documents. Built with strict Swift 6 concurrency, zero third-party dependencies.
Requirements
- iOS 18+ / macOS 15+ / tvOS 18+ / watchOS 11+ / visionOS 2+
- Swift 6.2+
- No external dependencies (Foundation and XMLParser only)
Installation
Add as a local package dependency in Xcode, or reference it in your Package.swift:
.package(url: "https://github.com/Haven-Apps/HavenOPML")Usage
Import from Data
import HavenOPML
let document = try OPMLImporter.importOPML(from: data)
print(document.title) // "My Subscriptions"
print(document.format) // .v1_0 or .v2_0
for outline in document.outlines {
print(outline.text, outline.xmlUrl)
}Import from File URL
let document = try OPMLImporter.importOPML(from: fileURL)Export to Data
let data = try OPMLExporter.exportOPML(document)Quick Export from Outlines
let data = try OPMLExporter.exportOPML(
outlines: myOutlines,
title: "My Subscriptions"
)Using OPMLService
OPMLService is an actor that wraps the importer and exporter for use in concurrent contexts:
let service = OPMLService()
let document = try await service.importOPML(from: data)
let exported = try await service.exportOPML(document)
let isValid = try await service.validateRoundTrip(document)Working with the Document
// All feed outlines (depth-first)
let feeds = document.allFeeds
// Total feed count
let count = document.feedCount
// Top-level folders only
let folders = document.foldersOutline Traversal
// Depth-first traversal with depth tracking
OutlineTraversal.depthFirst(document.outlines) { outline, depth in
let indent = String(repeating: " ", count: depth)
print("\(indent)\(outline.displayName)")
}
// Flatten all outlines
let all = OutlineTraversal.flatten(document.outlines)Architecture
Sources/HavenOPML/
├── Models/ Sendable, Codable value types (OPMLDocument, OutlineItem, OPMLCategory, OPMLFormat)
├── Parsers/ OPMLImporter (static API), OPMLParserDelegate (SAX-style XMLParser)
├── Exporters/ OPMLExporter (static API, generates indented OPML/XML)
├── Services/ Actor-isolated OPMLService (unified import/export/validation API)
├── Utilities/ OPMLDateFormatter, OutlineTraversal, XMLEscaping
└── Errors/ OPMLError enumKey Types
| Type | Description | |---|---| | OPMLService | Actor-isolated public API — imports, exports, and validates OPML documents | | OPMLDocument | Parsed document with title, dates, owner metadata, and outline tree | | OutlineItem | Single outline element — feed entry or folder with nested children | | OPMLCategory | Category value parsed from comma-separated OPML category strings | | OPMLFormat | Enum: .v1_0, .v2_0 | | OPMLError | Typed errors for parsing, export, and I/O failures | | OPMLImporter | Stateless OPML parser using Foundation's XMLParser | | OPMLExporter | Stateless XML generator with proper character escaping | | OPMLDateFormatter | Parses and formats RFC 822 and ISO 8601 dates | | OutlineTraversal | Depth-first traversal, flattening, and counting utilities | | XMLEscaping | Escapes the five predefined XML entities for safe export |
OutlineItem
OutlineItem supports both feed entries and folder/grouping outlines. Folders are identified by having non-empty children and no xmlUrl. Custom or namespace-specific attributes are preserved in customAttributes for round-trip fidelity.
outline.isFolder // true if it has children and no xmlUrl
outline.isFeed // true if xmlUrl is present
outline.displayName // prefers title over textVersion Detection
The importer auto-detects the OPML version from the version attribute on the <opml> element. Unknown versions are treated as 2.0 for forward compatibility.
Security
- External XML entity resolution is disabled (
shouldResolveExternalEntities = false) to prevent XXE attacks. - Input size is capped at 10 MB to mitigate denial-of-service via large payloads.
- Outline nesting depth is limited to 128 levels on both import and export.
- Only file URLs are accepted by the URL-based import method; remote URLs are rejected.
Concurrency
OPMLImporter and OPMLExporter are stateless enums with static methods, safe to call from any context. OPMLService is an actor that provides a convenient async API for use in concurrent code.
License
BSD 3-Clause — see LICENSE.md.
Package Metadata
Repository: haven-apps/havenopml
Default branch: main
README: README.md