Markdown parsing and rendering for iOS and OS X

Related tags

Text CocoaMarkdown
Overview

CocoaMarkdown

Markdown parsing and rendering for iOS and macOS

CocoaMarkdown is a cross-platform framework for parsing and rendering Markdown, built on top of the C reference implementation of CommonMark.

Why?

CocoaMarkdown aims to solve two primary problems better than existing libraries:

  1. More flexibility. CocoaMarkdown allows you to define custom parsing hooks or even traverse the Markdown AST using the low-level API.
  2. Efficient NSAttributedString creation for easy rendering on iOS and macOS. Most existing libraries just generate HTML from the Markdown, which is not a convenient representation to work with in native apps.

Example app macOS

Example app iOS

Installation

First you will want to add this project as a submodule to your project:

git submodule add https://github.com/indragiek/CocoaMarkdown.git

Then, you need to pull down all of its dependencies.

cd CocoaMarkdown
git submodule update --init --recursive

Next, drag the .xcodeproj file from within CocoaMarkdown into your project. After that, click on the General tab of your target. Select the plus button under "Embedded Binaries" and select the CocoaMarkdown.framework.

API

Traversing the Markdown AST

CMNode and CMIterator wrap CommonMark's C types with an object-oriented interface for traversal of the Markdown AST.

let document = CMDocument(contentsOfFile: path, options: [])
document.rootNode.iterator().enumerateUsingBlock { (node, _, _) in
    print("String value: \(node.stringValue)")
}

Building Custom Renderers

The CMParser class isn't really a parser (it just traverses the AST), but it defines an NSXMLParser-style delegate API that provides handy callbacks for building your own renderers:

@protocol CMParserDelegate <NSObject>
@optional
- (void)parserDidStartDocument:(CMParser *)parser;
- (void)parserDidEndDocument:(CMParser *)parser;
...
- (void)parser:(CMParser *)parser foundText:(NSString *)text;
- (void)parserFoundHRule:(CMParser *)parser;
...
@end

CMAttributedStringRenderer is an example of a custom renderer that is built using this API.

Rendering Attributed Strings

CMAttributedStringRenderer is the high level API that will be useful to most apps. It creates an NSAttributedString directly from Markdown, skipping the step of converting it to HTML altogether.

Going from a Markdown document to rendering it on screen is as easy as:

let document = CMDocument(contentsOfFile: path, options: [])
let renderer = CMAttributedStringRenderer(document: document, attributes: CMTextAttributes())
textView.attributedText = renderer.render()

Or, using the convenience method on CMDocument:

textView.attributedText = CMDocument(contentsOfFile: path, options: []).attributedStringWithAttributes(CMTextAttributes())

HTML elements can be supported by implementing CMHTMLElementTransformer. The framework includes several transformers for commonly used tags:

Transformers can be registered with the renderer to use them:

let document = CMDocument(contentsOfFile: path, options: [])
let renderer = CMAttributedStringRenderer(document: document, attributes: CMTextAttributes())
renderer.registerHTMLElementTransformer(CMHTMLStrikethroughTransformer())
renderer.registerHTMLElementTransformer(CMHTMLSuperscriptTransformer())
textView.attributedText = renderer.render()

Customizing Attributed strings rendering

All attributes used to style the text are customizable using the CMTextAttributes class.

Every Markdown element type can be customized using the corresponding CMStyleAttributes property in CMTextAttributes, defining 3 different kinds of attributes:

  • String attributes, i.e. regular NSAttributedString attributes
  • Font attributes, for easy font setting
  • Paragraph attributes, relevant only for block elements

Attributes for any Markdown element kind can be directly set:

let textAttributes = CMTextAttributes()
textAttributes.linkAttributes.stringAttributes[NSAttributedString.Key.backgroundColor] = UIColor.yellow

A probably better alternative for style customization is to use grouped attributes setting methods available in CMTextAttributes:

let textAttributes = CMTextAttributes()

// Set the text color for all headers
textAttributes.addStringAttributes([ .foregroundColor: UIColor(red: 0.0, green: 0.446, blue: 0.657, alpha: 1.0)], 
                                   forElementWithKinds: .anyHeader)

// Set a specific font + font-traits for all headers
let boldItalicTrait: UIFontDescriptor.SymbolicTraits = [.traitBold, .traitItalic]
textAttributes.addFontAttributes([ .family: "Avenir Next" ,
                                   .traits: [ UIFontDescriptor.TraitKey.symbolic: boldItalicTrait.rawValue]], 
                                 forElementWithKinds: .anyHeader)
// Set specific font traits for header1 and header2
textAttributes.setFontTraits([.weight: UIFont.Weight.heavy], 
                             forElementWithKinds: [.header1, .header2])

// Center block-quote paragraphs        
textAttributes.addParagraphStyleAttributes([ .alignment: NSTextAlignment.center.rawValue], 
                                           forElementWithKinds: .blockQuote)

// Set a background color for code elements        
textAttributes.addStringAttributes([ .backgroundColor: UIColor(white: 0.9, alpha: 0.5)], 
                                   forElementWithKinds: [.inlineCode, .codeBlock])

List styles can be customized using dedicated paragraph style attributes:

// Customize the list bullets
textAttributes.addParagraphStyleAttributes([ .listItemBulletString: "🍏" ], 
                                           forElementWithKinds: .unorderedList)
textAttributes.addParagraphStyleAttributes([ .listItemBulletString: "🌼" ], 
                                           forElementWithKinds: .unorderedSublist)

// Customize numbered list item labels format and distance between label and paragraph
textAttributes.addParagraphStyleAttributes([ .listItemNumberFormat: "(%02ld)", 
                                             .listItemLabelIndent: 30 ],    
                                           forElementWithKinds: .orderedList)

Font and paragraph attributes are incremental, meaning that they allow to modify only specific aspects of the default rendering styles.

Additionally on iOS, Markdown elements styled using the font attributes API get automatic Dynamic-Type compliance in the generated attributed string, just like default rendering styles.

Rendering HTML

CMHTMLRenderer provides the ability to render HTML from Markdown:

let document = CMDocument(contentsOfFile: path, options: [])
let renderer = CMHTMLRenderer(document: document)
let HTML = renderer.render()

Or, using the convenience method on CMDocument:

let HTML = CMDocument(contentsOfFile: path).HTMLString()

Example Apps

The project includes example apps for iOS and macOS to demonstrate rendering attributed strings.

Contact

License

CocoaMarkdown is licensed under the MIT License. See LICENSE for more information.

Comments
  • Fix flipped images on macOS 10.15

    Fix flipped images on macOS 10.15

    MacOS Catalina fixes a flipping issue regarding the display of image attachments in attributed strings. Therefore the workaround included in CMImageTextAttachment.m is could cause markdown images to appear vertically flipped on macOS 10.15.

    So this pull request conditionally removes the extra image flipping when the masOS version is 10.15 or above.

    It also includes a new CMDocument init method added in a previous commit.

    opened by jlj 10
  • Image Support

    Image Support

    I noticed that the images are not being implemented in the CMAttributedStringRenderer. Is there a plan for that?

    I've been trying to work on an asynchronous version and getting some progress here.

    opened by jamztang 10
  • Links are white

    Links are white

    let attributes = CMTextAttributes() attributes.linkAttributes = [ NSForegroundColorAttributeName: UIColor.redColor() ]

    Will render white links if the window.tintColor property is set...

    Aside from that the code above actually doesn't work at all. Starting to think the external tints colors are causing this issue?

    Any solid explanation why?

    opened by adamdahan 7
  • Add podspec

    Add podspec

    Very minimal support for iOS only. I bet OS X should work out of box if we add :osx to pod spec but I didn't test. The source code is left untouched. I removed CocoaMarkdown/Ono.h from CocoaMarkdown.h because it's not used anywhere but in CMAttributedStringRenderer.m where it is included as Ono.h which should work just fine for the time being.

    This work is based on @krodak's effort in #19 but takes into account the fact that you would like to keep submodules.

    Good to have in future:

    1. Separate private headers from public. Currently pod spec includes the entire folder but I bet there were some private headers that normally should not be exposed.
    2. Normalize import statements for dependencies, so they follow <LIBRARY/LIBRARY.h> pattern or something like that to avoid collisions.

    After merge:

    You would have to change the git URL in podspec, add tag to repo and put this tag in pod spec.

    opened by pronebird 6
  • Corrected fix for flipped images on macOS 10.15.

    Corrected fix for flipped images on macOS 10.15.

    Improved the original fix for flipped images in [b2397d6]: The SDK version used for generating the application shall be considered as well as the OS version on which the application is running (otherwise an application compiled with Xcode 10 and running on macOS 10,15 would show flipped images).

    Thanks to @hisaac for reporting the issue! (See the discussion at https://github.com/indragiek/CocoaMarkdown/pull/52#issuecomment-557681225)

    Also, to avoid creating a dedicated PR, this one includes a few minor typo fixes in the README :)

    opened by jlj 5
  • Image renders as a placeholder until user clicks on it

    Image renders as a placeholder until user clicks on it

    Hi, I'm using the inline image functionality which was recently merged into the master branch. However, when I specify an image in markdown, I get a grey "question mark" in a box, which I see the code draws as a placeholder image. But the real image never appears until I click on that question mark, and then it instantly shows up.

    Any advice? I am not fluent in NSAttributedStrings or NSTextAttachemts unfortunately.

    opened by tsfischer 3
  • Sample apps not working after #49 merge

    Sample apps not working after #49 merge

    Thanks for merging #49. However I went to try out the example app on both mac and iOS and it fails. In either case I get an exception... on iOS this crashes the app and on Mac it just fails to render anything.

    The exception happens in CMAttributedStringRenderer:appendString because string parm is nil. This seems to go deep into the parsing logic which I didn't fully debug into yet.

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSConcreteAttributedString initWithString:: nil value' *** First throw call stack: ( 0 CoreFoundation 0x00000001116f98db __exceptionPreprocess + 331 1 libobjc.A.dylib 0x00000001104e3ac5 objc_exception_throw + 48 2 CoreFoundation 0x00000001116f9735 +[NSException raise:format:] + 197 3 Foundation 0x000000010fb538f6 -[NSConcreteAttributedString initWithString:] + 135 4 Foundation 0x000000010fb539e9 -[NSConcreteAttributedString initWithString:attributes:] + 27 5 CocoaMarkdown 0x000000010f9669f8 -[CMAttributedStringRenderer appendString:] + 152 6 CocoaMarkdown 0x000000010f963a5b -[CMAttributedStringRenderer parser:foundText:] + 315 7 CocoaMarkdown 0x000000010f94a9ca -[CMParser handleNode:event:] + 778 8 CocoaMarkdown 0x000000010f94a567 __17-[CMParser parse]_block_invoke + 151 9 CocoaMarkdown 0x000000010f949eb7 -[CMIterator enumerateUsingBlock:] + 487 10 CocoaMarkdown 0x000000010f94a484 -[CMParser parse] + 244 11 CocoaMarkdown 0x000000010f96370b -[CMAttributedStringRenderer render] + 315 12 Example-iOS 0x000000010f5b9325 $s11Example_iOS14ViewControllerC11viewDidLoadyyF + 1765 13 Example-iOS 0x000000010f5b9784 $s11Example_iOS14ViewControllerC11viewDidLoadyyFTo + 36 14 UIKitCore 0x000000011978e0f7 -[UIViewController loadViewIfRequired] + 1183 15 UIKitCore 0x000000011978e524 -[UIViewController view] + 27 16 UIKitCore 0x0000000119dc722b -[UIWindow addRootViewControllerViewIfPossible] + 122 17 UIKitCore 0x0000000119dc791f -[UIWindow _setHidden:forced:] + 289 18 UIKitCore 0x0000000119dda57e -[UIWindow makeKeyAndVisible] + 42 19 UIKitCore 0x0000000119d8a33c -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4555 20 UIKitCore 0x0000000119d8f4e6 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1617 21 UIKitCore 0x00000001195d3a4e __111-[__UICanvasLifecycleMonitor_Compatability _scheduleFirstCommitForScene:transition:firstActivation:completion:]_block_invoke + 904 22 UIKitCore 0x00000001195dc346 +[_UICanvas _enqueuePostSettingUpdateTransactionBlock:] + 153 23 UIKitCore 0x00000001195d3664 -[__UICanvasLifecycleMonitor_Compatability _scheduleFirstCommitForScene:transition:firstActivation:completion:] + 236 24 UIKitCore 0x00000001195d3fc0 -[__UICanvasLifecycleMonitor_Compatability activateEventsOnly:withContext:completion:] + 1091 25 UIKitCore 0x00000001195d2332 __82-[_UIApplicationCanvas _transitionLifecycleStateWithTransitionContext:completion:]_block_invoke + 782 26 UIKitCore 0x00000001195d1fe9 -[_UIApplicationCanvas _transitionLifecycleStateWithTransitionContext:completion:] + 433 27 UIKitCore 0x00000001195d6d2e __125-[_UICanvasLifecycleSettingsDiffAction performActionsForCanvas:withUpdatedScene:settingsDiff:fromSettings:transitionContext:]_block_invoke + 576 28 UIKitCore 0x00000001195d7988 _performActionsWithDelayForTransitionContext + 100 29 UIKitCore 0x00000001195d6a95 -[_UICanvasLifecycleSettingsDiffAction performActionsForCanvas:withUpdatedScene:settingsDiff:fromSettings:transitionContext:] + 223 30 UIKitCore 0x00000001195dba48 -[_UICanvas scene:didUpdateWithDiff:transitionContext:completion:] + 392 31 UIKitCore 0x0000000119d8ddc8 -[UIApplication workspace:didCreateScene:withTransitionContext:completion:] + 514 32 UIKitCore 0x000000011994502f -[UIApplicationSceneClientAgent scene:didInitializeWithEvent:completion:] + 361 33 FrontBoardServices 0x000000011cbead25 -[FBSSceneImpl _didCreateWithTransitionContext:completion:] + 448 34 FrontBoardServices 0x000000011cbf4ad6 __56-[FBSWorkspace client:handleCreateScene:withCompletion:]_block_invoke_2 + 283 35 FrontBoardServices 0x000000011cbf4300 __40-[FBSWorkspace _performDelegateCallOut:]_block_invoke + 53 36 libdispatch.dylib 0x0000000112a21db5 _dispatch_client_callout + 8 37 libdispatch.dylib 0x0000000112a252ba _dispatch_block_invoke_direct + 300 38 FrontBoardServices 0x000000011cc260da FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK + 30 39 FrontBoardServices 0x000000011cc25d92 -[FBSSerialQueue _performNext] + 451 40 FrontBoardServices 0x000000011cc26327 -[FBSSerialQueue _performNextFromRunLoopSource] + 42 41 CoreFoundation 0x0000000111660db1 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION + 17 42 CoreFoundation 0x0000000111660633 __CFRunLoopDoSources0 + 243 43 CoreFoundation 0x000000011165acef __CFRunLoopRun + 1231 44 CoreFoundation 0x000000011165a4d2 CFRunLoopRunSpecific + 626 45 GraphicsServices 0x0000000115ebd2fe GSEventRunModal + 65 46 UIKitCore 0x0000000119d90fc2 UIApplicationMain + 140 47 Example-iOS 0x000000010f5ba9eb main + 75 48 libdyld.dylib 0x0000000112a96541 start + 1 49 ??? 0x0000000000000001 0x0 + 1 ) libc++abi.dylib: terminating with uncaught exception of type NSException

    opened by tsfischer 3
  • Support for images and relative links in attributed string renderer.

    Support for images and relative links in attributed string renderer.

    Hi,

    I don't know if you are still maintaining this project, but if you are, you might be interested by this PR adding images and relative links to attributed string rendering on both iOS and macOS. Additionally I made a overall refresh to the project: cmark v0.29, Swift 4.2 example apps, paragraph attributes… (see the commit message for all details).

    opened by jlj 3
  • Umbrella header for a framework build generates warnings in Xcode 7.3.1 (iOS framework build)

    Umbrella header for a framework build generates warnings in Xcode 7.3.1 (iOS framework build)

    :1:1: warning: umbrella header for module 'CocoaMarkdown' does not include header 'Ono.h' [-Wincomplete-umbrella]

    import "Headers/CocoaMarkdown.h"

    ^ :1:1: warning: umbrella header for module 'CocoaMarkdown' does not include header 'ONOXMLDocument.h' [-Wincomplete-umbrella] 2 warnings generated. 2 warnings generated.

    opened by blackfog 3
  • Some README code doesn't compile

    Some README code doesn't compile

    Hello.

    The README includes several references to code that sends nil for the options. But that parameter is a required one so it doesn't compile:

    let document = CMDocument(contentsOfFile: path, options: nil)

    Maybe I'm missing something?

    opened by ghost 3
  • Convert Markdown from String not File

    Convert Markdown from String not File

    Hi,

    Is there a way to convert Markdown directly from String instead of creating CMDocument? The reason is I have the markdown text in a String, so I need to save to local directory and then convert to CMDocument which is an expensive task. CMDocument can only take contentOfFile.

    Thank you Tal

    opened by talezion 3
  • Custom hooks?

    Custom hooks?

    I am using CocoaMarkdown for my open source macOS reddit client, and I was wondering if there is a way to create a custom hook? for example, if I want cocoaMarkdown to catch /r/subreddit text and highlight it and make it a clickable link. Is that possible using this library?

    opened by JoeyBodnar 1
  • Use pod to directly reference the master branch code, running crash

    Use pod to directly reference the master branch code, running crash

    image Use pod to directly reference the master branch code, running crash image But there is no problem with the way of drag the .xcodeproj file. What is the difference between the two? @indragiek @jlj

    opened by SarielTang 1
  • Link by cocopods with duplicate symbol error

    Link by cocopods with duplicate symbol error

    • Pod version : 1.8.4
    • Xcode 11.2
    • iPhone device 12.4.2
    Podfile:
    pod "CocoaMarkdown", :git => 'https://github.com/indragiek/CocoaMarkdown.git'
    

    Link error:

    duplicate symbol '_main' in:
        /Users/chenyn/Library/Developer/Xcode/DerivedData/CaJian-gdqwwrpegxglvygdepdetmfjtucl/Build/Intermediates.noindex/CaJian.build/Debug-iphoneos/CaJian.build/Objects-normal/arm64/main.o
        /Users/chenyn/Library/Developer/Xcode/DerivedData/CaJian-gdqwwrpegxglvygdepdetmfjtucl/Build/Products/Debug-iphoneos/cmark/libcmark.a(main.o)
    ld: 1 duplicate symbol for architecture arm64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    

    Is this an error related to my environment or resulted from any incorrect operation?

    opened by lgb1234a 0
  • Failed to generate the attributed string for given text "">

    Failed to generate the attributed string for given text ""

    Below is the code to generate an AttributedString in iOS

    NSString * markdown = @"" CMDocument *doc = [[CMDocument alloc] initWithData: [markdown dataUsingEncoding: NSUTF8StringEncoding] options: 0]; CMTextAttributes *markdownAttributes = [[CMTextAttributes alloc]init]; CMAttributedStringRenderer *renderer = [[CMAttributedStringRenderer alloc] initWithDocument: doc attributes: markdownAttributes]; return [render render];

    opened by srinivas-kurakula 0
Owner
Indragie Karunaratne
Indragie Karunaratne
Rendering Markdown text natively in SwiftUI.

MarkdownView MarkdownView is a Swift Package for rendering Markdown text natively in SwiftUI. Thanks to apple/swift-markdown, it can fully compliant w

LiYanan2004 13 Oct 22, 2022
AttributedText is a Swift µpackage that provides NSAttributedString rendering in SwiftUI by wrapping either an NSTextView or a UITextView depending on the platform.

AttributedText AttributedText is a Swift µpackage that provides NSAttributedString rendering in SwiftUI by wrapping either an NSTextView or a UITextVi

null 1 Jul 18, 2022
Notepad - A fully themeable iOS markdown editor with live syntax highlighting.

Notepad is just like any other UITextView, but you need to use the convenience initializer in order to use the themes. To create a new theme, copy one of the existing themes and edit the JSON.

Rudd Fawcett 802 Dec 31, 2022
Markdown parser for iOS

Marky Mark Marky Mark is a parser written in Swift that converts markdown into native views. The way it looks it highly customizable and the supported

M2mobi 254 Jun 11, 2021
Markdown syntax highlighter for iOS

Marklight Markdown syntax highlighter for iOS and macOS. Description Marklight is a drop in component to easily add realtime Markdown syntax highlight

Matteo Gavagnin 539 Dec 29, 2022
Rich Markdown editing control for iOS

MarkdownTextView Rich Markdown Editing for iOS MarkdownTextView is an iOS framework for adding rich Markdown editing capabilities. Support for Markdow

Indragie Karunaratne 676 Dec 7, 2022
Markdown parser for iOS

Marky Mark Marky Mark is a parser written in Swift that converts markdown into native views. The way it looks it highly customizable and the supported

M2mobi 262 Nov 23, 2021
Generate help centers for your iOS apps, with Markdown

Generate help centers for your iOS apps, with Markdown! All you need to do is wr

Peter Salz 6 Jan 15, 2022
A simple and customizable Markdown Parser for Swift

MarkdownKit MarkdownKit is a customizable and extensible Markdown parser for iOS and macOS. It supports many of the standard Markdown elements through

Bruno Oliveira 687 Dec 18, 2022
MarkdownView is a WKWebView based UI element, and internally use bootstrap, highlight.js, markdown-it.

MarkdownView is a WKWebView based UI element, and internally use bootstrap, highlight.js, markdown-it.

Keita Oouchi 1.8k Dec 21, 2022
Markdown in SwiftUI, and some other interesting components.

RoomTime RoomTime is a bundle of tools developed in my app RoomTime Lite. ( ?? RoomTime Lite is still in development) Features TextArea AutoWrap Markd

Chen SiWei 56 Dec 20, 2022
Converts Markdown files and strings into NSAttributedStrings with lots of customisation options.

SwiftyMarkdown 1.0 SwiftyMarkdown converts Markdown files and strings into NSAttributedStrings using sensible defaults and a Swift-style syntax. It us

Simon Fairbairn 1.5k Dec 22, 2022
Marky Mark is a parser written in Swift that converts markdown into native views.

Marky Mark is a parser written in Swift that converts markdown into native views. The way it looks it highly customizable and the supported markdown syntax is easy to extend.

M2mobi 287 Nov 29, 2022
Swift markdown library

Markdown ![Swift version](https://img.shields.io/badge/Swift-2.1 | 2.2-blue.svg) ![GitHub license](https://img.shields.io/badge/license-LGPL v3-green.

Crossroad Labs 79 Oct 9, 2022
A Pure Swift implementation of the markdown mark-up language

SmarkDown A pure Swift markdown implementation consistent with Gruber's 1.0.1 version. It is released under the BSD license so please feel free to use

Swift Studies 67 Jan 24, 2022
`resultBuilder` support for `swift-markdown`

SwiftMarkdownBuilder resultBuilder support for swift-markdown. The default way to build Markdown in swift-markdown is to use varargs initializers, e.g

DocZ 9 May 31, 2022
Leverages Apple's Swift-based Markdown parser to output NSAttributedString.

Markdownosaur ?? Markdownosaur uses Apple's excellent and relatively new Swift Markdown library to analyze a Markdown source, and then takes that anal

Christian Selig 232 Dec 20, 2022
AttributedString Markdown initializer with custom styling

AttributedString Markdown initializer with custom styling AttributedString in iOS 15 and macOS 12 comes with a Markdown initializer. But: There is no

Frank Rausch 41 Dec 19, 2022
An Objective-C framework for converting Markdown to HTML.

MMMarkdown MMMarkdown is an Objective-C framework for converting Markdown to HTML. It is compatible with OS X 10.7+, iOS 8.0+, tvOS, and watchOS. Unli

Matt Diephouse 1.2k Dec 14, 2022