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