Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore unknown elements #121

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. Items under
### Fixes
### Internal Changes
- Add unit tests. Ariel Elkin.
- Handle attributes on `<a>` elements and ignore unknown elements. Scott Talbot [#121](https://github.com/pocketsvg/PocketSVG/pull/121)

## [2.4.0]
### New Features
Expand Down
51 changes: 50 additions & 1 deletion PocketSVGTests/PocketSVGTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,54 @@ class PocketSVGTests: XCTestCase {

XCTAssertEqual(rectanglePath.svgRepresentation, representation)
}


func testIgnoresMaskElement() {
let svgString = """
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px">
<g>
<mask>
<g>
<rect width="100" height="100" style="fill: #FFFFFF" />
</g>
</mask>
<rect x="20" y="20" width="60" height="60" style="fill: #FF0000"/>
</g>
</svg>
"""

let paths = SVGBezierPath.paths(fromSVGString: svgString)
XCTAssertEqual(paths.count, 1)

guard let path = paths.first else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if XCTAssertEqual(paths.count, 1) passes, then we know that paths.first != nil, so this guard let is redundant and it's safe to do path!.bounds below.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured that bailing after failing the assertion was friendlier than hitting a fatal error and breaking the whole test run in the case that we don't parse any paths out of the svg?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an individual test's execution stops as soon as an assertion fails, so if we don't parse any paths out of the svg we'll duly fail the assertion on line 101 and we're guaranteed not to execute the code with the force unwrap.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. That’s not how Xcode (9.3) behaves on my computer and I can’t see .continueAfterFailure being set anywhere. Is that a global configuration option in Xcode?

Copy link
Collaborator

@arielelkin arielelkin Apr 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, sorry, you're absolutely right, I hadn't set .continueAfterFailure to false

I have now: 1c014fa

return
}

XCTAssertEqual(path.bounds, CGRect(x: 20, y: 20, width: 60, height: 60))
}

func testRespectsAttributesOnAElement() {
let svgString = """
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px">
<a transform="translate(5 10)">
<rect x="15" y="10" width="60" height="60"/>
</a>
</svg>
"""

let paths = SVGBezierPath.paths(fromSVGString: svgString)
XCTAssertEqual(paths.count, 1)

guard let path = paths.first else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto as above.

return
}

let pathTransform = (path.svgAttributes["transform"]! as! NSValue).cgAffineTransformValue
let pathBounds = path.bounds
let translatedPathBounds = pathBounds.applying(pathTransform)

XCTAssertEqual(pathTransform, CGAffineTransform(translationX: 5, y: 10))
XCTAssertEqual(pathBounds, CGRect(x: 15, y: 10, width: 60, height: 60))
XCTAssertEqual(translatedPathBounds, CGRect(x: 20, y: 20, width: 60, height: 60))
}

}
2 changes: 1 addition & 1 deletion SVGBezierPath.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ FOUNDATION_EXTERN void SVGDrawPathsWithBlock(NSArray<SVGBezierPath*> * const pat
* @brief Returns an array of paths given the XML string of an SVG.
*
*/
+ (NSArray *)pathsFromSVGString:(NSString *)svgString;
+ (NSArray<SVGBezierPath*> *)pathsFromSVGString:(NSString *)svgString;

/*!
* @brief Returns a new path with the values of `attributes` added to `svgAttributes`
Expand Down
6 changes: 3 additions & 3 deletions SVGBezierPath.mm
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ + (void)resetCache
return [[NSArray alloc] initWithArray:paths copyItems:YES];
}

+ (NSArray *)pathsFromSVGString:(NSString * const)svgString
+ (NSArray<SVGBezierPath*> *)pathsFromSVGString:(NSString * const)svgString
{
SVGAttributeSet *cgAttrs;
NSArray * const pathRefs = CGPathsFromSVGString(svgString, &cgAttrs);
NSMutableArray * const paths = [NSMutableArray arrayWithCapacity:pathRefs.count];
NSArray * const pathRefs = CGPathsFromSVGString(svgString, &cgAttrs);
NSMutableArray<SVGBezierPath*> * const paths = [NSMutableArray arrayWithCapacity:pathRefs.count];
for(id pathRef in pathRefs) {
SVGBezierPath * const uiPath = [self bezierPathWithCGPath:(__bridge CGPathRef)pathRef];
uiPath->_svgAttributes = [cgAttrs attributesForPath:(__bridge CGPathRef)pathRef] ?: @{};
Expand Down
16 changes: 13 additions & 3 deletions SVGEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,21 @@ @interface SVGAttributeSet () {
valueOptions:NSMapTableStrongMemory];
NSMutableArray * const paths = [NSMutableArray new];

NSUInteger depthWithinUnknownElement = 0;

while(xmlTextReaderRead(_xmlReader) == 1) {
int const type = xmlTextReaderNodeType(_xmlReader);
const char * const tag = (char *)xmlTextReaderConstName(_xmlReader);

CGPathRef path = NULL;
if(type == XML_READER_TYPE_ELEMENT && strcasecmp(tag, "path") == 0)
if(depthWithinUnknownElement > 0) {
if(type == XML_READER_TYPE_ELEMENT && !xmlTextReaderIsEmptyElement(_xmlReader))
++depthWithinUnknownElement;
else if(type == XML_READER_TYPE_END_ELEMENT)
--depthWithinUnknownElement;
} else if(type == XML_READER_TYPE_ELEMENT && strcasecmp(tag, "svg") == 0) {
// recognize the root svg element but we don't need to do anything with it
} else if(type == XML_READER_TYPE_ELEMENT && strcasecmp(tag, "path") == 0)
path = readPathTag();
else if(type == XML_READER_TYPE_ELEMENT && strcasecmp(tag, "polyline") == 0)
path = readPolylineTag();
Expand All @@ -122,12 +131,13 @@ @interface SVGAttributeSet () {
path = readCircleTag();
else if(type == XML_READER_TYPE_ELEMENT && strcasecmp(tag, "ellipse") == 0)
path = readEllipseTag();
else if(strcasecmp(tag, "g") == 0) {
else if(strcasecmp(tag, "g") == 0 || strcasecmp(tag, "a") == 0) {
if(type == XML_READER_TYPE_ELEMENT)
pushGroup(readAttributes());
else if(type == XML_READER_TYPE_END_ELEMENT)
popGroup();
}
} else if(type == XML_READER_TYPE_ELEMENT && !xmlTextReaderIsEmptyElement(_xmlReader))
++depthWithinUnknownElement;
if(path) {
[paths addObject:CFBridgingRelease(path)];

Expand Down