644 lines
21 KiB
Objective-C
644 lines
21 KiB
Objective-C
//
|
|
// DTHTMLAttributedStringBuilder.m
|
|
// DTCoreText (WeRead custom fork)
|
|
//
|
|
// Reverse-engineered pseudo-implementation from WeChat Reading binary.
|
|
// This file reconstructs the full HTML → NSAttributedString pipeline.
|
|
//
|
|
// Pipeline overview:
|
|
// 1. Parse raw HTML via DTHTMLParser (SAX-style).
|
|
// 2. Each SAX event (startElement / endElement / foundCharacters / CDATA)
|
|
// builds or mutates DTHTMLElement nodes in a DOM tree.
|
|
// 3. After parsing completes, -_buildString walks the DOM tree and calls
|
|
// -[DTHTMLElement attributedString] recursively.
|
|
// 4. The resulting NSAttributedString is stored in _generatedAttributedString.
|
|
//
|
|
|
|
#import "DTHTMLAttributedStringBuilder.h"
|
|
#import "DTHTMLParserDelegate.h"
|
|
#import "DTHTMLElement.h"
|
|
#import "DTCSSStylesheet.h"
|
|
#import "DTCoreTextFontDescriptor.h"
|
|
#import "DTCoreTextParagraphStyle.h"
|
|
#import "DTHTMLParser.h"
|
|
#import "WRBook.h"
|
|
#import "WRChapter.h"
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// WeRead custom attribute key definitions
|
|
// ---------------------------------------------------------------------------
|
|
NSString *const DTPageBackgroundColorAttribute = @"DTPageBackgroundColorAttribute";
|
|
NSString *const DTPageBackgroundImageAttribute = @"DTPageBackgroundImageAttribute";
|
|
NSString *const DTPageBackgroundImagePathAttribute = @"DTPageBackgroundImagePathAttribute";
|
|
NSString *const DTPageBreakAfterAttribute = @"DTPageBreakAfterAttribute";
|
|
NSString *const DTPageBreakBeforeAttribute = @"DTPageBreakBeforeAttribute";
|
|
NSString *const DTPageBreakInsideAvoidAttribute = @"DTPageBreakInsideAvoidAttribute";
|
|
NSString *const DTPageRelateAttribute = @"DTPageRelateAttribute";
|
|
NSString *const DTPageSize = @"DTPageSize";
|
|
NSString *const DTPageFlippingStyle = @"DTPageFlippingStyle";
|
|
NSString *const DTHTMLVerticalCenterAttribute = @"DTHTMLVerticalCenterAttribute";
|
|
NSString *const DTHTMLTranslateTagAttribute = @"DTHTMLTranslateTagAttribute";
|
|
NSString *const DTHTMLTranslateNoStyleAttribute = @"DTHTMLTranslateNoStyleAttribute";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
#pragma mark - Private interface
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@interface DTHTMLAttributedStringBuilder ()
|
|
{
|
|
// ---- ivar: the SAX parser delegate that accumulates the DOM tree ----
|
|
DTHTMLParserDelegate *_parserDelegate;
|
|
|
|
// ---- Cached input ----
|
|
NSData *_htmlData;
|
|
DTCSSStylesheet *_cssStyleSheet;
|
|
NSDictionary *_options;
|
|
|
|
// ---- Result ----
|
|
NSAttributedString *_generatedAttributedString;
|
|
|
|
// ---- WeRead book/chapter context ----
|
|
WRBook *_book;
|
|
WRChapter *_chapter;
|
|
}
|
|
|
|
@end
|
|
|
|
// ---------------------------------------------------------------------------
|
|
#pragma mark - DTHTMLParserDelegate (internal helper)
|
|
// ---------------------------------------------------------------------------
|
|
// In the actual binary this is a separate class whose ivars include all the
|
|
// mutable state needed during parsing. We define it here to show the fields
|
|
// that DTHTMLAttributedStringBuilder delegates to.
|
|
|
|
@interface DTHTMLParserDelegate : NSObject
|
|
{
|
|
// Current tag handlers (block-based dispatch tables keyed on tag name)
|
|
NSData *_tagStartHandlers; // actually a block map
|
|
NSDictionary *_tagEndHandlers;
|
|
|
|
// CSS stylesheet applied during parsing
|
|
DTCSSStylesheet *_cssStyleSheet;
|
|
|
|
// Base URL for resolving relative links / images
|
|
NSURL *_baseURL;
|
|
|
|
// Default font / paragraph descriptors used when no CSS overrides exist
|
|
DTCoreTextFontDescriptor *_defaultFontDescriptor;
|
|
DTCoreTextParagraphStyle *_defaultParagraphStyle;
|
|
|
|
// The root of the DOM tree being built
|
|
DTHTMLElement *_rootElement;
|
|
|
|
// Stack of open elements (for nesting / parent resolution)
|
|
NSMutableDictionary *_elementStack; // index → DTHTMLElement
|
|
|
|
// Accumulated text for the current text run
|
|
NSMutableDictionary *_currentTextBuffer;
|
|
|
|
// "Current" element pointers (set during SAX traversal)
|
|
DTHTMLElement *_currentElement;
|
|
DTHTMLElement *_parentElement;
|
|
DTHTMLElement *_lastInlineElement;
|
|
DTHTMLElement *_lastBlockElement;
|
|
|
|
// WeRead-specific rendering context
|
|
WRBook *_book;
|
|
WRChapter *_chapter;
|
|
|
|
// Final output accumulator
|
|
NSMutableAttributedString *_outputString;
|
|
|
|
// Image / view references (for lazy image loading)
|
|
UIImageView *_currentImageView;
|
|
UIView *_imageContainerView;
|
|
UIView *_currentView;
|
|
NSString *_currentImageSrc;
|
|
UIView *_parentView;
|
|
UIView *_rootView;
|
|
|
|
// Collected tag list for post-processing
|
|
NSArray *_tagOrder;
|
|
}
|
|
|
|
// Methods used during SAX parsing
|
|
- (void)_registerTagStartHandlers;
|
|
- (void)_registerTagEndHandlers;
|
|
|
|
@end
|
|
|
|
@implementation DTHTMLParserDelegate
|
|
|
|
// --------------------------------------------------
|
|
# pragma mark Handler registration
|
|
// --------------------------------------------------
|
|
|
|
/**
|
|
Registers block handlers for HTML start tags.
|
|
Each handler receives the element name, attributes dict, and position,
|
|
then creates or configures the appropriate DTHTMLElement subclass.
|
|
|
|
WeRead registers custom handlers for their proprietary CSS attributes:
|
|
- wr-vertical-center-style → DTHTMLVerticalCenterAttribute
|
|
- weread-page-relate → DTPageRelateAttribute
|
|
- avoidPageBreakInside → DTPageBreakInsideAvoidAttribute
|
|
- DTPageBreakAfter / DTPageBreakBefore
|
|
- DTPageBackgroundColor / DTPageBackgroundImage
|
|
*/
|
|
- (void)_registerTagStartHandlers
|
|
{
|
|
// Pseudo-code: build a dictionary mapping tag names to handler blocks.
|
|
//
|
|
// _tagStartHandlers = @{
|
|
// @"p" : ^(NSString *tag, NSDictionary *attrs, NSUInteger pos) { ... },
|
|
// @"div" : ^(NSString *tag, NSDictionary *attrs, NSUInteger pos) { ... },
|
|
// @"img" : ^(NSString *tag, NSDictionary *attrs, NSUInteger pos) { ... },
|
|
// @"br" : ^(NSString *tag, NSDictionary *attrs, NSUInteger pos) { ... },
|
|
// @"a" : ^(NSString *tag, NSDictionary *attrs, NSUInteger pos) { ... },
|
|
// @"h1" : ^(NSString *tag, NSDictionary *attrs, NSUInteger pos) { ... },
|
|
// ...
|
|
// // WeRead custom tags / attributes handled here
|
|
// };
|
|
//
|
|
// Each handler typically:
|
|
// 1. Creates a new DTHTMLElement.
|
|
// 2. Sets its tagName, attributes, parent.
|
|
// 3. Calls -[DTHTMLElement applyStyleDictionary:isLatinLanguageBook:].
|
|
// 4. Pushes the element onto _elementStack.
|
|
// 5. Updates _currentElement.
|
|
}
|
|
|
|
/**
|
|
Registers block handlers for HTML end tags.
|
|
Responsible for:
|
|
- Popping the element from the stack.
|
|
- Finalizing inline text runs.
|
|
- Applying paragraph-level styles.
|
|
- Handling DTPageBreakAfter / Before attributes.
|
|
*/
|
|
- (void)_registerTagEndHandlers
|
|
{
|
|
// Pseudo-code: analogous to start handlers but for closing tags.
|
|
//
|
|
// _tagEndHandlers = @{
|
|
// @"p" : ^(NSString *tag) { ... },
|
|
// @"div" : ^(NSString *tag) { ... },
|
|
// ...
|
|
// };
|
|
//
|
|
// Each handler typically:
|
|
// 1. Pops the current element from the stack.
|
|
// 2. Calls -[DTHTMLElement interpretAttributes] to finalize.
|
|
// 3. If element has DTPageBreakAfter, inserts page break marker.
|
|
// 4. Sets _currentElement back to the parent.
|
|
}
|
|
|
|
@end
|
|
|
|
// ---------------------------------------------------------------------------
|
|
#pragma mark - DTHTMLAttributedStringBuilder implementation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@implementation DTHTMLAttributedStringBuilder
|
|
|
|
// --------------------------------------------------
|
|
# pragma mark Initialization
|
|
// --------------------------------------------------
|
|
|
|
- (instancetype)initWithHTML:(NSData *)htmlData
|
|
options:(NSDictionary *)options
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
_htmlData = [htmlData copy];
|
|
_options = [options copy] ?: @{};
|
|
|
|
// Create the parser delegate and configure it.
|
|
_parserDelegate = [[DTHTMLParserDelegate alloc] init];
|
|
|
|
// If a CSS stylesheet is provided in options, install it.
|
|
DTCSSStylesheet *sheet = _options[@"DTDefaultCSSStyleSheet"];
|
|
if (sheet) {
|
|
_cssStyleSheet = sheet;
|
|
_parserDelegate->_cssStyleSheet = sheet;
|
|
}
|
|
|
|
// Base URL for resolving <img src>, <a href>, etc.
|
|
NSURL *baseURL = _options[@"DTBaseURL"];
|
|
if (baseURL) {
|
|
_parserDelegate->_baseURL = baseURL;
|
|
}
|
|
|
|
// Default font descriptor (system font fallback).
|
|
DTCoreTextFontDescriptor *fontDesc = _options[@"DTDefaultFontDescriptor"];
|
|
if (fontDesc) {
|
|
_parserDelegate->_defaultFontDescriptor = fontDesc;
|
|
}
|
|
|
|
// Default paragraph style.
|
|
DTCoreTextParagraphStyle *paraStyle = _options[@"DTDefaultParagraphStyle"];
|
|
if (paraStyle) {
|
|
_parserDelegate->_defaultParagraphStyle = paraStyle;
|
|
}
|
|
|
|
// Register the tag handler dispatch tables.
|
|
[_parserDelegate _registerTagStartHandlers];
|
|
[_parserDelegate _registerTagEndHandlers];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (instancetype)initWithHTML:(NSData *)htmlData
|
|
cssStyleSheet:(DTCSSStylesheet *)styleSheet
|
|
options:(NSDictionary *)options
|
|
{
|
|
// Merge stylesheet into options so the designated init picks it up.
|
|
NSMutableDictionary *merged = [options mutableCopy] ?: [NSMutableDictionary dictionary];
|
|
if (styleSheet) {
|
|
merged[@"DTDefaultCSSStyleSheet"] = styleSheet;
|
|
}
|
|
return [self initWithHTML:htmlData options:merged];
|
|
}
|
|
|
|
// --------------------------------------------------
|
|
# pragma mark Build pipeline
|
|
// --------------------------------------------------
|
|
|
|
/**
|
|
Main entry point. Kicks off the SAX parser; when parsing completes the
|
|
delegate has built a DOM tree of DTHTMLElement nodes. Then -_buildString
|
|
converts that tree into the final NSAttributedString.
|
|
*/
|
|
- (void)buildString
|
|
{
|
|
// Step 1: Create a SAX parser and point it at our delegate.
|
|
DTHTMLParser *parser = [[DTHTMLParser alloc] initWithData:_htmlData];
|
|
parser.delegate = _parserDelegate;
|
|
|
|
// Step 2: Parse. This triggers the delegate callbacks below.
|
|
[parser parse];
|
|
|
|
// Step 3: Convert the DOM tree to an attributed string.
|
|
[self _buildString];
|
|
}
|
|
|
|
/**
|
|
Walks the DOM tree rooted at _parserDelegate->_rootElement and recursively
|
|
calls -[DTHTMLElement attributedString] to produce the final output.
|
|
*/
|
|
- (void)_buildString
|
|
{
|
|
DTHTMLElement *root = _parserDelegate->_rootElement;
|
|
if (!root) {
|
|
_generatedAttributedString = [[NSAttributedString alloc] initWithString:@""];
|
|
return;
|
|
}
|
|
|
|
// Recursively convert the DOM tree.
|
|
// DTHTMLElement's -attributedString walks children and concatenates.
|
|
NSAttributedString *result = [root attributedString];
|
|
|
|
// If the result is nil (empty document), produce an empty string.
|
|
if (!result) {
|
|
result = [[NSAttributedString alloc] initWithString:@""];
|
|
}
|
|
|
|
_generatedAttributedString = result;
|
|
|
|
// Store into the delegate's output for external access if needed.
|
|
_parserDelegate->_outputString = [result mutableCopy];
|
|
}
|
|
|
|
// --------------------------------------------------
|
|
# pragma mark DTHTMLParser delegate callbacks
|
|
// --------------------------------------------------
|
|
|
|
/**
|
|
Called by the SAX parser when an opening HTML tag is encountered.
|
|
|
|
@param parser The DTHTMLParser instance.
|
|
@param elementName Tag name (e.g. "p", "div", "img").
|
|
@param attributeDict Parsed attributes from the HTML tag.
|
|
@param position Character offset in the original HTML data.
|
|
*/
|
|
- (void)parser:(id)parser
|
|
didStartElement:(NSString *)elementName
|
|
attributes:(NSDictionary *)attributeDict
|
|
position:(NSUInteger)position
|
|
{
|
|
// Look up the registered handler block for this tag.
|
|
// If found, invoke it. Otherwise, fall through to default handling.
|
|
|
|
DTHTMLElement *newElement = [[DTHTMLElement alloc] initWithTagName:elementName
|
|
attributes:attributeDict];
|
|
|
|
// Set the parent to the current element on the stack.
|
|
DTHTMLElement *parent = _parserDelegate->_currentElement;
|
|
newElement.parent = parent;
|
|
[parent.children addObject:newElement];
|
|
|
|
// Push onto the element stack.
|
|
_parserDelegate->_currentElement = newElement;
|
|
|
|
// Apply inline style attribute (style="...") and any CSS rules
|
|
// matching this element.
|
|
NSDictionary *styleDict = [self _resolveStyleForElement:newElement
|
|
attributes:attributeDict];
|
|
if (styleDict) {
|
|
// Determine if this is a Latin-language book (affects font fallback).
|
|
BOOL isLatin = [_parserDelegate->_book isLatinLanguageBook];
|
|
[newElement applyStyleDictionary:styleDict isLatinLanguageBook:isLatin];
|
|
}
|
|
|
|
// Handle WeRead-specific custom CSS attributes.
|
|
[self _applyWeReadCustomAttributes:newElement fromAttributes:attributeDict];
|
|
|
|
// Handle special tags.
|
|
if ([elementName caseInsensitiveCompare:@"img"] == NSOrderedSame) {
|
|
[self _handleImageElement:newElement attributes:attributeDict];
|
|
}
|
|
else if ([elementName caseInsensitiveCompare:@"br"] == NSOrderedSame) {
|
|
[self _handleBRElement:newElement];
|
|
}
|
|
else if ([elementName caseInsensitiveCompare:@"a"] == NSOrderedSame) {
|
|
[self _handleAnchorElement:newElement attributes:attributeDict];
|
|
}
|
|
|
|
// Track last block vs. inline element for layout decisions.
|
|
if ([newElement isBlockElement]) {
|
|
_parserDelegate->_lastBlockElement = newElement;
|
|
} else {
|
|
_parserDelegate->_lastInlineElement = newElement;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Called when character data is found between tags.
|
|
|
|
@param parser The DTHTMLParser instance.
|
|
@param string The character data.
|
|
@param position Character offset in the original HTML.
|
|
*/
|
|
- (void)parser:(id)parser
|
|
foundCharacters:(NSString *)string
|
|
position:(NSUInteger)position
|
|
{
|
|
if (!string || string.length == 0) {
|
|
return;
|
|
}
|
|
|
|
// Append to the current text buffer.
|
|
// The delegate accumulates text until a closing tag flushes it.
|
|
DTHTMLElement *current = _parserDelegate->_currentElement;
|
|
if (current) {
|
|
[current appendText:string];
|
|
}
|
|
}
|
|
|
|
/**
|
|
Called when a CDATA section is encountered (e.g. inside <script> or <style>).
|
|
WeRead uses CDATA in some book content.
|
|
|
|
@param parser The DTHTMLParser instance.
|
|
@param CDATABlock Raw CDATA bytes.
|
|
*/
|
|
- (void)parser:(id)parser
|
|
foundCDATA:(NSData *)CDATABlock
|
|
{
|
|
// CDATA is typically treated as raw text content.
|
|
NSString *text = [[NSString alloc] initWithData:CDATABlock
|
|
encoding:NSUTF8StringEncoding];
|
|
if (text) {
|
|
DTHTMLElement *current = _parserDelegate->_currentElement;
|
|
if (current) {
|
|
[current appendText:text];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Called when the parser finishes parsing the entire document.
|
|
*/
|
|
- (void)parserDidEndDocument:(id)parser
|
|
{
|
|
// All elements have been opened and closed.
|
|
// The DOM tree is complete in _parserDelegate->_rootElement.
|
|
// Post-processing can happen here if needed.
|
|
}
|
|
|
|
// --------------------------------------------------
|
|
# pragma mark Style resolution
|
|
// --------------------------------------------------
|
|
|
|
/**
|
|
Resolves the effective style dictionary for an element by merging:
|
|
1. CSS stylesheet rules matching this element.
|
|
2. Inline style="" attribute.
|
|
3. Element-specific default styles.
|
|
*/
|
|
- (NSDictionary *)_resolveStyleForElement:(DTHTMLElement *)element
|
|
attributes:(NSDictionary *)attrs
|
|
{
|
|
NSMutableDictionary *resolved = [NSMutableDictionary dictionary];
|
|
|
|
// 1. Apply CSS stylesheet rules (class, id, tag selectors).
|
|
if (_cssStyleSheet) {
|
|
NSDictionary *cssRules = [_cssStyleSheet stylesForElement:element];
|
|
if (cssRules) {
|
|
[resolved addEntriesFromDictionary:cssRules];
|
|
}
|
|
}
|
|
|
|
// 2. Parse inline style attribute.
|
|
NSString *inlineStyle = attrs[@"style"];
|
|
if (inlineStyle) {
|
|
NSDictionary *inlineDict = [self _parseInlineStyle:inlineStyle];
|
|
if (inlineDict) {
|
|
[resolved addEntriesFromDictionary:inlineDict];
|
|
}
|
|
}
|
|
|
|
return resolved.count > 0 ? resolved : nil;
|
|
}
|
|
|
|
/**
|
|
Parses a CSS inline style string (e.g. "color:red;font-size:14px")
|
|
into a dictionary.
|
|
*/
|
|
- (NSDictionary *)_parseInlineStyle:(NSString *)styleString
|
|
{
|
|
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
|
|
NSArray *declarations = [styleString componentsSeparatedByString:@";"];
|
|
for (NSString *decl in declarations) {
|
|
NSArray *parts = [decl componentsSeparatedByString:@":"];
|
|
if (parts.count == 2) {
|
|
NSString *key = [parts[0] stringByTrimmingCharactersInSet:
|
|
[NSCharacterSet whitespaceCharacterSet]];
|
|
NSString *val = [parts[1] stringByTrimmingCharactersInSet:
|
|
[NSCharacterSet whitespaceCharacterSet]];
|
|
if (key.length > 0 && val.length > 0) {
|
|
dict[key] = val;
|
|
}
|
|
}
|
|
}
|
|
return dict;
|
|
}
|
|
|
|
// --------------------------------------------------
|
|
# pragma mark WeRead custom attribute handling
|
|
// --------------------------------------------------
|
|
|
|
/**
|
|
Applies WeRead-specific CSS attributes to the element.
|
|
These control page layout features unique to WeRead's reading engine.
|
|
*/
|
|
- (void)_applyWeReadCustomAttributes:(DTHTMLElement *)element
|
|
fromAttributes:(NSDictionary *)attrs
|
|
{
|
|
// wr-vertical-center-style: vertically center content within page
|
|
NSString *vCenter = attrs[@"wr-vertical-center-style"];
|
|
if (vCenter) {
|
|
element.verticalCenterStyle = vCenter;
|
|
}
|
|
|
|
// weread-page-relate: marks content as page-related
|
|
NSString *pageRelate = attrs[@"weread-page-relate"];
|
|
if (pageRelate) {
|
|
element.pageRelate = pageRelate;
|
|
}
|
|
|
|
// avoidPageBreakInside: prevent page breaks within this element
|
|
NSString *avoidBreak = attrs[@"avoidPageBreakInside"];
|
|
if ([avoidBreak boolValue] || [avoidBreak isEqualToString:@"true"]) {
|
|
element.shouldAvoidPageBreakInside = YES;
|
|
}
|
|
|
|
// DTPageBreakAfter: force page break after this element
|
|
NSString *breakAfter = attrs[@"DTPageBreakAfter"];
|
|
if (breakAfter) {
|
|
element.pageBreakAfter = YES;
|
|
}
|
|
|
|
// DTPageBreakBefore: force page break before this element
|
|
NSString *breakBefore = attrs[@"DTPageBreakBefore"];
|
|
if (breakBefore) {
|
|
element.pageBreakBefore = YES;
|
|
}
|
|
|
|
// DTPageBackgroundColor: per-page background color
|
|
NSString *bgColor = attrs[@"DTPageBackgroundColor"];
|
|
if (bgColor) {
|
|
element.pageBackgroundColor = [self _colorFromCSSValue:bgColor];
|
|
}
|
|
|
|
// DTPageBackgroundImage: per-page background image
|
|
NSString *bgImage = attrs[@"DTPageBackgroundImage"];
|
|
if (bgImage) {
|
|
element.pageBackgroundImage = bgImage;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Converts a CSS color value string to UIColor.
|
|
*/
|
|
- (UIColor *)_colorFromCSSValue:(NSString *)cssValue
|
|
{
|
|
// Simplified: real implementation handles hex (#RRGGBB), rgb(), named colors.
|
|
if ([cssValue hasPrefix:@"#"]) {
|
|
NSString *hex = [cssValue substringFromIndex:1];
|
|
unsigned int rgb = 0;
|
|
[[NSScanner scannerWithString:hex] scanHexInt:&rgb];
|
|
return [UIColor colorWithRed:((rgb >> 16) & 0xFF) / 255.0
|
|
green:((rgb >> 8) & 0xFF) / 255.0
|
|
blue:((rgb >> 0) & 0xFF) / 255.0
|
|
alpha:1.0];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
// --------------------------------------------------
|
|
# pragma mark Special element handlers
|
|
// --------------------------------------------------
|
|
|
|
- (void)_handleImageElement:(DTHTMLElement *)element
|
|
attributes:(NSDictionary *)attrs
|
|
{
|
|
// Resolve image source relative to base URL.
|
|
NSString *src = attrs[@"src"];
|
|
if (!src) return;
|
|
|
|
NSURL *baseURL = _parserDelegate->_baseURL;
|
|
NSURL *imageURL = [NSURL URLWithString:src relativeToURL:baseURL];
|
|
|
|
element.imageURL = imageURL;
|
|
|
|
// Store for lazy loading pipeline.
|
|
_parserDelegate->_currentImageSrc = src;
|
|
}
|
|
|
|
- (void)_handleBRElement:(DTHTMLElement *)element
|
|
{
|
|
// <br> inserts a newline / line break.
|
|
element.tagName = @"br";
|
|
element.isLineBreak = YES;
|
|
}
|
|
|
|
- (void)_handleAnchorElement:(DTHTMLElement *)element
|
|
attributes:(NSDictionary *)attrs
|
|
{
|
|
// <a href="..."> creates a hyperlink.
|
|
NSString *href = attrs[@"href"];
|
|
if (href) {
|
|
NSURL *baseURL = _parserDelegate->_baseURL;
|
|
element.linkURL = [NSURL URLWithString:href relativeToURL:baseURL];
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------
|
|
# pragma mark Properties
|
|
// --------------------------------------------------
|
|
|
|
- (NSData *)htmlData
|
|
{
|
|
return _htmlData;
|
|
}
|
|
|
|
- (DTCSSStylesheet *)cssStyleSheet
|
|
{
|
|
return _cssStyleSheet;
|
|
}
|
|
|
|
- (NSDictionary *)options
|
|
{
|
|
return _options;
|
|
}
|
|
|
|
- (NSAttributedString *)generatedAttributedString
|
|
{
|
|
return _generatedAttributedString;
|
|
}
|
|
|
|
- (WRBook *)book
|
|
{
|
|
return _parserDelegate->_book;
|
|
}
|
|
|
|
- (void)setBook:(WRBook *)book
|
|
{
|
|
_parserDelegate->_book = book;
|
|
_book = book;
|
|
}
|
|
|
|
- (WRChapter *)chapter
|
|
{
|
|
return _parserDelegate->_chapter;
|
|
}
|
|
|
|
- (void)setChapter:(WRChapter *)chapter
|
|
{
|
|
_parserDelegate->_chapter = chapter;
|
|
_chapter = chapter;
|
|
}
|
|
|
|
@end
|