如何用SWXMLHash构建深层XML结构体?并解析Reddit RSS图片地址
Hey there! Since you already have a working setup with Alamofire and your APIUsable protocol, let's break down your two main goals: grabbing the src attribute from <img> inside the <content> node, and building nested structs with SWXMLHash for deep XML hierarchies.
1. Extracting the <img> src from the <content> Node
First, let's address the image source extraction. Reddit's RSS <content> node usually contains HTML wrapped in CDATA, so we need to:
- Pull the raw HTML string from the
<content>node - Parse that HTML to find the
<img>tag and itssrcattribute
Step 1: Get the Content HTML with SWXMLHash
Assuming you have your base entry struct, first extract the content text:
import SWXMLHash struct RedditEntry: XMLIndexerDeserializable { let title: String let contentHtml: String static func deserialize(_ node: XMLIndexer) throws -> RedditEntry { return try RedditEntry( title: node["title"].value(), contentHtml: node["content"].value() ) } }
Step 2: Parse HTML to Extract Image Src
You can use a simple regex to pull the src value from the content HTML, or use a lightweight HTML parser for more robustness. Here's both approaches:
Regex Approach (Quick & Simple)
extension RedditEntry { var postImageSrc: String? { // Regex pattern to match img tags and capture src attribute let pattern = #"<img[^>]+src="([^"]+)""# guard let regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive), let match = regex.firstMatch(in: contentHtml, range: NSRange(contentHtml.startIndex..., in: contentHtml)) else { return nil } let srcRange = Range(match.range(at: 1), in: contentHtml)! return String(contentHtml[srcRange]) } }
HTML Parser Approach (More Reliable)
If you want to avoid regex edge cases, use a library like SwiftSoup:
import SwiftSoup extension RedditEntry { var postImageSrc: String? { do { let doc = try SwiftSoup.parse(contentHtml) if let img = try doc.select("img").first() { return try img.attr("src") } } catch { print("HTML parsing error: \(error)") } return nil } }
2. Building Deep Nested Structs with SWXMLHash
SWXMLHash makes mapping complex XML hierarchies to nested Swift structs straightforward by conforming to XMLIndexerDeserializable. Let's build out a full example matching Reddit's RSS structure, including nested nodes like <author> or <category>.
Example Deep Struct Setup
// Top-level RSS feed struct struct RedditFeed: XMLIndexerDeserializable { let entries: [RedditEntry] static func deserialize(_ node: XMLIndexer) throws -> RedditFeed { return try RedditFeed( entries: node["entry"].value() ) } } // Individual entry with nested author and categories struct RedditEntry: XMLIndexerDeserializable { let title: String let contentHtml: String let author: RedditAuthor let categories: [RedditCategory] static func deserialize(_ node: XMLIndexer) throws -> RedditEntry { return try RedditEntry( title: node["title"].value(), contentHtml: node["content"].value(), author: node["author"].value(), categories: node["category"].value() ) } } // Nested author struct struct RedditAuthor: XMLIndexerDeserializable { let name: String let uri: String static func deserialize(_ node: XMLIndexer) throws -> RedditAuthor { return try RedditAuthor( name: node["name"].value(), uri: node["uri"].value() ) } } // Nested category struct (handles XML attributes) struct RedditCategory: XMLIndexerDeserializable { let term: String static func deserialize(_ node: XMLIndexer) throws -> RedditCategory { // Access XML attributes with the `@` prefix return try RedditCategory( term: node.attributes["term"].value() ) } }
Key Notes for Deep Structs:
- Nested Nodes: When a node has child elements (like
<author>containing<name>and<uri>), create a separate struct for the child and callnode["childNode"].value()to deserialize it. - Attributes: For XML attributes (e.g.,
<category term="funny">), usenode.attributes["attributeName"].value()to access the value. - Arrays: If multiple nodes exist (like multiple
<category>entries), declare the property as an array—SWXMLHash will automatically deserialize all matching nodes.
Integrating with Your Alamofire & APIUsable Setup
Assuming your APIUsable protocol handles network calls, parse the response like this:
// Example API struct conforming to APIUsable struct RedditHotAPI: APIUsable { var baseURL: String { "https://www.reddit.com" } var path: String { "/hot/.rss" } var method: HTTPMethod { .get } func parseResponse(data: Data) throws -> RedditFeed { let xml = SWXMLHash.parse(data) return try xml["feed"].value() } } // Usage Alamofire.request(RedditHotAPI()).responseData { response in switch response.result { case .success(let data): do { let feed = try RedditHotAPI().parseResponse(data: data) // Access entries and their image src for entry in feed.entries { print("Post title: \(entry.title)") print("Image src: \(entry.postImageSrc ?? "No image")") } } catch { print("Parsing error: \(error)") } case .failure(let error): print("Network error: \(error)") } }
That should cover both extracting the image source and building out deep structs with SWXMLHash. Adjust any part to fit your specific APIUsable implementation if needed!
内容的提问来源于stack exchange,提问作者BeauZ

