From d13014787a5fbfe37af6419243800c19f0ba6d39 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 26 Aug 2024 20:53:57 -0700 Subject: [PATCH] Create separate SAX target. --- Modules/Parser/Package.swift | 12 + .../Sources/Parser/OPML/OPMLParser.swift | 2 +- .../Sources/{Parser => SAX}/ParserData.swift | 0 .../Parser/Sources/SAX/SAXHTMLParser.swift | 54 +++ .../Sources/{Parser => SAX}/SAXParser.swift | 22 +- .../{Parser => SAX}/SAXUtilities.swift | 6 +- .../Sources/ParserObjC/RSOPMLParser.h | 26 -- .../Sources/ParserObjC/RSOPMLParser.m | 310 --------------- .../Sources/ParserObjC/RSSAXParser.h | 69 ---- .../Sources/ParserObjC/RSSAXParser.m | 353 ------------------ 10 files changed, 78 insertions(+), 776 deletions(-) rename Modules/Parser/Sources/{Parser => SAX}/ParserData.swift (100%) create mode 100644 Modules/Parser/Sources/SAX/SAXHTMLParser.swift rename Modules/Parser/Sources/{Parser => SAX}/SAXParser.swift (83%) rename Modules/Parser/Sources/{Parser => SAX}/SAXUtilities.swift (67%) delete mode 100755 Modules/ParserObjC/Sources/ParserObjC/RSOPMLParser.h delete mode 100755 Modules/ParserObjC/Sources/ParserObjC/RSOPMLParser.m delete mode 100755 Modules/ParserObjC/Sources/ParserObjC/RSSAXParser.h delete mode 100755 Modules/ParserObjC/Sources/ParserObjC/RSSAXParser.m diff --git a/Modules/Parser/Package.swift b/Modules/Parser/Package.swift index 6d7320717..3a68833dc 100644 --- a/Modules/Parser/Package.swift +++ b/Modules/Parser/Package.swift @@ -12,6 +12,10 @@ let package = Package( name: "Parser", type: .dynamic, targets: ["Parser"]), + .library( + name: "SAX", + type: .dynamic, + targets: ["SAX"]) ], dependencies: [ ], @@ -20,6 +24,14 @@ let package = Package( // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "Parser", + dependencies: [ + "SAX" + ], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency") + ]), + .target( + name: "SAX", dependencies: [], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency") diff --git a/Modules/Parser/Sources/Parser/OPML/OPMLParser.swift b/Modules/Parser/Sources/Parser/OPML/OPMLParser.swift index 59ae75b7b..0ecda56e9 100644 --- a/Modules/Parser/Sources/Parser/OPML/OPMLParser.swift +++ b/Modules/Parser/Sources/Parser/OPML/OPMLParser.swift @@ -76,7 +76,7 @@ private extension OPMLParser { extension OPMLParser: SAXParserDelegate { - func saxParser(_ saxParser: SAXParser, xmlStartElement localName: XMLPointer, prefix: XMLPointer?, uri: XMLPointer?, namespaceCount: Int, namespaces: UnsafeMutablePointer?, attributeCount: Int, attributesDefaultedCount: Int, attributes: UnsafeMutablePointer?) { + func saxParser(_ saxParser: SAXParser, xmlStartElement localName: XMLPointer, prefix: XMLPointer?, uri: XMLPointer?, namespaceCount: Int, namespaces: UnsafePointer?, attributeCount: Int, attributesDefaultedCount: Int, attributes: UnsafePointer?) { if SAXEqualStrings(localName, XMLKey.title) { saxParser.beginStoringCharacters() diff --git a/Modules/Parser/Sources/Parser/ParserData.swift b/Modules/Parser/Sources/SAX/ParserData.swift similarity index 100% rename from Modules/Parser/Sources/Parser/ParserData.swift rename to Modules/Parser/Sources/SAX/ParserData.swift diff --git a/Modules/Parser/Sources/SAX/SAXHTMLParser.swift b/Modules/Parser/Sources/SAX/SAXHTMLParser.swift new file mode 100644 index 000000000..43ba1cc38 --- /dev/null +++ b/Modules/Parser/Sources/SAX/SAXHTMLParser.swift @@ -0,0 +1,54 @@ +//// +//// SAXHTMLParser.swift +//// +//// +//// Created by Brent Simmons on 8/26/24. +//// +// +//import Foundation +//import libxml2 +// +//protocol SAXHTMLParserDelegate: AnyObject { +// +// func saxParser(_: SAXHTMLParser, XMLStartElement localName: XMLPointer, attributes: UnsafePointer?) +// +// func saxParser(_: SAXHTMLParser, XMLEndElement localName: XMLPointer?) +// +// // Length is guaranteed to be greater than 0. +// func saxParser(_: SAXHTMLParser, XMLCharactersFound characters: XMLPointer?, length: Int) +//} +// +//final class SAXHTMLParser { +// +// fileprivate let delegate: SAXHTMLParserDelegate +// private var data: Data +// +// init(delegate: SAXHTMLParserDelegate, data: Data) { +// +// self.delegate = delegate +// self.data = data +// } +// +// func parse() { +// +// guard !data.isEmpty else { +// return +// } +// +// data.withUnsafeBytes { bufferPointer in +// +// guard let bytes = bufferPointer.bindMemory(to: xmlChar.self).baseAddress else { +// return +// } +// +// let characterEncoding = xmlDetectCharEncoding(bytes, Int32(data.count)) +// let context = htmlCreatePushParserCtxt(&saxHandlerStruct, Unmanaged.passUnretained(self).toOpaque(), nil, 0, nil, characterEncoding) +// htmlCtxtUseOptions(context, Int32(XML_PARSE_RECOVER | XML_PARSE_NONET | HTML_PARSE_COMPACT)) +// +// htmlParseChunk(context, bytes, Int32(data.count), 0) +// +// htmlParseChunk(context, nil, 0, 1) +// htmlFreeParserCtxt(context) +// } +// } +//} diff --git a/Modules/Parser/Sources/Parser/SAXParser.swift b/Modules/Parser/Sources/SAX/SAXParser.swift similarity index 83% rename from Modules/Parser/Sources/Parser/SAXParser.swift rename to Modules/Parser/Sources/SAX/SAXParser.swift index c614e063f..160395f8d 100644 --- a/Modules/Parser/Sources/Parser/SAXParser.swift +++ b/Modules/Parser/Sources/SAX/SAXParser.swift @@ -12,7 +12,7 @@ typealias XMLPointer = UnsafePointer protocol SAXParserDelegate { - func saxParser(_: SAXParser, xmlStartElement: XMLPointer, prefix: XMLPointer?, uri: XMLPointer?, namespaceCount: Int, namespaces: UnsafeMutablePointer?, attributeCount: Int, attributesDefaultedCount: Int, attributes: UnsafeMutablePointer?) + func saxParser(_: SAXParser, xmlStartElement: XMLPointer, prefix: XMLPointer?, uri: XMLPointer?, namespaceCount: Int, namespaces: UnsafePointer?, attributeCount: Int, attributesDefaultedCount: Int, attributes: UnsafePointer?) func saxParser(_: SAXParser, xmlEndElement: XMLPointer, prefix: XMLPointer?, uri: XMLPointer?) @@ -69,8 +69,8 @@ final class SAXParser { xmlCtxtUseOptions(context, Int32(XML_PARSE_RECOVER.rawValue | XML_PARSE_NOENT.rawValue)) data.withUnsafeBytes { bufferPointer in - if let bytes = bufferPointer.bindMemory(to: CChar.self).baseAddress { - xmlParseChunk(context, bytes, CInt(data.count), 0) + if let bytes = bufferPointer.bindMemory(to: xmlChar.self).baseAddress { + xmlParseChunk(context, bytes, Int32(data.count), 0) } } @@ -110,16 +110,16 @@ final class SAXParser { var attributeName = String(cString: attribute) if let prefix { let attributePrefix = String(cString: prefix) - attributeName = "\(attributePrefix):\(attributeName!)" + attributeName = "\(attributePrefix):\(attributeName)" } guard let valueStart = attributes[j + 3], let valueEnd = attributes[j + 4] else { continue } let valueCount = valueEnd - valueStart - var value = String(bytes: UnsafeRawBufferPointer(start: valueStart, count: Int(valueCount)), encoding: .utf8) + let value = String(bytes: UnsafeRawBufferPointer(start: valueStart, count: Int(valueCount)), encoding: .utf8) - if let value, let attributeName { + if let value { dictionary[attributeName] = value } @@ -129,12 +129,6 @@ final class SAXParser { return dictionary } - - func stringNoCopy(_ bytes: XMLPointer) -> String { - - let length = strlen(bytes) - return NSString(bytesNoCopy: bytes, length: length, encoding: .utf8, freeWhenDone: false) as String - } } private extension SAXParser { @@ -148,7 +142,7 @@ private extension SAXParser { delegate.saxParser(self, xmlCharactersFound: xmlCharacters, count: count) } - func startElement(_ name: XMLPointer, prefix: XMLPointer?, uri: XMLPointer?, namespaceCount: Int, namespaces: UnsafeMutablePointer?, attributeCount: Int, attributesDefaultedCount: Int, attributes: UnsafeMutablePointer?) { + func startElement(_ name: XMLPointer, prefix: XMLPointer?, uri: XMLPointer?, namespaceCount: Int, namespaces: UnsafePointer?, attributeCount: Int, attributesDefaultedCount: Int, attributes: UnsafePointer?) { delegate.saxParser(self, xmlStartElement: name, prefix: prefix, uri: uri, namespaceCount: namespaceCount, namespaces: namespaces, attributeCount: attributeCount, attributesDefaultedCount: attributesDefaultedCount, attributes: attributes) } @@ -160,7 +154,7 @@ private extension SAXParser { } } -private func startElement(_ context: UnsafeMutableRawPointer?, name: XMLPointer?, prefix: XMLPointer?, URI: XMLPointer?, nb_namespaces: CInt, namespaces: UnsafeMutablePointer?, nb_attributes: CInt, nb_defaulted: CInt, attributes: UnsafeMutablePointer?) { +private func startElement(_ context: UnsafeMutableRawPointer?, name: XMLPointer?, prefix: XMLPointer?, URI: XMLPointer?, nb_namespaces: CInt, namespaces: UnsafePointer?, nb_attributes: CInt, nb_defaulted: CInt, attributes: UnsafeMutablePointer?) { guard let context, let name else { return diff --git a/Modules/Parser/Sources/Parser/SAXUtilities.swift b/Modules/Parser/Sources/SAX/SAXUtilities.swift similarity index 67% rename from Modules/Parser/Sources/Parser/SAXUtilities.swift rename to Modules/Parser/Sources/SAX/SAXUtilities.swift index 81871f08e..bbc2b9352 100644 --- a/Modules/Parser/Sources/Parser/SAXUtilities.swift +++ b/Modules/Parser/Sources/SAX/SAXUtilities.swift @@ -10,9 +10,9 @@ import libxml2 func SAXEqualStrings(_ s1: XMLPointer, _ s2: XMLPointer, length: Int? = nil) -> Bool { - if length == nil { - return Bool(xmlStrEqual(s1, s2)) + if let length { + return xmlStrncmp(s1, s2, Int32(length)) == 0 } - return xmlStrncmp(s1, s2, length) == 0 + return xmlStrEqual(s1, s2) != 0 } diff --git a/Modules/ParserObjC/Sources/ParserObjC/RSOPMLParser.h b/Modules/ParserObjC/Sources/ParserObjC/RSOPMLParser.h deleted file mode 100755 index 8db594b03..000000000 --- a/Modules/ParserObjC/Sources/ParserObjC/RSOPMLParser.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// RSOPMLParser.h -// RSParser -// -// Created by Brent Simmons on 7/12/15. -// Copyright © 2015 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - - -@class ParserData; -@class RSOPMLDocument; - -typedef void (^OPMLParserCallback)(RSOPMLDocument *opmlDocument, NSError *error); - -// Parses on background thread; calls back on main thread. -void RSParseOPML(ParserData *parserData, OPMLParserCallback callback); - - -@interface RSOPMLParser: NSObject - -+ (RSOPMLDocument *)parseOPMLWithParserData:(ParserData *)parserData error:(NSError **)error; - -@end - diff --git a/Modules/ParserObjC/Sources/ParserObjC/RSOPMLParser.m b/Modules/ParserObjC/Sources/ParserObjC/RSOPMLParser.m deleted file mode 100755 index 93f2c420c..000000000 --- a/Modules/ParserObjC/Sources/ParserObjC/RSOPMLParser.m +++ /dev/null @@ -1,310 +0,0 @@ -// -// RSOPMLParser.m -// RSParser -// -// Created by Brent Simmons on 7/12/15. -// Copyright © 2015 Ranchero Software, LLC. All rights reserved. -// - -#import "RSOPMLParser.h" -#import "RSSAXParser.h" -#import "RSOPMLItem.h" -#import "RSOPMLDocument.h" -#import "RSOPMLAttributes.h" -#import "RSOPMLError.h" -#import "RSOPMLParser.h" -#import "ParserData.h" - -#import - - - -@interface RSOPMLParser () - -@property (nonatomic, readwrite) RSOPMLDocument *OPMLDocument; -@property (nonatomic, readwrite) NSError *error; -@property (nonatomic) NSMutableArray *itemStack; - -@end - -void RSParseOPML(ParserData *parserData, OPMLParserCallback callback) { - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - - @autoreleasepool { - NSError *error = nil; - RSOPMLDocument *opmlDocument = [RSOPMLParser parseOPMLWithParserData:parserData error:&error]; - - dispatch_async(dispatch_get_main_queue(), ^{ - callback(opmlDocument, error); - }); - } - }); -} - -@implementation RSOPMLParser - -#pragma mark - Class Methods - -+ (RSOPMLDocument *)parseOPMLWithParserData:(ParserData *)parserData error:(NSError **)error { - - RSOPMLParser *parser = [[RSOPMLParser alloc] initWithParserData:parserData]; - - RSOPMLDocument *document = parser.OPMLDocument; - document.url = parserData.url; - if (parser.error && error) { - *error = parser.error; - return nil; - } - return document; -} - -#pragma mark - Init - -- (instancetype)initWithParserData:(ParserData *)parserData { - - self = [super init]; - if (!self) { - return nil; - } - - [self parse:parserData]; - - return self; -} - - -#pragma mark - Private - -- (void)parse:(ParserData *)parserData { - - @autoreleasepool { - - if (![self canParseData:parserData.data]) { - - NSString *filename = nil; - NSURL *url = [NSURL URLWithString:parserData.url]; - if (url && url.isFileURL) { - filename = url.path.lastPathComponent; - } - if ([parserData.url hasPrefix:@"http"]) { - filename = parserData.url; - } - if (!filename) { - filename = parserData.url; - } - self.error = RSOPMLWrongFormatError(filename); - return; - } - - RSSAXParser *parser = [[RSSAXParser alloc] initWithDelegate:self]; - - self.itemStack = [NSMutableArray new]; - self.OPMLDocument = [RSOPMLDocument new]; - [self pushItem:self.OPMLDocument]; - - [parser parseData:parserData.data]; - [parser finishParsing]; - } -} - -- (BOOL)canParseData:(NSData *)d { - - // Check for 0, nil); - - /*If itemStack is empty, bad things are happening. - But we still shouldn't crash in production.*/ - - if (self.itemStack.count > 0) { - [self.itemStack removeLastObject]; - } -} - - -- (RSOPMLItem *)currentItem { - - return self.itemStack.lastObject; -} - - -#pragma mark - RSSAXParserDelegate - -static const char *kOutline = "outline"; -static const char kOutlineLength = 8; - -- (void)saxParser:(RSSAXParser *)SAXParser XMLStartElement:(const xmlChar *)localName prefix:(const xmlChar *)prefix uri:(const xmlChar *)uri numberOfNamespaces:(NSInteger)numberOfNamespaces namespaces:(const xmlChar **)namespaces numberOfAttributes:(NSInteger)numberOfAttributes numberDefaulted:(int)numberDefaulted attributes:(const xmlChar **)attributes { - - if (RSSAXEqualTags(localName, kTitle, kTitleLength)) { - [SAXParser beginStoringCharacters]; - return; - } - - if (!RSSAXEqualTags(localName, kOutline, kOutlineLength)) { - return; - } - - RSOPMLItem *item = [RSOPMLItem new]; - item.attributes = [SAXParser attributesDictionary:attributes numberOfAttributes:numberOfAttributes]; - - [[self currentItem] addChild:item]; - [self pushItem:item]; -} - - -- (void)saxParser:(RSSAXParser *)SAXParser XMLEndElement:(const xmlChar *)localName prefix:(const xmlChar *)prefix uri:(const xmlChar *)uri { - - if (RSSAXEqualTags(localName, kTitle, kTitleLength)) { - RSOPMLItem* item = [self currentItem]; - if ([item isKindOfClass:[RSOPMLDocument class]]) { - ((RSOPMLDocument *)item).title = SAXParser.currentStringWithTrimmedWhitespace; - } - return; - } - - if (RSSAXEqualTags(localName, kOutline, kOutlineLength)) { - [self popItem]; - } -} - - -static const char *kText = "text"; -static const NSInteger kTextLength = 5; - -static const char *kTitle = "title"; -static const NSInteger kTitleLength = 6; - -static const char *kDescription = "description"; -static const NSInteger kDescriptionLength = 12; - -static const char *kType = "type"; -static const NSInteger kTypeLength = 5; - -static const char *kVersion = "version"; -static const NSInteger kVersionLength = 8; - -static const char *kHTMLURL = "htmlUrl"; -static const NSInteger kHTMLURLLength = 8; - -static const char *kXMLURL = "xmlUrl"; -static const NSInteger kXMLURLLength = 7; - -- (NSString *)saxParser:(RSSAXParser *)SAXParser internedStringForName:(const xmlChar *)name prefix:(const xmlChar *)prefix { - - if (prefix) { - return nil; - } - - size_t nameLength = strlen((const char *)name); - - if (nameLength == kTextLength - 1) { - if (RSSAXEqualTags(name, kText, kTextLength)) { - return OPMLTextKey; - } - if (RSSAXEqualTags(name, kType, kTypeLength)) { - return OPMLTypeKey; - } - } - - else if (nameLength == kTitleLength - 1) { - if (RSSAXEqualTags(name, kTitle, kTitleLength)) { - return OPMLTitleKey; - } - } - - else if (nameLength == kXMLURLLength - 1) { - if (RSSAXEqualTags(name, kXMLURL, kXMLURLLength)) { - return OPMLXMLURLKey; - } - } - - else if (nameLength == kVersionLength - 1) { - if (RSSAXEqualTags(name, kVersion, kVersionLength)) { - return OPMLVersionKey; - } - if (RSSAXEqualTags(name, kHTMLURL, kHTMLURLLength)) { - return OPMLHMTLURLKey; - } - } - - else if (nameLength == kDescriptionLength - 1) { - if (RSSAXEqualTags(name, kDescription, kDescriptionLength)) { - return OPMLDescriptionKey; - } - } - - return nil; -} - - -static const char *kRSSUppercase = "RSS"; -static const char *kRSSLowercase = "rss"; -static const NSUInteger kRSSLength = 3; -static NSString *RSSUppercaseValue = @"RSS"; -static NSString *RSSLowercaseValue = @"rss"; -static NSString *emptyString = @""; - -static BOOL equalBytes(const void *bytes1, const void *bytes2, NSUInteger length) { - - return memcmp(bytes1, bytes2, length) == 0; -} - -- (NSString *)saxParser:(RSSAXParser *)SAXParser internedStringForValue:(const void *)bytes length:(NSUInteger)length { - - - if (length < 1) { - return emptyString; - } - - if (length == kRSSLength) { - - if (equalBytes(bytes, kRSSUppercase, kRSSLength)) { - return RSSUppercaseValue; - } - else if (equalBytes(bytes, kRSSLowercase, kRSSLength)) { - return RSSLowercaseValue; - } - - } - - return nil; -} - - -@end diff --git a/Modules/ParserObjC/Sources/ParserObjC/RSSAXParser.h b/Modules/ParserObjC/Sources/ParserObjC/RSSAXParser.h deleted file mode 100755 index 80ca30a75..000000000 --- a/Modules/ParserObjC/Sources/ParserObjC/RSSAXParser.h +++ /dev/null @@ -1,69 +0,0 @@ -// -// RSSAXParser.h -// RSParser -// -// Created by Brent Simmons on 3/25/15. -// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - -/*Thread-safe, not re-entrant. - - Calls to the delegate will happen on the same thread where the parser runs. - - This is a low-level streaming XML parser, a thin wrapper for libxml2's SAX parser. It doesn't do much Foundation-ifying quite on purpose -- because the goal is performance and low memory use. - - This class is not meant to be sub-classed. Use the delegate methods. - */ - - -@class RSSAXParser; - -@protocol RSSAXParserDelegate - -@optional - -- (void)saxParser:(RSSAXParser *)SAXParser XMLStartElement:(const unsigned char *)localName prefix:(const unsigned char *)prefix uri:(const unsigned char *)uri numberOfNamespaces:(NSInteger)numberOfNamespaces namespaces:(const unsigned char **)namespaces numberOfAttributes:(NSInteger)numberOfAttributes numberDefaulted:(int)numberDefaulted attributes:(const unsigned char **)attributes; - -- (void)saxParser:(RSSAXParser *)SAXParser XMLEndElement:(const unsigned char *)localName prefix:(const unsigned char *)prefix uri:(const unsigned char *)uri; - -// Length is guaranteed to be greater than 0. -- (void)saxParser:(RSSAXParser *)SAXParser XMLCharactersFound:(const unsigned char *)characters length:(NSUInteger)length; - -- (void)saxParserDidReachEndOfDocument:(RSSAXParser *)SAXParser; /*If canceled, may not get called (but might).*/ - -- (NSString *)saxParser:(RSSAXParser *)SAXParser internedStringForName:(const unsigned char *)name prefix:(const unsigned char *)prefix; /*Okay to return nil. Prefix may be nil.*/ - -- (NSString *)saxParser:(RSSAXParser *)SAXParser internedStringForValue:(const void *)bytes length:(NSUInteger)length; - -@end - - -void RSSAXInitLibXMLParser(void); // Needed by RSSAXHTMLParser. - -/*For use by delegate.*/ - -BOOL RSSAXEqualTags(const unsigned char *localName, const char *tag, NSInteger tagLength); - - -@interface RSSAXParser : NSObject - -- (instancetype)initWithDelegate:(id)delegate; - -- (void)parseData:(NSData *)data; -- (void)parseBytes:(const void *)bytes numberOfBytes:(NSUInteger)numberOfBytes; -- (void)finishParsing; -- (void)cancel; - -@property (nonatomic, strong, readonly) NSData *currentCharacters; /*nil if not storing characters. UTF-8 encoded.*/ -@property (nonatomic, strong, readonly) NSString *currentString; /*Convenience to get string version of currentCharacters.*/ -@property (nonatomic, strong, readonly) NSString *currentStringWithTrimmedWhitespace; - -- (void)beginStoringCharacters; /*Delegate can call from XMLStartElement. Characters will be available in XMLEndElement as currentCharacters property. Storing characters is stopped after each XMLEndElement.*/ - -/*Delegate can call from within XMLStartElement. Returns nil if numberOfAttributes < 1.*/ - -- (NSDictionary *)attributesDictionary:(const unsigned char **)attributes numberOfAttributes:(NSInteger)numberOfAttributes; - -@end diff --git a/Modules/ParserObjC/Sources/ParserObjC/RSSAXParser.m b/Modules/ParserObjC/Sources/ParserObjC/RSSAXParser.m deleted file mode 100755 index 02d6988b8..000000000 --- a/Modules/ParserObjC/Sources/ParserObjC/RSSAXParser.m +++ /dev/null @@ -1,353 +0,0 @@ -// -// RSSAXParser.m -// RSParser -// -// Created by Brent Simmons on 3/25/15. -// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved. -// - -#import "RSSAXParser.h" -#import "RSParserInternal.h" - -#import -#import -#import - - - -@interface RSSAXParser () - -@property (nonatomic, weak) id delegate; -@property (nonatomic, assign) xmlParserCtxtPtr context; -@property (nonatomic, assign) BOOL storingCharacters; -@property (nonatomic) NSMutableData *characters; -@property (nonatomic) BOOL delegateRespondsToInternedStringMethod; -@property (nonatomic) BOOL delegateRespondsToInternedStringForValueMethod; -@property (nonatomic) BOOL delegateRespondsToStartElementMethod; -@property (nonatomic) BOOL delegateRespondsToEndElementMethod; -@property (nonatomic) BOOL delegateRespondsToCharactersFoundMethod; -@property (nonatomic) BOOL delegateRespondsToEndOfDocumentMethod; - -@end - - -@implementation RSSAXParser - -+ (void)initialize { - - RSSAXInitLibXMLParser(); -} - - -#pragma mark - Init - -- (instancetype)initWithDelegate:(id)delegate { - - self = [super init]; - if (self == nil) - return nil; - - _delegate = delegate; - - if ([_delegate respondsToSelector:@selector(saxParser:internedStringForName:prefix:)]) { - _delegateRespondsToInternedStringMethod = YES; - } - if ([_delegate respondsToSelector:@selector(saxParser:internedStringForValue:length:)]) { - _delegateRespondsToInternedStringForValueMethod = YES; - } - if ([_delegate respondsToSelector:@selector(saxParser:XMLStartElement:prefix:uri:numberOfNamespaces:namespaces:numberOfAttributes:numberDefaulted:attributes:)]) { - _delegateRespondsToStartElementMethod = YES; - } - if ([_delegate respondsToSelector:@selector(saxParser:XMLEndElement:prefix:uri:)]) { - _delegateRespondsToEndElementMethod = YES; - } - if ([_delegate respondsToSelector:@selector(saxParser:XMLCharactersFound:length:)]) { - _delegateRespondsToCharactersFoundMethod = YES; - } - if ([_delegate respondsToSelector:@selector(saxParserDidReachEndOfDocument:)]) { - _delegateRespondsToEndOfDocumentMethod = YES; - } - - return self; -} - - -#pragma mark - Dealloc - -- (void)dealloc { - if (_context != nil) { - xmlFreeParserCtxt(_context); - _context = nil; - } - _delegate = nil; -} - - -#pragma mark - API - -static xmlSAXHandler saxHandlerStruct; - -- (void)parseData:(NSData *)data { - - [self parseBytes:data.bytes numberOfBytes:data.length]; -} - - -- (void)parseBytes:(const void *)bytes numberOfBytes:(NSUInteger)numberOfBytes { - - if (self.context == nil) { - - self.context = xmlCreatePushParserCtxt(&saxHandlerStruct, (__bridge void *)self, nil, 0, nil); - xmlCtxtUseOptions(self.context, XML_PARSE_RECOVER | XML_PARSE_NOENT); - } - - @autoreleasepool { - xmlParseChunk(self.context, (const char *)bytes, (int)numberOfBytes, 0); - } -} - - -- (void)finishParsing { - - NSAssert(self.context != nil, nil); - if (self.context == nil) - return; - - @autoreleasepool { - xmlParseChunk(self.context, nil, 0, 1); - xmlFreeParserCtxt(self.context); - self.context = nil; - self.characters = nil; - } -} - - -- (void)cancel { - - @autoreleasepool { - xmlStopParser(self.context); - } -} - - -- (void)beginStoringCharacters { - self.storingCharacters = YES; - self.characters = [NSMutableData new]; -} - - -- (void)endStoringCharacters { - self.storingCharacters = NO; - self.characters = nil; -} - - -- (NSData *)currentCharacters { - - if (!self.storingCharacters) { - return nil; - } - - return self.characters; -} - - -- (NSString *)currentString { - - NSData *d = self.currentCharacters; - if (RSParserObjectIsEmpty(d)) { - return nil; - } - - return [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding]; -} - - -- (NSString *)currentStringWithTrimmedWhitespace { - - return [self.currentString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; -} - - -#pragma mark - Attributes Dictionary - -- (NSDictionary *)attributesDictionary:(const xmlChar **)attributes numberOfAttributes:(NSInteger)numberOfAttributes { - - if (numberOfAttributes < 1 || !attributes) { - return nil; - } - - NSMutableDictionary *d = [NSMutableDictionary new]; - - @autoreleasepool { - NSInteger i = 0, j = 0; - for (i = 0, j = 0; i < numberOfAttributes; i++, j+=5) { - - NSUInteger lenValue = (NSUInteger)(attributes[j + 4] - attributes[j + 3]); - NSString *value = nil; - - if (self.delegateRespondsToInternedStringForValueMethod) { - value = [self.delegate saxParser:self internedStringForValue:(const void *)attributes[j + 3] length:lenValue]; - } - if (!value) { - value = [[NSString alloc] initWithBytes:(const void *)attributes[j + 3] length:lenValue encoding:NSUTF8StringEncoding]; - } - - NSString *attributeName = nil; - - if (self.delegateRespondsToInternedStringMethod) { - attributeName = [self.delegate saxParser:self internedStringForName:(const xmlChar *)attributes[j] prefix:(const xmlChar *)attributes[j + 1]]; - } - - if (!attributeName) { - attributeName = [NSString stringWithUTF8String:(const char *)attributes[j]]; - if (attributes[j + 1]) { - NSString *attributePrefix = [NSString stringWithUTF8String:(const char *)attributes[j + 1]]; - attributeName = [NSString stringWithFormat:@"%@:%@", attributePrefix, attributeName]; - } - } - - if (value && attributeName) { - d[attributeName] = value; - } - } - } - - return d; -} - - -#pragma mark - Equal Tags - -BOOL RSSAXEqualTags(const xmlChar *localName, const char *tag, NSInteger tagLength) { - - if (!localName) { - return NO; - } - return !strncmp((const char *)localName, tag, (size_t)tagLength); -} - - -#pragma mark - Callbacks - -- (void)xmlEndDocument { - - @autoreleasepool { - if (self.delegateRespondsToEndOfDocumentMethod) { - [self.delegate saxParserDidReachEndOfDocument:self]; - } - - [self endStoringCharacters]; - } -} - - -- (void)xmlCharactersFound:(const xmlChar *)ch length:(NSUInteger)length { - - if (length < 1) { - return; - } - - @autoreleasepool { - if (self.storingCharacters) { - [self.characters appendBytes:(const void *)ch length:length]; - } - - if (self.delegateRespondsToCharactersFoundMethod) { - [self.delegate saxParser:self XMLCharactersFound:ch length:length]; - } - } -} - - -- (void)xmlStartElement:(const xmlChar *)localName prefix:(const xmlChar *)prefix uri:(const xmlChar *)uri numberOfNamespaces:(int)numberOfNamespaces namespaces:(const xmlChar **)namespaces numberOfAttributes:(int)numberOfAttributes numberDefaulted:(int)numberDefaulted attributes:(const xmlChar **)attributes { - - @autoreleasepool { - if (self.delegateRespondsToStartElementMethod) { - - [self.delegate saxParser:self XMLStartElement:localName prefix:prefix uri:uri numberOfNamespaces:numberOfNamespaces namespaces:namespaces numberOfAttributes:numberOfAttributes numberDefaulted:numberDefaulted attributes:attributes]; - } - } -} - - -- (void)xmlEndElement:(const xmlChar *)localName prefix:(const xmlChar *)prefix uri:(const xmlChar *)uri { - - @autoreleasepool { - if (self.delegateRespondsToEndElementMethod) { - [self.delegate saxParser:self XMLEndElement:localName prefix:prefix uri:uri]; - } - - [self endStoringCharacters]; - } -} - - -@end - - -static void startElementSAX(void *context, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI, int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes) { - - [(__bridge RSSAXParser *)context xmlStartElement:localname prefix:prefix uri:URI numberOfNamespaces:nb_namespaces namespaces:namespaces numberOfAttributes:nb_attributes numberDefaulted:nb_defaulted attributes:attributes]; -} - - -static void endElementSAX(void *context, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI) { - [(__bridge RSSAXParser *)context xmlEndElement:localname prefix:prefix uri:URI]; -} - - -static void charactersFoundSAX(void *context, const xmlChar *ch, int len) { - [(__bridge RSSAXParser *)context xmlCharactersFound:ch length:(NSUInteger)len]; -} - - -static void endDocumentSAX(void *context) { - [(__bridge RSSAXParser *)context xmlEndDocument]; -} - - -static xmlSAXHandler saxHandlerStruct = { - nil, /* internalSubset */ - nil, /* isStandalone */ - nil, /* hasInternalSubset */ - nil, /* hasExternalSubset */ - nil, /* resolveEntity */ - nil, /* getEntity */ - nil, /* entityDecl */ - nil, /* notationDecl */ - nil, /* attributeDecl */ - nil, /* elementDecl */ - nil, /* unparsedEntityDecl */ - nil, /* setDocumentLocator */ - nil, /* startDocument */ - endDocumentSAX, /* endDocument */ - nil, /* startElement*/ - nil, /* endElement */ - nil, /* reference */ - charactersFoundSAX, /* characters */ - nil, /* ignorableWhitespace */ - nil, /* processingInstruction */ - nil, /* comment */ - nil, /* warning */ - nil, /* error */ - nil, /* fatalError //: unused error() get all the errors */ - nil, /* getParameterEntity */ - nil, /* cdataBlock */ - nil, /* externalSubset */ - XML_SAX2_MAGIC, - nil, - startElementSAX, /* startElementNs */ - endElementSAX, /* endElementNs */ - nil /* serror */ -}; - - -void RSSAXInitLibXMLParser(void) { - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - xmlInitParser(); - }); -} -