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