| LEFT | RIGHT |
| 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 FavIcon.XMLDocument |
| 19 import Foundation | |
| 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 } |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 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 | |
| 74 case "og:description": | 72 case "og:description": |
| 75 resp["description"] = content | 73 resp["description"] = content |
| 76 break | |
| 77 case "og:image": | 74 case "og:image": |
| 78 resp["image"] = content | 75 resp["image"] = content |
| 79 break | |
| 80 case "og:title": | 76 case "og:title": |
| 81 resp["title"] = content | 77 resp["title"] = content |
| 82 break | |
| 83 case "og:site_name": | 78 case "og:site_name": |
| 84 resp["site_name"] = content | 79 resp["site_name"] = content |
| 85 break | |
| 86 default: | 80 default: |
| 87 break | 81 break |
| 88 } | 82 } |
| 89 } | 83 } |
| 90 if let name = meta.attributes["name"]?.lowercased(), | 84 if let name = meta.attributes["name"]?.lowercased(), |
| 91 let content = meta.attributes["content"], | 85 let content = meta.attributes["content"], |
| 92 name == "description" { | 86 name == "description" { |
| 93 resp["description"] = resp["description"] ?? content | 87 resp["description"] = resp["description"] ?? content |
| 94 } | 88 } |
| 95 } | 89 } |
| 96 | 90 |
| 97 for title in document.query("/html/head/title") { | 91 for title in document.query("/html/head/title") { |
| 98 if let titleString = title.contents { | 92 if let titleString = title.contents { |
| 99 resp["title"] = resp["title"] ?? titleString | 93 resp["title"] = resp["title"] ?? titleString |
| 100 } | 94 } |
| 101 } | 95 } |
| 102 | 96 |
| 103 for link in document.query("/html/head/link") { | 97 for link in document.query("/html/head/link") { |
| 104 if let rel = link.attributes["rel"], | 98 if let rel = link.attributes["rel"], |
| 105 let href = link.attributes["href"], | 99 let href = link.attributes["href"], |
| 106 let url = URL(string: href, relativeTo: baseURL) { | 100 let url = URL(string: href, relativeTo: baseURL) { |
| 107 switch rel.lowercased() { | 101 switch rel.lowercased() { |
| 108 case "canonical": | 102 case "canonical": |
| 109 resp["canonical"] = url.absoluteString | 103 resp["canonical"] = url.absoluteString |
| 110 break | |
| 111 case "amphtml": | 104 case "amphtml": |
| 112 resp["amphtml"] = url.absoluteString | 105 resp["amphtml"] = url.absoluteString |
| 113 break | |
| 114 case "search": | 106 case "search": |
| 115 resp["search"] = url.absoluteString | 107 resp["search"] = url.absoluteString |
| 116 break | |
| 117 case "fluid-icon": | 108 case "fluid-icon": |
| 118 resp["fluid-icon"] = url.absoluteString | 109 resp["fluid-icon"] = url.absoluteString |
| 119 break | |
| 120 case "alternate": | 110 case "alternate": |
| 121 let application = link.attributes["application"] | 111 let application = link.attributes["application"] |
| 122 if application == "application/atom+xml" { | 112 if application == "application/atom+xml" { |
| 123 resp["atom"] = url.absoluteString | 113 resp["atom"] = url.absoluteString |
| 124 } | 114 } |
| 125 break | |
| 126 default: | 115 default: |
| 127 break | 116 break |
| 128 } | 117 } |
| 129 } | 118 } |
| 130 } | 119 } |
| 131 return resp | 120 return resp |
| 132 } | 121 } |
| 133 | 122 |
| 134 func extractHTMLHeadIcons(_ document: HTMLDocument, baseURL: URL) -> [DetectedIc
on] { | 123 func extractHTMLHeadIcons(_ document: HTMLDocument, baseURL: URL) -> [DetectedIc
on] { |
| 135 var icons: [DetectedIcon] = [] | 124 var icons: [DetectedIcon] = [] |
| 136 | 125 |
| 137 for link in document.query("/html/head/link") { | 126 for link in document.query("/html/head/link") { |
| 138 if let rel = link.attributes["rel"], | 127 if let rel = link.attributes["rel"], |
| 139 let href = link.attributes["href"], | 128 let href = link.attributes["href"], |
| 140 let url = URL(string: href, relativeTo: baseURL) { | 129 let url = URL(string: href, relativeTo: baseURL) { |
| 141 switch rel.lowercased() { | 130 switch rel.lowercased() { |
| 142 case "shortcut icon": | 131 case "shortcut icon": |
| 143 icons.append(DetectedIcon(url: url.absoluteURL, type: .shortcut)
) | 132 icons.append(DetectedIcon(url: url.absoluteURL, type: .shortcut)
) |
| 144 break | |
| 145 case "icon": | 133 case "icon": |
| 146 if let type = link.attributes["type"], type.lowercased() == "ima
ge/png" { | 134 if let type = link.attributes["type"], type.lowercased() == "ima
ge/png" { |
| 147 let sizes = parseHTMLIconSizes(link.attributes["sizes"]) | 135 let sizes = parseHTMLIconSizes(link.attributes["sizes"]) |
| 148 if sizes.count > 0 { | 136 if sizes.count > 0 { |
| 149 for size in sizes { | 137 for size in sizes { |
| 150 if let type = kRelIconTypeMap[size] { | 138 if let type = kRelIconTypeMap[size] { |
| 151 icons.append(DetectedIcon(url: url, | 139 icons.append(DetectedIcon(url: url, |
| 152 type: type, | 140 type: type, |
| 153 width: size.width, | 141 width: size.width, |
| 154 height: size.height)) | 142 height: size.height)) |
| (...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 248 /// - returns: An array of `DetectedIcon` structures. | 236 /// - returns: An array of `DetectedIcon` structures. |
| 249 func extractBrowserConfigXMLIcons(_ document: LBXMLDocument, baseURL: URL) -> [D
etectedIcon] { | 237 func extractBrowserConfigXMLIcons(_ document: LBXMLDocument, baseURL: URL) -> [D
etectedIcon] { |
| 250 var icons: [DetectedIcon] = [] | 238 var icons: [DetectedIcon] = [] |
| 251 | 239 |
| 252 for tile in document.query("/browserconfig/msapplication/tile/*") { | 240 for tile in document.query("/browserconfig/msapplication/tile/*") { |
| 253 if let src = tile.attributes["src"], | 241 if let src = tile.attributes["src"], |
| 254 let url = URL(string: src, relativeTo: baseURL)?.absoluteURL { | 242 let url = URL(string: src, relativeTo: baseURL)?.absoluteURL { |
| 255 switch tile.name.lowercased() { | 243 switch tile.name.lowercased() { |
| 256 case "tileimage": | 244 case "tileimage": |
| 257 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite,
width: 144, height: 144)) | 245 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite,
width: 144, height: 144)) |
| 258 break | |
| 259 case "square70x70logo": | 246 case "square70x70logo": |
| 260 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite,
width: 70, height: 70)) | 247 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite,
width: 70, height: 70)) |
| 261 break | |
| 262 case "square150x150logo": | 248 case "square150x150logo": |
| 263 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite,
width: 150, height: 150)) | 249 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite,
width: 150, height: 150)) |
| 264 break | |
| 265 case "wide310x150logo": | 250 case "wide310x150logo": |
| 266 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite,
width: 310, height: 150)) | 251 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite,
width: 310, height: 150)) |
| 267 break | |
| 268 case "square310x310logo": | 252 case "square310x310logo": |
| 269 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite,
width: 310, height: 310)) | 253 icons.append(DetectedIcon(url: url, type: .microsoftPinnedSite,
width: 310, height: 310)) |
| 270 break | |
| 271 default: | 254 default: |
| 272 break | 255 break |
| 273 } | 256 } |
| 274 } | 257 } |
| 275 } | 258 } |
| 276 | 259 |
| 277 return icons | 260 return icons |
| 278 } | 261 } |
| 279 | 262 |
| 280 /// 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. |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 324 for size in string.components(separatedBy: .whitespaces) { | 307 for size in string.components(separatedBy: .whitespaces) { |
| 325 let parts = size.components(separatedBy: "x") | 308 let parts = size.components(separatedBy: "x") |
| 326 if parts.count != 2 { continue } | 309 if parts.count != 2 { continue } |
| 327 if let width = Int(parts[0]), let height = Int(parts[1]) { | 310 if let width = Int(parts[0]), let height = Int(parts[1]) { |
| 328 sizes.append(IconSize(width: width, height: height)) | 311 sizes.append(IconSize(width: width, height: height)) |
| 329 } | 312 } |
| 330 } | 313 } |
| 331 } | 314 } |
| 332 return sizes | 315 return sizes |
| 333 } | 316 } |
| LEFT | RIGHT |