@@ -77,16 +77,9 @@ public extension String {
77
77
///
78
78
79
79
fileprivate func escapeHTML( isEncodingUnicode: Bool ) -> String {
80
-
81
- var finalString = String ( )
82
-
83
- for character in characters {
84
- let escapingSequence = isEncodingUnicode ? character. escapingForUnicode : character. escapingForASCII
85
- finalString. append ( escapingSequence)
86
- }
87
-
88
- return finalString
89
-
80
+ return self . characters. map {
81
+ isEncodingUnicode ? $0. escapingForUnicode : $0. escapingForASCII
82
+ } . joined ( )
90
83
}
91
84
92
85
// MARK: - Unescaping
@@ -103,95 +96,101 @@ public extension String {
103
96
return self
104
97
}
105
98
106
- var finalString = self
107
- var searchRange = finalString . startIndex ..< finalString . endIndex
99
+ var unescapedString = self
100
+ var searchRange = unescapedString . startIndex ..< unescapedString . endIndex
108
101
109
- while let delimiterRange = finalString . range ( of: " & " , options : [ ] , range: searchRange, locale : nil ) {
102
+ while let delimiterRange = unescapedString . range ( of: " & " , range: searchRange) {
110
103
111
- let semicolonSearchRange = delimiterRange. upperBound ..< finalString . endIndex
104
+ let semicolonSearchRange = delimiterRange. upperBound ..< unescapedString . endIndex
112
105
113
- guard let semicolonRange = finalString . range ( of: " ; " , options : [ ] , range: semicolonSearchRange, locale : nil ) else {
114
- searchRange = delimiterRange. upperBound ..< finalString . endIndex
106
+ guard let semicolonRange = unescapedString . range ( of: " ; " , range: semicolonSearchRange) else {
107
+ searchRange = delimiterRange. upperBound ..< unescapedString . endIndex
115
108
continue
116
109
}
117
110
118
111
let escapeSequenceBounds = delimiterRange. lowerBound ..< semicolonRange. upperBound
119
- let escapeRange = delimiterRange. upperBound ..< semicolonRange. lowerBound
120
112
121
- let escapeString = finalString. substring ( with: escapeRange)
113
+ let escapableContentRange = delimiterRange. upperBound ..< semicolonRange. lowerBound
114
+ let escapableContent = unescapedString. substring ( with: escapableContentRange)
122
115
123
116
let replacementString : String
124
117
125
- if escapeString [ escapeString. startIndex] == " # " {
126
-
127
- let secondCharacter = escapeString [ escapeString. index ( after: escapeString. startIndex) ]
118
+ if escapableContent [ escapableContent. startIndex] == " # " {
128
119
129
- let isHexadecimal = ( secondCharacter == " X " || secondCharacter == " x " )
130
- let firstCharacterOffset = isHexadecimal ? 2 : 1
120
+ guard let unescapedNumericalSequence = unescaped ( numericalSequence: escapableContent) else {
121
+ searchRange = escapeSequenceBounds. upperBound ..< unescapedString. endIndex
122
+ continue
123
+ }
131
124
132
- let sequenceRange = escapeString . index ( escapeString . startIndex , offsetBy : firstCharacterOffset ) ..< escapeString . endIndex
125
+ replacementString = unescapedNumericalSequence
133
126
134
- let sequence = escapeString . substring ( with : sequenceRange )
127
+ } else {
135
128
136
- var value = UInt32 ( )
129
+ guard let unescapedCharacter = HTMLTables . unescapingTable [ escapableContent] else {
130
+ searchRange = escapeSequenceBounds. upperBound ..< unescapedString. endIndex
131
+ continue
132
+ }
137
133
138
- if isHexadecimal {
134
+ replacementString = unescapedCharacter
139
135
140
- let scanner = Scanner ( string : sequence )
136
+ }
141
137
142
- #if os(OSX) || os(iOS) || os(watchOS) || os(tvOS)
138
+ unescapedString. replaceSubrange ( escapeSequenceBounds, with: replacementString)
139
+ searchRange = delimiterRange. upperBound ..< unescapedString. endIndex
143
140
144
- guard scanner. scanHexInt32 ( & value) && value > 0 else {
145
- searchRange = escapeSequenceBounds. upperBound ..< finalString. endIndex
146
- continue
147
- }
141
+ }
148
142
149
- #else
143
+ return unescapedString
150
144
151
- guard let _value = scanner. scanHexInt ( ) else {
152
- searchRange = escapeSequenceBounds. upperBound ..< finalString. endIndex
153
- continue
154
- }
145
+ }
155
146
156
- value = _value
147
+ ///
148
+ /// Unescapes a numerical escape sequence.
149
+ ///
150
+ /// Numerical sequences can be either decimal (`-`) or hexadecimal (`Á`).
151
+ ///
152
+ /// - parameter numericalSequence: The sequence to escape. It must not contain the `&` prefix of the `;` suffix.
153
+ ///
154
+ /// - returns: The unescaped version of the sequence, or `nil` if unescaping failed.
155
+ ///
157
156
158
- #endif
157
+ fileprivate func unescaped ( numericalSequence : String ) -> String ? {
159
158
160
- } else {
159
+ let secondCharacter = numericalSequence [ numericalSequence . index ( after : numericalSequence . startIndex ) ]
161
160
162
- guard let _value = UInt32 ( sequence) else {
163
- searchRange = escapeSequenceBounds. upperBound ..< finalString. endIndex
164
- continue
165
- }
161
+ let isHexadecimal = ( secondCharacter == " X " || secondCharacter == " x " )
162
+ let numberStartIndexOffset = isHexadecimal ? 2 : 1
166
163
167
- value = _value
164
+ let numberStringRange = numericalSequence. index ( numericalSequence. startIndex, offsetBy: numberStartIndexOffset) ..< numericalSequence. endIndex
165
+ let numberString = numericalSequence. substring ( with: numberStringRange)
168
166
169
- }
167
+ var codePoint = UInt32 ( )
170
168
171
- guard let scalar = UnicodeScalar ( value) else {
172
- searchRange = escapeSequenceBounds. upperBound ..< finalString. endIndex
173
- continue
174
- }
169
+ if isHexadecimal {
175
170
176
- replacementString = String ( Character ( scalar ) )
171
+ let scanner = Scanner ( string : numberString )
177
172
178
- } else {
173
+ guard let _codePoint = scanner. scanHexInt ( ) else {
174
+ return nil
175
+ }
179
176
180
- guard let escapeSequence = HTMLEscaping . escapeSequenceTable [ escapeString] else {
181
- searchRange = escapeSequenceBounds. upperBound ..< finalString. endIndex
182
- continue
183
- }
177
+ codePoint = _codePoint
184
178
185
- replacementString = escapeSequence
179
+ } else {
186
180
181
+ guard let _codePoint = UInt32 ( numberString) else {
182
+ return nil
187
183
}
188
184
189
- finalString. replaceSubrange ( escapeSequenceBounds, with: replacementString)
190
- searchRange = delimiterRange. upperBound ..< finalString. endIndex
185
+ codePoint = _codePoint
186
+
187
+ }
191
188
189
+ guard let scalar = UnicodeScalar ( codePoint) else {
190
+ return nil
192
191
}
193
192
194
- return finalString
193
+ return String ( Character ( scalar ) )
195
194
196
195
}
197
196
0 commit comments