1/* 2 * Copyright (C) 2011, 2012 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 "CSSComputedStyleDeclaration.h" 31#import "CSSParser.h" 32#import "CSSPrimitiveValue.h" 33#import "CachedImage.h" 34#import "CharacterData.h" 35#import "ColorMac.h" 36#import "Document.h" 37#import "DocumentLoader.h" 38#import "Element.h" 39#import "ElementTraversal.h" 40#import "Font.h" 41#import "Frame.h" 42#import "FrameLoader.h" 43#import "HTMLElement.h" 44#import "HTMLFrameElementBase.h" 45#import "HTMLInputElement.h" 46#import "HTMLMetaElement.h" 47#import "HTMLNames.h" 48#import "HTMLOListElement.h" 49#import "HTMLParserIdioms.h" 50#import "HTMLTableCellElement.h" 51#import "HTMLTextAreaElement.h" 52#import "LoaderNSURLExtras.h" 53#import "RGBColor.h" 54#import "RenderImage.h" 55#import "SoftLinking.h" 56#import "StyleProperties.h" 57#import "StyledElement.h" 58#import "TextIterator.h" 59#import <objc/runtime.h> 60#import <wtf/ASCIICType.h> 61#import <wtf/text/StringBuilder.h> 62 63#if PLATFORM(IOS) 64 65SOFT_LINK_FRAMEWORK(UIKit) 66SOFT_LINK_CLASS(UIKit, UIColor) 67 68SOFT_LINK_PRIVATE_FRAMEWORK(UIFoundation) 69SOFT_LINK_CLASS(UIFoundation, UIFont) 70SOFT_LINK_CLASS(UIFoundation, NSColor) 71SOFT_LINK_CLASS(UIFoundation, NSShadow) 72SOFT_LINK_CLASS(UIFoundation, NSTextAttachment) 73SOFT_LINK_CLASS(UIFoundation, NSMutableParagraphStyle) 74SOFT_LINK_CLASS(UIFoundation, NSParagraphStyle) 75SOFT_LINK_CLASS(UIFoundation, NSTextList) 76SOFT_LINK_CLASS(UIFoundation, NSTextBlock) 77SOFT_LINK_CLASS(UIFoundation, NSTextTableBlock) 78SOFT_LINK_CLASS(UIFoundation, NSTextTable) 79SOFT_LINK_CLASS(UIFoundation, NSTextTab) 80 81SOFT_LINK_CONSTANT(UIFoundation, NSFontAttributeName, NSString *) 82#define NSFontAttributeName getNSFontAttributeName() 83SOFT_LINK_CONSTANT(UIFoundation, NSForegroundColorAttributeName, NSString *) 84#define NSForegroundColorAttributeName getNSForegroundColorAttributeName() 85SOFT_LINK_CONSTANT(UIFoundation, NSBackgroundColorAttributeName, NSString *) 86#define NSBackgroundColorAttributeName getNSBackgroundColorAttributeName() 87SOFT_LINK_CONSTANT(UIFoundation, NSStrokeColorAttributeName, NSString *) 88#define NSStrokeColorAttributeName getNSStrokeColorAttributeName() 89SOFT_LINK_CONSTANT(UIFoundation, NSStrokeWidthAttributeName, NSString *) 90#define NSStrokeWidthAttributeName getNSStrokeWidthAttributeName() 91SOFT_LINK_CONSTANT(UIFoundation, NSShadowAttributeName, NSString *) 92#define NSShadowAttributeName getNSShadowAttributeName() 93SOFT_LINK_CONSTANT(UIFoundation, NSKernAttributeName, NSString *) 94#define NSKernAttributeName getNSKernAttributeName() 95SOFT_LINK_CONSTANT(UIFoundation, NSLigatureAttributeName, NSString *) 96#define NSLigatureAttributeName getNSLigatureAttributeName() 97SOFT_LINK_CONSTANT(UIFoundation, NSUnderlineStyleAttributeName, NSString *) 98#define NSUnderlineStyleAttributeName getNSUnderlineStyleAttributeName() 99SOFT_LINK_CONSTANT(UIFoundation, NSStrikethroughStyleAttributeName, NSString *) 100#define NSStrikethroughStyleAttributeName getNSStrikethroughStyleAttributeName() 101SOFT_LINK_CONSTANT(UIFoundation, NSBaselineOffsetAttributeName, NSString *) 102#define NSBaselineOffsetAttributeName getNSBaselineOffsetAttributeName() 103SOFT_LINK_CONSTANT(UIFoundation, NSWritingDirectionAttributeName, NSString *) 104#define NSWritingDirectionAttributeName getNSWritingDirectionAttributeName() 105SOFT_LINK_CONSTANT(UIFoundation, NSParagraphStyleAttributeName, NSString *) 106#define NSParagraphStyleAttributeName getNSParagraphStyleAttributeName() 107SOFT_LINK_CONSTANT(UIFoundation, NSAttachmentAttributeName, NSString *) 108#define NSAttachmentAttributeName getNSAttachmentAttributeName() 109SOFT_LINK_CONSTANT(UIFoundation, NSLinkAttributeName, NSString *) 110#define NSLinkAttributeName getNSLinkAttributeName() 111SOFT_LINK_CONSTANT(UIFoundation, NSAuthorDocumentAttribute, NSString *) 112#define NSAuthorDocumentAttribute getNSAuthorDocumentAttribute() 113SOFT_LINK_CONSTANT(UIFoundation, NSEditorDocumentAttribute, NSString *) 114#define NSEditorDocumentAttribute getNSEditorDocumentAttribute() 115SOFT_LINK_CONSTANT(UIFoundation, NSGeneratorDocumentAttribute, NSString *) 116#define NSGeneratorDocumentAttribute getNSGeneratorDocumentAttribute() 117SOFT_LINK_CONSTANT(UIFoundation, NSCompanyDocumentAttribute, NSString *) 118#define NSCompanyDocumentAttribute getNSCompanyDocumentAttribute() 119SOFT_LINK_CONSTANT(UIFoundation, NSDisplayNameDocumentAttribute, NSString *) 120#define NSDisplayNameDocumentAttribute getNSDisplayNameDocumentAttribute() 121SOFT_LINK_CONSTANT(UIFoundation, NSCopyrightDocumentAttribute, NSString *) 122#define NSCopyrightDocumentAttribute getNSCopyrightDocumentAttribute() 123SOFT_LINK_CONSTANT(UIFoundation, NSSubjectDocumentAttribute, NSString *) 124#define NSSubjectDocumentAttribute getNSSubjectDocumentAttribute() 125SOFT_LINK_CONSTANT(UIFoundation, NSCommentDocumentAttribute, NSString *) 126#define NSCommentDocumentAttribute getNSCommentDocumentAttribute() 127SOFT_LINK_CONSTANT(UIFoundation, NSNoIndexDocumentAttribute, NSString *) 128#define NSNoIndexDocumentAttribute getNSNoIndexDocumentAttribute() 129SOFT_LINK_CONSTANT(UIFoundation, NSKeywordsDocumentAttribute, NSString *) 130#define NSKeywordsDocumentAttribute getNSKeywordsDocumentAttribute() 131SOFT_LINK_CONSTANT(UIFoundation, NSCreationTimeDocumentAttribute, NSString *) 132#define NSCreationTimeDocumentAttribute getNSCreationTimeDocumentAttribute() 133SOFT_LINK_CONSTANT(UIFoundation, NSModificationTimeDocumentAttribute, NSString *) 134#define NSModificationTimeDocumentAttribute getNSModificationTimeDocumentAttribute() 135SOFT_LINK_CONSTANT(UIFoundation, NSConvertedDocumentAttribute, NSString *) 136#define NSConvertedDocumentAttribute getNSConvertedDocumentAttribute() 137SOFT_LINK_CONSTANT(UIFoundation, NSCocoaVersionDocumentAttribute, NSString *) 138#define NSCocoaVersionDocumentAttribute getNSCocoaVersionDocumentAttribute() 139 140#define PlatformNSShadow getNSShadowClass() 141#define PlatformNSTextAttachment getNSTextAttachmentClass() 142#define PlatformNSParagraphStyle getNSParagraphStyleClass() 143#define PlatformNSTextList getNSTextListClass() 144#define PlatformNSTextTableBlock getNSTextTableBlockClass() 145#define PlatformNSTextTable getNSTextTableClass() 146#define PlatformNSTextTab getNSTextTabClass() 147#define PlatformColor UIColor 148#define PlatformColorClass getUIColorClass() 149#define PlatformNSColorClass getNSColorClass() 150#define PlatformFont UIFont 151#define PlatformFontClass getUIFontClass() 152 153// We don't softlink NSSuperscriptAttributeName because UIFoundation stopped exporting it. 154// This attribute is being deprecated at the API level, but internally UIFoundation 155// will continue to support it. 156static NSString *const NSSuperscriptAttributeName = @"NSSuperscript"; 157#else 158 159#define PlatformNSShadow NSShadow 160#define PlatformNSTextAttachment NSTextAttachment 161#define PlatformNSParagraphStyle NSParagraphStyle 162#define PlatformNSTextList NSTextList 163#define PlatformNSTextTableBlock NSTextTableBlock 164#define PlatformNSTextTable NSTextTable 165#define PlatformNSTextTab NSTextTab 166#define PlatformColor NSColor 167#define PlatformColorClass NSColor 168#define PlatformNSColorClass NSColor 169#define PlatformFont NSFont 170#define PlatformFontClass NSFont 171 172#define NSTextAlignmentLeft NSLeftTextAlignment 173#define NSTextAlignmentRight NSRightTextAlignment 174#define NSTextAlignmentCenter NSCenterTextAlignment 175#define NSTextAlignmentJustified NSJustifiedTextAlignment 176#endif 177 178using namespace WebCore; 179using namespace HTMLNames; 180 181#if PLATFORM(IOS) 182 183typedef enum { 184 UIFontTraitPlain = 0x00000000, 185 UIFontTraitItalic = 0x00000001, // 1 << 0 186 UIFontTraitBold = 0x00000002, // 1 << 1 187 UIFontTraitThin = (1 << 2), 188 UIFontTraitLight = (1 << 3), 189 UIFontTraitUltraLight = (1 << 4) 190} UIFontTrait; 191 192typedef NS_ENUM(NSInteger, NSUnderlineStyle) { 193 NSUnderlineStyleNone = 0x00, 194 NSUnderlineStyleSingle = 0x01, 195 NSUnderlineStyleThick NS_ENUM_AVAILABLE_IOS(7_0) = 0x02, 196 NSUnderlineStyleDouble NS_ENUM_AVAILABLE_IOS(7_0) = 0x09, 197 198 NSUnderlinePatternSolid NS_ENUM_AVAILABLE_IOS(7_0) = 0x0000, 199 NSUnderlinePatternDot NS_ENUM_AVAILABLE_IOS(7_0) = 0x0100, 200 NSUnderlinePatternDash NS_ENUM_AVAILABLE_IOS(7_0) = 0x0200, 201 NSUnderlinePatternDashDot NS_ENUM_AVAILABLE_IOS(7_0) = 0x0300, 202 NSUnderlinePatternDashDotDot NS_ENUM_AVAILABLE_IOS(7_0) = 0x0400, 203 204 NSUnderlineByWord NS_ENUM_AVAILABLE_IOS(7_0) = 0x8000 205}; 206 207enum { 208 NSTextBlockAbsoluteValueType = 0, // Absolute value in points 209 NSTextBlockPercentageValueType = 1 // Percentage value (out of 100) 210}; 211typedef NSUInteger NSTextBlockValueType; 212 213enum { 214 NSTextBlockWidth = 0, 215 NSTextBlockMinimumWidth = 1, 216 NSTextBlockMaximumWidth = 2, 217 NSTextBlockHeight = 4, 218 NSTextBlockMinimumHeight = 5, 219 NSTextBlockMaximumHeight = 6 220}; 221typedef NSUInteger NSTextBlockDimension; 222 223enum { 224 NSTextBlockPadding = -1, 225 NSTextBlockBorder = 0, 226 NSTextBlockMargin = 1 227}; 228typedef NSInteger NSTextBlockLayer; 229 230enum { 231 NSTextTableAutomaticLayoutAlgorithm = 0, 232 NSTextTableFixedLayoutAlgorithm = 1 233}; 234typedef NSUInteger NSTextTableLayoutAlgorithm; 235 236enum { 237 NSTextBlockTopAlignment = 0, 238 NSTextBlockMiddleAlignment = 1, 239 NSTextBlockBottomAlignment = 2, 240 NSTextBlockBaselineAlignment = 3 241}; 242typedef NSUInteger NSTextBlockVerticalAlignment; 243 244typedef NS_ENUM(NSInteger, NSTextAlignment) { 245 NSTextAlignmentLeft = 0, // Visually left aligned 246 NSTextAlignmentCenter = 1, // Visually centered 247 NSTextAlignmentRight = 2, // Visually right aligned 248 NSTextAlignmentJustified = 3, // Fully-justified. The last line in a paragraph is natural-aligned. 249 NSTextAlignmentNatural = 4, // Indicates the default alignment for script 250} NS_ENUM_AVAILABLE_IOS(6_0); 251 252typedef NS_ENUM(NSInteger, NSWritingDirection) { 253 NSWritingDirectionNatural = -1, // Determines direction using the Unicode Bidi Algorithm rules P2 and P3 254 NSWritingDirectionLeftToRight = 0, // Left to right writing direction 255 NSWritingDirectionRightToLeft = 1 // Right to left writing direction 256} NS_ENUM_AVAILABLE_IOS(6_0); 257 258typedef NS_ENUM(NSInteger, NSTextWritingDirection) { 259 NSTextWritingDirectionEmbedding = (0 << 1), 260 NSTextWritingDirectionOverride = (1 << 1) 261} NS_ENUM_AVAILABLE_IOS(7_0); 262 263enum { 264 NSEnterCharacter = 0x0003, 265 NSBackspaceCharacter = 0x0008, 266 NSTabCharacter = 0x0009, 267 NSNewlineCharacter = 0x000a, 268 NSFormFeedCharacter = 0x000c, 269 NSCarriageReturnCharacter = 0x000d, 270 NSBackTabCharacter = 0x0019, 271 NSDeleteCharacter = 0x007f, 272 NSLineSeparatorCharacter = 0x2028, 273 NSParagraphSeparatorCharacter = 0x2029, 274 NSAttachmentCharacter = 0xFFFC // Replacement character is used for attachments 275}; 276 277enum { 278 NSLeftTabStopType = 0, 279 NSRightTabStopType, 280 NSCenterTabStopType, 281 NSDecimalTabStopType 282}; 283typedef NSUInteger NSTextTabType; 284 285@interface UIColor : NSObject 286+ (UIColor *)clearColor; 287- (CGFloat)alphaComponent; 288+ (UIColor *)_disambiguated_due_to_CIImage_colorWithCGColor:(CGColorRef)cgColor; 289@end 290 291@interface NSColor : UIColor 292+ (id)colorWithCalibratedRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha; 293@end 294 295@interface UIFont 296+ (UIFont *)fontWithName:(NSString *)fontName size:(CGFloat)fontSize; 297+ (UIFont *)fontWithFamilyName:(NSString *)familyName traits:(UIFontTrait)traits size:(CGFloat)fontSize; 298- (NSString *)familyName; 299- (CGFloat)pointSize; 300- (UIFont *)fontWithSize:(CGFloat)fontSize; 301+ (NSArray *)familyNames; 302+ (NSArray *)fontNamesForFamilyName:(NSString *)familyName; 303+ (UIFont *)systemFontOfSize:(CGFloat)fontSize; 304@end 305 306@interface NSTextTab 307- (id)initWithType:(NSTextTabType)type location:(CGFloat)loc; 308- (id)initWithTextAlignment:(NSTextAlignment)alignment location:(CGFloat)loc options:(NSDictionary *)options; 309- (CGFloat)location; 310- (void)release; 311@end 312 313@interface NSParagraphStyle : NSObject 314+ (NSParagraphStyle *)defaultParagraphStyle; 315- (void)setAlignment:(NSTextAlignment)alignment; 316- (void)setBaseWritingDirection:(NSWritingDirection)writingDirection; 317- (void)setHeadIndent:(CGFloat)aFloat; 318- (CGFloat)headIndent; 319- (void)setHeaderLevel:(NSInteger)level; 320- (void)setFirstLineHeadIndent:(CGFloat)aFloat; 321- (void)setTailIndent:(CGFloat)aFloat; 322- (void)setParagraphSpacing:(CGFloat)paragraphSpacing; 323- (void)setTextLists:(NSArray *)array; 324- (void)setTextBlocks:(NSArray *)array; 325- (void)setMinimumLineHeight:(CGFloat)aFloat; 326- (NSArray *)textLists; 327- (void)removeTabStop:(NSTextTab *)anObject; 328- (void)addTabStop:(NSTextTab *)anObject; 329- (NSArray *)tabStops; 330- (void)setHyphenationFactor:(float)aFactor; 331@end 332 333@interface NSShadow 334- (void)setShadowOffset:(CGSize)size; 335- (void)setShadowBlurRadius:(CGFloat)radius; 336- (void)setShadowColor:(UIColor *)color; 337@end 338 339@interface NSTextBlock : NSObject 340- (void)setValue:(CGFloat)val type:(NSTextBlockValueType)type forDimension:(NSTextBlockDimension)dimension; 341- (void)setWidth:(CGFloat)val type:(NSTextBlockValueType)type forLayer:(NSTextBlockLayer)layer edge:(NSRectEdge)edge; 342- (void)setBackgroundColor:(UIColor *)color; 343- (UIColor *)backgroundColor; 344- (void)setBorderColor:(UIColor *)color forEdge:(NSRectEdge)edge; 345- (void)setBorderColor:(UIColor *)color; // Convenience method sets all edges at once 346- (void)setVerticalAlignment:(NSTextBlockVerticalAlignment)alignment; 347@end 348 349@interface NSTextList 350- (id)initWithMarkerFormat:(NSString *)format options:(NSUInteger)mask; 351- (void)setStartingItemNumber:(NSInteger)itemNum; 352- (NSInteger)startingItemNumber; 353- (NSString *)markerForItemNumber:(NSInteger)itemNum; 354- (void)release; 355@end 356 357@interface NSMutableParagraphStyle : NSParagraphStyle 358- (void)setDefaultTabInterval:(CGFloat)aFloat; 359- (void)setTabStops:(NSArray *)array; 360@end 361 362@interface NSTextAttachment : NSObject 363- (id)initWithFileWrapper:(NSFileWrapper *)fileWrapper; 364#if PLATFORM(IOS) 365- (void)setBounds:(CGRect)bounds; 366#endif 367- (void)release; 368@end 369 370@interface NSTextTable : NSTextBlock 371- (void)setNumberOfColumns:(NSUInteger)numCols; 372- (void)setCollapsesBorders:(BOOL)flag; 373- (void)setHidesEmptyCells:(BOOL)flag; 374- (void)setLayoutAlgorithm:(NSTextTableLayoutAlgorithm)algorithm; 375- (NSUInteger)numberOfColumns; 376- (void)release; 377@end 378 379@interface NSTextTableBlock : NSTextBlock 380- (id)initWithTable:(NSTextTable *)table startingRow:(NSInteger)row rowSpan:(NSInteger)rowSpan startingColumn:(NSInteger)col columnSpan:(NSInteger)colSpan; // Designated initializer 381- (NSInteger)startingColumn; 382- (NSInteger)startingRow; 383- (NSUInteger)numberOfColumns; 384- (NSInteger)columnSpan; 385- (NSInteger)rowSpan; 386@end 387 388#else 389static NSFileWrapper *fileWrapperForURL(DocumentLoader *, NSURL *); 390static NSFileWrapper *fileWrapperForElement(Element*); 391 392@interface NSTextAttachment (WebCoreNSTextAttachment) 393- (void)setIgnoresOrientation:(BOOL)flag; 394- (void)setBounds:(CGRect)bounds; 395- (BOOL)ignoresOrientation; 396@end 397 398#endif 399 400// Additional control Unicode characters 401const unichar WebNextLineCharacter = 0x0085; 402 403static const CGFloat defaultFontSize = 12; 404static const CGFloat minimumFontSize = 1; 405 406class HTMLConverterCaches { 407public: 408 String propertyValueForNode(Node&, CSSPropertyID ); 409 bool floatPropertyValueForNode(Node&, CSSPropertyID, float&); 410 Color colorPropertyValueForNode(Node&, CSSPropertyID); 411 412 bool isBlockElement(Element&); 413 bool elementHasOwnBackgroundColor(Element&); 414 415 PassRefPtr<CSSValue> computedStylePropertyForElement(Element&, CSSPropertyID); 416 PassRefPtr<CSSValue> inlineStylePropertyForElement(Element&, CSSPropertyID); 417 418 Node* cacheAncestorsOfStartToBeConverted(const Range&); 419 bool isAncestorsOfStartToBeConverted(Node& node) const { return m_ancestorsUnderCommonAncestor.contains(&node); } 420 421private: 422 HashMap<Element*, std::unique_ptr<ComputedStyleExtractor>> m_computedStyles; 423 HashSet<Node*> m_ancestorsUnderCommonAncestor; 424}; 425 426@interface NSTextList (WebCoreNSTextListDetails) 427+ (NSDictionary *)_standardMarkerAttributesForAttributes:(NSDictionary *)attrs; 428@end 429 430@interface NSURL (WebCoreNSURLDetails) 431// FIXME: What is the reason to use this Foundation method, and not +[NSURL URLWithString:relativeToURL:]? 432+ (NSURL *)_web_URLWithString:(NSString *)string relativeToURL:(NSURL *)baseURL; 433@end 434 435@interface NSObject(WebMessageDocumentSimulation) 436+ (void)document:(NSObject **)outDocument attachment:(NSTextAttachment **)outAttachment forURL:(NSURL *)url; 437@end 438 439class HTMLConverter { 440public: 441 HTMLConverter(Range&); 442 ~HTMLConverter(); 443 444 NSAttributedString* convert(); 445 446private: 447 Ref<Range> m_range; 448 DocumentLoader* m_dataSource; 449 450 HashMap<RefPtr<Element>, RetainPtr<NSDictionary>> m_attributesForElements; 451 HashMap<RetainPtr<NSTextTable>, RefPtr<Element>> m_textTableFooters; 452 HashMap<RefPtr<Element>, RetainPtr<NSDictionary>> m_aggregatedAttributesForElements; 453 454 NSMutableAttributedString *_attrStr; 455 NSMutableDictionary *_documentAttrs; 456 NSURL *_baseURL; 457 NSMutableArray *_textLists; 458 NSMutableArray *_textBlocks; 459 NSMutableArray *_textTables; 460 NSMutableArray *_textTableSpacings; 461 NSMutableArray *_textTablePaddings; 462 NSMutableArray *_textTableRows; 463 NSMutableArray *_textTableRowArrays; 464 NSMutableArray *_textTableRowBackgroundColors; 465 NSMutableDictionary *_fontCache; 466 NSMutableArray *_writingDirectionArray; 467 468 CGFloat _defaultTabInterval; 469 NSUInteger _domRangeStartIndex; 470 NSInteger _quoteLevel; 471 472 std::unique_ptr<HTMLConverterCaches> _caches; 473 474 struct { 475 unsigned int isSoft:1; 476 unsigned int reachedStart:1; 477 unsigned int reachedEnd:1; 478 unsigned int hasTrailingNewline:1; 479 unsigned int pad:26; 480 } _flags; 481 482 PlatformColor *_colorForElement(Element&, CSSPropertyID); 483 484 void _traverseNode(Node&, unsigned depth, bool embedded); 485 void _traverseFooterNode(Element&, unsigned depth); 486 487 NSDictionary *computedAttributesForElement(Element&); 488 NSDictionary *attributesForElement(Element&); 489 NSDictionary *aggregatedAttributesForAncestors(CharacterData&); 490 NSDictionary* aggregatedAttributesForElementAndItsAncestors(Element&); 491 492 Element* _blockLevelElementForNode(Node*); 493 494 void _newParagraphForElement(Element&, NSString *tag, BOOL flag, BOOL suppressTrailingSpace); 495 void _newLineForElement(Element&); 496 void _newTabForElement(Element&); 497 BOOL _addAttachmentForElement(Element&, NSURL *url, BOOL needsParagraph, BOOL usePlaceholder); 498 void _addQuoteForElement(Element&, BOOL opening, NSInteger level); 499 void _addValue(NSString *value, Element&); 500 void _fillInBlock(NSTextBlock *block, Element&, PlatformColor *backgroundColor, CGFloat extraMargin, CGFloat extraPadding, BOOL isTable); 501 502 BOOL _enterElement(Element&, BOOL embedded); 503 BOOL _processElement(Element&, NSInteger depth); 504 void _exitElement(Element&, NSInteger depth, NSUInteger startIndex); 505 506 void _processHeadElement(Element&); 507 void _processMetaElementWithName(NSString *name, NSString *content); 508 509 void _addTableForElement(Element* tableElement); 510 void _addTableCellForElement(Element* tableCellElement); 511 void _addMarkersToList(NSTextList *list, NSRange range); 512 void _processText(CharacterData&); 513 void _adjustTrailingNewline(); 514}; 515 516HTMLConverter::HTMLConverter(Range& range) 517 : m_range(range) 518 , m_dataSource(nullptr) 519{ 520 _attrStr = [[NSMutableAttributedString alloc] init]; 521 _documentAttrs = [[NSMutableDictionary alloc] init]; 522 _baseURL = nil; 523 _textLists = [[NSMutableArray alloc] init]; 524 _textBlocks = [[NSMutableArray alloc] init]; 525 _textTables = [[NSMutableArray alloc] init]; 526 _textTableSpacings = [[NSMutableArray alloc] init]; 527 _textTablePaddings = [[NSMutableArray alloc] init]; 528 _textTableRows = [[NSMutableArray alloc] init]; 529 _textTableRowArrays = [[NSMutableArray alloc] init]; 530 _textTableRowBackgroundColors = [[NSMutableArray alloc] init]; 531 _fontCache = [[NSMutableDictionary alloc] init]; 532 _writingDirectionArray = [[NSMutableArray alloc] init]; 533 534 _defaultTabInterval = 36; 535 _domRangeStartIndex = 0; 536 _quoteLevel = 0; 537 538 _flags.isSoft = false; 539 _flags.reachedStart = false; 540 _flags.reachedEnd = false; 541 542 _caches = std::make_unique<HTMLConverterCaches>(); 543} 544 545HTMLConverter::~HTMLConverter() 546{ 547 [_attrStr release]; 548 [_documentAttrs release]; 549 [_textLists release]; 550 [_textBlocks release]; 551 [_textTables release]; 552 [_textTableSpacings release]; 553 [_textTablePaddings release]; 554 [_textTableRows release]; 555 [_textTableRowArrays release]; 556 [_textTableRowBackgroundColors release]; 557 [_fontCache release]; 558 [_writingDirectionArray release]; 559} 560 561NSAttributedString *HTMLConverter::convert() 562{ 563 Node* commonAncestorContainer = _caches->cacheAncestorsOfStartToBeConverted(m_range.get()); 564 ASSERT(commonAncestorContainer); 565 566 m_dataSource = commonAncestorContainer->document().frame()->loader().documentLoader(); 567 if (!m_dataSource) 568 return nil; 569 570 _domRangeStartIndex = 0; 571 _traverseNode(*commonAncestorContainer, 0, false /* embedded */); 572 if (_domRangeStartIndex > 0 && _domRangeStartIndex <= [_attrStr length]) 573 [_attrStr deleteCharactersInRange:NSMakeRange(0, _domRangeStartIndex)]; 574 575 return [[_attrStr retain] autorelease]; 576} 577 578#if !PLATFORM(IOS) 579// Returns the font to be used if the NSFontAttributeName doesn't exist 580static NSFont *WebDefaultFont() 581{ 582 static NSFont *defaultFont = nil; 583 if (defaultFont) 584 return defaultFont; 585 586 NSFont *font = [NSFont fontWithName:@"Helvetica" size:12]; 587 if (!font) 588 font = [NSFont systemFontOfSize:12]; 589 590 defaultFont = [font retain]; 591 return defaultFont; 592} 593#endif 594 595static PlatformFont *_fontForNameAndSize(NSString *fontName, CGFloat size, NSMutableDictionary *cache) 596{ 597 PlatformFont *font = [cache objectForKey:fontName]; 598#if PLATFORM(IOS) 599 if (font) 600 return [font fontWithSize:size]; 601 602 font = [PlatformFontClass fontWithName:fontName size:size]; 603#else 604 NSFontManager *fontManager = [NSFontManager sharedFontManager]; 605 if (font) { 606 font = [fontManager convertFont:font toSize:size]; 607 return font; 608 } 609 font = [fontManager fontWithFamily:fontName traits:0 weight:0 size:size]; 610#endif 611 if (!font) { 612#if PLATFORM(IOS) 613 NSArray *availableFamilyNames = [PlatformFontClass familyNames]; 614#else 615 NSArray *availableFamilyNames = [fontManager availableFontFamilies]; 616#endif 617 NSRange dividingRange; 618 NSRange dividingSpaceRange = [fontName rangeOfString:@" " options:NSBackwardsSearch]; 619 NSRange dividingDashRange = [fontName rangeOfString:@"-" options:NSBackwardsSearch]; 620 dividingRange = (0 < dividingSpaceRange.length && 0 < dividingDashRange.length) ? (dividingSpaceRange.location > dividingDashRange.location ? dividingSpaceRange : dividingDashRange) : (0 < dividingSpaceRange.length ? dividingSpaceRange : dividingDashRange); 621 622 while (dividingRange.length > 0) { 623 NSString *familyName = [fontName substringToIndex:dividingRange.location]; 624 if ([availableFamilyNames containsObject:familyName]) { 625#if PLATFORM(IOS) 626 NSString *faceName = [fontName substringFromIndex:(dividingRange.location + dividingRange.length)]; 627 NSArray *familyMemberFaceNames = [PlatformFontClass fontNamesForFamilyName:familyName]; 628 for (NSString *familyMemberFaceName in familyMemberFaceNames) { 629 if ([familyMemberFaceName compare:faceName options:NSCaseInsensitiveSearch] == NSOrderedSame) { 630 font = [PlatformFontClass fontWithName:familyMemberFaceName size:size]; 631 break; 632 } 633 } 634 if (!font && [familyMemberFaceNames count]) 635 font = [getUIFontClass() fontWithName:familyName size:size]; 636#else 637 NSArray *familyMemberArray; 638 NSString *faceName = [fontName substringFromIndex:(dividingRange.location + dividingRange.length)]; 639 NSArray *familyMemberArrays = [fontManager availableMembersOfFontFamily:familyName]; 640 NSEnumerator *familyMemberArraysEnum = [familyMemberArrays objectEnumerator]; 641 while ((familyMemberArray = [familyMemberArraysEnum nextObject])) { 642 NSString *familyMemberFaceName = [familyMemberArray objectAtIndex:1]; 643 if ([familyMemberFaceName compare:faceName options:NSCaseInsensitiveSearch] == NSOrderedSame) { 644 NSFontTraitMask traits = [[familyMemberArray objectAtIndex:3] integerValue]; 645 NSInteger weight = [[familyMemberArray objectAtIndex:2] integerValue]; 646 font = [fontManager fontWithFamily:familyName traits:traits weight:weight size:size]; 647 break; 648 } 649 } 650 if (!font) { 651 if (0 < [familyMemberArrays count]) { 652 NSArray *familyMemberArray = [familyMemberArrays objectAtIndex:0]; 653 NSFontTraitMask traits = [[familyMemberArray objectAtIndex:3] integerValue]; 654 NSInteger weight = [[familyMemberArray objectAtIndex:2] integerValue]; 655 font = [fontManager fontWithFamily:familyName traits:traits weight:weight size:size]; 656 } 657 } 658#endif 659 break; 660 } else { 661 dividingSpaceRange = [familyName rangeOfString:@" " options:NSBackwardsSearch]; 662 dividingDashRange = [familyName rangeOfString:@"-" options:NSBackwardsSearch]; 663 dividingRange = (0 < dividingSpaceRange.length && 0 < dividingDashRange.length) ? (dividingSpaceRange.location > dividingDashRange.location ? dividingSpaceRange : dividingDashRange) : (0 < dividingSpaceRange.length ? dividingSpaceRange : dividingDashRange); 664 } 665 } 666 } 667#if PLATFORM(IOS) 668 if (!font) 669 font = [PlatformFontClass systemFontOfSize:size]; 670#else 671 if (!font) 672 font = [NSFont fontWithName:@"Times" size:size]; 673 if (!font) 674 font = [NSFont userFontOfSize:size]; 675 if (!font) 676 font = [fontManager convertFont:WebDefaultFont() toSize:size]; 677 if (!font) 678 font = WebDefaultFont(); 679#endif 680 [cache setObject:font forKey:fontName]; 681 682 return font; 683} 684 685static NSParagraphStyle *defaultParagraphStyle() 686{ 687 static NSMutableParagraphStyle *defaultParagraphStyle = nil; 688 if (!defaultParagraphStyle) { 689 defaultParagraphStyle = [[PlatformNSParagraphStyle defaultParagraphStyle] mutableCopy]; 690 [defaultParagraphStyle setDefaultTabInterval:36]; 691 [defaultParagraphStyle setTabStops:[NSArray array]]; 692 } 693 return defaultParagraphStyle; 694} 695 696PassRefPtr<CSSValue> HTMLConverterCaches::computedStylePropertyForElement(Element& element, CSSPropertyID propertyId) 697{ 698 if (propertyId == CSSPropertyInvalid) 699 return nullptr; 700 701 auto result = m_computedStyles.add(&element, nullptr); 702 if (result.isNewEntry) 703 result.iterator->value = std::make_unique<ComputedStyleExtractor>(&element, true); 704 ComputedStyleExtractor& computedStyle = *result.iterator->value; 705 return computedStyle.propertyValue(propertyId); 706} 707 708PassRefPtr<CSSValue> HTMLConverterCaches::inlineStylePropertyForElement(Element& element, CSSPropertyID propertyId) 709{ 710 if (propertyId == CSSPropertyInvalid || !element.isStyledElement()) 711 return nullptr; 712 const StyleProperties* properties = toStyledElement(element).inlineStyle(); 713 if (!properties) 714 return nullptr; 715 return properties->getPropertyCSSValue(propertyId); 716} 717 718static bool stringFromCSSValue(CSSValue& value, String& result) 719{ 720 if (value.isPrimitiveValue()) { 721 unsigned short primitiveType = toCSSPrimitiveValue(value).primitiveType(); 722 if (primitiveType == CSSPrimitiveValue::CSS_STRING || primitiveType == CSSPrimitiveValue::CSS_URI || 723 primitiveType == CSSPrimitiveValue::CSS_IDENT || primitiveType == CSSPrimitiveValue::CSS_ATTR) { 724 String stringValue = value.cssText(); 725 if (stringValue.length()) { 726 result = stringValue; 727 return true; 728 } 729 } 730 } else if (value.isValueList()) { 731 result = value.cssText(); 732 return true; 733 } 734 return false; 735} 736 737String HTMLConverterCaches::propertyValueForNode(Node& node, CSSPropertyID propertyId) 738{ 739 if (!node.isElementNode()) { 740 if (Node* parent = node.parentNode()) 741 return propertyValueForNode(*parent, propertyId); 742 return String(); 743 } 744 745 bool inherit = false; 746 Element& element = toElement(node); 747 if (RefPtr<CSSValue> value = computedStylePropertyForElement(element, propertyId)) { 748 String result; 749 if (stringFromCSSValue(*value, result)) 750 return result; 751 } 752 753 if (RefPtr<CSSValue> value = inlineStylePropertyForElement(element, propertyId)) { 754 String result; 755 if (value->isInheritedValue()) 756 inherit = true; 757 else if (stringFromCSSValue(*value, result)) 758 return result; 759 } 760 761 switch (propertyId) { 762 case CSSPropertyDisplay: 763 if (element.hasTagName(headTag) || element.hasTagName(scriptTag) || element.hasTagName(appletTag) || element.hasTagName(noframesTag)) 764 return "none"; 765 else if (element.hasTagName(addressTag) || element.hasTagName(blockquoteTag) || element.hasTagName(bodyTag) || element.hasTagName(centerTag) 766 || element.hasTagName(ddTag) || element.hasTagName(dirTag) || element.hasTagName(divTag) || element.hasTagName(dlTag) 767 || element.hasTagName(dtTag) || element.hasTagName(fieldsetTag) || element.hasTagName(formTag) || element.hasTagName(frameTag) 768 || element.hasTagName(framesetTag) || element.hasTagName(hrTag) || element.hasTagName(htmlTag) || element.hasTagName(h1Tag) 769 || element.hasTagName(h2Tag) || element.hasTagName(h3Tag) || element.hasTagName(h4Tag) || element.hasTagName(h5Tag) 770 || element.hasTagName(h6Tag) || element.hasTagName(iframeTag) || element.hasTagName(menuTag) || element.hasTagName(noscriptTag) 771 || element.hasTagName(olTag) || element.hasTagName(pTag) || element.hasTagName(preTag) || element.hasTagName(ulTag)) 772 return "block"; 773 else if (element.hasTagName(liTag)) 774 return "list-item"; 775 else if (element.hasTagName(tableTag)) 776 return "table"; 777 else if (element.hasTagName(trTag)) 778 return "table-row"; 779 else if (element.hasTagName(thTag) || element.hasTagName(tdTag)) 780 return "table-cell"; 781 else if (element.hasTagName(theadTag)) 782 return "table-header-group"; 783 else if (element.hasTagName(tbodyTag)) 784 return "table-row-group"; 785 else if (element.hasTagName(tfootTag)) 786 return "table-footer-group"; 787 else if (element.hasTagName(colTag)) 788 return "table-column"; 789 else if (element.hasTagName(colgroupTag)) 790 return "table-column-group"; 791 else if (element.hasTagName(captionTag)) 792 return "table-caption"; 793 break; 794 case CSSPropertyWhiteSpace: 795 if (element.hasTagName(preTag)) 796 return "pre"; 797 inherit = true; 798 break; 799 case CSSPropertyFontStyle: 800 if (element.hasTagName(iTag) || element.hasTagName(citeTag) || element.hasTagName(emTag) || element.hasTagName(varTag) || element.hasTagName(addressTag)) 801 return "italic"; 802 inherit = true; 803 break; 804 case CSSPropertyFontWeight: 805 if (element.hasTagName(bTag) || element.hasTagName(strongTag) || element.hasTagName(thTag)) 806 return "bolder"; 807 inherit = true; 808 break; 809 case CSSPropertyTextDecoration: 810 if (element.hasTagName(uTag) || element.hasTagName(insTag)) 811 return "underline"; 812 else if (element.hasTagName(sTag) || element.hasTagName(strikeTag) || element.hasTagName(delTag)) 813 return "line-through"; 814 inherit = true; // FIXME: This is not strictly correct 815 break; 816 case CSSPropertyTextAlign: 817 if (element.hasTagName(centerTag) || element.hasTagName(captionTag) || element.hasTagName(thTag)) 818 return "center"; 819 inherit = true; 820 break; 821 case CSSPropertyVerticalAlign: 822 if (element.hasTagName(supTag)) 823 return "super"; 824 else if (element.hasTagName(subTag)) 825 return "sub"; 826 else if (element.hasTagName(theadTag) || element.hasTagName(tbodyTag) || element.hasTagName(tfootTag)) 827 return "middle"; 828 else if (element.hasTagName(trTag) || element.hasTagName(thTag) || element.hasTagName(tdTag)) 829 inherit = true; 830 break; 831 case CSSPropertyFontFamily: 832 case CSSPropertyFontVariant: 833 case CSSPropertyTextTransform: 834 case CSSPropertyTextShadow: 835 case CSSPropertyVisibility: 836 case CSSPropertyBorderCollapse: 837 case CSSPropertyEmptyCells: 838 case CSSPropertyWordSpacing: 839 case CSSPropertyListStyleType: 840 case CSSPropertyDirection: 841 inherit = true; // FIXME: Let classes in the css component figure this out. 842 break; 843 default: 844 break; 845 } 846 847 if (inherit) { 848 if (Node* parent = node.parentNode()) 849 return propertyValueForNode(*parent, propertyId); 850 } 851 852 return String(); 853} 854 855static inline bool floatValueFromPrimitiveValue(CSSPrimitiveValue& primitiveValue, float& result) 856{ 857 // FIXME: Use CSSPrimitiveValue::computeValue. 858 switch (primitiveValue.primitiveType()) { 859 case CSSPrimitiveValue::CSS_PX: 860 result = primitiveValue.getFloatValue(CSSPrimitiveValue::CSS_PX); 861 return true; 862 case CSSPrimitiveValue::CSS_PT: 863 result = 4 * primitiveValue.getFloatValue(CSSPrimitiveValue::CSS_PT) / 3; 864 return true; 865 case CSSPrimitiveValue::CSS_PC: 866 result = 16 * primitiveValue.getFloatValue(CSSPrimitiveValue::CSS_PC); 867 return true; 868 case CSSPrimitiveValue::CSS_CM: 869 result = 96 * primitiveValue.getFloatValue(CSSPrimitiveValue::CSS_PC) / 2.54; 870 return true; 871 case CSSPrimitiveValue::CSS_MM: 872 result = 96 * primitiveValue.getFloatValue(CSSPrimitiveValue::CSS_PC) / 25.4; 873 return true; 874 case CSSPrimitiveValue::CSS_IN: 875 result = 96 * primitiveValue.getFloatValue(CSSPrimitiveValue::CSS_IN); 876 return true; 877 default: 878 return false; 879 } 880} 881 882bool HTMLConverterCaches::floatPropertyValueForNode(Node& node, CSSPropertyID propertyId, float& result) 883{ 884 if (!node.isElementNode()) { 885 if (ContainerNode* parent = node.parentNode()) 886 return floatPropertyValueForNode(*parent, propertyId, result); 887 return false; 888 } 889 890 Element& element = toElement(node); 891 if (RefPtr<CSSValue> value = computedStylePropertyForElement(element, propertyId)) { 892 if (value->isPrimitiveValue() && floatValueFromPrimitiveValue(toCSSPrimitiveValue(*value), result)) 893 return true; 894 } 895 896 bool inherit = false; 897 if (RefPtr<CSSValue> value = inlineStylePropertyForElement(element, propertyId)) { 898 if (value->isPrimitiveValue() && floatValueFromPrimitiveValue(toCSSPrimitiveValue(*value), result)) 899 return true; 900 if (value->isInheritedValue()) 901 inherit = true; 902 } 903 904 switch (propertyId) { 905 case CSSPropertyTextIndent: 906 case CSSPropertyLetterSpacing: 907 case CSSPropertyWordSpacing: 908 case CSSPropertyLineHeight: 909 case CSSPropertyWidows: 910 case CSSPropertyOrphans: 911 inherit = true; 912 break; 913 default: 914 break; 915 } 916 917 if (inherit) { 918 if (ContainerNode* parent = node.parentNode()) 919 return floatPropertyValueForNode(*parent, propertyId, result); 920 } 921 922 return false; 923} 924 925#if PLATFORM(IOS) 926static NSString *_NSFirstPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde) 927{ 928 NSArray *array = NSSearchPathForDirectoriesInDomains(directory, domainMask, expandTilde); 929 return [array count] >= 1 ? [array objectAtIndex:0] : nil; 930} 931 932static NSString *_NSSystemLibraryPath(void) 933{ 934 return _NSFirstPathForDirectoriesInDomains(NSLibraryDirectory, NSSystemDomainMask, YES); 935} 936 937static NSBundle *_webKitBundle() 938{ 939 // FIXME: This should probably use the WebCore bundle to avoid the layering violation. 940 NSBundle *bundle = [NSBundle bundleWithIdentifier:@"com.apple.WebKit"]; 941 if (!bundle) 942 bundle = [NSBundle bundleWithPath:[_NSSystemLibraryPath() stringByAppendingPathComponent:@"Frameworks/WebKit.framework"]]; 943 return bundle; 944} 945 946static inline UIColor *_platformColor(Color color) 947{ 948 return [getUIColorClass() _disambiguated_due_to_CIImage_colorWithCGColor:cachedCGColor(color, WebCore::ColorSpaceDeviceRGB)]; 949} 950#else 951static inline NSColor *_platformColor(Color color) 952{ 953 return nsColor(color); 954} 955#endif 956 957static inline NSShadow *_shadowForShadowStyle(NSString *shadowStyle) 958{ 959 NSShadow *shadow = nil; 960 NSUInteger shadowStyleLength = [shadowStyle length]; 961 NSRange openParenRange = [shadowStyle rangeOfString:@"("]; 962 NSRange closeParenRange = [shadowStyle rangeOfString:@")"]; 963 NSRange firstRange = NSMakeRange(NSNotFound, 0); 964 NSRange secondRange = NSMakeRange(NSNotFound, 0); 965 NSRange thirdRange = NSMakeRange(NSNotFound, 0); 966 NSRange spaceRange; 967 if (openParenRange.length > 0 && closeParenRange.length > 0 && NSMaxRange(openParenRange) < closeParenRange.location) { 968 NSArray *components = [[shadowStyle substringWithRange:NSMakeRange(NSMaxRange(openParenRange), closeParenRange.location - NSMaxRange(openParenRange))] componentsSeparatedByString:@","]; 969 if ([components count] >= 3) { 970 CGFloat red = [[components objectAtIndex:0] floatValue] / 255; 971 CGFloat green = [[components objectAtIndex:1] floatValue] / 255; 972 CGFloat blue = [[components objectAtIndex:2] floatValue] / 255; 973 CGFloat alpha = ([components count] >= 4) ? [[components objectAtIndex:3] floatValue] / 255 : 1; 974 NSColor *shadowColor = [PlatformNSColorClass colorWithCalibratedRed:red green:green blue:blue alpha:alpha]; 975 NSSize shadowOffset; 976 CGFloat shadowBlurRadius; 977 firstRange = [shadowStyle rangeOfString:@"px"]; 978 if (firstRange.length > 0 && NSMaxRange(firstRange) < shadowStyleLength) 979 secondRange = [shadowStyle rangeOfString:@"px" options:0 range:NSMakeRange(NSMaxRange(firstRange), shadowStyleLength - NSMaxRange(firstRange))]; 980 if (secondRange.length > 0 && NSMaxRange(secondRange) < shadowStyleLength) 981 thirdRange = [shadowStyle rangeOfString:@"px" options:0 range:NSMakeRange(NSMaxRange(secondRange), shadowStyleLength - NSMaxRange(secondRange))]; 982 if (firstRange.location > 0 && firstRange.length > 0 && secondRange.length > 0 && thirdRange.length > 0) { 983 spaceRange = [shadowStyle rangeOfString:@" " options:NSBackwardsSearch range:NSMakeRange(0, firstRange.location)]; 984 if (spaceRange.length == 0) 985 spaceRange = NSMakeRange(0, 0); 986 shadowOffset.width = [[shadowStyle substringWithRange:NSMakeRange(NSMaxRange(spaceRange), firstRange.location - NSMaxRange(spaceRange))] floatValue]; 987 spaceRange = [shadowStyle rangeOfString:@" " options:NSBackwardsSearch range:NSMakeRange(0, secondRange.location)]; 988 if (!spaceRange.length) 989 spaceRange = NSMakeRange(0, 0); 990 CGFloat shadowHeight = [[shadowStyle substringWithRange:NSMakeRange(NSMaxRange(spaceRange), secondRange.location - NSMaxRange(spaceRange))] floatValue]; 991 // I don't know why we have this difference between the two platforms. 992#if PLATFORM(IOS) 993 shadowOffset.height = shadowHeight; 994#else 995 shadowOffset.height = -shadowHeight; 996#endif 997 spaceRange = [shadowStyle rangeOfString:@" " options:NSBackwardsSearch range:NSMakeRange(0, thirdRange.location)]; 998 if (!spaceRange.length) 999 spaceRange = NSMakeRange(0, 0); 1000 shadowBlurRadius = [[shadowStyle substringWithRange:NSMakeRange(NSMaxRange(spaceRange), thirdRange.location - NSMaxRange(spaceRange))] floatValue]; 1001 shadow = [[[PlatformNSShadow alloc] init] autorelease]; 1002 [shadow setShadowColor:shadowColor]; 1003 [shadow setShadowOffset:shadowOffset]; 1004 [shadow setShadowBlurRadius:shadowBlurRadius]; 1005 } 1006 } 1007 } 1008 return shadow; 1009} 1010 1011bool HTMLConverterCaches::isBlockElement(Element& element) 1012{ 1013 String displayValue = propertyValueForNode(element, CSSPropertyDisplay); 1014 if (displayValue == "block" || displayValue == "list-item" || displayValue.startsWith("table")) 1015 return true; 1016 String floatValue = propertyValueForNode(element, CSSPropertyFloat); 1017 if (floatValue == "left" || floatValue == "right") 1018 return true; 1019 return false; 1020} 1021 1022bool HTMLConverterCaches::elementHasOwnBackgroundColor(Element& element) 1023{ 1024 if (!isBlockElement(element)) 1025 return false; 1026 // In the text system, text blocks (table elements) and documents (body elements) 1027 // have their own background colors, which should not be inherited. 1028 return element.hasTagName(htmlTag) || element.hasTagName(bodyTag) || propertyValueForNode(element, CSSPropertyDisplay).startsWith("table"); 1029} 1030 1031Element* HTMLConverter::_blockLevelElementForNode(Node* node) 1032{ 1033 Element* element = node->parentElement(); 1034 if (element && !_caches->isBlockElement(*element)) 1035 element = _blockLevelElementForNode(element->parentNode()); 1036 return element; 1037} 1038 1039static Color normalizedColor(Color color, bool ignoreBlack) 1040{ 1041 const double ColorEpsilon = 1 / (2 * (double)255.0); 1042 1043 double red, green, blue, alpha; 1044 color.getRGBA(red, green, blue, alpha); 1045 if (red < ColorEpsilon && green < ColorEpsilon && blue < ColorEpsilon && (ignoreBlack || alpha < ColorEpsilon)) 1046 return Color(); 1047 1048 return color; 1049} 1050 1051Color HTMLConverterCaches::colorPropertyValueForNode(Node& node, CSSPropertyID propertyId) 1052{ 1053 if (!node.isElementNode()) { 1054 if (Node* parent = node.parentNode()) 1055 return colorPropertyValueForNode(*parent, propertyId); 1056 return Color(); 1057 } 1058 1059 Element& element = toElement(node); 1060 if (RefPtr<CSSValue> value = computedStylePropertyForElement(element, propertyId)) { 1061 if (value->isPrimitiveValue() && toCSSPrimitiveValue(*value).isRGBColor()) 1062 return normalizedColor(Color(toCSSPrimitiveValue(*value).getRGBA32Value()), propertyId == CSSPropertyColor); 1063 } 1064 1065 bool inherit = false; 1066 if (RefPtr<CSSValue> value = inlineStylePropertyForElement(element, propertyId)) { 1067 if (value->isPrimitiveValue() && toCSSPrimitiveValue(*value).isRGBColor()) 1068 return normalizedColor(Color(toCSSPrimitiveValue(*value).getRGBA32Value()), propertyId == CSSPropertyColor); 1069 if (value->isInheritedValue()) 1070 inherit = true; 1071 } 1072 1073 switch (propertyId) { 1074 case CSSPropertyColor: 1075 inherit = true; 1076 break; 1077 case CSSPropertyBackgroundColor: 1078 if (!elementHasOwnBackgroundColor(element)) { 1079 if (Element* parentElement = node.parentElement()) { 1080 if (!elementHasOwnBackgroundColor(*parentElement)) 1081 inherit = true; 1082 } 1083 } 1084 break; 1085 default: 1086 break; 1087 } 1088 1089 if (inherit) { 1090 if (Node* parent = node.parentNode()) 1091 return colorPropertyValueForNode(*parent, propertyId); 1092 } 1093 1094 return Color(); 1095} 1096 1097PlatformColor *HTMLConverter::_colorForElement(Element& element, CSSPropertyID propertyId) 1098{ 1099 Color result = _caches->colorPropertyValueForNode(element, propertyId); 1100 if (!result.isValid()) 1101 return nil; 1102 PlatformColor *platformResult = _platformColor(result); 1103 if ([[PlatformColorClass clearColor] isEqual:platformResult] || ([platformResult alphaComponent] == 0.0)) 1104 return nil; 1105 return platformResult; 1106} 1107 1108#if !PLATFORM(IOS) 1109static PlatformFont *_font(Element& element) 1110{ 1111 auto renderer = element.renderer(); 1112 if (!renderer) 1113 return nil; 1114 return renderer->style().font().primaryFont()->getNSFont(); 1115} 1116#else 1117static PlatformFont *_font(Element& element) 1118{ 1119 auto renderer = element.renderer(); 1120 if (!renderer) 1121 return nil; 1122 return (PlatformFont *)renderer->style().font().primaryFont()->getCTFont(); 1123} 1124#endif 1125 1126#define UIFloatIsZero(number) (fabs(number - 0) < FLT_EPSILON) 1127 1128NSDictionary *HTMLConverter::computedAttributesForElement(Element& element) 1129{ 1130 NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; 1131#if !PLATFORM(IOS) 1132 NSFontManager *fontManager = [NSFontManager sharedFontManager]; 1133#endif 1134 1135 PlatformFont *font = nil; 1136 PlatformFont *actualFont = _font(element); 1137 PlatformColor *foregroundColor = _colorForElement(element, CSSPropertyColor); 1138 PlatformColor *backgroundColor = _colorForElement(element, CSSPropertyBackgroundColor); 1139 PlatformColor *strokeColor = _colorForElement(element, CSSPropertyWebkitTextStrokeColor); 1140 1141 float fontSize = 0; 1142 if (!_caches->floatPropertyValueForNode(element, CSSPropertyFontSize, fontSize) || fontSize <= 0.0) 1143 fontSize = defaultFontSize; 1144 if (fontSize < minimumFontSize) 1145 fontSize = minimumFontSize; 1146 if (fabs(floor(2.0 * fontSize + 0.5) / 2.0 - fontSize) < 0.05) 1147 fontSize = floor(2.0 * fontSize + 0.5) / 2; 1148 else if (fabs(floor(10.0 * fontSize + 0.5) / 10.0 - fontSize) < 0.005) 1149 fontSize = floor(10.0 * fontSize + 0.5) / 10; 1150 1151 if (fontSize <= 0.0) 1152 fontSize = defaultFontSize; 1153 1154#if PLATFORM(IOS) 1155 if (actualFont) 1156 font = [actualFont fontWithSize:fontSize]; 1157#else 1158 if (actualFont) 1159 font = [fontManager convertFont:actualFont toSize:fontSize]; 1160#endif 1161 if (!font) { 1162 String fontName = _caches->propertyValueForNode(element, CSSPropertyFontFamily); 1163 if (fontName.length()) 1164 font = _fontForNameAndSize(fontName.upper(), fontSize, _fontCache); 1165 if (!font) 1166 font = [PlatformFontClass fontWithName:@"Times" size:fontSize]; 1167 1168 String fontStyle = _caches->propertyValueForNode(element, CSSPropertyFontStyle); 1169 if (fontStyle == "italic" || fontStyle == "oblique") { 1170 PlatformFont *originalFont = font; 1171#if PLATFORM(IOS) 1172 font = [PlatformFontClass fontWithFamilyName:[font familyName] traits:UIFontTraitItalic size:[font pointSize]]; 1173#else 1174 font = [fontManager convertFont:font toHaveTrait:NSItalicFontMask]; 1175#endif 1176 if (!font) 1177 font = originalFont; 1178 } 1179 1180 String fontWeight = _caches->propertyValueForNode(element, CSSPropertyFontStyle); 1181 if (fontWeight.startsWith("bold") || fontWeight.toInt() >= 700) { 1182 // ??? handle weight properly using NSFontManager 1183 PlatformFont *originalFont = font; 1184#if PLATFORM(IOS) 1185 font = [PlatformFontClass fontWithFamilyName:[font familyName] traits:UIFontTraitBold size:[font pointSize]]; 1186#else 1187 font = [fontManager convertFont:font toHaveTrait:NSBoldFontMask]; 1188#endif 1189 if (!font) 1190 font = originalFont; 1191 } 1192#if !PLATFORM(IOS) // IJB: No small caps support on iOS 1193 if (_caches->propertyValueForNode(element, CSSPropertyFontVariant) == "small-caps") { 1194 // ??? synthesize small-caps if [font isEqual:originalFont] 1195 NSFont *originalFont = font; 1196 font = [fontManager convertFont:font toHaveTrait:NSSmallCapsFontMask]; 1197 if (!font) 1198 font = originalFont; 1199 } 1200#endif 1201 } 1202 if (font) 1203 [attrs setObject:font forKey:NSFontAttributeName]; 1204 if (foregroundColor) 1205 [attrs setObject:foregroundColor forKey:NSForegroundColorAttributeName]; 1206 if (backgroundColor && !_caches->elementHasOwnBackgroundColor(element)) 1207 [attrs setObject:backgroundColor forKey:NSBackgroundColorAttributeName]; 1208 1209 float strokeWidth = 0.0; 1210 if (_caches->floatPropertyValueForNode(element, CSSPropertyWebkitTextStrokeWidth, strokeWidth)) { 1211 float textStrokeWidth = strokeWidth / ([font pointSize] * 0.01); 1212 [attrs setObject:[NSNumber numberWithDouble:textStrokeWidth] forKey:NSStrokeWidthAttributeName]; 1213 } 1214 if (strokeColor) 1215 [attrs setObject:strokeColor forKey:NSStrokeColorAttributeName]; 1216 1217 String fontKerning = _caches->propertyValueForNode(element, CSSPropertyWebkitFontKerning); 1218 String letterSpacing = _caches->propertyValueForNode(element, CSSPropertyLetterSpacing); 1219 if (fontKerning.length() || letterSpacing.length()) { 1220 if (fontKerning == "none") 1221 [attrs setObject:@0.0 forKey:NSKernAttributeName]; 1222 else { 1223 double kernVal = letterSpacing.length() ? letterSpacing.toDouble() : 0.0; 1224 if (UIFloatIsZero(kernVal)) 1225 [attrs setObject:@0.0 forKey:NSKernAttributeName]; // auto and normal, the other possible values, are both "kerning enabled" 1226 else 1227 [attrs setObject:[NSNumber numberWithDouble:kernVal] forKey:NSKernAttributeName]; 1228 } 1229 } 1230 1231 String fontLigatures = _caches->propertyValueForNode(element, CSSPropertyWebkitFontVariantLigatures); 1232 if (fontLigatures.length()) { 1233 if (fontLigatures.contains("normal")) 1234 ; // default: whatever the system decides to do 1235 else if (fontLigatures.contains("common-ligatures")) 1236 [attrs setObject:@1 forKey:NSLigatureAttributeName]; // explicitly enabled 1237 else if (fontLigatures.contains("no-common-ligatures")) 1238 [attrs setObject:@0 forKey:NSLigatureAttributeName]; // explicitly disabled 1239 } 1240 1241 String textDecoration = _caches->propertyValueForNode(element, CSSPropertyTextDecoration); 1242 if (textDecoration.length()) { 1243 if (textDecoration.contains("underline")) 1244 [attrs setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName]; 1245 if (textDecoration.contains("line-through")) 1246 [attrs setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName]; 1247 } 1248 1249 String verticalAlign = _caches->propertyValueForNode(element, CSSPropertyVerticalAlign); 1250 if (verticalAlign.length()) { 1251 if (verticalAlign == "super") 1252 [attrs setObject:[NSNumber numberWithInteger:1] forKey:NSSuperscriptAttributeName]; 1253 else if (verticalAlign == "sub") 1254 [attrs setObject:[NSNumber numberWithInteger:-1] forKey:NSSuperscriptAttributeName]; 1255 } 1256 1257 float baselineOffset = 0.0; 1258 if (_caches->floatPropertyValueForNode(element, CSSPropertyVerticalAlign, baselineOffset)) 1259 [attrs setObject:[NSNumber numberWithDouble:baselineOffset] forKey:NSBaselineOffsetAttributeName]; 1260 1261 String textShadow = _caches->propertyValueForNode(element, CSSPropertyTextShadow); 1262 if (textShadow.length() > 4) { 1263 NSShadow *shadow = _shadowForShadowStyle(textShadow); 1264 if (shadow) 1265 [attrs setObject:shadow forKey:NSShadowAttributeName]; 1266 } 1267 1268 Element* blockElement = _blockLevelElementForNode(&element); 1269 if (&element != blockElement && [_writingDirectionArray count] > 0) 1270 [attrs setObject:[NSArray arrayWithArray:_writingDirectionArray] forKey:NSWritingDirectionAttributeName]; 1271 1272 if (blockElement) { 1273 Element& coreBlockElement = *blockElement; 1274 NSMutableParagraphStyle *paragraphStyle = [defaultParagraphStyle() mutableCopy]; 1275 unsigned heading = 0; 1276 if (coreBlockElement.hasTagName(h1Tag)) 1277 heading = 1; 1278 else if (coreBlockElement.hasTagName(h2Tag)) 1279 heading = 2; 1280 else if (coreBlockElement.hasTagName(h3Tag)) 1281 heading = 3; 1282 else if (coreBlockElement.hasTagName(h4Tag)) 1283 heading = 4; 1284 else if (coreBlockElement.hasTagName(h5Tag)) 1285 heading = 5; 1286 else if (coreBlockElement.hasTagName(h6Tag)) 1287 heading = 6; 1288 bool isParagraph = coreBlockElement.hasTagName(pTag) || coreBlockElement.hasTagName(liTag) || heading; 1289 1290 String textAlign = _caches->propertyValueForNode(coreBlockElement, CSSPropertyTextAlign); 1291 if (textAlign.length()) { 1292 // WebKit can return -khtml-left, -khtml-right, -khtml-center 1293 if (textAlign.endsWith("left")) 1294 [paragraphStyle setAlignment:NSTextAlignmentLeft]; 1295 else if (textAlign.endsWith("right")) 1296 [paragraphStyle setAlignment:NSTextAlignmentRight]; 1297 else if (textAlign.endsWith("center")) 1298 [paragraphStyle setAlignment:NSTextAlignmentCenter]; 1299 else if (textAlign.endsWith("justify")) 1300 [paragraphStyle setAlignment:NSTextAlignmentJustified]; 1301 } 1302 1303 String direction = _caches->propertyValueForNode(coreBlockElement, CSSPropertyDirection); 1304 if (direction.length()) { 1305 if (direction == "ltr") 1306 [paragraphStyle setBaseWritingDirection:NSWritingDirectionLeftToRight]; 1307 else if (direction == "rtl") 1308 [paragraphStyle setBaseWritingDirection:NSWritingDirectionRightToLeft]; 1309 } 1310 1311 String hyphenation = _caches->propertyValueForNode(coreBlockElement, CSSPropertyWebkitHyphens); 1312 if (hyphenation.length()) { 1313 if (hyphenation == "auto") 1314 [paragraphStyle setHyphenationFactor:1.0]; 1315 else 1316 [paragraphStyle setHyphenationFactor:0.0]; 1317 } 1318 if (heading) 1319 [paragraphStyle setHeaderLevel:heading]; 1320 if (isParagraph) { 1321 // FIXME: Why are we ignoring margin-top? 1322 float marginLeft = 0.0; 1323 if (_caches->floatPropertyValueForNode(coreBlockElement, CSSPropertyMarginLeft, marginLeft) && marginLeft > 0.0) 1324 [paragraphStyle setHeadIndent:marginLeft]; 1325 float textIndent = 0.0; 1326 if (_caches->floatPropertyValueForNode(coreBlockElement, CSSPropertyTextIndent, textIndent) && textIndent > 0.0) 1327 [paragraphStyle setFirstLineHeadIndent:[paragraphStyle headIndent] + textIndent]; 1328 float marginRight = 0.0; 1329 if (_caches->floatPropertyValueForNode(coreBlockElement, CSSPropertyMarginRight, marginRight) && marginRight > 0.0) 1330 [paragraphStyle setTailIndent:-marginRight]; 1331 float marginBottom = 0.0; 1332 if (_caches->floatPropertyValueForNode(coreBlockElement, CSSPropertyMarginRight, marginBottom) && marginBottom > 0.0) 1333 [paragraphStyle setParagraphSpacing:marginBottom]; 1334 } 1335 if ([_textLists count] > 0) 1336 [paragraphStyle setTextLists:_textLists]; 1337 if ([_textBlocks count] > 0) 1338 [paragraphStyle setTextBlocks:_textBlocks]; 1339 [attrs setObject:paragraphStyle forKey:NSParagraphStyleAttributeName]; 1340 [paragraphStyle release]; 1341 } 1342 return attrs; 1343} 1344 1345 1346NSDictionary* HTMLConverter::attributesForElement(Element& element) 1347{ 1348 auto& attributes = m_attributesForElements.add(&element, nullptr).iterator->value; 1349 if (!attributes) 1350 attributes = computedAttributesForElement(element); 1351 return attributes.get(); 1352} 1353 1354NSDictionary* HTMLConverter::aggregatedAttributesForAncestors(CharacterData& node) 1355{ 1356 Node* ancestor = node.parentNode(); 1357 while (ancestor && !ancestor->isElementNode()) 1358 ancestor = ancestor->parentNode(); 1359 if (!ancestor) 1360 return nullptr; 1361 return aggregatedAttributesForElementAndItsAncestors(*toElement(ancestor)); 1362} 1363 1364NSDictionary* HTMLConverter::aggregatedAttributesForElementAndItsAncestors(Element& element) 1365{ 1366 auto& cachedAttributes = m_aggregatedAttributesForElements.add(&element, nullptr).iterator->value; 1367 if (cachedAttributes) 1368 return cachedAttributes.get(); 1369 1370 NSDictionary* attributesForCurrentElement = attributesForElement(element); 1371 ASSERT(attributesForCurrentElement); 1372 1373 Node* ancestor = element.parentNode(); 1374 while (ancestor && !ancestor->isElementNode()) 1375 ancestor = ancestor->parentNode(); 1376 1377 if (!ancestor) { 1378 cachedAttributes = attributesForCurrentElement; 1379 return attributesForCurrentElement; 1380 } 1381 1382 RetainPtr<NSMutableDictionary> attributesForAncestors = adoptNS([aggregatedAttributesForElementAndItsAncestors(*toElement(ancestor)) mutableCopy]); 1383 [attributesForAncestors addEntriesFromDictionary:attributesForCurrentElement]; 1384 m_aggregatedAttributesForElements.set(&element, attributesForAncestors); 1385 1386 return attributesForAncestors.get(); 1387} 1388 1389void HTMLConverter::_newParagraphForElement(Element& element, NSString *tag, BOOL flag, BOOL suppressTrailingSpace) 1390{ 1391 NSUInteger textLength = [_attrStr length]; 1392 unichar lastChar = (textLength > 0) ? [[_attrStr string] characterAtIndex:textLength - 1] : '\n'; 1393 NSRange rangeToReplace = (suppressTrailingSpace && _flags.isSoft && (lastChar == ' ' || lastChar == NSLineSeparatorCharacter)) ? NSMakeRange(textLength - 1, 1) : NSMakeRange(textLength, 0); 1394 BOOL needBreak = (flag || lastChar != '\n'); 1395 if (needBreak) { 1396 NSString *string = (([@"BODY" isEqualToString:tag] || [@"HTML" isEqualToString:tag]) ? @"" : @"\n"); 1397 [_writingDirectionArray removeAllObjects]; 1398 [_attrStr replaceCharactersInRange:rangeToReplace withString:string]; 1399 if (rangeToReplace.location < _domRangeStartIndex) 1400 _domRangeStartIndex += [string length] - rangeToReplace.length; 1401 rangeToReplace.length = [string length]; 1402 NSDictionary *attrs = attributesForElement(element); 1403 if (rangeToReplace.length > 0) 1404 [_attrStr setAttributes:attrs range:rangeToReplace]; 1405 _flags.isSoft = YES; 1406 } 1407} 1408 1409void HTMLConverter::_newLineForElement(Element& element) 1410{ 1411 unichar c = NSLineSeparatorCharacter; 1412 RetainPtr<NSString> string = adoptNS([[NSString alloc] initWithCharacters:&c length:1]); 1413 NSUInteger textLength = [_attrStr length]; 1414 NSRange rangeToReplace = NSMakeRange(textLength, 0); 1415 [_attrStr replaceCharactersInRange:rangeToReplace withString:string.get()]; 1416 rangeToReplace.length = [string length]; 1417 if (rangeToReplace.location < _domRangeStartIndex) 1418 _domRangeStartIndex += rangeToReplace.length; 1419 NSDictionary *attrs = attributesForElement(element); 1420 if (rangeToReplace.length > 0) 1421 [_attrStr setAttributes:attrs range:rangeToReplace]; 1422 _flags.isSoft = YES; 1423} 1424 1425void HTMLConverter::_newTabForElement(Element& element) 1426{ 1427 NSString *string = @"\t"; 1428 NSUInteger textLength = [_attrStr length]; 1429 unichar lastChar = (textLength > 0) ? [[_attrStr string] characterAtIndex:textLength - 1] : '\n'; 1430 NSRange rangeToReplace = (_flags.isSoft && lastChar == ' ') ? NSMakeRange(textLength - 1, 1) : NSMakeRange(textLength, 0); 1431 [_attrStr replaceCharactersInRange:rangeToReplace withString:string]; 1432 rangeToReplace.length = [string length]; 1433 if (rangeToReplace.location < _domRangeStartIndex) 1434 _domRangeStartIndex += rangeToReplace.length; 1435 NSDictionary *attrs = attributesForElement(element); 1436 if (rangeToReplace.length > 0) 1437 [_attrStr setAttributes:attrs range:rangeToReplace]; 1438 _flags.isSoft = YES; 1439} 1440 1441static Class _WebMessageDocumentClass() 1442{ 1443 static Class _WebMessageDocumentClass = Nil; 1444 static BOOL lookedUpClass = NO; 1445 if (!lookedUpClass) { 1446 // If the class is not there, we don't want to try again 1447 _WebMessageDocumentClass = objc_lookUpClass("MFWebMessageDocument"); 1448 if (_WebMessageDocumentClass && ![_WebMessageDocumentClass respondsToSelector:@selector(document:attachment:forURL:)]) 1449 _WebMessageDocumentClass = Nil; 1450 lookedUpClass = YES; 1451 } 1452 return _WebMessageDocumentClass; 1453} 1454 1455BOOL HTMLConverter::_addAttachmentForElement(Element& element, NSURL *url, BOOL needsParagraph, BOOL usePlaceholder) 1456{ 1457 BOOL retval = NO; 1458 BOOL notFound = NO; 1459 NSFileWrapper *fileWrapper = nil; 1460 Frame* frame = element.document().frame(); 1461 DocumentLoader *dataSource = frame->loader().frameHasLoaded() ? frame->loader().documentLoader() : 0; 1462 BOOL ignoreOrientation = YES; 1463 1464 if ([url isFileURL]) { 1465 NSString *path = [[url path] stringByStandardizingPath]; 1466 if (path) 1467 fileWrapper = [[[NSFileWrapper alloc] initWithURL:url options:0 error:NULL] autorelease]; 1468 } 1469 if (!fileWrapper) { 1470 RefPtr<ArchiveResource> resource = dataSource->subresource(url); 1471 if (!resource) 1472 resource = dataSource->subresource(url); 1473 1474 const String& mimeType = resource->mimeType(); 1475 if (usePlaceholder && resource && mimeType == "text/html") 1476 notFound = YES; 1477 if (resource && !notFound) { 1478 fileWrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:resource->data()->createNSData().get()] autorelease]; 1479 [fileWrapper setPreferredFilename:suggestedFilenameWithMIMEType(url, mimeType)]; 1480 } 1481 } 1482#if !PLATFORM(IOS) 1483 if (!fileWrapper && !notFound) { 1484 fileWrapper = fileWrapperForURL(dataSource, url); 1485 if (usePlaceholder && fileWrapper && [[[[fileWrapper preferredFilename] pathExtension] lowercaseString] hasPrefix:@"htm"]) 1486 notFound = YES; 1487 if (notFound) 1488 fileWrapper = nil; 1489 } 1490 if (!fileWrapper && !notFound) { 1491 fileWrapper = fileWrapperForURL(m_dataSource, url); 1492 if (usePlaceholder && fileWrapper && [[[[fileWrapper preferredFilename] pathExtension] lowercaseString] hasPrefix:@"htm"]) 1493 notFound = YES; 1494 if (notFound) 1495 fileWrapper = nil; 1496 } 1497#endif 1498 if (!fileWrapper && !notFound && url) { 1499 // Special handling for Mail attachments, until WebKit provides a standard way to get the data. 1500 Class WebMessageDocumentClass = _WebMessageDocumentClass(); 1501 if (WebMessageDocumentClass) { 1502 NSTextAttachment *mimeTextAttachment = nil; 1503 [WebMessageDocumentClass document:NULL attachment:&mimeTextAttachment forURL:url]; 1504 if (mimeTextAttachment && [mimeTextAttachment respondsToSelector:@selector(fileWrapper)]) { 1505 fileWrapper = [mimeTextAttachment performSelector:@selector(fileWrapper)]; 1506 ignoreOrientation = NO; 1507 } 1508 } 1509 } 1510 if (fileWrapper || usePlaceholder) { 1511 NSUInteger textLength = [_attrStr length]; 1512 RetainPtr<NSTextAttachment> attachment = adoptNS([[PlatformNSTextAttachment alloc] initWithFileWrapper:fileWrapper]); 1513#if PLATFORM(IOS) 1514 float verticalAlign = 0.0; 1515 _caches->floatPropertyValueForNode(element, CSSPropertyVerticalAlign, verticalAlign); 1516 attachment.get().bounds = CGRectMake(0, (verticalAlign / 100) * element.clientHeight(), element.clientWidth(), element.clientHeight()); 1517#endif 1518 RetainPtr<NSString> string = adoptNS([[NSString alloc] initWithFormat:(needsParagraph ? @"%C\n" : @"%C"), static_cast<unichar>(NSAttachmentCharacter)]); 1519 NSRange rangeToReplace = NSMakeRange(textLength, 0); 1520 NSDictionary *attrs; 1521 if (fileWrapper) { 1522#if !PLATFORM(IOS) 1523 if (ignoreOrientation) 1524 [attachment setIgnoresOrientation:YES]; 1525#endif 1526 } else { 1527#if PLATFORM(IOS) 1528 [attachment release]; 1529 NSURL *missingImageURL = [_webKitBundle() URLForResource:@"missing_image" withExtension:@"tiff"]; 1530 ASSERT_WITH_MESSAGE(missingImageURL != nil, "Unable to find missing_image.tiff!"); 1531 NSFileWrapper *missingImageFileWrapper = [[[NSFileWrapper alloc] initWithURL:missingImageURL options:0 error:NULL] autorelease]; 1532 attachment = [[PlatformNSTextAttachment alloc] initWithFileWrapper:missingImageFileWrapper]; 1533#else 1534 static NSImage *missingImage = nil; 1535 NSTextAttachmentCell *cell; 1536 cell = [[NSTextAttachmentCell alloc] initImageCell:missingImage]; 1537 [attachment setAttachmentCell:cell]; 1538 [cell release]; 1539#endif 1540 } 1541 [_attrStr replaceCharactersInRange:rangeToReplace withString:string.get()]; 1542 rangeToReplace.length = [string length]; 1543 if (rangeToReplace.location < _domRangeStartIndex) 1544 _domRangeStartIndex += rangeToReplace.length; 1545 attrs = attributesForElement(element); 1546 if (rangeToReplace.length > 0) { 1547 [_attrStr setAttributes:attrs range:rangeToReplace]; 1548 rangeToReplace.length = 1; 1549 [_attrStr addAttribute:NSAttachmentAttributeName value:attachment.get() range:rangeToReplace]; 1550 } 1551 _flags.isSoft = NO; 1552 retval = YES; 1553 } 1554 return retval; 1555} 1556 1557void HTMLConverter::_addQuoteForElement(Element& element, BOOL opening, NSInteger level) 1558{ 1559 unichar c = ((level % 2) == 0) ? (opening ? 0x201c : 0x201d) : (opening ? 0x2018 : 0x2019); 1560 RetainPtr<NSString> string = adoptNS([[NSString alloc] initWithCharacters:&c length:1]); 1561 NSUInteger textLength = [_attrStr length]; 1562 NSRange rangeToReplace = NSMakeRange(textLength, 0); 1563 [_attrStr replaceCharactersInRange:rangeToReplace withString:string.get()]; 1564 rangeToReplace.length = [string length]; 1565 if (rangeToReplace.location < _domRangeStartIndex) 1566 _domRangeStartIndex += rangeToReplace.length; 1567 RetainPtr<NSDictionary> attrs = attributesForElement(element); 1568 if (rangeToReplace.length > 0) 1569 [_attrStr setAttributes:attrs.get() range:rangeToReplace]; 1570 _flags.isSoft = NO; 1571} 1572 1573void HTMLConverter::_addValue(NSString *value, Element& element) 1574{ 1575 NSUInteger textLength = [_attrStr length]; 1576 NSUInteger valueLength = [value length]; 1577 NSRange rangeToReplace = NSMakeRange(textLength, 0); 1578 if (valueLength) { 1579 [_attrStr replaceCharactersInRange:rangeToReplace withString:value]; 1580 rangeToReplace.length = valueLength; 1581 if (rangeToReplace.location < _domRangeStartIndex) 1582 _domRangeStartIndex += rangeToReplace.length; 1583 RetainPtr<NSDictionary> attrs = attributesForElement(element); 1584 if (rangeToReplace.length > 0) 1585 [_attrStr setAttributes:attrs.get() range:rangeToReplace]; 1586 _flags.isSoft = NO; 1587 } 1588} 1589 1590void HTMLConverter::_fillInBlock(NSTextBlock *block, Element& element, PlatformColor *backgroundColor, CGFloat extraMargin, CGFloat extraPadding, BOOL isTable) 1591{ 1592 float result = 0; 1593 1594 NSString *width = element.getAttribute(widthAttr); 1595 if ((width && [width length]) || !isTable) { 1596 if (_caches->floatPropertyValueForNode(element, CSSPropertyWidth, result)) 1597 [block setValue:result type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockWidth]; 1598 } 1599 1600 if (_caches->floatPropertyValueForNode(element, CSSPropertyMinWidth, result)) 1601 [block setValue:result type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockMinimumWidth]; 1602 if (_caches->floatPropertyValueForNode(element, CSSPropertyMaxWidth, result)) 1603 [block setValue:result type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockMaximumWidth]; 1604 if (_caches->floatPropertyValueForNode(element, CSSPropertyMinHeight, result)) 1605 [block setValue:result type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockMinimumHeight]; 1606 if (_caches->floatPropertyValueForNode(element, CSSPropertyMaxHeight, result)) 1607 [block setValue:result type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockMaximumHeight]; 1608 1609 if (_caches->floatPropertyValueForNode(element, CSSPropertyPaddingLeft, result)) 1610 [block setWidth:result + extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMinXEdge]; 1611 else 1612 [block setWidth:extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMinXEdge]; 1613 1614 if (_caches->floatPropertyValueForNode(element, CSSPropertyPaddingTop, result)) 1615 [block setWidth:result + extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMinYEdge]; 1616 else 1617 [block setWidth:extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMinYEdge]; 1618 1619 if (_caches->floatPropertyValueForNode(element, CSSPropertyPaddingRight, result)) 1620 [block setWidth:result + extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMaxXEdge]; 1621 else 1622 [block setWidth:extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMaxXEdge]; 1623 1624 if (_caches->floatPropertyValueForNode(element, CSSPropertyPaddingBottom, result)) 1625 [block setWidth:result + extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMaxYEdge]; 1626 else 1627 [block setWidth:extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMaxYEdge]; 1628 1629 if (_caches->floatPropertyValueForNode(element, CSSPropertyBorderLeftWidth, result)) 1630 [block setWidth:result type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockBorder edge:NSMinXEdge]; 1631 if (_caches->floatPropertyValueForNode(element, CSSPropertyBorderTopWidth, result)) 1632 [block setWidth:result type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockBorder edge:NSMinYEdge]; 1633 if (_caches->floatPropertyValueForNode(element, CSSPropertyBorderRightWidth, result)) 1634 [block setWidth:result type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockBorder edge:NSMaxXEdge]; 1635 if (_caches->floatPropertyValueForNode(element, CSSPropertyBorderBottomWidth, result)) 1636 [block setWidth:result type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockBorder edge:NSMaxYEdge]; 1637 1638 if (_caches->floatPropertyValueForNode(element, CSSPropertyMarginLeft, result)) 1639 [block setWidth:result + extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMinXEdge]; 1640 else 1641 [block setWidth:extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMinXEdge]; 1642 if (_caches->floatPropertyValueForNode(element, CSSPropertyMarginTop, result)) 1643 [block setWidth:result + extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMinYEdge]; 1644 else 1645 [block setWidth:extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMinYEdge]; 1646 if (_caches->floatPropertyValueForNode(element, CSSPropertyMarginRight, result)) 1647 [block setWidth:result + extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMaxXEdge]; 1648 else 1649 [block setWidth:extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMaxXEdge]; 1650 if (_caches->floatPropertyValueForNode(element, CSSPropertyMarginBottom, result)) 1651 [block setWidth:result + extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMaxYEdge]; 1652 else 1653 [block setWidth:extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMaxYEdge]; 1654 1655 PlatformColor *color = nil; 1656 if ((color = _colorForElement(element, CSSPropertyBackgroundColor))) 1657 [block setBackgroundColor:color]; 1658 if (!color && backgroundColor) 1659 [block setBackgroundColor:backgroundColor]; 1660 1661 if ((color = _colorForElement(element, CSSPropertyBorderLeftColor))) 1662 [block setBorderColor:color forEdge:NSMinXEdge]; 1663 1664 if ((color = _colorForElement(element, CSSPropertyBorderTopColor))) 1665 [block setBorderColor:color forEdge:NSMinYEdge]; 1666 if ((color = _colorForElement(element, CSSPropertyBorderRightColor))) 1667 [block setBorderColor:color forEdge:NSMaxXEdge]; 1668 if ((color = _colorForElement(element, CSSPropertyBorderBottomColor))) 1669 [block setBorderColor:color forEdge:NSMaxYEdge]; 1670} 1671 1672static inline BOOL read2DigitNumber(const char **pp, int8_t *outval) 1673{ 1674 BOOL result = NO; 1675 char c1 = *(*pp)++, c2; 1676 if (isASCIIDigit(c1)) { 1677 c2 = *(*pp)++; 1678 if (isASCIIDigit(c2)) { 1679 *outval = 10 * (c1 - '0') + (c2 - '0'); 1680 result = YES; 1681 } 1682 } 1683 return result; 1684} 1685 1686static inline NSDate *_dateForString(NSString *string) 1687{ 1688 const char *p = [string UTF8String]; 1689 RetainPtr<NSDateComponents> dateComponents = adoptNS([[NSDateComponents alloc] init]); 1690 1691 // Set the time zone to GMT 1692 [dateComponents setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; 1693 1694 NSInteger year = 0; 1695 while (*p && isASCIIDigit(*p)) 1696 year = 10 * year + *p++ - '0'; 1697 if (*p++ != '-') 1698 return nil; 1699 [dateComponents setYear:year]; 1700 1701 int8_t component; 1702 if (!read2DigitNumber(&p, &component) || *p++ != '-') 1703 return nil; 1704 [dateComponents setMonth:component]; 1705 1706 if (!read2DigitNumber(&p, &component) || *p++ != 'T') 1707 return nil; 1708 [dateComponents setDay:component]; 1709 1710 if (!read2DigitNumber(&p, &component) || *p++ != ':') 1711 return nil; 1712 [dateComponents setHour:component]; 1713 1714 if (!read2DigitNumber(&p, &component) || *p++ != ':') 1715 return nil; 1716 [dateComponents setMinute:component]; 1717 1718 if (!read2DigitNumber(&p, &component) || *p++ != 'Z') 1719 return nil; 1720 [dateComponents setSecond:component]; 1721 1722#if (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090) 1723 NSString *calendarIdentifier = NSCalendarIdentifierGregorian; 1724#else 1725 NSString *calendarIdentifier = NSGregorianCalendar; 1726#endif 1727 1728 return [[[[NSCalendar alloc] initWithCalendarIdentifier:calendarIdentifier] autorelease] dateFromComponents:dateComponents.get()]; 1729} 1730 1731static NSInteger _colCompare(id block1, id block2, void *) 1732{ 1733 NSInteger col1 = [(NSTextTableBlock *)block1 startingColumn]; 1734 NSInteger col2 = [(NSTextTableBlock *)block2 startingColumn]; 1735 return ((col1 < col2) ? NSOrderedAscending : ((col1 == col2) ? NSOrderedSame : NSOrderedDescending)); 1736} 1737 1738void HTMLConverter::_processMetaElementWithName(NSString *name, NSString *content) 1739{ 1740 NSString *key = nil; 1741 if (NSOrderedSame == [@"CocoaVersion" compare:name options:NSCaseInsensitiveSearch]) { 1742 CGFloat versionNumber = [content doubleValue]; 1743 if (versionNumber > 0.0) { 1744 // ??? this should be keyed off of version number in future 1745 [_documentAttrs removeObjectForKey:NSConvertedDocumentAttribute]; 1746 [_documentAttrs setObject:[NSNumber numberWithDouble:versionNumber] forKey:NSCocoaVersionDocumentAttribute]; 1747 } 1748#if PLATFORM(IOS) 1749 } else if (NSOrderedSame == [@"Generator" compare:name options:NSCaseInsensitiveSearch]) { 1750 key = NSGeneratorDocumentAttribute; 1751#endif 1752 } else if (NSOrderedSame == [@"Keywords" compare:name options:NSCaseInsensitiveSearch]) { 1753 if (content && [content length] > 0) { 1754 NSArray *array; 1755 // ??? need better handling here and throughout 1756 if ([content rangeOfString:@", "].length == 0 && [content rangeOfString:@","].length > 0) 1757 array = [content componentsSeparatedByString:@","]; 1758 else if ([content rangeOfString:@", "].length == 0 && [content rangeOfString:@" "].length > 0) 1759 array = [content componentsSeparatedByString:@" "]; 1760 else 1761 array = [content componentsSeparatedByString:@", "]; 1762 [_documentAttrs setObject:array forKey:NSKeywordsDocumentAttribute]; 1763 } 1764 } else if (NSOrderedSame == [@"Author" compare:name options:NSCaseInsensitiveSearch]) 1765 key = NSAuthorDocumentAttribute; 1766 else if (NSOrderedSame == [@"LastAuthor" compare:name options:NSCaseInsensitiveSearch]) 1767 key = NSEditorDocumentAttribute; 1768 else if (NSOrderedSame == [@"Company" compare:name options:NSCaseInsensitiveSearch]) 1769 key = NSCompanyDocumentAttribute; 1770 else if (NSOrderedSame == [@"Copyright" compare:name options:NSCaseInsensitiveSearch]) 1771 key = NSCopyrightDocumentAttribute; 1772 else if (NSOrderedSame == [@"Subject" compare:name options:NSCaseInsensitiveSearch]) 1773 key = NSSubjectDocumentAttribute; 1774 else if (NSOrderedSame == [@"Description" compare:name options:NSCaseInsensitiveSearch] || NSOrderedSame == [@"Comment" compare:name options:NSCaseInsensitiveSearch]) 1775 key = NSCommentDocumentAttribute; 1776 else if (NSOrderedSame == [@"CreationTime" compare:name options:NSCaseInsensitiveSearch]) { 1777 if (content && [content length] > 0) { 1778 NSDate *date = _dateForString(content); 1779 if (date) 1780 [_documentAttrs setObject:date forKey:NSCreationTimeDocumentAttribute]; 1781 } 1782 } else if (NSOrderedSame == [@"ModificationTime" compare:name options:NSCaseInsensitiveSearch]) { 1783 if (content && [content length] > 0) { 1784 NSDate *date = _dateForString(content); 1785 if (date) 1786 [_documentAttrs setObject:date forKey:NSModificationTimeDocumentAttribute]; 1787 } 1788 } 1789#if PLATFORM(IOS) 1790 else if (NSOrderedSame == [@"DisplayName" compare:name options:NSCaseInsensitiveSearch] || NSOrderedSame == [@"IndexTitle" compare:name options:NSCaseInsensitiveSearch]) 1791 key = NSDisplayNameDocumentAttribute; 1792 else if (NSOrderedSame == [@"robots" compare:name options:NSCaseInsensitiveSearch]) { 1793 if ([content rangeOfString:@"noindex" options:NSCaseInsensitiveSearch].length > 0) 1794 [_documentAttrs setObject:[NSNumber numberWithInteger:1] forKey:NSNoIndexDocumentAttribute]; 1795 } 1796#endif 1797 if (key && content && [content length] > 0) 1798 [_documentAttrs setObject:content forKey:key]; 1799} 1800 1801void HTMLConverter::_processHeadElement(Element& element) 1802{ 1803 // FIXME: Should gather data from other sources e.g. Word, but for that we would need to be able to get comments from DOM 1804 1805 for (HTMLMetaElement* child = Traversal<HTMLMetaElement>::firstChild(&element); child; child = Traversal<HTMLMetaElement>::nextSibling(child)) { 1806 NSString *name = child->name(); 1807 NSString *content = child->content(); 1808 if (name && content) 1809 _processMetaElementWithName(name, content); 1810 } 1811} 1812 1813BOOL HTMLConverter::_enterElement(Element& element, BOOL embedded) 1814{ 1815 String displayValue = _caches->propertyValueForNode(element, CSSPropertyDisplay); 1816 1817 if (element.hasTagName(headTag) && !embedded) 1818 _processHeadElement(element); 1819 else if (!displayValue.length() || !(displayValue == "none" || displayValue == "table-column" || displayValue == "table-column-group")) { 1820 if (_caches->isBlockElement(element) && !element.hasTagName(brTag) && !(displayValue == "table-cell" && [_textTables count] == 0) 1821 && !([_textLists count] > 0 && displayValue == "block" && !element.hasTagName(liTag) && !element.hasTagName(ulTag) && !element.hasTagName(olTag))) 1822 _newParagraphForElement(element, element.tagName(), NO, YES); 1823 return YES; 1824 } 1825 return NO; 1826} 1827 1828void HTMLConverter::_addTableForElement(Element *tableElement) 1829{ 1830 RetainPtr<NSTextTable> table = adoptNS([[PlatformNSTextTable alloc] init]); 1831 CGFloat cellSpacingVal = 1; 1832 CGFloat cellPaddingVal = 1; 1833 [table setNumberOfColumns:1]; 1834 [table setLayoutAlgorithm:NSTextTableAutomaticLayoutAlgorithm]; 1835 [table setCollapsesBorders:NO]; 1836 [table setHidesEmptyCells:NO]; 1837 1838 if (tableElement) { 1839 ASSERT(tableElement); 1840 Element& coreTableElement = *tableElement; 1841 1842 NSString *cellSpacing = coreTableElement.getAttribute(cellspacingAttr); 1843 if (cellSpacing && [cellSpacing length] > 0 && ![cellSpacing hasSuffix:@"%"]) 1844 cellSpacingVal = [cellSpacing floatValue]; 1845 NSString *cellPadding = coreTableElement.getAttribute(cellpaddingAttr); 1846 if (cellPadding && [cellPadding length] > 0 && ![cellPadding hasSuffix:@"%"]) 1847 cellPaddingVal = [cellPadding floatValue]; 1848 1849 _fillInBlock(table.get(), coreTableElement, nil, 0, 0, YES); 1850 1851 if (_caches->propertyValueForNode(coreTableElement, CSSPropertyBorderCollapse) == "collapse") { 1852 [table setCollapsesBorders:YES]; 1853 cellSpacingVal = 0; 1854 } 1855 if (_caches->propertyValueForNode(coreTableElement, CSSPropertyEmptyCells) == "hide") 1856 [table setHidesEmptyCells:YES]; 1857 if (_caches->propertyValueForNode(coreTableElement, CSSPropertyTableLayout) == "fixed") 1858 [table setLayoutAlgorithm:NSTextTableFixedLayoutAlgorithm]; 1859 } 1860 1861 [_textTables addObject:table.get()]; 1862 [_textTableSpacings addObject:[NSNumber numberWithDouble:cellSpacingVal]]; 1863 [_textTablePaddings addObject:[NSNumber numberWithDouble:cellPaddingVal]]; 1864 [_textTableRows addObject:[NSNumber numberWithInteger:0]]; 1865 [_textTableRowArrays addObject:[NSMutableArray array]]; 1866} 1867 1868void HTMLConverter::_addTableCellForElement(Element* element) 1869{ 1870 NSTextTable *table = [_textTables lastObject]; 1871 NSInteger rowNumber = [[_textTableRows lastObject] integerValue]; 1872 NSInteger columnNumber = 0; 1873 NSInteger rowSpan = 1; 1874 NSInteger colSpan = 1; 1875 NSMutableArray *rowArray = [_textTableRowArrays lastObject]; 1876 NSUInteger count = [rowArray count]; 1877 PlatformColor *color = ([_textTableRowBackgroundColors count] > 0) ? [_textTableRowBackgroundColors lastObject] : nil; 1878 NSTextTableBlock *previousBlock; 1879 CGFloat cellSpacingVal = [[_textTableSpacings lastObject] floatValue]; 1880 if ([color isEqual:[PlatformColorClass clearColor]]) color = nil; 1881 for (NSUInteger i = 0; i < count; i++) { 1882 previousBlock = [rowArray objectAtIndex:i]; 1883 if (columnNumber >= [previousBlock startingColumn] && columnNumber < [previousBlock startingColumn] + [previousBlock columnSpan]) 1884 columnNumber = [previousBlock startingColumn] + [previousBlock columnSpan]; 1885 } 1886 1887 RetainPtr<NSTextTableBlock> block; 1888 1889 if (element) { 1890 if (isHTMLTableCellElement(*element)) { 1891 HTMLTableCellElement& tableCellElement = toHTMLTableCellElement(*element); 1892 1893 rowSpan = tableCellElement.rowSpan(); 1894 if (rowSpan < 1) 1895 rowSpan = 1; 1896 colSpan = tableCellElement.colSpan(); 1897 if (colSpan < 1) 1898 colSpan = 1; 1899 } 1900 1901 block = adoptNS([[PlatformNSTextTableBlock alloc] initWithTable:table startingRow:rowNumber rowSpan:rowSpan startingColumn:columnNumber columnSpan:colSpan]); 1902 1903 String verticalAlign = _caches->propertyValueForNode(*element, CSSPropertyVerticalAlign); 1904 1905 _fillInBlock(block.get(), *element, color, cellSpacingVal / 2, 0, NO); 1906 if (verticalAlign == "middle") 1907 [block setVerticalAlignment:NSTextBlockMiddleAlignment]; 1908 else if (verticalAlign == "bottom") 1909 [block setVerticalAlignment:NSTextBlockBottomAlignment]; 1910 else if (verticalAlign == "baseline") 1911 [block setVerticalAlignment:NSTextBlockBaselineAlignment]; 1912 else if (verticalAlign == "top") 1913 [block setVerticalAlignment:NSTextBlockTopAlignment]; 1914 } else { 1915 block = adoptNS([[PlatformNSTextTableBlock alloc] initWithTable:table startingRow:rowNumber rowSpan:rowSpan startingColumn:columnNumber columnSpan:colSpan]); 1916 } 1917 1918 [_textBlocks addObject:block.get()]; 1919 [rowArray addObject:block.get()]; 1920 [rowArray sortUsingFunction:_colCompare context:NULL]; 1921} 1922 1923BOOL HTMLConverter::_processElement(Element& element, NSInteger depth) 1924{ 1925 BOOL retval = YES; 1926 BOOL isBlockLevel = _caches->isBlockElement(element); 1927 String displayValue = _caches->propertyValueForNode(element, CSSPropertyDisplay); 1928 if (isBlockLevel) 1929 [_writingDirectionArray removeAllObjects]; 1930 else { 1931 String bidi = _caches->propertyValueForNode(element, CSSPropertyUnicodeBidi); 1932 if (bidi == "embed") { 1933 NSUInteger val = NSTextWritingDirectionEmbedding; 1934 if (_caches->propertyValueForNode(element, CSSPropertyDirection) == "rtl") 1935 val |= NSWritingDirectionRightToLeft; 1936 [_writingDirectionArray addObject:[NSNumber numberWithUnsignedInteger:val]]; 1937 } else if (bidi == "bidi-override") { 1938 NSUInteger val = NSTextWritingDirectionOverride; 1939 if (_caches->propertyValueForNode(element, CSSPropertyDirection) == "rtl") 1940 val |= NSWritingDirectionRightToLeft; 1941 [_writingDirectionArray addObject:[NSNumber numberWithUnsignedInteger:val]]; 1942 } 1943 } 1944 if (displayValue == "table" || ([_textTables count] == 0 && displayValue == "table-row-group")) { 1945 Element* tableElement = &element; 1946 if (displayValue == "table-row-group") { 1947 // If we are starting in medias res, the first thing we see may be the tbody, so go up to the table 1948 tableElement = _blockLevelElementForNode(element.parentNode()); 1949 if (!tableElement || _caches->propertyValueForNode(*tableElement, CSSPropertyDisplay) != "table") 1950 tableElement = &element; 1951 } 1952 while ([_textTables count] > [_textBlocks count]) 1953 _addTableCellForElement(nil); 1954 _addTableForElement(tableElement); 1955 } else if (displayValue == "table-footer-group" && [_textTables count] > 0) { 1956 m_textTableFooters.add([_textTables lastObject], &element); 1957 retval = NO; 1958 } else if (displayValue == "table-row" && [_textTables count] > 0) { 1959 PlatformColor *color = _colorForElement(element, CSSPropertyBackgroundColor); 1960 if (!color) 1961 color = [PlatformColorClass clearColor]; 1962 [_textTableRowBackgroundColors addObject:color]; 1963 } else if (displayValue == "table-cell") { 1964 while ([_textTables count] < [_textBlocks count] + 1) 1965 _addTableForElement(nil); 1966 _addTableCellForElement(&element); 1967 } else if (element.hasTagName(imgTag)) { 1968 NSString *urlString = element.getAttribute(srcAttr); 1969 if (urlString && [urlString length] > 0) { 1970 NSURL *url = element.document().completeURL(stripLeadingAndTrailingHTMLSpaces(urlString)); 1971 if (!url) 1972 url = [NSURL _web_URLWithString:[urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] relativeToURL:_baseURL]; 1973#if PLATFORM(IOS) 1974 BOOL usePlaceholderImage = NO; 1975#else 1976 BOOL usePlaceholderImage = YES; 1977#endif 1978 if (url) 1979 _addAttachmentForElement(element, url, isBlockLevel, usePlaceholderImage); 1980 } 1981 retval = NO; 1982 } else if (element.hasTagName(objectTag)) { 1983 NSString *baseString = element.getAttribute(codebaseAttr); 1984 NSString *urlString = element.getAttribute(dataAttr); 1985 NSString *declareString = element.getAttribute(declareAttr); 1986 if (urlString && [urlString length] > 0 && ![@"true" isEqualToString:declareString]) { 1987 NSURL *baseURL = nil; 1988 NSURL *url = nil; 1989 if (baseString && [baseString length] > 0) { 1990 baseURL = element.document().completeURL(stripLeadingAndTrailingHTMLSpaces(baseString)); 1991 if (!baseURL) 1992 baseURL = [NSURL _web_URLWithString:[baseString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] relativeToURL:_baseURL]; 1993 } 1994 if (baseURL) 1995 url = [NSURL _web_URLWithString:[urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] relativeToURL:baseURL]; 1996 if (!url) 1997 url = element.document().completeURL(stripLeadingAndTrailingHTMLSpaces(urlString)); 1998 if (!url) 1999 url = [NSURL _web_URLWithString:[urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] relativeToURL:_baseURL]; 2000 if (url) 2001 retval = !_addAttachmentForElement(element, url, isBlockLevel, NO); 2002 } 2003 } else if (element.hasTagName(frameTag) || element.hasTagName(iframeTag)) { 2004 if (Document* contentDocument = toHTMLFrameElementBase(element).contentDocument()) { 2005 _traverseNode(*contentDocument, depth + 1, true /* embedded */); 2006 retval = NO; 2007 } 2008 } else if (element.hasTagName(brTag)) { 2009 Element* blockElement = _blockLevelElementForNode(element.parentNode()); 2010 NSString *breakClass = element.getAttribute(classAttr); 2011 NSString *blockTag = blockElement ? (NSString *)blockElement->tagName() : nil; 2012 BOOL isExtraBreak = [@"Apple-interchange-newline" isEqualToString:breakClass]; 2013 BOOL blockElementIsParagraph = ([@"P" isEqualToString:blockTag] || [@"LI" isEqualToString:blockTag] || ([blockTag hasPrefix:@"H"] && 2 == [blockTag length])); 2014 if (isExtraBreak) 2015 _flags.hasTrailingNewline = YES; 2016 else { 2017 if (blockElement && blockElementIsParagraph) 2018 _newLineForElement(element); 2019 else 2020 _newParagraphForElement(element, element.tagName(), YES, NO); 2021 } 2022 } else if (element.hasTagName(ulTag)) { 2023 RetainPtr<NSTextList> list; 2024 String listStyleType = _caches->propertyValueForNode(element, CSSPropertyListStyleType); 2025 if (!listStyleType.length()) 2026 listStyleType = @"disc"; 2027 list = adoptNS([[PlatformNSTextList alloc] initWithMarkerFormat:String("{" + listStyleType + "}") options:0]); 2028 [_textLists addObject:list.get()]; 2029 } else if (element.hasTagName(olTag)) { 2030 RetainPtr<NSTextList> list; 2031 String listStyleType = _caches->propertyValueForNode(element, CSSPropertyListStyleType); 2032 if (!listStyleType.length()) 2033 listStyleType = "decimal"; 2034 list = adoptNS([[PlatformNSTextList alloc] initWithMarkerFormat:String("{" + listStyleType + "}") options:0]); 2035 if (isHTMLOListElement(element)) { 2036 NSInteger startingItemNumber = toHTMLOListElement(element).start();; 2037 [list setStartingItemNumber:startingItemNumber]; 2038 } 2039 [_textLists addObject:list.get()]; 2040 } else if (element.hasTagName(qTag)) { 2041 _addQuoteForElement(element, YES, _quoteLevel++); 2042 } else if (element.hasTagName(inputTag)) { 2043 if (isHTMLInputElement(element)) { 2044 HTMLInputElement& inputElement = toHTMLInputElement(element); 2045 if (inputElement.type() == "text") { 2046 NSString *value = inputElement.value(); 2047 if (value && [value length] > 0) 2048 _addValue(value, element); 2049 } 2050 } 2051 } else if (element.hasTagName(textareaTag)) { 2052 if (isHTMLTextAreaElement(element)) { 2053 HTMLTextAreaElement& textAreaElement = toHTMLTextAreaElement(element); 2054 NSString *value = textAreaElement.value(); 2055 if (value && [value length] > 0) 2056 _addValue(value, element); 2057 } 2058 retval = NO; 2059 } 2060 return retval; 2061} 2062 2063void HTMLConverter::_addMarkersToList(NSTextList *list, NSRange range) 2064{ 2065 NSInteger itemNum = [list startingItemNumber]; 2066 NSString *string = [_attrStr string]; 2067 NSString *stringToInsert; 2068 NSDictionary *attrsToInsert = nil; 2069 PlatformFont *font; 2070 NSParagraphStyle *paragraphStyle; 2071 NSMutableParagraphStyle *newStyle; 2072 NSTextTab *tab = nil; 2073 NSTextTab *tabToRemove; 2074 NSRange paragraphRange; 2075 NSRange styleRange; 2076 NSUInteger textLength = [_attrStr length]; 2077 NSUInteger listIndex; 2078 NSUInteger insertLength; 2079 NSUInteger i; 2080 NSUInteger count; 2081 NSArray *textLists; 2082 CGFloat markerLocation; 2083 CGFloat listLocation; 2084 CGFloat pointSize; 2085 2086 if (range.length == 0 || range.location >= textLength) 2087 return; 2088 if (NSMaxRange(range) > textLength) 2089 range.length = textLength - range.location; 2090 paragraphStyle = [_attrStr attribute:NSParagraphStyleAttributeName atIndex:range.location effectiveRange:NULL]; 2091 if (paragraphStyle) { 2092 textLists = [paragraphStyle textLists]; 2093 listIndex = [textLists indexOfObject:list]; 2094 if (textLists && listIndex != NSNotFound) { 2095 for (NSUInteger idx = range.location; idx < NSMaxRange(range);) { 2096 paragraphRange = [string paragraphRangeForRange:NSMakeRange(idx, 0)]; 2097 paragraphStyle = [_attrStr attribute:NSParagraphStyleAttributeName atIndex:idx effectiveRange:&styleRange]; 2098 font = [_attrStr attribute:NSFontAttributeName atIndex:idx effectiveRange:NULL]; 2099 pointSize = font ? [font pointSize] : 12; 2100 if ([[paragraphStyle textLists] count] == listIndex + 1) { 2101 stringToInsert = [NSString stringWithFormat:@"\t%@\t", [list markerForItemNumber:itemNum++]]; 2102 insertLength = [stringToInsert length]; 2103 attrsToInsert = [PlatformNSTextList _standardMarkerAttributesForAttributes:[_attrStr attributesAtIndex:paragraphRange.location effectiveRange:NULL]]; 2104 2105 [_attrStr replaceCharactersInRange:NSMakeRange(paragraphRange.location, 0) withString:stringToInsert]; 2106 [_attrStr setAttributes:attrsToInsert range:NSMakeRange(paragraphRange.location, insertLength)]; 2107 range.length += insertLength; 2108 paragraphRange.length += insertLength; 2109 if (paragraphRange.location < _domRangeStartIndex) 2110 _domRangeStartIndex += insertLength; 2111 2112 newStyle = [paragraphStyle mutableCopy]; 2113 listLocation = (listIndex + 1) * 36; 2114 markerLocation = listLocation - 25; 2115 [newStyle setFirstLineHeadIndent:0]; 2116 [newStyle setHeadIndent:listLocation]; 2117 while ((count = [[newStyle tabStops] count]) > 0) { 2118 for (i = 0, tabToRemove = nil; !tabToRemove && i < count; i++) { 2119 tab = [[newStyle tabStops] objectAtIndex:i]; 2120 if ([tab location] <= listLocation) 2121 tabToRemove = tab; 2122 } 2123 if (tabToRemove) 2124 [newStyle removeTabStop:tab]; 2125 else 2126 break; 2127 } 2128 tab = [[PlatformNSTextTab alloc] initWithType:NSLeftTabStopType location:markerLocation]; 2129 [newStyle addTabStop:tab]; 2130 [tab release]; 2131#if PLATFORM(IOS) 2132 tab = [[PlatformNSTextTab alloc] initWithTextAlignment:NSTextAlignmentNatural location:listLocation options:nil]; 2133#else 2134 tab = [[PlatformNSTextTab alloc] initWithTextAlignment:NSNaturalTextAlignment location:listLocation options:nil]; 2135#endif 2136 [newStyle addTabStop:tab]; 2137 [tab release]; 2138 [_attrStr addAttribute:NSParagraphStyleAttributeName value:newStyle range:paragraphRange]; 2139 [newStyle release]; 2140 2141 idx = NSMaxRange(paragraphRange); 2142 } else { 2143 // skip any deeper-nested lists 2144 idx = NSMaxRange(styleRange); 2145 } 2146 } 2147 } 2148 } 2149} 2150 2151void HTMLConverter::_exitElement(Element& element, NSInteger depth, NSUInteger startIndex) 2152{ 2153 String displayValue = _caches->propertyValueForNode(element, CSSPropertyDisplay); 2154 NSRange range = NSMakeRange(startIndex, [_attrStr length] - startIndex); 2155 if (range.length > 0 && element.hasTagName(aTag)) { 2156 NSString *urlString = element.getAttribute(hrefAttr); 2157 NSString *strippedString = [urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 2158 if (urlString && [urlString length] > 0 && strippedString && [strippedString length] > 0 && ![strippedString hasPrefix:@"#"]) { 2159 NSURL *url = element.document().completeURL(stripLeadingAndTrailingHTMLSpaces(urlString)); 2160 if (!url) 2161 url = element.document().completeURL(stripLeadingAndTrailingHTMLSpaces(strippedString)); 2162 if (!url) 2163 url = [NSURL _web_URLWithString:strippedString relativeToURL:_baseURL]; 2164 [_attrStr addAttribute:NSLinkAttributeName value:url ? (id)url : (id)urlString range:range]; 2165 } 2166 } 2167 if (!_flags.reachedEnd && _caches->isBlockElement(element)) { 2168 [_writingDirectionArray removeAllObjects]; 2169 if (displayValue == "table-cell" && [_textBlocks count] == 0) { 2170 _newTabForElement(element); 2171 } else if ([_textLists count] > 0 && displayValue == "block" && !element.hasTagName(liTag) && !element.hasTagName(ulTag) && !element.hasTagName(olTag)) { 2172 _newLineForElement(element); 2173 } else { 2174 _newParagraphForElement(element, element.tagName(), (range.length == 0), YES); 2175 } 2176 } else if ([_writingDirectionArray count] > 0) { 2177 String bidi = _caches->propertyValueForNode(element, CSSPropertyUnicodeBidi); 2178 if (bidi == "embed" || bidi == "bidi-override") 2179 [_writingDirectionArray removeLastObject]; 2180 } 2181 range = NSMakeRange(startIndex, [_attrStr length] - startIndex); 2182 if (displayValue == "table" && [_textTables count] > 0) { 2183 NSTextTable *key = [_textTables lastObject]; 2184 Element* footer = m_textTableFooters.get(key); 2185 while ([_textTables count] < [_textBlocks count] + 1) 2186 [_textBlocks removeLastObject]; 2187 if (footer) { 2188 _traverseFooterNode(*footer, depth + 1); 2189 m_textTableFooters.remove(key); 2190 } 2191 [_textTables removeLastObject]; 2192 [_textTableSpacings removeLastObject]; 2193 [_textTablePaddings removeLastObject]; 2194 [_textTableRows removeLastObject]; 2195 [_textTableRowArrays removeLastObject]; 2196 } else if (displayValue == "table-row" && [_textTables count] > 0) { 2197 NSTextTable *table = [_textTables lastObject]; 2198 NSTextTableBlock *block; 2199 NSMutableArray *rowArray = [_textTableRowArrays lastObject], *previousRowArray; 2200 NSUInteger i, count; 2201 NSInteger numberOfColumns = [table numberOfColumns]; 2202 NSInteger openColumn; 2203 NSInteger rowNumber = [[_textTableRows lastObject] integerValue]; 2204 do { 2205 rowNumber++; 2206 previousRowArray = rowArray; 2207 rowArray = [NSMutableArray array]; 2208 count = [previousRowArray count]; 2209 for (i = 0; i < count; i++) { 2210 block = [previousRowArray objectAtIndex:i]; 2211 if ([block startingColumn] + [block columnSpan] > numberOfColumns) numberOfColumns = [block startingColumn] + [block columnSpan]; 2212 if ([block startingRow] + [block rowSpan] > rowNumber) [rowArray addObject:block]; 2213 } 2214 count = [rowArray count]; 2215 openColumn = 0; 2216 for (i = 0; i < count; i++) { 2217 block = [rowArray objectAtIndex:i]; 2218 if (openColumn >= [block startingColumn] && openColumn < [block startingColumn] + [block columnSpan]) openColumn = [block startingColumn] + [block columnSpan]; 2219 } 2220 } while (openColumn >= numberOfColumns); 2221 if ((NSUInteger)numberOfColumns > [table numberOfColumns]) 2222 [table setNumberOfColumns:numberOfColumns]; 2223 [_textTableRows removeLastObject]; 2224 [_textTableRows addObject:[NSNumber numberWithInteger:rowNumber]]; 2225 [_textTableRowArrays removeLastObject]; 2226 [_textTableRowArrays addObject:rowArray]; 2227 if ([_textTableRowBackgroundColors count] > 0) 2228 [_textTableRowBackgroundColors removeLastObject]; 2229 } else if (displayValue == "table-cell" && [_textBlocks count] > 0) { 2230 while ([_textTables count] > [_textBlocks count]) { 2231 [_textTables removeLastObject]; 2232 [_textTableSpacings removeLastObject]; 2233 [_textTablePaddings removeLastObject]; 2234 [_textTableRows removeLastObject]; 2235 [_textTableRowArrays removeLastObject]; 2236 } 2237 [_textBlocks removeLastObject]; 2238 } else if ((element.hasTagName(ulTag) || element.hasTagName(olTag)) && [_textLists count] > 0) { 2239 NSTextList *list = [_textLists lastObject]; 2240 _addMarkersToList(list, range); 2241 [_textLists removeLastObject]; 2242 } else if (element.hasTagName(qTag)) { 2243 _addQuoteForElement(element, NO, --_quoteLevel); 2244 } else if (element.hasTagName(spanTag)) { 2245 NSString *className = element.getAttribute(classAttr); 2246 NSMutableString *mutableString; 2247 NSUInteger i, count = 0; 2248 unichar c; 2249 if ([@"Apple-converted-space" isEqualToString:className]) { 2250 mutableString = [_attrStr mutableString]; 2251 for (i = range.location; i < NSMaxRange(range); i++) { 2252 c = [mutableString characterAtIndex:i]; 2253 if (0xa0 == c) 2254 [mutableString replaceCharactersInRange:NSMakeRange(i, 1) withString:@" "]; 2255 } 2256 } else if ([@"Apple-converted-tab" isEqualToString:className]) { 2257 mutableString = [_attrStr mutableString]; 2258 for (i = range.location; i < NSMaxRange(range); i++) { 2259 NSRange rangeToReplace = NSMakeRange(NSNotFound, 0); 2260 c = [mutableString characterAtIndex:i]; 2261 if (' ' == c || 0xa0 == c) { 2262 count++; 2263 if (count >= 4 || i + 1 >= NSMaxRange(range)) 2264 rangeToReplace = NSMakeRange(i + 1 - count, count); 2265 } else { 2266 if (count > 0) 2267 rangeToReplace = NSMakeRange(i - count, count); 2268 } 2269 if (rangeToReplace.length > 0) { 2270 [mutableString replaceCharactersInRange:rangeToReplace withString:@"\t"]; 2271 range.length -= (rangeToReplace.length - 1); 2272 i -= (rangeToReplace.length - 1); 2273 if (NSMaxRange(rangeToReplace) <= _domRangeStartIndex) { 2274 _domRangeStartIndex -= (rangeToReplace.length - 1); 2275 } else if (rangeToReplace.location < _domRangeStartIndex) { 2276 _domRangeStartIndex = rangeToReplace.location; 2277 } 2278 count = 0; 2279 } 2280 } 2281 } 2282 } 2283} 2284 2285void HTMLConverter::_processText(CharacterData& characterData) 2286{ 2287 NSUInteger textLength = [_attrStr length]; 2288 unichar lastChar = (textLength > 0) ? [[_attrStr string] characterAtIndex:textLength - 1] : '\n'; 2289 BOOL suppressLeadingSpace = ((_flags.isSoft && lastChar == ' ') || lastChar == '\n' || lastChar == '\r' || lastChar == '\t' || lastChar == NSParagraphSeparatorCharacter || lastChar == NSLineSeparatorCharacter || lastChar == NSFormFeedCharacter || lastChar == WebNextLineCharacter); 2290 NSRange rangeToReplace = NSMakeRange(textLength, 0); 2291 CFMutableStringRef mutstr = NULL; 2292 2293 String originalString = characterData.data(); 2294 unsigned startOffset = 0; 2295 unsigned endOffset = originalString.length(); 2296 if (&characterData == m_range->startContainer()) { 2297 startOffset = m_range->startOffset(); 2298 _domRangeStartIndex = [_attrStr length]; 2299 _flags.reachedStart = YES; 2300 } 2301 if (&characterData == m_range->endContainer()) { 2302 endOffset = m_range->endOffset(); 2303 _flags.reachedEnd = YES; 2304 } 2305 if ((startOffset > 0 || endOffset < originalString.length()) && endOffset >= startOffset) 2306 originalString = originalString.substring(startOffset, endOffset - startOffset); 2307 String outputString = originalString; 2308 2309 // FIXME: Use RenderText's content instead. 2310 bool wasSpace = false; 2311 if (_caches->propertyValueForNode(characterData, CSSPropertyWhiteSpace).startsWith("pre")) { 2312 if (textLength && originalString.length() && _flags.isSoft) { 2313 unichar c = originalString.at(0); 2314 if (c == '\n' || c == '\r' || c == NSParagraphSeparatorCharacter || c == NSLineSeparatorCharacter || c == NSFormFeedCharacter || c == WebNextLineCharacter) 2315 rangeToReplace = NSMakeRange(textLength - 1, 1); 2316 } 2317 } else { 2318 unsigned count = originalString.length(); 2319 bool wasLeading = true; 2320 StringBuilder builder; 2321 for (unsigned i = 0; i < count; i++) { 2322 UChar c = originalString.at(i); 2323 bool isWhitespace = c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == 0xc || c == 0x200b; 2324 if (isWhitespace) 2325 wasSpace = (!wasLeading || !suppressLeadingSpace); 2326 else { 2327 if (wasSpace) 2328 builder.append(' '); 2329 builder.append(c); 2330 wasSpace = false; 2331 wasLeading = false; 2332 } 2333 } 2334 if (wasSpace) 2335 builder.append(' '); 2336 outputString = builder.toString(); 2337 } 2338 2339 if (outputString.length()) { 2340 String textTransform = _caches->propertyValueForNode(characterData, CSSPropertyTextTransform); 2341 if (textTransform.length()) { 2342 if (textTransform == "capitalize") {// FIXME: This is extremely inefficient. 2343 NSString *temporaryString = outputString; 2344 outputString = [temporaryString capitalizedString]; 2345 } else if (textTransform == "uppercase") 2346 outputString = outputString.upper(); 2347 else if (textTransform == "lowercase") 2348 outputString = outputString.lower(); 2349 } 2350 2351 [_attrStr replaceCharactersInRange:rangeToReplace withString:outputString]; 2352 rangeToReplace.length = outputString.length(); 2353 if (rangeToReplace.length) 2354 [_attrStr setAttributes:aggregatedAttributesForAncestors(characterData) range:rangeToReplace]; 2355 _flags.isSoft = wasSpace; 2356 } 2357 if (mutstr) 2358 CFRelease(mutstr); 2359} 2360 2361void HTMLConverter::_traverseNode(Node& node, unsigned depth, bool embedded) 2362{ 2363 if (_flags.reachedEnd) 2364 return; 2365 if (!_flags.reachedStart && !_caches->isAncestorsOfStartToBeConverted(node)) 2366 return; 2367 2368 unsigned startOffset = 0; 2369 unsigned endOffset = UINT_MAX; 2370 bool isStart = false; 2371 bool isEnd = false; 2372 if (&node == m_range->startContainer()) { 2373 startOffset = m_range->startOffset(); 2374 isStart = true; 2375 _flags.reachedStart = YES; 2376 } 2377 if (&node == m_range->endContainer()) { 2378 endOffset = m_range->endOffset(); 2379 isEnd = true; 2380 } 2381 2382 if (node.isDocumentNode() || node.isDocumentFragment()) { 2383 Node* child = node.firstChild(); 2384 for (NSUInteger i = 0; child; i++) { 2385 if (isStart && i == startOffset) 2386 _domRangeStartIndex = [_attrStr length]; 2387 if ((!isStart || startOffset <= i) && (!isEnd || endOffset > i)) 2388 _traverseNode(*child, depth + 1, embedded); 2389 if (isEnd && i + 1 >= endOffset) 2390 _flags.reachedEnd = YES; 2391 if (_flags.reachedEnd) 2392 break; 2393 child = child->nextSibling(); 2394 } 2395 } else if (node.isElementNode()) { 2396 Element& element = toElement(node); 2397 if (_enterElement(element, embedded)) { 2398 NSUInteger startIndex = [_attrStr length]; 2399 if (_processElement(element, depth)) { 2400 Node* child = node.firstChild(); 2401 for (NSUInteger i = 0; child; i++) { 2402 if (isStart && i == startOffset) 2403 _domRangeStartIndex = [_attrStr length]; 2404 if ((!isStart || startOffset <= i) && (!isEnd || endOffset > i)) 2405 _traverseNode(*child, depth + 1, embedded); 2406 if (isEnd && i + 1 >= endOffset) 2407 _flags.reachedEnd = YES; 2408 if (_flags.reachedEnd) 2409 break; 2410 child = child->nextSibling(); 2411 } 2412 _exitElement(element, depth, startIndex); 2413 } 2414 } 2415 } else if (node.isCharacterDataNode()) 2416 _processText(toCharacterData(node)); 2417 2418 if (isEnd) 2419 _flags.reachedEnd = YES; 2420} 2421 2422void HTMLConverter::_traverseFooterNode(Element& element, unsigned depth) 2423{ 2424 if (_flags.reachedEnd) 2425 return; 2426 if (!_flags.reachedStart && !_caches->isAncestorsOfStartToBeConverted(element)) 2427 return; 2428 2429 unsigned startOffset = 0; 2430 unsigned endOffset = UINT_MAX; 2431 bool isStart = false; 2432 bool isEnd = false; 2433 if (&element == m_range->startContainer()) { 2434 startOffset = m_range->startOffset(); 2435 isStart = true; 2436 _flags.reachedStart = YES; 2437 } 2438 if (&element == m_range->endContainer()) { 2439 endOffset = m_range->endOffset(); 2440 isEnd = true; 2441 } 2442 2443 if (_enterElement(element, YES)) { 2444 NSUInteger startIndex = [_attrStr length]; 2445 if (_processElement(element, depth)) { 2446 Node* child = element.firstChild(); 2447 for (NSUInteger i = 0; child; i++) { 2448 if (isStart && i == startOffset) 2449 _domRangeStartIndex = [_attrStr length]; 2450 if ((!isStart || startOffset <= i) && (!isEnd || endOffset > i)) 2451 _traverseNode(*child, depth + 1, true /* embedded */); 2452 if (isEnd && i + 1 >= endOffset) 2453 _flags.reachedEnd = YES; 2454 if (_flags.reachedEnd) 2455 break; 2456 child = child->nextSibling(); 2457 } 2458 _exitElement(element, depth, startIndex); 2459 } 2460 } 2461 if (isEnd) 2462 _flags.reachedEnd = YES; 2463} 2464 2465void HTMLConverter::_adjustTrailingNewline() 2466{ 2467 NSUInteger textLength = [_attrStr length]; 2468 unichar lastChar = (textLength > 0) ? [[_attrStr string] characterAtIndex:textLength - 1] : 0; 2469 BOOL alreadyHasTrailingNewline = (lastChar == '\n' || lastChar == '\r' || lastChar == NSParagraphSeparatorCharacter || lastChar == NSLineSeparatorCharacter || lastChar == WebNextLineCharacter); 2470 if (_flags.hasTrailingNewline && !alreadyHasTrailingNewline) 2471 [_attrStr replaceCharactersInRange:NSMakeRange(textLength, 0) withString:@"\n"]; 2472} 2473 2474Node* HTMLConverterCaches::cacheAncestorsOfStartToBeConverted(const Range& range) 2475{ 2476 Node* commonAncestor = range.commonAncestorContainer(ASSERT_NO_EXCEPTION); 2477 Node* ancestor = range.startContainer(); 2478 2479 while (ancestor) { 2480 m_ancestorsUnderCommonAncestor.add(ancestor); 2481 if (ancestor == commonAncestor) 2482 break; 2483 ancestor = ancestor->parentNode(); 2484 } 2485 2486 return commonAncestor; 2487} 2488 2489#if !PLATFORM(IOS) 2490 2491static NSFileWrapper *fileWrapperForURL(DocumentLoader *dataSource, NSURL *URL) 2492{ 2493 if ([URL isFileURL]) 2494 return [[[NSFileWrapper alloc] initWithURL:[URL URLByResolvingSymlinksInPath] options:0 error:nullptr] autorelease]; 2495 2496 RefPtr<ArchiveResource> resource = dataSource->subresource(URL); 2497 if (resource) { 2498 NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:resource->data()->createNSData().get()] autorelease]; 2499 NSString *filename = resource->response().suggestedFilename(); 2500 if (!filename || ![filename length]) 2501 filename = suggestedFilenameWithMIMEType(resource->url(), resource->mimeType()); 2502 [wrapper setPreferredFilename:filename]; 2503 return wrapper; 2504 } 2505 2506 NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL]; 2507 2508 NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request]; 2509 [request release]; 2510 2511 if (cachedResponse) { 2512 NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:[cachedResponse data]] autorelease]; 2513 [wrapper setPreferredFilename:[[cachedResponse response] suggestedFilename]]; 2514 return wrapper; 2515 } 2516 2517 return nil; 2518} 2519 2520static NSFileWrapper *fileWrapperForElement(Element* element) 2521{ 2522 NSFileWrapper *wrapper = nil; 2523 2524 const AtomicString& attr = element->getAttribute(srcAttr); 2525 if (!attr.isEmpty()) { 2526 NSURL *URL = element->document().completeURL(attr); 2527 if (DocumentLoader* loader = element->document().loader()) 2528 wrapper = fileWrapperForURL(loader, URL); 2529 } 2530 if (!wrapper) { 2531 RenderImage* renderer = toRenderImage(element->renderer()); 2532 if (renderer->cachedImage() && !renderer->cachedImage()->errorOccurred()) { 2533 wrapper = [[NSFileWrapper alloc] initRegularFileWithContents:(NSData *)(renderer->cachedImage()->imageForRenderer(renderer)->getTIFFRepresentation())]; 2534 [wrapper setPreferredFilename:@"image.tiff"]; 2535 [wrapper autorelease]; 2536 } 2537 } 2538 2539 return wrapper; 2540} 2541 2542#endif 2543 2544namespace WebCore { 2545 2546// This function supports more HTML features than the editing variant below, such as tables. 2547NSAttributedString *attributedStringFromRange(Range& range) 2548{ 2549 HTMLConverter converter(range); 2550 return converter.convert(); 2551} 2552 2553#if !PLATFORM(IOS) 2554// This function uses TextIterator, which makes offsets in its result compatible with HTML editing. 2555NSAttributedString *editingAttributedStringFromRange(Range& range) 2556{ 2557 NSFontManager *fontManager = [NSFontManager sharedFontManager]; 2558 NSMutableAttributedString *string = [[NSMutableAttributedString alloc] init]; 2559 NSUInteger stringLength = 0; 2560 RetainPtr<NSMutableDictionary> attrs = adoptNS([[NSMutableDictionary alloc] init]); 2561 2562 for (TextIterator it(&range); !it.atEnd(); it.advance()) { 2563 RefPtr<Range> currentTextRange = it.range(); 2564 Node* startContainer = currentTextRange->startContainer(); 2565 Node* endContainer = currentTextRange->endContainer(); 2566 int startOffset = currentTextRange->startOffset(); 2567 int endOffset = currentTextRange->endOffset(); 2568 2569 if (startContainer == endContainer && (startOffset == endOffset - 1)) { 2570 Node* node = startContainer->childNode(startOffset); 2571 if (node && node->hasTagName(imgTag)) { 2572 NSFileWrapper* fileWrapper = fileWrapperForElement(toElement(node)); 2573 NSTextAttachment* attachment = [[NSTextAttachment alloc] initWithFileWrapper:fileWrapper]; 2574 [string appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]]; 2575 [attachment release]; 2576 } 2577 } 2578 2579 int currentTextLength = it.text().length(); 2580 if (!currentTextLength) 2581 continue; 2582 2583 RenderObject* renderer = startContainer->renderer(); 2584 ASSERT(renderer); 2585 if (!renderer) 2586 continue; 2587 const RenderStyle& style = renderer->style(); 2588 if (style.textDecorationsInEffect() & TextDecorationUnderline) 2589 [attrs.get() setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName]; 2590 if (style.textDecorationsInEffect() & TextDecorationLineThrough) 2591 [attrs.get() setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName]; 2592 if (NSFont *font = style.font().primaryFont()->getNSFont()) 2593 [attrs.get() setObject:font forKey:NSFontAttributeName]; 2594 else 2595 [attrs.get() setObject:[fontManager convertFont:WebDefaultFont() toSize:style.font().primaryFont()->platformData().size()] forKey:NSFontAttributeName]; 2596 if (style.visitedDependentColor(CSSPropertyColor).alpha()) 2597 [attrs.get() setObject:nsColor(style.visitedDependentColor(CSSPropertyColor)) forKey:NSForegroundColorAttributeName]; 2598 else 2599 [attrs.get() removeObjectForKey:NSForegroundColorAttributeName]; 2600 if (style.visitedDependentColor(CSSPropertyBackgroundColor).alpha()) 2601 [attrs.get() setObject:nsColor(style.visitedDependentColor(CSSPropertyBackgroundColor)) forKey:NSBackgroundColorAttributeName]; 2602 else 2603 [attrs.get() removeObjectForKey:NSBackgroundColorAttributeName]; 2604 2605 [string replaceCharactersInRange:NSMakeRange(stringLength, 0) withString:it.text().createNSStringWithoutCopying().get()]; 2606 [string setAttributes:attrs.get() range:NSMakeRange(stringLength, currentTextLength)]; 2607 stringLength += currentTextLength; 2608 } 2609 2610 return [string autorelease]; 2611} 2612#endif 2613 2614} 2615