Skip to content

Commit 9de08c3

Browse files
committed
remove percent encoding from URL fragments
1 parent 67ed5e4 commit 9de08c3

File tree

8 files changed

+92
-23
lines changed

8 files changed

+92
-23
lines changed

Examples/Sources/ViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class ViewController: UIViewController {
6565

6666
override func loadView() {
6767
let imageView = UIImageView(frame: UIScreen.main.bounds)
68-
imageView.image = SVG(named: "rgba.svg", in: .samples)?.rasterize()
68+
imageView.image = SVG(named: "stars.svg", in: .samples)?.rasterize()
6969
imageView.contentMode = .scaleAspectFit
7070
imageView.backgroundColor = .white
7171
self.view = imageView

Samples.bundle/stars.svg

Lines changed: 20 additions & 0 deletions
Loading

SwiftDraw/LayerTree.Builder.Layer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ extension LayerTree.Builder {
4141

4242
func makeUseLayerContents(from use: DOM.Use, with state: State) throws -> LayerTree.Layer.Contents {
4343
guard
44-
let id = use.href.fragment,
44+
let id = use.href.fragmentID,
4545
let element = svg.firstGraphicsElement(with: id) else {
4646
throw LayerTree.Error.invalid("missing referenced element: \(use.href)")
4747
}

SwiftDraw/LayerTree.Builder.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,14 +124,14 @@ extension LayerTree {
124124
}
125125

126126
func createClipShapes(for element: DOM.GraphicsElement) -> [Shape] {
127-
guard let clipId = element.attributes.clipPath?.fragment,
127+
guard let clipId = element.attributes.clipPath?.fragmentID,
128128
let clip = svg.defs.clipPaths.first(where: { $0.id == clipId }) else { return [] }
129129

130130
return clip.childElements.compactMap{ Builder.makeShape(from: $0) }
131131
}
132132

133133
func createMaskLayer(for element: DOM.GraphicsElement) -> Layer? {
134-
guard let maskId = element.attributes.mask?.fragment,
134+
guard let maskId = element.attributes.mask?.fragmentID,
135135
let mask = svg.defs.masks.first(where: { $0.id == maskId }) else { return nil }
136136

137137
let l = Layer()
@@ -145,7 +145,7 @@ extension LayerTree {
145145
}
146146

147147
func makeFilters(for state: State) -> [Filter] {
148-
guard let filterId = state.filter?.fragment,
148+
guard let filterId = state.filter?.fragmentID,
149149
let filter = svg.defs.filters.first(where: { $0.id == filterId }) else { return [] }
150150
return filter.effects
151151
}
@@ -191,15 +191,15 @@ extension LayerTree.Builder {
191191
.withAlpha(state.fillOpacity).maybeNone()
192192

193193
if case .url(let patternId) = state.fill,
194-
let element = svg.defs.patterns.first(where: { $0.id == patternId.fragment }) {
194+
let element = svg.defs.patterns.first(where: { $0.id == patternId.fragmentID }) {
195195
let pattern = makePattern(for: element)
196196
return LayerTree.FillAttributes(pattern: pattern, rule: state.fillRule, opacity: state.fillOpacity)
197197
} else if case .url(let gradientId) = state.fill,
198-
let element = svg.defs.linearGradients.first(where: { $0.id == gradientId.fragment }),
198+
let element = svg.defs.linearGradients.first(where: { $0.id == gradientId.fragmentID }),
199199
let gradient = makeGradient(for: element) {
200200
return LayerTree.FillAttributes(linear: gradient, rule: state.fillRule, opacity: state.fillOpacity)
201201
} else if case .url(let gradientId) = state.fill,
202-
let element = svg.defs.radialGradients.first(where: { $0.id == gradientId.fragment }),
202+
let element = svg.defs.radialGradients.first(where: { $0.id == gradientId.fragmentID }),
203203
let gradient = makeGradient(for: element) {
204204
return LayerTree.FillAttributes(radial: gradient, rule: state.fillRule, opacity: state.fillOpacity)
205205
} else {
@@ -208,15 +208,15 @@ extension LayerTree.Builder {
208208
}
209209

210210
func makeLinearGradient(for gradientId: URL) -> LayerTree.LinearGradient? {
211-
guard let element = svg.defs.linearGradients.first(where: { $0.id == gradientId.fragment }),
211+
guard let element = svg.defs.linearGradients.first(where: { $0.id == gradientId.fragmentID }),
212212
let gradient = makeGradient(for: element) else {
213213
return nil
214214
}
215215
return gradient
216216
}
217217

218218
func makeRadialGradient(for gradientId: URL) -> LayerTree.RadialGradient? {
219-
guard let element = svg.defs.radialGradients.first(where: { $0.id == gradientId.fragment }),
219+
guard let element = svg.defs.radialGradients.first(where: { $0.id == gradientId.fragmentID }),
220220
let gradient = makeGradient(for: element) else {
221221
return nil
222222
}
@@ -249,7 +249,7 @@ extension LayerTree.Builder {
249249
let y2 = element.y2 ?? 0
250250

251251
var stops = [LayerTree.Gradient.Stop]()
252-
if let id = element.href?.fragment,
252+
if let id = element.href?.fragmentID,
253253
let reference = svg.defs.linearGradients.first(where: { $0.id == id }) {
254254
stops = makeGradientStops(for: reference)
255255
} else {
@@ -272,7 +272,7 @@ extension LayerTree.Builder {
272272

273273
func makeGradient(for element: DOM.RadialGradient) -> LayerTree.RadialGradient? {
274274
var stops = [LayerTree.Gradient.Stop]()
275-
if let id = element.href?.fragment,
275+
if let id = element.href?.fragmentID,
276276
let reference = svg.defs.radialGradients.first(where: { $0.id == id }) {
277277
stops = makeGradientStops(for: reference)
278278
} else {

SwiftDraw/URL+Fragment.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// URL+Fragment.swift
3+
// SwiftDraw
4+
//
5+
// Created by Simon Whitty on 12/10/24.
6+
// Copyright 2024 Simon Whitty
7+
//
8+
// Distributed under the permissive zlib license
9+
// Get the latest version from here:
10+
//
11+
// https://github.com/swhitty/SwiftDraw
12+
//
13+
// This software is provided 'as-is', without any express or implied
14+
// warranty. In no event will the authors be held liable for any damages
15+
// arising from the use of this software.
16+
//
17+
// Permission is granted to anyone to use this software for any purpose,
18+
// including commercial applications, and to alter it and redistribute it
19+
// freely, subject to the following restrictions:
20+
//
21+
// 1. The origin of this software must not be misrepresented; you must not
22+
// claim that you wrote the original software. If you use this software
23+
// in a product, an acknowledgment in the product documentation would be
24+
// appreciated but is not required.
25+
//
26+
// 2. Altered source versions must be plainly marked as such, and must not be
27+
// misrepresented as being the original software.
28+
//
29+
// 3. This notice may not be removed or altered from any source distribution.
30+
//
31+
32+
import Foundation
33+
34+
extension URL {
35+
36+
var fragmentID: String? {
37+
#if compiler(>=5.7) && canImport(Darwin)
38+
if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) {
39+
return fragment(percentEncoded: false)
40+
} else {
41+
return fragment
42+
}
43+
#else
44+
return fragment
45+
#endif
46+
}
47+
48+
}

SwiftDrawTests/Parser.GraphicAttributeTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ final class ParserGraphicAttributeTests: XCTestCase {
8282
XCTAssertEqual(parsed.fillOpacity, 0.25)
8383
XCTAssertEqual(parsed.fillRule, .evenodd)
8484
XCTAssertEqual(parsed.transform!, [.scale(sx: 15, sy: 15)])
85-
XCTAssertEqual(parsed.clipPath?.fragment, "circlePath")
86-
XCTAssertEqual(parsed.mask?.fragment, "fancyMask")
87-
XCTAssertEqual(parsed.filter?.fragment, "blur")
85+
XCTAssertEqual(parsed.clipPath?.fragmentID, "circlePath")
86+
XCTAssertEqual(parsed.mask?.fragmentID, "fancyMask")
87+
XCTAssertEqual(parsed.filter?.fragmentID, "blur")
8888
}
8989

9090
func testCircle() throws {
@@ -93,7 +93,7 @@ final class ParserGraphicAttributeTests: XCTestCase {
9393
let parsed = try XMLParser().parseGraphicsElement(el)
9494
let circle = parsed as? DOM.Circle
9595
XCTAssertNotNil(circle)
96-
XCTAssertEqual(circle?.style.clipPath?.fragment, "cp1")
96+
XCTAssertEqual(circle?.style.clipPath?.fragmentID, "cp1")
9797
XCTAssertEqual(circle?.style.fill, .color(.keyword(.black)))
9898
XCTAssertEqual(circle?.style.strokeWidth, 2)
9999
}

SwiftDrawTests/UseTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@ final class UseTests: XCTestCase {
3838
var node = ["xlink:href": "#line2", "href": "#line1"]
3939

4040
var parsed = try XMLParser().parseUse(node)
41-
XCTAssertEqual(parsed.href.fragment, "line2")
41+
XCTAssertEqual(parsed.href.fragmentID, "line2")
4242
XCTAssertNil(parsed.x)
4343
XCTAssertNil(parsed.y)
4444

4545
node["x"] = "20"
4646
node["y"] = "30"
4747

4848
parsed = try XMLParser().parseUse(node)
49-
XCTAssertEqual(parsed.href.fragment, "line2")
49+
XCTAssertEqual(parsed.href.fragmentID, "line2")
5050
XCTAssertEqual(parsed.x, 20)
5151
XCTAssertEqual(parsed.y, 30)
5252
}

SwiftDrawTests/ValueParserTests.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,16 @@ final class ValueParserTests: XCTestCase {
126126
}
127127

128128
func testUrl() {
129-
XCTAssertEqual(try parser.parseUrl("#testingId").fragment, "testingId")
129+
#if compiler(>=5.9) && canImport(Darwin)
130+
XCTAssertEqual(try parser.parseUrl("#testing🐟").fragmentID, "testing🐟")
131+
#else
132+
XCTAssertEqual(try parser.parseUrl("#testing").fragmentID, "testing")
133+
#endif
130134
XCTAssertEqual(try parser.parseUrl("http://www.google.com").host, "www.google.com")
131-
132-
//XCTAssertThrowsError(try parser.parseUrl("www.google.com"))
133-
//XCTAssertThrowsError(try parser.parseUrl("sd"))
134135
}
135136

136137
func testUrlSelector() {
137-
XCTAssertEqual(try parser.parseUrlSelector("url(#testingId)").fragment, "testingId")
138+
XCTAssertEqual(try parser.parseUrlSelector("url(#testingId)").fragmentID, "testingId")
138139
XCTAssertEqual(try parser.parseUrlSelector("url(http://www.google.com)").host, "www.google.com")
139140

140141
XCTAssertThrowsError(try parser.parseUrlSelector("url(#testingId) other"))

0 commit comments

Comments
 (0)