Giter VIP home page Giter VIP logo

oakcitylabs / swift-markdownkit Goto Github PK

View Code? Open in Web Editor NEW

This project forked from wataru343/swift-markdownkit

0.0 1.0 0.0 117 KB

A macOS framework for parsing and transforming text in Markdown format written in Swift 5. The supported 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 creating HTML and attributed strings.

License: Apache License 2.0

Swift 99.61% Objective-C 0.39%

swift-markdownkit's Introduction

Swift MarkdownKit

Platform: macOS Language: Swift 5.3 IDE: Xcode 12.0 License: Apache

Overview

Swift MarkdownKit is a framework for parsing text in Markdown format. The supported syntax is based on the CommonMark Markdown specification. Swift MarkdownKit also provides an extended version of the parser that is able to handle Markdown tables.

Swift MarkdownKit defines an abstract syntax for Markdown, it provides a parser for parsing strings into abstract syntax trees, and comes with generators for creating HTML and attributed strings.

Using the framework

Parsing Markdown

Class MarkdownParser provides a simple API for parsing Markdown in a string. The parser returns an abstract syntax tree representing the Markdown structure in the string:

let markdown = MarkdownParser.standard.parse("""
                 # Header
                 ## Sub-header
                 And this is a **paragraph**.
                 """)
print(markdown)

Executing this code will result in the follwing data structure of type Block getting printed:

document(heading(1, text("Header")),
         heading(2, text("Sub-header")),
         paragraph(text("And this is a "),
                   strong(text("paragraph")),
                   text("."))))

Block is a recursively defined enumeration of cases with associated values (also called an algebraic datatype). Case document refers to the root of a document. It contains a sequence of blocks. In the example above, two different types of blocks appear within the document: heading and paragraph. A heading case consists of a heading level (as its first argument) and heading text (as the second argument). A paragraph case simply consists of text.

Text is represented using the struct Text which is effectively a sequence of TextFragment values. TextFragment is yet another recursively defined enumeration with associated values. The example above shows two different TextFragment cases in action: text and strong. Case text represents plain strings. Case strong contains a Text object, i.e. it encapsulates a sequence of TextFragment values which are "marked up strongly".

Class ExtendedMarkdownParser has the same interface like MarkdownParser but supports table blocks in addition to the block types defined by the CommonMark specification. Tables are based on the GitHub Flavored Markdown specification with one extension: within a table block it is possible to escape newline characters to enable cell text to be written on multiple lines. Here is an example:

| Column 1     | Column 2       |
| ------------ | -------------- |
| This text \
  is very long | More cell text |
| Last line    | Last cell      |        

Configuring the Markdown parser

The Markdown dialect supported by MarkdownParser is defined by two parameters: a sequence of block parsers (each represented as a subclass of BlockParser), and a sequence of inline transformers (each represented as a subclass of InlineTransformer). The initializer of class MarkdownParser accepts both components optionally. The default configuration (neither block parsers nor inline transformers are provided for the initializer) is able to handle Markdown based on the CommonMark specification.

Since MarkdownParser objects are stateless (beyond the configuration of block parsers and inline transformers), there is a predefined default MarkdownParser object accessible via the static property MarkdownParser.standard. This default parsing object is used in the example above.

New markdown parsers with different configurations can also be created by subclassing MarkdownParser and by overriding the class properties defaultBlockParsers and defaultInlineTransformers. Here is an example how class ExtendedMarkdownParser is derived from MarkdownParser simply by overriding defaultBlockParsers and by specializing standard in a covariant fashion.

open class ExtendedMarkdownParser: MarkdownParser {
  override open class var defaultBlockParsers: [BlockParser.Type] {
    return self.blockParsers
  }
  private static let blockParsers: [BlockParser.Type] =
    MarkdownParser.defaultBlockParsers + [TableParser.self]
  override open class var standard: ExtendedMarkdownParser {
    return self.singleton
  }
  private static let singleton: ExtendedMarkdownParser = ExtendedMarkdownParser()
}

Processing Markdown

The usage of abstract syntax trees for representing Markdown text has the advantage that it is very easy to process such data, in particular, to transform it and to extract information. Below is a short Swift snippet which illustrates how to process an abstract syntax tree for the purpose of extracting all top-level headers (i.e. this code prints the top-level outline of a text in Markdown format).

let markdown = MarkdownParser.standard.parse("""
                   # First *Header*
                   ## Sub-header
                   And this is a **paragraph**.
                   # Second **Header**
                   And this is another paragraph.
                 """)

func topLevelHeaders(doc: Block) -> [String] {
  guard case .document(let topLevelBlocks) = doc else {
    preconditionFailure("markdown block does not represent a document")
  }
  var outline: [String] = []
  for block in topLevelBlocks {
    if case .heading(1, let text) = block {
      outline.append(text.rawDescription)
    }
  }
  return outline
}

let headers = topLevelHeaders(doc: markdown)
print(headers)

This will print an array with the following two entries:

["First Header", "Second Header"]

Converting Markdown into other formats

Swift MarkdownKit currently provides two different generators, i.e. Markdown processors which output, for a given Markdown document, a corresponding representation in a different format.

HtmlGenerator defines a simple mapping from Markdown into HTML. Here is an example for the usage of the generator:

let html = HtmlGenerator.standard.generate(doc: markdown)

There are currently no means to customize HtmlGenerator beyond subclassing. Here is an example that defines a customized HTML generator which formats blockquote Markdown blocks using HTML tables:

open class CustomizedHtmlGenerator: HtmlGenerator {
  open override func generate(block: Block, tight: Bool = false) -> String {
    switch block {
      case .blockquote(let blocks):
        return "<table><tbody><tr><td style=\"background: #bbb; width: 0.2em;\"  />" +
               "<td style=\"width: 0.2em;\" /><td>\n" +
               self.generate(blocks: blocks) +
               "</td></tr></tbody></table>\n"
      default:
        return super.generate(block: block, tight: tight)
    }
  }
}

Swift MarkdownKit also comes with a generator for attributed strings. AttributedStringGenerator uses a customized HTML generator internally to define the translation from Markdown into NSAttributedString. The initializer of AttributedStringGenerator provides a number of parameters for customizing the style of the generated attributed string.

let generator = AttributedStringGenerator(fontSize: 12,
                                          fontFamily: "Helvetica, sans-serif",
                                          fontColor: "#33C",
                                          h1Color: "#000")
let attributedStr = generator.generate(doc: markdown)

Using the command-line tool

The Swift MarkdownKit Xcode project also implements a very simple command-line tool for either translating a single Markdown text file into HTML or for translating all Markdown files within a given directory into HTML.

The tool is provided to serve as a basis for customization to specific use cases. The simplest way to build the binary is to use the Swift Package Manager (SPM):

> git clone https://github.com/objecthub/swift-markdownkit.git
Cloning into 'swift-markdownkit'...
remote: Enumerating objects: 70, done.
remote: Counting objects: 100% (70/70), done.
remote: Compressing objects: 100% (54/54), done.
remote: Total 70 (delta 13), reused 65 (delta 11), pack-reused 0
Unpacking objects: 100% (70/70), done.
> cd swift-markdownkit
> swift build -c release
[1/3] Compiling Swift Module 'MarkdownKit' (25 sources)
[2/3] Compiling Swift Module 'MarkdownKitProcess' (1 sources)
[3/3] Linking ./.build/x86_64-apple-macosx/release/MarkdownKitProcess
> ./.build/x86_64-apple-macosx/release/MarkdownKitProcess
usage: mdkitprocess <source> [<target>]
where: <source> is either a Markdown file or a directory containing Markdown files
       <target> is either an HTML file or a directory in which HTML files are written

Known issues

There are a number of limitations and known issues:

  • The Markdown parser currently does not fully support link reference definitions in a CommonMark-compliant fashion. It is possible to define link reference definitions and use them, but for some corner cases, the current implementation behaves differently from the spec.

Requirements

The following technologies are needed to build the components of the Swift MarkdownKit framework. The command-line tool can be compiled with the Swift Package Manager, so Xcode is not strictly needed for that. Similarly, just for compiling the framework and trying the command-line tool in Xcode, the Swift Package Manager is not needed.

Copyright

Author: Matthias Zenger ([email protected])
Copyright © 2019-2020 Google LLC.
Please note: This is not an official Google product.

swift-markdownkit's People

Contributors

objecthub avatar wataru343 avatar

Watchers

James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.