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