@@ -140,9 +140,22 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString
140
140
}
141
141
142
142
public init ( bundleIdentifier: String , path: String , fragment: String ? = nil , sourceLanguages: Set < SourceLanguage > ) {
143
+ self . init (
144
+ bundleIdentifier: bundleIdentifier,
145
+ urlReadablePath: urlReadablePath ( path) ,
146
+ urlReadableFragment: fragment. map ( urlReadableFragment ( _: ) ) ,
147
+ sourceLanguages: sourceLanguages
148
+ )
149
+ }
150
+
151
+ private init ( bundleIdentifier: String , urlReadablePath: String , urlReadableFragment: String ? = nil , sourceLanguages: Set < SourceLanguage > ) {
143
152
precondition ( !sourceLanguages. isEmpty, " ResolvedTopicReference.sourceLanguages cannot be empty " )
144
153
// Check for a cached instance of the reference
145
- let key = Self . cacheKey ( path: path, fragment: fragment, sourceLanguages: sourceLanguages)
154
+ let key = Self . cacheKey (
155
+ urlReadablePath: urlReadablePath,
156
+ urlReadableFragment: urlReadableFragment,
157
+ sourceLanguages: sourceLanguages
158
+ )
146
159
let cached = Self . sharedPool. sync { $0 [ bundleIdentifier] ? [ key] }
147
160
if let resolved = cached {
148
161
self = resolved
@@ -252,7 +265,7 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString
252
265
public func appendingPath( _ path: String ) -> ResolvedTopicReference {
253
266
let newReference = ResolvedTopicReference (
254
267
bundleIdentifier: bundleIdentifier,
255
- path : url. appendingPathComponent ( urlReadablePath ( path) , isDirectory: false ) . path,
268
+ urlReadablePath : url. appendingPathComponent ( urlReadablePath ( path) , isDirectory: false ) . path,
256
269
sourceLanguages: sourceLanguages
257
270
)
258
271
return newReference
@@ -273,8 +286,8 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString
273
286
let newPath = url. appendingPathComponent ( referencePath, isDirectory: false ) . path
274
287
let newReference = ResolvedTopicReference (
275
288
bundleIdentifier: bundleIdentifier,
276
- path : newPath,
277
- fragment : reference. fragment,
289
+ urlReadablePath : newPath,
290
+ urlReadableFragment : reference. fragment,
278
291
sourceLanguages: sourceLanguages
279
292
)
280
293
return newReference
@@ -285,8 +298,8 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString
285
298
let newPath = String ( pathComponents. dropLast ( ) . joined ( separator: " / " ) . dropFirst ( ) )
286
299
let newReference = ResolvedTopicReference (
287
300
bundleIdentifier: bundleIdentifier,
288
- path : newPath,
289
- fragment : fragment,
301
+ urlReadablePath : newPath,
302
+ urlReadableFragment : fragment,
290
303
sourceLanguages: sourceLanguages
291
304
)
292
305
return newReference
@@ -514,32 +527,30 @@ public struct ResourceReference: Hashable {
514
527
/// For example, a path like `"hello world/example project"` is converted to `"hello-world/example-project"`
515
528
/// instead of `"hello%20world/example%20project"`.
516
529
func urlReadablePath( _ path: String ) -> String {
517
- return path. components ( separatedBy: CharacterSet . urlPathAllowed. inverted)
518
- . joined ( separator: " - " )
530
+ return path. components ( separatedBy: . urlPathNotAllowed) . joined ( separator: " - " )
531
+ }
532
+
533
+ private extension CharacterSet {
534
+ static let invalidCharacterSet = CharacterSet ( charactersIn: " ' \" ` " )
535
+ static let whitespaceAndDashes = CharacterSet ( charactersIn: " - " ) . union ( . whitespaces)
519
536
}
520
537
521
538
/// Creates a more readable version of a fragment by replacing characters that are not allowed in the fragment of a URL with hyphens.
522
539
///
523
540
/// If this step is not performed, the disallowed characters are instead percent escape encoded, which is less readable.
524
541
/// For example, a fragment like `"#hello world"` is converted to `"#hello-world"` instead of `"#hello%20world"`.
525
542
func urlReadableFragment( _ fragment: String ) -> String {
526
- // Trim leading/trailing invalid characters
527
543
var fragment = fragment
544
+ // Trim leading/trailing whitespace
528
545
. trimmingCharacters ( in: . whitespaces)
529
546
530
- // Replace continuous whitespace
531
- fragment = fragment . components ( separatedBy: . whitespaces )
547
+ // Replace continuous whitespace and dashes
548
+ . components ( separatedBy: . whitespaceAndDashes )
532
549
. filter ( { !$0. isEmpty } )
533
550
. joined ( separator: " - " )
534
-
535
- let invalidCharacterSet = CharacterSet ( charactersIn: " ' \" ` " )
536
- fragment = fragment. components ( separatedBy: invalidCharacterSet)
537
- . joined ( )
538
-
539
- // Replace continuous dashes
540
- fragment = fragment. components ( separatedBy: CharacterSet ( charactersIn: " - " ) )
541
- . filter ( { !$0. isEmpty } )
542
- . joined ( separator: " - " )
543
-
551
+
552
+ // Remove invalid characters
553
+ fragment. unicodeScalars. removeAll ( where: CharacterSet . invalidCharacterSet. contains)
554
+
544
555
return fragment
545
556
}
0 commit comments