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 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 | |
OLD | NEW |