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.