Last active
January 27, 2026 16:21
-
-
Save fxm90/abd949e4258050f2f3cd80118024e5bd to your computer and use it in GitHub Desktop.
Revisions
-
fxm90 revised this gist
Jan 27, 2026 . 1 changed file with 104 additions and 82 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -7,104 +7,126 @@ import SwiftUI /// A lightweight utility that converts a limited subset of HTML tags /// into their Markdown equivalents. /// /// - SeeAlso: ``HTMLToMarkdownConverter.Tag`` for the list of supported tags. @available(iOS 15.0, *) enum HTMLToMarkdownConverter { // MARK: - Public Methods /// Converts supported HTML tags in the given string into their /// corresponding Markdown syntax. /// /// - Parameter html: A string containing HTML markup. /// /// - Returns: A Markdown-formatted string representing the supported /// HTML tags found in the input. /// /// - SeeAlso: ``HTMLToMarkdownConverter.Tag`` for the list of supported tags. static func convert(_ html: String) -> String { // Convert "basic" HTML tags that don't use an attribute. let markdown = Tag.allCases.reduce(html) { result, tag in result .replacingOccurrences(of: tag.openingHtmlTag, with: tag.markdownDelimiter) .replacingOccurrences(of: tag.closingHtmlTag, with: tag.markdownDelimiter) } // Anchor tags (`<a>`) use an attribute and therefore needs to be handled differently. return convertAnchorTagsToMarkdown(markdown) } // MARK: - Private Methods /// Converts HTML anchor (`<a>`) tags into their Markdown representation. /// /// Only the following syntax is supported: /// `<a href="URL">TEXT</a>` → `[TEXT](URL)` /// /// Anchor tags with additional attributes (e.g. `target`, `rel`, nested elements, or multiline content) /// are **not supported** and may produce incorrect results. /// /// - Parameter html: A string potentially containing HTML anchor tags. /// /// - Returns: A string where supported HTML links are replaced with Markdown links. private static func convertAnchorTagsToMarkdown(_ html: String) -> String { html.replacingOccurrences( of: "<a href=\"(.+)\">(.+)</a>", with: "[$2]($1)", options: .regularExpression, range: nil, ) } } // MARK: - Supporting Types extension HTMLToMarkdownConverter { /// A supported inline HTML tag that can be converted to Markdown. /// /// Each case represents an HTML tag whose opening and closing tags /// are replaced with a corresponding Markdown delimiter. enum Tag: String, CaseIterable { case strong case em case s case code /// The opening HTML tag (e.g. `<strong>`). var openingHtmlTag: String { "<\(rawValue)>" } /// The closing HTML tag (e.g. `</strong>`). var closingHtmlTag: String { "</\(rawValue)>" } /// The Markdown delimiter corresponding to the HTML tag. var markdownDelimiter: String { switch self { case .strong: "**" case .em: "*" case .s: "~~" case .code: "`" } } } } @available(iOS 15.0, *) extension Text { // MARK: - Initializer /// Creates a `Text` view by rendering a string containing supported HTML tags. /// /// The HTML is first converted to Markdown using ``HTMLToMarkdownConverter``, /// then parsed into an `AttributedString`. /// /// - Parameter html: A string containing supported HTML markup. /// /// - Note: If Markdown parsing fails, the initializer falls back /// to rendering the raw Markdown string without formatting. /// /// - SeeAlso: ``HTMLToMarkdownConverter.Tag`` for the list of supported tags. init(html: String) { let markdown = HTMLToMarkdownConverter.convert(html) do { let markdownAsAttributedString = try AttributedString(markdown: markdown) self = .init(markdownAsAttributedString) } catch { print("⚠️ – Couldn't parse markdown:", error) // Render the raw Markdown string without formatting as fallback. self = .init(markdown) } } } -
fxm90 created this gist
Jun 28, 2021 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,110 @@ // // SwiftUI+HTML.swift // // Created by Felix Mau on 28.05.21. // Copyright © 2021 Felix Mau. All rights reserved. // import SwiftUI @available(iOS 15.0, *) enum HTMLToMarkdownConverter { // MARK: - Public methods /// Converts the HTML-tags in the given string to their corresponding markdown tags. /// /// - SeeAlso: See type `HTMLToMarkdownConverter.Tags` for a list of supported HTML-tags. static func convert(_ htmlAsString: String) -> String { // Convert "basic" HTML-tags that don't use an attribute. let markdownAsString = Tags.allCases.reduce(htmlAsString) { result, textFormattingTag in result .replacingOccurrences(of: textFormattingTag.openingHtmlTag, with: textFormattingTag.markdownTag) .replacingOccurrences(of: textFormattingTag.closingHtmlTag, with: textFormattingTag.markdownTag) } // Hyperlinks use an attribute and therefore need to be handled differently. return convertHtmlLinksToMarkdown(markdownAsString) } // MARK: - Private methods /// Converts hyperlinks in HTML-format to their corresponding markdown representations. /// /// - Note: Currently we only support a basic HTML syntax without any attributed other than `href`. /// E.g. `<a href="URL">TEXT</a>` will be converted to `[TEXT](URL)` /// /// - Parameter htmlAsString: The string containing hyperlinks in HTML-format. /// /// - Returns: A string with hyperlinks converted to their corresponding markdown representations. private static func convertHtmlLinksToMarkdown(_ htmlAsString: String) -> String { htmlAsString.replacingOccurrences(of: "<a href=\"(.+)\">(.+)</a>", with: "[$2]($1)", options: .regularExpression, range: nil) } } extension HTMLToMarkdownConverter { /// The supported tags inside a string we can format. enum Tags: String, CaseIterable { case strong case em case s case code // Hyperlinks need to be handled differently, as they not only have simple opening and closing tag, but also use the attribute `href`. // See private method `Text.convertHtmlLinksToMarkdown(:)` for further details. // case a // MARK: - Public properties var openingHtmlTag: String { "<\(rawValue)>" } var closingHtmlTag: String { "</\(rawValue)>" } var markdownTag: String { switch self { case .strong: return "**" case .em: return "*" case .s: return "~~" case .code: return "`" } } } } @available(iOS 15.0, *) extension Text { // MARK: - Initializer /// Renders the given string containing HTML-tags with the related formatting. /// /// - SeeAlso: See type `HTMLToMarkdownConverter.Tags` for a list of supported HTML-tags. init(html htmlAsString: String) { let markdownAsString = HTMLToMarkdownConverter.convert(htmlAsString) do { let markdownAsAttributedString = try AttributedString(markdown: markdownAsString) self = .init(markdownAsAttributedString) } catch { print("⚠️ – Couldn't parse markdown: \(error)") // Show the "plain" markdown string as a fallback. self = .init(markdownAsString) } } }