Epub阅读器0.0.1

This commit is contained in:
shen
2026-05-21 19:40:51 +08:00
commit daa36d8fe7
559 changed files with 106266 additions and 0 deletions
@@ -0,0 +1,182 @@
//
// DTHTMLParser.h
// DTCoreText
//
// Created by Oliver Drobnik on 1/18/12.
// Copyright (c) 2012 Cocoanetics. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <DTFoundation/DTWeakSupport.h>
@class DTHTMLParser;
/** The DTHTMLParserDelegate protocol defines the optional methods implemented by delegates of DTHTMLParser objects.
Dependencies: libxml2.dylib
*/
@protocol DTHTMLParserDelegate <NSObject>
@optional
/**
Sent by the parser object to the delegate when it begins parsing a document.
@param parser A parser object.
*/
- (void)parserDidStartDocument:(DTHTMLParser *)parser;
/**
Sent by the parser object to the delegate when it has successfully completed parsing
@param parser A parser object.
*/
- (void)parserDidEndDocument:(DTHTMLParser *)parser;
/**
Sent by a parser object to its delegate when it encounters a start tag for a given element.
@param parser A parser object.
@param elementName A string that is the name of an element (in its start tag).
@param attributeDict A dictionary that contains any attributes associated with the element. Keys are the names of attributes, and values are attribute values.
*/
- (void)parser:(DTHTMLParser *)parser didStartElement:(NSString *)elementName attributes:(NSDictionary *)attributeDict;
/**
Sent by a parser object to its delegate when it encounters an end tag for a specific element.
@param parser A parser object.
@param elementName A string that is the name of an element (in its end tag).
*/
- (void)parser:(DTHTMLParser *)parser didEndElement:(NSString *)elementName;
/**
Sent by a parser object to provide its delegate with a string representing all or part of the characters of the current element.
The parser object may send the delegate several parser:foundCharacters: messages to report the characters of an element. Because string may be only part of the total character content for the current element, you should append it to the current accumulation of characters until the element changes.
@param parser A parser object.
@param string A string representing the complete or partial textual content of the current element.
*/
- (void)parser:(DTHTMLParser *)parser foundCharacters:(NSString *)string;
/**
Sent by a parser object to its delegate when it encounters a comment in the HTML.
@param parser A DTHTMLParser object parsing HTML.
@param comment A string that is a the content of a comment in the XML.
*/
- (void)parser:(DTHTMLParser *)parser foundComment:(NSString *)comment;
/**
Sent by a parser object to its delegate when it encounters a CDATA block.
Through this method the parser object passes the contents of the block to its delegate in an NSData object. The CDATA block is character data that is ignored by the parser. The encoding of the character data is UTF-8. To convert the data object to a string object, use the NSString method initWithData:encoding:. Note: CSS style blocks are returned as CDATA.
@param parser A DTHTMLParser object parsing HTML.
@param CDATABlock A data object containing a block of CDATA.
*/
- (void)parser:(DTHTMLParser *)parser foundCDATA:(NSData *)CDATABlock;
/**
Sent by a parser object to its delegate when it encounters a processing instruction.
@param parser A DTHTMLParser object parsing HTML.
@param target A string representing the target of a processing instruction.
@param data A string representing the data for a processing instruction.
*/
- (void)parser:(DTHTMLParser *)parser foundProcessingInstructionWithTarget:(NSString *)target data:(NSString *)data;
/**
Sent by a parser object to its delegate when it encounters a fatal error.
When this method is invoked, parsing is stopped. For further information about the error, you can query parseError or you can send the parser a parserError message. You can also send the parser lineNumber and columnNumber messages to further isolate where the error occurred. Typically you implement this method to display information about the error to the user.
@param parser A parser object.
@param parseError An `NSError` object describing the parsing error that occurred.
*/
- (void)parser:(DTHTMLParser *)parser parseErrorOccurred:(NSError *)parseError;
@end
/** Instances of this class parse HTML documents (including DTD declarations) in an event-driven manner. A DTHTMLParser notifies its delegate about the items (elements, attributes, CDATA blocks, comments, and so on) that it encounters as it processes an HTML document. It does not itself do anything with those parsed items except report them. It also reports parsing errors. For convenience, an DTHTMLParser object in the following descriptions is sometimes referred to as a parser object.
*/
@interface DTHTMLParser : NSObject
/**-------------------------------------------------------------------------------------
@name Initializing a Parser Object
---------------------------------------------------------------------------------------
*/
/**
Initializes the receiver with the HTML contents encapsulated in a given data object.
@param data An `NSData` object containing XML markup.
@param encoding The encoding used for encoding the HTML data
@returns An initialized `DTHTMLParser` object or nil if an error occurs.
*/
- (id)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding;
/**-------------------------------------------------------------------------------------
@name Parsing
---------------------------------------------------------------------------------------
*/
/**
Starts the event-driven parsing operation.
If you invoke this method, the delegate, if it implements parser:parseErrorOccurred:, is informed of the cancelled parsing operation.
@returns `YES` if parsing is successful and `NO` in there is an error or if the parsing operation is aborted.
*/
- (BOOL)parse;
/**
Stops the parser object.
@see parse
@see parserError
*/
- (void)abortParsing;
/**
The receivers delegate. It is not retained. The delegate must conform to the DTHTMLParserDelegate Protocol protocol.
*/
@property (nonatomic, DT_WEAK_PROPERTY) id <DTHTMLParserDelegate> delegate;
/**
Returns the column number of the XML document being processed by the receiver.
The column refers to the nesting level of the HTML elements in the document. You may invoke this method once a parsing operation has begun or after an error occurs.
*/
@property (nonatomic, readonly) NSInteger columnNumber;
/**
Returns the line number of the HTML document being processed by the receiver.
You may invoke this method once a parsing operation has begun or after an error occurs.
*/
@property (nonatomic, readonly) NSInteger lineNumber;
/**
Returns an `NSError` object from which you can obtain information about a parsing error.
You may invoke this method after a parsing operation abnormally terminates to determine the cause of error.
*/
@property (nonatomic, readonly, strong) NSError *parserError;
/**
Returns the public identifier of the external entity referenced in the HTML document.
You may invoke this method once a parsing operation has begun or after an error occurs.
*/
@property (nonatomic, readonly) NSString *publicID;
/**
Returns the system identifier of the external entity referenced in the HTML document.
You may invoke this method once a parsing operation has begun or after an error occurs.
*/
@property (nonatomic, readonly) NSString *systemID;
@end
@@ -0,0 +1,484 @@
//
// 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 = XML_CHAR_ENCODING_NONE;
if (!_encoding)
{
charEnc = xmlDetectCharEncoding(dataBytes, (int)dataSize);
}
else
{
// convert the encoding
CFStringEncoding cfenc = CFStringConvertNSStringEncodingToEncoding(_encoding);
if (cfenc != kCFStringEncodingInvalidId)
{
CFStringRef cfencstr = CFStringConvertEncodingToIANACharSetName(cfenc);
if (cfencstr)
{
NSString *NS_VALID_UNTIL_END_OF_SCOPE encstr = [NSString stringWithString:(__bridge NSString*)cfencstr];
const char *enc = [encstr UTF8String];
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