Skip to content

Commit 3afd423

Browse files
authored
Merge pull request #5 from alexaubry/develop
Release 2.1.1
2 parents 33c63e3 + 3513479 commit 3afd423

File tree

26 files changed

+901
-367
lines changed

26 files changed

+901
-367
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.DS_Store
1+
**/.DS_Store
22
build/
33
DerivedData/
44
xcuserdata/

.jazzy.yaml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ author: "Alexis Aubry Radanovic"
33
author_url: https://github.com/alexaubry
44
github_url: https://github.com/alexaubry/HTMLString
55
github_file_prefix: https://github.com/alexaubry/HTMLString/tree/2.1.0
6-
module_version: 2.1.0
7-
xcodebuild_arguments: -scheme,HTMLString
6+
module_version: 2.1.1
7+
xcodebuild_arguments:
8+
- -scheme
9+
- HTMLString
10+
- -destination
11+
- 'platform=OS X,arch=x86_64'
812
module: HTMLString
913
root_url: https://alexaubry.github.io/HTMLString
1014
output: docs/
1115
swift_version: 3.0.2
1216
copyright: "Copyright © 2016-2017 Alexis Aubry Radanovic. Licensed under the [MIT License](https://github.com/alexaubry/HTMLString/blob/master/LICENSE)"
13-
podspec: ./HTMLString.podspec
14-
skip-undocumented: true
17+
skip_undocumented: true
1518
theme: apple

Benchmark.md

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,47 +4,47 @@ A set of measures tracking the performance of the library.
44

55
## ⏱ Compile times
66

7-
| Configuration | v2.0.1 | v2.1.0 |
8-
|:---|---|---|
9-
| Debug | 7.15s | 6.40s |
10-
| Release | 32.83s | 27.21ss |
7+
| Configuration | v2.0.1 | v2.1.0 | v2.1.1 |
8+
|:---|---|---|---|
9+
| Debug | 7.15s | 6.40s | 5,64s |
10+
| Release | 32.83s | 27.21s | 23,07s |
1111

1212
## 🤖 Common Tasks
1313

1414
### Debug
1515

16-
| Task | v2.0.1 | v2.1.0 |
17-
|:---|---|---|
18-
| Unicode-escaping 2 emojis | 0.000083s | 0.000040s |
19-
| ASCII-escaping 2 emojis | 0.000104s | 0.000035s |
20-
| Unescaping 2 emojis | 0.000046s | 0.000049s |
21-
| Unescaping a tweet | 0.000085s | 0.000033s |
22-
| Unicode-escaping a tweet | 0.000844s | 0.000131s |
23-
| ASCII-escaping a tweet | 0.001023s | 0.000158s |
24-
| Unicode-escaping 23,145 characters | 0.159909s | 0.024684s |
25-
| ASCII-escaping 23,145 characters | 0.198981s | 0.030419s |
26-
| Unescaping 3,026 words with 366 escapes | 0.165962s | 0.001686s |
16+
| Task | v2.0.1 | v2.1.0 | v2.1.1 |
17+
|:---|---|---|---|
18+
| Unicode-escaping 2 emojis | 0.000083s | 0.000040s | 0.000006s |
19+
| ASCII-escaping 2 emojis | 0.000104s | 0.000035s | 0.000008s |
20+
| Unescaping 2 emojis | 0.000046s | 0.000049s | 0.000021s |
21+
| Unescaping a tweet | 0.000085s | 0.000033s | 0.000029s |
22+
| Unicode-escaping a tweet | 0.000844s | 0.000131s | 0.000056s |
23+
| ASCII-escaping a tweet | 0.001023s | 0.000158s | 0.000058s |
24+
| Unicode-escaping 23,145 characters | 0.159909s | 0.024684s | 0.009895s |
25+
| ASCII-escaping 23,145 characters | 0.198981s | 0.030419s | 0.010272s |
26+
| Unescaping 3,026 words with 366 escapes | 0.165962s | 0.001686s | 0.001706s |
2727

2828
### Release
2929

30-
| Task | v2.0.1 | v2.1.0 |
31-
|:---|---|---|
32-
| Unicode-escaping 2 emojis | 0.000022s | 0.000010s |
33-
| ASCII-escaping 2 emojis | 0.000033s | 0.000015s |
34-
| Unescaping 2 emojis | 0.000031s | 0.000016s |
35-
| Unescaping a tweet | 0.000052s | 0.000017s |
36-
| Unicode-escaping a tweet | 0.000183s | 0.000064s |
37-
| ASCII-escaping a tweet | 0.000276s | 0.000077s |
38-
| Unicode-escaping 23,145 characters | 0.034701s | 0.011941s |
39-
| ASCII-escaping 23,145 characters | 0.052937s | 0.014950s |
40-
| Unescaping 3,026 words with 366 escapes | 0.014192s | 0.001162s |
41-
42-
## ❓ Complexity
43-
44-
TODO
45-
46-
| Algorithm | v2.0.1 | v2.1.0 |
47-
|:---|---|---|
48-
| ASCII escaping | N/A | N/A |
49-
| Unicode escaping | N/A | N/A |
50-
| Unescaping | N/A | N/A |
30+
| Task | v2.0.1 | v2.1.0 | v2.1.1 |
31+
|:---|---|---|---|
32+
| Unicode-escaping 2 emojis | 0.000022s | 0.000010s | 0.000004s |
33+
| ASCII-escaping 2 emojis | 0.000033s | 0.000015s | 0.000008s |
34+
| Unescaping 2 emojis | 0.000031s | 0.000016s | 0.000012s |
35+
| Unescaping a tweet | 0.000052s | 0.000017s | 0.000023s |
36+
| Unicode-escaping a tweet | 0.000183s | 0.000064s | 0.000047s |
37+
| ASCII-escaping a tweet | 0.000276s | 0.000077s | 0.000047s |
38+
| Unicode-escaping 23,145 characters | 0.034701s | 0.011941s | 0.006806s |
39+
| ASCII-escaping 23,145 characters | 0.052937s | 0.014950s | 0.007000s |
40+
| Unescaping 3,026 words with 366 escapes | 0.014192s | 0.001162s | 0.001204s |
41+
42+
## Estimated Asymptotic Complexity
43+
44+
Where `N` is the number of characters in the string.
45+
46+
| Algorithm | v2.0.1 | v2.1.0 | v2.1.1 |
47+
|:---|---|---|---|
48+
| ASCII escaping | N/A | N/A | O(N) |
49+
| Unicode escaping | N/A | N/A | O(N) |
50+
| Unescaping | N/A | N/A | O(N) |

CHANGELOG.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
11
# HTMLString Change Log
22

3+
## 2017-01-05 — Version 2.1.1
4+
5+
### API
6+
7+
- You can now perform custom escaping by escaping Unicode scalars individually.
8+
9+
### Improvements
10+
11+
- Further improved the escaping algorithm, which is now up to 6 times faster.
12+
- Changed the escaping strategy: special characters are now escaped with decimal sequences. This allows for better compatibility with browsers (HTML 4.0 compatible) and better speed
13+
- Changed the Unicode escaping strategy: only escape characters that could cause an XSS injection
14+
- Added an asymptotic complexity approximation calculator (every algorithm is now O(N))
15+
16+
### Fixed
17+
18+
- Removed .DS_Store
19+
320
## 2017-01-04 — Version 2.1.0
421

5-
- Change the escaping algorithm (`reduce` instead of `map`)
22+
- Changed the escaping algorithm (`reduce` instead of `map`)
623
- Reduced the size of the escaping mappings
724
- Performance improvements: escaping is up to **6.5 times** faster and unescaping is up to **98 times** faster
825
- Improved documentation

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.1.0"
4+
s.version = "2.1.1"
55
s.summary = "Escape and unescape HTML Strings in Swift"
66

77
s.description = <<-DESC

HTMLString.xcodeproj/xcshareddata/xcschemes/HTMLString.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
1818
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
1919
shouldUseLaunchSchemeArgsEnv = "YES"
20-
codeCoverageEnabled = "NO">
20+
codeCoverageEnabled = "YES">
2121
<Testables>
2222
<TestableReference
2323
skipped = "NO">

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ let targets = [
3939
// Target(name: "Performance", dependencies: [.Target(name: "HTMLString")])
4040
]
4141

42-
let excludes = [
42+
let excludes: [String] = [
4343
"Sources/Performance"
4444
]
4545

Sources/HTMLString/HTMLString.swift

Lines changed: 34 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,16 @@ public extension String {
4747
///
4848
/// | String | Result | Format |
4949
/// |--------|---------|---------------------------------------------------------|
50-
/// | `&` | `&amp;` | Keyword escape (part of the Unicode special characters) |
50+
/// | `&` | `&#38;` | Decimal escape (part of the Unicode special characters) |
5151
/// | `Σ` | `Σ` | Not escaped (Unicode compliant) |
5252
/// | `🇺🇸` | `🇺🇸` | Not escaped (Unicode compliant) |
5353
/// | `a` | `a` | Not escaped (alphanumerical) |
5454
///
55+
/// **Complexity**: `O(N)` where `N` is the number of characters in the string.
56+
///
5557

5658
public var escapingForUnicodeHTML: String {
57-
return escapeHTML(isEncodingUnicode: true)
59+
return unicodeScalars.reduce(String()) { $0 + $1.escapingIfNeeded }
5860
}
5961

6062
///
@@ -65,7 +67,7 @@ public extension String {
6567
///
6668
/// | String | Result | Format |
6769
/// |--------|----------------------|------------------------------------------------------|
68-
/// | `&` | `&amp;` | Keyword escape |
70+
/// | `&` | `&#38;` | Keyword escape |
6971
/// | `Σ` | `&#931;` | Decimal escape |
7072
/// | `🇺🇸` | `&#127482;&#127480;` | Combined decimal escapes (extented grapheme cluster) |
7173
/// | `a` | `a` | Not escaped (alphanumerical) |
@@ -76,41 +78,11 @@ public extension String {
7678
/// as it is faster, and produces less bloated and more readable HTML (as long as you are using
7779
/// a unicode compliant HTML reader).
7880
///
81+
/// **Complexity**: `O(N)` where `N` is the number of characters in the string.
82+
///
7983

8084
public var escapingForASCIIHTML: String {
81-
return escapeHTML(isEncodingUnicode: false)
82-
}
83-
84-
private func escapeHTML(isEncodingUnicode: Bool) -> String {
85-
86-
return self.characters.reduce(String()) {
87-
88-
let character = String($1)
89-
90-
// Ignore alphanumerical characters to avoid unnecessary lookups
91-
guard character < "\u{30}" || character > "\u{7a}" else {
92-
return $0 + character
93-
}
94-
95-
let escaped = isEncodingUnicode ? character.performUnicodeEscaping() : character.performASCIIEscaping()
96-
return $0 + escaped
97-
98-
}
99-
100-
}
101-
102-
private func performASCIIEscaping() -> String {
103-
104-
guard let escapeSequence = HTMLTables.escapingTable[self] else {
105-
return unicodeScalars.reduce(String()) { $0 + $1.escapingForASCII }
106-
}
107-
108-
return escapeSequence
109-
110-
}
111-
112-
private func performUnicodeEscaping() -> String {
113-
return unicodeScalars.reduce(String()) { $0 + $1.escapingIfNeeded }
85+
return unicodeScalars.reduce(String()) { $0 + $1.escapingForASCII }
11486
}
11587

11688
}
@@ -134,6 +106,8 @@ extension String {
134106
/// | `a` | `a` | Not an escape |
135107
/// | `&` | `&` | Not an escape |
136108
///
109+
/// **Complexity**: `O(N)` where `N` is the number of characters in the string.
110+
///
137111

138112
public var unescapingFromHTML: String {
139113

@@ -220,19 +194,37 @@ extension String {
220194

221195
extension UnicodeScalar {
222196

197+
///
198+
/// Returns the decimal HTML escape of the Unicode scalar.
199+
///
200+
/// This allows you to perform custom escaping.
201+
///
202+
203+
public var htmlEscaped: String {
204+
return "&#" + String(value) + ";"
205+
}
206+
207+
///
208+
/// The scalar escaped for ASCII encoding.
209+
///
210+
223211
fileprivate var escapingForASCII: String {
224-
return isASCII ? escapingIfNeeded : ("&#" + String(value) + ";")
212+
return isASCII ? escapingIfNeeded : htmlEscaped
225213
}
226214

215+
///
216+
/// Escapes the scalar only if it needs to be escaped for Unicode pages.
217+
///
218+
/// [Reference](http://wonko.com/post/html-escaping)
219+
///
220+
227221
fileprivate var escapingIfNeeded: String {
228222

229-
// Avoid unnecessary lookups
230-
guard value > 0x22 && value < 0x20ac else {
231-
return String(self)
223+
switch value {
224+
case 33, 34, 36, 37, 38, 39, 43, 44, 60, 61, 62, 64, 91, 93, 96, 123, 125: return htmlEscaped
225+
default: return String(self)
232226
}
233227

234-
return HTMLTables.requiredEscapingsTable[value] ?? String(self)
235-
236228
}
237229

238230
}

Sources/HTMLString/Mappings.swift

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,6 @@ import Foundation
4040

4141
internal struct HTMLTables {
4242

43-
///
44-
/// The table to use for escaping.
45-
///
46-
47-
internal static let escapingTable: [String:String] = {
48-
return [
49-
"\u{22}":"&quot;","\u{26}":"&amp;","\u{27}":"&apos;","\u{3c}":"&lt;","\u{3e}":"&gt;","\u{a0}":"&nbsp;","\u{a1}":"&iexcl;","\u{a2}":"&cent;","\u{a3}":"&pound;","\u{a4}":"&curren;","\u{a5}":"&yen;","\u{a6}":"&brvbar;","\u{a7}":"&sect;","\u{a8}":"&uml;","\u{a9}":"&copy;","\u{aa}":"&ordf;","\u{ab}":"&laquo;","\u{ac}":"&not;","\u{ad}":"&shy;","\u{ae}":"&reg;","\u{af}":"&macr;","\u{b0}":"&deg;","\u{b1}":"&plusmn;","\u{b2}":"&sup2;","\u{b3}":"&sup3;","\u{b4}":"&acute;","\u{b5}":"&micro;","\u{b6}":"&para;","\u{b7}":"&middot;","\u{b8}":"&cedil;","\u{b9}":"&sup1;","\u{ba}":"&ordm;","\u{bb}":"&raquo;","\u{bc}":"&frac14;","\u{bd}":"&frac12;","\u{be}":"&frac34;","\u{bf}":"&iquest;","\u{c0}":"&Agrave;","\u{c1}":"&Aacute;","\u{c2}":"&Acirc;","\u{c3}":"&Atilde;","\u{c4}":"&Auml;","\u{c5}":"&Aring;","\u{c6}":"&AElig;","\u{c7}":"&Ccedil;","\u{c8}":"&Egrave;","\u{c9}":"&Eacute;","\u{ca}":"&Ecirc;","\u{cb}":"&Euml;","\u{cc}":"&Igrave;","\u{cd}":"&Iacute;","\u{ce}":"&Icirc;","\u{cf}":"&Iuml;","\u{d0}":"&ETH;","\u{d1}":"&Ntilde;","\u{d2}":"&Ograve;","\u{d3}":"&Oacute;","\u{d4}":"&Ocirc;","\u{d5}":"&Otilde;","\u{d6}":"&Ouml;","\u{d7}":"&times;","\u{d8}":"&Oslash;","\u{d9}":"&Ugrave;","\u{da}":"&Uacute;","\u{db}":"&Ucirc;","\u{dc}":"&Uuml;","\u{dd}":"&Yacute;","\u{de}":"&THORN;","\u{df}":"&szlig;","\u{e0}":"&agrave;","\u{e1}":"&aacute;","\u{e2}":"&acirc;","\u{e3}":"&atilde;","\u{e4}":"&auml;","\u{e5}":"&aring;","\u{e6}":"&aelig;","\u{e7}":"&ccedil;","\u{e8}":"&egrave;","\u{e9}":"&eacute;","\u{ea}":"&ecirc;","\u{eb}":"&euml;","\u{ec}":"&igrave;","\u{ed}":"&iacute;","\u{ee}":"&icirc;","\u{ef}":"&iuml;","\u{f0}":"&eth;","\u{f1}":"&ntilde;","\u{f2}":"&ograve;","\u{f3}":"&oacute;","\u{f4}":"&ocirc;","\u{f5}":"&otilde;","\u{f6}":"&ouml;","\u{f7}":"&divide;","\u{f8}":"&oslash;","\u{f9}":"&ugrave;","\u{fa}":"&uacute;","\u{fb}":"&ucirc;","\u{fc}":"&uuml;","\u{fd}":"&yacute;","\u{fe}":"&thorn;","\u{ff}":"&yuml;","\u{152}":"&OElig;","\u{153}":"&oelig;","\u{160}":"&Scaron;","\u{161}":"&scaron;","\u{178}":"&Yuml;","\u{192}":"&fnof;","\u{2c6}":"&circ;","\u{2dc}":"&tilde;","\u{391}":"&Alpha;","\u{392}":"&Beta;","\u{393}":"&Gamma;","\u{394}":"&Delta;","\u{395}":"&Epsilon;","\u{396}":"&Zeta;","\u{397}":"&Eta;","\u{398}":"&Theta;","\u{399}":"&Iota;","\u{39a}":"&Kappa;","\u{39b}":"&Lambda;","\u{39c}":"&Mu;","\u{39d}":"&Nu;","\u{39e}":"&Xi;","\u{39f}":"&Omicron;","\u{3a0}":"&Pi;","\u{3a1}":"&Rho;","\u{3a3}":"&Sigma;","\u{3a4}":"&Tau;","\u{3a5}":"&Upsilon;","\u{3a6}":"&Phi;","\u{3a7}":"&Chi;","\u{3a8}":"&Psi;","\u{3a9}":"&Omega;","\u{3b1}":"&alpha;","\u{3b2}":"&beta;","\u{3b3}":"&gamma;","\u{3b4}":"&delta;","\u{3b5}":"&epsilon;","\u{3b6}":"&zeta;","\u{3b7}":"&eta;","\u{3b8}":"&theta;","\u{3b9}":"&iota;","\u{3ba}":"&kappa;","\u{3bb}":"&lambda;","\u{3bc}":"&mu;","\u{3bd}":"&nu;","\u{3be}":"&xi;","\u{3bf}":"&omicron;","\u{3c0}":"&pi;","\u{3c1}":"&rho;","\u{3c2}":"&sigmaf;","\u{3c3}":"&sigma;","\u{3c4}":"&tau;","\u{3c5}":"&upsilon;","\u{3c6}":"&phi;","\u{3c7}":"&chi;","\u{3c8}":"&psi;","\u{3c9}":"&omega;","\u{3d1}":"&thetasym;","\u{3d2}":"&upsih;","\u{3d6}":"&piv;","\u{2002}":"&ensp;","\u{2013}":"&ndash;","\u{2014}":"&mdash;","\u{2018}":"&lsquo;","\u{2019}":"&rsquo;","\u{201a}":"&sbquo;","\u{201c}":"&ldquo;","\u{201d}":"&rdquo;","\u{201e}":"&bdquo;","\u{2020}":"&dagger;","\u{2021}":"&Dagger;","\u{2022}":"&bull;","\u{2026}":"&hellip;","\u{2030}":"&permil;","\u{2032}":"&prime;","\u{2033}":"&Prime;","\u{2039}":"&lsaquo;","\u{203a}":"&rsaquo;","\u{203e}":"&oline;","\u{2044}":"&frasl;","\u{20ac}":"&euro;","\u{2111}":"&image;","\u{2118}":"&weierp;","\u{211c}":"&real;","\u{2122}":"&trade;","\u{2135}":"&alefsym;","\u{2190}":"&larr;","\u{2191}":"&uarr;","\u{2192}":"&rarr;","\u{2193}":"&darr;","\u{2194}":"&harr;","\u{21b5}":"&crarr;","\u{21d0}":"&lArr;","\u{21d1}":"&uArr;","\u{21d2}":"&rArr;","\u{21d3}":"&dArr;","\u{21d4}":"&hArr;","\u{2200}":"&forall;","\u{2202}":"&part;","\u{2203}":"&exist;","\u{2205}":"&empty;","\u{2207}":"&nabla;","\u{2208}":"&isin;","\u{2209}":"&notin;","\u{220b}":"&ni;","\u{220f}":"&prod;","\u{2211}":"&sum;","\u{2212}":"&minus;","\u{2217}":"&lowast;","\u{221a}":"&radic;","\u{221d}":"&prop;","\u{221e}":"&infin;","\u{2220}":"&ang;","\u{2227}":"&and;","\u{2228}":"&or;","\u{2229}":"&cap;","\u{222a}":"&cup;","\u{222b}":"&int;","\u{2234}":"&there4;","\u{223c}":"&sim;","\u{2245}":"&cong;","\u{2248}":"&asymp;","\u{2260}":"&ne;","\u{2261}":"&equiv;","\u{2264}":"&le;","\u{2265}":"&ge;","\u{2282}":"&sub;","\u{2283}":"&sup;","\u{2284}":"&nsub;","\u{2286}":"&sube;","\u{2287}":"&supe;","\u{2295}":"&oplus;","\u{2297}":"&otimes;","\u{22a5}":"&perp;","\u{22c5}":"&sdot;","\u{2308}":"&lceil;","\u{2309}":"&rceil;","\u{230a}":"&lfloor;","\u{230b}":"&rfloor;","\u{2329}":"&lang;","\u{232a}":"&rang;","\u{25ca}":"&loz;","\u{2660}":"&spades;","\u{2663}":"&clubs;","\u{2665}":"&hearts;","\u{2666}":"&diams;"
50-
]
51-
}()
52-
5343
///
5444
/// The table to use for unescaping.
5545
///
@@ -60,16 +50,5 @@ internal struct HTMLTables {
6050
]
6151
}()
6252

63-
///
64-
/// The table for code points of characters that need to be escaped regardless of the encoding.
65-
///
66-
/// [Reference](http://wonko.com/post/html-escaping)
67-
///
68-
69-
internal static let requiredEscapingsTable: [UInt32:String] = {
70-
return [
71-
0x22:"&quot;",0x26:"&amp;",0x27:"&apos;",0x3c:"&lt;",0x3e:"&gt;",0x152:"&OElig;",0x153:"&oelig;",0x160:"&Scaron;",0x161:"&scaron;",0x178:"&Yuml;",0x2c6:"&circ;",0x2dc:"&tilde;",0x2002:"&ensp;",0x2003:"&emsp;",0x2009:"&thinsp;",0x200c:"&zwnj;",0x200d:"&zwj;",0x200e:"&lrm;",0x200f:"&rlm;",0x2013:"&ndash;",0x2014:"&mdash;",0x2018:"&lsquo;",0x2019:"&rsquo;",0x201a:"&sbquo;",0x201c:"&ldquo;",0x201d:"&rdquo;",0x201e:"&bdquo;",0x2020:"&dagger;",0x2021:"&Dagger;",0x2030:"&permil;",0x2039:"&lsaquo;",0x203a:"&rsaquo;",0x20ac:"euro"
72-
]
73-
}()
74-
7553
}
54+

0 commit comments

Comments
 (0)