1/* 2 * Copyright (C) 2011 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#import "config.h" 27#import "HTMLConverter.h" 28 29#import "ArchiveResource.h" 30#import "ColorMac.h" 31#import "Document.h" 32#import "DocumentLoader.h" 33#import "DOMDocumentInternal.h" 34#import "DOMElementInternal.h" 35#import "DOMHTMLTableCellElement.h" 36#import "DOMPrivate.h" 37#import "DOMRangeInternal.h" 38#import "Element.h" 39#import "Frame.h" 40#import "HTMLNames.h" 41#import "HTMLParserIdioms.h" 42#import "LoaderNSURLExtras.h" 43#import "RenderImage.h" 44#import "TextIterator.h" 45#import <wtf/ASCIICType.h> 46 47using namespace WebCore; 48using namespace HTMLNames; 49 50static NSFileWrapper *fileWrapperForURL(DocumentLoader *, NSURL *); 51static NSFileWrapper *fileWrapperForElement(Element*); 52 53#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 54 55// Additional control Unicode characters 56const unichar WebNextLineCharacter = 0x0085; 57 58@interface NSTextList (WebCoreNSTextListDetails) 59+ (NSDictionary *)_standardMarkerAttributesForAttributes:(NSDictionary *)attrs; 60@end 61 62@interface NSTextAttachment (NSIgnoreOrientation) 63- (void)setIgnoresOrientation:(BOOL)flag; 64- (BOOL)ignoresOrientation; 65@end 66 67@interface NSURL (WebCoreNSURLDetails) 68// FIXME: What is the reason to use this Foundation method, and not +[NSURL URLWithString:relativeToURL:]? 69+ (NSURL *)_web_URLWithString:(NSString *)string relativeToURL:(NSURL *)baseURL; 70@end 71 72@interface WebHTMLConverter(WebHTMLConverterInternal) 73 74- (NSString *)_stringForNode:(DOMNode *)node property:(NSString *)key; 75- (NSColor *)_colorForNode:(DOMNode *)node property:(NSString *)key; 76- (BOOL)_getFloat:(CGFloat *)val forNode:(DOMNode *)node property:(NSString *)key; 77- (void)_traverseNode:(DOMNode *)node depth:(NSInteger)depth embedded:(BOOL)embedded; 78- (void)_traverseFooterNode:(DOMNode *)node depth:(NSInteger)depth; 79 80@end 81 82// Returns the font to be used if the NSFontAttributeName doesn't exist 83static NSFont *WebDefaultFont() 84{ 85 static NSFont *defaultFont = nil; 86 87 if (defaultFont) 88 return defaultFont; 89 90 NSFont *font = [NSFont fontWithName:@"Helvetica" size:12]; 91 if (!font) 92 font = [NSFont systemFontOfSize:12]; 93 94 defaultFont = [font retain]; 95 96 return defaultFont; 97} 98 99#endif 100 101@implementation WebHTMLConverter 102 103#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 104 105static NSFont *_fontForNameAndSize(NSString *fontName, CGFloat size, NSMutableDictionary *cache) 106{ 107 NSFontManager *fontManager = [NSFontManager sharedFontManager]; 108 NSFont *font = [cache objectForKey:fontName]; 109 110 if (font) { 111 font = [fontManager convertFont:font toSize:size]; 112 return font; 113 } 114 font = [fontManager fontWithFamily:fontName traits:0 weight:0 size:size]; 115 if (!font) { 116 NSArray *availableFamilyNames = [fontManager availableFontFamilies]; 117 NSRange dividingRange, dividingSpaceRange = [fontName rangeOfString:@" " options:NSBackwardsSearch], dividingDashRange = [fontName rangeOfString:@"-" options:NSBackwardsSearch]; 118 dividingRange = (0 < dividingSpaceRange.length && 0 < dividingDashRange.length) ? (dividingSpaceRange.location > dividingDashRange.location ? dividingSpaceRange : dividingDashRange) : (0 < dividingSpaceRange.length ? dividingSpaceRange : dividingDashRange); 119 while (0 < dividingRange.length) { 120 NSString *familyName = [fontName substringToIndex:dividingRange.location]; 121 if ([availableFamilyNames containsObject:familyName]) { 122 NSArray *familyMemberArray; 123 NSString *faceName = [fontName substringFromIndex:(dividingRange.location + dividingRange.length)]; 124 NSArray *familyMemberArrays = [fontManager availableMembersOfFontFamily:familyName]; 125 NSEnumerator *familyMemberArraysEnum = [familyMemberArrays objectEnumerator]; 126 while ((familyMemberArray = [familyMemberArraysEnum nextObject])) { 127 NSString *familyMemberFaceName = [familyMemberArray objectAtIndex:1]; 128 if ([familyMemberFaceName compare:faceName options:NSCaseInsensitiveSearch] == NSOrderedSame) { 129 NSFontTraitMask traits = [[familyMemberArray objectAtIndex:3] integerValue]; 130 NSInteger weight = [[familyMemberArray objectAtIndex:2] integerValue]; 131 font = [fontManager fontWithFamily:familyName traits:traits weight:weight size:size]; 132 break; 133 } 134 } 135 if (!font) { 136 if (0 < [familyMemberArrays count]) { 137 NSArray *familyMemberArray = [familyMemberArrays objectAtIndex:0]; 138 NSFontTraitMask traits = [[familyMemberArray objectAtIndex:3] integerValue]; 139 NSInteger weight = [[familyMemberArray objectAtIndex:2] integerValue]; 140 font = [fontManager fontWithFamily:familyName traits:traits weight:weight size:size]; 141 } 142 } 143 break; 144 } else { 145 dividingSpaceRange = [familyName rangeOfString:@" " options:NSBackwardsSearch]; 146 dividingDashRange = [familyName rangeOfString:@"-" options:NSBackwardsSearch]; 147 dividingRange = (0 < dividingSpaceRange.length && 0 < dividingDashRange.length) ? (dividingSpaceRange.location > dividingDashRange.location ? dividingSpaceRange : dividingDashRange) : (0 < dividingSpaceRange.length ? dividingSpaceRange : dividingDashRange); 148 } 149 } 150 } 151 if (!font) font = [NSFont fontWithName:@"Times" size:size]; 152 if (!font) font = [NSFont userFontOfSize:size]; 153 if (!font) font = [fontManager convertFont:WebDefaultFont() toSize:size]; 154 if (!font) font = WebDefaultFont(); 155 [cache setObject:font forKey:fontName]; 156 return font; 157} 158 159+ (NSParagraphStyle *)defaultParagraphStyle 160{ 161 static NSMutableParagraphStyle *defaultParagraphStyle = nil; 162 if (!defaultParagraphStyle) { 163 defaultParagraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; 164 [defaultParagraphStyle setDefaultTabInterval:36]; 165 [defaultParagraphStyle setTabStops:[NSArray array]]; 166 } 167 return defaultParagraphStyle; 168} 169 170- (NSArray *)_childrenForNode:(DOMNode *)node 171{ 172 NSMutableArray *array = [NSMutableArray array]; 173 DOMNode *child = [node firstChild]; 174 while (child) { 175 [array addObject:child]; 176 child = [child nextSibling]; 177 } 178 return array; 179} 180 181- (DOMCSSStyleDeclaration *)_computedStyleForElement:(DOMElement *)element 182{ 183 DOMDocument *document = [element ownerDocument]; 184 DOMCSSStyleDeclaration *result = nil; 185 result = [_computedStylesForElements objectForKey:element]; 186 if (result) { 187 if ([[NSNull null] isEqual:result]) result = nil; 188 } else { 189 result = [document getComputedStyle:element pseudoElement:@""] ; 190 [_computedStylesForElements setObject:(result ? (id)result : (id)[NSNull null]) forKey:element]; 191 } 192 return result; 193} 194 195- (DOMCSSStyleDeclaration *)_specifiedStyleForElement:(DOMElement *)element 196{ 197 DOMCSSStyleDeclaration *result = [_specifiedStylesForElements objectForKey:element]; 198 if (result) { 199 if ([[NSNull null] isEqual:result]) result = nil; 200 } else { 201 result = [element style]; 202 [_specifiedStylesForElements setObject:(result ? (id)result : (id)[NSNull null]) forKey:element]; 203 } 204 return result; 205} 206 207- (NSString *)_computedStringForNode:(DOMNode *)node property:(NSString *)key 208{ 209 NSString *result = nil; 210 BOOL inherit = YES; 211 DOMElement *element = (DOMElement *)node; 212 if (element && [element nodeType] == DOM_ELEMENT_NODE) { 213 DOMCSSStyleDeclaration *computedStyle, *specifiedStyle; 214 inherit = NO; 215 if (!result && (computedStyle = [self _computedStyleForElement:element])) { 216 DOMCSSPrimitiveValue *computedValue = (DOMCSSPrimitiveValue *)[computedStyle getPropertyCSSValue:key]; 217 if (computedValue) { 218 unsigned short valueType = [computedValue cssValueType]; 219 if (valueType == DOM_CSS_PRIMITIVE_VALUE) { 220 unsigned short primitiveType = [computedValue primitiveType]; 221 if (primitiveType == DOM_CSS_STRING || primitiveType == DOM_CSS_URI || primitiveType == DOM_CSS_IDENT || primitiveType == DOM_CSS_ATTR) { 222 result = [computedValue getStringValue]; 223 if (result && [result length] == 0) result = nil; 224 } 225 } else if (valueType == DOM_CSS_VALUE_LIST) { 226 result = [computedStyle getPropertyValue:key]; 227 } 228 } 229 } 230 if (!result && (specifiedStyle = [self _specifiedStyleForElement:element])) { 231 DOMCSSPrimitiveValue *specifiedValue = (DOMCSSPrimitiveValue *)[specifiedStyle getPropertyCSSValue:key]; 232 if (specifiedValue) { 233 unsigned short valueType = [specifiedValue cssValueType]; 234 if (valueType == DOM_CSS_PRIMITIVE_VALUE) { 235 unsigned short primitiveType = [specifiedValue primitiveType]; 236 if (primitiveType == DOM_CSS_STRING || primitiveType == DOM_CSS_URI || primitiveType == DOM_CSS_IDENT || primitiveType == DOM_CSS_ATTR) { 237 result = [specifiedValue getStringValue]; 238 if (result && [result length] == 0) result = nil; 239 // ??? hack alert 240 if (!result) { 241 result = [specifiedStyle getPropertyValue:key]; 242 } 243 } 244 } else if (valueType == DOM_CSS_INHERIT) { 245 inherit = YES; 246 } else if (valueType == DOM_CSS_VALUE_LIST) { 247 result = [specifiedStyle getPropertyValue:key]; 248 } 249 } 250 } 251 if (!result) { 252 Element* coreElement = core(element); 253 if ([@"display" isEqualToString:key]) { 254 if (coreElement->hasTagName(headTag) || coreElement->hasTagName(scriptTag) || coreElement->hasTagName(appletTag) || coreElement->hasTagName(noframesTag)) 255 result = @"none"; 256 else if (coreElement->hasTagName(addressTag) || coreElement->hasTagName(blockquoteTag) || coreElement->hasTagName(bodyTag) || coreElement->hasTagName(centerTag) 257 || coreElement->hasTagName(ddTag) || coreElement->hasTagName(dirTag) || coreElement->hasTagName(divTag) || coreElement->hasTagName(dlTag) 258 || coreElement->hasTagName(dtTag) || coreElement->hasTagName(fieldsetTag) || coreElement->hasTagName(formTag) || coreElement->hasTagName(frameTag) 259 || coreElement->hasTagName(framesetTag) || coreElement->hasTagName(hrTag) || coreElement->hasTagName(htmlTag) || coreElement->hasTagName(h1Tag) 260 || coreElement->hasTagName(h2Tag) || coreElement->hasTagName(h3Tag) || coreElement->hasTagName(h4Tag) || coreElement->hasTagName(h5Tag) 261 || coreElement->hasTagName(h6Tag) || coreElement->hasTagName(iframeTag) || coreElement->hasTagName(menuTag) || coreElement->hasTagName(noscriptTag) 262 || coreElement->hasTagName(olTag) || coreElement->hasTagName(pTag) || coreElement->hasTagName(preTag) || coreElement->hasTagName(ulTag)) 263 result = @"block"; 264 else if (coreElement->hasTagName(liTag)) 265 result = @"list-item"; 266 else if (coreElement->hasTagName(tableTag)) 267 result = @"table"; 268 else if (coreElement->hasTagName(trTag)) 269 result = @"table-row"; 270 else if (coreElement->hasTagName(thTag) || coreElement->hasTagName(tdTag)) 271 result = @"table-cell"; 272 else if (coreElement->hasTagName(theadTag)) 273 result = @"table-header-group"; 274 else if (coreElement->hasTagName(tbodyTag)) 275 result = @"table-row-group"; 276 else if (coreElement->hasTagName(tfootTag)) 277 result = @"table-footer-group"; 278 else if (coreElement->hasTagName(colTag)) 279 result = @"table-column"; 280 else if (coreElement->hasTagName(colgroupTag)) 281 result = @"table-column-group"; 282 else if (coreElement->hasTagName(captionTag)) 283 result = @"table-caption"; 284 } else if ([@"white-space" isEqualToString:key]) { 285 if (coreElement->hasTagName(preTag)) 286 result = @"pre"; 287 else 288 inherit = YES; 289 } else if ([@"font-style" isEqualToString:key]) { 290 if (coreElement->hasTagName(iTag) || coreElement->hasTagName(citeTag) || coreElement->hasTagName(emTag) || coreElement->hasTagName(varTag) || coreElement->hasTagName(addressTag)) 291 result = @"italic"; 292 else 293 inherit = YES; 294 } else if ([@"font-weight" isEqualToString:key]) { 295 if (coreElement->hasTagName(bTag) || coreElement->hasTagName(strongTag) || coreElement->hasTagName(thTag)) 296 result = @"bolder"; 297 else 298 inherit = YES; 299 } else if ([@"text-decoration" isEqualToString:key]) { 300 if (coreElement->hasTagName(uTag) || coreElement->hasTagName(insTag)) 301 result = @"underline"; 302 else if (coreElement->hasTagName(sTag) || coreElement->hasTagName(strikeTag) || coreElement->hasTagName(delTag)) 303 result = @"line-through"; 304 else 305 inherit = YES; // ??? this is not strictly correct 306 } else if ([@"text-align" isEqualToString:key]) { 307 if (coreElement->hasTagName(centerTag) || coreElement->hasTagName(captionTag) || coreElement->hasTagName(thTag)) 308 result = @"center"; 309 else 310 inherit = YES; 311 } else if ([@"vertical-align" isEqualToString:key]) { 312 if (coreElement->hasTagName(supTag)) 313 result = @"super"; 314 else if (coreElement->hasTagName(subTag)) 315 result = @"sub"; 316 else if (coreElement->hasTagName(theadTag) || coreElement->hasTagName(tbodyTag) || coreElement->hasTagName(tfootTag)) 317 result = @"middle"; 318 else if (coreElement->hasTagName(trTag) || coreElement->hasTagName(thTag) || coreElement->hasTagName(tdTag)) 319 inherit = YES; 320 } else if ([@"font-family" isEqualToString:key] || [@"font-variant" isEqualToString:key] || [@"font-effect" isEqualToString:key] 321 || [@"text-transform" isEqualToString:key] || [@"text-shadow" isEqualToString:key] || [@"visibility" isEqualToString:key] 322 || [@"border-collapse" isEqualToString:key] || [@"empty-cells" isEqualToString:key] || [@"word-spacing" isEqualToString:key] 323 || [@"list-style-type" isEqualToString:key] || [@"direction" isEqualToString:key]) { 324 inherit = YES; 325 } 326 } 327 } 328 if (!result && inherit) { 329 DOMNode *parentNode = [node parentNode]; 330 if (parentNode) result = [self _stringForNode:parentNode property:key]; 331 } 332 return result ? [result lowercaseString] : nil; 333} 334 335- (NSString *)_stringForNode:(DOMNode *)node property:(NSString *)key 336{ 337 NSString *result = nil; 338 NSMutableDictionary *attributeDictionary = [_stringsForNodes objectForKey:node]; 339 if (!attributeDictionary) { 340 attributeDictionary = [[NSMutableDictionary alloc] init]; 341 [_stringsForNodes setObject:attributeDictionary forKey:node]; 342 [attributeDictionary release]; 343 } 344 result = [attributeDictionary objectForKey:key]; 345 if (result) { 346 if ([@"" isEqualToString:result]) result = nil; 347 } else { 348 result = [self _computedStringForNode:node property:key]; 349 [attributeDictionary setObject:(result ? result : @"") forKey:key]; 350 } 351 return result; 352} 353 354static inline BOOL _getFloat(DOMCSSPrimitiveValue *primitiveValue, CGFloat *val) 355{ 356 if (!val) 357 return NO; 358 switch ([primitiveValue primitiveType]) { 359 case DOM_CSS_PX: 360 *val = [primitiveValue getFloatValue:DOM_CSS_PX]; 361 return YES; 362 case DOM_CSS_PT: 363 *val = 4 * [primitiveValue getFloatValue:DOM_CSS_PT] / 3; 364 return YES; 365 case DOM_CSS_PC: 366 *val = 16 * [primitiveValue getFloatValue:DOM_CSS_PC]; 367 return YES; 368 case DOM_CSS_CM: 369 *val = 96 * [primitiveValue getFloatValue:DOM_CSS_CM] / (CGFloat)2.54; 370 return YES; 371 case DOM_CSS_MM: 372 *val = 96 * [primitiveValue getFloatValue:DOM_CSS_MM] / (CGFloat)25.4; 373 return YES; 374 case DOM_CSS_IN: 375 *val = 96 * [primitiveValue getFloatValue:DOM_CSS_IN]; 376 return YES; 377 default: 378 return NO; 379 } 380} 381 382- (BOOL)_getComputedFloat:(CGFloat *)val forNode:(DOMNode *)node property:(NSString *)key 383{ 384 BOOL result = NO, inherit = YES; 385 CGFloat floatVal = 0; 386 DOMElement *element = (DOMElement *)node; 387 if (element && [element nodeType] == DOM_ELEMENT_NODE) { 388 DOMCSSStyleDeclaration *computedStyle, *specifiedStyle; 389 inherit = NO; 390 if (!result && (computedStyle = [self _computedStyleForElement:element])) { 391 DOMCSSPrimitiveValue *computedValue = (DOMCSSPrimitiveValue *)[computedStyle getPropertyCSSValue:key]; 392 if (computedValue && [computedValue cssValueType] == DOM_CSS_PRIMITIVE_VALUE) { 393 result = _getFloat(computedValue, &floatVal); 394 } 395 } 396 if (!result && (specifiedStyle = [self _specifiedStyleForElement:element])) { 397 DOMCSSPrimitiveValue *specifiedValue = (DOMCSSPrimitiveValue *)[specifiedStyle getPropertyCSSValue:key]; 398 if (specifiedValue) { 399 unsigned short valueType = [specifiedValue cssValueType]; 400 if (valueType == DOM_CSS_PRIMITIVE_VALUE) { 401 result = _getFloat(specifiedValue, &floatVal); 402 } else if (valueType == DOM_CSS_INHERIT) { 403 inherit = YES; 404 } 405 } 406 } 407 if (!result) { 408 if ([@"text-indent" isEqualToString:key] || [@"letter-spacing" isEqualToString:key] || [@"word-spacing" isEqualToString:key] 409 || [@"line-height" isEqualToString:key] || [@"widows" isEqualToString:key] || [@"orphans" isEqualToString:key]) 410 inherit = YES; 411 } 412 } 413 if (!result && inherit) { 414 DOMNode *parentNode = [node parentNode]; 415 if (parentNode) result = [self _getFloat:&floatVal forNode:parentNode property:key]; 416 } 417 if (result && val) 418 *val = floatVal; 419 return result; 420} 421 422- (BOOL)_getFloat:(CGFloat *)val forNode:(DOMNode *)node property:(NSString *)key 423{ 424 BOOL result = NO; 425 CGFloat floatVal = 0; 426 NSNumber *floatNumber; 427 NSMutableDictionary *attributeDictionary = [_floatsForNodes objectForKey:node]; 428 if (!attributeDictionary) { 429 attributeDictionary = [[NSMutableDictionary alloc] init]; 430 [_floatsForNodes setObject:attributeDictionary forKey:node]; 431 [attributeDictionary release]; 432 } 433 floatNumber = [attributeDictionary objectForKey:key]; 434 if (floatNumber) { 435 if (![[NSNull null] isEqual:floatNumber]) { 436 result = YES; 437 floatVal = [floatNumber floatValue]; 438 } 439 } else { 440 result = [self _getComputedFloat:&floatVal forNode:node property:key]; 441 [attributeDictionary setObject:(result ? (id)[NSNumber numberWithDouble:floatVal] : (id)[NSNull null]) forKey:key]; 442 } 443 if (result && val) *val = floatVal; 444 return result; 445} 446 447static inline NSColor *_colorForRGBColor(DOMRGBColor *domRGBColor, BOOL ignoreBlack) 448{ 449 NSColor *color = [domRGBColor _color]; 450 NSColorSpace *colorSpace = [color colorSpace]; 451 const CGFloat ColorEpsilon = 1 / (2 * (CGFloat)255.0); 452 453 if (color) { 454 if ([colorSpace isEqual:[NSColorSpace genericGrayColorSpace]] || [colorSpace isEqual:[NSColorSpace deviceGrayColorSpace]]) { 455 CGFloat white, alpha; 456 [color getWhite:&white alpha:&alpha]; 457 if (white < ColorEpsilon && (ignoreBlack || alpha < ColorEpsilon)) color = nil; 458 } else { 459 NSColor *rgbColor = nil; 460 if ([colorSpace isEqual:[NSColorSpace genericRGBColorSpace]] || [colorSpace isEqual:[NSColorSpace deviceRGBColorSpace]]) rgbColor = color; 461 if (!rgbColor) rgbColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace]; 462 if (rgbColor) { 463 CGFloat red, green, blue, alpha; 464 [rgbColor getRed:&red green:&green blue:&blue alpha:&alpha]; 465 if (red < ColorEpsilon && green < ColorEpsilon && blue < ColorEpsilon && (ignoreBlack || alpha < ColorEpsilon)) color = nil; 466 } 467 } 468 } 469 return color; 470} 471 472static inline NSShadow *_shadowForShadowStyle(NSString *shadowStyle) 473{ 474 NSShadow *shadow = nil; 475 NSUInteger shadowStyleLength = [shadowStyle length]; 476 NSRange openParenRange = [shadowStyle rangeOfString:@"("], closeParenRange = [shadowStyle rangeOfString:@")"], firstRange = NSMakeRange(NSNotFound, 0), secondRange = NSMakeRange(NSNotFound, 0), thirdRange = NSMakeRange(NSNotFound, 0), spaceRange; 477 if (openParenRange.length > 0 && closeParenRange.length > 0 && NSMaxRange(openParenRange) < closeParenRange.location) { 478 NSArray *components = [[shadowStyle substringWithRange:NSMakeRange(NSMaxRange(openParenRange), closeParenRange.location - NSMaxRange(openParenRange))] componentsSeparatedByString:@","]; 479 if ([components count] >= 3) { 480 CGFloat red = [[components objectAtIndex:0] floatValue] / 255, green = [[components objectAtIndex:1] floatValue] / 255, blue = [[components objectAtIndex:2] floatValue] / 255, alpha = ([components count] >= 4) ? [[components objectAtIndex:3] floatValue] / 255 : 1; 481 NSColor *shadowColor = [NSColor colorWithCalibratedRed:red green:green blue:blue alpha:alpha]; 482 NSSize shadowOffset; 483 CGFloat shadowBlurRadius; 484 firstRange = [shadowStyle rangeOfString:@"px"]; 485 if (firstRange.length > 0 && NSMaxRange(firstRange) < shadowStyleLength) secondRange = [shadowStyle rangeOfString:@"px" options:0 range:NSMakeRange(NSMaxRange(firstRange), shadowStyleLength - NSMaxRange(firstRange))]; 486 if (secondRange.length > 0 && NSMaxRange(secondRange) < shadowStyleLength) thirdRange = [shadowStyle rangeOfString:@"px" options:0 range:NSMakeRange(NSMaxRange(secondRange), shadowStyleLength - NSMaxRange(secondRange))]; 487 if (firstRange.location > 0 && firstRange.length > 0 && secondRange.length > 0 && thirdRange.length > 0) { 488 spaceRange = [shadowStyle rangeOfString:@" " options:NSBackwardsSearch range:NSMakeRange(0, firstRange.location)]; 489 if (spaceRange.length == 0) spaceRange = NSMakeRange(0, 0); 490 shadowOffset.width = [[shadowStyle substringWithRange:NSMakeRange(NSMaxRange(spaceRange), firstRange.location - NSMaxRange(spaceRange))] floatValue]; 491 spaceRange = [shadowStyle rangeOfString:@" " options:NSBackwardsSearch range:NSMakeRange(0, secondRange.location)]; 492 if (spaceRange.length == 0) spaceRange = NSMakeRange(0, 0); 493 shadowOffset.height = -[[shadowStyle substringWithRange:NSMakeRange(NSMaxRange(spaceRange), secondRange.location - NSMaxRange(spaceRange))] floatValue]; 494 spaceRange = [shadowStyle rangeOfString:@" " options:NSBackwardsSearch range:NSMakeRange(0, thirdRange.location)]; 495 if (spaceRange.length == 0) spaceRange = NSMakeRange(0, 0); 496 shadowBlurRadius = [[shadowStyle substringWithRange:NSMakeRange(NSMaxRange(spaceRange), thirdRange.location - NSMaxRange(spaceRange))] floatValue]; 497 shadow = [[[NSShadow alloc] init] autorelease]; 498 [shadow setShadowColor:shadowColor]; 499 [shadow setShadowOffset:shadowOffset]; 500 [shadow setShadowBlurRadius:shadowBlurRadius]; 501 } 502 } 503 } 504 return shadow; 505} 506 507- (BOOL)_elementIsBlockLevel:(DOMElement *)element 508{ 509 BOOL isBlockLevel = NO; 510 NSNumber *val = nil; 511 val = [_elementIsBlockLevel objectForKey:element]; 512 if (val) { 513 isBlockLevel = [val boolValue]; 514 } else { 515 NSString *displayVal = [self _stringForNode:element property:@"display"], *floatVal = [self _stringForNode:element property:@"float"]; 516 if (floatVal && ([@"left" isEqualToString:floatVal] || [@"right" isEqualToString:floatVal])) { 517 isBlockLevel = YES; 518 } else if (displayVal) { 519 isBlockLevel = ([@"block" isEqualToString:displayVal] || [@"list-item" isEqualToString:displayVal] || [displayVal hasPrefix:@"table"]); 520 } 521 [_elementIsBlockLevel setObject:[NSNumber numberWithBool:isBlockLevel] forKey:element]; 522 } 523 return isBlockLevel; 524} 525 526- (BOOL)_elementHasOwnBackgroundColor:(DOMElement *)element 527{ 528 // In the text system, text blocks (table elements) and documents (body elements) have their own background colors, which should not be inherited 529 if ([self _elementIsBlockLevel:element]) { 530 Element* coreElement = core(element); 531 NSString *displayVal = [self _stringForNode:element property:@"display"]; 532 if (coreElement->hasTagName(htmlTag) || coreElement->hasTagName(bodyTag) || [displayVal hasPrefix:@"table"]) 533 return YES; 534 } 535 return NO; 536} 537 538- (DOMElement *)_blockLevelElementForNode:(DOMNode *)node 539{ 540 DOMElement *element = (DOMElement *)node; 541 while (element && [element nodeType] != DOM_ELEMENT_NODE) 542 element = (DOMElement *)[element parentNode]; 543 if (element && ![self _elementIsBlockLevel:element]) 544 element = [self _blockLevelElementForNode:[element parentNode]]; 545 return element; 546} 547 548- (NSColor *)_computedColorForNode:(DOMNode *)node property:(NSString *)key 549{ 550 NSColor *result = nil; 551 BOOL inherit = YES, haveResult = NO, isColor = [@"color" isEqualToString:key], isBackgroundColor = [@"background-color" isEqualToString:key]; 552 DOMElement *element = (DOMElement *)node; 553 if (element && [element nodeType] == DOM_ELEMENT_NODE) { 554 DOMCSSStyleDeclaration *computedStyle, *specifiedStyle; 555 inherit = NO; 556 if (!haveResult && (computedStyle = [self _computedStyleForElement:element])) { 557 DOMCSSPrimitiveValue *computedValue = (DOMCSSPrimitiveValue *)[computedStyle getPropertyCSSValue:key]; 558 if (computedValue && [computedValue cssValueType] == DOM_CSS_PRIMITIVE_VALUE && [computedValue primitiveType] == DOM_CSS_RGBCOLOR) { 559 result = _colorForRGBColor([computedValue getRGBColorValue], isColor); 560 haveResult = YES; 561 } 562 } 563 if (!haveResult && (specifiedStyle = [self _specifiedStyleForElement:element])) { 564 DOMCSSPrimitiveValue *specifiedValue = (DOMCSSPrimitiveValue *)[specifiedStyle getPropertyCSSValue:key]; 565 if (specifiedValue) { 566 unsigned short valueType = [specifiedValue cssValueType]; 567 if (valueType == DOM_CSS_PRIMITIVE_VALUE && [specifiedValue primitiveType] == DOM_CSS_RGBCOLOR) { 568 result = _colorForRGBColor([specifiedValue getRGBColorValue], isColor); 569 haveResult = YES; 570 } else if (valueType == DOM_CSS_INHERIT) { 571 inherit = YES; 572 } 573 } 574 } 575 if (!result) { 576 if ((isColor && !haveResult) || (isBackgroundColor && ![self _elementHasOwnBackgroundColor:element])) inherit = YES; 577 } 578 } 579 if (!result && inherit) { 580 DOMNode *parentNode = [node parentNode]; 581 if (parentNode && !(isBackgroundColor && [parentNode nodeType] == DOM_ELEMENT_NODE && [self _elementHasOwnBackgroundColor:(DOMElement *)parentNode])) { 582 result = [self _colorForNode:parentNode property:key]; 583 } 584 } 585 return result; 586} 587 588- (NSColor *)_colorForNode:(DOMNode *)node property:(NSString *)key 589{ 590 NSColor *result = nil; 591 NSMutableDictionary *attributeDictionary = [_colorsForNodes objectForKey:node]; 592 if (!attributeDictionary) { 593 attributeDictionary = [[NSMutableDictionary alloc] init]; 594 [_colorsForNodes setObject:attributeDictionary forKey:node]; 595 [attributeDictionary release]; 596 } 597 result = [attributeDictionary objectForKey:key]; 598 if (result) { 599 if ([[NSColor clearColor] isEqual:result]) result = nil; 600 } else { 601 result = [self _computedColorForNode:node property:key]; 602 [attributeDictionary setObject:(result ? result : [NSColor clearColor]) forKey:key]; 603 } 604 return result; 605} 606 607- (NSDictionary *)_computedAttributesForElement:(DOMElement *)element 608{ 609 DOMElement *blockElement = [self _blockLevelElementForNode:element]; 610 NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; 611 NSFontManager *fontManager = [NSFontManager sharedFontManager]; 612 NSString *fontEffect = [self _stringForNode:element property:@"font-effect"], *textDecoration = [self _stringForNode:element property:@"text-decoration"], *verticalAlign = [self _stringForNode:element property:@"vertical-align"], *textShadow = [self _stringForNode:element property:@"text-shadow"]; 613 CGFloat fontSize = 0, baselineOffset = 0, kerning = 0; 614 NSFont *font = nil, *actualFont = [element _font]; 615 NSColor *foregroundColor = [self _colorForNode:element property:@"color"], *backgroundColor = [self _colorForNode:element property:@"background-color"]; 616 617 if (![self _getFloat:&fontSize forNode:element property:@"font-size"] || fontSize <= 0.0) fontSize = _defaultFontSize; 618 fontSize *= _textSizeMultiplier; 619 if (fontSize < _minimumFontSize) fontSize = _minimumFontSize; 620 if (fabs(floor(2.0 * fontSize + 0.5) / 2.0 - fontSize) < 0.05) { 621 fontSize = (CGFloat)floor(2.0 * fontSize + 0.5) / 2; 622 } else if (fabs(floor(10.0 * fontSize + 0.5) / 10.0 - fontSize) < 0.005) { 623 fontSize = (CGFloat)floor(10.0 * fontSize + 0.5) / 10; 624 } 625 if (fontSize <= 0.0) fontSize = 12; 626 627 if (actualFont) font = [fontManager convertFont:actualFont toSize:fontSize]; 628 if (!font) { 629 NSString *fontName = [[self _stringForNode:element property:@"font-family"] capitalizedString], *fontStyle = [self _stringForNode:element property:@"font-style"], *fontWeight = [self _stringForNode:element property:@"font-weight"], *fontVariant = [self _stringForNode:element property:@"font-variant"]; 630 631 if (!fontName) fontName = _standardFontFamily; 632 if (fontName) font = _fontForNameAndSize(fontName, fontSize, _fontCache); 633 if (!font) font = [NSFont fontWithName:@"Times" size:fontSize]; 634 if ([@"italic" isEqualToString:fontStyle] || [@"oblique" isEqualToString:fontStyle]) { 635 NSFont *originalFont = font; 636 font = [fontManager convertFont:font toHaveTrait:NSItalicFontMask]; 637 if (!font) font = originalFont; 638 } 639 if ([fontWeight hasPrefix:@"bold"] || [fontWeight integerValue] >= 700) { 640 // ??? handle weight properly using NSFontManager 641 NSFont *originalFont = font; 642 font = [fontManager convertFont:font toHaveTrait:NSBoldFontMask]; 643 if (!font) font = originalFont; 644 } 645 if ([@"small-caps" isEqualToString:fontVariant]) { 646 // ??? synthesize small-caps if [font isEqual:originalFont] 647 NSFont *originalFont = font; 648 font = [fontManager convertFont:font toHaveTrait:NSSmallCapsFontMask]; 649 if (!font) font = originalFont; 650 } 651 } 652 if (font) [attrs setObject:font forKey:NSFontAttributeName]; 653 if (foregroundColor) [attrs setObject:foregroundColor forKey:NSForegroundColorAttributeName]; 654 if (backgroundColor && ![self _elementHasOwnBackgroundColor:element]) [attrs setObject:backgroundColor forKey:NSBackgroundColorAttributeName]; 655 if (fontEffect) { 656 if ([fontEffect rangeOfString:@"outline"].location != NSNotFound) [attrs setObject:[NSNumber numberWithDouble:3.0] forKey:NSStrokeWidthAttributeName]; 657 if ([fontEffect rangeOfString:@"emboss"].location != NSNotFound) [attrs setObject:[[[NSShadow alloc] init] autorelease] forKey:NSShadowAttributeName]; 658 } 659 if (textDecoration && [textDecoration length] > 4) { 660 if ([textDecoration rangeOfString:@"underline"].location != NSNotFound) [attrs setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName]; 661 if ([textDecoration rangeOfString:@"line-through"].location != NSNotFound) [attrs setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName]; 662 } 663 if (verticalAlign) { 664 if ([verticalAlign rangeOfString:@"super"].location != NSNotFound) [attrs setObject:[NSNumber numberWithInteger:1] forKey:NSSuperscriptAttributeName]; 665 if ([verticalAlign rangeOfString:@"sub"].location != NSNotFound) [attrs setObject:[NSNumber numberWithInteger:-1] forKey:NSSuperscriptAttributeName]; 666 } 667 if ([self _getFloat:&baselineOffset forNode:element property:@"vertical-align"]) [attrs setObject:[NSNumber numberWithDouble:baselineOffset] forKey:NSBaselineOffsetAttributeName]; 668 if ([self _getFloat:&kerning forNode:element property:@"letter-spacing"]) [attrs setObject:[NSNumber numberWithDouble:kerning] forKey:NSKernAttributeName]; 669 if (textShadow && [textShadow length] > 4) { 670 NSShadow *shadow = _shadowForShadowStyle(textShadow); 671 if (shadow) [attrs setObject:shadow forKey:NSShadowAttributeName]; 672 } 673 if (element != blockElement && [_writingDirectionArray count] > 0) [attrs setObject:[NSArray arrayWithArray:_writingDirectionArray] forKey:NSWritingDirectionAttributeName]; 674 675 if (blockElement) { 676 NSMutableParagraphStyle *paragraphStyle = [[[self class] defaultParagraphStyle] mutableCopy]; 677 NSString *blockTag = [blockElement tagName]; 678 BOOL isParagraph = ([@"P" isEqualToString:blockTag] || [@"LI" isEqualToString:blockTag] || ([blockTag hasPrefix:@"H"] && 2 == [blockTag length])); 679 NSString *textAlign = [self _stringForNode:blockElement property:@"text-align"], *direction = [self _stringForNode:blockElement property:@"direction"]; 680 CGFloat leftMargin = 0, rightMargin = 0, bottomMargin = 0, textIndent = 0, lineHeight = 0; 681 if (textAlign) { 682 // WebKit can return -khtml-left, -khtml-right, -khtml-center 683 if ([textAlign hasSuffix:@"left"]) [paragraphStyle setAlignment:NSLeftTextAlignment]; 684 else if ([textAlign hasSuffix:@"right"]) [paragraphStyle setAlignment:NSRightTextAlignment]; 685 else if ([textAlign hasSuffix:@"center"]) [paragraphStyle setAlignment:NSCenterTextAlignment]; 686 else if ([textAlign hasSuffix:@"justify"]) [paragraphStyle setAlignment:NSJustifiedTextAlignment]; 687 } 688 if (direction) { 689 if ([direction isEqualToString:@"ltr"]) [paragraphStyle setBaseWritingDirection:NSWritingDirectionLeftToRight]; 690 else if ([direction isEqualToString:@"rtl"]) [paragraphStyle setBaseWritingDirection:NSWritingDirectionRightToLeft]; 691 } 692 if ([blockTag hasPrefix:@"H"] && 2 == [blockTag length]) { 693 NSInteger headerLevel = [blockTag characterAtIndex:1] - '0'; 694 if (1 <= headerLevel && headerLevel <= 6) [paragraphStyle setHeaderLevel:headerLevel]; 695 } 696 if (isParagraph) { 697 //if ([self _getFloat:&topMargin forNode:blockElement property:@"margin-top"] && topMargin > 0.0) [paragraphStyle setParagraphSpacingBefore:topMargin]; 698 if ([self _getFloat:&leftMargin forNode:blockElement property:@"margin-left"] && leftMargin > 0.0) [paragraphStyle setHeadIndent:leftMargin]; 699 if ([self _getFloat:&textIndent forNode:blockElement property:@"text-indent"]) [paragraphStyle setFirstLineHeadIndent:[paragraphStyle headIndent] + textIndent]; 700 if ([self _getFloat:&rightMargin forNode:blockElement property:@"margin-right"] && rightMargin > 0.0) [paragraphStyle setTailIndent:-rightMargin]; 701 if ([self _getFloat:&bottomMargin forNode:blockElement property:@"margin-bottom"] && bottomMargin > 0.0) [paragraphStyle setParagraphSpacing:bottomMargin]; 702 } 703 if (_webViewTextSizeMultiplier > 0.0 && [self _getFloat:&lineHeight forNode:element property:@"line-height"] && lineHeight > 0.0) { 704 [paragraphStyle setMinimumLineHeight:lineHeight / _webViewTextSizeMultiplier]; 705 } 706 if ([_textLists count] > 0) [paragraphStyle setTextLists:_textLists]; 707 if ([_textBlocks count] > 0) [paragraphStyle setTextBlocks:_textBlocks]; 708 [attrs setObject:paragraphStyle forKey:NSParagraphStyleAttributeName]; 709 [paragraphStyle release]; 710 } 711 return attrs; 712} 713 714- (NSDictionary *)_attributesForElement:(DOMElement *)element 715{ 716 NSDictionary *result; 717 if (element) { 718 result = [_attributesForElements objectForKey:element]; 719 if (!result) { 720 result = [self _computedAttributesForElement:element]; 721 [_attributesForElements setObject:result forKey:element]; 722 } 723 } else { 724 result = [NSDictionary dictionary]; 725 } 726 return result; 727 728} 729 730- (void)_newParagraphForElement:(DOMElement *)element tag:(NSString *)tag allowEmpty:(BOOL)flag suppressTrailingSpace:(BOOL)suppress 731{ 732 NSUInteger textLength = [_attrStr length]; 733 unichar lastChar = (textLength > 0) ? [[_attrStr string] characterAtIndex:textLength - 1] : '\n'; 734 NSRange rangeToReplace = (suppress && _flags.isSoft && (lastChar == ' ' || lastChar == NSLineSeparatorCharacter)) ? NSMakeRange(textLength - 1, 1) : NSMakeRange(textLength, 0); 735 BOOL needBreak = (flag || lastChar != '\n'); 736 if (needBreak) { 737 NSString *string = (([@"BODY" isEqualToString:tag] || [@"HTML" isEqualToString:tag]) ? @"" : @"\n"); 738 [_writingDirectionArray removeAllObjects]; 739 [_attrStr replaceCharactersInRange:rangeToReplace withString:string]; 740 if (rangeToReplace.location < _domRangeStartIndex) _domRangeStartIndex += [string length] - rangeToReplace.length; 741 rangeToReplace.length = [string length]; 742 if (!_flags.isIndexing) { 743 NSDictionary *attrs = [self _attributesForElement:element]; 744 if (!_flags.isTesting && rangeToReplace.length > 0) [_attrStr setAttributes:attrs range:rangeToReplace]; 745 } 746 _flags.isSoft = YES; 747 } 748} 749 750- (void)_newLineForElement:(DOMElement *)element 751{ 752 unichar c = NSLineSeparatorCharacter; 753 NSString *string = [[NSString alloc] initWithCharacters:&c length:1]; 754 NSUInteger textLength = [_attrStr length]; 755 NSRange rangeToReplace = NSMakeRange(textLength, 0); 756 [_attrStr replaceCharactersInRange:rangeToReplace withString:string]; 757 rangeToReplace.length = [string length]; 758 if (rangeToReplace.location < _domRangeStartIndex) _domRangeStartIndex += rangeToReplace.length; 759 if (!_flags.isIndexing) { 760 NSDictionary *attrs = [self _attributesForElement:element]; 761 if (!_flags.isTesting && rangeToReplace.length > 0) [_attrStr setAttributes:attrs range:rangeToReplace]; 762 } 763 [string release]; 764 _flags.isSoft = YES; 765} 766 767- (void)_newTabForElement:(DOMElement *)element 768{ 769 NSString *string = @"\t"; 770 NSUInteger textLength = [_attrStr length]; 771 unichar lastChar = (textLength > 0) ? [[_attrStr string] characterAtIndex:textLength - 1] : '\n'; 772 NSRange rangeToReplace = (_flags.isSoft && lastChar == ' ') ? NSMakeRange(textLength - 1, 1) : NSMakeRange(textLength, 0); 773 [_attrStr replaceCharactersInRange:rangeToReplace withString:string]; 774 rangeToReplace.length = [string length]; 775 if (rangeToReplace.location < _domRangeStartIndex) _domRangeStartIndex += rangeToReplace.length; 776 if (!_flags.isIndexing) { 777 NSDictionary *attrs = [self _attributesForElement:element]; 778 if (!_flags.isTesting && rangeToReplace.length > 0) [_attrStr setAttributes:attrs range:rangeToReplace]; 779 } 780 [string release]; 781 _flags.isSoft = YES; 782} 783 784- (BOOL)_addAttachmentForElement:(DOMElement *)element URL:(NSURL *)url needsParagraph:(BOOL)needsParagraph usePlaceholder:(BOOL)flag 785{ 786 BOOL retval = NO, notFound = NO; 787 NSFileWrapper *fileWrapper = nil; 788 static NSImage *missingImage = nil; 789 Frame* frame = core([element ownerDocument])->frame(); 790 DocumentLoader *dataSource = frame->loader()->frameHasLoaded() ? frame->loader()->documentLoader() : 0; 791 BOOL ignoreOrientation = YES; 792 793 if (_flags.isIndexing) return NO; 794 if ([url isFileURL]) { 795 NSString *path = [[url path] stringByStandardizingPath]; 796 if (path) fileWrapper = [[[NSFileWrapper alloc] initWithPath:path] autorelease]; 797 } 798 if (!fileWrapper) { 799 RefPtr<ArchiveResource> resource = dataSource->subresource(url); 800 if (!resource) resource = dataSource->subresource(url); 801 if (flag && resource && [@"text/html" isEqual:resource->mimeType()]) notFound = YES; 802 if (resource && !notFound) { 803 fileWrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:[resource->data()->createNSData() autorelease]] autorelease]; 804 [fileWrapper setPreferredFilename:suggestedFilenameWithMIMEType(url, resource->mimeType())]; 805 } 806 } 807 if (!fileWrapper && !notFound) { 808 fileWrapper = fileWrapperForURL(dataSource, url); 809 if (flag && fileWrapper && [[[[fileWrapper preferredFilename] pathExtension] lowercaseString] hasPrefix:@"htm"]) notFound = YES; 810 if (notFound) fileWrapper = nil; 811 } 812 if (!fileWrapper && !notFound) { 813 fileWrapper = fileWrapperForURL(_dataSource, url); 814 if (flag && fileWrapper && [[[[fileWrapper preferredFilename] pathExtension] lowercaseString] hasPrefix:@"htm"]) notFound = YES; 815 if (notFound) fileWrapper = nil; 816 } 817 if (fileWrapper || flag) { 818 NSUInteger textLength = [_attrStr length]; 819 NSTextAttachment *attachment = [[NSTextAttachment alloc] initWithFileWrapper:fileWrapper]; 820 NSTextAttachmentCell *cell; 821 NSString *string = [[NSString alloc] initWithFormat:(needsParagraph ? @"%C\n" : @"%C"), NSAttachmentCharacter]; 822 NSRange rangeToReplace = NSMakeRange(textLength, 0); 823 NSDictionary *attrs; 824 if (fileWrapper) { 825 if (ignoreOrientation) [attachment setIgnoresOrientation:YES]; 826 } else { 827 cell = [[NSTextAttachmentCell alloc] initImageCell:missingImage]; 828 [attachment setAttachmentCell:cell]; 829 [cell release]; 830 } 831 [_attrStr replaceCharactersInRange:rangeToReplace withString:string]; 832 rangeToReplace.length = [string length]; 833 if (rangeToReplace.location < _domRangeStartIndex) _domRangeStartIndex += rangeToReplace.length; 834 attrs = [self _attributesForElement:element]; 835 if (!_flags.isTesting && rangeToReplace.length > 0) { 836 [_attrStr setAttributes:attrs range:rangeToReplace]; 837 rangeToReplace.length = 1; 838 [_attrStr addAttribute:NSAttachmentAttributeName value:attachment range:rangeToReplace]; 839 } 840 [string release]; 841 [attachment release]; 842 _flags.isSoft = NO; 843 retval = YES; 844 } 845 return retval; 846} 847 848- (void)_addQuoteForElement:(DOMElement *)element opening:(BOOL)opening level:(NSInteger)level 849{ 850 unichar c = ((level % 2) == 0) ? (opening ? 0x201c : 0x201d) : (opening ? 0x2018 : 0x2019); 851 NSString *string = [[NSString alloc] initWithCharacters:&c length:1]; 852 NSUInteger textLength = [_attrStr length]; 853 NSRange rangeToReplace = NSMakeRange(textLength, 0); 854 [_attrStr replaceCharactersInRange:rangeToReplace withString:string]; 855 rangeToReplace.length = [string length]; 856 if (rangeToReplace.location < _domRangeStartIndex) _domRangeStartIndex += rangeToReplace.length; 857 if (!_flags.isIndexing) { 858 NSDictionary *attrs = [self _attributesForElement:element]; 859 if (!_flags.isTesting && rangeToReplace.length > 0) [_attrStr setAttributes:attrs range:rangeToReplace]; 860 } 861 [string release]; 862 _flags.isSoft = NO; 863} 864 865- (void)_addValue:(NSString *)value forElement:(DOMElement *)element 866{ 867 NSUInteger textLength = [_attrStr length], valueLength = [value length]; 868 NSRange rangeToReplace = NSMakeRange(textLength, 0); 869 if (valueLength > 0) { 870 [_attrStr replaceCharactersInRange:rangeToReplace withString:value]; 871 rangeToReplace.length = valueLength; 872 if (rangeToReplace.location < _domRangeStartIndex) _domRangeStartIndex += rangeToReplace.length; 873 if (!_flags.isIndexing) { 874 NSDictionary *attrs = [self _attributesForElement:element]; 875 if (!_flags.isTesting && rangeToReplace.length > 0) [_attrStr setAttributes:attrs range:rangeToReplace]; 876 } 877 _flags.isSoft = NO; 878 } 879} 880 881- (void)_fillInBlock:(NSTextBlock *)block forElement:(DOMElement *)element backgroundColor:(NSColor *)backgroundColor extraMargin:(CGFloat)extraMargin extraPadding:(CGFloat)extraPadding isTable:(BOOL)isTable 882{ 883 CGFloat val = 0; 884 NSColor *color = nil; 885 BOOL isTableCellElement = [element isKindOfClass:[DOMHTMLTableCellElement class]]; 886 NSString *width = isTableCellElement ? [(DOMHTMLTableCellElement *)element width] : [element getAttribute:@"width"]; 887 888 if ((width && [width length] > 0) || !isTable) { 889 if ([self _getFloat:&val forNode:element property:@"width"]) [block setValue:val type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockWidth]; 890 } 891 892 if ([self _getFloat:&val forNode:element property:@"min-width"]) [block setValue:val type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockMinimumWidth]; 893 if ([self _getFloat:&val forNode:element property:@"max-width"]) [block setValue:val type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockMaximumWidth]; 894 if ([self _getFloat:&val forNode:element property:@"min-height"]) [block setValue:val type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockMinimumHeight]; 895 if ([self _getFloat:&val forNode:element property:@"max-height"]) [block setValue:val type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockMaximumHeight]; 896 897 if ([self _getFloat:&val forNode:element property:@"padding-left"]) [block setWidth:val + extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMinXEdge]; else [block setWidth:extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMinXEdge]; 898 if ([self _getFloat:&val forNode:element property:@"padding-top"]) [block setWidth:val + extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMinYEdge]; else [block setWidth:extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMinYEdge]; 899 if ([self _getFloat:&val forNode:element property:@"padding-right"]) [block setWidth:val + extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMaxXEdge]; else [block setWidth:extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMaxXEdge]; 900 if ([self _getFloat:&val forNode:element property:@"padding-bottom"]) [block setWidth:val + extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMaxYEdge]; else [block setWidth:extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMaxYEdge]; 901 902 if ([self _getFloat:&val forNode:element property:@"border-left-width"]) [block setWidth:val type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockBorder edge:NSMinXEdge]; 903 if ([self _getFloat:&val forNode:element property:@"border-top-width"]) [block setWidth:val type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockBorder edge:NSMinYEdge]; 904 if ([self _getFloat:&val forNode:element property:@"border-right-width"]) [block setWidth:val type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockBorder edge:NSMaxXEdge]; 905 if ([self _getFloat:&val forNode:element property:@"border-bottom-width"]) [block setWidth:val type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockBorder edge:NSMaxYEdge]; 906 907 if ([self _getFloat:&val forNode:element property:@"margin-left"]) [block setWidth:val + extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMinXEdge]; else [block setWidth:extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMinXEdge]; 908 if ([self _getFloat:&val forNode:element property:@"margin-top"]) [block setWidth:val + extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMinYEdge]; else [block setWidth:extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMinYEdge]; 909 if ([self _getFloat:&val forNode:element property:@"margin-right"]) [block setWidth:val + extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMaxXEdge]; else [block setWidth:extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMaxXEdge]; 910 if ([self _getFloat:&val forNode:element property:@"margin-bottom"]) [block setWidth:val + extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMaxYEdge]; else [block setWidth:extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMaxYEdge]; 911 912 if ((color = [self _colorForNode:element property:@"background-color"])) [block setBackgroundColor:color]; 913 if (!color && backgroundColor) [block setBackgroundColor:backgroundColor]; 914 if ((color = [self _colorForNode:element property:@"border-left-color"])) [block setBorderColor:color forEdge:NSMinXEdge]; 915 if ((color = [self _colorForNode:element property:@"border-top-color"])) [block setBorderColor:color forEdge:NSMinYEdge]; 916 if ((color = [self _colorForNode:element property:@"border-right-color"])) [block setBorderColor:color forEdge:NSMaxXEdge]; 917 if ((color = [self _colorForNode:element property:@"border-bottom-color"])) [block setBorderColor:color forEdge:NSMaxYEdge]; 918} 919 920static inline BOOL read2DigitNumber(const char **pp, int8_t *outval) 921{ 922 BOOL result = NO; 923 char c1 = *(*pp)++, c2; 924 if (isASCIIDigit(c1)) { 925 c2 = *(*pp)++; 926 if (isASCIIDigit(c2)) { 927 *outval = 10 * (c1 - '0') + (c2 - '0'); 928 result = YES; 929 } 930 } 931 return result; 932} 933 934static inline NSDate *_dateForString(NSString *string) 935{ 936 CFGregorianDate date; 937 const char *p = [string UTF8String]; 938 int8_t secval = 0; 939 BOOL wellFormed = YES; 940 941 date.year = 0; 942 while (*p && isASCIIDigit(*p)) date.year = 10 * date.year + *p++ - '0'; 943 if (*p++ != '-') wellFormed = NO; 944 if (!wellFormed || !read2DigitNumber(&p, &date.month) || *p++ != '-') wellFormed = NO; 945 if (!wellFormed || !read2DigitNumber(&p, &date.day) || *p++ != 'T') wellFormed = NO; 946 if (!wellFormed || !read2DigitNumber(&p, &date.hour) || *p++ != ':') wellFormed = NO; 947 if (!wellFormed || !read2DigitNumber(&p, &date.minute) || *p++ != ':') wellFormed = NO; 948 if (!wellFormed || !read2DigitNumber(&p, &secval) || *p++ != 'Z') wellFormed = NO; 949 if (wellFormed) date.second = secval; 950 return wellFormed ? [(NSDate *)CFDateCreate(NULL, CFGregorianDateGetAbsoluteTime(date, NULL)) autorelease] : nil; 951} 952 953static NSInteger _colCompare(id block1, id block2, void *) 954{ 955 NSInteger col1 = [(NSTextTableBlock *)block1 startingColumn], col2 = [(NSTextTableBlock *)block2 startingColumn]; 956 return ((col1 < col2) ? NSOrderedAscending : ((col1 == col2) ? NSOrderedSame : NSOrderedDescending)); 957} 958 959- (BOOL)_enterElement:(DOMElement *)element tag:(NSString *)tag display:(NSString *)displayVal 960{ 961 if (!displayVal || !([@"none" isEqualToString:displayVal] || [@"table-column" isEqualToString:displayVal] || [@"table-column-group" isEqualToString:displayVal])) { 962 if ([self _elementIsBlockLevel:element] && ![@"BR" isEqualToString:tag] && !([@"table-cell" isEqualToString:displayVal] && [_textTables count] == 0) 963 && !([_textLists count] > 0 && [@"block" isEqualToString:displayVal] && ![@"LI" isEqualToString:tag] && ![@"UL" isEqualToString:tag] && ![@"OL" isEqualToString:tag])) 964 [self _newParagraphForElement:element tag:tag allowEmpty:NO suppressTrailingSpace:YES]; 965 return YES; 966 } 967 return NO; 968} 969 970- (void)_addTableForElement:(DOMElement *)tableElement 971{ 972 NSTextTable *table = [[NSTextTable alloc] init]; 973 CGFloat cellSpacingVal = 1, cellPaddingVal = 1; 974 [table setNumberOfColumns:1]; 975 [table setLayoutAlgorithm:NSTextTableAutomaticLayoutAlgorithm]; 976 [table setCollapsesBorders:NO]; 977 [table setHidesEmptyCells:NO]; 978 if (tableElement) { 979 NSString *borderCollapse = [self _stringForNode:tableElement property:@"border-collapse"], *emptyCells = [self _stringForNode:tableElement property:@"empty-cells"], *tableLayout = [self _stringForNode:tableElement property:@"table-layout"]; 980 if ([tableElement respondsToSelector:@selector(cellSpacing)]) { 981 NSString *cellSpacing = [(DOMHTMLTableElement *)tableElement cellSpacing]; 982 if (cellSpacing && [cellSpacing length] > 0 && ![cellSpacing hasSuffix:@"%"]) cellSpacingVal = [cellSpacing floatValue]; 983 } 984 if ([tableElement respondsToSelector:@selector(cellPadding)]) { 985 NSString *cellPadding = [(DOMHTMLTableElement *)tableElement cellPadding]; 986 if (cellPadding && [cellPadding length] > 0 && ![cellPadding hasSuffix:@"%"]) cellPaddingVal = [cellPadding floatValue]; 987 } 988 [self _fillInBlock:table forElement:tableElement backgroundColor:nil extraMargin:0 extraPadding:0 isTable:YES]; 989 if ([@"collapse" isEqualToString:borderCollapse]) { 990 [table setCollapsesBorders:YES]; 991 cellSpacingVal = 0; 992 } 993 if ([@"hide" isEqualToString:emptyCells]) [table setHidesEmptyCells:YES]; 994 if ([@"fixed" isEqualToString:tableLayout]) [table setLayoutAlgorithm:NSTextTableFixedLayoutAlgorithm]; 995 } 996 [_textTables addObject:table]; 997 [_textTableSpacings addObject:[NSNumber numberWithDouble:cellSpacingVal]]; 998 [_textTablePaddings addObject:[NSNumber numberWithDouble:cellPaddingVal]]; 999 [_textTableRows addObject:[NSNumber numberWithInteger:0]]; 1000 [_textTableRowArrays addObject:[NSMutableArray array]]; 1001 [table release]; 1002} 1003 1004- (void)_addTableCellForElement:(DOMElement *)tableCellElement 1005{ 1006 NSTextTable *table = [_textTables lastObject]; 1007 NSInteger rowNumber = [[_textTableRows lastObject] integerValue], columnNumber = 0, rowSpan = 1, colSpan = 1; 1008 NSMutableArray *rowArray = [_textTableRowArrays lastObject]; 1009 NSUInteger i, count = [rowArray count]; 1010 NSColor *color = ([_textTableRowBackgroundColors count] > 0) ? [_textTableRowBackgroundColors lastObject] : nil; 1011 NSTextTableBlock *block, *previousBlock; 1012 CGFloat cellSpacingVal = [[_textTableSpacings lastObject] floatValue]; 1013 if ([color isEqual:[NSColor clearColor]]) color = nil; 1014 for (i = 0; i < count; i++) { 1015 previousBlock = [rowArray objectAtIndex:i]; 1016 if (columnNumber >= [previousBlock startingColumn] && columnNumber < [previousBlock startingColumn] + [previousBlock columnSpan]) columnNumber = [previousBlock startingColumn] + [previousBlock columnSpan]; 1017 } 1018 if (tableCellElement) { 1019 if ([tableCellElement respondsToSelector:@selector(rowSpan)]) { 1020 rowSpan = [(DOMHTMLTableCellElement *)tableCellElement rowSpan]; 1021 if (rowSpan < 1) rowSpan = 1; 1022 } 1023 if ([tableCellElement respondsToSelector:@selector(colSpan)]) { 1024 colSpan = [(DOMHTMLTableCellElement *)tableCellElement colSpan]; 1025 if (colSpan < 1) colSpan = 1; 1026 } 1027 } 1028 block = [[NSTextTableBlock alloc] initWithTable:table startingRow:rowNumber rowSpan:rowSpan startingColumn:columnNumber columnSpan:colSpan]; 1029 if (tableCellElement) { 1030 NSString *verticalAlign = [self _stringForNode:tableCellElement property:@"vertical-align"]; 1031 [self _fillInBlock:block forElement:tableCellElement backgroundColor:color extraMargin:cellSpacingVal / 2 extraPadding:0 isTable:NO]; 1032 if ([@"middle" isEqualToString:verticalAlign]) [block setVerticalAlignment:NSTextBlockMiddleAlignment]; 1033 else if ([@"bottom" isEqualToString:verticalAlign]) [block setVerticalAlignment:NSTextBlockBottomAlignment]; 1034 else if ([@"baseline" isEqualToString:verticalAlign]) [block setVerticalAlignment:NSTextBlockBaselineAlignment]; 1035 else if ([@"top" isEqualToString:verticalAlign]) [block setVerticalAlignment:NSTextBlockTopAlignment]; 1036 } 1037 [_textBlocks addObject:block]; 1038 [rowArray addObject:block]; 1039 [rowArray sortUsingFunction:_colCompare context:NULL]; 1040 [block release]; 1041} 1042 1043- (BOOL)_processElement:(DOMElement *)element tag:(NSString *)tag display:(NSString *)displayVal depth:(NSInteger)depth 1044{ 1045 BOOL retval = YES, isBlockLevel = [self _elementIsBlockLevel:element]; 1046 if (isBlockLevel) { 1047 [_writingDirectionArray removeAllObjects]; 1048 } else { 1049 NSString *bidi = [self _stringForNode:element property:@"unicode-bidi"]; 1050 if (bidi && [bidi isEqualToString:@"embed"]) { 1051 NSUInteger val = NSTextWritingDirectionEmbedding; 1052 NSString *direction = [self _stringForNode:element property:@"direction"]; 1053 if ([direction isEqualToString:@"rtl"]) val |= NSWritingDirectionRightToLeft; 1054 [_writingDirectionArray addObject:[NSNumber numberWithUnsignedInteger:val]]; 1055 } else if (bidi && [bidi isEqualToString:@"bidi-override"]) { 1056 NSUInteger val = NSTextWritingDirectionOverride; 1057 NSString *direction = [self _stringForNode:element property:@"direction"]; 1058 if ([direction isEqualToString:@"rtl"]) val |= NSWritingDirectionRightToLeft; 1059 [_writingDirectionArray addObject:[NSNumber numberWithUnsignedInteger:val]]; 1060 } 1061 } 1062 if ([@"table" isEqualToString:displayVal] || ([_textTables count] == 0 && [@"table-row-group" isEqualToString:displayVal])) { 1063 DOMElement *tableElement = element; 1064 if ([@"table-row-group" isEqualToString:displayVal]) { 1065 // If we are starting in medias res, the first thing we see may be the tbody, so go up to the table 1066 tableElement = [self _blockLevelElementForNode:[element parentNode]]; 1067 if (![@"table" isEqualToString:[self _stringForNode:tableElement property:@"display"]]) tableElement = element; 1068 } 1069 while ([_textTables count] > [_textBlocks count]) { 1070 [self _addTableCellForElement:nil]; 1071 } 1072 [self _addTableForElement:tableElement]; 1073 } else if ([@"table-footer-group" isEqualToString:displayVal] && [_textTables count] > 0) { 1074 [_textTableFooters setObject:element forKey:[NSValue valueWithNonretainedObject:[_textTables lastObject]]]; 1075 retval = NO; 1076 } else if ([@"table-row" isEqualToString:displayVal] && [_textTables count] > 0) { 1077 NSColor *color = [self _colorForNode:element property:@"background-color"]; 1078 if (!color) color = [NSColor clearColor]; 1079 [_textTableRowBackgroundColors addObject:color]; 1080 } else if ([@"table-cell" isEqualToString:displayVal]) { 1081 while ([_textTables count] < [_textBlocks count] + 1) { 1082 [self _addTableForElement:nil]; 1083 } 1084 [self _addTableCellForElement:element]; 1085 } else if ([@"IMG" isEqualToString:tag]) { 1086 NSString *urlString = [element getAttribute:@"src"]; 1087 if (urlString && [urlString length] > 0) { 1088 NSURL *url = core([element ownerDocument])->completeURL(stripLeadingAndTrailingHTMLSpaces(urlString)); 1089 if (!url) url = [NSURL _web_URLWithString:[urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] relativeToURL:_baseURL]; 1090 if (url) [self _addAttachmentForElement:element URL:url needsParagraph:isBlockLevel usePlaceholder:YES]; 1091 } 1092 retval = NO; 1093 } else if ([@"OBJECT" isEqualToString:tag]) { 1094 NSString *baseString = [element getAttribute:@"codebase"], *urlString = [element getAttribute:@"data"], *declareString = [element getAttribute:@"declare"]; 1095 if (urlString && [urlString length] > 0 && ![@"true" isEqualToString:declareString]) { 1096 NSURL *baseURL = nil, *url = nil; 1097 if (baseString && [baseString length] > 0) { 1098 baseURL = core([element ownerDocument])->completeURL(stripLeadingAndTrailingHTMLSpaces(baseString)); 1099 if (!baseURL) baseURL = [NSURL _web_URLWithString:[baseString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] relativeToURL:_baseURL]; 1100 } 1101 if (baseURL) url = [NSURL _web_URLWithString:[urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] relativeToURL:baseURL]; 1102 if (!url) url =core([element ownerDocument])->completeURL(stripLeadingAndTrailingHTMLSpaces(urlString)); 1103 if (!url) url = [NSURL _web_URLWithString:[urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] relativeToURL:_baseURL]; 1104 if (url) retval = ![self _addAttachmentForElement:element URL:url needsParagraph:isBlockLevel usePlaceholder:NO]; 1105 } 1106 } else if ([@"FRAME" isEqualToString:tag]) { 1107 if ([element respondsToSelector:@selector(contentDocument)]) { 1108 DOMDocument *contentDocument = [(DOMHTMLFrameElement *)element contentDocument]; 1109 if (contentDocument) [self _traverseNode:contentDocument depth:depth + 1 embedded:YES]; 1110 } 1111 retval = NO; 1112 } else if ([@"IFRAME" isEqualToString:tag]) { 1113 if ([element respondsToSelector:@selector(contentDocument)]) { 1114 DOMDocument *contentDocument = [(DOMHTMLIFrameElement *)element contentDocument]; 1115 if (contentDocument) { 1116 [self _traverseNode:contentDocument depth:depth + 1 embedded:YES]; 1117 retval = NO; 1118 } 1119 } 1120 } else if ([@"BR" isEqualToString:tag]) { 1121 DOMElement *blockElement = [self _blockLevelElementForNode:[element parentNode]]; 1122 NSString *breakClass = [element getAttribute:@"class"], *blockTag = [blockElement tagName]; 1123 BOOL isExtraBreak = [@"Apple-interchange-newline" isEqualToString:breakClass], blockElementIsParagraph = ([@"P" isEqualToString:blockTag] || [@"LI" isEqualToString:blockTag] || ([blockTag hasPrefix:@"H"] && 2 == [blockTag length])); 1124 if (isExtraBreak) { 1125 _flags.hasTrailingNewline = YES; 1126 } else { 1127 if (blockElement && blockElementIsParagraph) { 1128 [self _newLineForElement:element]; 1129 } else { 1130 [self _newParagraphForElement:element tag:tag allowEmpty:YES suppressTrailingSpace:NO]; 1131 } 1132 } 1133 } else if ([@"UL" isEqualToString:tag]) { 1134 NSTextList *list; 1135 NSString *listStyleType = [self _stringForNode:element property:@"list-style-type"]; 1136 if (!listStyleType || [listStyleType length] == 0) listStyleType = @"disc"; 1137 list = [[NSTextList alloc] initWithMarkerFormat:[NSString stringWithFormat:@"{%@}", listStyleType] options:0]; 1138 [_textLists addObject:list]; 1139 [list release]; 1140 } else if ([@"OL" isEqualToString:tag]) { 1141 NSTextList *list; 1142 NSString *listStyleType = [self _stringForNode:element property:@"list-style-type"]; 1143 if (!listStyleType || [listStyleType length] == 0) listStyleType = @"decimal"; 1144 list = [[NSTextList alloc] initWithMarkerFormat:[NSString stringWithFormat:@"{%@}.", listStyleType] options:0]; 1145 if ([element respondsToSelector:@selector(start)]) { 1146 NSInteger startingItemNumber = [(DOMHTMLOListElement *)element start]; 1147 [list setStartingItemNumber:startingItemNumber]; 1148 } 1149 [_textLists addObject:list]; 1150 [list release]; 1151 } else if ([@"Q" isEqualToString:tag]) { 1152 [self _addQuoteForElement:element opening:YES level:_quoteLevel++]; 1153 } else if ([@"INPUT" isEqualToString:tag]) { 1154 if ([element respondsToSelector:@selector(type)] && [element respondsToSelector:@selector(value)] && [@"text" compare:[(DOMHTMLInputElement *)element type] options:NSCaseInsensitiveSearch] == NSOrderedSame) { 1155 NSString *value = [(DOMHTMLInputElement *)element value]; 1156 if (value && [value length] > 0) [self _addValue:value forElement:element]; 1157 } 1158 } else if ([@"TEXTAREA" isEqualToString:tag]) { 1159 if ([element respondsToSelector:@selector(value)]) { 1160 NSString *value = [(DOMHTMLTextAreaElement *)element value]; 1161 if (value && [value length] > 0) [self _addValue:value forElement:element]; 1162 } 1163 retval = NO; 1164 } 1165 return retval; 1166} 1167 1168- (void)_addMarkersToList:(NSTextList *)list range:(NSRange)range 1169{ 1170 NSInteger itemNum = [list startingItemNumber]; 1171 NSString *string = [_attrStr string], *stringToInsert; 1172 NSDictionary *attrsToInsert = nil; 1173 NSFont *font; 1174 NSParagraphStyle *paragraphStyle; 1175 NSMutableParagraphStyle *newStyle; 1176 NSTextTab *tab = nil, *tabToRemove; 1177 NSRange paragraphRange, styleRange; 1178 NSUInteger textLength = [_attrStr length], listIndex, idx, insertLength, i, count; 1179 NSArray *textLists; 1180 CGFloat markerLocation, listLocation, pointSize; 1181 1182 if (range.length == 0 || range.location >= textLength) return; 1183 if (NSMaxRange(range) > textLength) range.length = textLength - range.location; 1184 paragraphStyle = [_attrStr attribute:NSParagraphStyleAttributeName atIndex:range.location effectiveRange:NULL]; 1185 if (paragraphStyle) { 1186 textLists = [paragraphStyle textLists]; 1187 listIndex = [textLists indexOfObject:list]; 1188 if (textLists && listIndex != NSNotFound) { 1189 for (idx = range.location; idx < NSMaxRange(range);) { 1190 paragraphRange = [string paragraphRangeForRange:NSMakeRange(idx, 0)]; 1191 paragraphStyle = [_attrStr attribute:NSParagraphStyleAttributeName atIndex:idx effectiveRange:&styleRange]; 1192 font = [_attrStr attribute:NSFontAttributeName atIndex:idx effectiveRange:NULL]; 1193 pointSize = font ? [font pointSize] : 12; 1194 if ([[paragraphStyle textLists] count] == listIndex + 1) { 1195 stringToInsert = [NSString stringWithFormat:@"\t%@\t", [list markerForItemNumber:itemNum++]]; 1196 insertLength = [stringToInsert length]; 1197 if (!_flags.isIndexing && !_flags.isTesting) attrsToInsert = [NSTextList _standardMarkerAttributesForAttributes:[_attrStr attributesAtIndex:paragraphRange.location effectiveRange:NULL]]; 1198 [_attrStr replaceCharactersInRange:NSMakeRange(paragraphRange.location, 0) withString:stringToInsert]; 1199 if (!_flags.isIndexing && !_flags.isTesting) [_attrStr setAttributes:attrsToInsert range:NSMakeRange(paragraphRange.location, insertLength)]; 1200 range.length += insertLength; 1201 paragraphRange.length += insertLength; 1202 if (paragraphRange.location < _domRangeStartIndex) _domRangeStartIndex += insertLength; 1203 1204 newStyle = [paragraphStyle mutableCopy]; 1205 listLocation = (listIndex + 1) * 36; 1206 markerLocation = listLocation - 25; 1207 [newStyle setFirstLineHeadIndent:0]; 1208 [newStyle setHeadIndent:listLocation]; 1209 while ((count = [[newStyle tabStops] count]) > 0) { 1210 for (i = 0, tabToRemove = nil; !tabToRemove && i < count; i++) { 1211 tab = [[newStyle tabStops] objectAtIndex:i]; 1212 if ([tab location] <= listLocation) tabToRemove = tab; 1213 } 1214 if (tabToRemove) [newStyle removeTabStop:tab]; else break; 1215 } 1216 tab = [[NSTextTab alloc] initWithType:NSLeftTabStopType location:markerLocation]; 1217 [newStyle addTabStop:tab]; 1218 [tab release]; 1219 tab = [[NSTextTab alloc] initWithTextAlignment:NSNaturalTextAlignment location:listLocation options:nil]; 1220 [newStyle addTabStop:tab]; 1221 [tab release]; 1222 if (!_flags.isIndexing && !_flags.isTesting) [_attrStr addAttribute:NSParagraphStyleAttributeName value:newStyle range:paragraphRange]; 1223 [newStyle release]; 1224 1225 idx = NSMaxRange(paragraphRange); 1226 } else { 1227 // skip any deeper-nested lists 1228 idx = NSMaxRange(styleRange); 1229 } 1230 } 1231 } 1232 } 1233} 1234 1235- (void)_exitElement:(DOMElement *)element tag:(NSString *)tag display:(NSString *)displayVal depth:(NSInteger)depth startIndex:(NSUInteger)startIndex 1236{ 1237 NSRange range = NSMakeRange(startIndex, [_attrStr length] - startIndex); 1238 if (range.length > 0 && [@"A" isEqualToString:tag]) { 1239 NSString *urlString = [element getAttribute:@"href"], *strippedString = [urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 1240 if (urlString && [urlString length] > 0 && strippedString && [strippedString length] > 0 && ![strippedString hasPrefix:@"#"]) { 1241 NSURL *url = core([element ownerDocument])->completeURL(stripLeadingAndTrailingHTMLSpaces(urlString)); 1242 if (!url) url = core([element ownerDocument])->completeURL(stripLeadingAndTrailingHTMLSpaces(strippedString)); 1243 if (!url) url = [NSURL _web_URLWithString:strippedString relativeToURL:_baseURL]; 1244 if (!_flags.isIndexing && !_flags.isTesting) [_attrStr addAttribute:NSLinkAttributeName value:url ? (id)url : (id)urlString range:range]; 1245 } 1246 } 1247 if (!_flags.reachedEnd && [self _elementIsBlockLevel:element]) { 1248 [_writingDirectionArray removeAllObjects]; 1249 if ([@"table-cell" isEqualToString:displayVal] && [_textBlocks count] == 0) { 1250 [self _newTabForElement:element]; 1251 } else if ([_textLists count] > 0 && [@"block" isEqualToString:displayVal] && ![@"LI" isEqualToString:tag] && ![@"UL" isEqualToString:tag] && ![@"OL" isEqualToString:tag]) { 1252 [self _newLineForElement:element]; 1253 } else { 1254 [self _newParagraphForElement:element tag:tag allowEmpty:(range.length == 0) suppressTrailingSpace:YES]; 1255 } 1256 } else if ([_writingDirectionArray count] > 0) { 1257 NSString *bidi = [self _stringForNode:element property:@"unicode-bidi"]; 1258 if (bidi && ([bidi isEqualToString:@"embed"] || [bidi isEqualToString:@"bidi-override"])) { 1259 [_writingDirectionArray removeLastObject]; 1260 } 1261 } 1262 range = NSMakeRange(startIndex, [_attrStr length] - startIndex); 1263 if ([@"table" isEqualToString:displayVal] && [_textTables count] > 0) { 1264 NSValue *key = [NSValue valueWithNonretainedObject:[_textTables lastObject]]; 1265 DOMNode *footer = [_textTableFooters objectForKey:key]; 1266 while ([_textTables count] < [_textBlocks count] + 1) { 1267 [_textBlocks removeLastObject]; 1268 } 1269 if (footer) { 1270 [self _traverseFooterNode:footer depth:depth + 1]; 1271 [_textTableFooters removeObjectForKey:key]; 1272 } 1273 [_textTables removeLastObject]; 1274 [_textTableSpacings removeLastObject]; 1275 [_textTablePaddings removeLastObject]; 1276 [_textTableRows removeLastObject]; 1277 [_textTableRowArrays removeLastObject]; 1278 } else if ([@"table-row" isEqualToString:displayVal] && [_textTables count] > 0) { 1279 NSTextTable *table = [_textTables lastObject]; 1280 NSTextTableBlock *block; 1281 NSMutableArray *rowArray = [_textTableRowArrays lastObject], *previousRowArray; 1282 NSUInteger i, count; 1283 NSInteger numberOfColumns = [table numberOfColumns]; 1284 NSInteger openColumn; 1285 NSInteger rowNumber = [[_textTableRows lastObject] integerValue]; 1286 do { 1287 rowNumber++; 1288 previousRowArray = rowArray; 1289 rowArray = [NSMutableArray array]; 1290 count = [previousRowArray count]; 1291 for (i = 0; i < count; i++) { 1292 block = [previousRowArray objectAtIndex:i]; 1293 if ([block startingColumn] + [block columnSpan] > numberOfColumns) numberOfColumns = [block startingColumn] + [block columnSpan]; 1294 if ([block startingRow] + [block rowSpan] > rowNumber) [rowArray addObject:block]; 1295 } 1296 count = [rowArray count]; 1297 openColumn = 0; 1298 for (i = 0; i < count; i++) { 1299 block = [rowArray objectAtIndex:i]; 1300 if (openColumn >= [block startingColumn] && openColumn < [block startingColumn] + [block columnSpan]) openColumn = [block startingColumn] + [block columnSpan]; 1301 } 1302 } while (openColumn >= numberOfColumns); 1303 if ((NSUInteger)numberOfColumns > [table numberOfColumns]) [table setNumberOfColumns:numberOfColumns]; 1304 [_textTableRows removeLastObject]; 1305 [_textTableRows addObject:[NSNumber numberWithInteger:rowNumber]]; 1306 [_textTableRowArrays removeLastObject]; 1307 [_textTableRowArrays addObject:rowArray]; 1308 if ([_textTableRowBackgroundColors count] > 0) [_textTableRowBackgroundColors removeLastObject]; 1309 } else if ([@"table-cell" isEqualToString:displayVal] && [_textBlocks count] > 0) { 1310 while ([_textTables count] > [_textBlocks count]) { 1311 [_textTables removeLastObject]; 1312 [_textTableSpacings removeLastObject]; 1313 [_textTablePaddings removeLastObject]; 1314 [_textTableRows removeLastObject]; 1315 [_textTableRowArrays removeLastObject]; 1316 } 1317 [_textBlocks removeLastObject]; 1318 } else if (([@"UL" isEqualToString:tag] || [@"OL" isEqualToString:tag]) && [_textLists count] > 0) { 1319 NSTextList *list = [_textLists lastObject]; 1320 [self _addMarkersToList:list range:range]; 1321 [_textLists removeLastObject]; 1322 } else if ([@"Q" isEqualToString:tag]) { 1323 [self _addQuoteForElement:element opening:NO level:--_quoteLevel]; 1324 } else if ([@"SPAN" isEqualToString:tag]) { 1325 NSString *className = [element getAttribute:@"class"]; 1326 NSMutableString *mutableString; 1327 NSUInteger i, count = 0; 1328 unichar c; 1329 if ([@"Apple-converted-space" isEqualToString:className]) { 1330 mutableString = [_attrStr mutableString]; 1331 for (i = range.location; i < NSMaxRange(range); i++) { 1332 c = [mutableString characterAtIndex:i]; 1333 if (0xa0 == c) [mutableString replaceCharactersInRange:NSMakeRange(i, 1) withString:@" "]; 1334 } 1335 } else if ([@"Apple-converted-tab" isEqualToString:className]) { 1336 mutableString = [_attrStr mutableString]; 1337 for (i = range.location; i < NSMaxRange(range); i++) { 1338 NSRange rangeToReplace = NSMakeRange(NSNotFound, 0); 1339 c = [mutableString characterAtIndex:i]; 1340 if (' ' == c || 0xa0 == c) { 1341 count++; 1342 if (count >= 4 || i + 1 >= NSMaxRange(range)) rangeToReplace = NSMakeRange(i + 1 - count, count); 1343 } else { 1344 if (count > 0) rangeToReplace = NSMakeRange(i - count, count); 1345 } 1346 if (rangeToReplace.length > 0) { 1347 [mutableString replaceCharactersInRange:rangeToReplace withString:@"\t"]; 1348 range.length -= (rangeToReplace.length - 1); 1349 i -= (rangeToReplace.length - 1); 1350 if (NSMaxRange(rangeToReplace) <= _domRangeStartIndex) { 1351 _domRangeStartIndex -= (rangeToReplace.length - 1); 1352 } else if (rangeToReplace.location < _domRangeStartIndex) { 1353 _domRangeStartIndex = rangeToReplace.location; 1354 } 1355 count = 0; 1356 } 1357 } 1358 } 1359 } 1360} 1361 1362- (void)_processText:(DOMCharacterData *)text 1363{ 1364 NSString *instr = [text data], *outstr = instr, *whitespaceVal, *transformVal; 1365 NSUInteger textLength = [_attrStr length], startOffset = 0, endOffset = [instr length]; 1366 unichar lastChar = (textLength > 0) ? [[_attrStr string] characterAtIndex:textLength - 1] : '\n'; 1367 BOOL wasSpace = NO, wasLeading = YES, suppressLeadingSpace = ((_flags.isSoft && lastChar == ' ') || lastChar == '\n' || lastChar == '\r' || lastChar == '\t' || lastChar == NSParagraphSeparatorCharacter || lastChar == NSLineSeparatorCharacter || lastChar == NSFormFeedCharacter || lastChar == WebNextLineCharacter); 1368 NSRange rangeToReplace = NSMakeRange(textLength, 0); 1369 CFMutableStringRef mutstr = NULL; 1370 whitespaceVal = [self _stringForNode:text property:@"white-space"]; 1371 transformVal = [self _stringForNode:text property:@"text-transform"]; 1372 1373 if (_domRange) { 1374 if (text == [_domRange startContainer]) { 1375 startOffset = (NSUInteger)[_domRange startOffset]; 1376 _domRangeStartIndex = [_attrStr length]; 1377 _flags.reachedStart = YES; 1378 } 1379 if (text == [_domRange endContainer]) { 1380 endOffset = (NSUInteger)[_domRange endOffset]; 1381 _flags.reachedEnd = YES; 1382 } 1383 if ((startOffset > 0 || endOffset < [instr length]) && endOffset >= startOffset) { 1384 instr = [instr substringWithRange:NSMakeRange(startOffset, endOffset - startOffset)]; 1385 outstr = instr; 1386 } 1387 } 1388 if ([whitespaceVal hasPrefix:@"pre"]) { 1389 if (textLength > 0 && [instr length] > 0 && _flags.isSoft) { 1390 unichar c = [instr characterAtIndex:0]; 1391 if (c == '\n' || c == '\r' || c == NSParagraphSeparatorCharacter || c == NSLineSeparatorCharacter || c == NSFormFeedCharacter || c == WebNextLineCharacter) rangeToReplace = NSMakeRange(textLength - 1, 1); 1392 } 1393 } else { 1394 CFStringInlineBuffer inlineBuffer; 1395 const unsigned int TextBufferSize = 255; 1396 1397 unichar buffer[TextBufferSize + 1]; 1398 NSUInteger i, count = [instr length], idx = 0; 1399 1400 mutstr = CFStringCreateMutable(NULL, 0); 1401 CFStringInitInlineBuffer((CFStringRef)instr, &inlineBuffer, CFRangeMake(0, count)); 1402 for (i = 0; i < count; i++) { 1403 unichar c = CFStringGetCharacterFromInlineBuffer(&inlineBuffer, i); 1404 if (c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == 0xc || c == 0x200b) { 1405 wasSpace = (!wasLeading || !suppressLeadingSpace); 1406 } else { 1407 if (wasSpace) buffer[idx++] = ' '; 1408 buffer[idx++] = c; 1409 if (idx >= TextBufferSize) { 1410 CFStringAppendCharacters(mutstr, buffer, idx); 1411 idx = 0; 1412 } 1413 wasSpace = wasLeading = NO; 1414 } 1415 } 1416 if (wasSpace) buffer[idx++] = ' '; 1417 if (idx > 0) CFStringAppendCharacters(mutstr, buffer, idx); 1418 outstr = (NSString *)mutstr; 1419 } 1420 if ([outstr length] > 0) { 1421 if ([@"capitalize" isEqualToString:transformVal]) { 1422 outstr = [outstr capitalizedString]; 1423 } else if ([@"uppercase" isEqualToString:transformVal]) { 1424 outstr = [outstr uppercaseString]; 1425 } else if ([@"lowercase" isEqualToString:transformVal]) { 1426 outstr = [outstr lowercaseString]; 1427 } 1428 [_attrStr replaceCharactersInRange:rangeToReplace withString:outstr]; 1429 rangeToReplace.length = [outstr length]; 1430 if (!_flags.isIndexing) { 1431 NSDictionary *attrs; 1432 DOMElement *element = (DOMElement *)text; 1433 while (element && [element nodeType] != DOM_ELEMENT_NODE) element = (DOMElement *)[element parentNode]; 1434 attrs = [self _attributesForElement:element]; 1435 if (!_flags.isTesting && rangeToReplace.length > 0) [_attrStr setAttributes:attrs range:rangeToReplace]; 1436 } 1437 _flags.isSoft = wasSpace; 1438 } 1439 if (mutstr) CFRelease(mutstr); 1440} 1441 1442- (void)_traverseNode:(DOMNode *)node depth:(NSInteger)depth embedded:(BOOL)embedded 1443{ 1444 unsigned short nodeType; 1445 NSArray *childNodes; 1446 NSUInteger i, count, startOffset, endOffset; 1447 BOOL isStart = NO, isEnd = NO; 1448 1449 if (_flags.reachedEnd) return; 1450 if (_domRange && !_flags.reachedStart && _domStartAncestors && ![_domStartAncestors containsObject:node]) return; 1451 1452 nodeType = [node nodeType]; 1453 childNodes = [self _childrenForNode:node]; 1454 count = [childNodes count]; 1455 startOffset = 0; 1456 endOffset = count; 1457 1458 if (_domRange) { 1459 if (node == [_domRange startContainer]) { 1460 startOffset = (NSUInteger)[_domRange startOffset]; 1461 isStart = YES; 1462 _flags.reachedStart = YES; 1463 } 1464 if (node == [_domRange endContainer]) { 1465 endOffset = (NSUInteger)[_domRange endOffset]; 1466 isEnd = YES; 1467 } 1468 } 1469 1470 if (nodeType == DOM_DOCUMENT_NODE || nodeType == DOM_DOCUMENT_FRAGMENT_NODE) { 1471 for (i = 0; i < count; i++) { 1472 if (isStart && i == startOffset) _domRangeStartIndex = [_attrStr length]; 1473 if ((!isStart || startOffset <= i) && (!isEnd || endOffset > i)) [self _traverseNode:[childNodes objectAtIndex:i] depth:depth + 1 embedded:embedded]; 1474 if (isEnd && i + 1 >= endOffset) _flags.reachedEnd = YES; 1475 if (_thumbnailLimit > 0 && [_attrStr length] >= _thumbnailLimit) _flags.reachedEnd = YES; 1476 if (_flags.reachedEnd) break; 1477 } 1478 } else if (nodeType == DOM_ELEMENT_NODE) { 1479 DOMElement *element = (DOMElement *)node; 1480 NSString *tag = [element tagName], *displayVal = [self _stringForNode:element property:@"display"], *floatVal = [self _stringForNode:element property:@"float"]; 1481 BOOL isBlockLevel = NO; 1482 if (floatVal && ([@"left" isEqualToString:floatVal] || [@"right" isEqualToString:floatVal])) { 1483 isBlockLevel = YES; 1484 } else if (displayVal) { 1485 isBlockLevel = ([@"block" isEqualToString:displayVal] || [@"list-item" isEqualToString:displayVal] || [displayVal hasPrefix:@"table"]); 1486 } 1487 [_elementIsBlockLevel setObject:[NSNumber numberWithBool:isBlockLevel] forKey:element]; 1488 if ([self _enterElement:element tag:tag display:displayVal]) { 1489 NSUInteger startIndex = [_attrStr length]; 1490 if ([self _processElement:element tag:tag display:displayVal depth:depth]) { 1491 for (i = 0; i < count; i++) { 1492 if (isStart && i == startOffset) _domRangeStartIndex = [_attrStr length]; 1493 if ((!isStart || startOffset <= i) && (!isEnd || endOffset > i)) [self _traverseNode:[childNodes objectAtIndex:i] depth:depth + 1 embedded:embedded]; 1494 if (isEnd && i + 1 >= endOffset) _flags.reachedEnd = YES; 1495 if (_flags.reachedEnd) break; 1496 } 1497 [self _exitElement:element tag:tag display:displayVal depth:depth startIndex:startIndex]; 1498 } 1499 } 1500 } else if (nodeType == DOM_TEXT_NODE || nodeType == DOM_CDATA_SECTION_NODE) { 1501 [self _processText:(DOMCharacterData *)node]; 1502 } 1503 1504 if (isEnd) _flags.reachedEnd = YES; 1505} 1506 1507- (void)_traverseFooterNode:(DOMNode *)node depth:(NSInteger)depth 1508{ 1509 DOMElement *element = (DOMElement *)node; 1510 NSArray *childNodes = [self _childrenForNode:node]; 1511 NSString *tag = @"TBODY", *displayVal = @"table-row-group"; 1512 NSUInteger i, count = [childNodes count], startOffset = 0, endOffset = count; 1513 BOOL isStart = NO, isEnd = NO; 1514 1515 if (_flags.reachedEnd) return; 1516 if (_domRange && !_flags.reachedStart && _domStartAncestors && ![_domStartAncestors containsObject:node]) return; 1517 if (_domRange) { 1518 if (node == [_domRange startContainer]) { 1519 startOffset = (NSUInteger)[_domRange startOffset]; 1520 isStart = YES; 1521 _flags.reachedStart = YES; 1522 } 1523 if (node == [_domRange endContainer]) { 1524 endOffset = (NSUInteger)[_domRange endOffset]; 1525 isEnd = YES; 1526 } 1527 } 1528 if ([self _enterElement:element tag:tag display:displayVal]) { 1529 NSUInteger startIndex = [_attrStr length]; 1530 if ([self _processElement:element tag:tag display:displayVal depth:depth]) { 1531 for (i = 0; i < count; i++) { 1532 if (isStart && i == startOffset) _domRangeStartIndex = [_attrStr length]; 1533 if ((!isStart || startOffset <= i) && (!isEnd || endOffset > i)) [self _traverseNode:[childNodes objectAtIndex:i] depth:depth + 1 embedded:YES]; 1534 if (isEnd && i + 1 >= endOffset) _flags.reachedEnd = YES; 1535 if (_flags.reachedEnd) break; 1536 } 1537 [self _exitElement:element tag:tag display:displayVal depth:depth startIndex:startIndex]; 1538 } 1539 } 1540 if (isEnd) _flags.reachedEnd = YES; 1541} 1542 1543- (void)_adjustTrailingNewline 1544{ 1545 NSUInteger textLength = [_attrStr length]; 1546 unichar lastChar = (textLength > 0) ? [[_attrStr string] characterAtIndex:textLength - 1] : 0; 1547 BOOL alreadyHasTrailingNewline = (lastChar == '\n' || lastChar == '\r' || lastChar == NSParagraphSeparatorCharacter || lastChar == NSLineSeparatorCharacter || lastChar == WebNextLineCharacter); 1548 if (_flags.hasTrailingNewline && !alreadyHasTrailingNewline) 1549 [_attrStr replaceCharactersInRange:NSMakeRange(textLength, 0) withString:@"\n"]; 1550} 1551 1552- (void)_loadFromDOMRange 1553{ 1554 if (-1 == _errorCode) { 1555 DOMNode *commonAncestorContainer = [_domRange commonAncestorContainer], *ancestorContainer = [_domRange startContainer]; 1556 1557 _domStartAncestors = [[NSMutableArray alloc] init]; 1558 while (ancestorContainer) { 1559 [_domStartAncestors addObject:ancestorContainer]; 1560 if (ancestorContainer == commonAncestorContainer) break; 1561 ancestorContainer = [ancestorContainer parentNode]; 1562 } 1563 _document = [commonAncestorContainer ownerDocument]; 1564 _dataSource = (DocumentLoader *)core(_document)->frame()->loader()->documentLoader(); 1565 if (_textSizeMultiplier <= 0.0) _textSizeMultiplier = 1; 1566 if (_defaultFontSize <= 0.0) _defaultFontSize = 12; 1567 if (_minimumFontSize < 1.0) _minimumFontSize = 1; 1568 if (_document && _dataSource) { 1569 _domRangeStartIndex = 0; 1570 _errorCode = 0; 1571 [self _traverseNode:commonAncestorContainer depth:0 embedded:NO]; 1572 if (_domRangeStartIndex > 0 && _domRangeStartIndex <= [_attrStr length]) [_attrStr deleteCharactersInRange:NSMakeRange(0, _domRangeStartIndex)]; 1573 } 1574 } 1575} 1576 1577- (void)dealloc 1578{ 1579 [_attrStr release]; 1580 [_domRange release]; 1581 [_domStartAncestors release]; 1582 [_standardFontFamily release]; 1583 [_textLists release]; 1584 [_textBlocks release]; 1585 [_textTables release]; 1586 [_textTableFooters release]; 1587 [_textTableSpacings release]; 1588 [_textTablePaddings release]; 1589 [_textTableRows release]; 1590 [_textTableRowArrays release]; 1591 [_textTableRowBackgroundColors release]; 1592 [_computedStylesForElements release]; 1593 [_specifiedStylesForElements release]; 1594 [_stringsForNodes release]; 1595 [_floatsForNodes release]; 1596 [_colorsForNodes release]; 1597 [_attributesForElements release]; 1598 [_elementIsBlockLevel release]; 1599 [_fontCache release]; 1600 [_writingDirectionArray release]; 1601 [super dealloc]; 1602} 1603 1604- (id)init 1605{ 1606 self = [super init]; 1607 if (!self) return nil; 1608 1609 _attrStr = [[NSMutableAttributedString alloc] init]; 1610 1611 _textLists = [[NSMutableArray alloc] init]; 1612 _textBlocks = [[NSMutableArray alloc] init]; 1613 _textTables = [[NSMutableArray alloc] init]; 1614 _textTableFooters = [[NSMutableDictionary alloc] init]; 1615 _textTableSpacings = [[NSMutableArray alloc] init]; 1616 _textTablePaddings = [[NSMutableArray alloc] init]; 1617 _textTableRows = [[NSMutableArray alloc] init]; 1618 _textTableRowArrays = [[NSMutableArray alloc] init]; 1619 _textTableRowBackgroundColors = [[NSMutableArray alloc] init]; 1620 _computedStylesForElements = [[NSMutableDictionary alloc] init]; 1621 _specifiedStylesForElements = [[NSMutableDictionary alloc] init]; 1622 _stringsForNodes = [[NSMutableDictionary alloc] init]; 1623 _floatsForNodes = [[NSMutableDictionary alloc] init]; 1624 _colorsForNodes = [[NSMutableDictionary alloc] init]; 1625 _attributesForElements = [[NSMutableDictionary alloc] init]; 1626 _elementIsBlockLevel = [[NSMutableDictionary alloc] init]; 1627 _fontCache = [[NSMutableDictionary alloc] init]; 1628 _writingDirectionArray = [[NSMutableArray alloc] init]; 1629 1630 _textSizeMultiplier = 1; 1631 _webViewTextSizeMultiplier = 0; 1632 _defaultTabInterval = 36; 1633 _defaultFontSize = 12; 1634 _minimumFontSize = 1; 1635 _errorCode = -1; 1636 _indexingLimit = 0; 1637 _thumbnailLimit = 0; 1638 1639 _flags.isIndexing = (_indexingLimit > 0); 1640 _flags.isTesting = 0; 1641 1642 return self; 1643} 1644 1645- (id)initWithDOMRange:(DOMRange *)domRange 1646{ 1647 self = [self init]; 1648 if (!self) return nil; 1649 _domRange = [domRange retain]; 1650 return self; 1651} 1652 1653// This function supports more HTML features than the editing variant below, such as tables. 1654- (NSAttributedString *)attributedString 1655{ 1656 [self _loadFromDOMRange]; 1657 return (0 == _errorCode) ? [[_attrStr retain] autorelease] : nil; 1658} 1659 1660#endif // !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 1661 1662// This function uses TextIterator, which makes offsets in its result compatible with HTML editing. 1663+ (NSAttributedString *)editingAttributedStringFromRange:(Range*)range 1664{ 1665 NSMutableAttributedString *string = [[NSMutableAttributedString alloc] init]; 1666 NSUInteger stringLength = 0; 1667 RetainPtr<NSMutableDictionary> attrs(AdoptNS, [[NSMutableDictionary alloc] init]); 1668 1669 for (TextIterator it(range); !it.atEnd(); it.advance()) { 1670 RefPtr<Range> currentTextRange = it.range(); 1671 ExceptionCode ec = 0; 1672 Node* startContainer = currentTextRange->startContainer(ec); 1673 Node* endContainer = currentTextRange->endContainer(ec); 1674 int startOffset = currentTextRange->startOffset(ec); 1675 int endOffset = currentTextRange->endOffset(ec); 1676 1677 if (startContainer == endContainer && (startOffset == endOffset - 1)) { 1678 Node* node = startContainer->childNode(startOffset); 1679 if (node && node->hasTagName(imgTag)) { 1680 NSFileWrapper *fileWrapper = fileWrapperForElement(static_cast<Element*>(node)); 1681 NSTextAttachment *attachment = [[NSTextAttachment alloc] initWithFileWrapper:fileWrapper]; 1682 [string appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]]; 1683 [attachment release]; 1684 } 1685 } 1686 1687 int currentTextLength = it.length(); 1688 if (!currentTextLength) 1689 continue; 1690 1691 RenderObject* renderer = startContainer->renderer(); 1692 ASSERT(renderer); 1693 if (!renderer) 1694 continue; 1695 RenderStyle* style = renderer->style(); 1696 NSFont *font = style->font().primaryFont()->getNSFont(); 1697 [attrs.get() setObject:font forKey:NSFontAttributeName]; 1698 if (style->visitedDependentColor(CSSPropertyColor).alpha()) 1699 [attrs.get() setObject:nsColor(style->visitedDependentColor(CSSPropertyColor)) forKey:NSForegroundColorAttributeName]; 1700 else 1701 [attrs.get() removeObjectForKey:NSForegroundColorAttributeName]; 1702 if (style->visitedDependentColor(CSSPropertyBackgroundColor).alpha()) 1703 [attrs.get() setObject:nsColor(style->visitedDependentColor(CSSPropertyBackgroundColor)) forKey:NSBackgroundColorAttributeName]; 1704 else 1705 [attrs.get() removeObjectForKey:NSBackgroundColorAttributeName]; 1706 1707 RetainPtr<NSString> substring(AdoptNS, [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(it.characters()) length:currentTextLength freeWhenDone:NO]); 1708 [string replaceCharactersInRange:NSMakeRange(stringLength, 0) withString:substring.get()]; 1709 [string setAttributes:attrs.get() range:NSMakeRange(stringLength, currentTextLength)]; 1710 stringLength += currentTextLength; 1711 } 1712 1713 return [string autorelease]; 1714} 1715 1716@end 1717 1718static NSFileWrapper *fileWrapperForURL(DocumentLoader *dataSource, NSURL *URL) 1719{ 1720 if ([URL isFileURL]) { 1721 NSString *path = [[URL path] stringByResolvingSymlinksInPath]; 1722 return [[[NSFileWrapper alloc] initWithPath:path] autorelease]; 1723 } 1724 1725 RefPtr<ArchiveResource> resource = dataSource->subresource(URL); 1726 if (resource) { 1727 NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:[resource->data()->createNSData() autorelease]] autorelease]; 1728 NSString *filename = resource->response().suggestedFilename(); 1729 if (!filename || ![filename length]) 1730 filename = suggestedFilenameWithMIMEType(resource->url(), resource->mimeType()); 1731 [wrapper setPreferredFilename:filename]; 1732 return wrapper; 1733 } 1734 1735 NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL]; 1736 1737 NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request]; 1738 [request release]; 1739 1740 if (cachedResponse) { 1741 NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:[cachedResponse data]] autorelease]; 1742 [wrapper setPreferredFilename:[[cachedResponse response] suggestedFilename]]; 1743 return wrapper; 1744 } 1745 1746 return nil; 1747} 1748 1749static NSFileWrapper *fileWrapperForElement(Element* element) 1750{ 1751 NSFileWrapper *wrapper = nil; 1752 1753 const AtomicString& attr = element->getAttribute(srcAttr); 1754 if (!attr.isEmpty()) { 1755 NSURL *URL = element->document()->completeURL(attr); 1756 if (DocumentLoader* loader = element->document()->loader()) 1757 wrapper = fileWrapperForURL(loader, URL); 1758 } 1759 if (!wrapper) { 1760 RenderImage* renderer = toRenderImage(element->renderer()); 1761 if (renderer->cachedImage() && !renderer->cachedImage()->errorOccurred()) { 1762 wrapper = [[NSFileWrapper alloc] initRegularFileWithContents:(NSData *)(renderer->cachedImage()->image()->getTIFFRepresentation())]; 1763 [wrapper setPreferredFilename:@"image.tiff"]; 1764 [wrapper autorelease]; 1765 } 1766 } 1767 1768 return wrapper; 1769} 1770