2 * Copyright (C) 2011 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #import "HTMLConverter.h"
29 #import "ArchiveResource.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"
41 #import "HTMLParserIdioms.h"
42 #import "LoaderNSURLExtras.h"
43 #import "RenderImage.h"
44 #import "TextIterator.h"
45 #import <wtf/ASCIICType.h>
47 using namespace WebCore;
48 using namespace HTMLNames;
50 static NSFileWrapper *fileWrapperForURL(DocumentLoader *, NSURL *);
51 static NSFileWrapper *fileWrapperForElement(Element*);
53 #ifndef BUILDING_ON_LEOPARD
55 // Additional control Unicode characters
56 const unichar WebNextLineCharacter = 0x0085;
58 @interface NSTextList (WebCoreNSTextListDetails)
59 + (NSDictionary *)_standardMarkerAttributesForAttributes:(NSDictionary *)attrs;
62 @interface NSTextAttachment (NSIgnoreOrientation)
63 - (void)setIgnoresOrientation:(BOOL)flag;
64 - (BOOL)ignoresOrientation;
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;
72 @interface WebHTMLConverter(WebHTMLConverterInternal)
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;
82 // Returns the font to be used if the NSFontAttributeName doesn't exist
83 static NSFont *WebDefaultFont()
85 static NSFont *defaultFont = nil;
90 NSFont *font = [NSFont fontWithName:@"Helvetica" size:12];
92 font = [NSFont systemFontOfSize:12];
94 defaultFont = [font retain];
101 @implementation WebHTMLConverter
103 #ifndef BUILDING_ON_LEOPARD
105 static NSFont *_fontForNameAndSize(NSString *fontName, CGFloat size, NSMutableDictionary *cache)
107 NSFontManager *fontManager = [NSFontManager sharedFontManager];
108 NSFont *font = [cache objectForKey:fontName];
111 font = [fontManager convertFont:font toSize:size];
114 font = [fontManager fontWithFamily:fontName traits:0 weight:0 size:size];
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];
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];
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);
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];
159 + (NSParagraphStyle *)defaultParagraphStyle
161 static NSMutableParagraphStyle *defaultParagraphStyle = nil;
162 if (!defaultParagraphStyle) {
163 defaultParagraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
164 [defaultParagraphStyle setDefaultTabInterval:36];
165 [defaultParagraphStyle setTabStops:[NSArray array]];
167 return defaultParagraphStyle;
170 - (NSArray *)_childrenForNode:(DOMNode *)node
172 NSMutableArray *array = [NSMutableArray array];
173 DOMNode *child = [node firstChild];
175 [array addObject:child];
176 child = [child nextSibling];
181 - (DOMCSSStyleDeclaration *)_computedStyleForElement:(DOMElement *)element
183 DOMDocument *document = [element ownerDocument];
184 DOMCSSStyleDeclaration *result = nil;
185 result = [_computedStylesForElements objectForKey:element];
187 if ([[NSNull null] isEqual:result]) result = nil;
189 result = [document getComputedStyle:element pseudoElement:@""] ;
190 [_computedStylesForElements setObject:(result ? (id)result : (id)[NSNull null]) forKey:element];
195 - (DOMCSSStyleDeclaration *)_specifiedStyleForElement:(DOMElement *)element
197 DOMCSSStyleDeclaration *result = [_specifiedStylesForElements objectForKey:element];
199 if ([[NSNull null] isEqual:result]) result = nil;
201 result = [element style];
202 [_specifiedStylesForElements setObject:(result ? (id)result : (id)[NSNull null]) forKey:element];
207 - (NSString *)_computedStringForNode:(DOMNode *)node property:(NSString *)key
209 NSString *result = nil;
211 DOMElement *element = (DOMElement *)node;
212 if (element && [element nodeType] == DOM_ELEMENT_NODE) {
213 DOMCSSStyleDeclaration *computedStyle, *specifiedStyle;
215 if (!result && (computedStyle = [self _computedStyleForElement:element])) {
216 DOMCSSPrimitiveValue *computedValue = (DOMCSSPrimitiveValue *)[computedStyle getPropertyCSSValue:key];
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;
225 } else if (valueType == DOM_CSS_VALUE_LIST) {
226 result = [computedStyle getPropertyValue:key];
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;
241 result = [specifiedStyle getPropertyValue:key];
244 } else if (valueType == DOM_CSS_INHERIT) {
246 } else if (valueType == DOM_CSS_VALUE_LIST) {
247 result = [specifiedStyle getPropertyValue:key];
252 Element* coreElement = core(element);
253 if ([@"display" isEqualToString:key]) {
254 if (coreElement->hasTagName(headTag) || coreElement->hasTagName(scriptTag) || coreElement->hasTagName(appletTag) || coreElement->hasTagName(noframesTag))
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))
264 else if (coreElement->hasTagName(liTag))
265 result = @"list-item";
266 else if (coreElement->hasTagName(tableTag))
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))
289 } else if ([@"font-style" isEqualToString:key]) {
290 if (coreElement->hasTagName(iTag) || coreElement->hasTagName(citeTag) || coreElement->hasTagName(emTag) || coreElement->hasTagName(varTag) || coreElement->hasTagName(addressTag))
294 } else if ([@"font-weight" isEqualToString:key]) {
295 if (coreElement->hasTagName(bTag) || coreElement->hasTagName(strongTag) || coreElement->hasTagName(thTag))
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";
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))
311 } else if ([@"vertical-align" isEqualToString:key]) {
312 if (coreElement->hasTagName(supTag))
314 else if (coreElement->hasTagName(subTag))
316 else if (coreElement->hasTagName(theadTag) || coreElement->hasTagName(tbodyTag) || coreElement->hasTagName(tfootTag))
318 else if (coreElement->hasTagName(trTag) || coreElement->hasTagName(thTag) || coreElement->hasTagName(tdTag))
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]) {
328 if (!result && inherit) {
329 DOMNode *parentNode = [node parentNode];
330 if (parentNode) result = [self _stringForNode:parentNode property:key];
332 return result ? [result lowercaseString] : nil;
335 - (NSString *)_stringForNode:(DOMNode *)node property:(NSString *)key
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];
344 result = [attributeDictionary objectForKey:key];
346 if ([@"" isEqualToString:result]) result = nil;
348 result = [self _computedStringForNode:node property:key];
349 [attributeDictionary setObject:(result ? result : @"") forKey:key];
354 static inline BOOL _getFloat(DOMCSSPrimitiveValue *primitiveValue, CGFloat *val)
358 switch ([primitiveValue primitiveType]) {
360 *val = [primitiveValue getFloatValue:DOM_CSS_PX];
363 *val = 4 * [primitiveValue getFloatValue:DOM_CSS_PT] / 3;
366 *val = 16 * [primitiveValue getFloatValue:DOM_CSS_PC];
369 *val = 96 * [primitiveValue getFloatValue:DOM_CSS_CM] / (CGFloat)2.54;
372 *val = 96 * [primitiveValue getFloatValue:DOM_CSS_MM] / (CGFloat)25.4;
375 *val = 96 * [primitiveValue getFloatValue:DOM_CSS_IN];
382 - (BOOL)_getComputedFloat:(CGFloat *)val forNode:(DOMNode *)node property:(NSString *)key
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;
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);
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) {
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])
413 if (!result && inherit) {
414 DOMNode *parentNode = [node parentNode];
415 if (parentNode) result = [self _getFloat:&floatVal forNode:parentNode property:key];
422 - (BOOL)_getFloat:(CGFloat *)val forNode:(DOMNode *)node property:(NSString *)key
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];
433 floatNumber = [attributeDictionary objectForKey:key];
435 if (![[NSNull null] isEqual:floatNumber]) {
437 floatVal = [floatNumber floatValue];
440 result = [self _getComputedFloat:&floatVal forNode:node property:key];
441 [attributeDictionary setObject:(result ? (id)[NSNumber numberWithDouble:floatVal] : (id)[NSNull null]) forKey:key];
443 if (result && val) *val = floatVal;
447 static inline NSColor *_colorForRGBColor(DOMRGBColor *domRGBColor, BOOL ignoreBlack)
449 NSColor *color = [domRGBColor _color];
450 NSColorSpace *colorSpace = [color colorSpace];
451 const CGFloat ColorEpsilon = 1 / (2 * (CGFloat)255.0);
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;
459 NSColor *rgbColor = nil;
460 if ([colorSpace isEqual:[NSColorSpace genericRGBColorSpace]] || [colorSpace isEqual:[NSColorSpace deviceRGBColorSpace]]) rgbColor = color;
461 if (!rgbColor) rgbColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace];
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;
472 static inline NSShadow *_shadowForShadowStyle(NSString *shadowStyle)
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];
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];
507 - (BOOL)_elementIsBlockLevel:(DOMElement *)element
509 BOOL isBlockLevel = NO;
511 val = [_elementIsBlockLevel objectForKey:element];
513 isBlockLevel = [val boolValue];
515 NSString *displayVal = [self _stringForNode:element property:@"display"], *floatVal = [self _stringForNode:element property:@"float"];
516 if (floatVal && ([@"left" isEqualToString:floatVal] || [@"right" isEqualToString:floatVal])) {
518 } else if (displayVal) {
519 isBlockLevel = ([@"block" isEqualToString:displayVal] || [@"list-item" isEqualToString:displayVal] || [displayVal hasPrefix:@"table"]);
521 [_elementIsBlockLevel setObject:[NSNumber numberWithBool:isBlockLevel] forKey:element];
526 - (BOOL)_elementHasOwnBackgroundColor:(DOMElement *)element
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"])
538 - (DOMElement *)_blockLevelElementForNode:(DOMNode *)node
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]];
548 - (NSColor *)_computedColorForNode:(DOMNode *)node property:(NSString *)key
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;
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);
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);
570 } else if (valueType == DOM_CSS_INHERIT) {
576 if ((isColor && !haveResult) || (isBackgroundColor && ![self _elementHasOwnBackgroundColor:element])) inherit = YES;
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];
588 - (NSColor *)_colorForNode:(DOMNode *)node property:(NSString *)key
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];
597 result = [attributeDictionary objectForKey:key];
599 if ([[NSColor clearColor] isEqual:result]) result = nil;
601 result = [self _computedColorForNode:node property:key];
602 [attributeDictionary setObject:(result ? result : [NSColor clearColor]) forKey:key];
607 - (NSDictionary *)_computedAttributesForElement:(DOMElement *)element
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"];
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;
625 if (fontSize <= 0.0) fontSize = 12;
627 if (actualFont) font = [fontManager convertFont:actualFont toSize:fontSize];
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"];
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;
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;
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;
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];
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];
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];
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];
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];
673 if (element != blockElement && [_writingDirectionArray count] > 0) [attrs setObject:[NSArray arrayWithArray:_writingDirectionArray] forKey:NSWritingDirectionAttributeName];
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;
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];
689 if ([direction isEqualToString:@"ltr"]) [paragraphStyle setBaseWritingDirection:NSWritingDirectionLeftToRight];
690 else if ([direction isEqualToString:@"rtl"]) [paragraphStyle setBaseWritingDirection:NSWritingDirectionRightToLeft];
692 if ([blockTag hasPrefix:@"H"] && 2 == [blockTag length]) {
693 NSInteger headerLevel = [blockTag characterAtIndex:1] - '0';
694 if (1 <= headerLevel && headerLevel <= 6) [paragraphStyle setHeaderLevel:headerLevel];
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];
703 if (_webViewTextSizeMultiplier > 0.0 && [self _getFloat:&lineHeight forNode:element property:@"line-height"] && lineHeight > 0.0) {
704 [paragraphStyle setMinimumLineHeight:lineHeight / _webViewTextSizeMultiplier];
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];
714 - (NSDictionary *)_attributesForElement:(DOMElement *)element
716 NSDictionary *result;
718 result = [_attributesForElements objectForKey:element];
720 result = [self _computedAttributesForElement:element];
721 [_attributesForElements setObject:result forKey:element];
724 result = [NSDictionary dictionary];
730 - (void)_newParagraphForElement:(DOMElement *)element tag:(NSString *)tag allowEmpty:(BOOL)flag suppressTrailingSpace:(BOOL)suppress
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');
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];
750 - (void)_newLineForElement:(DOMElement *)element
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];
767 - (void)_newTabForElement:(DOMElement *)element
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];
784 - (BOOL)_addAttachmentForElement:(DOMElement *)element URL:(NSURL *)url needsParagraph:(BOOL)needsParagraph usePlaceholder:(BOOL)flag
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;
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];
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())];
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;
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;
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);
825 if (ignoreOrientation) [attachment setIgnoresOrientation:YES];
827 cell = [[NSTextAttachmentCell alloc] initImageCell:missingImage];
828 [attachment setAttachmentCell:cell];
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];
841 [attachment release];
848 - (void)_addQuoteForElement:(DOMElement *)element opening:(BOOL)opening level:(NSInteger)level
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];
865 - (void)_addValue:(NSString *)value forElement:(DOMElement *)element
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];
881 - (void)_fillInBlock:(NSTextBlock *)block forElement:(DOMElement *)element backgroundColor:(NSColor *)backgroundColor extraMargin:(CGFloat)extraMargin extraPadding:(CGFloat)extraPadding isTable:(BOOL)isTable
884 NSColor *color = nil;
885 BOOL isTableCellElement = [element isKindOfClass:[DOMHTMLTableCellElement class]];
886 NSString *width = isTableCellElement ? [(DOMHTMLTableCellElement *)element width] : [element getAttribute:@"width"];
888 if ((width && [width length] > 0) || !isTable) {
889 if ([self _getFloat:&val forNode:element property:@"width"]) [block setValue:val type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockWidth];
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];
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];
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];
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];
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];
920 static inline BOOL read2DigitNumber(const char **pp, int8_t *outval)
923 char c1 = *(*pp)++, c2;
924 if (isASCIIDigit(c1)) {
926 if (isASCIIDigit(c2)) {
927 *outval = 10 * (c1 - '0') + (c2 - '0');
934 static inline NSDate *_dateForString(NSString *string)
936 CFGregorianDate date;
937 const char *p = [string UTF8String];
939 BOOL wellFormed = YES;
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;
953 static NSInteger _colCompare(id block1, id block2, void *)
955 NSInteger col1 = [(NSTextTableBlock *)block1 startingColumn], col2 = [(NSTextTableBlock *)block2 startingColumn];
956 return ((col1 < col2) ? NSOrderedAscending : ((col1 == col2) ? NSOrderedSame : NSOrderedDescending));
959 - (BOOL)_enterElement:(DOMElement *)element tag:(NSString *)tag display:(NSString *)displayVal
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];
970 - (void)_addTableForElement:(DOMElement *)tableElement
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];
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];
984 if ([tableElement respondsToSelector:@selector(cellPadding)]) {
985 NSString *cellPadding = [(DOMHTMLTableElement *)tableElement cellPadding];
986 if (cellPadding && [cellPadding length] > 0 && ![cellPadding hasSuffix:@"%"]) cellPaddingVal = [cellPadding floatValue];
988 [self _fillInBlock:table forElement:tableElement backgroundColor:nil extraMargin:0 extraPadding:0 isTable:YES];
989 if ([@"collapse" isEqualToString:borderCollapse]) {
990 [table setCollapsesBorders:YES];
993 if ([@"hide" isEqualToString:emptyCells]) [table setHidesEmptyCells:YES];
994 if ([@"fixed" isEqualToString:tableLayout]) [table setLayoutAlgorithm:NSTextTableFixedLayoutAlgorithm];
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]];
1004 - (void)_addTableCellForElement:(DOMElement *)tableCellElement
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];
1018 if (tableCellElement) {
1019 if ([tableCellElement respondsToSelector:@selector(rowSpan)]) {
1020 rowSpan = [(DOMHTMLTableCellElement *)tableCellElement rowSpan];
1021 if (rowSpan < 1) rowSpan = 1;
1023 if ([tableCellElement respondsToSelector:@selector(colSpan)]) {
1024 colSpan = [(DOMHTMLTableCellElement *)tableCellElement colSpan];
1025 if (colSpan < 1) colSpan = 1;
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];
1037 [_textBlocks addObject:block];
1038 [rowArray addObject:block];
1039 [rowArray sortUsingFunction:_colCompare context:NULL];
1043 - (BOOL)_processElement:(DOMElement *)element tag:(NSString *)tag display:(NSString *)displayVal depth:(NSInteger)depth
1045 BOOL retval = YES, isBlockLevel = [self _elementIsBlockLevel:element];
1047 [_writingDirectionArray removeAllObjects];
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]];
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;
1069 while ([_textTables count] > [_textBlocks count]) {
1070 [self _addTableCellForElement:nil];
1072 [self _addTableForElement:tableElement];
1073 } else if ([@"table-footer-group" isEqualToString:displayVal] && [_textTables count] > 0) {
1074 [_textTableFooters setObject:element forKey:[NSValue valueWithNonretainedObject:[_textTables lastObject]]];
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];
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];
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];
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];
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];
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];
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]));
1125 _flags.hasTrailingNewline = YES;
1127 if (blockElement && blockElementIsParagraph) {
1128 [self _newLineForElement:element];
1130 [self _newParagraphForElement:element tag:tag allowEmpty:YES suppressTrailingSpace:NO];
1133 } else if ([@"UL" isEqualToString:tag]) {
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];
1140 } else if ([@"OL" isEqualToString:tag]) {
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];
1149 [_textLists addObject:list];
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];
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];
1168 - (void)_addMarkersToList:(NSTextList *)list range:(NSRange)range
1170 NSInteger itemNum = [list startingItemNumber];
1171 NSString *string = [_attrStr string], *stringToInsert;
1172 NSDictionary *attrsToInsert = nil;
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;
1180 CGFloat markerLocation, listLocation, pointSize;
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;
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;
1214 if (tabToRemove) [newStyle removeTabStop:tab]; else break;
1216 tab = [[NSTextTab alloc] initWithType:NSLeftTabStopType location:markerLocation];
1217 [newStyle addTabStop:tab];
1219 tab = [[NSTextTab alloc] initWithTextAlignment:NSNaturalTextAlignment location:listLocation options:nil];
1220 [newStyle addTabStop:tab];
1222 if (!_flags.isIndexing && !_flags.isTesting) [_attrStr addAttribute:NSParagraphStyleAttributeName value:newStyle range:paragraphRange];
1225 idx = NSMaxRange(paragraphRange);
1227 // skip any deeper-nested lists
1228 idx = NSMaxRange(styleRange);
1235 - (void)_exitElement:(DOMElement *)element tag:(NSString *)tag display:(NSString *)displayVal depth:(NSInteger)depth startIndex:(NSUInteger)startIndex
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];
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];
1254 [self _newParagraphForElement:element tag:tag allowEmpty:(range.length == 0) suppressTrailingSpace:YES];
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];
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];
1270 [self _traverseFooterNode:footer depth:depth + 1];
1271 [_textTableFooters removeObjectForKey:key];
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];
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];
1296 count = [rowArray count];
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];
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];
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;
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:@" "];
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) {
1342 if (count >= 4 || i + 1 >= NSMaxRange(range)) rangeToReplace = NSMakeRange(i + 1 - count, count);
1344 if (count > 0) rangeToReplace = NSMakeRange(i - count, count);
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;
1362 - (void)_processText:(DOMCharacterData *)text
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"];
1374 if (text == [_domRange startContainer]) {
1375 startOffset = (NSUInteger)[_domRange startOffset];
1376 _domRangeStartIndex = [_attrStr length];
1377 _flags.reachedStart = YES;
1379 if (text == [_domRange endContainer]) {
1380 endOffset = (NSUInteger)[_domRange endOffset];
1381 _flags.reachedEnd = YES;
1383 if ((startOffset > 0 || endOffset < [instr length]) && endOffset >= startOffset) {
1384 instr = [instr substringWithRange:NSMakeRange(startOffset, endOffset - startOffset)];
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);
1394 CFStringInlineBuffer inlineBuffer;
1395 const unsigned int TextBufferSize = 255;
1397 unichar buffer[TextBufferSize + 1];
1398 NSUInteger i, count = [instr length], idx = 0;
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);
1407 if (wasSpace) buffer[idx++] = ' ';
1409 if (idx >= TextBufferSize) {
1410 CFStringAppendCharacters(mutstr, buffer, idx);
1413 wasSpace = wasLeading = NO;
1416 if (wasSpace) buffer[idx++] = ' ';
1417 if (idx > 0) CFStringAppendCharacters(mutstr, buffer, idx);
1418 outstr = (NSString *)mutstr;
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];
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];
1437 _flags.isSoft = wasSpace;
1439 if (mutstr) CFRelease(mutstr);
1442 - (void)_traverseNode:(DOMNode *)node depth:(NSInteger)depth embedded:(BOOL)embedded
1444 unsigned short nodeType;
1445 NSArray *childNodes;
1446 NSUInteger i, count, startOffset, endOffset;
1447 BOOL isStart = NO, isEnd = NO;
1449 if (_flags.reachedEnd) return;
1450 if (_domRange && !_flags.reachedStart && _domStartAncestors && ![_domStartAncestors containsObject:node]) return;
1452 nodeType = [node nodeType];
1453 childNodes = [self _childrenForNode:node];
1454 count = [childNodes count];
1459 if (node == [_domRange startContainer]) {
1460 startOffset = (NSUInteger)[_domRange startOffset];
1462 _flags.reachedStart = YES;
1464 if (node == [_domRange endContainer]) {
1465 endOffset = (NSUInteger)[_domRange endOffset];
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;
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])) {
1484 } else if (displayVal) {
1485 isBlockLevel = ([@"block" isEqualToString:displayVal] || [@"list-item" isEqualToString:displayVal] || [displayVal hasPrefix:@"table"]);
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;
1497 [self _exitElement:element tag:tag display:displayVal depth:depth startIndex:startIndex];
1500 } else if (nodeType == DOM_TEXT_NODE || nodeType == DOM_CDATA_SECTION_NODE) {
1501 [self _processText:(DOMCharacterData *)node];
1504 if (isEnd) _flags.reachedEnd = YES;
1507 - (void)_traverseFooterNode:(DOMNode *)node depth:(NSInteger)depth
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;
1515 if (_flags.reachedEnd) return;
1516 if (_domRange && !_flags.reachedStart && _domStartAncestors && ![_domStartAncestors containsObject:node]) return;
1518 if (node == [_domRange startContainer]) {
1519 startOffset = (NSUInteger)[_domRange startOffset];
1521 _flags.reachedStart = YES;
1523 if (node == [_domRange endContainer]) {
1524 endOffset = (NSUInteger)[_domRange endOffset];
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;
1537 [self _exitElement:element tag:tag display:displayVal depth:depth startIndex:startIndex];
1540 if (isEnd) _flags.reachedEnd = YES;
1543 - (void)_adjustTrailingNewline
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"];
1552 - (void)_loadFromDOMRange
1554 if (-1 == _errorCode) {
1555 DOMNode *commonAncestorContainer = [_domRange commonAncestorContainer], *ancestorContainer = [_domRange startContainer];
1557 _domStartAncestors = [[NSMutableArray alloc] init];
1558 while (ancestorContainer) {
1559 [_domStartAncestors addObject:ancestorContainer];
1560 if (ancestorContainer == commonAncestorContainer) break;
1561 ancestorContainer = [ancestorContainer parentNode];
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;
1571 [self _traverseNode:commonAncestorContainer depth:0 embedded:NO];
1572 if (_domRangeStartIndex > 0 && _domRangeStartIndex <= [_attrStr length]) [_attrStr deleteCharactersInRange:NSMakeRange(0, _domRangeStartIndex)];
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];
1606 self = [super init];
1607 if (!self) return nil;
1609 _attrStr = [[NSMutableAttributedString alloc] init];
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];
1630 _textSizeMultiplier = 1;
1631 _webViewTextSizeMultiplier = 0;
1632 _defaultTabInterval = 36;
1633 _defaultFontSize = 12;
1634 _minimumFontSize = 1;
1637 _thumbnailLimit = 0;
1639 _flags.isIndexing = (_indexingLimit > 0);
1640 _flags.isTesting = 0;
1645 - (id)initWithDOMRange:(DOMRange *)domRange
1648 if (!self) return nil;
1649 _domRange = [domRange retain];
1653 // This function supports more HTML features than the editing variant below, such as tables.
1654 - (NSAttributedString *)attributedString
1656 [self _loadFromDOMRange];
1657 return (0 == _errorCode) ? [[_attrStr retain] autorelease] : nil;
1660 #endif // !defined(BUILDING_ON_LEOPARD)
1662 // This function uses TextIterator, which makes offsets in its result compatible with HTML editing.
1663 + (NSAttributedString *)editingAttributedStringFromRange:(Range*)range
1665 NSMutableAttributedString *string = [[NSMutableAttributedString alloc] init];
1666 NSUInteger stringLength = 0;
1667 RetainPtr<NSMutableDictionary> attrs(AdoptNS, [[NSMutableDictionary alloc] init]);
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);
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];
1687 int currentTextLength = it.length();
1688 if (!currentTextLength)
1691 RenderObject* renderer = startContainer->renderer();
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];
1701 [attrs.get() removeObjectForKey:NSForegroundColorAttributeName];
1702 if (style->visitedDependentColor(CSSPropertyBackgroundColor).alpha())
1703 [attrs.get() setObject:nsColor(style->visitedDependentColor(CSSPropertyBackgroundColor)) forKey:NSBackgroundColorAttributeName];
1705 [attrs.get() removeObjectForKey:NSBackgroundColorAttributeName];
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;
1713 return [string autorelease];
1718 static NSFileWrapper *fileWrapperForURL(DocumentLoader *dataSource, NSURL *URL)
1720 if ([URL isFileURL]) {
1721 NSString *path = [[URL path] stringByResolvingSymlinksInPath];
1722 return [[[NSFileWrapper alloc] initWithPath:path] autorelease];
1725 RefPtr<ArchiveResource> resource = dataSource->subresource(URL);
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];
1735 NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL];
1737 NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
1740 if (cachedResponse) {
1741 NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:[cachedResponse data]] autorelease];
1742 [wrapper setPreferredFilename:[[cachedResponse response] suggestedFilename]];
1749 static NSFileWrapper *fileWrapperForElement(Element* element)
1751 NSFileWrapper *wrapper = nil;
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);
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];