mirror of
https://github.com/wu736139669/ManagerDemo.git
synced 2026-08-06 06:14:32 +00:00
477 lines
11 KiB
Objective-C
477 lines
11 KiB
Objective-C
//
|
|
// DTHTMLParser.m
|
|
// DTFoundation
|
|
//
|
|
// Created by Oliver Drobnik on 1/18/12.
|
|
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
|
//
|
|
|
|
#import "DTHTMLParser.h"
|
|
#import <libxml/HTMLparser.h>
|
|
|
|
|
|
@interface DTHTMLParser()
|
|
|
|
@property (nonatomic, strong) NSError *parserError;
|
|
@property (nonatomic, assign) NSStringEncoding encoding;
|
|
|
|
- (void)_resetAccumulateBufferAndReportCharacters;
|
|
- (void)_accumulateCharacters:(const xmlChar *)characters length:(int)length;
|
|
|
|
@end
|
|
|
|
|
|
#pragma mark Event function prototypes
|
|
|
|
void _startDocument(void *context);
|
|
void _endDocument(void *context);
|
|
void _startElement(void *context, const xmlChar *name,const xmlChar **atts);
|
|
void _startElement_no_delegate(void *context, const xmlChar *name, const xmlChar **atts);
|
|
void _endElement(void *context, const xmlChar *name);
|
|
void _endElement_no_delegate(void *context, const xmlChar *chars);
|
|
void _characters(void *context, const xmlChar *ch, int len);
|
|
void _comment(void *context, const xmlChar *value);
|
|
void _dterror(void *context, const char *msg, ...);
|
|
void _cdataBlock(void *context, const xmlChar *value, int len);
|
|
void _processingInstruction (void *context, const xmlChar *target, const xmlChar *data);
|
|
|
|
#pragma mark Event functions
|
|
void _startDocument(void *context)
|
|
{
|
|
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
|
|
|
[myself.delegate parserDidStartDocument:myself];
|
|
}
|
|
|
|
void _endDocument(void *context)
|
|
{
|
|
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
|
|
|
[myself.delegate parserDidEndDocument:myself];
|
|
}
|
|
|
|
void _startElement(void *context, const xmlChar *name, const xmlChar **atts)
|
|
{
|
|
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
|
|
|
[myself _resetAccumulateBufferAndReportCharacters];
|
|
|
|
NSString *nameStr = [NSString stringWithUTF8String:(char *)name];
|
|
|
|
NSMutableDictionary *attributes = nil;
|
|
|
|
if (atts)
|
|
{
|
|
NSString *key = nil;
|
|
NSString *value = nil;
|
|
|
|
attributes = [[NSMutableDictionary alloc] init];
|
|
|
|
int i = 0;
|
|
while (1)
|
|
{
|
|
char *att = (char *)atts[i++];
|
|
|
|
if (!key)
|
|
{
|
|
if (!att)
|
|
{
|
|
// we're done
|
|
break;
|
|
}
|
|
|
|
key = [NSString stringWithUTF8String:att];
|
|
}
|
|
else
|
|
{
|
|
if (att)
|
|
{
|
|
value = [NSString stringWithUTF8String:att];
|
|
}
|
|
else
|
|
{
|
|
// solo attribute
|
|
value = key;
|
|
}
|
|
|
|
[attributes setObject:value forKey:key];
|
|
|
|
value = nil;
|
|
key = nil;
|
|
}
|
|
}
|
|
}
|
|
|
|
[myself.delegate parser:myself didStartElement:nameStr attributes:attributes];
|
|
}
|
|
|
|
void _startElement_no_delegate(void *context, const xmlChar *name, const xmlChar **atts)
|
|
{
|
|
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
|
|
|
[myself _resetAccumulateBufferAndReportCharacters];
|
|
}
|
|
|
|
void _endElement(void *context, const xmlChar *chars)
|
|
{
|
|
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
|
|
|
[myself _resetAccumulateBufferAndReportCharacters];
|
|
|
|
NSString *nameStr = [NSString stringWithUTF8String:(char *)chars];
|
|
|
|
[myself.delegate parser:myself didEndElement:nameStr];
|
|
}
|
|
|
|
void _endElement_no_delegate(void *context, const xmlChar *chars)
|
|
{
|
|
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
|
|
|
[myself _resetAccumulateBufferAndReportCharacters];
|
|
}
|
|
|
|
// libxml reports characters in batches of at most 1000 at a time
|
|
// in addition, entities are reported separately
|
|
void _characters(void *context, const xmlChar *chars, int len)
|
|
{
|
|
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
|
|
|
[myself _accumulateCharacters:chars length:len];
|
|
}
|
|
|
|
void _comment(void *context, const xmlChar *chars)
|
|
{
|
|
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
|
|
|
NSString *string = [NSString stringWithCString:(const char *)chars encoding:myself.encoding];
|
|
|
|
[myself.delegate parser:myself foundComment:string];
|
|
}
|
|
|
|
void _dterror(void *context, const char *msg, ...)
|
|
{
|
|
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
|
|
|
char string[256];
|
|
va_list arg_ptr;
|
|
|
|
va_start(arg_ptr, msg);
|
|
vsnprintf(string, 256, msg, arg_ptr);
|
|
va_end(arg_ptr);
|
|
|
|
NSString *errorMsg = [NSString stringWithUTF8String:string];
|
|
|
|
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMsg forKey:NSLocalizedDescriptionKey];
|
|
myself.parserError = [NSError errorWithDomain:@"DTHTMLParser" code:1 userInfo:userInfo];
|
|
|
|
[myself.delegate parser:myself parseErrorOccurred:myself.parserError];
|
|
}
|
|
|
|
void _cdataBlock(void *context, const xmlChar *value, int len)
|
|
{
|
|
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
|
|
|
NSData *data = [NSData dataWithBytes:(const void *)value length:len];
|
|
|
|
[myself.delegate parser:myself foundCDATA:data];
|
|
}
|
|
|
|
void _processingInstruction (void *context, const xmlChar *target, const xmlChar *data)
|
|
{
|
|
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
|
|
|
NSStringEncoding encoding = myself.encoding;
|
|
|
|
NSString *targetStr = [NSString stringWithCString:(const char *)target encoding:encoding];
|
|
NSString *dataStr = [NSString stringWithCString:(const char *)data encoding:encoding];
|
|
|
|
[myself.delegate parser:myself foundProcessingInstructionWithTarget:targetStr data:dataStr];
|
|
}
|
|
|
|
@implementation DTHTMLParser
|
|
{
|
|
htmlSAXHandler _handler;
|
|
|
|
NSData *_data;
|
|
NSStringEncoding _encoding;
|
|
|
|
DT_WEAK_VARIABLE id <DTHTMLParserDelegate> _delegate;
|
|
htmlParserCtxtPtr _parserContext;
|
|
|
|
NSMutableString *_accumulateBuffer;
|
|
|
|
BOOL _isAborting;
|
|
}
|
|
|
|
|
|
- (id)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding
|
|
{
|
|
if (!data)
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
self = [super init];
|
|
if (self)
|
|
{
|
|
_data = data;
|
|
_encoding = encoding;
|
|
|
|
xmlSAX2InitHtmlDefaultSAXHandler(&_handler);
|
|
|
|
// set default handlers as we would crash otherwise
|
|
self.delegate = nil;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
if (_parserContext)
|
|
{
|
|
htmlFreeParserCtxt(_parserContext);
|
|
}
|
|
}
|
|
|
|
- (void)_resetAccumulateBufferAndReportCharacters
|
|
{
|
|
if (!_accumulateBuffer.length)
|
|
{
|
|
// nothing in the buffer
|
|
return;
|
|
}
|
|
|
|
[self.delegate parser:self foundCharacters:_accumulateBuffer];
|
|
|
|
// reset buffer
|
|
_accumulateBuffer = nil;
|
|
}
|
|
|
|
- (void)_accumulateCharacters:(const xmlChar *)characters length:(int)length
|
|
{
|
|
if (!_accumulateBuffer)
|
|
{
|
|
_accumulateBuffer = [[NSMutableString alloc] initWithBytes:characters length:length encoding:NSUTF8StringEncoding];
|
|
}
|
|
else
|
|
{
|
|
// we don't need to use the copy version since _accumulateBuffer will copy characters immediately
|
|
[_accumulateBuffer appendString:[[NSString alloc] initWithBytesNoCopy:(void *)characters length:length encoding:NSUTF8StringEncoding freeWhenDone:NO]];
|
|
}
|
|
}
|
|
|
|
- (BOOL)parse
|
|
{
|
|
void *dataBytes = (char *)[_data bytes];
|
|
unsigned long dataSize = [_data length];
|
|
|
|
// detect encoding if necessary
|
|
xmlCharEncoding charEnc = 0;
|
|
|
|
if (!_encoding)
|
|
{
|
|
charEnc = xmlDetectCharEncoding(dataBytes, (int)dataSize);
|
|
}
|
|
else
|
|
{
|
|
// convert the encoding
|
|
// TODO: proper mapping from _encoding to xmlCharEncoding
|
|
CFStringEncoding cfenc = CFStringConvertNSStringEncodingToEncoding(_encoding);
|
|
CFStringRef cfencstr = CFStringConvertEncodingToIANACharSetName(cfenc);
|
|
const char *enc = CFStringGetCStringPtr(cfencstr, 0);
|
|
|
|
charEnc = xmlParseCharEncoding(enc);
|
|
}
|
|
|
|
// create a parse context
|
|
_parserContext = htmlCreatePushParserCtxt(&_handler, (__bridge void *)self, dataBytes, (int)dataSize, NULL, charEnc);
|
|
|
|
// set some options
|
|
htmlCtxtUseOptions(_parserContext, HTML_PARSE_RECOVER | HTML_PARSE_NONET | HTML_PARSE_COMPACT | HTML_PARSE_NOBLANKS);
|
|
|
|
// parse!
|
|
int result = htmlParseDocument(_parserContext);
|
|
|
|
return (result==0 && !_isAborting);
|
|
}
|
|
|
|
- (void)abortParsing
|
|
{
|
|
if (_parserContext)
|
|
{
|
|
// apparently this frees it too
|
|
xmlStopParser(_parserContext);
|
|
_parserContext = NULL;
|
|
}
|
|
|
|
_isAborting = YES;
|
|
|
|
// prevent future callbacks
|
|
_handler.startDocument = NULL;
|
|
_handler.endDocument = NULL;
|
|
_handler.startElement = NULL;
|
|
_handler.endElement = NULL;
|
|
_handler.characters = NULL;
|
|
_handler.comment = NULL;
|
|
_handler.error = NULL;
|
|
_handler.processingInstruction = NULL;
|
|
|
|
// inform delegate
|
|
__strong __typeof__(_delegate) delegate = _delegate;
|
|
if ([delegate respondsToSelector:@selector(parser:parseErrorOccurred:)])
|
|
{
|
|
[delegate parser:self parseErrorOccurred:self.parserError];
|
|
}
|
|
}
|
|
|
|
#pragma mark Properties
|
|
|
|
- (id <DTHTMLParserDelegate>)delegate
|
|
{
|
|
return _delegate;
|
|
}
|
|
|
|
- (void)setDelegate:(id <DTHTMLParserDelegate>)delegate
|
|
{
|
|
_delegate = delegate;
|
|
|
|
if ([delegate respondsToSelector:@selector(parserDidStartDocument:)])
|
|
{
|
|
_handler.startDocument = _startDocument;
|
|
}
|
|
else
|
|
{
|
|
_handler.startDocument = NULL;
|
|
}
|
|
|
|
if ([delegate respondsToSelector:@selector(parserDidEndDocument:)])
|
|
{
|
|
_handler.endDocument = _endDocument;
|
|
}
|
|
else
|
|
{
|
|
_handler.endDocument = NULL;
|
|
}
|
|
|
|
if ([delegate respondsToSelector:@selector(parser:foundCharacters:)])
|
|
{
|
|
_handler.characters = _characters;
|
|
}
|
|
else
|
|
{
|
|
_handler.characters = NULL;
|
|
}
|
|
|
|
if ([delegate respondsToSelector:@selector(parser:didStartElement:attributes:)])
|
|
{
|
|
_handler.startElement = _startElement;
|
|
}
|
|
else
|
|
{
|
|
// if there is a character handler we need to still report start of elements for accumulation
|
|
if (_handler.characters)
|
|
{
|
|
_handler.startElement = _startElement_no_delegate;
|
|
}
|
|
else
|
|
{
|
|
_handler.startElement = NULL;
|
|
}
|
|
}
|
|
|
|
if ([delegate respondsToSelector:@selector(parser:didEndElement:)])
|
|
{
|
|
_handler.endElement = _endElement;
|
|
}
|
|
else
|
|
{
|
|
// if there is a character handler we need to still report start of elements for accumulation
|
|
if (_handler.characters)
|
|
{
|
|
_handler.endElement = _endElement_no_delegate;
|
|
}
|
|
else
|
|
{
|
|
_handler.endElement = NULL;
|
|
}
|
|
}
|
|
|
|
if ([delegate respondsToSelector:@selector(parser:foundComment:)])
|
|
{
|
|
_handler.comment = _comment;
|
|
}
|
|
else
|
|
{
|
|
_handler.comment = NULL;
|
|
}
|
|
|
|
if ([delegate respondsToSelector:@selector(parser:parseErrorOccurred:)])
|
|
{
|
|
_handler.error = _dterror;
|
|
}
|
|
else
|
|
{
|
|
_handler.error = NULL;
|
|
}
|
|
|
|
if ([delegate respondsToSelector:@selector(parser:foundCDATA:)])
|
|
{
|
|
_handler.cdataBlock = _cdataBlock;
|
|
}
|
|
else
|
|
{
|
|
_handler.cdataBlock = NULL;
|
|
}
|
|
|
|
if ([delegate respondsToSelector:@selector(parser:foundProcessingInstructionWithTarget:data:)])
|
|
{
|
|
_handler.processingInstruction = _processingInstruction;
|
|
}
|
|
else
|
|
{
|
|
_handler.processingInstruction = NULL;
|
|
}
|
|
}
|
|
|
|
- (NSInteger)lineNumber
|
|
{
|
|
return xmlSAX2GetLineNumber(_parserContext);
|
|
}
|
|
|
|
- (NSInteger)columnNumber
|
|
{
|
|
return xmlSAX2GetColumnNumber(_parserContext);
|
|
}
|
|
|
|
- (NSString *)systemID
|
|
{
|
|
char *systemID = (char *)xmlSAX2GetSystemId(_parserContext);
|
|
|
|
if (!systemID)
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
return [NSString stringWithUTF8String:systemID];
|
|
}
|
|
|
|
- (NSString *)publicID
|
|
{
|
|
char *publicID = (char *)xmlSAX2GetPublicId(_parserContext);
|
|
|
|
if (!publicID)
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
return [NSString stringWithUTF8String:publicID];
|
|
}
|
|
|
|
|
|
@synthesize parserError = _parserError;
|
|
@synthesize encoding = _encoding;
|
|
|
|
|
|
@end
|