Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Cocoanetics / DTCoreText / 180 / 1

Source File

91.44
/Core/Source/DTCSSStylesheet.m
1
//
1×
2
//  DTCSSStylesheet.m
3
//  DTCoreText
1×
4
//
1×
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
24
{
25
        NSMutableDictionary *_styles;
26
        NSMutableDictionary *_orderedSelectorWeights;
27
        NSMutableArray *_orderedSelectors;
28
}
29

30
#pragma mark Creating Stylesheets
31

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

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

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

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

91
#pragma mark Working with Style Blocks
92

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

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

518

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

616

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

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

656
#pragma mark Accessing Style Information
657

658
- (NSDictionary *)mergedStyleDictionaryForElement:(DTHTMLElement *)element matchedSelectors:(NSSet **)matchedSelectors
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);
665
        
666
        NSMutableDictionary *tmpDict = [NSMutableDictionary dictionary];
667
        
668
        // Get based on element
669
        NSDictionary *byTagName = [self.styles objectForKey:element.name];
670
        
671
        if (byTagName)
672
        {
673
                [tmpDict addEntriesFromDictionary:byTagName];
674
        }
675
        
676
    // Get based on class(es)
677
        NSString *classString = [element.attributes objectForKey:@"class"];
678
        NSArray *classes = [classString componentsSeparatedByString:@" "];
679
        
680
        // Cascaded selectors with more than one part are sorted by specificity
681
        NSMutableArray *matchingCascadingSelectors = [self matchingComplexCascadingSelectorsForElement:element];
682
        [matchingCascadingSelectors sortUsingComparator:^NSComparisonResult(NSString *selector1, NSString *selector2)
683
         {
684
                 NSInteger weightForSelector1 = [_orderedSelectorWeights[selector1] integerValue];
685
                 NSInteger weightForSelector2 = [_orderedSelectorWeights[selector2] integerValue];
686
                 
687
                 if (weightForSelector1 == weightForSelector2)
688
                 {
689
                         weightForSelector1 += [_orderedSelectors indexOfObject:selector1];
690
                         weightForSelector2 += [_orderedSelectors indexOfObject:selector2];
691
                 }
692
                 
693
                 if (weightForSelector1 > weightForSelector2)
694
                 {
695
                         return (NSComparisonResult)NSOrderedDescending;
696
                 }
697
                 
698
                 if (weightForSelector1 < weightForSelector2)
699
                 {
700
                         return (NSComparisonResult)NSOrderedAscending;
701
                 }
702
                 
703
                 return (NSComparisonResult)NSOrderedSame;
704
         }];
705
        
706
        NSMutableSet *tmpMatchedSelectors;
707
        
708
        if (matchedSelectors)
709
        {
710
                tmpMatchedSelectors = [NSMutableSet set];
711
        }
712
        
713
        // Apply complex cascading selectors first, then apply most specific selectors
714
        for (NSString *cascadingSelector in matchingCascadingSelectors)
715
        {
716
                NSDictionary *byCascadingSelector = [_styles objectForKey:cascadingSelector];
717
                [tmpDict addEntriesFromDictionary:byCascadingSelector];
718
                [tmpMatchedSelectors addObject:cascadingSelector];
719
        }
720
        
721
        // Applied the parameter element's classes last
722
        for (NSString *class in classes)
723
        {
724
                NSString *classRule = [NSString stringWithFormat:@".%@", class];
725
                NSDictionary *byClass = [_styles objectForKey: classRule];
726
                
727
                if (byClass)
728
                {
729
                        [tmpDict addEntriesFromDictionary:byClass];
730
                        [tmpMatchedSelectors addObject:class];
731
                }
732
                
733
                NSString *classAndTagRule = [NSString stringWithFormat:@"%@.%@", element.name, class];
734
                NSDictionary *byClassAndName = [_styles objectForKey:classAndTagRule];
735
                
736
                if (byClassAndName)
737
                {
738
                        [tmpDict addEntriesFromDictionary:byClassAndName];
739
                        [tmpMatchedSelectors addObject:classAndTagRule];
740
                }
741
        }
742
        
743
        // Get based on id
744
        NSString *idRule = [NSString stringWithFormat:@"#%@", [element.attributes objectForKey:@"id"]];
745
        NSDictionary *byID = [_styles objectForKey:idRule];
746
        
747
        if (byID)
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"];
755
        
756
        if ([styleString length])
757
        {
758
                NSMutableDictionary *localStyles = [[styleString dictionaryOfCSSStyles] mutableCopy];
759
                
760
                // need to uncompress because otherwise we might get shorthands and non-shorthands together
761
                [self _uncompressShorthands:localStyles];
762
                
763
                [tmpDict addEntriesFromDictionary:localStyles];
764
        }
765
        
766
        if ([tmpDict count])
767
        {
768
                if (matchedSelectors && [tmpMatchedSelectors count])
769
                {
770
                        *matchedSelectors = [tmpMatchedSelectors copy];
771
                }
772
                
773
                return tmpDict;
774
        }
775
        else
776
        {
777
                return nil;
778
        }
779
}
780

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

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

791
// This looks for cascaded selectors with more than one part to them
792
- (NSMutableArray *)matchingComplexCascadingSelectorsForElement:(DTHTMLElement *)element
793
{
794
        __block NSMutableArray *matchedSelectors = [NSMutableArray array];
795
        
796
        for (NSString *selector in _orderedSelectors)
797
        {
798
                NSArray *selectorParts = [selector componentsSeparatedByString:@" "];
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)
802
                {
803
                        continue;
804
                }
805
                
806
                DTHTMLElement *nextElement = element;
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;)
811
                {
812
                        NSString *selectorPart = selectorParts[j];
813
                        BOOL matched = NO;
814
                        
815
                        if (selectorPart.length)
816
                        {
817
                                while (nextElement != nil)
818
                                {
819
                                        DTHTMLElement *currentElement = nextElement;
820
                                        
821
                                        //This must be set to advance here, above all of the breaks, so the loop properly advances.
822
                                        nextElement = currentElement.parentElement;
823

824
                                        if ([selectorPart characterAtIndex:0] == '#')
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"];
828
                                                if (currentElementId && [[selectorPart substringFromIndex:1] isEqualToString:currentElementId])
829
                                                {
830
                                                        matched = YES;
831
                                                        break;
832
                                                }
833
                                        } else if ([selectorPart characterAtIndex:0] == '.')
834
                                        {
835
                                                NSString *currentElementClassesString = [currentElement.attributes objectForKey:@"class"];
836
                                                NSArray *currentElementClasses = [currentElementClassesString componentsSeparatedByString:@" "];
837
                                                for (NSString *currentElementClass in currentElementClasses)
838
                                                {
839
                                                        if ([currentElementClass isEqualToString:[selectorPart substringFromIndex:1]])
840
                                                        {
841
                                                                matched = YES;
842
                                                                break;
843
                                                        }
844
                                                }
845
                                                
846
                                                if (matched)
847
                                                {
848
                                                        break;
849
                                                }
850
                                        } else if ([selectorPart isEqualToString:currentElement.name] && (selectorParts.count > 1))
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;
855
                                                break;
856
                                        }
857
                                        
858
                                        // break if the right most portion of the selector doesn't match the target element
859
                                        if (!matched && ([currentElement isEqual:element])) {
860
                                                break;
861
                                        }
862
                                }
863
                        }
864
                        
865
                        if (!matched)
866
                        {
867
                                break;
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)
872
                        {
873
                                if (matched && ![matchedSelectors containsObject:selector])
874
                                {
875
                                        [matchedSelectors addObject:selector];
876
                                }
877
                        }
878
                }
879
        }
880
        
881
        return matchedSelectors;
882
}
883

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

910
#pragma mark NSCopying
911

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

919
@end
  • Back to Build 180
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2023 Coveralls, Inc