195 lines
5.9 KiB
Swift
195 lines
5.9 KiB
Swift
import SwiftSoup
|
|
|
|
/**
|
|
Handles both inline HTML and the external HTML command
|
|
*/
|
|
struct HtmlCommand: CommandProcessor {
|
|
|
|
static let commandType: CommandType = .includedHtml
|
|
|
|
let results: PageGenerationResults
|
|
|
|
let content: Content
|
|
|
|
init(content: Content, results: PageGenerationResults, language: ContentLanguage) {
|
|
self.content = content
|
|
self.results = results
|
|
}
|
|
|
|
/**
|
|
Handle the HTML command
|
|
|
|
Format: ``
|
|
*/
|
|
func process(_ arguments: [String], markdown: Substring) -> String {
|
|
guard arguments.count == 1 else {
|
|
invalid(markdown)
|
|
return ""
|
|
}
|
|
let fileId = arguments[0]
|
|
guard let file = content.file(fileId) else {
|
|
results.missing(file: fileId, source: "External HTML command")
|
|
return ""
|
|
}
|
|
let content = file.textContent()
|
|
checkResources(in: content)
|
|
return content
|
|
}
|
|
|
|
/**
|
|
Handle inline HTML
|
|
*/
|
|
func process(_ html: String, markdown: Substring) -> String {
|
|
checkResources(in: html)
|
|
return html
|
|
}
|
|
|
|
private func checkResources(in html: String) {
|
|
let document: Document
|
|
do {
|
|
document = try SwiftSoup.parse(html)
|
|
} catch {
|
|
results.warning("Failed to parse inline HTML: \(error)")
|
|
return
|
|
}
|
|
|
|
checkImages(in: document)
|
|
checkLinks(in: document)
|
|
checkSourceSets(in: document)
|
|
}
|
|
|
|
private func checkImages(in document: Document) {
|
|
let srcAttributes: [String]
|
|
do {
|
|
let imgElements = try document.select("img")
|
|
|
|
srcAttributes = try imgElements.array()
|
|
.compactMap { try $0.attr("src") }
|
|
.filter { !$0.trimmed.isEmpty }
|
|
} catch {
|
|
results.warning("Failed to check 'src' attributes of <img> elements in inline HTML: \(error)")
|
|
return
|
|
}
|
|
|
|
for src in srcAttributes {
|
|
findFile(path: src, source: "src of <img>")
|
|
}
|
|
}
|
|
|
|
private func checkLinks(in document: Document) {
|
|
let hrefs: [String]
|
|
do {
|
|
let linkElements = try document.select("a")
|
|
|
|
hrefs = try linkElements.array()
|
|
.compactMap { try $0.attr("href").trimmed }
|
|
.filter { !$0.isEmpty }
|
|
} catch {
|
|
results.warning("Failed to check 'href' attributes of <a> elements in inline HTML: \(error)")
|
|
return
|
|
}
|
|
|
|
for url in hrefs {
|
|
if url.hasPrefix("http://") || url.hasPrefix("https://") {
|
|
results.externalLink(to: url)
|
|
} else {
|
|
findFile(path: url, source: "href of <a>")
|
|
}
|
|
}
|
|
}
|
|
|
|
private func checkSourceSets(in document: Document) {
|
|
let sources: [Element]
|
|
do {
|
|
sources = try document.select("source").array()
|
|
} catch {
|
|
results.warning("Failed to find <source> elements in inline HTML: \(error)")
|
|
return
|
|
}
|
|
checkSourceSetAttributes(sources: sources)
|
|
checkSourceAttributes(sources: sources)
|
|
}
|
|
|
|
private func checkSourceSetAttributes(sources: [Element]) {
|
|
let srcSets: [String]
|
|
do {
|
|
srcSets = try sources
|
|
.compactMap { try $0.attr("srcset") }
|
|
.filter { !$0.trimmed.isEmpty }
|
|
} catch {
|
|
results.warning("Failed to check 'srcset' attributes of <source> elements in inline HTML: \(error)")
|
|
return
|
|
}
|
|
|
|
for src in srcSets {
|
|
findFile(path: src, source: "srcset of <source>")
|
|
}
|
|
}
|
|
|
|
private func checkSourceAttributes(sources: [Element]) {
|
|
let srcAttributes: [String]
|
|
do {
|
|
srcAttributes = try sources
|
|
.compactMap { try $0.attr("src") }
|
|
.filter { !$0.trimmed.isEmpty }
|
|
} catch {
|
|
results.warning("Failed to check 'src' attributes of <source> elements in inline HTML: \(error)")
|
|
return
|
|
}
|
|
|
|
for src in srcAttributes {
|
|
findFile(path: src, source: "src of <source>")
|
|
}
|
|
}
|
|
|
|
private func findFile(path: String, source: String) {
|
|
let type = FileType(fileExtension: path.fileExtension)
|
|
|
|
guard path.hasPrefix("/") else {
|
|
findFileWith(relativePath: path, type: type, source: source)
|
|
return
|
|
}
|
|
|
|
guard !type.generatesImageVersions else {
|
|
// Try to determine image version needed
|
|
findImageVersion(path: path, type: type, source: source)
|
|
return
|
|
}
|
|
if findFile(withAbsolutePath: path) {
|
|
return
|
|
}
|
|
|
|
let fileId = path.dropBeforeLast("/")
|
|
if content.isValidIdForFile(fileId) {
|
|
results.missing(file: fileId, source: "HTML: \(source)")
|
|
} else {
|
|
results.warning("Could not find file '\(path)' for \(source)")
|
|
}
|
|
}
|
|
|
|
private func findFile(withAbsolutePath absolutePath: String) -> Bool {
|
|
guard let file = content.file(withOutputPath: absolutePath) else {
|
|
return false
|
|
}
|
|
results.require(file: file)
|
|
return true
|
|
}
|
|
|
|
private func findImageVersion(path: String, type: FileType, source: String) {
|
|
// First check if image original should be used
|
|
if findFile(withAbsolutePath: path) {
|
|
return
|
|
}
|
|
let fileId = path.dropAfterLast("/").dropBeforeLast("/")
|
|
guard let file = content.file(fileId) else {
|
|
results.missing(file: fileId, source: "HTML: \(source)")
|
|
return
|
|
}
|
|
results.warning("Could not determine image version for file '\(file.id)' for \(source)")
|
|
}
|
|
|
|
private func findFileWith(relativePath: String, type: FileType, source: String) {
|
|
results.warning("Could not determine relative file '\(relativePath)' for \(source)")
|
|
}
|
|
}
|