SVGElement.m 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. //
  2. // SVGElement.m
  3. // SVGKit
  4. //
  5. // Copyright Matt Rajca 2010-2011. All rights reserved.
  6. //
  7. #import "SVGElement.h"
  8. #import "SVGElement_ForParser.h" //.h" // to solve insane Xcode circular dependencies
  9. #import "StyleSheetList+Mutable.h"
  10. #import "CSSStyleSheet.h"
  11. #import "CSSStyleRule.h"
  12. #import "CSSRuleList+Mutable.h"
  13. #import "SVGGElement.h"
  14. #import "SVGRect.h"
  15. #import "SVGTransformable.h"
  16. @interface SVGElement ()
  17. @property (nonatomic, copy) NSString *stringValue;
  18. @end
  19. /*! main class implementation for the base SVGElement: NOTE: in practice, most of the interesting
  20. stuff happens in subclasses, e.g.:
  21. SVGShapeElement
  22. SVGGroupElement
  23. SVGKImageElement
  24. SVGLineElement
  25. SVGPathElement
  26. ...etc
  27. */
  28. @implementation SVGElement
  29. @synthesize identifier = _identifier;
  30. @synthesize xmlbase;
  31. @synthesize rootOfCurrentDocumentFragment = _rootOfCurrentDocumentFragment;
  32. @synthesize viewportElement = _viewportElement;
  33. @synthesize stringValue = _stringValue;
  34. @synthesize className; /**< CSS class, from SVGStylable interface */
  35. @synthesize style; /**< CSS style, from SVGStylable interface */
  36. /** from SVGStylable interface */
  37. -(CSSValue*) getPresentationAttribute:(NSString*) name
  38. {
  39. NSAssert(FALSE, @"getPresentationAttribute: not implemented yet");
  40. return nil;
  41. }
  42. + (BOOL)shouldStoreContent {
  43. return NO;
  44. }
  45. /*! As per the SVG Spec, the local reference to "viewportElement" depends on the values of the
  46. attributes of the node - does it have a "width" attribute?
  47. NB: by definition, <svg> tags MAY NOT have a width, but they are still viewports */
  48. -(void) reCalculateAndSetViewportElementReferenceUsingFirstSVGAncestor:(SVGElement*) firstAncestor
  49. {
  50. // NB the root svg element IS a viewport, but SVG Spec defines it as NOT a viewport, and so we will overwrite this later
  51. BOOL isTagAllowedToBeAViewport = [self.tagName isEqualToString:@"svg"] || [self.tagName isEqualToString:@"foreignObject"]; // NB: Spec lists "image" tag too but only as an IMPLICIT CREATOR - we don't actually handle it (it creates an <SVG> tag ... that will be handled later)
  52. BOOL isTagDefiningAViewport = [self.attributes getNamedItem:@"width"] != nil || [self.attributes getNamedItem:@"height"] != nil;
  53. if( isTagAllowedToBeAViewport && isTagDefiningAViewport )
  54. {
  55. SVGKitLogVerbose(@"[%@] WARNING: setting self (tag = %@) to be a viewport", [self class], self.tagName );
  56. self.viewportElement = self;
  57. }
  58. else
  59. {
  60. SVGElement* ancestorsViewport = firstAncestor.viewportElement;
  61. if( ancestorsViewport == nil )
  62. {
  63. /**
  64. Because of the poorly-designed SVG Spec on Viewports, all the children of the root
  65. SVG node will find that their ancestor has a nil viewport! (this is defined in the spec)
  66. So, in that special case, we INSTEAD guess that the ancestor itself was the viewport...
  67. */
  68. self.viewportElement = firstAncestor;
  69. }
  70. else
  71. self.viewportElement = ancestorsViewport;
  72. }
  73. }
  74. /*! Override so that we can automatically set / unset the ownerSVGElement and viewportElement properties,
  75. as required by SVG Spec */
  76. -(void)setParentNode:(Node *)newParent
  77. {
  78. [super setParentNode:newParent];
  79. /** SVG Spec: if "outermost SVG tag" then both element refs should be nil */
  80. if( [self isKindOfClass:[SVGSVGElement class]]
  81. && (self.parentNode == nil || ! [self.parentNode isKindOfClass:[SVGElement class]]) )
  82. {
  83. self.rootOfCurrentDocumentFragment = nil;
  84. self.viewportElement = nil;
  85. }
  86. else
  87. {
  88. /**
  89. SVG Spec: we have to set a reference to the "root SVG tag of this part of the tree".
  90. If the tree is purely SVGElement nodes / subclasses, that's easy.
  91. But if there are custom nodes in there (any other DOM node, for instance), it gets
  92. more tricky. We have to recurse up the tree until we find an SVGElement we can latch
  93. onto
  94. */
  95. if( [self isKindOfClass:[SVGSVGElement class]] )
  96. {
  97. self.rootOfCurrentDocumentFragment = (SVGSVGElement*) self;
  98. self.viewportElement = self;
  99. }
  100. else
  101. {
  102. Node* currentAncestor = newParent;
  103. SVGElement* firstAncestorThatIsAnyKindOfSVGElement = nil;
  104. while( firstAncestorThatIsAnyKindOfSVGElement == nil
  105. && currentAncestor != nil ) // if we run out of tree! This would be an error (see below)
  106. {
  107. if( [currentAncestor isKindOfClass:[SVGElement class]] )
  108. firstAncestorThatIsAnyKindOfSVGElement = (SVGElement*) currentAncestor;
  109. else
  110. currentAncestor = currentAncestor.parentNode;
  111. }
  112. if( newParent == nil )
  113. {
  114. /** We've set the parent to nil, thereby "orphaning" this Node and the tree underneath it.
  115. This usually happens when you remove a Node from its parent.
  116. I'm not sure what the spec expects at that point - you have a valid DOM tree, but *not* a valid SVG fragment;
  117. or maybe it is valid, for some special-case kind of SVG fragment definition?
  118. TODO: this may also relate to SVG <use> nodes and instancing: if you're fixing that code, check this comment to see if you can improve it.
  119. For now: we simply "do nothing but set everything to nil"
  120. */
  121. SVGKitLogWarn( @"SVGElement has had its parent set to nil; this makes the element and tree beneath it no-longer-valid SVG data; this may require fix-up if you try to re-add that SVGElement or any of its children back to an existing/new SVG tree");
  122. self.rootOfCurrentDocumentFragment = nil;
  123. }
  124. else
  125. {
  126. NSAssert( firstAncestorThatIsAnyKindOfSVGElement != nil, @"This node has no valid SVG tags as ancestor, but it's not an <svg> tag, so this is an impossible SVG file" );
  127. if( [firstAncestorThatIsAnyKindOfSVGElement isKindOfClass:[SVGSVGElement class]] )
  128. self.rootOfCurrentDocumentFragment = (SVGSVGElement*) firstAncestorThatIsAnyKindOfSVGElement;
  129. else
  130. self.rootOfCurrentDocumentFragment = firstAncestorThatIsAnyKindOfSVGElement.rootOfCurrentDocumentFragment;
  131. [self reCalculateAndSetViewportElementReferenceUsingFirstSVGAncestor:firstAncestorThatIsAnyKindOfSVGElement];
  132. #if DEBUG_SVG_ELEMENT_PARSING
  133. SVGKitLogVerbose(@"viewport Element = %@ ... for node/element = %@", self.viewportElement, self.tagName);
  134. #endif
  135. }
  136. }
  137. }
  138. }
  139. - (void)setRootOfCurrentDocumentFragment:(SVGSVGElement *)root {
  140. _rootOfCurrentDocumentFragment = root;
  141. for (Node *child in self.childNodes)
  142. if ([child isKindOfClass:SVGElement.class])
  143. ((SVGElement *) child).rootOfCurrentDocumentFragment = root;
  144. }
  145. - (void)setViewportElement:(SVGElement *)viewport {
  146. _viewportElement = viewport;
  147. for (Node *child in self.childNodes)
  148. if ([child isKindOfClass:SVGElement.class])
  149. ((SVGElement *) child).viewportElement = viewport;
  150. }
  151. - (void)loadDefaults {
  152. // to be overriden by subclasses
  153. }
  154. -(SVGLength*) getAttributeAsSVGLength:(NSString*) attributeName
  155. {
  156. NSString* attributeAsString = [self getAttribute:attributeName];
  157. SVGLength* svgLength = [SVGLength svgLengthFromNSString:attributeAsString];
  158. return svgLength;
  159. }
  160. - (void)postProcessAttributesAddingErrorsTo:(SVGKParseResult *)parseResult {
  161. // to be overriden by subclasses
  162. // make sure super implementation is called
  163. if( [[self getAttribute:@"id"] length] > 0 )
  164. self.identifier = [self getAttribute:@"id"];
  165. /** CSS styles and classes */
  166. if ( [self getAttributeNode:@"style"] )
  167. {
  168. self.style = [[CSSStyleDeclaration alloc] init];
  169. self.style.cssText = [self getAttribute:@"style"]; // causes all the LOCALLY EMBEDDED style info to be parsed
  170. }
  171. if( [self getAttributeNode:@"class"])
  172. {
  173. self.className = [self getAttribute:@"class"];
  174. }
  175. /**
  176. http://www.w3.org/TR/SVG/coords.html#TransformAttribute
  177. The available types of transform definitions include:
  178. * matrix(<a> <b> <c> <d> <e> <f>), which specifies a transformation in the form of a transformation matrix of six values. matrix(a,b,c,d,e,f) is equivalent to applying the transformation matrix [a b c d e f].
  179. * translate(<tx> [<ty>]), which specifies a translation by tx and ty. If <ty> is not provided, it is assumed to be zero.
  180. * scale(<sx> [<sy>]), which specifies a scale operation by sx and sy. If <sy> is not provided, it is assumed to be equal to <sx>.
  181. * rotate(<rotate-angle> [<cx> <cy>]), which specifies a rotation by <rotate-angle> degrees about a given point.
  182. If optional parameters <cx> and <cy> are not supplied, the rotate is about the origin of the current user coordinate system. The operation corresponds to the matrix [cos(a) sin(a) -sin(a) cos(a) 0 0].
  183. If optional parameters <cx> and <cy> are supplied, the rotate is about the point (cx, cy). The operation represents the equivalent of the following specification: translate(<cx>, <cy>) rotate(<rotate-angle>) translate(-<cx>, -<cy>).
  184. * skewX(<skew-angle>), which specifies a skew transformation along the x-axis.
  185. * skewY(<skew-angle>), which specifies a skew transformation along the y-axis.
  186. */
  187. if( [[self getAttribute:@"transform"] length] > 0 || [[self getAttribute:@"gradientTransform"] length] > 0)
  188. {
  189. if( [self conformsToProtocol:@protocol(SVGTransformable)] )
  190. {
  191. SVGElement<SVGTransformable>* selfTransformable = (SVGElement<SVGTransformable>*) self;
  192. /**
  193. http://www.w3.org/TR/SVG/coords.html#TransformAttribute
  194. The individual transform definitions are separated by whitespace and/or a comma.
  195. */
  196. NSString* value = [self getAttribute:@"transform"];
  197. if (!value.length) {
  198. value = [self getAttribute:@"gradientTransform"];
  199. }
  200. NSError* error = nil;
  201. NSRegularExpression* regexpTransformListItem = [NSRegularExpression regularExpressionWithPattern:@"[^\\(\\),]*\\([^\\)]*" options:0 error:&error]; // anything except space and brackets ... followed by anything except open bracket ... plus anything until you hit a close bracket
  202. [regexpTransformListItem enumerateMatchesInString:value options:0 range:NSMakeRange(0, [value length]) usingBlock:
  203. ^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop)
  204. {
  205. NSString* transformString = [value substringWithRange:[result range]];
  206. //EXTREME DEBUG: SVGKitLogVerbose(@"[%@] DEBUG: found a transform element (should be command + open bracket + args + close bracket) = %@", [self class], transformString);
  207. NSRange loc = [transformString rangeOfString:@"("];
  208. if( loc.length == 0 )
  209. {
  210. SVGKitLogError(@"[%@] ERROR: input file is illegal, has an item in the SVG transform attribute which has no open-bracket. Item = %@, transform attribute value = %@", [self class], transformString, value );
  211. return;
  212. }
  213. NSString* command = [transformString substringToIndex:loc.location];
  214. NSString* rawParametersString = [transformString substringFromIndex:loc.location+1];
  215. NSArray* parameterStrings = [rawParametersString componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@", "]];
  216. /** if you get ", " (comma AND space), Apple sends you an extra 0-length match - "" - between your args. We strip that here */
  217. parameterStrings = [parameterStrings filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"length > 0"]];
  218. //EXTREME DEBUG: SVGKitLogVerbose(@"[%@] DEBUG: found parameters = %@", [self class], parameterStrings);
  219. command = [command stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" "]];
  220. if( [command isEqualToString:@"translate"] )
  221. {
  222. CGFloat xtrans = [(NSString*)[parameterStrings objectAtIndex:0] floatValue];
  223. CGFloat ytrans = [parameterStrings count] > 1 ? [(NSString*)[parameterStrings objectAtIndex:1] floatValue] : 0.0;
  224. CGAffineTransform nt = CGAffineTransformMakeTranslation(xtrans, ytrans);
  225. selfTransformable.transform = CGAffineTransformConcat( nt, selfTransformable.transform ); // Apple's method appears to be backwards, and not doing what Apple's docs state
  226. }
  227. else if( [command isEqualToString:@"scale"] )
  228. {
  229. CGFloat xScale = [(NSString*)[parameterStrings objectAtIndex:0] floatValue];
  230. CGFloat yScale = [parameterStrings count] > 1 ? [(NSString*)[parameterStrings objectAtIndex:1] floatValue] : xScale;
  231. CGAffineTransform nt = CGAffineTransformMakeScale(xScale, yScale);
  232. selfTransformable.transform = CGAffineTransformConcat( nt, selfTransformable.transform ); // Apple's method appears to be backwards, and not doing what Apple's docs state
  233. }
  234. else if( [command isEqualToString:@"matrix"] )
  235. {
  236. CGFloat a = [(NSString*)[parameterStrings objectAtIndex:0] floatValue];
  237. CGFloat b = [(NSString*)[parameterStrings objectAtIndex:1] floatValue];
  238. CGFloat c = [(NSString*)[parameterStrings objectAtIndex:2] floatValue];
  239. CGFloat d = [(NSString*)[parameterStrings objectAtIndex:3] floatValue];
  240. CGFloat tx = [(NSString*)[parameterStrings objectAtIndex:4] floatValue];
  241. CGFloat ty = [(NSString*)[parameterStrings objectAtIndex:5] floatValue];
  242. CGAffineTransform nt = CGAffineTransformMake(a, b, c, d, tx, ty );
  243. selfTransformable.transform = CGAffineTransformConcat( nt, selfTransformable.transform ); // Apple's method appears to be backwards, and not doing what Apple's docs state
  244. }
  245. else if( [command isEqualToString:@"rotate"] )
  246. {
  247. /**
  248. This section merged from warpflyght's commit:
  249. https://github.com/warpflyght/SVGKit/commit/c1bd9b3d0607635dda14ec03579793fc682763d9
  250. */
  251. if( [parameterStrings count] == 1)
  252. {
  253. CGFloat degrees = [[parameterStrings objectAtIndex:0] floatValue];
  254. CGFloat radians = degrees * M_PI / 180.0;
  255. selfTransformable.transform = CGAffineTransformRotate(selfTransformable.transform, radians);
  256. // CGAffineTransform nt = CGAffineTransformMakeRotation(radians);
  257. // selfTransformable.transform = CGAffineTransformConcat( nt, selfTransformable.transform ); // Apple's method appears to be backwards, and not doing what Apple's docs state
  258. }
  259. else if( [parameterStrings count] == 3)
  260. {
  261. CGFloat degrees = [[parameterStrings objectAtIndex:0] floatValue];
  262. CGFloat radians = degrees * M_PI / 180.0;
  263. CGFloat centerX = [[parameterStrings objectAtIndex:1] floatValue];
  264. CGFloat centerY = [[parameterStrings objectAtIndex:2] floatValue];
  265. selfTransformable.transform = CGAffineTransformTranslate(selfTransformable.transform, centerX, centerY);
  266. selfTransformable.transform = CGAffineTransformRotate(selfTransformable.transform, radians);
  267. selfTransformable.transform = CGAffineTransformTranslate(selfTransformable.transform, -1.0 * centerX, -1.0 * centerY);
  268. // CGAffineTransform nt = CGAffineTransformIdentity;
  269. // nt = CGAffineTransformConcat( nt, CGAffineTransformMakeTranslation(centerX, centerY) );
  270. // nt = CGAffineTransformConcat( nt, CGAffineTransformMakeRotation(radians) );
  271. // nt = CGAffineTransformConcat( nt, CGAffineTransformMakeTranslation(-1.0 * centerX, -1.0 * centerY) );
  272. // selfTransformable.transform = CGAffineTransformConcat( nt, selfTransformable.transform ); // Apple's method appears to be backwards, and not doing what Apple's docs state
  273. } else
  274. {
  275. SVGKitLogError(@"[%@] ERROR: input file is illegal, has an SVG matrix transform attribute without the required 1 or 3 parameters. Item = %@, transform attribute value = %@", [self class], transformString, value );
  276. return;
  277. }
  278. }
  279. else if( [command isEqualToString:@"skewX"] )
  280. {
  281. CGFloat degrees = [[parameterStrings objectAtIndex:0] floatValue];
  282. CGFloat radians = degrees * M_PI / 180.0;
  283. CGAffineTransform nt = CGAffineTransformMake(1, 0, tan(radians), 1, 0, 0);
  284. selfTransformable.transform = CGAffineTransformConcat( nt, selfTransformable.transform );
  285. }
  286. else if( [command isEqualToString:@"skewY"] )
  287. {
  288. CGFloat degrees = [[parameterStrings objectAtIndex:0] floatValue];
  289. CGFloat radians = degrees * M_PI / 180.0;
  290. CGAffineTransform nt = CGAffineTransformMake(1, tan(radians), 0, 1, 0, 0);
  291. selfTransformable.transform = CGAffineTransformConcat( nt, selfTransformable.transform );
  292. }
  293. else
  294. {
  295. NSAssert( FALSE, @"Not implemented yet: transform = %@ %@", command, transformString );
  296. }
  297. }];
  298. //DEBUG: SVGKitLogVerbose(@"[%@] Set local / relative transform = (%2.2f, %2.2f // %2.2f, %2.2f) + (%2.2f, %2.2f translate)", [self class], selfTransformable.transform.a, selfTransformable.transform.b, selfTransformable.transform.c, selfTransformable.transform.d, selfTransformable.transform.tx, selfTransformable.transform.ty );
  299. }
  300. }
  301. }
  302. - (NSString *)description {
  303. return [NSString stringWithFormat:@"<%@ %p | id=%@ | prefix:localName=%@:%@ | tagName=%@ | stringValue=%@ | children=%ld>",
  304. [self class], self, _identifier, self.prefix, self.localName, self.tagName, _stringValue, (unsigned long)self.childNodes.length];
  305. }
  306. #pragma mark - Objective-C init methods (not in SVG Spec - the official spec has no explicit way to create nodes, which is clearly a bug in the Spec. Until they fix the spec, we have to do something or else SVG would be unusable)
  307. - (id)initWithLocalName:(NSString*) n attributes:(NSMutableDictionary*) attributes
  308. {
  309. self = [super initWithLocalName:n attributes:attributes];
  310. if( self )
  311. {
  312. [self loadDefaults];
  313. if( [self conformsToProtocol:@protocol(SVGTransformable)] )
  314. {
  315. SVGElement<SVGTransformable>* selfTransformable = (SVGElement<SVGTransformable>*) self;
  316. selfTransformable.transform = CGAffineTransformIdentity;
  317. }
  318. }
  319. return self;
  320. }
  321. - (id)initWithQualifiedName:(NSString*) n inNameSpaceURI:(NSString*) nsURI attributes:(NSMutableDictionary*) attributes
  322. {
  323. self = [super initWithQualifiedName:n inNameSpaceURI:nsURI attributes:attributes];
  324. if( self )
  325. {
  326. [self loadDefaults];
  327. if( [self conformsToProtocol:@protocol(SVGTransformable)] )
  328. {
  329. SVGElement<SVGTransformable>* selfTransformable = (SVGElement<SVGTransformable>*) self;
  330. selfTransformable.transform = CGAffineTransformIdentity;
  331. }
  332. }
  333. return self;
  334. }
  335. - (NSRange) nextSelectorGroupFromText:(NSString *) selectorText startFrom:(NSRange) previous
  336. {
  337. previous.location = previous.location + previous.length;
  338. if( previous.location < selectorText.length )
  339. {
  340. if( [selectorText characterAtIndex:previous.location] == ',' )
  341. previous.location = previous.location + 1;
  342. NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
  343. while( previous.location < selectorText.length && [whitespace characterIsMember:[selectorText characterAtIndex:previous.location]] )
  344. previous.location = previous.location + 1;
  345. if( previous.location < selectorText.length ) {
  346. previous.length = selectorText.length - previous.location;
  347. NSRange nextGroup = [selectorText rangeOfString:@"," options:0 range:previous];
  348. if( nextGroup.location == NSNotFound )
  349. return previous;
  350. else
  351. return NSMakeRange(previous.location, nextGroup.location - previous.location);
  352. }
  353. }
  354. return NSMakeRange(NSNotFound, -1);
  355. }
  356. - (NSRange) nextSelectorRangeFromText:(NSString *) selectorText startFrom:(NSRange) previous
  357. {
  358. NSMutableCharacterSet *identifier = [NSMutableCharacterSet alphanumericCharacterSet];
  359. [identifier addCharactersInString:@"-_"];
  360. NSCharacterSet *selectorStart = [NSCharacterSet characterSetWithCharactersInString:@"#."];
  361. NSInteger start = -1;
  362. NSUInteger end = 0;
  363. for( NSUInteger i = previous.location + previous.length; i < selectorText.length; i++ )
  364. {
  365. unichar c = [selectorText characterAtIndex:i];
  366. if( [selectorStart characterIsMember:c] )
  367. {
  368. if( start == -1 )
  369. start = i;
  370. else
  371. break;
  372. }
  373. else if( [identifier characterIsMember:c] )
  374. {
  375. if( start == -1 )
  376. start = i;
  377. end = i;
  378. }
  379. else if( start != -1 )
  380. {
  381. break;
  382. }
  383. }
  384. if( start != -1 )
  385. return NSMakeRange(start, end + 1 - start);
  386. else
  387. return NSMakeRange(NSNotFound, -1);
  388. }
  389. - (BOOL) selector:(NSString *)selector appliesTo:(SVGElement *) element specificity:(NSInteger*) specificity
  390. {
  391. if( [selector characterAtIndex:0] == '.' )
  392. {
  393. if( element.className != nil )
  394. {
  395. NSScanner *classNameScanner = [NSScanner scannerWithString:element.className];
  396. NSMutableCharacterSet *whitespaceAndCommaSet = [NSMutableCharacterSet whitespaceCharacterSet];
  397. NSString *substring;
  398. [whitespaceAndCommaSet addCharactersInString:@","];
  399. selector = [selector substringFromIndex:1];
  400. __block BOOL matched = NO;
  401. while ([classNameScanner scanUpToCharactersFromSet:whitespaceAndCommaSet intoString:&substring])
  402. {
  403. if( [substring isEqualToString:selector] )
  404. {
  405. matched = YES;
  406. break;
  407. }
  408. if (!classNameScanner.isAtEnd)
  409. classNameScanner.scanLocation = classNameScanner.scanLocation+1L;
  410. }
  411. if( matched )
  412. {
  413. *specificity += 100;
  414. return YES;
  415. }
  416. }
  417. }
  418. else if( [selector characterAtIndex:0] == '#' )
  419. {
  420. if( element.identifier != nil && [element.identifier isEqualToString:[selector substringFromIndex:1]] )
  421. {
  422. *specificity += 10000;
  423. return YES;
  424. }
  425. }
  426. else if( element.nodeName != nil && [element.nodeName isEqualToString:selector] )
  427. {
  428. *specificity += 1;
  429. return YES;
  430. }
  431. else if( [selector isEqualToString:@"*"] )
  432. {
  433. return YES;
  434. }
  435. return NO;
  436. }
  437. - (BOOL) styleRule:(CSSStyleRule *) styleRule appliesTo:(SVGElement *) element specificity:(NSInteger*) specificity
  438. {
  439. NSRange nextGroup = [self nextSelectorGroupFromText:styleRule.selectorText startFrom:NSMakeRange(0, 0)];
  440. while( nextGroup.location != NSNotFound )
  441. {
  442. NSRange nextRule = [self nextSelectorRangeFromText:styleRule.selectorText startFrom:NSMakeRange(nextGroup.location, 0)];
  443. BOOL match = nextRule.location != NSNotFound;
  444. while( nextRule.location != NSNotFound )
  445. {
  446. if( ![self selector:[styleRule.selectorText substringWithRange:nextRule] appliesTo:element specificity:specificity] )
  447. {
  448. match = NO;
  449. break;
  450. }
  451. nextRule = [self nextSelectorRangeFromText:styleRule.selectorText startFrom:nextRule];
  452. if( nextRule.location > (nextGroup.location + nextGroup.length) )
  453. break;
  454. }
  455. if( match )
  456. return YES;
  457. nextGroup = [self nextSelectorGroupFromText:styleRule.selectorText startFrom:nextGroup];
  458. }
  459. return NO;
  460. }
  461. #pragma mark - CSS cascading special attributes
  462. -(NSString*) cascadedValueForStylableProperty:(NSString*) stylableProperty
  463. {
  464. return [self cascadedValueForStylableProperty:stylableProperty inherit:YES];
  465. }
  466. -(NSString*) cascadedValueForStylableProperty:(NSString*) stylableProperty inherit:(BOOL)inherit
  467. {
  468. /**
  469. This is the core implementation of Cascading Style Sheets, inside SVG.
  470. c.f.: http://www.w3.org/TR/SVG/styling.html
  471. In SVG, the set of things that can be cascaded is strictly defined, c.f.:
  472. http://www.w3.org/TR/SVG/propidx.html
  473. For each of those, the implementation is the same.
  474. ********* WAWRNING: THE CURRENT IMPLEMENTATION BELOW IS VERY MUCH INCOMPLETE, BUT IT WORKS FOR VERY SIMPLE SVG'S ************
  475. */
  476. NSString* localStyleValue = [self.style getPropertyValue:stylableProperty];
  477. if( localStyleValue != nil )
  478. return localStyleValue;
  479. /** we have a locally declared CSS class; let's go hunt for it in the document's stylesheets */
  480. @autoreleasepool /** DOM / CSS is insanely verbose, so this is likely to generate a lot of crud objects */
  481. {
  482. CSSStyleRule *mostSpecificRule = nil;
  483. NSInteger mostSpecificity = -1;
  484. for( StyleSheet* genericSheet in self.rootOfCurrentDocumentFragment.styleSheets.internalArray.reverseObjectEnumerator ) // because it's far too much effort to use CSS's low-quality iteration here...
  485. {
  486. if( [genericSheet isKindOfClass:[CSSStyleSheet class]])
  487. {
  488. CSSStyleSheet* cssSheet = (CSSStyleSheet*) genericSheet;
  489. for( CSSRule* genericRule in cssSheet.cssRules.internalArray.reverseObjectEnumerator)
  490. {
  491. if( [genericRule isKindOfClass:[CSSStyleRule class]])
  492. {
  493. CSSStyleRule* styleRule = (CSSStyleRule*) genericRule;
  494. if( [styleRule.style getPropertyCSSValue:stylableProperty] != nil )
  495. {
  496. NSInteger ruleSpecificity = 0;
  497. if( [self styleRule:styleRule appliesTo:self specificity:&ruleSpecificity] )
  498. {
  499. if( ruleSpecificity > mostSpecificity ) {
  500. mostSpecificity = ruleSpecificity;
  501. mostSpecificRule = styleRule;
  502. }
  503. }
  504. }
  505. }
  506. }
  507. }
  508. }
  509. if( mostSpecificRule != nil )
  510. return [mostSpecificRule.style getPropertyValue:stylableProperty];
  511. }
  512. /** if there's a local property, use that */
  513. if( [self hasAttribute:stylableProperty])
  514. return [self getAttribute:stylableProperty];
  515. if( inherit )
  516. {
  517. /** Finally: move up the tree until you find a <G> or <SVG> node, and ask it to provide the value
  518. */
  519. Node* parentElement = self.parentNode;
  520. while( parentElement != nil
  521. && ! [parentElement isKindOfClass:[SVGGElement class]]
  522. && ! [parentElement isKindOfClass:[SVGSVGElement class]])
  523. {
  524. parentElement = parentElement.parentNode;
  525. }
  526. if( parentElement == nil )
  527. {
  528. return nil; // give up!
  529. }
  530. else
  531. {
  532. return [((SVGElement*)parentElement) cascadedValueForStylableProperty:stylableProperty];
  533. }
  534. }
  535. else
  536. {
  537. return nil;
  538. }
  539. }
  540. @end