Swift Markdown

Swift Markdown is a Swift package for parsing, building, editing, and analyzing Markdown documents.

The parser is powered by GitHub-flavored Markdown’s cmark-gfm implementation, so it follows the spec closely. As the needs of the community change, the effective dialect implemented by this library may change.

The markup tree provided by this package is comprised of immutable/persistent, thread-safe, copy-on-write value types that only copy substructure that has changed. Other examples of the main strategy behind this library can be seen in Swift’s lib/Syntax and its Swift bindings, SwiftSyntax.

Getting Started Using Markup

In your Package.swift Swift Package Manager manifest, add the following dependency to your dependencies argument:

.package(url: "ssh://[email protected]/apple/swift-markdown.git", .branch("main")),

Add the dependency to any targets you’ve declared in your manifest:

.target(name: "MyTarget", dependencies: ["Markdown"]),

Parsing

To parse a document, use Document(parsing:), supplying a String or URL:

import Markdown

let source = "This is a markup *document*."
let document = Document(parsing: source)
print(document.debugDescription())
// Document
// └─ Paragraph
//    ├─ Text "This is a markup "
//    ├─ Emphasis
//    │  └─ Text "document"
//    └─ Text "."

Parsing text is just one way to build a tree of Markup elements. You can also build them yourself declaratively.

Building Markup Trees

You can build trees using initializers for the various element types provided.

import Markdown

let document = Document(
    Paragraph(
        Text("This is a "),
        Emphasis(
            Text("paragraph."))))

This would be equivalent to parsing "This is a *paragraph.*" but allows you to programmatically insert content from other data sources into individual elements.

Modifying Markup Trees with Persistence

Swift Markdown uses a persistent tree for its backing storage, providing effectively immutable, copy-on-write value types that only copy the substructure necessary to create a unique root without affecting the previous version of the tree.

Modifying Elements Directly

If you just need to make a quick change, you can modify an element anywhere in a tree, and Swift Markdown will create copies of substructure that cannot be shared.

import Markdown

let source = "This is *emphasized.*"
let document = Document(parsing: source)
print(document.debugDescription())
// Document
// └─ Paragraph
//    ├─ Text "This is "
//    └─ Emphasis
//       └─ Text "emphasized."

var text = document.child(through:
    0, // Paragraph
    1, // Emphasis
    0) as! Text // Text

text.string = "really emphasized!"
print(text.root.debugDescription())
// Document
// └─ Paragraph
//    ├─ Text "This is "
//    └─ Emphasis
//       └─ Text "really emphasized!"

// The original document is unchanged:

print(document.debugDescription())
// Document
// └─ Paragraph
//    ├─ Text "This is "
//    └─ Emphasis
//       └─ Text "emphasized."

If you find yourself needing to systematically change many parts of a tree, or even provide a complete transformation into something else, maybe the familiar Visitor Pattern is what you want.

Visitors, Walkers, and Rewriters

There is a core MarkupVisitor protocol that provides the basis for transforming, walking, or rewriting a markup tree.

public protocol MarkupVisitor {
    associatedtype Result
}

Using its Result type, you can transform a markup tree into anything: another markup tree, or perhaps a tree of XML or HTML elements. There are two included refinements of MarkupVisitor for common uses.

The first refinement, MarkupWalker, has an associated Result type of Void, so it’s meant for summarizing or detecting aspects of a markup tree. If you wanted to append to a string as elements are visited, this might be a good tool for that.

import Markdown

/// Counts `Link`s in a `Document`.
struct LinkCounter: MarkupWalker {
    var count = 0
    mutating func visitLink(_ link: Link) {
        if link.destination == "https://swift.org" {
            count += 1
        }
        descendInto(link)
    }
}

let source = "There are [two](https://swift.org) links to <https://swift.org> here."
let document = Document(parsing: source)
print(document.debugDescription())
var linkCounter = LinkCounter()
linkCounter.visit(document)
print(linkCounter.count)
// 2

The second refinement, MarkupRewriter, has an associated Result type of Markup?, so it’s meant to change or even remove elements from a markup tree. You can return nil to delete an element, or return another element to substitute in its place.

import Markdown

/// Delete all **strong** elements in a markup tree.
struct StrongDeleter: MarkupRewriter {
    mutating func visitStrong(_ strong: Strong) -> Markup? {
        return nil
    }
}

let source = "Now you see me, **now you don't**"
let document = Document(parsing: source)
var strongDeleter = StrongDeleter()
let newDocument = strongDeleter.visit(document)

print(newDocument!.debugDescription())
// Document
// └─ Paragraph
//    └─ Text "Now you see me, "

Block Directives

Swift Markdown includes a syntax extension for attributed block elements. See Block Directive documentation for more information.

Getting Involved

Submitting a Bug Report

Swift Markdown tracks all bug reports with Swift JIRA.
You can use the “SwiftMarkdown” component for issues and feature requests specific to Swift Markdown.
When you submit a bug report we ask that you follow the
Swift Bug Reporting guidelines
and provide as many details as possible.

Submitting a Feature Request

For feature requests, please feel free to create an issue
on Swift JIRA with the New Feature type
or start a discussion on the Swift Forums.

Don’t hesitate to submit a feature request if you see a way
Swift Markdown can be improved to better meet your needs.

Contributing to Swift Markdown

Please see the contributing guide for more information.

GitHub

View Github