import Markdown nonisolated func markdownToHTML(_ text: String, imageURLResolver: ((String) -> String?)? = nil) -> String { let document = Document(parsing: text) var renderer = MarkdownHTMLRenderer(imageURLResolver: imageURLResolver) return renderer.visit(document) } private struct MarkdownHTMLRenderer: MarkupVisitor { typealias Result = String nonisolated(unsafe) let imageURLResolver: ((String) -> String?)? nonisolated(unsafe) private var isRenderingTableHead = false nonisolated(unsafe) private var currentTableAlignments: [Markdown.Table.ColumnAlignment?] = [] nonisolated(unsafe) private var currentTableColumnIndex = 0 nonisolated init(imageURLResolver: ((String) -> String?)?) { self.imageURLResolver = imageURLResolver } nonisolated mutating func visit(_ markup: Markup) -> String { markup.accept(&self) } nonisolated mutating func defaultVisit(_ markup: Markup) -> String { visitChildren(of: markup) } nonisolated mutating func visitDocument(_ document: Document) -> String { visitChildren(of: document) } nonisolated mutating func visitHeading(_ heading: Heading) -> String { "\(visitChildren(of: heading))\n" } nonisolated mutating func visitParagraph(_ paragraph: Paragraph) -> String { "

\(visitChildren(of: paragraph))

\n" } nonisolated mutating func visitBlockQuote(_ blockQuote: BlockQuote) -> String { "
\n\(visitChildren(of: blockQuote))
\n" } nonisolated mutating func visitUnorderedList(_ unorderedList: UnorderedList) -> String { "\n" } nonisolated mutating func visitOrderedList(_ orderedList: OrderedList) -> String { "
    \n\(visitChildren(of: orderedList))
\n" } nonisolated mutating func visitListItem(_ listItem: ListItem) -> String { if let checkbox = listItem.checkbox, listItem.childCount == 1, let paragraph = listItem.child(at: 0) as? Paragraph { let content = visitChildren(of: paragraph) return "
  • \(checkboxHTML(for: checkbox)) \(content)
  • \n" } var body = visitChildren(of: listItem) if let checkbox = listItem.checkbox { body = "\(checkboxHTML(for: checkbox))" + body } return "
  • \(body)
  • \n" } nonisolated mutating func visitCodeBlock(_ codeBlock: CodeBlock) -> String { let classAttribute: String if let language = codeBlock.language, !language.isEmpty { classAttribute = " class=\"language-\(escapeHTMLAttribute(language))\"" } else { classAttribute = "" } return "
    \(escapeHTML(codeBlock.code))
    \n" } nonisolated mutating func visitInlineCode(_ inlineCode: InlineCode) -> String { "\(escapeHTML(inlineCode.code))" } nonisolated mutating func visitThematicBreak(_: ThematicBreak) -> String { "
    \n" } nonisolated mutating func visitHTMLBlock(_ html: HTMLBlock) -> String { guard let sanitized = sanitizedMarkdownHTMLBlock(html.rawHTML) else { return "" } return sanitized + "\n" } nonisolated mutating func visitInlineHTML(_ inlineHTML: InlineHTML) -> String { sanitizedMarkdownHTMLTag(inlineHTML.rawHTML) ?? "" } nonisolated mutating func visitLink(_ link: Markdown.Link) -> String { let content = visitChildren(of: link) guard let destination = link.destination, let sanitizedDestination = sanitizedReadmeLinkURLString(destination) else { return content } let href = escapeHTMLAttribute(decodeHTMLEntities(sanitizedDestination)) return "\(content)" } nonisolated mutating func visitImage(_ image: Markdown.Image) -> String { let altText = plainText(from: image) guard let source = image.source, !source.isEmpty else { return escapeHTML(altText) } let resolvedSource = imageURLResolver?(source) ?? source guard let sanitizedSource = sanitizedReadmeImageURLString(resolvedSource) else { return escapeHTML(altText) } let src = escapeHTMLAttribute(decodeHTMLEntities(sanitizedSource)) return "\"\(escapeHTMLAttribute(altText))\"" } nonisolated mutating func visitStrong(_ strong: Strong) -> String { "\(visitChildren(of: strong))" } nonisolated mutating func visitEmphasis(_ emphasis: Emphasis) -> String { "\(visitChildren(of: emphasis))" } nonisolated mutating func visitStrikethrough(_ strikethrough: Strikethrough) -> String { "\(visitChildren(of: strikethrough))" } nonisolated mutating func visitText(_ text: Markdown.Text) -> String { escapeHTML(text.string) } nonisolated mutating func visitSoftBreak(_: SoftBreak) -> String { " " } nonisolated mutating func visitLineBreak(_: LineBreak) -> String { "
    " } nonisolated mutating func visitTable(_ table: Markdown.Table) -> String { let previousAlignments = currentTableAlignments let previousColumnIndex = currentTableColumnIndex currentTableAlignments = table.columnAlignments currentTableColumnIndex = 0 let content = visitChildren(of: table) currentTableAlignments = previousAlignments currentTableColumnIndex = previousColumnIndex return "\n\(content)
    \n" } nonisolated mutating func visitTableHead(_ tableHead: Markdown.Table.Head) -> String { let previousValue = isRenderingTableHead isRenderingTableHead = true let content = visitChildren(of: tableHead) isRenderingTableHead = previousValue return "\(content)\n" } nonisolated mutating func visitTableBody(_ tableBody: Markdown.Table.Body) -> String { let previousValue = isRenderingTableHead isRenderingTableHead = false let content = visitChildren(of: tableBody) isRenderingTableHead = previousValue return "\n\(content)\n" } nonisolated mutating func visitTableRow(_ tableRow: Markdown.Table.Row) -> String { let previousColumnIndex = currentTableColumnIndex currentTableColumnIndex = 0 let content = visitChildren(of: tableRow) currentTableColumnIndex = previousColumnIndex return "\(content)\n" } nonisolated mutating func visitTableCell(_ tableCell: Markdown.Table.Cell) -> String { let tagName = isRenderingTableHead ? "th" : "td" let styleAttribute = alignmentStyleAttribute(forColumn: currentTableColumnIndex) currentTableColumnIndex += 1 return "<\(tagName)\(styleAttribute)>\(visitChildren(of: tableCell))" } nonisolated private mutating func visitChildren(of markup: Markup) -> String { var html = "" for child in markup.children { html += visit(child) } return html } nonisolated private func plainText(from markup: Markup) -> String { switch markup { case let text as Markdown.Text: return text.string case let inlineCode as InlineCode: return inlineCode.code case is SoftBreak: return " " case is LineBreak: return "\n" default: var text = "" for child in markup.children { text += plainText(from: child) } return text } } nonisolated private func alignmentStyleAttribute(forColumn column: Int) -> String { guard column < currentTableAlignments.count, let alignment = currentTableAlignments[column] else { return "" } let textAlignment: String switch alignment { case .left: textAlignment = "left" case .center: textAlignment = "center" case .right: textAlignment = "right" } return " style=\"text-align: \(textAlignment);\"" } nonisolated private func checkboxHTML(for checkbox: Checkbox) -> String { switch checkbox { case .checked: return "" case .unchecked: return "" } } }