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