Home | History | Annotate | Download | only in mac
      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 
     47 using namespace WebCore;
     48 using namespace HTMLNames;
     49 
     50 static NSFileWrapper *fileWrapperForURL(DocumentLoader *, NSURL *);
     51 static NSFileWrapper *fileWrapperForElement(Element*);
     52 
     53 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
     54 
     55 // Additional control Unicode characters
     56 const 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
     83 static 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 
    105 static 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 
    354 static 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 
    447 static 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 
    472 static 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 
    920 static 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 
    934 static 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 
    953 static 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 
   1718 static 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 
   1749 static 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