Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: FavIcon/IconExtraction.swift

Issue 29664569: Favicon: Issue 6245 - SwiftLinted project files (Closed)
Patch Set: Created Jan. 12, 2018, 11:04 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « FavIcon/FavIcon.swift ('k') | FavIcon/URLRequestOperation.swift » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // 1 //
2 // FavIcon 2 // FavIcon
3 // Copyright © 2016 Leon Breedt 3 // Copyright © 2016 Leon Breedt
4 // 4 //
5 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License. 6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at 7 // You may obtain a copy of the License at
8 // 8 //
9 // http://www.apache.org/licenses/LICENSE-2.0 9 // http://www.apache.org/licenses/LICENSE-2.0
10 // 10 //
11 // Unless required by applicable law or agreed to in writing, software 11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS, 12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and 14 // See the License for the specific language governing permissions and
15 // limitations under the License. 15 // limitations under the License.
16 // 16 //
17 17
18 import FavIcon.XMLDocument
18 import Foundation 19 import Foundation
19 import FavIcon.XMLDocument
20 20
21 /// Represents an icon size. 21 /// Represents an icon size.
22 struct IconSize : Hashable, Equatable { 22 struct IconSize: Hashable, Equatable {
23 var hashValue: Int { 23 var hashValue: Int {
24 return width.hashValue ^ height.hashValue 24 return width.hashValue ^ height.hashValue
25 } 25 }
26 26
27 static func ==(lhs: IconSize, rhs: IconSize) -> Bool { 27 static func == (lhs: IconSize, rhs: IconSize) -> Bool {
28 return lhs.width == rhs.width && lhs.height == rhs.height 28 return lhs.width == rhs.width && lhs.height == rhs.height
29 } 29 }
30 30
31 /// The width of the icon. 31 /// The width of the icon.
32 let width: Int 32 let width: Int
33 /// The height of the icon. 33 /// The height of the icon.
34 let height: Int 34 let height: Int
35 } 35 }
36 36
37 private let kRelIconTypeMap: [IconSize: DetectedIconType] = [ 37 private let kRelIconTypeMap: [IconSize: DetectedIconType] = [
38 IconSize(width: 16, height: 16): .classic, 38 IconSize(width: 16, height: 16): .classic,
39 IconSize(width: 32, height: 32): .appleOSXSafariTab, 39 IconSize(width: 32, height: 32): .appleOSXSafariTab,
40 IconSize(width: 96, height: 96): .googleTV, 40 IconSize(width: 96, height: 96): .googleTV,
41 IconSize(width: 192, height: 192): .googleAndroidChrome, 41 IconSize(width: 192, height: 192): .googleAndroidChrome,
42 IconSize(width: 196, height: 196): .googleAndroidChrome 42 IconSize(width: 196, height: 196): .googleAndroidChrome
43 ] 43 ]
44 44
45 private let kMicrosoftSizeMap: [String: IconSize] = [ 45 private let kMicrosoftSizeMap: [String: IconSize] = [
46 "msapplication-tileimage": IconSize(width: 144, height: 144), 46 "msapplication-tileimage": IconSize(width: 144, height: 144),
47 "msapplication-square70x70logo": IconSize(width: 70, height: 70), 47 "msapplication-square70x70logo": IconSize(width: 70, height: 70),
48 "msapplication-square150x150logo": IconSize(width: 150, height: 150), 48 "msapplication-square150x150logo": IconSize(width: 150, height: 150),
49 "msapplication-wide310x150logo": IconSize(width: 310, height: 150), 49 "msapplication-wide310x150logo": IconSize(width: 310, height: 150),
50 "msapplication-square310x310logo": IconSize(width: 310, height: 310), 50 "msapplication-square310x310logo": IconSize(width: 310, height: 310)
51 ] 51 ]
52 52
53 private let siteImage: [String: IconSize] = [ 53 private let siteImage: [String: IconSize] = [
54 "og:image": IconSize(width: 1024, height: 512), 54 "og:image": IconSize(width: 1024, height: 512),
55 "twitter:image": IconSize(width: 1024, height: 512) 55 "twitter:image": IconSize(width: 1024, height: 512)
56 ] 56 ]
57 57
58 /// Extracts a list of icons from the `<head>` section of an HTML document. 58 /// Extracts a list of icons from the `<head>` section of an HTML document.
59 /// 59 ///
60 /// - parameter document: An HTML document to process. 60 /// - parameter document: An HTML document to process.
61 /// - parameter baseURL: A base URL to combine with any relative image paths. 61 /// - parameter baseURL: A base URL to combine with any relative image paths.
62 /// - parameter returns: An array of `DetectedIcon` structures. 62 /// - parameter returns: An array of `DetectedIcon` structures.
63 // swiftlint:disable function_body_length 63 // swiftlint:disable function_body_length
64 // swiftlint:disable cyclomatic_complexity 64 // swiftlint:disable cyclomatic_complexity
65 func examineHTMLMeta(_ document: HTMLDocument, baseURL: URL) -> [String:String] { 65 func examineHTMLMeta(_ document: HTMLDocument, baseURL: URL) -> [String: String] {
66 var resp: [String:String] = [:] 66 var resp: [String: String] = [:]
67 for meta in document.query("/html/head/meta") { 67 for meta in document.query("/html/head/meta") {
68 if let property = meta.attributes["property"]?.lowercased(), 68 if let property = meta.attributes["property"]?.lowercased(),
69 let content = meta.attributes["content"]{ 69 let content = meta.attributes["content"] {
70 switch property { 70 switch property {
71 case "og:url": 71 case "og:url":
72 resp["og:url"] = content; 72 resp["og:url"] = content
73 break 73 break
74 case "og:description": 74 case "og:description":
75 resp["description"] = content; 75 resp["description"] = content
76 break 76 break
77 case "og:image": 77 case "og:image":
78 resp["image"] = content; 78 resp["image"] = content
79 break 79 break
80 case "og:title": 80 case "og:title":
81 resp["title"] = content; 81 resp["title"] = content
82 break 82 break
83 case "og:site_name": 83 case "og:site_name":
84 resp["site_name"] = content; 84 resp["site_name"] = content
85 break 85 break
86 default: 86 default:
87 break 87 break
88 } 88 }
89 }
90 if let name = meta.attributes["name"]?.lowercased(),
91 let content = meta.attributes["content"],
92 name == "description" {
93 resp["description"] = resp["description"] ?? content
94 }
89 } 95 }
90 if let name = meta.attributes["name"]?.lowercased(), 96
91 let content = meta.attributes["content"], 97 for title in document.query("/html/head/title") {
92 name == "description" { 98 if let titleString = title.contents {
93 resp["description"] = resp["description"] ?? content; 99 resp["title"] = resp["title"] ?? titleString
100 }
94 } 101 }
95 } 102
96 103 for link in document.query("/html/head/link") {
97 for title in document.query("/html/head/title") { 104 if let rel = link.attributes["rel"],
98 if let titleString = title.contents { 105 let href = link.attributes["href"],
99 resp["title"] = resp["title"] ?? titleString; 106 let url = URL(string: href, relativeTo: baseURL) {
107 switch rel.lowercased() {
108 case "canonical":
109 resp["canonical"] = url.absoluteString
110 break
111 case "amphtml":
112 resp["amphtml"] = url.absoluteString
113 break
114 case "search":
115 resp["search"] = url.absoluteString
116 break
117 case "fluid-icon":
118 resp["fluid-icon"] = url.absoluteString
119 break
120 case "alternate":
121 let application = link.attributes["application"]
122 if application == "application/atom+xml" {
123 resp["atom"] = url.absoluteString
124 }
125 break
126 default:
127 break
128 }
129 }
100 } 130 }
101 } 131 return resp
102
103 for link in document.query("/html/head/link") {
104 if let rel = link.attributes["rel"],
105 let href = link.attributes["href"],
106 let url = URL(string: href, relativeTo: baseURL)
107 {
108 switch rel.lowercased() {
109 case "canonical":
110 resp["canonical"] = url.absoluteString;
111 break
112 case "amphtml":
113 resp["amphtml"] = url.absoluteString;
114 break
115 case "search":
116 resp["search"] = url.absoluteString;
117 break
118 case "fluid-icon":
119 resp["fluid-icon"] = url.absoluteString;
120 break
121 case "alternate":
122 let application = link.attributes["application"]
123 if application == "application/atom+xml" {
124 resp["atom"] = url.absoluteString;
125 }
126 break
127 default:
128 break
129 }
130 }
131 }
132
133 return resp;
134 } 132 }
135 133
136 func extractHTMLHeadIcons(_ document: HTMLDocument, baseURL: URL) -> [DetectedIc on] { 134 func extractHTMLHeadIcons(_ document: HTMLDocument, baseURL: URL) -> [DetectedIc on] {
137 var icons: [DetectedIcon] = [] 135 var icons: [DetectedIcon] = []
138 136
139 for link in document.query("/html/head/link") { 137 for link in document.query("/html/head/link") {
140 if let rel = link.attributes["rel"], 138 if let rel = link.attributes["rel"],
141 let href = link.attributes["href"], 139 let href = link.attributes["href"],
142 let url = URL(string: href, relativeTo: baseURL) { 140 let url = URL(string: href, relativeTo: baseURL) {
143 switch rel.lowercased() { 141 switch rel.lowercased() {
144 case "shortcut icon": 142 case "shortcut icon":
145 icons.append(DetectedIcon(url: url.absoluteURL, type:.shortcut)) 143 icons.append(DetectedIcon(url: url.absoluteURL, type: .shortcut) )
146 break 144 break
147 case "icon": 145 case "icon":
148 if let type = link.attributes["type"], type.lowercased() == "image/png" { 146 if let type = link.attributes["type"], type.lowercased() == "ima ge/png" {
149 let sizes = parseHTMLIconSizes(link.attributes["sizes"]) 147 let sizes = parseHTMLIconSizes(link.attributes["sizes"])
150 if sizes.count > 0 { 148 if sizes.count > 0 {
151 for size in sizes { 149 for size in sizes {
152 if let type = kRelIconTypeMap[size] { 150 if let type = kRelIconTypeMap[size] {
151 icons.append(DetectedIcon(url: url,
152 type: type,
153 width: size.width,
154 height: size.height))
155 }
156 }
157 } else {
158 icons.append(DetectedIcon(url: url.absoluteURL, type: .c lassic))
159 }
160 } else {
161 icons.append(DetectedIcon(url: url.absoluteURL, type: .class ic))
162 }
163 case "apple-touch-icon":
164 let sizes = parseHTMLIconSizes(link.attributes["sizes"])
165 if sizes.count > 0 {
166 for size in sizes {
167 icons.append(DetectedIcon(url: url.absoluteURL,
168 type: .appleIOSWebClip,
169 width: size.width,
170 height: size.height))
171 }
172 } else {
173 icons.append(DetectedIcon(url: url.absoluteURL,
174 type: .appleIOSWebClip,
175 width: 60,
176 height: 60))
177 }
178 default:
179 break
180 }
181 }
182 }
183
184 for meta in document.query("/html/head/meta") {
185 if let name = meta.attributes["name"]?.lowercased(),
186 let content = meta.attributes["content"],
187 let url = URL(string: content, relativeTo: baseURL),
188 let size = kMicrosoftSizeMap[name] {
189 icons.append(DetectedIcon(url: url,
190 type: .microsoftPinnedSite,
191 width: size.width,
192 height: size.height))
193 } else if
194 let property = meta.attributes["property"]?.lowercased(),
195 let content = meta.attributes["content"],
196 let url = URL(string: content, relativeTo: baseURL),
197 let size = siteImage[property] {
153 icons.append(DetectedIcon(url: url, 198 icons.append(DetectedIcon(url: url,
154 type: type, 199 type: .FBImage,
155 width: size.width, 200 width: size.width,
156 height: size.height)) 201 height: size.height))
157 }
158 }
159 } else {
160 icons.append(DetectedIcon(url: url.absoluteURL, type: .classic))
161 }
162 } else {
163 icons.append(DetectedIcon(url: url.absoluteURL, type: .classic))
164 } 202 }
165 case "apple-touch-icon":
166 let sizes = parseHTMLIconSizes(link.attributes["sizes"])
167 if sizes.count > 0 {
168 for size in sizes {
169 icons.append(DetectedIcon(url: url.absoluteURL,
170 type: .appleIOSWebClip,
171 width: size.width,
172 height: size.height))
173 }
174 } else {
175 icons.append(DetectedIcon(url: url.absoluteURL,
176 type: .appleIOSWebClip,
177 width: 60,
178 height: 60))
179 }
180 default:
181 break
182 }
183 } 203 }
184 } 204
185 205 return icons
186 for meta in document.query("/html/head/meta") {
187 if let name = meta.attributes["name"]?.lowercased(),
188 let content = meta.attributes["content"],
189 let url = URL(string: content, relativeTo: baseURL),
190 let size = kMicrosoftSizeMap[name] {
191 icons.append(DetectedIcon(url: url,
192 type: .microsoftPinnedSite,
193 width: size.width,
194 height: size.height))
195 } else if
196 let property = meta.attributes["property"]?.lowercased(),
197 let content = meta.attributes["content"],
198 let url = URL(string: content, relativeTo: baseURL),
199 let size = siteImage[property] {
200 icons.append(DetectedIcon(url: url,
201 type: .FBImage,
202 width: size.width,
203 height: size.height))
204 }
205 }
206
207 return icons
208 } 206 }
209 // swiftlint:enable cyclomatic_complexity 207 // swiftlint:enable cyclomatic_complexity
210 // swiftlint:enable function_body_length 208 // swiftlint:enable function_body_length
211 209
212 /// Extracts a list of icons from a Web Application Manifest file 210 /// Extracts a list of icons from a Web Application Manifest file
213 /// 211 ///
214 /// - parameter jsonString: A JSON string containing the contents of the manifes t file. 212 /// - parameter jsonString: A JSON string containing the contents of the manifes t file.
215 /// - parameter baseURL: A base URL to combine with any relative image paths. 213 /// - parameter baseURL: A base URL to combine with any relative image paths.
216 /// - returns: An array of `DetectedIcon` structures. 214 /// - returns: An array of `DetectedIcon` structures.
217 func extractManifestJSONIcons(_ jsonString: String, baseURL: URL) -> [DetectedIc on] { 215 func extractManifestJSONIcons(_ jsonString: String, baseURL: URL) -> [DetectedIc on] {
218 var icons: [DetectedIcon] = [] 216 var icons: [DetectedIcon] = []
219 217
220 if let data = jsonString.data(using: String.Encoding.utf8), 218 if let data = jsonString.data(using: String.Encoding.utf8),
221 let object = try? JSONSerialization.jsonObject(with: data, options: JSONSeri alization.ReadingOptions()), 219 let object = try? JSONSerialization.jsonObject(with: data, options: JSON Serialization.ReadingOptions()),
222 let manifest = object as? NSDictionary, 220 let manifest = object as? NSDictionary,
223 let manifestIcons = manifest["icons"] as? [NSDictionary] { 221 let manifestIcons = manifest["icons"] as? [NSDictionary] {
224 for icon in manifestIcons { 222 for icon in manifestIcons {
225 if let type = icon["type"] as? String, type.lowercased() == "image/png", 223 if let type = icon["type"] as? String, type.lowercased() == "image/p ng",
226 let src = icon["src"] as? String, 224 let src = icon["src"] as? String,
227 let url = URL(string: src, relativeTo: baseURL)?.absoluteURL { 225 let url = URL(string: src, relativeTo: baseURL)?.absoluteURL {
228 let sizes = parseHTMLIconSizes(icon["sizes"] as? String) 226 let sizes = parseHTMLIconSizes(icon["sizes"] as? String)
229 if sizes.count > 0 { 227 if sizes.count > 0 {
230 for size in sizes { 228 for size in sizes {
231 icons.append(DetectedIcon(url: url, 229 icons.append(DetectedIcon(url: url,
232 type: .webAppManifest, 230 type: .webAppManifest,
233 width: size.width, 231 width: size.width,
234 height: size.height)) 232 height: size.height))
235 } 233 }
236 } else { 234 } else {
237 icons.append(DetectedIcon(url: url, type: .webAppManifest)) 235 icons.append(DetectedIcon(url: url, type: .webAppManifest))
236 }
237 }
238 } 238 }
239 }
240 } 239 }
241 } 240
242 241 return icons
243 return icons
244 } 242 }
245 243
246 /// Extracts a list of icons from a Microsoft browser configuration XML document . 244 /// Extracts a list of icons from a Microsoft browser configuration XML document .
247 /// 245 ///
248 /// - parameter document: An `XMLDocument` for the Microsoft browser configurati on file. 246 /// - parameter document: An `XMLDocument` for the Microsoft browser configurati on file.
249 /// - parameter baseURL: A base URL to combine with any relative image paths. 247 /// - parameter baseURL: A base URL to combine with any relative image paths.
250 /// - returns: An array of `DetectedIcon` structures. 248 /// - returns: An array of `DetectedIcon` structures.
251 func extractBrowserConfigXMLIcons(_ document: LBXMLDocument, baseURL: URL) -> [D etectedIcon] { 249 func extractBrowserConfigXMLIcons(_ document: LBXMLDocument, baseURL: URL) -> [D etectedIcon] {
252 var icons: [DetectedIcon] = [] 250 var icons: [DetectedIcon] = []
253 251
254 for tile in document.query("/browserconfig/msapplication/tile/*") { 252 for tile in document.query("/browserconfig/msapplication/tile/*") {
255 if let src = tile.attributes["src"], 253 if let src = tile.attributes["src"],
256 let url = URL(string: src, relativeTo: baseURL)?.absoluteURL { 254 let url = URL(string: src, relativeTo: baseURL)?.absoluteURL {
257 switch tile.name.lowercased() { 255 switch tile.name.lowercased() {
258 case "tileimage": 256 case "tileimage":
259 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite, width: 1 44, height: 144)) 257 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite, width: 144, height: 144))
260 break 258 break
261 case "square70x70logo": 259 case "square70x70logo":
262 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite, width: 7 0, height: 70)) 260 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite, width: 70, height: 70))
263 break 261 break
264 case "square150x150logo": 262 case "square150x150logo":
265 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite, width: 1 50, height: 150)) 263 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite, width: 150, height: 150))
266 break 264 break
267 case "wide310x150logo": 265 case "wide310x150logo":
268 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite, width: 3 10, height: 150)) 266 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite, width: 310, height: 150))
269 break 267 break
270 case "square310x310logo": 268 case "square310x310logo":
271 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite, width: 3 10, height: 310)) 269 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite, width: 310, height: 310))
272 break 270 break
273 default: 271 default:
274 break 272 break
275 } 273 }
274 }
276 } 275 }
277 } 276
278 277 return icons
279 return icons
280 } 278 }
281 279
282 /// Extracts the Web App Manifest URLs from an HTML document, if any. 280 /// Extracts the Web App Manifest URLs from an HTML document, if any.
283 /// 281 ///
284 /// - parameter document: The HTML document to scan for Web App Manifest URLs 282 /// - parameter document: The HTML document to scan for Web App Manifest URLs
285 /// - parameter baseURL: The base URL that any 'href' attributes are relative to . 283 /// - parameter baseURL: The base URL that any 'href' attributes are relative to .
286 /// - returns: An array of Web App Manifest `URL`s. 284 /// - returns: An array of Web App Manifest `URL`s.
287 func extractWebAppManifestURLs(_ document: HTMLDocument, baseURL: URL) -> [URL] { 285 func extractWebAppManifestURLs(_ document: HTMLDocument, baseURL: URL) -> [URL] {
288 var urls: [URL] = [] 286 var urls: [URL] = []
289 for link in document.query("/html/head/link") { 287 for link in document.query("/html/head/link") {
290 if let rel = link.attributes["rel"]?.lowercased(), rel == "manifest", 288 if let rel = link.attributes["rel"]?.lowercased(), rel == "manifest",
291 let href = link.attributes["href"], let manifestURL = URL(string: href, re lativeTo: baseURL) { 289 let href = link.attributes["href"], let manifestURL = URL(string: hr ef, relativeTo: baseURL) {
292 urls.append(manifestURL) 290 urls.append(manifestURL)
291 }
293 } 292 }
294 } 293 return urls
295 return urls
296 } 294 }
297 295
298 /// Extracts the first browser config XML file URL from an HTML document, if any . 296 /// Extracts the first browser config XML file URL from an HTML document, if any .
299 /// 297 ///
300 /// - parameter document: The HTML document to scan for browser config XML file URLs. 298 /// - parameter document: The HTML document to scan for browser config XML file URLs.
301 /// - parameter baseURL: The base URL that any 'href' attributes are relative to . 299 /// - parameter baseURL: The base URL that any 'href' attributes are relative to .
302 /// - returns: A named tuple describing the file URL or a flag indicating that t he server 300 /// - returns: A named tuple describing the file URL or a flag indicating that t he server
303 /// explicitly requested that the file not be downloaded. 301 /// explicitly requested that the file not be downloaded.
304 func extractBrowserConfigURL(_ document: HTMLDocument, baseURL: URL) -> (url: UR L?, disabled: Bool) { 302 func extractBrowserConfigURL(_ document: HTMLDocument, baseURL: URL) -> (url: UR L?, disabled: Bool) {
305 for meta in document.query("/html/head/meta") { 303 for meta in document.query("/html/head/meta") {
306 if let name = meta.attributes["name"]?.lowercased(), name == "msapplication- config", 304 if let name = meta.attributes["name"]?.lowercased(), name == "msapplicat ion-config",
307 let content = meta.attributes["content"] { 305 let content = meta.attributes["content"] {
308 if content.lowercased() == "none" { 306 if content.lowercased() == "none" {
309 // Explicitly asked us not to download the file. 307 // Explicitly asked us not to download the file.
310 return (url: nil, disabled: true) 308 return (url: nil, disabled: true)
311 } else { 309 } else {
312 return (url: URL(string: content, relativeTo: baseURL)?.absoluteURL, dis abled: false) 310 return (url: URL(string: content, relativeTo: baseURL)?.absolute URL, disabled: false)
313 } 311 }
312 }
314 } 313 }
315 } 314 return (url: nil, disabled: false)
316 return (url: nil, disabled: false)
317 } 315 }
318 316
319 /// Helper function for parsing a W3 `sizes` attribute value. 317 /// Helper function for parsing a W3 `sizes` attribute value.
320 /// 318 ///
321 /// - parameter string: If not `nil`, the value of the attribute to parse (e.g. `50x50 144x144`). 319 /// - parameter string: If not `nil`, the value of the attribute to parse (e.g. `50x50 144x144`).
322 /// - returns: An array of `IconSize` structs for each size found. 320 /// - returns: An array of `IconSize` structs for each size found.
323 func parseHTMLIconSizes(_ string: String?) -> [IconSize] { 321 func parseHTMLIconSizes(_ string: String?) -> [IconSize] {
324 var sizes: [IconSize] = [] 322 var sizes: [IconSize] = []
325 if let string = string?.lowercased(), string != "any" { 323 if let string = string?.lowercased(), string != "any" {
326 for size in string.components(separatedBy: .whitespaces) { 324 for size in string.components(separatedBy: .whitespaces) {
327 let parts = size.components(separatedBy: "x") 325 let parts = size.components(separatedBy: "x")
328 if parts.count != 2 { continue } 326 if parts.count != 2 { continue }
329 if let width = Int(parts[0]), let height = Int(parts[1]) { 327 if let width = Int(parts[0]), let height = Int(parts[1]) {
330 sizes.append(IconSize(width: width, height: height)) 328 sizes.append(IconSize(width: width, height: height))
331 } 329 }
330 }
332 } 331 }
333 } 332 return sizes
334 return sizes
335 } 333 }
336
OLDNEW
« no previous file with comments | « FavIcon/FavIcon.swift ('k') | FavIcon/URLRequestOperation.swift » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld