@@ -77,16 +77,9 @@ public extension String {
7777 ///
7878
7979 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 ( )
9083 }
9184
9285 // MARK: - Unescaping
@@ -103,95 +96,101 @@ public extension String {
10396 return self
10497 }
10598
106- var finalString = self
107- var searchRange = finalString . startIndex ..< finalString . endIndex
99+ var unescapedString = self
100+ var searchRange = unescapedString . startIndex ..< unescapedString . endIndex
108101
109- while let delimiterRange = finalString . range ( of: " & " , options : [ ] , range: searchRange, locale : nil ) {
102+ while let delimiterRange = unescapedString . range ( of: " & " , range: searchRange) {
110103
111- let semicolonSearchRange = delimiterRange. upperBound ..< finalString . endIndex
104+ let semicolonSearchRange = delimiterRange. upperBound ..< unescapedString . endIndex
112105
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
115108 continue
116109 }
117110
118111 let escapeSequenceBounds = delimiterRange. lowerBound ..< semicolonRange. upperBound
119- let escapeRange = delimiterRange. upperBound ..< semicolonRange. lowerBound
120112
121- let escapeString = finalString. substring ( with: escapeRange)
113+ let escapableContentRange = delimiterRange. upperBound ..< semicolonRange. lowerBound
114+ let escapableContent = unescapedString. substring ( with: escapableContentRange)
122115
123116 let replacementString : String
124117
125- if escapeString [ escapeString. startIndex] == " # " {
126-
127- let secondCharacter = escapeString [ escapeString. index ( after: escapeString. startIndex) ]
118+ if escapableContent [ escapableContent. startIndex] == " # " {
128119
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+ }
131124
132- let sequenceRange = escapeString . index ( escapeString . startIndex , offsetBy : firstCharacterOffset ) ..< escapeString . endIndex
125+ replacementString = unescapedNumericalSequence
133126
134- let sequence = escapeString . substring ( with : sequenceRange )
127+ } else {
135128
136- var value = UInt32 ( )
129+ guard let unescapedCharacter = HTMLTables . unescapingTable [ escapableContent] else {
130+ searchRange = escapeSequenceBounds. upperBound ..< unescapedString. endIndex
131+ continue
132+ }
137133
138- if isHexadecimal {
134+ replacementString = unescapedCharacter
139135
140- let scanner = Scanner ( string : sequence )
136+ }
141137
142- #if os(OSX) || os(iOS) || os(watchOS) || os(tvOS)
138+ unescapedString. replaceSubrange ( escapeSequenceBounds, with: replacementString)
139+ searchRange = delimiterRange. upperBound ..< unescapedString. endIndex
143140
144- guard scanner. scanHexInt32 ( & value) && value > 0 else {
145- searchRange = escapeSequenceBounds. upperBound ..< finalString. endIndex
146- continue
147- }
141+ }
148142
149- #else
143+ return unescapedString
150144
151- guard let _value = scanner. scanHexInt ( ) else {
152- searchRange = escapeSequenceBounds. upperBound ..< finalString. endIndex
153- continue
154- }
145+ }
155146
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+ ///
157156
158- #endif
157+ fileprivate func unescaped ( numericalSequence : String ) -> String ? {
159158
160- } else {
159+ let secondCharacter = numericalSequence [ numericalSequence . index ( after : numericalSequence . startIndex ) ]
161160
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
166163
167- value = _value
164+ let numberStringRange = numericalSequence. index ( numericalSequence. startIndex, offsetBy: numberStartIndexOffset) ..< numericalSequence. endIndex
165+ let numberString = numericalSequence. substring ( with: numberStringRange)
168166
169- }
167+ var codePoint = UInt32 ( )
170168
171- guard let scalar = UnicodeScalar ( value) else {
172- searchRange = escapeSequenceBounds. upperBound ..< finalString. endIndex
173- continue
174- }
169+ if isHexadecimal {
175170
176- replacementString = String ( Character ( scalar ) )
171+ let scanner = Scanner ( string : numberString )
177172
178- } else {
173+ guard let _codePoint = scanner. scanHexInt ( ) else {
174+ return nil
175+ }
179176
180- guard let escapeSequence = HTMLEscaping . escapeSequenceTable [ escapeString] else {
181- searchRange = escapeSequenceBounds. upperBound ..< finalString. endIndex
182- continue
183- }
177+ codePoint = _codePoint
184178
185- replacementString = escapeSequence
179+ } else {
186180
181+ guard let _codePoint = UInt32 ( numberString) else {
182+ return nil
187183 }
188184
189- finalString. replaceSubrange ( escapeSequenceBounds, with: replacementString)
190- searchRange = delimiterRange. upperBound ..< finalString. endIndex
185+ codePoint = _codePoint
186+
187+ }
191188
189+ guard let scalar = UnicodeScalar ( codePoint) else {
190+ return nil
192191 }
193192
194- return finalString
193+ return String ( Character ( scalar ) )
195194
196195 }
197196
0 commit comments