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