11/*
22 This source file is part of the Swift.org open source project
33
4- Copyright (c) 2021 Apple Inc. and the Swift project authors
4+ Copyright (c) 2021-2024 Apple Inc. and the Swift project authors
55 Licensed under Apache License v2.0 with Runtime Library Exception
66
77 See https://swift.org/LICENSE.txt for license information
@@ -20,7 +20,7 @@ public struct JSONPointer: Codable, CustomStringConvertible, Equatable {
2020 public var pathComponents : [ String ]
2121
2222 public var description : String {
23- " / \( pathComponents . map ( Self . escape ) . joined ( separator : " / " ) ) "
23+ Self . escaped ( pathComponents )
2424 }
2525
2626 /// Creates a JSON Pointer given its path components.
@@ -87,36 +87,79 @@ public struct JSONPointer: Codable, CustomStringConvertible, Equatable {
8787 public init ( from decoder: Decoder ) throws {
8888 let container = try decoder. singleValueContainer ( )
8989 let stringValue = try container. decode ( String . self)
90- self . pathComponents = stringValue . removingLeadingSlash . components ( separatedBy : " / " ) . map ( Self . unescape )
90+ self . pathComponents = Self . unescaped ( stringValue )
9191 }
9292
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)
101123 }
102124
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+ }
120150 }
151+
152+ return String ( decoding: string, as: UTF8 . self)
153+ }
121154 }
122155}
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