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