1
1
/*
2
2
This source file is part of the Swift.org open source project
3
3
4
- Copyright (c) 2021 Apple Inc. and the Swift project authors
4
+ Copyright (c) 2021-2024 Apple Inc. and the Swift project authors
5
5
Licensed under Apache License v2.0 with Runtime Library Exception
6
6
7
7
See https://swift.org/LICENSE.txt for license information
@@ -20,7 +20,7 @@ public struct JSONPointer: Codable, CustomStringConvertible, Equatable {
20
20
public var pathComponents : [ String ]
21
21
22
22
public var description : String {
23
- " / \( pathComponents . map ( Self . escape ) . joined ( separator : " / " ) ) "
23
+ Self . escaped ( pathComponents )
24
24
}
25
25
26
26
/// Creates a JSON Pointer given its path components.
@@ -87,36 +87,79 @@ public struct JSONPointer: Codable, CustomStringConvertible, Equatable {
87
87
public init ( from decoder: Decoder ) throws {
88
88
let container = try decoder. singleValueContainer ( )
89
89
let stringValue = try container. decode ( String . self)
90
- self . pathComponents = stringValue . removingLeadingSlash . components ( separatedBy : " / " ) . map ( Self . unescape )
90
+ self . pathComponents = Self . unescaped ( stringValue )
91
91
}
92
92
93
- /// Escapes a path component of a JSON pointer.
94
- static func escape( _ pointerPathComponents: String ) -> String {
95
- applyEscaping ( pointerPathComponents, shouldUnescape: false )
96
- }
97
-
98
- /// Unescaped a path component of a JSON pointer.
99
- static func unescape( _ pointerPathComponents: String ) -> String {
100
- applyEscaping ( pointerPathComponents, shouldUnescape: true )
93
+ private static func escaped( _ pathComponents: [ String ] ) -> String {
94
+ // This code is called quite frequently for mixed language content.
95
+ // Optimizing it has a measurable impact on the total documentation build time.
96
+
97
+ var string : [ UTF8 . CodeUnit ] = [ ]
98
+ string. reserveCapacity (
99
+ pathComponents. reduce ( 0 ) { acc, component in
100
+ acc + 1 /* the "/" separator */ + component. utf8. count
101
+ }
102
+ + 16 // some extra capacity since the escaped replacements grow the string beyond its original length.
103
+ )
104
+
105
+ for component in pathComponents {
106
+ // The leading slash and component separator
107
+ string. append ( forwardSlash)
108
+
109
+ // The escaped component
110
+ for char in component. utf8 {
111
+ switch char {
112
+ case tilde:
113
+ string. append ( contentsOf: escapedTilde)
114
+ case forwardSlash:
115
+ string. append ( contentsOf: escapedForwardSlash)
116
+ default :
117
+ string. append ( char)
118
+ }
119
+ }
120
+ }
121
+
122
+ return String ( decoding: string, as: UTF8 . self)
101
123
}
102
124
103
- /// Applies an escaping operation to the path component of a JSON pointer.
104
- /// - Parameters:
105
- /// - pointerPathComponent: The path component to escape.
106
- /// - shouldUnescape: Whether this function should unescape or escape the path component.
107
- /// - Returns: The escaped value if `shouldUnescape` is false, otherwise the escaped value.
108
- private static func applyEscaping( _ pointerPathComponent: String , shouldUnescape: Bool ) -> String {
109
- EscapedCharacters . allCases
110
- . reduce ( pointerPathComponent) { partialResult, characterThatNeedsEscaping in
111
- partialResult
112
- . replacingOccurrences (
113
- of: characterThatNeedsEscaping [
114
- keyPath: shouldUnescape ? \EscapedCharacters . escaped : \EscapedCharacters . rawValue
115
- ] ,
116
- with: characterThatNeedsEscaping [
117
- keyPath: shouldUnescape ? \EscapedCharacters . rawValue : \EscapedCharacters . escaped
118
- ]
119
- )
125
+ private static func unescaped( _ escapedRawString: String ) -> [ String ] {
126
+ escapedRawString. removingLeadingSlash. components ( separatedBy: " / " ) . map {
127
+ // This code is called quite frequently for mixed language content.
128
+ // Optimizing it has a measurable impact on the total documentation build time.
129
+
130
+ var string : [ UTF8 . CodeUnit ] = [ ]
131
+ string. reserveCapacity ( $0. utf8. count)
132
+
133
+ var remaining = $0. utf8 [ ... ]
134
+ while let char = remaining. popFirst ( ) {
135
+ guard char == tilde, let escapedCharacterIndicator = remaining. popFirst ( ) else {
136
+ string. append ( char)
137
+ continue
138
+ }
139
+
140
+ // Check the character
141
+ switch escapedCharacterIndicator {
142
+ case zero:
143
+ string. append ( tilde)
144
+ case one:
145
+ string. append ( forwardSlash)
146
+ default :
147
+ // This string isn't an escaped JSON Pointer. Return it as-is.
148
+ return $0
149
+ }
120
150
}
151
+
152
+ return String ( decoding: string, as: UTF8 . self)
153
+ }
121
154
}
122
155
}
156
+
157
+ // A few UInt8 raw values for various UTF-8 characters that this implementation frequently checks for
158
+
159
+ private let tilde = UTF8 . CodeUnit ( ascii: " ~ " )
160
+ private let forwardSlash = UTF8 . CodeUnit ( ascii: " / " )
161
+ private let zero = UTF8 . CodeUnit ( ascii: " 0 " )
162
+ private let one = UTF8 . CodeUnit ( ascii: " 1 " )
163
+
164
+ private let escapedTilde = [ tilde, zero]
165
+ private let escapedForwardSlash = [ tilde, one]
0 commit comments