Cocoanetics / DTCoreText / 180 / 180.1

Source File Details

Source File

91.44% /Core/Source/DTCSSStylesheet.m

Previous Version (Job #178.1)

1
//
2
//  DTCSSStylesheet.m
3
//  DTCoreText
4
//
5
//  Created by Oliver Drobnik on 9/5/11.
6
//  Copyright (c) 2011 Drobnik.com. All rights reserved.
7
//
8

9
#import "DTCSSStylesheet.h"
10
#import "DTCSSListStyle.h"
11

12
#import "DTHTMLElement.h"
13
#import "NSScanner+HTML.h"
14
#import "NSString+CSS.h"
15
#import "NSString+HTML.h"
16

17

18
// external symbols generated via custom build rule and xxd
19
extern unsigned char default_css[];
20
extern unsigned int default_css_len;
21

22

23
@implementation DTCSSStylesheet
134×
24
{
25
        NSMutableDictionary *_styles;
26
        NSMutableDictionary *_orderedSelectorWeights;
27
        NSMutableArray *_orderedSelectors;
28
}
29

30
#pragma mark Creating Stylesheets
31

32
+ (DTCSSStylesheet *)defaultStyleSheet
106×
33
{
34
        static DTCSSStylesheet *defaultDTCSSStylesheet = nil;
35
        if (defaultDTCSSStylesheet)
106×
36
        {
37
                return defaultDTCSSStylesheet;
105×
38
        }
39
        
40
        @synchronized(self)
1×
41
        {
42
                if (!defaultDTCSSStylesheet)
1×
43
                {
44
                        // get the data from the external symbol
45
                        NSData *data = [NSData dataWithBytes:default_css length:default_css_len];
2×
46
                        NSString *cssString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
3×
47
                        
48
                        defaultDTCSSStylesheet = [[DTCSSStylesheet alloc] initWithStyleBlock:cssString];
3×
49
                }
1×
50
        }
1×
51
        return defaultDTCSSStylesheet;
1×
52
}
106×
53

54
- (id)initWithStyleBlock:(NSString *)css
29×
55
{
56
        self = [super init];
29×
57
        
58
        if (self)
29×
59
        {
60
                _styles        = [[NSMutableDictionary alloc] init];
29×
61
                _orderedSelectorWeights = [[NSMutableDictionary alloc] init];
29×
62
                _orderedSelectors = [[NSMutableArray alloc] init];
29×
63
                
64
                [self parseStyleBlock:css];
29×
65
        }
29×
66
        
67
        return self;
58×
68
}
29×
69

70
- (id)initWithStylesheet:(DTCSSStylesheet *)stylesheet
106×
71
{
72
        self = [super init];
106×
73
        
74
        if (self)
106×
75
        {
76
                _styles        = [[NSMutableDictionary alloc] init];
106×
77
                _orderedSelectorWeights = [[NSMutableDictionary alloc] init];
106×
78
                _orderedSelectors = [[NSMutableArray alloc] init];
106×
79
                
80
                [self mergeStylesheet:stylesheet];
106×
81
        }
106×
82
        
83
        return self;
212×
84
}
106×
85

86
- (NSString *)description
1×
87
{
88
        return [_styles description];
1×
89
}
90

91
#pragma mark Working with Style Blocks
92

93
- (void)_uncompressShorthands:(NSMutableDictionary *)styles
363×
94
{
95
        // list-style shorthand
96
        NSString *shortHand = [[styles objectForKey:@"list-style"] lowercaseString];
363×
97
        
98
        if (shortHand)
363×
99
        {
100
                [styles removeObjectForKey:@"list-style"];
20×
101
                
102
                if ([shortHand isEqualToString:@"inherit"])
20×
103
                {
104
                        [styles setObject:@"inherit" forKey:@"list-style-type"];
!
105
                        [styles setObject:@"inherit" forKey:@"list-style-position"];
!
106
                        return;
!
107
                }
108
                
109
                NSArray *components = [shortHand componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
20×
110
                
111
                BOOL typeWasSet = NO;
20×
112
                BOOL positionWasSet = NO;
20×
113
                
114
                DTCSSListStyleType listStyleType = DTCSSListStyleTypeNone;
20×
115
                DTCSSListStylePosition listStylePosition = DTCSSListStylePositionInherit;
20×
116
                
117
                for (NSString *oneComponent in components)
118×
118
                {
119
                        if ([oneComponent hasPrefix:@"url"])
39×
120
                        {
121
                                // list-style-image
122
                                NSScanner *scanner = [NSScanner scannerWithString:oneComponent];
!
123
                                
124
                                if ([scanner scanCSSURL:NULL])
!
125
                                {
126
                                        [styles setObject:oneComponent forKey:@"list-style-image"];
!
127
                                        
128
                                        continue;
!
129
                                }
130
                        }
131
                        
132
                        if (!typeWasSet)
39×
133
                        {
134
                                // check if valid type
135
                                listStyleType = [DTCSSListStyle listStyleTypeFromString:oneComponent];
20×
136
                                
137
                                if (listStyleType != DTCSSListStyleTypeInvalid)
20×
138
                                {
139
                                        [styles setObject:oneComponent forKey:@"list-style-type"];
20×
140
                                        
141
                                        typeWasSet = YES;
20×
142
                                        continue;
20×
143
                                }
144
                        }
145
                        
146
                        if (!positionWasSet)
19×
147
                        {
148
                                // check if valid position
149
                                listStylePosition = [DTCSSListStyle listStylePositionFromString:oneComponent];
19×
150
                                
151
                                if (listStylePosition != DTCSSListStylePositionInvalid)
19×
152
                                {
153
                                        [styles setObject:oneComponent forKey:@"list-style-position"];
19×
154
                                        
155
                                        positionWasSet = YES;
19×
156
                                        continue;
19×
157
                                }
158
                        }
159
                }
79×
160
        }
20×
161
        
162
        // font shorthand, see http://www.w3.org/TR/CSS21/fonts.html#font-shorthand
163
        shortHand = [styles objectForKey:@"font"];
363×
164
        
165
        if (shortHand)
363×
166
        {
167
                NSString *fontStyle = @"normal";
1×
168
                NSArray *validFontStyles = [NSArray arrayWithObjects:@"italic", @"oblique", nil];
1×
169
                
170
                NSString *fontVariant = @"normal";
1×
171
                NSArray *validFontVariants = [NSArray arrayWithObjects:@"small-caps", nil];
1×
172
                BOOL fontVariantSet = NO;
1×
173
                
174
                NSString *fontWeight = @"normal";
1×
175
                NSArray *validFontWeights = [NSArray arrayWithObjects:@"bold", @"bolder", @"lighter", @"100", @"200", @"300", @"400", @"500", @"600", @"700", @"800", @"900", nil];
1×
176
                BOOL fontWeightSet = NO;
1×
177
                
178
                NSString *fontSize = @"normal";
1×
179
                NSArray *validFontSizes = [NSArray arrayWithObjects:@"xx-small", @"x-small", @"small", @"medium", @"large", @"x-large", @"xx-large", @"larger", @"smaller", nil];
1×
180
                BOOL fontSizeSet = NO;
1×
181
                
182
                NSArray *suffixesToIgnore = [NSArray arrayWithObjects:@"caption", @"icon", @"menu", @"message-box", @"small-caption", @"status-bar", @"inherit", nil];
1×
183
                
184
                NSString *lineHeight = @"normal";
1×
185
                
186
                NSMutableString *fontFamily = [NSMutableString string];
1×
187
                
188
                NSArray *components = [shortHand componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
1×
189
                
190
                for (NSString *oneComponent in components)
18×
191
                {
192
                        // try font size keywords
193
                        if ([validFontSizes containsObject:oneComponent])
8×
194
                        {
195
                                fontSize = oneComponent;
!
196
                                fontSizeSet = YES;
!
197
                                
198
                                continue;
!
199
                        }
200
                        
201
                        NSInteger slashIndex = [oneComponent rangeOfString:@"/"].location;
8×
202
                        
203
                        if (slashIndex != NSNotFound)
8×
204
                        {
205
                                // font-size / line-height
206
                                
207
                                fontSize = [oneComponent substringToIndex:slashIndex-1];
1×
208
                                fontSizeSet = YES;
1×
209
                                
210
                                lineHeight = [oneComponent substringFromIndex:slashIndex+1];
1×
211
                                
212
                                continue;
1×
213
                        }
214
                        else
215
                        {
216
                                // length
217
                                if ([oneComponent hasSuffix:@"%"] || [oneComponent hasSuffix:@"em"] || [oneComponent hasSuffix:@"px"] || [oneComponent hasSuffix:@"pt"])
28×
218
                                {
219
                                        fontSize = oneComponent;
!
220
                                        fontSizeSet = YES;
!
221
                                        
222
                                        continue;
!
223
                                }
224
                        }
225
                        
226
                        if (fontSizeSet)
7×
227
                        {
228
                                if ([suffixesToIgnore containsObject:oneComponent])
4×
229
                                {
230
                                        break;
!
231
                                }
232
                                
233
                                // assume that this is part of font family
234
                                if ([fontFamily length])
4×
235
                                {
236
                                        [fontFamily appendString:@" "];
3×
237
                                }
3×
238
                                
239
                                [fontFamily appendString:oneComponent];
4×
240
                        }
4×
241
                        else
242
                        {
243
                                if (!fontWeightSet && [validFontStyles containsObject:oneComponent])
6×
244
                                {
245
                                        fontStyle = oneComponent;
1×
246
                                }
1×
247
                                else if (!fontVariantSet && [validFontVariants containsObject:oneComponent])
3×
248
                                {
249
                                        fontVariant = oneComponent;
1×
250
                                        fontVariantSet = YES;
1×
251
                                }
1×
252
                                else if (!fontWeightSet && [validFontWeights containsObject:oneComponent])
2×
253
                                {
254
                                        fontWeight = oneComponent;
1×
255
                                        fontWeightSet = YES;
1×
256
                                }
1×
257
                        }
258
                }
20×
259
                
260
                [styles removeObjectForKey:@"font"];
1×
261
                
262
                // size and family are mandatory, without them this is invalid
263
                if ([fontSize length] && [fontFamily length])
2×
264
                {
265
                        [styles setObject:fontStyle forKey:@"font-style"];
1×
266
                        [styles setObject:fontWeight forKey:@"font-weight"];
1×
267
                        [styles setObject:fontVariant forKey:@"font-variant"];
1×
268
                        [styles setObject:fontSize forKey:@"font-size"];
1×
269
                        [styles setObject:lineHeight forKey:@"line-height"];
1×
270
                        [styles setObject:fontFamily forKey:@"font-family"];
1×
271
                }
1×
272
        }
1×
273
        
274
        shortHand = [styles objectForKey:@"margin"];
363×
275
        
276
        if (shortHand)
363×
277
        {
278
                NSArray *parts = [shortHand componentsSeparatedByString:@" "];
5×
279
                
280
                NSString *topMargin;
5×
281
                NSString *rightMargin;
5×
282
                NSString *bottomMargin;
5×
283
                NSString *leftMargin;
5×
284
                
285
                if ([parts count] == 4)
5×
286
                {
287
                        topMargin = [parts objectAtIndex:0];
1×
288
                        rightMargin = [parts objectAtIndex:1];
1×
289
                        bottomMargin = [parts objectAtIndex:2];
1×
290
                        leftMargin = [parts objectAtIndex:3];
1×
291
                }
1×
292
                else if ([parts count] == 3)
4×
293
                {
294
                        topMargin = [parts objectAtIndex:0];
!
295
                        rightMargin = [parts objectAtIndex:1];
!
296
                        bottomMargin = [parts objectAtIndex:2];
!
297
                        leftMargin = [parts objectAtIndex:1];
!
298
                }
299
                else if ([parts count] == 2)
4×
300
                {
301
                        topMargin = [parts objectAtIndex:0];
4×
302
                        rightMargin = [parts objectAtIndex:1];
4×
303
                        bottomMargin = [parts objectAtIndex:0];
4×
304
                        leftMargin = [parts objectAtIndex:1];
4×
305
                }
4×
306
                else
307
                {
308
                        NSString *onlyValue = [parts objectAtIndex:0];
!
309
                        
310
                        topMargin = onlyValue;
!
311
                        rightMargin = onlyValue;
!
312
                        bottomMargin = onlyValue;
!
313
                        leftMargin = onlyValue;
!
314
                }
315
                
316
                // only apply the ones where there is no previous direct setting
317
                
318
                if (![styles objectForKey:@"margin-top"])
5×
319
                {
320
                        [styles setObject:topMargin forKey:@"margin-top"];
5×
321
                }
5×
322
                
323
                if (![styles objectForKey:@"margin-right"])
5×
324
                {
325
                        [styles setObject:rightMargin forKey:@"margin-right"];
5×
326
                }
5×
327
                
328
                if (![styles objectForKey:@"margin-bottom"])
5×
329
                {
330
                        [styles setObject:bottomMargin forKey:@"margin-bottom"];
5×
331
                }
5×
332
                
333
                if (![styles objectForKey:@"margin-left"])
5×
334
                {
335
                        [styles setObject:leftMargin forKey:@"margin-left"];
5×
336
                }
5×
337
                
338
                // remove the shorthand
339
                [styles removeObjectForKey:@"margin"];
5×
340
        }
5×
341
        
342
        shortHand = [styles objectForKey:@"padding"];
363×
343
        
344
        if (shortHand)
363×
345
        {
346
                NSArray *parts = [shortHand componentsSeparatedByString:@" "];
1×
347
                
348
                NSString *topPadding;
1×
349
                NSString *rightPadding;
1×
350
                NSString *bottomPadding;
1×
351
                NSString *leftPadding;
1×
352
                
353
                if ([parts count] == 4)
1×
354
                {
355
                        topPadding = [parts objectAtIndex:0];
!
356
                        rightPadding = [parts objectAtIndex:1];
!
357
                        bottomPadding = [parts objectAtIndex:2];
!
358
                        leftPadding = [parts objectAtIndex:3];
!
359
                }
360
                else if ([parts count] == 3)
1×
361
                {
362
                        topPadding = [parts objectAtIndex:0];
!
363
                        rightPadding = [parts objectAtIndex:1];
!
364
                        bottomPadding = [parts objectAtIndex:2];
!
365
                        leftPadding = [parts objectAtIndex:1];
!
366
                }
367
                else if ([parts count] == 2)
1×
368
                {
369
                        topPadding = [parts objectAtIndex:0];
!
370
                        rightPadding = [parts objectAtIndex:1];
!
371
                        bottomPadding = [parts objectAtIndex:0];
!
372
                        leftPadding = [parts objectAtIndex:1];
!
373
                }
374
                else
375
                {
376
                        NSString *onlyValue = [parts objectAtIndex:0];
1×
377
                        
378
                        topPadding = onlyValue;
1×
379
                        rightPadding = onlyValue;
1×
380
                        bottomPadding = onlyValue;
1×
381
                        leftPadding = onlyValue;
1×
382
                }
1×
383
                
384
                // only apply the ones where there is no previous direct setting
385
                
386
                if (![styles objectForKey:@"padding-top"])
1×
387
                {
388
                        [styles setObject:topPadding forKey:@"padding-top"];
1×
389
                }
1×
390
                
391
                if (![styles objectForKey:@"padding-right"])
1×
392
                {
393
                        [styles setObject:rightPadding forKey:@"padding-right"];
1×
394
                }
1×
395
                
396
                if (![styles objectForKey:@"padding-bottom"])
1×
397
                {
398
                        [styles setObject:bottomPadding forKey:@"padding-bottom"];
1×
399
                }
1×
400
                
401
                if (![styles objectForKey:@"padding-left"])
1×
402
                {
403
                        [styles setObject:leftPadding forKey:@"padding-left"];
1×
404
                }
1×
405
                
406
                // remove the shorthand
407
                [styles removeObjectForKey:@"padding"];
1×
408
        }
1×
409
}
2,178×
410

411
- (void)_addStyleRule:(NSString *)rule withSelector:(NSString*)selectors
116×
412
{
413
        NSArray *split = [selectors componentsSeparatedByString:@","];
116×
414
        
415
        for (NSString *selector in split)
514×
416
        {
417
                NSString *cleanSelector = [selector stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
141×
418
                
419
                NSMutableDictionary *ruleDictionary = [[rule dictionaryOfCSSStyles] mutableCopy];
141×
420
                
421
                // remove !important, we're ignoring these
422
                for (NSString *oneKey in [ruleDictionary allKeys])
902×
423
                {
424
                        id value = [ruleDictionary objectForKey:oneKey];
310×
425
                        if ([value isKindOfClass:[NSString class]])
310×
426
                        {
427
                                NSRange rangeOfImportant = [value rangeOfString:@"!important" options:NSCaseInsensitiveSearch];
307×
428
                                
429
                                if (rangeOfImportant.location != NSNotFound)
307×
430
                                {
431
                                        value = [value stringByReplacingCharactersInRange:rangeOfImportant withString:@""];
1×
432
                                        value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
1×
433
                                        
434
                                        [ruleDictionary setObject:value forKey:oneKey];
1×
435
                                }
1×
436
                                
437
                        } else if ([value isKindOfClass:[NSArray class]])
310×
438
                        {
439
                                NSMutableArray *newVal;
3×
440
                                
441
                                for (NSUInteger i = 0; i < [value count]; ++i)
18×
442
                                {
443
                                        NSString *s = [value objectAtIndex:i];
6×
444
                                        
445
                                        NSRange rangeOfImportant = [s rangeOfString:@"!important" options:NSCaseInsensitiveSearch];
6×
446
                                        
447
                                        if (rangeOfImportant.location != NSNotFound)
6×
448
                                        {
449
                                                s = [s stringByReplacingCharactersInRange:rangeOfImportant withString:@""];
2×
450
                                                s = [s stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
2×
451
                                                
452
                                                if (!newVal)
2×
453
                                                {
454
                                                        if ([value isKindOfClass:[NSMutableArray class]])
2×
455
                                                        {
456
                                                                newVal = value;
2×
457
                                                        } else
2×
458
                                                        {
459
                                                                newVal = [value mutableCopy];
!
460
                                                        }
461
                                                }
2×
462
                                                
463
                                                newVal[i] = s;
2×
464
                                        }
2×
465
                                }
6×
466
                                
467
                                if (newVal)
3×
468
                                {
469
                                        [ruleDictionary setObject:newVal forKey:oneKey];
2×
470
                                }
2×
471
                        }
3×
472
                }
902×
473
                
474
                // need to uncompress because otherwise we might get shorthands and non-shorthands together
475
                [self _uncompressShorthands:ruleDictionary];
141×
476
                
477
                // check if there is a pseudo selector
478
                NSRange colonRange = [cleanSelector rangeOfString:@":"];
141×
479
                NSString *pseudoSelector = nil;
141×
480
                
481
                if (colonRange.length==1)
141×
482
                {
483
                        pseudoSelector = [cleanSelector substringFromIndex:colonRange.location+1];
1×
484
                        cleanSelector = [cleanSelector substringToIndex:colonRange.location];
1×
485
                        
486
                        // prefix all rules with the pseudo-selector
487
                        for (NSString *oneRuleKey in [ruleDictionary allKeys])
4×
488
                        {
489
                                id value = [ruleDictionary objectForKey:oneRuleKey];
1×
490
                                
491
                                // prefix key with the pseudo selector
492
                                NSString *prefixedKey = [NSString stringWithFormat:@"%@:%@", pseudoSelector, oneRuleKey];
1×
493
                                [ruleDictionary setObject:value forKey:prefixedKey];
1×
494
                                [ruleDictionary removeObjectForKey:oneRuleKey];
1×
495
                        }
4×
496
                }
1×
497
                
498
                NSDictionary *existingRulesForSelector = [_styles objectForKey:cleanSelector];
141×
499
                
500
                if (existingRulesForSelector)
141×
501
                {
502
                        // substitute new rules over old ones
503
                        NSMutableDictionary *tmpDict = [existingRulesForSelector mutableCopy];
8×
504
                        
505
                        // append new rules
506
                        [tmpDict addEntriesFromDictionary:ruleDictionary];
8×
507
                        
508
                        // save it
509
                        [self _addStyles:tmpDict withSelector:cleanSelector];
8×
510
                }
8×
511
                else
512
                {
513
                        [self _addStyles:ruleDictionary withSelector:cleanSelector];
133×
514
                }
515
        }
514×
516
}
116×
517

518

519
- (void)parseStyleBlock:(NSString*)css
29×
520
{
521
        NSUInteger braceLevel = 0, braceMarker = 0;
29×
522
        
523
        NSString* selector;
29×
524
        
525
        NSUInteger length = [css length];
29×
526
        
527
        for (NSUInteger i = 0; i < length; i++)
14,370×
528
        {
529
                unichar c = [css characterAtIndex:i];
7,156×
530
                
531
                if (c == '/')
7,156×
532
                {
533
                        i++;
11×
534
                        
535
                        if (i < length)
11×
536
                        {
537
                                c = [css characterAtIndex:i];
11×
538
                                
539
                                if (c == '*')
11×
540
                                {
541
                                        // skip comment until closing /
542
                                        
543
                                        for (; i < length; i++)
1,126×
544
                                        {
545
                                                if ([css characterAtIndex:i] == '/')
563×
546
                                                {
547
                                                        break;
10×
548
                                                }
549
                                        }
553×
550
                                        
551
                                        if (i < length)
10×
552
                                        {
553
                                                braceMarker = i+1;
10×
554
                                                continue;
10×
555
                                        }
556
                                        else
557
                                        {
558
                                                // end of string
559
                                                return;
!
560
                                        }
561
                                }
562
                                else
563
                                {
564
                                        // not a comment
565
                                        i--;
1×
566
                                }
567
                        }
1×
568
                }
1×
569
                
570
                // An opening brace! It could be the start of a new rule, or it could be a nested brace.
571
                if (c == '{')
7,146×
572
                {
573
                        // If we start a new rule...
574
                        
575
                        if (braceLevel == 0)
116×
576
                        {
577
                                // Grab the selector and clean up extraneous spaces (we'll process it in a moment)
578
                                selector = [css substringWithRange:NSMakeRange(braceMarker, i-braceMarker)];
116×
579
                                NSArray *selectorParts = [selector componentsSeparatedByString:@" "];
116×
580
                                NSMutableArray *cleanSelectorParts = [NSMutableArray array];
116×
581
                                for (NSString *partialSelector in selectorParts)
770×
582
                                {
583
                                        if (partialSelector.length)
269×
584
                                        {
585
                                                [cleanSelectorParts addObject:partialSelector];
174×
586
                                        }
174×
587
                                }
770×
588
                                selector = [cleanSelectorParts componentsJoinedByString:@" "];
116×
589
                                
590
                                // And mark our position so we can grab the rule's CSS when it is closed
591
                                braceMarker = i + 1;
116×
592
                        }
116×
593
                        
594
                        // Increase the brace level.
595
                        braceLevel += 1;
116×
596
                }
116×
597
                
598
                // A closing brace!
599
                else if (c == '}')
7,030×
600
                {
601
                        // If we finished a rule...
602
                        if (braceLevel == 1)
116×
603
                        {
604
                                NSString *rule = [css substringWithRange:NSMakeRange(braceMarker, i-braceMarker)];
116×
605
                                
606
                                [self _addStyleRule:rule withSelector: selector];
116×
607
                                
608
                                braceMarker = i + 1;
116×
609
                        }
116×
610
                        
611
                        braceLevel = MAX(braceLevel-1, 0ul);
348×
612
                }
116×
613
        }
7,146×
614
}
174×
615

616

617
- (void)mergeStylesheet:(DTCSSStylesheet *)stylesheet
128×
618
{
619
        NSArray *otherStylesheetStyleKeys = stylesheet.orderedSelectors;
128×
620
        
621
        for (NSString *oneKey in otherStylesheetStyleKeys)
12,690×
622
        {
623
                NSDictionary *existingStyles = [_styles objectForKey:oneKey];
6,217×
624
                NSDictionary *stylesToMerge = [[stylesheet styles] objectForKey:oneKey];
6,217×
625
                if (existingStyles)
6,217×
626
                {
627
                        NSMutableDictionary *mutableStyles = [existingStyles mutableCopy];
5×
628
                        
629
                        for (NSString *oneStyleKey in stylesToMerge)
26×
630
                        {
631
                                NSString *mergingStyleString = [stylesToMerge objectForKey:oneStyleKey];
8×
632
                                
633
                                [mutableStyles setObject:mergingStyleString forKey:oneStyleKey];
8×
634
                        }
26×
635
                        
636
                        [self _addStyles:mutableStyles withSelector:oneKey];
5×
637
                }
5×
638
                else
639
                {
640
                        // nothing to worry
641
                        [self _addStyles:stylesToMerge withSelector:oneKey];
6,212×
642
                }
643
        }
12,690×
644
}
128×
645

646
- (void)_addStyles:(NSDictionary *)styles withSelector:(NSString *)selector {
6,358×
647
        [_styles setObject:styles forKey:selector];
6,358×
648
        
649
        if (![_orderedSelectors containsObject:selector])
6,358×
650
        {
651
                [_orderedSelectors addObject:selector];
6,345×
652
                _orderedSelectorWeights[selector] = @([self weightForSelector:selector]);
6,345×
653
        }
6,345×
654
}
6,358×
655

656
#pragma mark Accessing Style Information
657

658
- (NSDictionary *)mergedStyleDictionaryForElement:(DTHTMLElement *)element matchedSelectors:(NSSet **)matchedSelectors
1,536×
659
{
660
        // We are going to combine all the relevant styles for this tag.
661
        // (Note that when styles are applied, the later styles take precedence,
662
        //  so the order in which we grab them matters!)
663
        
664
        NSLog(@"%@", element);
1,536×
665
        
666
        NSMutableDictionary *tmpDict = [NSMutableDictionary dictionary];
1,536×
667
        
668
        // Get based on element
669
        NSDictionary *byTagName = [self.styles objectForKey:element.name];
1,536×
670
        
671
        if (byTagName)
1,536×
672
        {
673
                [tmpDict addEntriesFromDictionary:byTagName];
1,115×
674
        }
1,115×
675
        
676
    // Get based on class(es)
677
        NSString *classString = [element.attributes objectForKey:@"class"];
1,536×
678
        NSArray *classes = [classString componentsSeparatedByString:@" "];
1,536×
679
        
680
        // Cascaded selectors with more than one part are sorted by specificity
681
        NSMutableArray *matchingCascadingSelectors = [self matchingComplexCascadingSelectorsForElement:element];
1,536×
682
        [matchingCascadingSelectors sortUsingComparator:^NSComparisonResult(NSString *selector1, NSString *selector2)
1,559×
683
         {
23×
684
                 NSInteger weightForSelector1 = [_orderedSelectorWeights[selector1] integerValue];
23×
685
                 NSInteger weightForSelector2 = [_orderedSelectorWeights[selector2] integerValue];
23×
686
                 
687
                 if (weightForSelector1 == weightForSelector2)
23×
688
                 {
689
                         weightForSelector1 += [_orderedSelectors indexOfObject:selector1];
5×
690
                         weightForSelector2 += [_orderedSelectors indexOfObject:selector2];
5×
691
                 }
5×
692
                 
693
                 if (weightForSelector1 > weightForSelector2)
23×
694
                 {
695
                         return (NSComparisonResult)NSOrderedDescending;
10×
696
                 }
697
                 
698
                 if (weightForSelector1 < weightForSelector2)
13×
699
                 {
700
                         return (NSComparisonResult)NSOrderedAscending;
13×
701
                 }
702
                 
703
                 return (NSComparisonResult)NSOrderedSame;
!
704
         }];
46×
705
        
706
        NSMutableSet *tmpMatchedSelectors;
1,536×
707
        
708
        if (matchedSelectors)
1,536×
709
        {
710
                tmpMatchedSelectors = [NSMutableSet set];
1,535×
711
        }
1,535×
712
        
713
        // Apply complex cascading selectors first, then apply most specific selectors
714
        for (NSString *cascadingSelector in matchingCascadingSelectors)
1,697×
715
        {
716
                NSDictionary *byCascadingSelector = [_styles objectForKey:cascadingSelector];
60×
717
                [tmpDict addEntriesFromDictionary:byCascadingSelector];
60×
718
                [tmpMatchedSelectors addObject:cascadingSelector];
60×
719
        }
1,697×
720
        
721
        // Applied the parameter element's classes last
722
        for (NSString *class in classes)
2,206×
723
        {
724
                NSString *classRule = [NSString stringWithFormat:@".%@", class];
230×
725
                NSDictionary *byClass = [_styles objectForKey: classRule];
230×
726
                
727
                if (byClass)
230×
728
                {
729
                        [tmpDict addEntriesFromDictionary:byClass];
9×
730
                        [tmpMatchedSelectors addObject:class];
9×
731
                }
9×
732
                
733
                NSString *classAndTagRule = [NSString stringWithFormat:@"%@.%@", element.name, class];
230×
734
                NSDictionary *byClassAndName = [_styles objectForKey:classAndTagRule];
230×
735
                
736
                if (byClassAndName)
230×
737
                {
738
                        [tmpDict addEntriesFromDictionary:byClassAndName];
129×
739
                        [tmpMatchedSelectors addObject:classAndTagRule];
129×
740
                }
129×
741
        }
2,206×
742
        
743
        // Get based on id
744
        NSString *idRule = [NSString stringWithFormat:@"#%@", [element.attributes objectForKey:@"id"]];
1,536×
745
        NSDictionary *byID = [_styles objectForKey:idRule];
1,536×
746
        
747
        if (byID)
1,536×
748
        {
749
                [tmpDict addEntriesFromDictionary:byID];
!
750
                [tmpMatchedSelectors addObject:idRule];
!
751
        }
752
        
753
        // Get tag's local style attribute
754
        NSString *styleString = [element.attributes objectForKey:@"style"];
1,536×
755
        
756
        if ([styleString length])
1,536×
757
        {
758
                NSMutableDictionary *localStyles = [[styleString dictionaryOfCSSStyles] mutableCopy];
222×
759
                
760
                // need to uncompress because otherwise we might get shorthands and non-shorthands together
761
                [self _uncompressShorthands:localStyles];
222×
762
                
763
                [tmpDict addEntriesFromDictionary:localStyles];
222×
764
        }
222×
765
        
766
        if ([tmpDict count])
1,536×
767
        {
768
                if (matchedSelectors && [tmpMatchedSelectors count])
2,629×
769
                {
770
                        *matchedSelectors = [tmpMatchedSelectors copy];
172×
771
                }
172×
772
                
773
                return tmpDict;
1,315×
774
        }
775
        else
776
        {
777
                return nil;
221×
778
        }
779
}
1,536×
780

781
- (NSDictionary *)styles
7,757×
782
{
783
        return _styles;
7,757×
784
}
785

786
- (NSArray *)orderedSelectors
128×
787
{
788
        return _orderedSelectors;
128×
789
}
790

791
// This looks for cascaded selectors with more than one part to them
792
- (NSMutableArray *)matchingComplexCascadingSelectorsForElement:(DTHTMLElement *)element
1,536×
793
{
794
        __block NSMutableArray *matchedSelectors = [NSMutableArray array];
1,536×
795
        
796
        for (NSString *selector in _orderedSelectors)
184,708×
797
        {
798
                NSArray *selectorParts = [selector componentsSeparatedByString:@" "];
180,100×
799
                
800
                // We only process the selector if our selector has more than 1 part to it (e.g. ".foo" would be skipped and ".foo .bar" would not)
801
                if (selectorParts.count < 2)
180,100×
802
                {
803
                        continue;
80,553×
804
                }
805
                
806
                DTHTMLElement *nextElement = element;
9,497×
807
                
808
                // Walking up the hierarchy so start at the right side of the selector and work to the left
809
                // Aside: Manual for loop here is faster than for in with reverseObjectEnumerator
810
                for (NSUInteger j = selectorParts.count; j-- > 0;)
29,044×
811
                {
812
                        NSString *selectorPart = selectorParts[j];
19,980×
813
                        BOOL matched = NO;
9,990×
814
                        
815
                        if (selectorPart.length)
19,980×
816
                        {
817
                                while (nextElement != nil)
21,024×
818
                                {
819
                                        DTHTMLElement *currentElement = nextElement;
10,658×
820
                                        
821
                                        //This must be set to advance here, above all of the breaks, so the loop properly advances.
822
                                        nextElement = currentElement.parentElement;
21,316×
823

824
                                        if ([selectorPart characterAtIndex:0] == '#')
21,316×
825
                                        {
826
                                                // If we're at an id and it doesn't match the current element then the style doesn't apply
827
                                                NSString *currentElementId = [currentElement.attributes objectForKey:@"id"];
207×
828
                                                if (currentElementId && [[selectorPart substringFromIndex:1] isEqualToString:currentElementId])
132×
829
                                                {
830
                                                        matched = YES;
13×
831
                                                        break;
13×
832
                                                }
833
                                        } else if ([selectorPart characterAtIndex:0] == '.')
21,359×
834
                                        {
835
                                                NSString *currentElementClassesString = [currentElement.attributes objectForKey:@"class"];
819×
836
                                                NSArray *currentElementClasses = [currentElementClassesString componentsSeparatedByString:@" "];
546×
837
                                                for (NSString *currentElementClass in currentElementClasses)
1,047×
838
                                                {
839
                                                        if ([currentElementClass isEqualToString:[selectorPart substringFromIndex:1]])
543×
840
                                                        {
841
                                                                matched = YES;
48×
842
                                                                break;
48×
843
                                                        }
844
                                                }
1,396×
845
                                                
846
                                                if (matched)
273×
847
                                                {
848
                                                        break;
48×
849
                                                }
850
                                        } else if ([selectorPart isEqualToString:currentElement.name] && (selectorParts.count > 1))
32,655×
851
                                        {
852
                                                // This condition depends on the "if (selectorParts.count < 2)" conditional above. If that's removed, we must make sure selectorParts
853
                                                // contains > 1 item for this to be matched (we want the element name alone to be matched last).
854
                                                matched = YES;
492×
855
                                                break;
492×
856
                                        }
857
                                        
858
                                        // break if the right most portion of the selector doesn't match the target element
859
                                        if (!matched && ([currentElement isEqual:element])) {
30,315×
860
                                                break;
9,061×
861
                                        }
862
                                }
13,122×
863
                        }
9,990×
864
                        
865
                        if (!matched)
9,990×
866
                        {
867
                                break;
9,437×
868
                        }
869
                        
870
                        //Only match if we really are on the last part of the selector and all other parts have matched so far
871
                        if (j == 0)
553×
872
                        {
873
                                if (matched && ![matchedSelectors containsObject:selector])
180×
874
                                {
875
                                        [matchedSelectors addObject:selector];
60×
876
                                }
60×
877
                        }
60×
878
                }
11,156×
879
        }
213,199×
880
        
881
        return matchedSelectors;
3,072×
882
}
1,536×
883

884
// This computes the specificity for a given selector
885
- (NSUInteger)weightForSelector:(NSString *)selector {
6,345×
886
        if ((selector == nil) || (selector.length == 0))
12,690×
887
        {
888
                return 0;
!
889
        }
890
        
891
        NSUInteger weight = 0;
6,345×
892
        
893
        NSArray *selectorParts = [selector componentsSeparatedByString:@" "];
6,345×
894
        for (NSString *selectorPart in selectorParts)
27,660×
895
        {
896
                if ([selectorPart characterAtIndex:0] == '#')
7,485×
897
                {
898
                        weight += 100;
26×
899
                } else if ([selectorPart characterAtIndex:0] == '.')
7,485×
900
                {
901
                        weight += 10;
88×
902
                } else {
88×
903
                        weight += 1;
7,371×
904
                }
905
        }
27,660×
906
        
907
        return weight;
6,345×
908
}
12,690×
909

910
#pragma mark NSCopying
911

912
- (id)copyWithZone:(NSZone *)zone
106×
913
{
914
        DTCSSStylesheet *newStylesheet = [[DTCSSStylesheet allocWithZone:zone] initWithStylesheet:self];
106×
915
        
916
        return newStylesheet;
212×
917
}
106×
918

919
@end