Skip to content

Commit 146b239

Browse files
authored
Release 2.0.1 (#3)
- Performance improvements : escaping is 99.37% faster and unescaping is 10,38% faster (in average) - Changed the escaping/unescaping tables model - Better tests and coverage
1 parent 98c98e4 commit 146b239

23 files changed

+343
-206
lines changed

.codecov.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ignore:
2+
- "Tests"

.gitignore

Lines changed: 2 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,7 @@
1-
# Xcode
2-
#
3-
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4-
5-
## Build generated
61
build/
72
DerivedData/
8-
9-
## Various settings
10-
*.pbxuser
11-
!default.pbxuser
12-
*.mode1v3
13-
!default.mode1v3
14-
*.mode2v3
15-
!default.mode2v3
16-
*.perspectivev3
17-
!default.perspectivev3
183
xcuserdata/
19-
20-
## Other
21-
*.moved-aside
22-
*.xcuserstate
23-
24-
## Obj-C/Swift specific
25-
*.hmap
26-
*.ipa
27-
*.dSYM.zip
28-
*.dSYM
29-
30-
## Playgrounds
31-
timeline.xctimeline
32-
playground.xcworkspace
33-
34-
# Swift Package Manager
35-
#
36-
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
37-
# Packages/
4+
Packages/
385
.build/
39-
40-
# CocoaPods
41-
#
42-
# We recommend against adding the Pods directory to your .gitignore. However
43-
# you should judge for yourself, the pros and cons are mentioned at:
44-
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
45-
#
46-
# Pods/
47-
48-
# Carthage
49-
#
50-
# Add this line if you want to avoid checking in source code from Carthage dependencies.
51-
# Carthage/Checkouts
52-
536
Carthage/Build
54-
55-
# fastlane
56-
#
57-
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
58-
# screenshots whenever they are needed.
59-
# For more information about the recommended setup visit:
60-
# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
61-
62-
fastlane/report.xml
63-
fastlane/Preview.html
64-
fastlane/screenshots
65-
fastlane/test_output
7+
docs/undocumented.json

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# HTMLString Change Log
22

3+
## 2016-12-06 — Version 2.0.1
4+
5+
- Performance improvements : escaping is **99.37%** faster and unescaping is **10,38%** faster (in average)
6+
- Changed the escaping/unescaping tables model
7+
- Better tests and coverage
8+
39
## 2016-12-03 — Version 2.0.0
410

511
- Transitioned to a `Character`-based API: this adds full support for extended grapheme clusters.

Gemfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
source "https://rubygems.org"
2+
gem 'json', '~> 1.7'
3+
gem 'jazzy', '~> 0.7.3'

Gemfile.lock

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
GEM
2+
remote: https://rubygems.org/
3+
specs:
4+
CFPropertyList (2.3.4)
5+
activesupport (4.2.7.1)
6+
i18n (~> 0.7)
7+
json (~> 1.7, >= 1.7.7)
8+
minitest (~> 5.1)
9+
thread_safe (~> 0.3, >= 0.3.4)
10+
tzinfo (~> 1.1)
11+
claide (1.0.1)
12+
cocoapods (1.1.1)
13+
activesupport (>= 4.0.2, < 5)
14+
claide (>= 1.0.1, < 2.0)
15+
cocoapods-core (= 1.1.1)
16+
cocoapods-deintegrate (>= 1.0.1, < 2.0)
17+
cocoapods-downloader (>= 1.1.2, < 2.0)
18+
cocoapods-plugins (>= 1.0.0, < 2.0)
19+
cocoapods-search (>= 1.0.0, < 2.0)
20+
cocoapods-stats (>= 1.0.0, < 2.0)
21+
cocoapods-trunk (>= 1.1.1, < 2.0)
22+
cocoapods-try (>= 1.1.0, < 2.0)
23+
colored (~> 1.2)
24+
escape (~> 0.0.4)
25+
fourflusher (~> 2.0.1)
26+
gh_inspector (~> 1.0)
27+
molinillo (~> 0.5.1)
28+
nap (~> 1.0)
29+
xcodeproj (>= 1.3.3, < 2.0)
30+
cocoapods-core (1.1.1)
31+
activesupport (>= 4.0.2, < 5)
32+
fuzzy_match (~> 2.0.4)
33+
nap (~> 1.0)
34+
cocoapods-deintegrate (1.0.1)
35+
cocoapods-downloader (1.1.2)
36+
cocoapods-plugins (1.0.0)
37+
nap
38+
cocoapods-search (1.0.0)
39+
cocoapods-stats (1.0.0)
40+
cocoapods-trunk (1.1.1)
41+
nap (>= 0.8, < 2.0)
42+
netrc (= 0.7.8)
43+
cocoapods-try (1.1.0)
44+
colored (1.2)
45+
escape (0.0.4)
46+
fourflusher (2.0.1)
47+
fuzzy_match (2.0.4)
48+
gh_inspector (1.0.2)
49+
i18n (0.7.0)
50+
jazzy (0.7.3)
51+
cocoapods (~> 1.0)
52+
mustache (~> 0.99)
53+
open4
54+
redcarpet (~> 3.2)
55+
rouge (~> 1.5)
56+
sass (~> 3.4)
57+
sqlite3 (~> 1.3)
58+
xcinvoke (~> 0.3.0)
59+
json (1.8.3)
60+
liferaft (0.0.6)
61+
minitest (5.10.1)
62+
molinillo (0.5.4)
63+
mustache (0.99.8)
64+
nanaimo (0.2.3)
65+
nap (1.1.0)
66+
netrc (0.7.8)
67+
open4 (1.3.4)
68+
redcarpet (3.3.4)
69+
rouge (1.11.1)
70+
sass (3.4.22)
71+
sqlite3 (1.3.12)
72+
thread_safe (0.3.5)
73+
tzinfo (1.2.2)
74+
thread_safe (~> 0.1)
75+
xcinvoke (0.3.0)
76+
liferaft (~> 0.0.6)
77+
xcodeproj (1.4.1)
78+
CFPropertyList (~> 2.3.3)
79+
activesupport (>= 3)
80+
claide (>= 1.0.1, < 2.0)
81+
colored (~> 1.2)
82+
nanaimo (~> 0.2.0)
83+
84+
PLATFORMS
85+
ruby
86+
87+
DEPENDENCIES
88+
jazzy (~> 0.7.3)
89+
json (~> 1.7)
90+
91+
BUNDLED WITH
92+
1.13.6

HTMLString.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Pod::Spec.new do |s|
22

33
s.name = "HTMLString"
4-
s.version = "2.0.0"
4+
s.version = "2.0.1"
55
s.summary = "Convert Strings that contains HTML in Swift"
66

77
s.description = <<-DESC

Sources/HTMLString.swift

Lines changed: 60 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -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 (`&#45;`) or hexadecimal (`&#xc1`).
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

Sources/Mappings.swift

Lines changed: 18 additions & 6 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)