{"id":21585369,"url":"https://github.com/objecthub/swift-markdownkit","last_synced_at":"2025-04-09T09:05:32.198Z","repository":{"id":44656799,"uuid":"202857899","full_name":"objecthub/swift-markdownkit","owner":"objecthub","description":"A framework for parsing and transforming text in Markdown format written in Swift 5 for macOS, iOS, and Linux. The syntax is based on the CommonMark specification. The framework defines an abstract syntax for Markdown, provides a parser for parsing strings into abstract syntax trees, and comes with generators for HTML and attributed strings.","archived":false,"fork":false,"pushed_at":"2025-03-27T23:37:09.000Z","size":211,"stargazers_count":176,"open_issues_count":4,"forks_count":23,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-02T08:12:46.416Z","etag":null,"topics":["commonmark","ios","linux","macos","markdown","markdown-converter","markdown-parser","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/objecthub.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-08-17T08:53:22.000Z","updated_at":"2025-03-31T08:13:42.000Z","dependencies_parsed_at":"2024-12-13T15:09:05.132Z","dependency_job_id":"47d6dd3c-cae6-478d-b18c-40d41618398a","html_url":"https://github.com/objecthub/swift-markdownkit","commit_stats":{"total_commits":43,"total_committers":3,"mean_commits":"14.333333333333334","dds":"0.046511627906976716","last_synced_commit":"c3e40147cab4831ff03a27faaff8e8ed8917effa"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objecthub%2Fswift-markdownkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objecthub%2Fswift-markdownkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objecthub%2Fswift-markdownkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objecthub%2Fswift-markdownkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/objecthub","download_url":"https://codeload.github.com/objecthub/swift-markdownkit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248008630,"owners_count":21032556,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["commonmark","ios","linux","macos","markdown","markdown-converter","markdown-parser","swift"],"created_at":"2024-11-24T15:10:23.276Z","updated_at":"2025-04-09T09:05:32.165Z","avatar_url":"https://github.com/objecthub.png","language":"Swift","readme":"# Swift MarkdownKit\n\n\u003cp\u003e\n\u003ca href=\"https://developer.apple.com/osx/\"\u003e\u003cimg src=\"https://img.shields.io/badge/Platform-macOS%20%7C%20iOS%20%7C%20Linux-blue.svg?style=flat\" alt=\"Platform: macOS | iOS | Linux\" /\u003e\u003c/a\u003e\n\u003ca href=\"https://developer.apple.com/swift/\"\u003e\u003cimg src=\"https://img.shields.io/badge/Language-Swift%205.7-green.svg?style=flat\" alt=\"Language: Swift 5.7\" /\u003e\u003c/a\u003e\n\u003ca href=\"https://developer.apple.com/xcode/\"\u003e\u003cimg src=\"https://img.shields.io/badge/IDE-Xcode%2014-orange.svg?style=flat\" alt=\"IDE: Xcode 14\" /\u003e\u003c/a\u003e\n\u003ca href=\"https://raw.githubusercontent.com/objecthub/swift-markdownkit/master/LICENSE\"\u003e\u003cimg src=\"http://img.shields.io/badge/License-Apache-lightgrey.svg?style=flat\" alt=\"License: Apache\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n## Overview\n\n_Swift MarkdownKit_ is a framework for parsing text in [Markdown](https://daringfireball.net/projects/markdown/)\nformat. The supported syntax is based on the [CommonMark Markdown specification](https://commonmark.org).\n_Swift MarkdownKit_ also provides an extended version of the parser that is able to handle Markdown tables.\n\n_Swift MarkdownKit_ defines an abstract syntax for Markdown, it provides a parser for parsing strings into\nabstract syntax trees, and comes with generators for creating HTML and\n[attributed strings](https://developer.apple.com/documentation/foundation/nsattributedstring).\n\n## Using the framework\n\n### Parsing Markdown\n\nClass [`MarkdownParser`](https://github.com/objecthub/swift-markdownkit/blob/master/Sources/MarkdownKit/Parser/MarkdownParser.swift)\nprovides a simple API for parsing Markdown in a string. The parser returns an abstract syntax tree\nrepresenting the Markdown structure in the string:\n\n```swift\nlet markdown = MarkdownParser.standard.parse(\"\"\"\n                 # Header\n                 ## Sub-header\n                 And this is a **paragraph**.\n                 \"\"\")\nprint(markdown)\n```\n\nExecuting this code will result in the follwing data structure of type `Block` getting printed:\n\n```swift\ndocument(heading(1, text(\"Header\")),\n         heading(2, text(\"Sub-header\")),\n         paragraph(text(\"And this is a \"),\n                   strong(text(\"paragraph\")),\n                   text(\".\"))))\n```\n\n[`Block`](https://github.com/objecthub/swift-markdownkit/blob/master/Sources/MarkdownKit/Block.swift)\nis a recursively defined enumeration of cases with associated values (also called an _algebraic datatype_).\nCase `document` refers to the root of a document. It contains a sequence of blocks. In the example above, two\ndifferent types of blocks appear within the document: `heading` and `paragraph`. A `heading` case consists\nof a heading level (as its first argument) and heading text (as the second argument). A `paragraph` case simply\nconsists of text.\n\nText is represented using the struct\n[`Text`](https://github.com/objecthub/swift-markdownkit/blob/master/Sources/MarkdownKit/Text.swift)\nwhich is effectively a sequence of `TextFragment` values.\n[`TextFragment`](https://github.com/objecthub/swift-markdownkit/blob/master/Sources/MarkdownKit/TextFragment.swift)\nis yet another recursively defined enumeration with associated values. The example above shows two different\n`TextFragment` cases in action: `text` and `strong`. Case `text` represents plain strings. Case `strong`\ncontains a `Text`  object, i.e. it encapsulates a sequence of `TextFragment` values which are\n\"marked up strongly\".\n\n### Parsing \"extended\" Markdown\n\nClass `ExtendedMarkdownParser` has the same interface like `MarkdownParser` but supports tables and\ndefinition lists in addition to the block types defined by the [CommonMark specification](https://commonmark.org).\n[Tables](https://github.github.com/gfm/#tables-extension-) are based on the\n[GitHub Flavored Markdown specification](https://github.github.com/gfm/) with one extension: within a table\nblock, it is possible to escape newline characters to enable cell text to be written on multiple lines. Here is an example:\n\n```\n| Column 1     | Column 2       |\n| ------------ | -------------- |\n| This text \\\n  is very long | More cell text |\n| Last line    | Last cell      |        \n```\n\n[Definition lists](https://www.markdownguide.org/extended-syntax/#definition-lists) are implemented in an\nad hoc fashion. A definition consists of terms and their corresponding definitions. Here is an example of two\ndefinitions:\n\n```\nApple\n: Pomaceous fruit of plants of the genus Malus in the family Rosaceae.\n\nOrange\n: The fruit of an evergreen tree of the genus Citrus.\n: A large round juicy citrus fruit with a tough bright reddish-yellow rind.\n```\n\n### Configuring the Markdown parser\n\nThe Markdown dialect supported by `MarkdownParser` is defined by two parameters: a sequence of\n_block parsers_ (each represented as a subclass of\n[`BlockParser`](https://github.com/objecthub/swift-markdownkit/blob/master/Sources/MarkdownKit/Parser/BlockParser.swift)),\nand a sequence of _inline transformers_ (each represented as a subclass of\n[`InlineTransformer`](https://github.com/objecthub/swift-markdownkit/blob/master/Sources/MarkdownKit/Parser/InlineTransformer.swift)).\nThe initializer of class `MarkdownParser` accepts both components optionally. The default configuration\n(neither block parsers nor inline transformers are provided for the initializer) is able to handle Markdown based on the\n[CommonMark specification](https://commonmark.org).\n\nSince `MarkdownParser` objects are stateless (beyond the configuration of block parsers and inline\ntransformers), there is a predefined default `MarkdownParser` object accessible via the static property\n`MarkdownParser.standard`. This default parsing object is used in the example above.\n\nNew markdown parsers with different configurations can also be created by subclassing\n[`MarkdownParser`](https://github.com/objecthub/swift-markdownkit/blob/master/Sources/MarkdownKit/Parser/MarkdownParser.swift)\nand by overriding the class properties `defaultBlockParsers` and `defaultInlineTransformers`. Here is\nan example of how class\n[`ExtendedMarkdownParser`](https://github.com/objecthub/swift-markdownkit/blob/master/Sources/MarkdownKit/Parser/ExtendedMarkdownParser.swift)\nis derived from `MarkdownParser` simply by overriding\n`defaultBlockParsers` and by specializing `standard` in a covariant fashion.\n\n```swift\nopen class ExtendedMarkdownParser: MarkdownParser {\n  override open class var defaultBlockParsers: [BlockParser.Type] {\n    return self.blockParsers\n  }\n  private static let blockParsers: [BlockParser.Type] =\n    MarkdownParser.defaultBlockParsers + [TableParser.self]\n  override open class var standard: ExtendedMarkdownParser {\n    return self.singleton\n  }\n  private static let singleton: ExtendedMarkdownParser = ExtendedMarkdownParser()\n}\n```\n\n### Extending the Markdown parser\n\nWith version 1.1 of the MarkdownKit framework, it is now also possible to extend the abstract\nsyntax supported by MarkdownKit. Both `Block` and `TextFragment` enumerations now include\na `custom` case which refers to objects representing the extended syntax. These objects have to\nimplement protocol [`CustomBlock`](https://github.com/objecthub/swift-markdownkit/blob/master/Sources/MarkdownKit/CustomBlock.swift) for blocks and [`CustomTextFragment`](https://github.com/objecthub/swift-markdownkit/blob/master/Sources/MarkdownKit/CustomTextFragment.swift) for text fragments.\n\nHere is a simple example how one can add support for \"underline\" (e.g. `this is ~underlined~ text`)\nand \"strikethrough\" (e.g. `this is using ~~strike-through~~`) by subclassing existing inline transformers.\n\nFirst, a new custom text fragment type has to be implemented for representing underlined and\nstrike-through text. This is done with an enumeration which implements the `CustomTextFragment` protocol:\n\n```swift\nenum LineEmphasis: CustomTextFragment {\n  case underline(Text)\n  case strikethrough(Text)\n\n  func equals(to other: CustomTextFragment) -\u003e Bool {\n    guard let that = other as? LineEmphasis else {\n      return false\n    }\n    switch (self, that) {\n      case (.underline(let lhs), .underline(let rhs)):\n        return lhs == rhs\n      case (.strikethrough(let lhs), .strikethrough(let rhs)):\n        return lhs == rhs\n      default:\n        return false\n    }\n  }\n  func transform(via transformer: InlineTransformer) -\u003e TextFragment {\n    switch self {\n      case .underline(let text):\n        return .custom(LineEmphasis.underline(transformer.transform(text)))\n      case .strikethrough(let text):\n        return .custom(LineEmphasis.strikethrough(transformer.transform(text)))\n    }\n  }\n  func generateHtml(via htmlGen: HtmlGenerator) -\u003e String {\n    switch self {\n      case .underline(let text):\n        return \"\u003cu\u003e\" + htmlGen.generate(text: text) + \"\u003c/u\u003e\"\n      case .strikethrough(let text):\n        return \"\u003cs\u003e\" + htmlGen.generate(text: text) + \"\u003c/s\u003e\"\n    }\n  }\n  func generateHtml(via htmlGen: HtmlGenerator,\n                    and attrGen: AttributedStringGenerator?) -\u003e String {\n    return self.generateHtml(via: htmlGen)\n  }\n  var rawDescription: String {\n    switch self {\n      case .underline(let text):\n        return text.rawDescription\n      case .strikethrough(let text):\n        return text.rawDescription\n    }\n  }\n  var description: String {\n    switch self {\n      case .underline(let text):\n        return \"~\\(text.description)~\"\n      case .strikethrough(let text):\n        return \"~~\\(text.description)~~\"\n    }\n  }\n  var debugDescription: String {\n    switch self {\n      case .underline(let text):\n        return \"underline(\\(text.debugDescription))\"\n      case .strikethrough(let text):\n        return \"strikethrough(\\(text.debugDescription))\"\n    }\n  }\n}\n```\n\nNext, two inline transformers need to be extended to recognize the new emphasis delimiter `~`:\n\n```swift\nfinal class EmphasisTestTransformer: EmphasisTransformer {\n  override public class var supportedEmphasis: [Emphasis] {\n    return super.supportedEmphasis + [\n             Emphasis(ch: \"~\", special: false, factory: { double, text in\n               return .custom(double ? LineEmphasis.strikethrough(text)\n                                     : LineEmphasis.underline(text))\n             })]\n  }\n}\nfinal class DelimiterTestTransformer: DelimiterTransformer {\n  override public class var emphasisChars: [Character] {\n    return super.emphasisChars + [\"~\"]\n  }\n}\n```\n\nFinally, a new extended markdown parser can be created:\n\n```swift\nfinal class EmphasisTestMarkdownParser: MarkdownParser {\n  override public class var defaultInlineTransformers: [InlineTransformer.Type] {\n    return [DelimiterTestTransformer.self,\n            CodeLinkHtmlTransformer.self,\n            LinkTransformer.self,\n            EmphasisTestTransformer.self,\n            EscapeTransformer.self]\n  }\n  override public class var standard: EmphasisTestMarkdownParser {\n    return self.singleton\n  }\n  private static let singleton: EmphasisTestMarkdownParser = EmphasisTestMarkdownParser()\n}\n```\n\n### Processing Markdown\n\nThe usage of abstract syntax trees for representing Markdown text has the advantage that it is very easy to\nprocess such data, in particular, to transform it and to extract information. Below is a short  _Swift_  snippet\nwhich illustrates how to process an abstract syntax tree for the purpose of extracting all top-level headers\n(i.e. this code prints the top-level outline of a text in Markdown format).\n\n```swift\nlet markdown = MarkdownParser.standard.parse(\"\"\"\n                   # First *Header*\n                   ## Sub-header\n                   And this is a **paragraph**.\n                   # Second **Header**\n                   And this is another paragraph.\n                 \"\"\")\n\nfunc topLevelHeaders(doc: Block) -\u003e [String] {\n  guard case .document(let topLevelBlocks) = doc else {\n    preconditionFailure(\"markdown block does not represent a document\")\n  }\n  var outline: [String] = []\n  for block in topLevelBlocks {\n    if case .heading(1, let text) = block {\n      outline.append(text.rawDescription)\n    }\n  }\n  return outline\n}\n\nlet headers = topLevelHeaders(doc: markdown)\nprint(headers)\n```\n\nThis will print an array with the following two entries:\n\n```swift\n[\"First Header\", \"Second Header\"]\n```\n\n### Converting Markdown into other formats\n\n_Swift MarkdownKit_ currently provides two different _generators_, i.e. Markdown processors which\noutput, for a given Markdown document, a corresponding representation in a different format.\n\n[`HtmlGenerator`](https://github.com/objecthub/swift-markdownkit/blob/master/Sources/MarkdownKit/HTML/HtmlGenerator.swift)\ndefines a simple mapping from Markdown into HTML. Here is an example for the usage of the generator: \n\n```swift\nlet html = HtmlGenerator.standard.generate(doc: markdown)\n```\n\nThere are currently no means to customize `HtmlGenerator` beyond subclassing. Here is an example that\ndefines a customized HTML generator which formats `blockquote` Markdown blocks using HTML tables:\n\n```swift\nopen class CustomizedHtmlGenerator: HtmlGenerator {\n  open override func generate(block: Block, tight: Bool = false) -\u003e String {\n    switch block {\n      case .blockquote(let blocks):\n        return \"\u003ctable\u003e\u003ctbody\u003e\u003ctr\u003e\u003ctd style=\\\"background: #bbb; width: 0.2em;\\\"  /\u003e\" +\n               \"\u003ctd style=\\\"width: 0.2em;\\\" /\u003e\u003ctd\u003e\\n\" +\n               self.generate(blocks: blocks) +\n               \"\u003c/td\u003e\u003c/tr\u003e\u003c/tbody\u003e\u003c/table\u003e\\n\"\n      default:\n        return super.generate(block: block, tight: tight)\n    }\n  }\n}\n```\n\n_Swift MarkdownKit_ also comes with a generator for attributed strings.\n[`AttributedStringGenerator`](https://github.com/objecthub/swift-markdownkit/blob/master/Sources/MarkdownKit/AttributedString/AttributedStringGenerator.swift)\nuses a customized HTML generator internally to define the translation from Markdown into\n`NSAttributedString`. The initializer of `AttributedStringGenerator` provides a number of\nparameters for customizing the style of the generated attributed string. \n\n```swift\nlet generator = AttributedStringGenerator(fontSize: 12,\n                                          fontFamily: \"Helvetica, sans-serif\",\n                                          fontColor: \"#33C\",\n                                          h1Color: \"#000\")\nlet attributedStr = generator.generate(doc: markdown)\n```\n\n## Using the command-line tool\n\nThe _Swift MarkdownKit_ Xcode project also implements a\n[very simple command-line tool](https://github.com/objecthub/swift-markdownkit/blob/master/Sources/MarkdownKitProcess/main.swift)\nfor either translating a single Markdown text file into HTML or for translating all Markdown files within a given\ndirectory into HTML.\n\nThe tool is provided to serve as a basis for customization to specific use cases. The simplest way to build the\nbinary is to use the Swift Package Manager (SPM):\n\n```sh\n\u003e git clone https://github.com/objecthub/swift-markdownkit.git\nCloning into 'swift-markdownkit'...\nremote: Enumerating objects: 70, done.\nremote: Counting objects: 100% (70/70), done.\nremote: Compressing objects: 100% (54/54), done.\nremote: Total 70 (delta 13), reused 65 (delta 11), pack-reused 0\nUnpacking objects: 100% (70/70), done.\n\u003e cd swift-markdownkit\n\u003e swift build -c release\n[1/3] Compiling Swift Module 'MarkdownKit' (25 sources)\n[2/3] Compiling Swift Module 'MarkdownKitProcess' (1 sources)\n[3/3] Linking ./.build/x86_64-apple-macosx/release/MarkdownKitProcess\n\u003e ./.build/x86_64-apple-macosx/release/MarkdownKitProcess\nusage: mdkitprocess \u003csource\u003e [\u003ctarget\u003e]\nwhere: \u003csource\u003e is either a Markdown file or a directory containing Markdown files\n       \u003ctarget\u003e is either an HTML file or a directory in which HTML files are written\n```\n\n## Known issues\n\nThere are a number of limitations and known issues:\n\n  - The Markdown parser currently does not fully support _link reference definitions_ in a CommonMark-compliant\n    fashion. It is possible to define link reference definitions and use them, but for some corner cases, the current\n    implementation behaves differently from the spec.\n\n## Requirements\n\nThe following technologies are needed to build the components of the _Swift MarkdownKit_ framework.\nThe command-line tool can be compiled with the _Swift Package Manager_, so _Xcode_ is not strictly needed\nfor that. Similarly, just for compiling the framework and trying the command-line tool in _Xcode_, the\n_Swift Package Manager_ is not needed.\n\n- [Xcode 14](https://developer.apple.com/xcode/)\n- [Swift 5.7](https://developer.apple.com/swift/)\n- [Swift Package Manager](https://swift.org/package-manager/)\n\n## Copyright\n\nAuthor: Matthias Zenger (\u003cmatthias@objecthub.net\u003e)  \nCopyright © 2019-2025 Google LLC.  \n_Please note: This is not an official Google product._\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fobjecthub%2Fswift-markdownkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fobjecthub%2Fswift-markdownkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fobjecthub%2Fswift-markdownkit/lists"}