├── Sources ├── MarkdownKitObjC │ ├── include │ │ ├── MarkdownKitObjC.h │ │ └── BaseTextStorage.h │ └── BaseTextStorage.m ├── libcmark │ ├── include │ │ ├── tasklist.h │ │ ├── autolink.h │ │ ├── tagfilter.h │ │ ├── cmark-gfm_version.h │ │ ├── strikethrough.h │ │ ├── table.h │ │ ├── iterator.h │ │ ├── registry.h │ │ ├── footnotes.h │ │ ├── references.h │ │ ├── plugin.h │ │ ├── cmark_ctype.h │ │ ├── html.h │ │ ├── utf8.h │ │ ├── inlines.h │ │ ├── map.h │ │ ├── ext_scanners.h │ │ ├── cmark-gfm_export.h │ │ ├── syntax_extension.h │ │ ├── cmark-gfm-extensions_export.h │ │ ├── config.h │ │ ├── houdini.h │ │ ├── render.h │ │ ├── cmark-gfm-core-extensions.h │ │ ├── parser.h │ │ ├── node.h │ │ ├── scanners.h │ │ ├── buffer.h │ │ └── chunk.h │ ├── src │ │ ├── linked_list.c │ │ ├── plugin.c │ │ ├── footnotes.c │ │ ├── references.c │ │ ├── cmark.c │ │ ├── registry.c │ │ ├── cmark_ctype.c │ │ ├── houdini_html_e.c │ │ ├── arena.c │ │ ├── map.c │ │ ├── houdini_href_e.c │ │ ├── houdini_html_u.c │ │ ├── iterator.c │ │ ├── syntax_extension.c │ │ ├── xml.c │ │ ├── plaintext.c │ │ ├── man.c │ │ ├── render.c │ │ └── buffer.c │ └── extensions │ │ ├── core-extensions.c │ │ ├── tagfilter.c │ │ ├── tasklist.c │ │ └── strikethrough.c └── MarkdownKit │ ├── Models │ ├── Nodes │ │ ├── ThematicBreak.swift │ │ ├── Image.swift │ │ ├── ListItem.swift │ │ ├── Heading.swift │ │ ├── Document.swift │ │ ├── Link.swift │ │ └── Node.swift │ ├── Style.swift │ ├── Location.swift │ ├── NodeList.swift │ └── Kind.swift │ ├── Extensions │ ├── String+cmark_strbuf.swift │ ├── UIFont+Traits.swift │ ├── String+Querying.swift │ └── NSString+Location.swift │ ├── Text │ ├── TextContainer.swift │ ├── AttributedStringKeys.swift │ ├── LayoutManager.swift │ ├── TextStorage.swift │ └── TextStorage+Manipulation.swift │ ├── Renderer │ └── HTMLRenderer.swift │ ├── Parser │ ├── Extensions.swift │ └── Parser.swift │ ├── Views │ └── TextView.swift │ └── Themes │ ├── DefaultTheme.swift │ └── Theme.swift ├── .gitignore ├── Example ├── Screenshot@2x.png ├── Resources │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ └── Base.lproj │ │ └── LaunchScreen.storyboard ├── Example.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Sources │ ├── AppDelegate.swift │ └── DemoViewController.swift └── Support │ └── Info.plist ├── .swiftlint.yml ├── README.md ├── Tests └── MarkdownKitTests │ ├── LocationTests.swift │ ├── TextStorageTests.swift │ ├── BlockTests.swift │ └── InlineTests.swift ├── .github └── workflows │ ├── swiftlint.yml │ └── test.yml ├── Package.swift └── LICENSE /Sources/MarkdownKitObjC/include/MarkdownKitObjC.h: -------------------------------------------------------------------------------- 1 | #include "BaseTextStorage.h" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | /.swiftpm 7 | -------------------------------------------------------------------------------- /Example/Screenshot@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soffes/MarkdownKit/HEAD/Example/Screenshot@2x.png -------------------------------------------------------------------------------- /Example/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - Example 3 | - Sources 4 | disabled_rules: 5 | - cyclomatic_complexity 6 | - function_body_length 7 | - opening_brace 8 | line_length: 120 9 | -------------------------------------------------------------------------------- /Sources/libcmark/include/tasklist.h: -------------------------------------------------------------------------------- 1 | #ifndef TASKLIST_H 2 | #define TASKLIST_H 3 | 4 | #include "cmark-gfm-core-extensions.h" 5 | 6 | cmark_syntax_extension *create_tasklist_extension(void); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Models/Nodes/ThematicBreak.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class ThematicBreak: Link { 4 | public override var delimiters: [NSRange]? { 5 | range.flatMap { [$0] } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Sources/libcmark/include/autolink.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_GFM_AUTOLINK_H 2 | #define CMARK_GFM_AUTOLINK_H 3 | 4 | #include "cmark-gfm-core-extensions.h" 5 | 6 | cmark_syntax_extension *create_autolink_extension(void); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /Sources/libcmark/include/tagfilter.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_GFM_TAGFILTER_H 2 | #define CMARK_GFM_TAGFILTER_H 3 | 4 | #include "cmark-gfm-core-extensions.h" 5 | 6 | cmark_syntax_extension *create_tagfilter_extension(void); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sources/libcmark/include/cmark-gfm_version.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_GFM_VERSION_H 2 | #define CMARK_GFM_VERSION_H 3 | 4 | #define CMARK_GFM_VERSION ((0 << 24) | (28 << 16) | (3 << 8) | 20) 5 | #define CMARK_GFM_VERSION_STRING "0.28.3.gfm.20" 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /Sources/libcmark/include/strikethrough.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_GFM_STRIKETHROUGH_H 2 | #define CMARK_GFM_STRIKETHROUGH_H 3 | 4 | #include "cmark-gfm-core-extensions.h" 5 | 6 | extern cmark_node_type CMARK_NODE_STRIKETHROUGH; 7 | cmark_syntax_extension *create_strikethrough_extension(void); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /Sources/libcmark/include/table.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_GFM_TABLE_H 2 | #define CMARK_GFM_TABLE_H 3 | 4 | #include "cmark-gfm-core-extensions.h" 5 | 6 | 7 | extern cmark_node_type CMARK_NODE_TABLE, CMARK_NODE_TABLE_ROW, 8 | CMARK_NODE_TABLE_CELL; 9 | 10 | cmark_syntax_extension *create_table_extension(void); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MarkdownKit 2 | 3 | Swift wrapper for [cmark](https://github.com/commonmark/cmark). This is using an adapted version of [cmark-gfm](https://github.com/github/cmark-gfm). 4 | 5 | This was abstracted from one of the many unreleased rewrites of [Whiskey](https://usewhiskey.com). 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Extensions/String+cmark_strbuf.swift: -------------------------------------------------------------------------------- 1 | import libcmark 2 | 3 | extension String { 4 | init?(_ buffer: cmark_strbuf) { 5 | var buffer = buffer 6 | guard let string = cmark_strbuf_cstr(&buffer) else { 7 | return nil 8 | } 9 | 10 | self.init(utf8String: string) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Models/Nodes/Image.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class Image: Link { 4 | public override var delimiters: [NSRange]? { 5 | guard var delimiters = super.delimiters else { 6 | return nil 7 | } 8 | 9 | delimiters[0].length = 2 10 | return delimiters 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Models/Style.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public struct Style { 4 | public var range: NSRange 5 | public var attributes: [NSAttributedString.Key: Any] 6 | 7 | public init(range: NSRange, attributes: [NSAttributedString.Key: Any]) { 8 | self.range = range 9 | self.attributes = attributes 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Models/Nodes/ListItem.swift: -------------------------------------------------------------------------------- 1 | import libcmark 2 | 3 | public final class ListItem: Node { 4 | 5 | // MARK: - Properties 6 | 7 | public var isTask: Bool { 8 | return String(cString: cmark_node_get_type_string(node)) == "tasklist" 9 | } 10 | 11 | public var isCompleted: Bool { 12 | cmark_gfm_extensions_get_tasklist_item_checked(node) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/MarkdownKitTests/LocationTests.swift: -------------------------------------------------------------------------------- 1 | import MarkdownKit 2 | import XCTest 3 | 4 | final class LocationsTests: XCTestCase { 5 | func testRange() { 6 | let markdown = "Hello **world**.\n" 7 | let document = Parser.parse(markdown)! 8 | XCTAssertEqual(markdown, document.content) 9 | 10 | let paragraph = document.children.first! 11 | XCTAssertEqual(markdown, paragraph.content) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Text/TextContainer.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public final class TextContainer: NSTextContainer { 4 | 5 | // MARK: - Initializers 6 | 7 | override init(size: CGSize) { 8 | super.init(size: size) 9 | lineFragmentPadding = 0 10 | } 11 | 12 | @available(*, unavailable) 13 | required init(coder: NSCoder) { 14 | fatalError("init(coder:) has not been implemented") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/MarkdownKitObjC/include/BaseTextStorage.h: -------------------------------------------------------------------------------- 1 | @import Darwin.TargetConditionals; 2 | 3 | #if TARGET_OS_IPHONE 4 | @import UIKit; 5 | #else 6 | @import AppKit; 7 | #endif 8 | 9 | NS_ASSUME_NONNULL_BEGIN 10 | 11 | /// Concrete text storage intended to be subclassed. 12 | @interface BaseTextStorage : NSTextStorage 13 | @end 14 | 15 | @interface BaseTextStorage () 16 | @property (nonatomic) NSMutableAttributedString *storage; 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /Tests/MarkdownKitTests/TextStorageTests.swift: -------------------------------------------------------------------------------- 1 | import MarkdownKit 2 | import XCTest 3 | 4 | final class TextStorageTests: XCTestCase { 5 | func testRange() { 6 | let markdown = """ 7 | 8 | 9 | """ 10 | let storage = TextStorage() 11 | storage.replaceCharacters(in: NSRange(location: 0, length: 0), with: markdown) 12 | XCTAssertNoThrow(storage.parseIfNeeded()) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/libcmark/include/iterator.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_ITERATOR_H 2 | #define CMARK_ITERATOR_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "cmark-gfm.h" 9 | 10 | typedef struct { 11 | cmark_event_type ev_type; 12 | cmark_node *node; 13 | } cmark_iter_state; 14 | 15 | struct cmark_iter { 16 | cmark_mem *mem; 17 | cmark_node *root; 18 | cmark_iter_state cur; 19 | cmark_iter_state next; 20 | }; 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Models/Location.swift: -------------------------------------------------------------------------------- 1 | public struct Location { 2 | public var line: Int 3 | public var column: Int 4 | 5 | public init?(line: Int, column: Int) { 6 | guard line > 0 && column > 0 else { 7 | return nil 8 | } 9 | 10 | self.line = line 11 | self.column = column 12 | } 13 | } 14 | 15 | extension Location: CustomStringConvertible { 16 | public var description: String { 17 | return "\(line):\(column)" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/libcmark/include/registry.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_REGISTRY_H 2 | #define CMARK_REGISTRY_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "cmark-gfm.h" 9 | #include "plugin.h" 10 | 11 | CMARK_GFM_EXPORT 12 | void cmark_register_plugin(cmark_plugin_init_func reg_fn); 13 | 14 | CMARK_GFM_EXPORT 15 | void cmark_release_plugins(void); 16 | 17 | CMARK_GFM_EXPORT 18 | cmark_llist *cmark_list_syntax_extensions(cmark_mem *mem); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /.github/workflows/swiftlint.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | paths: 4 | - '.github/workflows/swiftlint.yml' 5 | - '.swiftlint.yml' 6 | - '**/*.swift' 7 | push: 8 | branches: 9 | - main 10 | 11 | name: SwiftLint 12 | 13 | jobs: 14 | Lint: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: SwiftLint 19 | uses: docker://norionomura/swiftlint:0.39.1 20 | with: 21 | args: swiftlint --strict --reporter github-actions-logging 22 | -------------------------------------------------------------------------------- /Sources/libcmark/include/footnotes.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_FOOTNOTES_H 2 | #define CMARK_FOOTNOTES_H 3 | 4 | #include "map.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | struct cmark_footnote { 11 | cmark_map_entry entry; 12 | cmark_node *node; 13 | unsigned int ix; 14 | }; 15 | 16 | typedef struct cmark_footnote cmark_footnote; 17 | 18 | void cmark_footnote_create(cmark_map *map, cmark_node *node); 19 | cmark_map *cmark_footnote_map_new(cmark_mem *mem); 20 | 21 | #ifdef __cplusplus 22 | } 23 | #endif 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Renderer/HTMLRenderer.swift: -------------------------------------------------------------------------------- 1 | import libcmark 2 | 3 | public final class HTMLRenderer { 4 | public enum Error: Swift.Error { 5 | case unknown 6 | } 7 | 8 | public static func render(_ document: Document) throws -> String { 9 | var output: UnsafeMutablePointer? 10 | 11 | Extensions.allList { list in 12 | output = cmark_render_html(document.node, CMARK_OPT_UNSAFE, list) 13 | } 14 | 15 | guard let string = output else { 16 | throw Error.unknown 17 | } 18 | 19 | return String(cString: string) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/libcmark/include/references.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_REFERENCES_H 2 | #define CMARK_REFERENCES_H 3 | 4 | #include "map.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | struct cmark_reference { 11 | cmark_map_entry entry; 12 | cmark_chunk url; 13 | cmark_chunk title; 14 | }; 15 | 16 | typedef struct cmark_reference cmark_reference; 17 | 18 | void cmark_reference_create(cmark_map *map, cmark_chunk *label, 19 | cmark_chunk *url, cmark_chunk *title); 20 | cmark_map *cmark_reference_map_new(cmark_mem *mem); 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /Example/Sources/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | final class AppDelegate: UIResponder { 5 | var window: UIWindow? 6 | } 7 | 8 | extension AppDelegate: UIApplicationDelegate { 9 | func application(_ application: UIApplication, 10 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool 11 | { 12 | let window = UIWindow() 13 | let viewController = DemoViewController() 14 | window.rootViewController = viewController 15 | window.makeKeyAndVisible() 16 | self.window = window 17 | 18 | return true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/libcmark/include/plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_PLUGIN_H 2 | #define CMARK_PLUGIN_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "cmark-gfm.h" 9 | #include "cmark-gfm-extension_api.h" 10 | 11 | /** 12 | * cmark_plugin: 13 | * 14 | * A plugin structure, which should be filled by plugin's 15 | * init functions. 16 | */ 17 | struct cmark_plugin { 18 | cmark_llist *syntax_extensions; 19 | }; 20 | 21 | cmark_llist * 22 | cmark_plugin_steal_syntax_extensions(cmark_plugin *plugin); 23 | 24 | cmark_plugin * 25 | cmark_plugin_new(void); 26 | 27 | void 28 | cmark_plugin_free(cmark_plugin *plugin); 29 | 30 | #ifdef __cplusplus 31 | } 32 | #endif 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Models/Nodes/Heading.swift: -------------------------------------------------------------------------------- 1 | import libcmark 2 | 3 | public final class Heading: Node { 4 | 5 | // MARK: - Types 6 | 7 | public enum Level: Int32 { 8 | case one = 1 9 | case two = 2 10 | case three = 3 11 | case four = 4 12 | case five = 5 13 | case six = 6 14 | } 15 | 16 | // MARK: - Properties 17 | 18 | public var level: Level { 19 | get { 20 | let level = cmark_node_get_heading_level(node) 21 | return Level(rawValue: level) ?? .one 22 | } 23 | 24 | set { 25 | cmark_node_set_heading_level(node, newValue.rawValue) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/libcmark/include/cmark_ctype.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_CMARK_CTYPE_H 2 | #define CMARK_CMARK_CTYPE_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "cmark-gfm_export.h" 9 | 10 | /** Locale-independent versions of functions from ctype.h. 11 | * We want cmark to behave the same no matter what the system locale. 12 | */ 13 | 14 | CMARK_GFM_EXPORT 15 | int cmark_isspace(char c); 16 | 17 | CMARK_GFM_EXPORT 18 | int cmark_ispunct(char c); 19 | 20 | CMARK_GFM_EXPORT 21 | int cmark_isalnum(char c); 22 | 23 | CMARK_GFM_EXPORT 24 | int cmark_isdigit(char c); 25 | 26 | CMARK_GFM_EXPORT 27 | int cmark_isalpha(char c); 28 | 29 | #ifdef __cplusplus 30 | } 31 | #endif 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Text/AttributedStringKeys.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension NSAttributedString.Key { 4 | /// `UIFontDescriptor.SymbolicTraits` to use for the given range. Prefer this over customizing the font so sizes 5 | /// and font traits can cascade. 6 | public static let fontTraits = NSAttributedString.Key("FontTraits") 7 | 8 | /// Customize the color of the `thematicBreak` line. The value should be a `UIColor`. 9 | public static let thematicBreakColor = NSAttributedString.Key("ThematicBreakColor") 10 | 11 | /// Customize the thickness of the `thematicBreak` line. The value should be a `CGFloat`. 12 | public static let thematicBreakThickness = NSAttributedString.Key("ThematicBreakThickness") 13 | } 14 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Extensions/UIFont+Traits.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UIFont { 4 | func addingTraits(_ additionalTraits: UIFontDescriptor.SymbolicTraits) -> UIFont { 5 | var traits = fontDescriptor.symbolicTraits 6 | traits.insert(additionalTraits) 7 | 8 | guard let descriptor = fontDescriptor.withSymbolicTraits(traits) else { 9 | assertionFailure("Failed to create font with symbol traits.") 10 | return self 11 | } 12 | 13 | return UIFont(descriptor: descriptor, size: pointSize) 14 | } 15 | 16 | var hasBoldOrItalicTraits: Bool { 17 | let traits = fontDescriptor.symbolicTraits 18 | return traits.contains(.traitBold) || traits.contains(.traitItalic) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | paths-ignore: 4 | - 'README.md' 5 | push: 6 | branches: 7 | - main 8 | 9 | name: Test 10 | jobs: 11 | test: 12 | name: Units 13 | runs-on: macOS-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Units 18 | env: 19 | # https://github.com/actions/virtual-environments/blob/master/images/macos/macos-10.15-Readme.md#xcode 20 | XCODE_PATH: "/Applications/Xcode_11.5.app" 21 | run: | 22 | sudo xcode-select -s ${{ env.XCODE_PATH }} 23 | xcrun simctl boot "iPhone 11" 24 | set -o pipefail \ 25 | && xcodebuild test -scheme MarkdownKit -destination 'platform=iOS Simulator,OS=13.5,name=iPhone 11' \ 26 | | xcpretty 27 | -------------------------------------------------------------------------------- /Sources/libcmark/include/html.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_HTML_H 2 | #define CMARK_HTML_H 3 | 4 | #include "buffer.h" 5 | #include "node.h" 6 | 7 | CMARK_INLINE 8 | static void cmark_html_render_cr(cmark_strbuf *html) { 9 | if (html->size && html->ptr[html->size - 1] != '\n') 10 | cmark_strbuf_putc(html, '\n'); 11 | } 12 | 13 | #define BUFFER_SIZE 100 14 | 15 | CMARK_INLINE 16 | static void cmark_html_render_sourcepos(cmark_node *node, cmark_strbuf *html, int options) { 17 | char buffer[BUFFER_SIZE]; 18 | if (CMARK_OPT_SOURCEPOS & options) { 19 | snprintf(buffer, BUFFER_SIZE, " data-sourcepos=\"%d:%d-%d:%d\"", 20 | cmark_node_get_start_line(node), cmark_node_get_start_column(node), 21 | cmark_node_get_end_line(node), cmark_node_get_end_column(node)); 22 | cmark_strbuf_puts(html, buffer); 23 | } 24 | } 25 | 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | 3 | import PackageDescription 4 | 5 | let libcmarkAsmFilePaths = [ 6 | "src/case_fold_switch.inc", 7 | "src/entities.inc" 8 | ] 9 | 10 | let package = Package( 11 | name: "MarkdownKit", 12 | platforms: [.iOS(.v13)], 13 | products: [ 14 | .library(name: "MarkdownKit", targets: ["MarkdownKit"]) 15 | ], 16 | targets: [ 17 | .target( 18 | name: "libcmark", 19 | exclude: libcmarkAsmFilePaths, 20 | cSettings: libcmarkAsmFilePaths.map { CSetting.headerSearchPath($0) } 21 | ), 22 | .target(name: "MarkdownKitObjC"), 23 | .target(name: "MarkdownKit", dependencies: ["libcmark", "MarkdownKitObjC"]), 24 | .testTarget(name: "MarkdownKitTests", dependencies: ["MarkdownKit"]) 25 | ], 26 | swiftLanguageVersions: [.v5] 27 | ) 28 | -------------------------------------------------------------------------------- /Sources/libcmark/include/utf8.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_UTF8_H 2 | #define CMARK_UTF8_H 3 | 4 | #include 5 | #include "buffer.h" 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | CMARK_GFM_EXPORT 12 | void cmark_utf8proc_case_fold(cmark_strbuf *dest, const uint8_t *str, 13 | bufsize_t len); 14 | 15 | CMARK_GFM_EXPORT 16 | void cmark_utf8proc_encode_char(int32_t uc, cmark_strbuf *buf); 17 | 18 | CMARK_GFM_EXPORT 19 | int cmark_utf8proc_iterate(const uint8_t *str, bufsize_t str_len, int32_t *dst); 20 | 21 | CMARK_GFM_EXPORT 22 | void cmark_utf8proc_check(cmark_strbuf *dest, const uint8_t *line, 23 | bufsize_t size); 24 | 25 | CMARK_GFM_EXPORT 26 | int cmark_utf8proc_is_space(int32_t uc); 27 | 28 | CMARK_GFM_EXPORT 29 | int cmark_utf8proc_is_punctuation(int32_t uc); 30 | 31 | #ifdef __cplusplus 32 | } 33 | #endif 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /Sources/libcmark/include/inlines.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_INLINES_H 2 | #define CMARK_INLINES_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "references.h" 9 | 10 | cmark_chunk cmark_clean_url(cmark_mem *mem, cmark_chunk *url); 11 | cmark_chunk cmark_clean_title(cmark_mem *mem, cmark_chunk *title); 12 | 13 | CMARK_GFM_EXPORT 14 | void cmark_parse_inlines(cmark_parser *parser, 15 | cmark_node *parent, 16 | cmark_map *refmap, 17 | int options); 18 | 19 | bufsize_t cmark_parse_reference_inline(cmark_mem *mem, cmark_chunk *input, 20 | cmark_map *refmap); 21 | 22 | void cmark_inlines_add_special_character(unsigned char c, bool emphasis); 23 | void cmark_inlines_remove_special_character(unsigned char c, bool emphasis); 24 | 25 | #ifdef __cplusplus 26 | } 27 | #endif 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /Sources/libcmark/src/linked_list.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cmark-gfm.h" 4 | 5 | cmark_llist *cmark_llist_append(cmark_mem *mem, cmark_llist *head, void *data) { 6 | cmark_llist *tmp; 7 | cmark_llist *new_node = (cmark_llist *) mem->calloc(1, sizeof(cmark_llist)); 8 | 9 | new_node->data = data; 10 | new_node->next = NULL; 11 | 12 | if (!head) 13 | return new_node; 14 | 15 | for (tmp = head; tmp->next; tmp=tmp->next); 16 | 17 | tmp->next = new_node; 18 | 19 | return head; 20 | } 21 | 22 | void cmark_llist_free_full(cmark_mem *mem, cmark_llist *head, cmark_free_func free_func) { 23 | cmark_llist *tmp, *prev; 24 | 25 | for (tmp = head; tmp;) { 26 | if (free_func) 27 | free_func(mem, tmp->data); 28 | 29 | prev = tmp; 30 | tmp = tmp->next; 31 | mem->free(prev); 32 | } 33 | } 34 | 35 | void cmark_llist_free(cmark_mem *mem, cmark_llist *head) { 36 | cmark_llist_free_full(mem, head, NULL); 37 | } 38 | -------------------------------------------------------------------------------- /Sources/libcmark/include/map.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_MAP_H 2 | #define CMARK_MAP_H 3 | 4 | #include "chunk.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | struct cmark_map_entry { 11 | struct cmark_map_entry *next; 12 | unsigned char *label; 13 | unsigned int age; 14 | }; 15 | 16 | typedef struct cmark_map_entry cmark_map_entry; 17 | 18 | struct cmark_map; 19 | 20 | typedef void (*cmark_map_free_f)(struct cmark_map *, cmark_map_entry *); 21 | 22 | struct cmark_map { 23 | cmark_mem *mem; 24 | cmark_map_entry *refs; 25 | cmark_map_entry **sorted; 26 | unsigned int size; 27 | cmark_map_free_f free; 28 | }; 29 | 30 | typedef struct cmark_map cmark_map; 31 | 32 | unsigned char *normalize_map_label(cmark_mem *mem, cmark_chunk *ref); 33 | cmark_map *cmark_map_new(cmark_mem *mem, cmark_map_free_f free); 34 | void cmark_map_free(cmark_map *map); 35 | cmark_map_entry *cmark_map_lookup(cmark_map *map, cmark_chunk *label); 36 | 37 | #ifdef __cplusplus 38 | } 39 | #endif 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /Sources/libcmark/include/ext_scanners.h: -------------------------------------------------------------------------------- 1 | #include "chunk.h" 2 | #include "cmark-gfm.h" 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | bufsize_t _ext_scan_at(bufsize_t (*scanner)(const unsigned char *), 9 | unsigned char *ptr, int len, bufsize_t offset); 10 | bufsize_t _scan_table_start(const unsigned char *p); 11 | bufsize_t _scan_table_cell(const unsigned char *p); 12 | bufsize_t _scan_table_cell_end(const unsigned char *p); 13 | bufsize_t _scan_table_row_end(const unsigned char *p); 14 | bufsize_t _scan_tasklist(const unsigned char *p); 15 | 16 | #define scan_table_start(c, l, n) _ext_scan_at(&_scan_table_start, c, l, n) 17 | #define scan_table_cell(c, l, n) _ext_scan_at(&_scan_table_cell, c, l, n) 18 | #define scan_table_cell_end(c, l, n) _ext_scan_at(&_scan_table_cell_end, c, l, n) 19 | #define scan_table_row_end(c, l, n) _ext_scan_at(&_scan_table_row_end, c, l, n) 20 | #define scan_tasklist(c, l, n) _ext_scan_at(&_scan_tasklist, c, l, n) 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | -------------------------------------------------------------------------------- /Sources/libcmark/extensions/core-extensions.c: -------------------------------------------------------------------------------- 1 | #include "cmark-gfm-core-extensions.h" 2 | #include "autolink.h" 3 | #include "strikethrough.h" 4 | #include "table.h" 5 | #include "tagfilter.h" 6 | #include "tasklist.h" 7 | #include "registry.h" 8 | #include "plugin.h" 9 | 10 | static int core_extensions_registration(cmark_plugin *plugin) { 11 | cmark_plugin_register_syntax_extension(plugin, create_table_extension()); 12 | cmark_plugin_register_syntax_extension(plugin, 13 | create_strikethrough_extension()); 14 | cmark_plugin_register_syntax_extension(plugin, create_autolink_extension()); 15 | cmark_plugin_register_syntax_extension(plugin, create_tagfilter_extension()); 16 | cmark_plugin_register_syntax_extension(plugin, create_tasklist_extension()); 17 | return 1; 18 | } 19 | 20 | void cmark_gfm_core_extensions_ensure_registered(void) { 21 | static int registered = 0; 22 | 23 | if (!registered) { 24 | cmark_register_plugin(core_extensions_registration); 25 | registered = 1; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Extensions/String+Querying.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String { 4 | var length: Int { 5 | return (self as NSString).length 6 | } 7 | 8 | func wordRange(atIndex index: Int) -> NSRange { 9 | if index > length { 10 | return NSRange(location: length, length: 0) 11 | } 12 | 13 | let tagger = NSLinguisticTagger(tagSchemes: [.tokenType], options: 0) 14 | tagger.string = self 15 | 16 | var range = NSRange(location: 0, length: 0) 17 | let tag = tagger.tag(at: index, scheme: .tokenType, tokenRange: &range, sentenceRange: nil) 18 | 19 | if tag == .word { 20 | return range 21 | } 22 | 23 | return NSRange(location: NSNotFound, length: 0) 24 | } 25 | 26 | func word(atIndex index: Int) -> String { 27 | let range = wordRange(atIndex: index) 28 | 29 | if range.location == NSNotFound { 30 | return "" 31 | } 32 | 33 | return (self as NSString).substring(with: range) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Parser/Extensions.swift: -------------------------------------------------------------------------------- 1 | import libcmark 2 | 3 | struct Extensions { 4 | 5 | // MARK: - GitHub Extensions 6 | 7 | static let autoLinkExtension = create_autolink_extension() 8 | static let strikethroughExtension = create_strikethrough_extension() 9 | static let tableExtension = create_table_extension() 10 | static let taskListExtension = create_tasklist_extension() 11 | 12 | // MARK: - Properties 13 | 14 | static let all = [ 15 | autoLinkExtension, 16 | strikethroughExtension, 17 | tableExtension, 18 | taskListExtension 19 | ] 20 | 21 | // MARK: - Enumerating 22 | 23 | static func allList(_ closure: (UnsafeMutablePointer?) -> Void) { 24 | var list: UnsafeMutablePointer? 25 | let memory = cmark_get_default_mem_allocator() 26 | 27 | for var ext in all { 28 | list = cmark_llist_append(memory, list, &ext) 29 | } 30 | 31 | closure(list) 32 | 33 | cmark_llist_free(memory, list) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Models/Nodes/Document.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import libcmark 3 | 4 | public final class Document: Node { 5 | 6 | // MARK: - Properties 7 | 8 | /// The text content of the first child if it’s an `

` 9 | public var title: String? { 10 | guard let node = firstChild as? Heading, node.level == .one else { 11 | return nil 12 | } 13 | 14 | return node.content 15 | } 16 | 17 | private let _content: String 18 | 19 | // MARK: - Initializers 20 | 21 | init(_ node: UnsafeMutablePointer, content: String) { 22 | self._content = content 23 | super.init(node) 24 | } 25 | 26 | // MARK: - Node 27 | 28 | public override weak var document: Document? { 29 | return self 30 | } 31 | 32 | public override var content: String? { 33 | return _content 34 | } 35 | 36 | public override var range: NSRange { 37 | let string = _content as NSString 38 | return NSRange(location: 0, length: string.length) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sam Soffes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Sources/libcmark/src/plugin.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "plugin.h" 4 | 5 | extern cmark_mem CMARK_DEFAULT_MEM_ALLOCATOR; 6 | 7 | int cmark_plugin_register_syntax_extension(cmark_plugin * plugin, 8 | cmark_syntax_extension * extension) { 9 | plugin->syntax_extensions = cmark_llist_append(&CMARK_DEFAULT_MEM_ALLOCATOR, plugin->syntax_extensions, extension); 10 | return 1; 11 | } 12 | 13 | cmark_plugin * 14 | cmark_plugin_new(void) { 15 | cmark_plugin *res = (cmark_plugin *) CMARK_DEFAULT_MEM_ALLOCATOR.calloc(1, sizeof(cmark_plugin)); 16 | 17 | res->syntax_extensions = NULL; 18 | 19 | return res; 20 | } 21 | 22 | void 23 | cmark_plugin_free(cmark_plugin *plugin) { 24 | cmark_llist_free_full(&CMARK_DEFAULT_MEM_ALLOCATOR, 25 | plugin->syntax_extensions, 26 | (cmark_free_func) cmark_syntax_extension_free); 27 | CMARK_DEFAULT_MEM_ALLOCATOR.free(plugin); 28 | } 29 | 30 | cmark_llist * 31 | cmark_plugin_steal_syntax_extensions(cmark_plugin *plugin) { 32 | cmark_llist *res = plugin->syntax_extensions; 33 | 34 | plugin->syntax_extensions = NULL; 35 | return res; 36 | } 37 | -------------------------------------------------------------------------------- /Sources/libcmark/src/footnotes.c: -------------------------------------------------------------------------------- 1 | #include "cmark-gfm.h" 2 | #include "parser.h" 3 | #include "footnotes.h" 4 | #include "inlines.h" 5 | #include "chunk.h" 6 | 7 | static void footnote_free(cmark_map *map, cmark_map_entry *_ref) { 8 | cmark_footnote *ref = (cmark_footnote *)_ref; 9 | cmark_mem *mem = map->mem; 10 | if (ref != NULL) { 11 | mem->free(ref->entry.label); 12 | if (ref->node) 13 | cmark_node_free(ref->node); 14 | mem->free(ref); 15 | } 16 | } 17 | 18 | void cmark_footnote_create(cmark_map *map, cmark_node *node) { 19 | cmark_footnote *ref; 20 | unsigned char *reflabel = normalize_map_label(map->mem, &node->as.literal); 21 | 22 | /* empty footnote name, or composed from only whitespace */ 23 | if (reflabel == NULL) 24 | return; 25 | 26 | assert(map->sorted == NULL); 27 | 28 | ref = (cmark_footnote *)map->mem->calloc(1, sizeof(*ref)); 29 | ref->entry.label = reflabel; 30 | ref->node = node; 31 | ref->entry.age = map->size; 32 | ref->entry.next = map->refs; 33 | 34 | map->refs = (cmark_map_entry *)ref; 35 | map->size++; 36 | } 37 | 38 | cmark_map *cmark_footnote_map_new(cmark_mem *mem) { 39 | return cmark_map_new(mem, footnote_free); 40 | } 41 | -------------------------------------------------------------------------------- /Sources/libcmark/include/cmark-gfm_export.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef CMARK_GFM_EXPORT_H 3 | #define CMARK_GFM_EXPORT_H 4 | 5 | #ifdef CMARK_GFM_STATIC_DEFINE 6 | # define CMARK_GFM_EXPORT 7 | # define CMARK_GFM_NO_EXPORT 8 | #else 9 | # ifndef CMARK_GFM_EXPORT 10 | # ifdef libcmark_gfm_EXPORTS 11 | /* We are building this library */ 12 | # define CMARK_GFM_EXPORT __attribute__((visibility("default"))) 13 | # else 14 | /* We are using this library */ 15 | # define CMARK_GFM_EXPORT __attribute__((visibility("default"))) 16 | # endif 17 | # endif 18 | 19 | # ifndef CMARK_GFM_NO_EXPORT 20 | # define CMARK_GFM_NO_EXPORT __attribute__((visibility("hidden"))) 21 | # endif 22 | #endif 23 | 24 | #ifndef CMARK_GFM_DEPRECATED 25 | # define CMARK_GFM_DEPRECATED __attribute__ ((__deprecated__)) 26 | #endif 27 | 28 | #ifndef CMARK_GFM_DEPRECATED_EXPORT 29 | # define CMARK_GFM_DEPRECATED_EXPORT CMARK_GFM_EXPORT CMARK_GFM_DEPRECATED 30 | #endif 31 | 32 | #ifndef CMARK_GFM_DEPRECATED_NO_EXPORT 33 | # define CMARK_GFM_DEPRECATED_NO_EXPORT CMARK_GFM_NO_EXPORT CMARK_GFM_DEPRECATED 34 | #endif 35 | 36 | #if 0 /* DEFINE_NO_DEPRECATED */ 37 | # ifndef CMARK_GFM_NO_DEPRECATED 38 | # define CMARK_GFM_NO_DEPRECATED 39 | # endif 40 | #endif 41 | 42 | #endif /* CMARK_GFM_EXPORT_H */ 43 | -------------------------------------------------------------------------------- /Sources/libcmark/src/references.c: -------------------------------------------------------------------------------- 1 | #include "cmark-gfm.h" 2 | #include "parser.h" 3 | #include "references.h" 4 | #include "inlines.h" 5 | #include "chunk.h" 6 | 7 | static void reference_free(cmark_map *map, cmark_map_entry *_ref) { 8 | cmark_reference *ref = (cmark_reference *)_ref; 9 | cmark_mem *mem = map->mem; 10 | if (ref != NULL) { 11 | mem->free(ref->entry.label); 12 | cmark_chunk_free(mem, &ref->url); 13 | cmark_chunk_free(mem, &ref->title); 14 | mem->free(ref); 15 | } 16 | } 17 | 18 | void cmark_reference_create(cmark_map *map, cmark_chunk *label, 19 | cmark_chunk *url, cmark_chunk *title) { 20 | cmark_reference *ref; 21 | unsigned char *reflabel = normalize_map_label(map->mem, label); 22 | 23 | /* empty reference name, or composed from only whitespace */ 24 | if (reflabel == NULL) 25 | return; 26 | 27 | assert(map->sorted == NULL); 28 | 29 | ref = (cmark_reference *)map->mem->calloc(1, sizeof(*ref)); 30 | ref->entry.label = reflabel; 31 | ref->url = cmark_clean_url(map->mem, url); 32 | ref->title = cmark_clean_title(map->mem, title); 33 | ref->entry.age = map->size; 34 | ref->entry.next = map->refs; 35 | 36 | map->refs = (cmark_map_entry *)ref; 37 | map->size++; 38 | } 39 | 40 | cmark_map *cmark_reference_map_new(cmark_mem *mem) { 41 | return cmark_map_new(mem, reference_free); 42 | } 43 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Parser/Parser.swift: -------------------------------------------------------------------------------- 1 | import libcmark 2 | 3 | public struct Parser { 4 | 5 | // MARK: - Types 6 | 7 | public struct Option: OptionSet { 8 | public let rawValue: Int32 9 | 10 | public init(rawValue: Int32) { 11 | self.rawValue = rawValue 12 | } 13 | 14 | /// Normalize tree by consolidating adjacent text nodes. 15 | public static let normalize = Option(rawValue: CMARK_OPT_NORMALIZE) 16 | 17 | /// Validate UTF-8 in the input before parsing, replacing illegal sequences with the replacement character 18 | /// `U+FFFD`. 19 | public static let validateUTF8 = Option(rawValue: CMARK_OPT_VALIDATE_UTF8) 20 | 21 | /// Convert straight quotes to curly, --- to em dashes, -- to en dashes. 22 | public static let smart = Option(rawValue: CMARK_OPT_SMART) 23 | } 24 | 25 | // MARK: - Parsing 26 | 27 | public static func parse(_ string: String, options: Option = []) -> Document? { 28 | let parser = cmark_parser_new(options.rawValue) 29 | Extensions.all.forEach { cmark_parser_attach_syntax_extension(parser, $0) } 30 | 31 | string.withCString { 32 | let stringLength = Int(strlen($0)) 33 | cmark_parser_feed(parser, $0, stringLength) 34 | } 35 | 36 | let tree = cmark_parser_finish(parser) 37 | cmark_parser_free(parser) 38 | 39 | return tree.map { Document($0, content: string) } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/libcmark/extensions/tagfilter.c: -------------------------------------------------------------------------------- 1 | #include "tagfilter.h" 2 | #include 3 | #include 4 | 5 | static const char *blacklist[] = { 6 | "title", "textarea", "style", "xmp", "iframe", 7 | "noembed", "noframes", "script", "plaintext", NULL, 8 | }; 9 | 10 | static int is_tag(const unsigned char *tag_data, size_t tag_size, 11 | const char *tagname) { 12 | size_t i; 13 | 14 | if (tag_size < 3 || tag_data[0] != '<') 15 | return 0; 16 | 17 | i = 1; 18 | 19 | if (tag_data[i] == '/') { 20 | i++; 21 | } 22 | 23 | for (; i < tag_size; ++i, ++tagname) { 24 | if (*tagname == 0) 25 | break; 26 | 27 | if (tolower(tag_data[i]) != *tagname) 28 | return 0; 29 | } 30 | 31 | if (i == tag_size) 32 | return 0; 33 | 34 | if (cmark_isspace(tag_data[i]) || tag_data[i] == '>') 35 | return 1; 36 | 37 | if (tag_data[i] == '/' && tag_size >= i + 2 && tag_data[i + 1] == '>') 38 | return 1; 39 | 40 | return 0; 41 | } 42 | 43 | static int filter(cmark_syntax_extension *ext, const unsigned char *tag, 44 | size_t tag_len) { 45 | const char **it; 46 | 47 | for (it = blacklist; *it; ++it) { 48 | if (is_tag(tag, tag_len, *it)) { 49 | return 0; 50 | } 51 | } 52 | 53 | return 1; 54 | } 55 | 56 | cmark_syntax_extension *create_tagfilter_extension(void) { 57 | cmark_syntax_extension *ext = cmark_syntax_extension_new("tagfilter"); 58 | cmark_syntax_extension_set_html_filter_func(ext, filter); 59 | return ext; 60 | } 61 | -------------------------------------------------------------------------------- /Sources/libcmark/include/syntax_extension.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_SYNTAX_EXTENSION_H 2 | #define CMARK_SYNTAX_EXTENSION_H 3 | 4 | #include "cmark-gfm.h" 5 | #include "cmark-gfm-extension_api.h" 6 | #include "config.h" 7 | 8 | struct cmark_syntax_extension { 9 | cmark_match_block_func last_block_matches; 10 | cmark_open_block_func try_opening_block; 11 | cmark_match_inline_func match_inline; 12 | cmark_inline_from_delim_func insert_inline_from_delim; 13 | cmark_llist * special_inline_chars; 14 | char * name; 15 | void * priv; 16 | bool emphasis; 17 | cmark_free_func free_function; 18 | cmark_get_type_string_func get_type_string_func; 19 | cmark_can_contain_func can_contain_func; 20 | cmark_contains_inlines_func contains_inlines_func; 21 | cmark_common_render_func commonmark_render_func; 22 | cmark_common_render_func plaintext_render_func; 23 | cmark_common_render_func latex_render_func; 24 | cmark_xml_attr_func xml_attr_func; 25 | cmark_common_render_func man_render_func; 26 | cmark_html_render_func html_render_func; 27 | cmark_html_filter_func html_filter_func; 28 | cmark_postprocess_func postprocess_func; 29 | cmark_opaque_alloc_func opaque_alloc_func; 30 | cmark_opaque_free_func opaque_free_func; 31 | cmark_commonmark_escape_func commonmark_escape_func; 32 | }; 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /Sources/libcmark/include/cmark-gfm-extensions_export.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef CMARK_GFM_EXTENSIONS_EXPORT_H 3 | #define CMARK_GFM_EXTENSIONS_EXPORT_H 4 | 5 | #ifdef CMARK_GFM_EXTENSIONS_STATIC_DEFINE 6 | # define CMARK_GFM_EXTENSIONS_EXPORT 7 | # define CMARK_GFM_EXTENSIONS_NO_EXPORT 8 | #else 9 | # ifndef CMARK_GFM_EXTENSIONS_EXPORT 10 | # ifdef libcmark_gfm_extensions_EXPORTS 11 | /* We are building this library */ 12 | # define CMARK_GFM_EXTENSIONS_EXPORT __attribute__((visibility("default"))) 13 | # else 14 | /* We are using this library */ 15 | # define CMARK_GFM_EXTENSIONS_EXPORT __attribute__((visibility("default"))) 16 | # endif 17 | # endif 18 | 19 | # ifndef CMARK_GFM_EXTENSIONS_NO_EXPORT 20 | # define CMARK_GFM_EXTENSIONS_NO_EXPORT __attribute__((visibility("hidden"))) 21 | # endif 22 | #endif 23 | 24 | #ifndef CMARK_GFM_EXTENSIONS_DEPRECATED 25 | # define CMARK_GFM_EXTENSIONS_DEPRECATED __attribute__ ((__deprecated__)) 26 | #endif 27 | 28 | #ifndef CMARK_GFM_EXTENSIONS_DEPRECATED_EXPORT 29 | # define CMARK_GFM_EXTENSIONS_DEPRECATED_EXPORT CMARK_GFM_EXTENSIONS_EXPORT CMARK_GFM_EXTENSIONS_DEPRECATED 30 | #endif 31 | 32 | #ifndef CMARK_GFM_EXTENSIONS_DEPRECATED_NO_EXPORT 33 | # define CMARK_GFM_EXTENSIONS_DEPRECATED_NO_EXPORT CMARK_GFM_EXTENSIONS_NO_EXPORT CMARK_GFM_EXTENSIONS_DEPRECATED 34 | #endif 35 | 36 | #if 0 /* DEFINE_NO_DEPRECATED */ 37 | # ifndef CMARK_GFM_EXTENSIONS_NO_DEPRECATED 38 | # define CMARK_GFM_EXTENSIONS_NO_DEPRECATED 39 | # endif 40 | #endif 41 | 42 | #endif /* CMARK_GFM_EXTENSIONS_EXPORT_H */ 43 | -------------------------------------------------------------------------------- /Sources/libcmark/src/cmark.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "registry.h" 5 | #include "node.h" 6 | #include "houdini.h" 7 | #include "cmark-gfm.h" 8 | #include "buffer.h" 9 | 10 | cmark_node_type CMARK_NODE_LAST_BLOCK = CMARK_NODE_FOOTNOTE_DEFINITION; 11 | cmark_node_type CMARK_NODE_LAST_INLINE = CMARK_NODE_FOOTNOTE_REFERENCE; 12 | 13 | int cmark_version() { return CMARK_GFM_VERSION; } 14 | 15 | const char *cmark_version_string() { return CMARK_GFM_VERSION_STRING; } 16 | 17 | static void *xcalloc(size_t nmem, size_t size) { 18 | void *ptr = calloc(nmem, size); 19 | if (!ptr) { 20 | fprintf(stderr, "[cmark] calloc returned null pointer, aborting\n"); 21 | abort(); 22 | } 23 | return ptr; 24 | } 25 | 26 | static void *xrealloc(void *ptr, size_t size) { 27 | void *new_ptr = realloc(ptr, size); 28 | if (!new_ptr) { 29 | fprintf(stderr, "[cmark] realloc returned null pointer, aborting\n"); 30 | abort(); 31 | } 32 | return new_ptr; 33 | } 34 | 35 | static void xfree(void *ptr) { 36 | free(ptr); 37 | } 38 | 39 | cmark_mem CMARK_DEFAULT_MEM_ALLOCATOR = {xcalloc, xrealloc, xfree}; 40 | 41 | cmark_mem *cmark_get_default_mem_allocator() { 42 | return &CMARK_DEFAULT_MEM_ALLOCATOR; 43 | } 44 | 45 | char *cmark_markdown_to_html(const char *text, size_t len, int options) { 46 | cmark_node *doc; 47 | char *result; 48 | 49 | doc = cmark_parse_document(text, len, options); 50 | 51 | result = cmark_render_html(doc, options, NULL); 52 | cmark_node_free(doc); 53 | 54 | return result; 55 | } 56 | -------------------------------------------------------------------------------- /Example/Support/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Views/TextView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | /// Text view configured with custom Text Kit components 4 | open class TextView: UITextView { 5 | 6 | // MARK: - Properties 7 | 8 | public let customTextContainer = TextContainer() 9 | public let customLayoutManager = LayoutManager() 10 | public let customTextStorage = TextStorage() 11 | 12 | // MARK: - Initializers 13 | 14 | public init() { 15 | customLayoutManager.addTextContainer(customTextContainer) 16 | customTextStorage.addLayoutManager(customLayoutManager) 17 | 18 | super.init(frame: .zero, textContainer: customTextContainer) 19 | 20 | delegate = self 21 | smartDashesType = .no 22 | smartQuotesType = .no 23 | typingAttributes = customTextStorage.typingAttributes 24 | } 25 | 26 | @available(*, unavailable) 27 | public required init?(coder: NSCoder) { 28 | fatalError("init(coder:) has not been implemented") 29 | } 30 | 31 | // MARK: - UIView 32 | 33 | open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 34 | super.traitCollectionDidChange(previousTraitCollection) 35 | 36 | if traitCollection.preferredContentSizeCategory != previousTraitCollection?.preferredContentSizeCategory { 37 | customTextStorage.refreshTheme() 38 | } 39 | } 40 | 41 | // MARK: - UITextView 42 | 43 | open override var text: String! { 44 | didSet { 45 | customTextStorage.parseIfNeeded() 46 | } 47 | } 48 | } 49 | 50 | extension TextView: UITextViewDelegate { 51 | open func textViewDidChange(_ textView: UITextView) { 52 | customTextStorage.parseIfNeeded() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/libcmark/include/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_CONFIG_H 2 | #define CMARK_CONFIG_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #define HAVE_STDBOOL_H 9 | 10 | #ifdef HAVE_STDBOOL_H 11 | #include 12 | #elif !defined(__cplusplus) 13 | typedef char bool; 14 | #endif 15 | 16 | #define HAVE___BUILTIN_EXPECT 17 | 18 | #define HAVE___ATTRIBUTE__ 19 | 20 | #ifdef HAVE___ATTRIBUTE__ 21 | #define CMARK_ATTRIBUTE(list) __attribute__ (list) 22 | #else 23 | #define CMARK_ATTRIBUTE(list) 24 | #endif 25 | 26 | #ifndef CMARK_INLINE 27 | #if defined(_MSC_VER) && !defined(__cplusplus) 28 | #define CMARK_INLINE __inline 29 | #else 30 | #define CMARK_INLINE inline 31 | #endif 32 | #endif 33 | 34 | /* snprintf and vsnprintf fallbacks for MSVC before 2015, 35 | due to Valentin Milea http://stackoverflow.com/questions/2915672/ 36 | */ 37 | 38 | #if defined(_MSC_VER) && _MSC_VER < 1900 39 | 40 | #include 41 | #include 42 | 43 | #define snprintf c99_snprintf 44 | #define vsnprintf c99_vsnprintf 45 | 46 | CMARK_INLINE int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) 47 | { 48 | int count = -1; 49 | 50 | if (size != 0) 51 | count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); 52 | if (count == -1) 53 | count = _vscprintf(format, ap); 54 | 55 | return count; 56 | } 57 | 58 | CMARK_INLINE int c99_snprintf(char *outBuf, size_t size, const char *format, ...) 59 | { 60 | int count; 61 | va_list ap; 62 | 63 | va_start(ap, format); 64 | count = c99_vsnprintf(outBuf, size, format, ap); 65 | va_end(ap); 66 | 67 | return count; 68 | } 69 | 70 | #endif 71 | 72 | #ifdef __cplusplus 73 | } 74 | #endif 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /Example/Resources/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Sources/libcmark/include/houdini.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_HOUDINI_H 2 | #define CMARK_HOUDINI_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include "config.h" 10 | #include "buffer.h" 11 | 12 | #ifdef HAVE___BUILTIN_EXPECT 13 | #define likely(x) __builtin_expect((x), 1) 14 | #define unlikely(x) __builtin_expect((x), 0) 15 | #else 16 | #define likely(x) (x) 17 | #define unlikely(x) (x) 18 | #endif 19 | 20 | #ifdef HOUDINI_USE_LOCALE 21 | #define _isxdigit(c) isxdigit(c) 22 | #define _isdigit(c) isdigit(c) 23 | #else 24 | /* 25 | * Helper _isdigit methods -- do not trust the current locale 26 | * */ 27 | #define _isxdigit(c) (strchr("0123456789ABCDEFabcdef", (c)) != NULL) 28 | #define _isdigit(c) ((c) >= '0' && (c) <= '9') 29 | #endif 30 | 31 | #define HOUDINI_ESCAPED_SIZE(x) (((x)*12) / 10) 32 | #define HOUDINI_UNESCAPED_SIZE(x) (x) 33 | 34 | CMARK_GFM_EXPORT 35 | bufsize_t houdini_unescape_ent(cmark_strbuf *ob, const uint8_t *src, 36 | bufsize_t size); 37 | CMARK_GFM_EXPORT 38 | int houdini_escape_html(cmark_strbuf *ob, const uint8_t *src, 39 | bufsize_t size); 40 | CMARK_GFM_EXPORT 41 | int houdini_escape_html0(cmark_strbuf *ob, const uint8_t *src, 42 | bufsize_t size, int secure); 43 | CMARK_GFM_EXPORT 44 | int houdini_unescape_html(cmark_strbuf *ob, const uint8_t *src, 45 | bufsize_t size); 46 | CMARK_GFM_EXPORT 47 | void houdini_unescape_html_f(cmark_strbuf *ob, const uint8_t *src, 48 | bufsize_t size); 49 | CMARK_GFM_EXPORT 50 | int houdini_escape_href(cmark_strbuf *ob, const uint8_t *src, 51 | bufsize_t size); 52 | 53 | #ifdef __cplusplus 54 | } 55 | #endif 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Models/Nodes/Link.swift: -------------------------------------------------------------------------------- 1 | import libcmark 2 | import Foundation 3 | 4 | public class Link: Node { 5 | public var url: URL? { 6 | cmark_node_get_url(node).map(String.init).flatMap(URL.init) 7 | } 8 | 9 | public var urlRange: NSRange? { 10 | guard let childRange = firstChild?.range, let range = range, range != childRange, let url = url else { 11 | return nil 12 | } 13 | 14 | if isAutolink { 15 | return childRange 16 | } 17 | 18 | return NSRange(location: NSMaxRange(childRange) + 2, length: (url.absoluteString as NSString).length) 19 | } 20 | 21 | public var title: String? { 22 | cmark_node_get_title(node).map(String.init) 23 | } 24 | 25 | public var isAutolink: Bool { 26 | guard let content = firstChild?.content, let url = url else { return false } 27 | 28 | if content == url.absoluteString { 29 | return true 30 | } 31 | 32 | // Special case for mailto 33 | if url.scheme == "mailto" { 34 | return content == url.absoluteString.dropFirst(7) // mailto: 35 | } 36 | 37 | return false 38 | } 39 | 40 | public override var delimiters: [NSRange]? { 41 | guard let childRange = firstChild?.range, let range = range, range != childRange else { 42 | return nil 43 | } 44 | 45 | if isAutolink { 46 | return [ 47 | NSRange(location: range.location, length: 1), 48 | NSRange(location: NSMaxRange(range) - 1, length: 1) 49 | ] 50 | } 51 | 52 | return [ 53 | NSRange(location: range.location, length: 1), 54 | NSRange(location: NSMaxRange(childRange), length: 2), 55 | NSRange(location: NSMaxRange(range) - 1, length: 1) 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/libcmark/src/registry.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "config.h" 6 | #include "cmark-gfm.h" 7 | #include "syntax_extension.h" 8 | #include "registry.h" 9 | #include "plugin.h" 10 | 11 | extern cmark_mem CMARK_DEFAULT_MEM_ALLOCATOR; 12 | 13 | static cmark_llist *syntax_extensions = NULL; 14 | 15 | void cmark_register_plugin(cmark_plugin_init_func reg_fn) { 16 | cmark_plugin *plugin = cmark_plugin_new(); 17 | 18 | if (!reg_fn(plugin)) { 19 | cmark_plugin_free(plugin); 20 | return; 21 | } 22 | 23 | cmark_llist *syntax_extensions_list = cmark_plugin_steal_syntax_extensions(plugin), 24 | *it; 25 | 26 | for (it = syntax_extensions_list; it; it = it->next) { 27 | syntax_extensions = cmark_llist_append(&CMARK_DEFAULT_MEM_ALLOCATOR, syntax_extensions, it->data); 28 | } 29 | 30 | cmark_llist_free(&CMARK_DEFAULT_MEM_ALLOCATOR, syntax_extensions_list); 31 | cmark_plugin_free(plugin); 32 | } 33 | 34 | void cmark_release_plugins(void) { 35 | if (syntax_extensions) { 36 | cmark_llist_free_full( 37 | &CMARK_DEFAULT_MEM_ALLOCATOR, 38 | syntax_extensions, 39 | (cmark_free_func) cmark_syntax_extension_free); 40 | syntax_extensions = NULL; 41 | } 42 | } 43 | 44 | cmark_llist *cmark_list_syntax_extensions(cmark_mem *mem) { 45 | cmark_llist *it; 46 | cmark_llist *res = NULL; 47 | 48 | for (it = syntax_extensions; it; it = it->next) { 49 | res = cmark_llist_append(mem, res, it->data); 50 | } 51 | return res; 52 | } 53 | 54 | cmark_syntax_extension *cmark_find_syntax_extension(const char *name) { 55 | cmark_llist *tmp; 56 | 57 | for (tmp = syntax_extensions; tmp; tmp = tmp->next) { 58 | cmark_syntax_extension *ext = (cmark_syntax_extension *) tmp->data; 59 | if (!strcmp(ext->name, name)) 60 | return ext; 61 | } 62 | return NULL; 63 | } 64 | -------------------------------------------------------------------------------- /Sources/libcmark/src/cmark_ctype.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cmark_ctype.h" 4 | 5 | /** 1 = space, 2 = punct, 3 = digit, 4 = alpha, 0 = other 6 | */ 7 | static const uint8_t cmark_ctype_class[256] = { 8 | /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ 9 | /* 0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 10 | /* 1 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11 | /* 2 */ 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 12 | /* 3 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 13 | /* 4 */ 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 14 | /* 5 */ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, 15 | /* 6 */ 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 16 | /* 7 */ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 0, 17 | /* 8 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18 | /* 9 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19 | /* a */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20 | /* b */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21 | /* c */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22 | /* d */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23 | /* e */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24 | /* f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 25 | 26 | /** 27 | * Returns 1 if c is a "whitespace" character as defined by the spec. 28 | */ 29 | int cmark_isspace(char c) { return cmark_ctype_class[(uint8_t)c] == 1; } 30 | 31 | /** 32 | * Returns 1 if c is an ascii punctuation character. 33 | */ 34 | int cmark_ispunct(char c) { return cmark_ctype_class[(uint8_t)c] == 2; } 35 | 36 | int cmark_isalnum(char c) { 37 | uint8_t result; 38 | result = cmark_ctype_class[(uint8_t)c]; 39 | return (result == 3 || result == 4); 40 | } 41 | 42 | int cmark_isdigit(char c) { return cmark_ctype_class[(uint8_t)c] == 3; } 43 | 44 | int cmark_isalpha(char c) { return cmark_ctype_class[(uint8_t)c] == 4; } 45 | -------------------------------------------------------------------------------- /Sources/libcmark/include/render.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_RENDER_H 2 | #define CMARK_RENDER_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include "buffer.h" 10 | #include "chunk.h" 11 | 12 | typedef enum { LITERAL, NORMAL, TITLE, URL } cmark_escaping; 13 | 14 | struct cmark_renderer { 15 | cmark_mem *mem; 16 | cmark_strbuf *buffer; 17 | cmark_strbuf *prefix; 18 | int column; 19 | int width; 20 | int need_cr; 21 | bufsize_t last_breakable; 22 | bool begin_line; 23 | bool begin_content; 24 | bool no_linebreaks; 25 | bool in_tight_list_item; 26 | void (*outc)(struct cmark_renderer *, cmark_node *, cmark_escaping, int32_t, unsigned char); 27 | void (*cr)(struct cmark_renderer *); 28 | void (*blankline)(struct cmark_renderer *); 29 | void (*out)(struct cmark_renderer *, cmark_node *, const char *, bool, cmark_escaping); 30 | unsigned int footnote_ix; 31 | }; 32 | 33 | typedef struct cmark_renderer cmark_renderer; 34 | 35 | struct cmark_html_renderer { 36 | cmark_strbuf *html; 37 | cmark_node *plain; 38 | cmark_llist *filter_extensions; 39 | unsigned int footnote_ix; 40 | unsigned int written_footnote_ix; 41 | void *opaque; 42 | }; 43 | 44 | typedef struct cmark_html_renderer cmark_html_renderer; 45 | 46 | void cmark_render_ascii(cmark_renderer *renderer, const char *s); 47 | 48 | void cmark_render_code_point(cmark_renderer *renderer, uint32_t c); 49 | 50 | char *cmark_render(cmark_mem *mem, cmark_node *root, int options, int width, 51 | void (*outc)(cmark_renderer *, cmark_node *, 52 | cmark_escaping, int32_t, 53 | unsigned char), 54 | int (*render_node)(cmark_renderer *renderer, 55 | cmark_node *node, 56 | cmark_event_type ev_type, int options)); 57 | 58 | #ifdef __cplusplus 59 | } 60 | #endif 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /Sources/libcmark/include/cmark-gfm-core-extensions.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_GFM_CORE_EXTENSIONS_H 2 | #define CMARK_GFM_CORE_EXTENSIONS_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "cmark-gfm-extension_api.h" 9 | #include "cmark-gfm-extensions_export.h" 10 | #include "config.h" // for bool 11 | #include 12 | 13 | CMARK_GFM_EXTENSIONS_EXPORT 14 | void cmark_gfm_core_extensions_ensure_registered(void); 15 | 16 | CMARK_GFM_EXTENSIONS_EXPORT 17 | uint16_t cmark_gfm_extensions_get_table_columns(cmark_node *node); 18 | 19 | /** Sets the number of columns for the table, returning 1 on success and 0 on error. 20 | */ 21 | CMARK_GFM_EXTENSIONS_EXPORT 22 | int cmark_gfm_extensions_set_table_columns(cmark_node *node, uint16_t n_columns); 23 | 24 | CMARK_GFM_EXTENSIONS_EXPORT 25 | uint8_t *cmark_gfm_extensions_get_table_alignments(cmark_node *node); 26 | 27 | /** Sets the alignments for the table, returning 1 on success and 0 on error. 28 | */ 29 | CMARK_GFM_EXTENSIONS_EXPORT 30 | int cmark_gfm_extensions_set_table_alignments(cmark_node *node, uint16_t ncols, uint8_t *alignments); 31 | 32 | CMARK_GFM_EXTENSIONS_EXPORT 33 | int cmark_gfm_extensions_get_table_row_is_header(cmark_node *node); 34 | 35 | /** Sets whether the node is a table header row, returning 1 on success and 0 on error. 36 | */ 37 | CMARK_GFM_EXTENSIONS_EXPORT 38 | int cmark_gfm_extensions_set_table_row_is_header(cmark_node *node, int is_header); 39 | 40 | CMARK_GFM_EXTENSIONS_EXPORT 41 | bool cmark_gfm_extensions_get_tasklist_item_checked(cmark_node *node); 42 | /* For backwards compatibility */ 43 | #define cmark_gfm_extensions_tasklist_is_checked cmark_gfm_extensions_get_tasklist_item_checked 44 | 45 | /** Sets whether a tasklist item is "checked" (completed), returning 1 on success and 0 on error. 46 | */ 47 | CMARK_GFM_EXTENSIONS_EXPORT 48 | int cmark_gfm_extensions_set_tasklist_item_checked(cmark_node *node, bool is_checked); 49 | 50 | #ifdef __cplusplus 51 | } 52 | #endif 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /Example/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Sources/libcmark/include/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_PARSER_H 2 | #define CMARK_PARSER_H 3 | 4 | #include 5 | #include "references.h" 6 | #include "node.h" 7 | #include "buffer.h" 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #define MAX_LINK_LABEL_LENGTH 1000 14 | 15 | struct cmark_parser { 16 | struct cmark_mem *mem; 17 | /* A hashtable of urls in the current document for cross-references */ 18 | struct cmark_map *refmap; 19 | /* The root node of the parser, always a CMARK_NODE_DOCUMENT */ 20 | struct cmark_node *root; 21 | /* The last open block after a line is fully processed */ 22 | struct cmark_node *current; 23 | /* See the documentation for cmark_parser_get_line_number() in cmark.h */ 24 | int line_number; 25 | /* See the documentation for cmark_parser_get_offset() in cmark.h */ 26 | bufsize_t offset; 27 | /* See the documentation for cmark_parser_get_column() in cmark.h */ 28 | bufsize_t column; 29 | /* See the documentation for cmark_parser_get_first_nonspace() in cmark.h */ 30 | bufsize_t first_nonspace; 31 | /* See the documentation for cmark_parser_get_first_nonspace_column() in cmark.h */ 32 | bufsize_t first_nonspace_column; 33 | bufsize_t thematic_break_kill_pos; 34 | /* See the documentation for cmark_parser_get_indent() in cmark.h */ 35 | int indent; 36 | /* See the documentation for cmark_parser_is_blank() in cmark.h */ 37 | bool blank; 38 | /* See the documentation for cmark_parser_has_partially_consumed_tab() in cmark.h */ 39 | bool partially_consumed_tab; 40 | /* Contains the currently processed line */ 41 | cmark_strbuf curline; 42 | /* See the documentation for cmark_parser_get_last_line_length() in cmark.h */ 43 | bufsize_t last_line_length; 44 | /* FIXME: not sure about the difference with curline */ 45 | cmark_strbuf linebuf; 46 | /* Options set by the user, see the Options section in cmark.h */ 47 | int options; 48 | bool last_buffer_ended_with_cr; 49 | cmark_llist *syntax_extensions; 50 | cmark_llist *inline_syntax_extensions; 51 | cmark_ispunct_func backslash_ispunct; 52 | }; 53 | 54 | #ifdef __cplusplus 55 | } 56 | #endif 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /Sources/MarkdownKitObjC/BaseTextStorage.m: -------------------------------------------------------------------------------- 1 | #import "BaseTextStorage.h" 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @implementation BaseTextStorage 6 | 7 | @synthesize storage = _storage; 8 | 9 | // MARK: - Initializers 10 | 11 | - (instancetype)init { 12 | if ((self = [super init])) { 13 | self.storage = [[NSMutableAttributedString alloc] init]; 14 | } 15 | return self; 16 | } 17 | 18 | 19 | // MARK: - NSTextStorage 20 | 21 | - (NSString *)string { 22 | return self.storage.string; 23 | } 24 | 25 | - (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(nullable NSRangePointer)effectiveRange { 26 | if (location >= self.length) { 27 | NSAssert(location < self.length, @"Tried to read attributed at out of bounds location %lu. Length: %lu", 28 | (unsigned long)location, (unsigned long)self.length); 29 | return @{}; 30 | } 31 | 32 | return [self.storage attributesAtIndex:location effectiveRange:effectiveRange]; 33 | } 34 | 35 | - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)aString { 36 | if (NSMaxRange(range) > self.length) { 37 | NSAssert(NSMaxRange(range) >= self.length, @"Tried to replace at out of bounds range %@. Length: %lu", 38 | NSStringFromRange(range), (unsigned long)self.length); 39 | return; 40 | } 41 | 42 | [self.storage replaceCharactersInRange:range withString:aString]; 43 | 44 | NSInteger change = aString.length - range.length; 45 | [self edited:NSTextStorageEditedCharacters range:range changeInLength:change]; 46 | } 47 | 48 | - (void)setAttributes:(nullable NSDictionary *)attributes range:(NSRange)range { 49 | if (NSMaxRange(range) > self.length) { 50 | NSAssert(NSMaxRange(range) >= self.length, @"Tried to set attributes at out of bounds range %@. Length: %lu", 51 | NSStringFromRange(range), (unsigned long)self.length); 52 | return; 53 | } 54 | 55 | [self beginEditing]; 56 | [self.storage setAttributes:attributes range:range]; 57 | [self edited:NSTextStorageEditedAttributes range:range changeInLength:0]; 58 | [self endEditing]; 59 | } 60 | 61 | @end 62 | 63 | NS_ASSUME_NONNULL_END 64 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Text/LayoutManager.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public final class LayoutManager: NSLayoutManager { 4 | 5 | // MARK: - NSLayoutManager 6 | 7 | public override func drawBackground(forGlyphRange range: NSRange, at origin: CGPoint) { 8 | super.drawBackground(forGlyphRange: range, at: origin) 9 | 10 | guard let textStorage = textStorage as? TextStorage, let document = textStorage.document else { 11 | return 12 | } 13 | 14 | // Draw thematic break decorations 15 | for block in document.children { 16 | guard block.kind == .thematicBreak, let range = block.range else { 17 | continue 18 | } 19 | 20 | drawThematicBreak(range: range, at: origin) 21 | } 22 | } 23 | 24 | // MARK: - Private 25 | 26 | private func drawThematicBreak(range: NSRange, at origin: CGPoint) { 27 | guard let textStorage = textStorage, let context = UIGraphicsGetCurrentContext() else { 28 | return 29 | } 30 | 31 | let attributes = textStorage.attributes(at: range.location, effectiveRange: nil) 32 | 33 | guard let font = attributes[.font] as? UIFont else { 34 | return 35 | } 36 | 37 | let color = attributes[.thematicBreakColor] as? UIColor ?? attributes[.foregroundColor] as? UIColor 38 | ?? .black 39 | 40 | // Find the remainder of the line 41 | let spacing: CGFloat = 4 42 | let used = lineFragmentUsedRect(forGlyphAt: range.location, effectiveRange: nil) 43 | var rect = lineFragmentRect(forGlyphAt: range.location, effectiveRange: nil) 44 | rect.size.width -= used.width + spacing 45 | rect.origin.x = used.maxX + spacing 46 | 47 | // Apply the line thickness 48 | let thickness = attributes[.thematicBreakThickness] as? CGFloat ?? 1 49 | rect.origin.y += rect.size.height - font.xHeight - floor(thickness / 2) 50 | rect.size.height = thickness 51 | 52 | // Offset for the layout manager’s origin 53 | rect.origin.x += origin.x 54 | rect.origin.y += origin.y 55 | 56 | // Draw 57 | context.saveGState() 58 | context.setFillColor(color.cgColor) 59 | context.fill(rect) 60 | context.restoreGState() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/libcmark/src/houdini_html_e.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "houdini.h" 6 | 7 | /** 8 | * According to the OWASP rules: 9 | * 10 | * & --> & 11 | * < --> < 12 | * > --> > 13 | * " --> " 14 | * ' --> ' ' is not recommended 15 | * / --> / forward slash is included as it helps end an HTML entity 16 | * 17 | */ 18 | static const char HTML_ESCAPE_TABLE[] = { 19 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4, 21 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30 | }; 31 | 32 | static const char *HTML_ESCAPES[] = {"", """, "&", "'", 33 | "/", "<", ">"}; 34 | 35 | int houdini_escape_html0(cmark_strbuf *ob, const uint8_t *src, bufsize_t size, 36 | int secure) { 37 | bufsize_t i = 0, org, esc = 0; 38 | 39 | while (i < size) { 40 | org = i; 41 | while (i < size && (esc = HTML_ESCAPE_TABLE[src[i]]) == 0) 42 | i++; 43 | 44 | if (i > org) 45 | cmark_strbuf_put(ob, src + org, i - org); 46 | 47 | /* escaping */ 48 | if (unlikely(i >= size)) 49 | break; 50 | 51 | /* The forward slash and single quote are only escaped in secure mode */ 52 | if ((src[i] == '/' || src[i] == '\'') && !secure) { 53 | cmark_strbuf_putc(ob, src[i]); 54 | } else { 55 | cmark_strbuf_puts(ob, HTML_ESCAPES[esc]); 56 | } 57 | 58 | i++; 59 | } 60 | 61 | return 1; 62 | } 63 | 64 | int houdini_escape_html(cmark_strbuf *ob, const uint8_t *src, bufsize_t size) { 65 | return houdini_escape_html0(ob, src, size, 1); 66 | } 67 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Models/NodeList.swift: -------------------------------------------------------------------------------- 1 | import libcmark 2 | 3 | public struct NodeList: Collection, Sequence { 4 | 5 | // MARK: - Types 6 | 7 | public typealias Iterator = NodeIterator 8 | public typealias Element = Node 9 | public typealias Index = Int 10 | 11 | // MARK: - Properties 12 | 13 | let firstNode: Node? 14 | 15 | // MARK: - Initializers 16 | 17 | init(_ firstNode: Node?) { 18 | self.firstNode = firstNode 19 | } 20 | 21 | // MARK: - Collection 22 | 23 | public var startIndex: Index { 24 | return 0 25 | } 26 | 27 | public var endIndex: Index { 28 | guard var node = firstNode else { 29 | return 0 30 | } 31 | 32 | var count = 1 33 | while let next = node.next() { 34 | node = next 35 | count = index(after: count) 36 | } 37 | 38 | return count 39 | } 40 | 41 | public subscript(index: Index) -> Iterator.Element { 42 | guard var element = firstNode else { 43 | fatalError("No element") 44 | } 45 | 46 | var position = 0 47 | while position < index { 48 | guard let next = element.next() else { 49 | fatalError("Index beyond bounds") 50 | } 51 | 52 | element = next 53 | position += 1 54 | } 55 | 56 | return element 57 | } 58 | 59 | public func index(after index: Index) -> Index { 60 | return index + 1 61 | } 62 | 63 | // MARK: - Sequence 64 | 65 | public func makeIterator() -> Iterator { 66 | return NodeIterator(firstNode: firstNode) 67 | } 68 | } 69 | 70 | public struct NodeIterator: IteratorProtocol { 71 | 72 | // MARK: - Properties 73 | 74 | private let firstNode: Node? 75 | private var currentNode: Node? 76 | 77 | // MARK: - Initializers 78 | 79 | init(firstNode: Node?) { 80 | self.firstNode = firstNode 81 | } 82 | 83 | // MARK: - IteratorProtocol 84 | 85 | public mutating func next() -> Node? { 86 | if currentNode == nil { 87 | currentNode = firstNode 88 | } else { 89 | currentNode = currentNode?.next() 90 | } 91 | 92 | return currentNode 93 | } 94 | } 95 | 96 | extension Node { 97 | fileprivate func next() -> Node? { 98 | guard let next = cmark_node_next(node) else { 99 | return nil 100 | } 101 | 102 | return Node.with(next, document: document) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Sources/libcmark/src/arena.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "cmark-gfm.h" 5 | #include "cmark-gfm-extension_api.h" 6 | 7 | static struct arena_chunk { 8 | size_t sz, used; 9 | uint8_t push_point; 10 | void *ptr; 11 | struct arena_chunk *prev; 12 | } *A = NULL; 13 | 14 | static struct arena_chunk *alloc_arena_chunk(size_t sz, struct arena_chunk *prev) { 15 | struct arena_chunk *c = (struct arena_chunk *)calloc(1, sizeof(*c)); 16 | if (!c) 17 | abort(); 18 | c->sz = sz; 19 | c->ptr = calloc(1, sz); 20 | if (!c->ptr) 21 | abort(); 22 | c->prev = prev; 23 | return c; 24 | } 25 | 26 | void cmark_arena_push(void) { 27 | if (!A) 28 | return; 29 | A->push_point = 1; 30 | A = alloc_arena_chunk(10240, A); 31 | } 32 | 33 | int cmark_arena_pop(void) { 34 | if (!A) 35 | return 0; 36 | while (A && !A->push_point) { 37 | free(A->ptr); 38 | struct arena_chunk *n = A->prev; 39 | free(A); 40 | A = n; 41 | } 42 | if (A) 43 | A->push_point = 0; 44 | return 1; 45 | } 46 | 47 | static void init_arena(void) { 48 | A = alloc_arena_chunk(4 * 1048576, NULL); 49 | } 50 | 51 | void cmark_arena_reset(void) { 52 | while (A) { 53 | free(A->ptr); 54 | struct arena_chunk *n = A->prev; 55 | free(A); 56 | A = n; 57 | } 58 | } 59 | 60 | static void *arena_calloc(size_t nmem, size_t size) { 61 | if (!A) 62 | init_arena(); 63 | 64 | size_t sz = nmem * size + sizeof(size_t); 65 | 66 | // Round allocation sizes to largest integer size to 67 | // ensure returned memory is correctly aligned 68 | const size_t align = sizeof(size_t) - 1; 69 | sz = (sz + align) & ~align; 70 | 71 | if (sz > A->sz) { 72 | A->prev = alloc_arena_chunk(sz, A->prev); 73 | return (uint8_t *) A->prev->ptr + sizeof(size_t); 74 | } 75 | if (sz > A->sz - A->used) { 76 | A = alloc_arena_chunk(A->sz + A->sz / 2, A); 77 | } 78 | void *ptr = (uint8_t *) A->ptr + A->used; 79 | A->used += sz; 80 | *((size_t *) ptr) = sz - sizeof(size_t); 81 | return (uint8_t *) ptr + sizeof(size_t); 82 | } 83 | 84 | static void *arena_realloc(void *ptr, size_t size) { 85 | if (!A) 86 | init_arena(); 87 | 88 | void *new_ptr = arena_calloc(1, size); 89 | if (ptr) 90 | memcpy(new_ptr, ptr, ((size_t *) ptr)[-1]); 91 | return new_ptr; 92 | } 93 | 94 | static void arena_free(void *ptr) { 95 | (void) ptr; 96 | /* no-op */ 97 | } 98 | 99 | cmark_mem CMARK_ARENA_MEM_ALLOCATOR = {arena_calloc, arena_realloc, arena_free}; 100 | 101 | cmark_mem *cmark_get_arena_mem_allocator() { 102 | return &CMARK_ARENA_MEM_ALLOCATOR; 103 | } 104 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Extensions/NSString+Location.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension NSString { 4 | public func range(start: Location, end: Location?) -> NSRange { 5 | let bounds = NSRange(location: 0, length: length) 6 | 7 | var startIndex = 0 8 | var endIndex = 0 9 | 10 | var foundStart = false 11 | var line = 1 12 | 13 | enumerateSubstrings(in: bounds, options: .byLines) { lineString, _, lineRange, stop in 14 | guard let lineString = lineString else { 15 | assertionFailure("Missing line string") 16 | return 17 | } 18 | 19 | // Start 20 | if !foundStart { 21 | // On the starting line 22 | if line == start.line { 23 | if start.column > 1 { 24 | // Add column information (-1 since it’s before) 25 | startIndex += self.convertUTF8ToUTF16Length(in: lineString, length: start.column - 1) 26 | } 27 | 28 | foundStart = true 29 | } else { 30 | // Add the entire line 31 | startIndex += lineRange.length 32 | } 33 | } 34 | 35 | // End 36 | if let end = end { 37 | if line == end.line { 38 | if end.column > 1 { 39 | // Add column information (not -1 since it’s after) 40 | endIndex += self.convertUTF8ToUTF16Length(in: lineString, length: end.column) 41 | } 42 | 43 | stop.pointee = true 44 | return 45 | } else { 46 | // Add the entire line 47 | endIndex += lineRange.length 48 | } 49 | } else if foundStart { 50 | endIndex = NSMaxRange(lineRange) 51 | stop.pointee = true 52 | return 53 | } 54 | 55 | line += 1 56 | } 57 | 58 | return NSRange(location: startIndex, length: endIndex - startIndex) 59 | } 60 | 61 | private func convertUTF8ToUTF16Length(in string: String, length: Int) -> Int { 62 | let utf8 = string.utf8 63 | let index = utf8.index(utf8.startIndex, offsetBy: min(length, utf8.count)) 64 | 65 | var cString = utf8[utf8.startIndex.. 9 | #include 10 | 11 | #include "cmark-gfm.h" 12 | #include "cmark-gfm-extension_api.h" 13 | #include "buffer.h" 14 | #include "chunk.h" 15 | 16 | typedef struct { 17 | cmark_list_type list_type; 18 | int marker_offset; 19 | int padding; 20 | int start; 21 | cmark_delim_type delimiter; 22 | unsigned char bullet_char; 23 | bool tight; 24 | bool checked; // For task list extension 25 | } cmark_list; 26 | 27 | typedef struct { 28 | cmark_chunk info; 29 | cmark_chunk literal; 30 | uint8_t fence_length; 31 | uint8_t fence_offset; 32 | unsigned char fence_char; 33 | int8_t fenced; 34 | } cmark_code; 35 | 36 | typedef struct { 37 | int level; 38 | bool setext; 39 | } cmark_heading; 40 | 41 | typedef struct { 42 | cmark_chunk url; 43 | cmark_chunk title; 44 | } cmark_link; 45 | 46 | typedef struct { 47 | cmark_chunk on_enter; 48 | cmark_chunk on_exit; 49 | } cmark_custom; 50 | 51 | enum cmark_node__internal_flags { 52 | CMARK_NODE__OPEN = (1 << 0), 53 | CMARK_NODE__LAST_LINE_BLANK = (1 << 1), 54 | CMARK_NODE__LAST_LINE_CHECKED = (1 << 2), 55 | }; 56 | 57 | struct cmark_node { 58 | cmark_strbuf content; 59 | 60 | struct cmark_node *next; 61 | struct cmark_node *prev; 62 | struct cmark_node *parent; 63 | struct cmark_node *first_child; 64 | struct cmark_node *last_child; 65 | 66 | void *user_data; 67 | cmark_free_func user_data_free_func; 68 | 69 | int start_line; 70 | int start_column; 71 | int end_line; 72 | int end_column; 73 | int internal_offset; 74 | uint16_t type; 75 | uint16_t flags; 76 | 77 | cmark_syntax_extension *extension; 78 | 79 | union { 80 | cmark_chunk literal; 81 | cmark_list list; 82 | cmark_code code; 83 | cmark_heading heading; 84 | cmark_link link; 85 | cmark_custom custom; 86 | int html_block_type; 87 | void *opaque; 88 | } as; 89 | }; 90 | 91 | static CMARK_INLINE cmark_mem *cmark_node_mem(cmark_node *node) { 92 | return node->content.mem; 93 | } 94 | CMARK_GFM_EXPORT int cmark_node_check(cmark_node *node, FILE *out); 95 | 96 | static CMARK_INLINE bool CMARK_NODE_TYPE_BLOCK_P(cmark_node_type node_type) { 97 | return (node_type & CMARK_NODE_TYPE_MASK) == CMARK_NODE_TYPE_BLOCK; 98 | } 99 | 100 | static CMARK_INLINE bool CMARK_NODE_BLOCK_P(cmark_node *node) { 101 | return node != NULL && CMARK_NODE_TYPE_BLOCK_P((cmark_node_type) node->type); 102 | } 103 | 104 | static CMARK_INLINE bool CMARK_NODE_TYPE_INLINE_P(cmark_node_type node_type) { 105 | return (node_type & CMARK_NODE_TYPE_MASK) == CMARK_NODE_TYPE_INLINE; 106 | } 107 | 108 | static CMARK_INLINE bool CMARK_NODE_INLINE_P(cmark_node *node) { 109 | return node != NULL && CMARK_NODE_TYPE_INLINE_P((cmark_node_type) node->type); 110 | } 111 | 112 | CMARK_GFM_EXPORT bool cmark_node_can_contain_type(cmark_node *node, cmark_node_type child_type); 113 | 114 | #ifdef __cplusplus 115 | } 116 | #endif 117 | 118 | #endif 119 | -------------------------------------------------------------------------------- /Sources/libcmark/include/scanners.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_SCANNERS_H 2 | #define CMARK_SCANNERS_H 3 | 4 | #include "cmark-gfm.h" 5 | #include "chunk.h" 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | bufsize_t _scan_at(bufsize_t (*scanner)(const unsigned char *), cmark_chunk *c, 12 | bufsize_t offset); 13 | bufsize_t _scan_scheme(const unsigned char *p); 14 | bufsize_t _scan_autolink_uri(const unsigned char *p); 15 | bufsize_t _scan_autolink_email(const unsigned char *p); 16 | bufsize_t _scan_html_tag(const unsigned char *p); 17 | bufsize_t _scan_liberal_html_tag(const unsigned char *p); 18 | bufsize_t _scan_html_block_start(const unsigned char *p); 19 | bufsize_t _scan_html_block_start_7(const unsigned char *p); 20 | bufsize_t _scan_html_block_end_1(const unsigned char *p); 21 | bufsize_t _scan_html_block_end_2(const unsigned char *p); 22 | bufsize_t _scan_html_block_end_3(const unsigned char *p); 23 | bufsize_t _scan_html_block_end_4(const unsigned char *p); 24 | bufsize_t _scan_html_block_end_5(const unsigned char *p); 25 | bufsize_t _scan_link_title(const unsigned char *p); 26 | bufsize_t _scan_spacechars(const unsigned char *p); 27 | bufsize_t _scan_atx_heading_start(const unsigned char *p); 28 | bufsize_t _scan_setext_heading_line(const unsigned char *p); 29 | bufsize_t _scan_open_code_fence(const unsigned char *p); 30 | bufsize_t _scan_close_code_fence(const unsigned char *p); 31 | bufsize_t _scan_entity(const unsigned char *p); 32 | bufsize_t _scan_dangerous_url(const unsigned char *p); 33 | bufsize_t _scan_footnote_definition(const unsigned char *p); 34 | 35 | #define scan_scheme(c, n) _scan_at(&_scan_scheme, c, n) 36 | #define scan_autolink_uri(c, n) _scan_at(&_scan_autolink_uri, c, n) 37 | #define scan_autolink_email(c, n) _scan_at(&_scan_autolink_email, c, n) 38 | #define scan_html_tag(c, n) _scan_at(&_scan_html_tag, c, n) 39 | #define scan_liberal_html_tag(c, n) _scan_at(&_scan_liberal_html_tag, c, n) 40 | #define scan_html_block_start(c, n) _scan_at(&_scan_html_block_start, c, n) 41 | #define scan_html_block_start_7(c, n) _scan_at(&_scan_html_block_start_7, c, n) 42 | #define scan_html_block_end_1(c, n) _scan_at(&_scan_html_block_end_1, c, n) 43 | #define scan_html_block_end_2(c, n) _scan_at(&_scan_html_block_end_2, c, n) 44 | #define scan_html_block_end_3(c, n) _scan_at(&_scan_html_block_end_3, c, n) 45 | #define scan_html_block_end_4(c, n) _scan_at(&_scan_html_block_end_4, c, n) 46 | #define scan_html_block_end_5(c, n) _scan_at(&_scan_html_block_end_5, c, n) 47 | #define scan_link_title(c, n) _scan_at(&_scan_link_title, c, n) 48 | #define scan_spacechars(c, n) _scan_at(&_scan_spacechars, c, n) 49 | #define scan_atx_heading_start(c, n) _scan_at(&_scan_atx_heading_start, c, n) 50 | #define scan_setext_heading_line(c, n) \ 51 | _scan_at(&_scan_setext_heading_line, c, n) 52 | #define scan_open_code_fence(c, n) _scan_at(&_scan_open_code_fence, c, n) 53 | #define scan_close_code_fence(c, n) _scan_at(&_scan_close_code_fence, c, n) 54 | #define scan_entity(c, n) _scan_at(&_scan_entity, c, n) 55 | #define scan_dangerous_url(c, n) _scan_at(&_scan_dangerous_url, c, n) 56 | #define scan_footnote_definition(c, n) _scan_at(&_scan_footnote_definition, c, n) 57 | 58 | #ifdef __cplusplus 59 | } 60 | #endif 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /Sources/libcmark/include/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_BUFFER_H 2 | #define CMARK_BUFFER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "config.h" 10 | #include "cmark-gfm.h" 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | typedef struct { 17 | cmark_mem *mem; 18 | unsigned char *ptr; 19 | bufsize_t asize, size; 20 | } cmark_strbuf; 21 | 22 | extern unsigned char cmark_strbuf__initbuf[]; 23 | 24 | #define CMARK_BUF_INIT(mem) \ 25 | { mem, cmark_strbuf__initbuf, 0, 0 } 26 | 27 | /** 28 | * Initialize a cmark_strbuf structure. 29 | * 30 | * For the cases where CMARK_BUF_INIT cannot be used to do static 31 | * initialization. 32 | */ 33 | CMARK_GFM_EXPORT 34 | void cmark_strbuf_init(cmark_mem *mem, cmark_strbuf *buf, 35 | bufsize_t initial_size); 36 | 37 | /** 38 | * Grow the buffer to hold at least `target_size` bytes. 39 | */ 40 | CMARK_GFM_EXPORT 41 | void cmark_strbuf_grow(cmark_strbuf *buf, bufsize_t target_size); 42 | 43 | CMARK_GFM_EXPORT 44 | void cmark_strbuf_free(cmark_strbuf *buf); 45 | 46 | CMARK_GFM_EXPORT 47 | void cmark_strbuf_swap(cmark_strbuf *buf_a, cmark_strbuf *buf_b); 48 | 49 | CMARK_GFM_EXPORT 50 | bufsize_t cmark_strbuf_len(const cmark_strbuf *buf); 51 | 52 | CMARK_GFM_EXPORT 53 | int cmark_strbuf_cmp(const cmark_strbuf *a, const cmark_strbuf *b); 54 | 55 | CMARK_GFM_EXPORT 56 | unsigned char *cmark_strbuf_detach(cmark_strbuf *buf); 57 | 58 | CMARK_GFM_EXPORT 59 | void cmark_strbuf_copy_cstr(char *data, bufsize_t datasize, 60 | const cmark_strbuf *buf); 61 | 62 | static CMARK_INLINE const char *cmark_strbuf_cstr(const cmark_strbuf *buf) { 63 | return (char *)buf->ptr; 64 | } 65 | 66 | #define cmark_strbuf_at(buf, n) ((buf)->ptr[n]) 67 | 68 | CMARK_GFM_EXPORT 69 | void cmark_strbuf_set(cmark_strbuf *buf, const unsigned char *data, 70 | bufsize_t len); 71 | 72 | CMARK_GFM_EXPORT 73 | void cmark_strbuf_sets(cmark_strbuf *buf, const char *string); 74 | 75 | CMARK_GFM_EXPORT 76 | void cmark_strbuf_putc(cmark_strbuf *buf, int c); 77 | 78 | CMARK_GFM_EXPORT 79 | void cmark_strbuf_put(cmark_strbuf *buf, const unsigned char *data, 80 | bufsize_t len); 81 | 82 | CMARK_GFM_EXPORT 83 | void cmark_strbuf_puts(cmark_strbuf *buf, const char *string); 84 | 85 | CMARK_GFM_EXPORT 86 | void cmark_strbuf_clear(cmark_strbuf *buf); 87 | 88 | CMARK_GFM_EXPORT 89 | bufsize_t cmark_strbuf_strchr(const cmark_strbuf *buf, int c, bufsize_t pos); 90 | 91 | CMARK_GFM_EXPORT 92 | bufsize_t cmark_strbuf_strrchr(const cmark_strbuf *buf, int c, bufsize_t pos); 93 | 94 | CMARK_GFM_EXPORT 95 | void cmark_strbuf_drop(cmark_strbuf *buf, bufsize_t n); 96 | 97 | CMARK_GFM_EXPORT 98 | void cmark_strbuf_truncate(cmark_strbuf *buf, bufsize_t len); 99 | 100 | CMARK_GFM_EXPORT 101 | void cmark_strbuf_rtrim(cmark_strbuf *buf); 102 | 103 | CMARK_GFM_EXPORT 104 | void cmark_strbuf_trim(cmark_strbuf *buf); 105 | 106 | CMARK_GFM_EXPORT 107 | void cmark_strbuf_normalize_whitespace(cmark_strbuf *s); 108 | 109 | CMARK_GFM_EXPORT 110 | void cmark_strbuf_unescape(cmark_strbuf *s); 111 | 112 | #ifdef __cplusplus 113 | } 114 | #endif 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /Sources/libcmark/src/map.c: -------------------------------------------------------------------------------- 1 | #include "map.h" 2 | #include "utf8.h" 3 | #include "parser.h" 4 | 5 | // normalize map label: collapse internal whitespace to single space, 6 | // remove leading/trailing whitespace, case fold 7 | // Return NULL if the label is actually empty (i.e. composed solely from 8 | // whitespace) 9 | unsigned char *normalize_map_label(cmark_mem *mem, cmark_chunk *ref) { 10 | cmark_strbuf normalized = CMARK_BUF_INIT(mem); 11 | unsigned char *result; 12 | 13 | if (ref == NULL) 14 | return NULL; 15 | 16 | if (ref->len == 0) 17 | return NULL; 18 | 19 | cmark_utf8proc_case_fold(&normalized, ref->data, ref->len); 20 | cmark_strbuf_trim(&normalized); 21 | cmark_strbuf_normalize_whitespace(&normalized); 22 | 23 | result = cmark_strbuf_detach(&normalized); 24 | assert(result); 25 | 26 | if (result[0] == '\0') { 27 | mem->free(result); 28 | return NULL; 29 | } 30 | 31 | return result; 32 | } 33 | 34 | static int 35 | labelcmp(const unsigned char *a, const unsigned char *b) { 36 | return strcmp((const char *)a, (const char *)b); 37 | } 38 | 39 | static int 40 | refcmp(const void *p1, const void *p2) { 41 | cmark_map_entry *r1 = *(cmark_map_entry **)p1; 42 | cmark_map_entry *r2 = *(cmark_map_entry **)p2; 43 | int res = labelcmp(r1->label, r2->label); 44 | return res ? res : ((int)r1->age - (int)r2->age); 45 | } 46 | 47 | static int 48 | refsearch(const void *label, const void *p2) { 49 | cmark_map_entry *ref = *(cmark_map_entry **)p2; 50 | return labelcmp((const unsigned char *)label, ref->label); 51 | } 52 | 53 | static void sort_map(cmark_map *map) { 54 | unsigned int i = 0, last = 0, size = map->size; 55 | cmark_map_entry *r = map->refs, **sorted = NULL; 56 | 57 | sorted = (cmark_map_entry **)map->mem->calloc(size, sizeof(cmark_map_entry *)); 58 | while (r) { 59 | sorted[i++] = r; 60 | r = r->next; 61 | } 62 | 63 | qsort(sorted, size, sizeof(cmark_map_entry *), refcmp); 64 | 65 | for (i = 1; i < size; i++) { 66 | if (labelcmp(sorted[i]->label, sorted[last]->label) != 0) 67 | sorted[++last] = sorted[i]; 68 | } 69 | 70 | map->sorted = sorted; 71 | map->size = last + 1; 72 | } 73 | 74 | cmark_map_entry *cmark_map_lookup(cmark_map *map, cmark_chunk *label) { 75 | cmark_map_entry **ref = NULL; 76 | unsigned char *norm; 77 | 78 | if (label->len < 1 || label->len > MAX_LINK_LABEL_LENGTH) 79 | return NULL; 80 | 81 | if (map == NULL || !map->size) 82 | return NULL; 83 | 84 | norm = normalize_map_label(map->mem, label); 85 | if (norm == NULL) 86 | return NULL; 87 | 88 | if (!map->sorted) 89 | sort_map(map); 90 | 91 | ref = (cmark_map_entry **)bsearch(norm, map->sorted, map->size, sizeof(cmark_map_entry *), refsearch); 92 | map->mem->free(norm); 93 | 94 | if (!ref) 95 | return NULL; 96 | 97 | return ref[0]; 98 | } 99 | 100 | void cmark_map_free(cmark_map *map) { 101 | cmark_map_entry *ref; 102 | 103 | if (map == NULL) 104 | return; 105 | 106 | ref = map->refs; 107 | while (ref) { 108 | cmark_map_entry *next = ref->next; 109 | map->free(map, ref); 110 | ref = next; 111 | } 112 | 113 | map->mem->free(map->sorted); 114 | map->mem->free(map); 115 | } 116 | 117 | cmark_map *cmark_map_new(cmark_mem *mem, cmark_map_free_f free) { 118 | cmark_map *map = (cmark_map *)mem->calloc(1, sizeof(cmark_map)); 119 | map->mem = mem; 120 | map->free = free; 121 | return map; 122 | } 123 | -------------------------------------------------------------------------------- /Sources/libcmark/src/houdini_href_e.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "houdini.h" 6 | 7 | /* 8 | * The following characters will not be escaped: 9 | * 10 | * -_.+!*'(),%#@?=;:/,+&$~ alphanum 11 | * 12 | * Note that this character set is the addition of: 13 | * 14 | * - The characters which are safe to be in an URL 15 | * - The characters which are *not* safe to be in 16 | * an URL because they are RESERVED characters. 17 | * 18 | * We assume (lazily) that any RESERVED char that 19 | * appears inside an URL is actually meant to 20 | * have its native function (i.e. as an URL 21 | * component/separator) and hence needs no escaping. 22 | * 23 | * There are two exceptions: the chacters & (amp) 24 | * and ' (single quote) do not appear in the table. 25 | * They are meant to appear in the URL as components, 26 | * yet they require special HTML-entity escaping 27 | * to generate valid HTML markup. 28 | * 29 | * All other characters will be escaped to %XX. 30 | * 31 | */ 32 | static const char HREF_SAFE[] = { 33 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 35 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 36 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 37 | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 38 | 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44 | }; 45 | 46 | int houdini_escape_href(cmark_strbuf *ob, const uint8_t *src, bufsize_t size) { 47 | static const uint8_t hex_chars[] = "0123456789ABCDEF"; 48 | bufsize_t i = 0, org; 49 | uint8_t hex_str[3]; 50 | 51 | hex_str[0] = '%'; 52 | 53 | while (i < size) { 54 | org = i; 55 | while (i < size && HREF_SAFE[src[i]] != 0) 56 | i++; 57 | 58 | if (likely(i > org)) 59 | cmark_strbuf_put(ob, src + org, i - org); 60 | 61 | /* escaping */ 62 | if (i >= size) 63 | break; 64 | 65 | switch (src[i]) { 66 | /* amp appears all the time in URLs, but needs 67 | * HTML-entity escaping to be inside an href */ 68 | case '&': 69 | cmark_strbuf_puts(ob, "&"); 70 | break; 71 | 72 | /* the single quote is a valid URL character 73 | * according to the standard; it needs HTML 74 | * entity escaping too */ 75 | case '\'': 76 | cmark_strbuf_puts(ob, "'"); 77 | break; 78 | 79 | /* the space can be escaped to %20 or a plus 80 | * sign. we're going with the generic escape 81 | * for now. the plus thing is more commonly seen 82 | * when building GET strings */ 83 | #if 0 84 | case ' ': 85 | cmark_strbuf_putc(ob, '+'); 86 | break; 87 | #endif 88 | 89 | /* every other character goes with a %XX escaping */ 90 | default: 91 | hex_str[1] = hex_chars[(src[i] >> 4) & 0xF]; 92 | hex_str[2] = hex_chars[src[i] & 0xF]; 93 | cmark_strbuf_put(ob, hex_str, 3); 94 | } 95 | 96 | i++; 97 | } 98 | 99 | return 1; 100 | } 101 | -------------------------------------------------------------------------------- /Sources/libcmark/include/chunk.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_CHUNK_H 2 | #define CMARK_CHUNK_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "cmark-gfm.h" 8 | #include "buffer.h" 9 | #include "cmark_ctype.h" 10 | 11 | #define CMARK_CHUNK_EMPTY \ 12 | { NULL, 0, 0 } 13 | 14 | typedef struct cmark_chunk { 15 | unsigned char *data; 16 | bufsize_t len; 17 | bufsize_t alloc; // also implies a NULL-terminated string 18 | } cmark_chunk; 19 | 20 | static CMARK_INLINE void cmark_chunk_free(cmark_mem *mem, cmark_chunk *c) { 21 | if (c->alloc) 22 | mem->free(c->data); 23 | 24 | c->data = NULL; 25 | c->alloc = 0; 26 | c->len = 0; 27 | } 28 | 29 | static CMARK_INLINE void cmark_chunk_ltrim(cmark_chunk *c) { 30 | assert(!c->alloc); 31 | 32 | while (c->len && cmark_isspace(c->data[0])) { 33 | c->data++; 34 | c->len--; 35 | } 36 | } 37 | 38 | static CMARK_INLINE void cmark_chunk_rtrim(cmark_chunk *c) { 39 | assert(!c->alloc); 40 | 41 | while (c->len > 0) { 42 | if (!cmark_isspace(c->data[c->len - 1])) 43 | break; 44 | 45 | c->len--; 46 | } 47 | } 48 | 49 | static CMARK_INLINE void cmark_chunk_trim(cmark_chunk *c) { 50 | cmark_chunk_ltrim(c); 51 | cmark_chunk_rtrim(c); 52 | } 53 | 54 | static CMARK_INLINE bufsize_t cmark_chunk_strchr(cmark_chunk *ch, int c, 55 | bufsize_t offset) { 56 | const unsigned char *p = 57 | (unsigned char *)memchr(ch->data + offset, c, ch->len - offset); 58 | return p ? (bufsize_t)(p - ch->data) : ch->len; 59 | } 60 | 61 | static CMARK_INLINE const char *cmark_chunk_to_cstr(cmark_mem *mem, 62 | cmark_chunk *c) { 63 | unsigned char *str; 64 | 65 | if (c->alloc) { 66 | return (char *)c->data; 67 | } 68 | str = (unsigned char *)mem->calloc(c->len + 1, 1); 69 | if (c->len > 0) { 70 | memcpy(str, c->data, c->len); 71 | } 72 | str[c->len] = 0; 73 | c->data = str; 74 | c->alloc = 1; 75 | 76 | return (char *)str; 77 | } 78 | 79 | static CMARK_INLINE void cmark_chunk_set_cstr(cmark_mem *mem, cmark_chunk *c, 80 | const char *str) { 81 | unsigned char *old = c->alloc ? c->data : NULL; 82 | if (str == NULL) { 83 | c->len = 0; 84 | c->data = NULL; 85 | c->alloc = 0; 86 | } else { 87 | c->len = (bufsize_t)strlen(str); 88 | c->data = (unsigned char *)mem->calloc(c->len + 1, 1); 89 | c->alloc = 1; 90 | memcpy(c->data, str, c->len + 1); 91 | } 92 | if (old != NULL) { 93 | mem->free(old); 94 | } 95 | } 96 | 97 | static CMARK_INLINE cmark_chunk cmark_chunk_literal(const char *data) { 98 | bufsize_t len = data ? (bufsize_t)strlen(data) : 0; 99 | cmark_chunk c = {(unsigned char *)data, len, 0}; 100 | return c; 101 | } 102 | 103 | static CMARK_INLINE cmark_chunk cmark_chunk_dup(const cmark_chunk *ch, 104 | bufsize_t pos, bufsize_t len) { 105 | cmark_chunk c = {ch->data + pos, len, 0}; 106 | return c; 107 | } 108 | 109 | static CMARK_INLINE cmark_chunk cmark_chunk_buf_detach(cmark_strbuf *buf) { 110 | cmark_chunk c; 111 | 112 | c.len = buf->size; 113 | c.data = cmark_strbuf_detach(buf); 114 | c.alloc = 1; 115 | 116 | return c; 117 | } 118 | 119 | /* trim_new variants are to be used when the source chunk may or may not be 120 | * allocated; forces a newly allocated chunk. */ 121 | static CMARK_INLINE cmark_chunk cmark_chunk_ltrim_new(cmark_mem *mem, cmark_chunk *c) { 122 | cmark_chunk r = cmark_chunk_dup(c, 0, c->len); 123 | cmark_chunk_ltrim(&r); 124 | cmark_chunk_to_cstr(mem, &r); 125 | return r; 126 | } 127 | 128 | static CMARK_INLINE cmark_chunk cmark_chunk_rtrim_new(cmark_mem *mem, cmark_chunk *c) { 129 | cmark_chunk r = cmark_chunk_dup(c, 0, c->len); 130 | cmark_chunk_rtrim(&r); 131 | cmark_chunk_to_cstr(mem, &r); 132 | return r; 133 | } 134 | 135 | #endif 136 | -------------------------------------------------------------------------------- /Example/Sources/DemoViewController.swift: -------------------------------------------------------------------------------- 1 | import MarkdownKit 2 | import UIKit 3 | 4 | private let exampleDocument = """ 5 | This is **bold** and __bold__. 6 | 7 | Here is *italic* and _italic_. 8 | 9 | Here’s ***both***, ___both___, _**both**_, and __*both*__. 10 | 11 | Links? We [got ‘em](https://github.com) 12 | 13 | This is an image ![reef](https://soffes.s3.amazonaws.com/reef.jpg) 14 | 15 | This is ~~strikethrough~~. 16 | 17 | Inline `code` too. You can do ```a silly amount of back ticks``` too. 18 | 19 | 20 | 21 | 22 | --- 23 | 24 | > Blockquote 25 | 26 | - List 27 | * List 28 | 29 | 1. One 30 | 1. Two 31 | 32 | - [ ] Task list 33 | - [x] Done 34 | 35 | ```ruby 36 | puts "hi" 37 | ``` 38 | 39 | # Heading 1 40 | 41 | ## Heading 2 42 | 43 | ### Heading 3 44 | 45 | #### Heading 4 46 | 47 | ##### Heading 5 48 | 49 | ###### Heading 6 50 | """ 51 | 52 | final class DemoViewController: UIViewController { 53 | 54 | // MARK: - Properties 55 | 56 | private let textView: TextView = { 57 | let view = TextView() 58 | view.translatesAutoresizingMaskIntoConstraints = false 59 | view.text = exampleDocument 60 | view.textContainerInset = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) 61 | return view 62 | }() 63 | 64 | // MARK: - UIViewController 65 | 66 | override func viewDidLoad() { 67 | super.viewDidLoad() 68 | 69 | view.addSubview(textView) 70 | 71 | let blur = UIVisualEffectView(effect: UIBlurEffect(style: .light)) 72 | blur.translatesAutoresizingMaskIntoConstraints = false 73 | view.addSubview(blur) 74 | 75 | let blurBottom = blur.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: -1) 76 | blurBottom.priority = .defaultHigh 77 | 78 | NSLayoutConstraint.activate([ 79 | blur.leadingAnchor.constraint(equalTo: view.leadingAnchor), 80 | blur.trailingAnchor.constraint(equalTo: view.trailingAnchor), 81 | blur.topAnchor.constraint(equalTo: view.topAnchor), 82 | blurBottom, 83 | 84 | textView.leadingAnchor.constraint(equalTo: view.leadingAnchor), 85 | textView.trailingAnchor.constraint(equalTo: view.trailingAnchor), 86 | textView.topAnchor.constraint(equalTo: view.topAnchor), 87 | textView.bottomAnchor.constraint(equalTo: view.bottomAnchor) 88 | ]) 89 | 90 | let notificationCenter = NotificationCenter.default 91 | notificationCenter.addObserver(self, selector: #selector(keyboardWillHide), 92 | name: UIResponder.keyboardWillHideNotification, object: nil) 93 | notificationCenter.addObserver(self, selector: #selector(keyboardWillChange), 94 | name: UIResponder.keyboardWillChangeFrameNotification, object: nil) 95 | } 96 | 97 | override func viewDidAppear(_ animated: Bool) { 98 | super.viewDidAppear(animated) 99 | textView.becomeFirstResponder() 100 | } 101 | 102 | // MARK: - Private 103 | 104 | @objc func keyboardWillHide(_ notification: Notification) { 105 | textView.contentInset = .zero 106 | textView.verticalScrollIndicatorInsets = textView.contentInset 107 | textView.scrollRangeToVisible(textView.selectedRange) 108 | } 109 | 110 | @objc func keyboardWillChange(_ notification: Notification) { 111 | guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { 112 | return 113 | } 114 | 115 | let keyboardScreenEndFrame = keyboardValue.cgRectValue 116 | let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window) 117 | 118 | textView.contentInset = UIEdgeInsets(top: 0, left: 0, 119 | bottom: keyboardViewEndFrame.height - view.safeAreaInsets.bottom, right: 0) 120 | 121 | textView.verticalScrollIndicatorInsets = textView.contentInset 122 | textView.scrollRangeToVisible(textView.selectedRange) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Text/TextStorage.swift: -------------------------------------------------------------------------------- 1 | import MarkdownKitObjC 2 | import UIKit 3 | 4 | public protocol TextStorageCustomDelegate: AnyObject { 5 | func textStorage(_ textStorage: TextStorage, didParseDocument document: Document) 6 | func textStorage(_ textStorage: TextStorage, didChangeTheme theme: Theme) 7 | 8 | func textStorage(_ textStorage: TextStorage, shouldChangeTextIn range: NSRange, with string: String, 9 | actionName: String) -> Bool 10 | func textStorage(_ textStorage: TextStorage, didUpdateSelectedRange range: NSRange) 11 | } 12 | 13 | public final class TextStorage: BaseTextStorage { 14 | 15 | // MARK: - Properties 16 | 17 | public weak var customDelegate: TextStorageCustomDelegate? 18 | 19 | public private(set) var document: Document? 20 | 21 | public var theme = DefaultTheme() { 22 | didSet { 23 | customDelegate?.textStorage(self, didChangeTheme: theme) 24 | refreshTheme() 25 | } 26 | } 27 | 28 | public var typingAttributes: [NSAttributedString.Key: Any] { 29 | return theme.baseAttributes 30 | } 31 | 32 | public var bounds: NSRange { 33 | return NSRange(location: 0, length: string.length) 34 | } 35 | 36 | private var needsParse = false 37 | 38 | // MARK: - NSTextStorage 39 | 40 | public override func replaceCharacters(in range: NSRange, with string: String) { 41 | needsParse = true 42 | super.replaceCharacters(in: range, with: string) 43 | } 44 | 45 | // MARK: - Parsing 46 | 47 | /// Reparse `string` and updated the attributes based on `theme`. 48 | /// 49 | /// This should be called from `textViewDidChange` or in `UITextView.text`’s `didSet`. 50 | public func parseIfNeeded() { 51 | guard needsParse else { 52 | return 53 | } 54 | 55 | parse() 56 | } 57 | 58 | /// Throw away the AST, reparse, and update the styles 59 | func parse() { 60 | needsParse = false 61 | 62 | beginEditing() 63 | resetAttributes() 64 | 65 | if let document = Parser.parse(string) { 66 | self.document = document 67 | customDelegate?.textStorage(self, didParseDocument: document) 68 | addAttributes(for: document, currentFont: theme.font) 69 | } else { 70 | self.document = nil 71 | } 72 | 73 | endEditing() 74 | } 75 | 76 | /// Reapply the theme without reparsing the document. 77 | /// 78 | /// This is automatically called after setting the `theme` property. You should only use this if a derived property 79 | /// in your theme changes. For example, maybe your theme’s font is based on Dynamic Type, you could call this method 80 | /// when you detect a Dynamic Type change. 81 | public func refreshTheme() { 82 | guard let document = document else { 83 | return 84 | } 85 | 86 | beginEditing() 87 | resetAttributes() 88 | addAttributes(for: document, currentFont: theme.font) 89 | endEditing() 90 | } 91 | 92 | // MARK: - Private 93 | 94 | /// Reset all attributes. Down the road, we could detect the maximum affect area and only reset those. 95 | private func resetAttributes() { 96 | setAttributes(typingAttributes, range: bounds) 97 | } 98 | 99 | private func addAttributes(for node: Node, currentFont: UIFont) { 100 | var currentFont = currentFont 101 | 102 | if let range = node.range { 103 | let styles = theme.styles(for: node, range: range) 104 | 105 | for style in styles { 106 | var attributes = style.attributes 107 | if let traits = attributes[.fontTraits] as? UIFontDescriptor.SymbolicTraits { 108 | currentFont = currentFont.addingTraits(traits) 109 | attributes[.font] = currentFont 110 | attributes.removeValue(forKey: .fontTraits) 111 | } 112 | 113 | addAttributes(attributes, range: style.range) 114 | } 115 | } 116 | 117 | for child in node.children { 118 | addAttributes(for: child, currentFont: currentFont) 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Sources/libcmark/src/houdini_html_u.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "buffer.h" 6 | #include "houdini.h" 7 | #include "utf8.h" 8 | #include "entities.inc" 9 | 10 | /* Binary tree lookup code for entities added by JGM */ 11 | 12 | static const unsigned char *S_lookup(int i, int low, int hi, 13 | const unsigned char *s, int len) { 14 | int j; 15 | int cmp = 16 | strncmp((const char *)s, (const char *)cmark_entities[i].entity, len); 17 | if (cmp == 0 && cmark_entities[i].entity[len] == 0) { 18 | return (const unsigned char *)cmark_entities[i].bytes; 19 | } else if (cmp <= 0 && i > low) { 20 | j = i - ((i - low) / 2); 21 | if (j == i) 22 | j -= 1; 23 | return S_lookup(j, low, i - 1, s, len); 24 | } else if (cmp > 0 && i < hi) { 25 | j = i + ((hi - i) / 2); 26 | if (j == i) 27 | j += 1; 28 | return S_lookup(j, i + 1, hi, s, len); 29 | } else { 30 | return NULL; 31 | } 32 | } 33 | 34 | static const unsigned char *S_lookup_entity(const unsigned char *s, int len) { 35 | return S_lookup(CMARK_NUM_ENTITIES / 2, 0, CMARK_NUM_ENTITIES - 1, s, len); 36 | } 37 | 38 | bufsize_t houdini_unescape_ent(cmark_strbuf *ob, const uint8_t *src, 39 | bufsize_t size) { 40 | bufsize_t i = 0; 41 | 42 | if (size >= 3 && src[0] == '#') { 43 | int codepoint = 0; 44 | int num_digits = 0; 45 | 46 | if (_isdigit(src[1])) { 47 | for (i = 1; i < size && _isdigit(src[i]); ++i) { 48 | codepoint = (codepoint * 10) + (src[i] - '0'); 49 | 50 | if (codepoint >= 0x110000) { 51 | // Keep counting digits but 52 | // avoid integer overflow. 53 | codepoint = 0x110000; 54 | } 55 | } 56 | 57 | num_digits = i - 1; 58 | } 59 | 60 | else if (src[1] == 'x' || src[1] == 'X') { 61 | for (i = 2; i < size && _isxdigit(src[i]); ++i) { 62 | codepoint = (codepoint * 16) + ((src[i] | 32) % 39 - 9); 63 | 64 | if (codepoint >= 0x110000) { 65 | // Keep counting digits but 66 | // avoid integer overflow. 67 | codepoint = 0x110000; 68 | } 69 | } 70 | 71 | num_digits = i - 2; 72 | } 73 | 74 | if (num_digits >= 1 && num_digits <= 8 && i < size && src[i] == ';') { 75 | if (codepoint == 0 || (codepoint >= 0xD800 && codepoint < 0xE000) || 76 | codepoint >= 0x110000) { 77 | codepoint = 0xFFFD; 78 | } 79 | cmark_utf8proc_encode_char(codepoint, ob); 80 | return i + 1; 81 | } 82 | } 83 | 84 | else { 85 | if (size > CMARK_ENTITY_MAX_LENGTH) 86 | size = CMARK_ENTITY_MAX_LENGTH; 87 | 88 | for (i = CMARK_ENTITY_MIN_LENGTH; i < size; ++i) { 89 | if (src[i] == ' ') 90 | break; 91 | 92 | if (src[i] == ';') { 93 | const unsigned char *entity = S_lookup_entity(src, i); 94 | 95 | if (entity != NULL) { 96 | cmark_strbuf_puts(ob, (const char *)entity); 97 | return i + 1; 98 | } 99 | 100 | break; 101 | } 102 | } 103 | } 104 | 105 | return 0; 106 | } 107 | 108 | int houdini_unescape_html(cmark_strbuf *ob, const uint8_t *src, 109 | bufsize_t size) { 110 | bufsize_t i = 0, org, ent; 111 | 112 | while (i < size) { 113 | org = i; 114 | while (i < size && src[i] != '&') 115 | i++; 116 | 117 | if (likely(i > org)) { 118 | if (unlikely(org == 0)) { 119 | if (i >= size) 120 | return 0; 121 | 122 | cmark_strbuf_grow(ob, HOUDINI_UNESCAPED_SIZE(size)); 123 | } 124 | 125 | cmark_strbuf_put(ob, src + org, i - org); 126 | } 127 | 128 | /* escaping */ 129 | if (i >= size) 130 | break; 131 | 132 | i++; 133 | 134 | ent = houdini_unescape_ent(ob, src + i, size - i); 135 | i += ent; 136 | 137 | /* not really an entity */ 138 | if (ent == 0) 139 | cmark_strbuf_putc(ob, '&'); 140 | } 141 | 142 | return 1; 143 | } 144 | 145 | void houdini_unescape_html_f(cmark_strbuf *ob, const uint8_t *src, 146 | bufsize_t size) { 147 | if (!houdini_unescape_html(ob, src, size)) 148 | cmark_strbuf_put(ob, src, size); 149 | } 150 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Models/Kind.swift: -------------------------------------------------------------------------------- 1 | import libcmark 2 | 3 | public typealias Kind = cmark_node_type 4 | 5 | extension Kind { 6 | 7 | // MARK: - Error 8 | 9 | public static let none: Kind = CMARK_NODE_NONE 10 | 11 | // MARK: - Block 12 | 13 | public static let document: Kind = CMARK_NODE_DOCUMENT 14 | public static let blockquote: Kind = CMARK_NODE_BLOCK_QUOTE 15 | public static let list: Kind = CMARK_NODE_LIST 16 | public static let item: Kind = CMARK_NODE_ITEM 17 | public static let codeBlock: Kind = CMARK_NODE_CODE_BLOCK 18 | public static let htmlBlock: Kind = CMARK_NODE_HTML_BLOCK 19 | public static let customBlock: Kind = CMARK_NODE_CUSTOM_BLOCK 20 | public static let paragraph: Kind = CMARK_NODE_PARAGRAPH 21 | public static let heading: Kind = CMARK_NODE_HEADING 22 | public static let thematicBreak: Kind = CMARK_NODE_THEMATIC_BREAK 23 | public static let table: Kind = CMARK_NODE_TABLE 24 | public static let tableRow: Kind = CMARK_NODE_TABLE_ROW 25 | public static let tableCell: Kind = CMARK_NODE_TABLE_CELL 26 | 27 | // MARK: - Inline 28 | 29 | public static let text: Kind = CMARK_NODE_TEXT 30 | public static let softBreak: Kind = CMARK_NODE_SOFTBREAK 31 | public static let lineBreak: Kind = CMARK_NODE_LINEBREAK 32 | public static let codeInline: Kind = CMARK_NODE_CODE 33 | public static let htmlInline: Kind = CMARK_NODE_HTML_INLINE 34 | public static let customInline: Kind = CMARK_NODE_CUSTOM_INLINE 35 | public static let emphasis: Kind = CMARK_NODE_EMPH 36 | public static let strong: Kind = CMARK_NODE_STRONG 37 | public static let link: Kind = CMARK_NODE_LINK 38 | public static let image: Kind = CMARK_NODE_IMAGE 39 | public static let strikethrough: Kind = CMARK_NODE_STRIKETHROUGH 40 | 41 | // MARK: - Inspecting 42 | 43 | public var isBlock: Bool { 44 | switch self { 45 | case .document, .blockquote, .list, .item, .codeBlock, .htmlBlock, .customBlock, .paragraph, .heading, 46 | .thematicBreak, .table, .tableRow, .tableCell: 47 | return true 48 | default: 49 | return false 50 | } 51 | } 52 | 53 | public var isInline: Bool { 54 | switch self { 55 | case .text, .softBreak, .lineBreak, .codeInline, .htmlInline, .customInline, .emphasis, .strong, .link, 56 | .image, .strikethrough: 57 | return true 58 | default: 59 | return false 60 | } 61 | } 62 | 63 | public var isCode: Bool { 64 | switch self { 65 | case .codeInline, .codeBlock, .htmlInline, .htmlBlock: 66 | return true 67 | default: 68 | return false 69 | } 70 | } 71 | } 72 | 73 | extension Kind: CustomStringConvertible { 74 | public var description: String { 75 | switch self { 76 | case .document: 77 | return "document" 78 | case .blockquote: 79 | return "blockquote" 80 | case .list: 81 | return "list" 82 | case .item: 83 | return "item" 84 | case .codeBlock: 85 | return "codeBlock" 86 | case .htmlBlock: 87 | return "htmlBlock" 88 | case .customBlock: 89 | return "customBlock" 90 | case .paragraph: 91 | return "paragraph" 92 | case .heading: 93 | return "heading" 94 | case .thematicBreak: 95 | return "thematicBreak" 96 | case .table: 97 | return "table" 98 | case .tableRow: 99 | return "tableRow" 100 | case .tableCell: 101 | return "tableCell" 102 | case .text: 103 | return "text" 104 | case .softBreak: 105 | return "softBreak" 106 | case .lineBreak: 107 | return "lineBreak" 108 | case .codeInline: 109 | return "codeInline" 110 | case .htmlInline: 111 | return "htmlInline" 112 | case .customInline: 113 | return "customInline" 114 | case .emphasis: 115 | return "emphasis" 116 | case .strong: 117 | return "strong" 118 | case .link: 119 | return "link" 120 | case .image: 121 | return "image" 122 | case .strikethrough: 123 | return "strikethrough" 124 | default: 125 | return "none" 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Text/TextStorage+Manipulation.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension TextStorage { 4 | 5 | // MARK: - Manipulation 6 | 7 | public func toggleBold(in selectedRange: NSRange) { 8 | toggleSpan(.strong, selectedRange: selectedRange) 9 | } 10 | 11 | public func toggleItalic(in selectedRange: NSRange) { 12 | toggleSpan(.emphasis, selectedRange: selectedRange) 13 | } 14 | 15 | // MARK: - Private 16 | 17 | private func toggleSpan(_ kind: Kind, selectedRange: NSRange) { 18 | let markdown: String 19 | let actionName: String 20 | 21 | switch kind { 22 | case .emphasis: 23 | markdown = "*" 24 | actionName = "Italic" 25 | case .strong: 26 | markdown = "**" 27 | actionName = "Bold" 28 | default: 29 | assertionFailure("Tried to toggle unsupported span") 30 | return 31 | } 32 | 33 | // If it's already this element, remove it. 34 | guard var node = self.document?.node(for: selectedRange) else { 35 | return 36 | } 37 | 38 | let delimiterLength = markdown.length 39 | 40 | if node.kind == .text, let parent = node.parent { 41 | node = parent 42 | } 43 | 44 | guard let nodeRange = node.range else { 45 | return 46 | } 47 | 48 | if node.kind == kind { 49 | let innerRange = NSRange(location: nodeRange.location + delimiterLength, 50 | length: nodeRange.length - delimiterLength * 2) 51 | let replacement = (string as NSString).substring(with: innerRange) 52 | if !changeTextIn(nodeRange, with: replacement, actionName: actionName) { 53 | return 54 | } 55 | 56 | if selectedRange.length == 0 { 57 | updateSelectedRange(NSRange(location: selectedRange.location - delimiterLength, length: 0)) 58 | } else { 59 | var updatedSelection = nodeRange 60 | updatedSelection.length -= delimiterLength * 2 61 | updateSelectedRange(updatedSelection) 62 | } 63 | } 64 | 65 | // No selection, add characters and move cursor. 66 | else if selectedRange.length == 0 { 67 | let wordRange = self.string.wordRange(atIndex: selectedRange.location) 68 | 69 | if wordRange.location == NSNotFound { 70 | return 71 | } 72 | 73 | // Not inside of a word. Just add the delmiters. 74 | if wordRange.length == 0 { 75 | let replacement = "\(markdown)\(markdown)" 76 | if !changeTextIn(wordRange, with: replacement, actionName: actionName) { 77 | return 78 | } 79 | } 80 | 81 | // Inside of a word. Toggle the whole word. 82 | else { 83 | let string = (self.string as NSString).substring(with: wordRange) 84 | let replacement = "\(markdown)\(string)\(markdown)" 85 | if !changeTextIn(wordRange, with: replacement, actionName: actionName) { 86 | return 87 | } 88 | } 89 | 90 | updateSelectedRange(NSRange(location: selectedRange.location + delimiterLength, length: 0)) 91 | } 92 | 93 | // Selection, add text. 94 | else { 95 | let string = (self.string as NSString).substring(with: selectedRange) 96 | let replacement = "\(markdown)\(string)\(markdown)" 97 | 98 | if !changeTextIn(selectedRange, with: replacement, actionName: actionName) { 99 | return 100 | } 101 | 102 | var updatedSelection = selectedRange 103 | updatedSelection.length += delimiterLength * 2 104 | updateSelectedRange(updatedSelection) 105 | } 106 | 107 | parse() 108 | } 109 | 110 | private func changeTextIn(_ range: NSRange, with replacement: String, actionName: String) -> Bool { 111 | guard let customDelegate = customDelegate else { 112 | assertionFailure("Tried to message missing delegate") 113 | return false 114 | } 115 | 116 | return customDelegate.textStorage(self, shouldChangeTextIn: range, with: replacement, actionName: actionName) 117 | } 118 | 119 | private func updateSelectedRange(_ range: NSRange) { 120 | guard let customDelegate = customDelegate else { 121 | assertionFailure("Tried to message missing delegate") 122 | return 123 | } 124 | 125 | return customDelegate.textStorage(self, didUpdateSelectedRange: range) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Sources/libcmark/src/iterator.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "config.h" 5 | #include "node.h" 6 | #include "cmark-gfm.h" 7 | #include "iterator.h" 8 | 9 | cmark_iter *cmark_iter_new(cmark_node *root) { 10 | if (root == NULL) { 11 | return NULL; 12 | } 13 | cmark_mem *mem = root->content.mem; 14 | cmark_iter *iter = (cmark_iter *)mem->calloc(1, sizeof(cmark_iter)); 15 | iter->mem = mem; 16 | iter->root = root; 17 | iter->cur.ev_type = CMARK_EVENT_NONE; 18 | iter->cur.node = NULL; 19 | iter->next.ev_type = CMARK_EVENT_ENTER; 20 | iter->next.node = root; 21 | return iter; 22 | } 23 | 24 | void cmark_iter_free(cmark_iter *iter) { iter->mem->free(iter); } 25 | 26 | static bool S_is_leaf(cmark_node *node) { 27 | switch (node->type) { 28 | case CMARK_NODE_HTML_BLOCK: 29 | case CMARK_NODE_THEMATIC_BREAK: 30 | case CMARK_NODE_CODE_BLOCK: 31 | case CMARK_NODE_TEXT: 32 | case CMARK_NODE_SOFTBREAK: 33 | case CMARK_NODE_LINEBREAK: 34 | case CMARK_NODE_CODE: 35 | case CMARK_NODE_HTML_INLINE: 36 | return 1; 37 | } 38 | return 0; 39 | } 40 | 41 | cmark_event_type cmark_iter_next(cmark_iter *iter) { 42 | cmark_event_type ev_type = iter->next.ev_type; 43 | cmark_node *node = iter->next.node; 44 | 45 | iter->cur.ev_type = ev_type; 46 | iter->cur.node = node; 47 | 48 | if (ev_type == CMARK_EVENT_DONE) { 49 | return ev_type; 50 | } 51 | 52 | /* roll forward to next item, setting both fields */ 53 | if (ev_type == CMARK_EVENT_ENTER && !S_is_leaf(node)) { 54 | if (node->first_child == NULL) { 55 | /* stay on this node but exit */ 56 | iter->next.ev_type = CMARK_EVENT_EXIT; 57 | } else { 58 | iter->next.ev_type = CMARK_EVENT_ENTER; 59 | iter->next.node = node->first_child; 60 | } 61 | } else if (node == iter->root) { 62 | /* don't move past root */ 63 | iter->next.ev_type = CMARK_EVENT_DONE; 64 | iter->next.node = NULL; 65 | } else if (node->next) { 66 | iter->next.ev_type = CMARK_EVENT_ENTER; 67 | iter->next.node = node->next; 68 | } else if (node->parent) { 69 | iter->next.ev_type = CMARK_EVENT_EXIT; 70 | iter->next.node = node->parent; 71 | } else { 72 | assert(false); 73 | iter->next.ev_type = CMARK_EVENT_DONE; 74 | iter->next.node = NULL; 75 | } 76 | 77 | return ev_type; 78 | } 79 | 80 | void cmark_iter_reset(cmark_iter *iter, cmark_node *current, 81 | cmark_event_type event_type) { 82 | iter->next.ev_type = event_type; 83 | iter->next.node = current; 84 | cmark_iter_next(iter); 85 | } 86 | 87 | cmark_node *cmark_iter_get_node(cmark_iter *iter) { return iter->cur.node; } 88 | 89 | cmark_event_type cmark_iter_get_event_type(cmark_iter *iter) { 90 | return iter->cur.ev_type; 91 | } 92 | 93 | cmark_node *cmark_iter_get_root(cmark_iter *iter) { return iter->root; } 94 | 95 | void cmark_consolidate_text_nodes(cmark_node *root) { 96 | if (root == NULL) { 97 | return; 98 | } 99 | cmark_iter *iter = cmark_iter_new(root); 100 | cmark_strbuf buf = CMARK_BUF_INIT(iter->mem); 101 | cmark_event_type ev_type; 102 | cmark_node *cur, *tmp, *next; 103 | 104 | while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { 105 | cur = cmark_iter_get_node(iter); 106 | if (ev_type == CMARK_EVENT_ENTER && cur->type == CMARK_NODE_TEXT && 107 | cur->next && cur->next->type == CMARK_NODE_TEXT) { 108 | cmark_strbuf_clear(&buf); 109 | cmark_strbuf_put(&buf, cur->as.literal.data, cur->as.literal.len); 110 | tmp = cur->next; 111 | while (tmp && tmp->type == CMARK_NODE_TEXT) { 112 | cmark_iter_next(iter); // advance pointer 113 | cmark_strbuf_put(&buf, tmp->as.literal.data, tmp->as.literal.len); 114 | cur->end_column = tmp->end_column; 115 | next = tmp->next; 116 | cmark_node_free(tmp); 117 | tmp = next; 118 | } 119 | cmark_chunk_free(iter->mem, &cur->as.literal); 120 | cur->as.literal = cmark_chunk_buf_detach(&buf); 121 | } 122 | } 123 | 124 | cmark_strbuf_free(&buf); 125 | cmark_iter_free(iter); 126 | } 127 | 128 | void cmark_node_own(cmark_node *root) { 129 | if (root == NULL) { 130 | return; 131 | } 132 | cmark_iter *iter = cmark_iter_new(root); 133 | cmark_event_type ev_type; 134 | cmark_node *cur; 135 | 136 | while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { 137 | cur = cmark_iter_get_node(iter); 138 | if (ev_type == CMARK_EVENT_ENTER) { 139 | switch (cur->type) { 140 | case CMARK_NODE_TEXT: 141 | case CMARK_NODE_HTML_INLINE: 142 | case CMARK_NODE_CODE: 143 | case CMARK_NODE_HTML_BLOCK: 144 | cmark_chunk_to_cstr(iter->mem, &cur->as.literal); 145 | break; 146 | case CMARK_NODE_LINK: 147 | cmark_chunk_to_cstr(iter->mem, &cur->as.link.url); 148 | cmark_chunk_to_cstr(iter->mem, &cur->as.link.title); 149 | break; 150 | case CMARK_NODE_CUSTOM_INLINE: 151 | cmark_chunk_to_cstr(iter->mem, &cur->as.custom.on_enter); 152 | cmark_chunk_to_cstr(iter->mem, &cur->as.custom.on_exit); 153 | break; 154 | } 155 | } 156 | } 157 | 158 | cmark_iter_free(iter); 159 | } 160 | -------------------------------------------------------------------------------- /Tests/MarkdownKitTests/BlockTests.swift: -------------------------------------------------------------------------------- 1 | import MarkdownKit 2 | import XCTest 3 | 4 | final class BlockTests: XCTestCase { 5 | 6 | // MARK: - Features 7 | 8 | func testContent() { 9 | let markdown = "Hello **world**.\n" 10 | let document = Parser.parse(markdown)! 11 | XCTAssertEqual(markdown, document.content) 12 | 13 | let paragraph = document.children.first! 14 | XCTAssertEqual(markdown, paragraph.content) 15 | } 16 | 17 | func testLineBreaks() { 18 | let markdown = "Hello\nworld\n\nTwo\n" 19 | let document = Parser.parse(markdown)! 20 | 21 | let paragraph1 = document.children[0] 22 | XCTAssertEqual("Hello\nworld\n", paragraph1.content) 23 | 24 | let paragraph2 = document.children[1] 25 | XCTAssertEqual("Two\n", paragraph2.content) 26 | } 27 | 28 | func testRanges() { 29 | let markdown = "Hello\nworld\n\nTwo\n" 30 | let document = Parser.parse(markdown)! 31 | 32 | let paragraph1 = document.children[0] 33 | XCTAssertEqual(NSRange(location: 0, length: 11), paragraph1.range) 34 | 35 | let paragraph2 = document.children[1] 36 | XCTAssertEqual(NSRange(location: 13, length: 3), paragraph2.range) 37 | } 38 | 39 | // MARK: - Blocks 40 | 41 | func testBlockQuote() { 42 | let markdown = "Hello\n\n> World.\n" 43 | let document = Parser.parse(markdown)! 44 | XCTAssertEqual(2, document.children.count) 45 | 46 | let block = document.children[1] 47 | XCTAssertEqual(.blockquote, block.kind) 48 | XCTAssertEqual(NSRange(location: 7, length: 8), block.range!) 49 | } 50 | 51 | func testList() { 52 | let markdown = "Hello\n\n* One\n* Two\n" 53 | let document = Parser.parse(markdown)! 54 | XCTAssertEqual(2, document.children.count) 55 | 56 | let block = document.children[1] 57 | XCTAssertEqual(.list, block.kind) 58 | XCTAssertEqual(NSRange(location: 7, length: 11), block.range!) 59 | } 60 | 61 | func testItem() { 62 | let markdown = "- Item\n\nParagraph\n" 63 | let document = Parser.parse(markdown)! 64 | XCTAssertEqual(2, document.children.count) 65 | 66 | let list = document.children[0] 67 | let block = list.children[0] 68 | XCTAssertEqual(.item, block.kind) 69 | XCTAssertEqual(NSRange(location: 0, length: 7), block.range!) 70 | } 71 | 72 | func testTask() { 73 | let markdown = "- [ ] Item\n" 74 | let document = Parser.parse(markdown)! 75 | 76 | let item = document.children[0].children[0] 77 | XCTAssertEqual(true, (item as? ListItem)?.isTask) 78 | } 79 | 80 | func testCompletedTask() { 81 | let markdown = "- [x] Item\n" 82 | let document = Parser.parse(markdown)! 83 | let item = document.children[0].children[0] 84 | XCTAssertEqual(true, (item as? ListItem)?.isTask) 85 | XCTAssertEqual(true, (item as? ListItem)?.isCompleted) 86 | } 87 | 88 | func testCode() { 89 | let markdown = "Hello\n\n puts 'hi'\n" 90 | let document = Parser.parse(markdown)! 91 | XCTAssertEqual(2, document.children.count) 92 | 93 | let block = document.children[1] 94 | XCTAssertEqual(.codeBlock, block.kind) 95 | XCTAssertEqual(NSRange(location: 11, length: 9), block.range!) 96 | } 97 | 98 | func testFencedCode() { 99 | let markdown = "Hello\n\n``` ruby\nputs 'hi'\n```\n" 100 | let document = Parser.parse(markdown)! 101 | XCTAssertEqual(2, document.children.count) 102 | 103 | let block = document.children[1] 104 | XCTAssertEqual(.codeBlock, block.kind) 105 | XCTAssertEqual(NSRange(location: 7, length: 22), block.range!) 106 | } 107 | 108 | func testHTML() { 109 | let markdown = "Hello\n\n
\nworld\n
\n" 110 | let document = Parser.parse(markdown)! 111 | XCTAssertEqual(2, document.children.count) 112 | 113 | let block = document.children[1] 114 | XCTAssertEqual(.htmlBlock, block.kind) 115 | XCTAssertEqual(NSRange(location: 7, length: 18), block.range!) 116 | } 117 | 118 | func testHeading() { 119 | let markdown = "Hello\n\n# World\n" 120 | let document = Parser.parse(markdown)! 121 | XCTAssertEqual(2, document.children.count) 122 | 123 | let block = document.children[1] 124 | XCTAssertEqual(.heading, block.kind) 125 | XCTAssertEqual(NSRange(location: 7, length: 7), block.range!) 126 | } 127 | 128 | func testUnderlinedHeading() { 129 | let markdown = "Hello\n\nWorld\n=====\n" 130 | let document = Parser.parse(markdown)! 131 | XCTAssertEqual(2, document.children.count) 132 | 133 | let block = document.children[1] 134 | XCTAssertEqual(.heading, block.kind) 135 | XCTAssertEqual(NSRange(location: 7, length: 11), block.range!) 136 | } 137 | 138 | func testThematicBreak() { 139 | let markdown = "Hello\n\n---\n" 140 | let document = Parser.parse(markdown)! 141 | XCTAssertEqual(2, document.children.count) 142 | 143 | let block = document.children[1] 144 | XCTAssertEqual(.thematicBreak, block.kind) 145 | XCTAssertEqual(NSRange(location: 7, length: 3), block.range!) 146 | XCTAssertEqual([NSRange(location: 7, length: 3)], block.delimiters!) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Sources/libcmark/extensions/tasklist.c: -------------------------------------------------------------------------------- 1 | #include "tasklist.h" 2 | #include 3 | #include 4 | #include 5 | #include "ext_scanners.h" 6 | 7 | typedef enum { 8 | CMARK_TASKLIST_NOCHECKED, 9 | CMARK_TASKLIST_CHECKED, 10 | } cmark_tasklist_type; 11 | 12 | // Local constants 13 | static const char *TYPE_STRING = "tasklist"; 14 | 15 | static const char *get_type_string(cmark_syntax_extension *extension, cmark_node *node) { 16 | return TYPE_STRING; 17 | } 18 | 19 | 20 | // Return 1 if state was set, 0 otherwise 21 | int cmark_gfm_extensions_set_tasklist_item_checked(cmark_node *node, bool is_checked) { 22 | // The node has to exist, and be an extension, and actually be the right type in order to get the value. 23 | if (!node || !node->extension || strcmp(cmark_node_get_type_string(node), TYPE_STRING)) 24 | return 0; 25 | 26 | node->as.list.checked = is_checked; 27 | return 1; 28 | } 29 | 30 | bool cmark_gfm_extensions_get_tasklist_item_checked(cmark_node *node) { 31 | if (!node || !node->extension || strcmp(cmark_node_get_type_string(node), TYPE_STRING)) 32 | return false; 33 | 34 | if (node->as.list.checked) { 35 | return true; 36 | } 37 | else { 38 | return false; 39 | } 40 | } 41 | 42 | static bool parse_node_item_prefix(cmark_parser *parser, const char *input, 43 | cmark_node *container) { 44 | bool res = false; 45 | 46 | if (parser->indent >= 47 | container->as.list.marker_offset + container->as.list.padding) { 48 | cmark_parser_advance_offset(parser, input, container->as.list.marker_offset + 49 | container->as.list.padding, 50 | true); 51 | res = true; 52 | } else if (parser->blank && container->first_child != NULL) { 53 | // if container->first_child is NULL, then the opening line 54 | // of the list item was blank after the list marker; in this 55 | // case, we are done with the list item. 56 | cmark_parser_advance_offset(parser, input, parser->first_nonspace - parser->offset, 57 | false); 58 | res = true; 59 | } 60 | return res; 61 | } 62 | 63 | static int matches(cmark_syntax_extension *self, cmark_parser *parser, 64 | unsigned char *input, int len, 65 | cmark_node *parent_container) { 66 | return parse_node_item_prefix(parser, (const char*)input, parent_container); 67 | } 68 | 69 | static int can_contain(cmark_syntax_extension *extension, cmark_node *node, 70 | cmark_node_type child_type) { 71 | return (node->type == CMARK_NODE_ITEM) ? 1 : 0; 72 | } 73 | 74 | static cmark_node *open_tasklist_item(cmark_syntax_extension *self, 75 | int indented, cmark_parser *parser, 76 | cmark_node *parent_container, 77 | unsigned char *input, int len) { 78 | cmark_node_type node_type = cmark_node_get_type(parent_container); 79 | if (node_type != CMARK_NODE_ITEM) { 80 | return NULL; 81 | } 82 | 83 | bufsize_t matched = scan_tasklist(input, len, 0); 84 | if (!matched) { 85 | return NULL; 86 | } 87 | 88 | cmark_node_set_syntax_extension(parent_container, self); 89 | cmark_parser_advance_offset(parser, (char *)input, 3, false); 90 | 91 | // Either an upper or lower case X means the task is completed. 92 | parent_container->as.list.checked = (strstr((char*)input, "[x]") || strstr((char*)input, "[X]")); 93 | 94 | return NULL; 95 | } 96 | 97 | static void commonmark_render(cmark_syntax_extension *extension, 98 | cmark_renderer *renderer, cmark_node *node, 99 | cmark_event_type ev_type, int options) { 100 | bool entering = (ev_type == CMARK_EVENT_ENTER); 101 | if (entering) { 102 | renderer->cr(renderer); 103 | if (node->as.list.checked) { 104 | renderer->out(renderer, node, "- [x] ", false, LITERAL); 105 | } else { 106 | renderer->out(renderer, node, "- [ ] ", false, LITERAL); 107 | } 108 | cmark_strbuf_puts(renderer->prefix, " "); 109 | } else { 110 | cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 2); 111 | renderer->cr(renderer); 112 | } 113 | } 114 | 115 | static void html_render(cmark_syntax_extension *extension, 116 | cmark_html_renderer *renderer, cmark_node *node, 117 | cmark_event_type ev_type, int options) { 118 | bool entering = (ev_type == CMARK_EVENT_ENTER); 119 | if (entering) { 120 | cmark_html_render_cr(renderer->html); 121 | cmark_strbuf_puts(renderer->html, "html, options); 123 | cmark_strbuf_putc(renderer->html, '>'); 124 | if (node->as.list.checked) { 125 | cmark_strbuf_puts(renderer->html, " "); 126 | } else { 127 | cmark_strbuf_puts(renderer->html, " "); 128 | } 129 | } else { 130 | cmark_strbuf_puts(renderer->html, "\n"); 131 | } 132 | } 133 | 134 | static const char *xml_attr(cmark_syntax_extension *extension, 135 | cmark_node *node) { 136 | if (node->as.list.checked) { 137 | return " completed=\"true\""; 138 | } else { 139 | return " completed=\"false\""; 140 | } 141 | } 142 | 143 | cmark_syntax_extension *create_tasklist_extension(void) { 144 | cmark_syntax_extension *ext = cmark_syntax_extension_new("tasklist"); 145 | 146 | cmark_syntax_extension_set_match_block_func(ext, matches); 147 | cmark_syntax_extension_set_get_type_string_func(ext, get_type_string); 148 | cmark_syntax_extension_set_open_block_func(ext, open_tasklist_item); 149 | cmark_syntax_extension_set_can_contain_func(ext, can_contain); 150 | cmark_syntax_extension_set_commonmark_render_func(ext, commonmark_render); 151 | cmark_syntax_extension_set_plaintext_render_func(ext, commonmark_render); 152 | cmark_syntax_extension_set_html_render_func(ext, html_render); 153 | cmark_syntax_extension_set_xml_attr_func(ext, xml_attr); 154 | 155 | return ext; 156 | } 157 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Models/Nodes/Node.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import libcmark 3 | 4 | public class Node { 5 | 6 | // MARK: - Properties 7 | 8 | /// Underlying `cmark_node` pointer 9 | let node: UnsafeMutablePointer 10 | 11 | /// Node type 12 | public var kind: Kind { 13 | cmark_node_get_type(node) 14 | } 15 | 16 | public var parent: Node? { 17 | cmark_node_parent(node).map { Node.with($0, document: document) } 18 | } 19 | 20 | /// All child nodes 21 | public var children: NodeList { 22 | firstChild.flatMap(NodeList.init) ?? NodeList(nil) 23 | } 24 | 25 | public var firstChild: Node? { 26 | guard let child = cmark_node_first_child(node) else { 27 | return nil 28 | } 29 | 30 | return Node.with(child, document: document) 31 | } 32 | 33 | /// Start location 34 | public var start: Location? { 35 | Location(line: Int(cmark_node_get_start_line(node)), column: Int(cmark_node_get_start_column(node))) 36 | } 37 | 38 | /// End location 39 | public var end: Location? { 40 | Location(line: Int(cmark_node_get_end_line(node)), column: Int(cmark_node_get_end_column(node))) 41 | } 42 | 43 | /// String content 44 | /// 45 | /// - note: This is only present for block nodes and inline `text` ndoes. 46 | public var content: String? { 47 | cmark_node_get_literal(node).map(String.init) ?? String(node.pointee.content) 48 | } 49 | 50 | public private(set) weak var document: Document? 51 | 52 | /// Range of the node in the document’s string 53 | public var range: NSRange? { 54 | guard let start = start else { 55 | return nil 56 | } 57 | 58 | // cmark can return a -1 range that references the previous line, causing 59 | // exceptions to be thrown within various NSString/NSAttributedString APIs. 60 | if let end = end, start.line > end.line { 61 | return nil 62 | } 63 | 64 | guard let content = document?.content.map(NSString.init) else { 65 | assertionFailure("Missing `document.content`") 66 | return nil 67 | } 68 | 69 | var range = content.range(start: start, end: end) 70 | let max = NSMaxRange(range) 71 | let contentLength = content.length 72 | 73 | // cmark sometimes adds an extra character since internally it always ensures the string ends with a new line. 74 | // If it's longer than the document’s string, just clip off the end. 75 | if max > contentLength { 76 | range.length -= contentLength - max 77 | } 78 | 79 | return range 80 | } 81 | 82 | public var delimiters: [NSRange]? { 83 | guard let childRange = firstChild?.range, let range = range, range != childRange else { 84 | return nil 85 | } 86 | 87 | return [ 88 | NSRange(location: range.location, length: childRange.location - range.location), 89 | NSRange(location: NSMaxRange(childRange), length: NSMaxRange(range) - NSMaxRange(childRange)) 90 | ].filter { $0.length > 0} 91 | } 92 | 93 | /// Recursive description 94 | public var recursiveDescription: String { 95 | var output = description 96 | 97 | children.forEach { node in 98 | for line in node.recursiveDescription.split(separator: "\n") { 99 | output += "\n \(line)" 100 | } 101 | } 102 | 103 | return output 104 | } 105 | 106 | // MARK: - Initializers 107 | 108 | /// Initialize with a `cmark_node` pointer 109 | init(_ node: UnsafeMutablePointer, document: Document? = nil) { 110 | self.node = node 111 | self.document = document 112 | } 113 | 114 | static func with(_ node: UnsafeMutablePointer, document: Document? = nil) -> Node { 115 | switch cmark_node_get_type(node) { 116 | case .heading: 117 | return Heading(node, document: document) 118 | case .thematicBreak: 119 | return ThematicBreak(node, document: document) 120 | case .item: 121 | return ListItem(node, document: document) 122 | case .link: 123 | return Link(node, document: document) 124 | case .image: 125 | return Image(node, document: document) 126 | default: 127 | return Node(node, document: document) 128 | } 129 | } 130 | 131 | // MARK: - Querying 132 | 133 | public func contains(_ index: Int) -> Bool { 134 | range.flatMap { $0.contains(index) } ?? false 135 | } 136 | 137 | /// Find the deepest node at `index`. 138 | /// 139 | /// If `kind` is specified, it will stop at the first node of that kind at `index`. 140 | public func node(at index: Int, kind: Kind? = nil) -> Node? { 141 | guard contains(index) else { 142 | return nil 143 | } 144 | 145 | guard let match = children.first(where: { $0.contains(index) }) else { 146 | return self 147 | } 148 | 149 | if match.kind == kind { 150 | return match 151 | } 152 | 153 | return match.node(at: index, kind: kind) ?? match 154 | } 155 | 156 | public func node(for range: NSRange) -> Node? { 157 | guard let selfRange = self.range else { 158 | return nil 159 | } 160 | 161 | // The range is invalid 162 | var range = range 163 | if NSIntersectionRange(selfRange, range).length == 0 { 164 | range.length = 1 165 | } 166 | 167 | if NSIntersectionRange(selfRange, range).length == 0 { 168 | return nil 169 | } 170 | 171 | for child in children { 172 | if NSIntersectionRange(selfRange, range).length > 0 { 173 | if let match = child.node(for: range) { 174 | return match 175 | } 176 | } 177 | } 178 | 179 | return self 180 | } 181 | } 182 | 183 | extension Node: CustomStringConvertible { 184 | public var description: String { 185 | "" 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /Sources/libcmark/src/syntax_extension.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "cmark-gfm.h" 5 | #include "syntax_extension.h" 6 | #include "buffer.h" 7 | 8 | extern cmark_mem CMARK_DEFAULT_MEM_ALLOCATOR; 9 | 10 | static cmark_mem *_mem = &CMARK_DEFAULT_MEM_ALLOCATOR; 11 | 12 | void cmark_syntax_extension_free(cmark_mem *mem, cmark_syntax_extension *extension) { 13 | if (extension->free_function && extension->priv) { 14 | extension->free_function(mem, extension->priv); 15 | } 16 | 17 | cmark_llist_free(mem, extension->special_inline_chars); 18 | mem->free(extension->name); 19 | mem->free(extension); 20 | } 21 | 22 | cmark_syntax_extension *cmark_syntax_extension_new(const char *name) { 23 | cmark_syntax_extension *res = (cmark_syntax_extension *) _mem->calloc(1, sizeof(cmark_syntax_extension)); 24 | res->name = (char *) _mem->calloc(1, sizeof(char) * (strlen(name)) + 1); 25 | strcpy(res->name, name); 26 | return res; 27 | } 28 | 29 | cmark_node_type cmark_syntax_extension_add_node(int is_inline) { 30 | cmark_node_type *ref = !is_inline ? &CMARK_NODE_LAST_BLOCK : &CMARK_NODE_LAST_INLINE; 31 | 32 | if ((*ref & CMARK_NODE_VALUE_MASK) == CMARK_NODE_VALUE_MASK) { 33 | assert(false); 34 | return (cmark_node_type) 0; 35 | } 36 | 37 | return *ref = (cmark_node_type) ((int) *ref + 1); 38 | } 39 | 40 | void cmark_syntax_extension_set_emphasis(cmark_syntax_extension *extension, 41 | int emphasis) { 42 | extension->emphasis = emphasis == 1; 43 | } 44 | 45 | void cmark_syntax_extension_set_open_block_func(cmark_syntax_extension *extension, 46 | cmark_open_block_func func) { 47 | extension->try_opening_block = func; 48 | } 49 | 50 | void cmark_syntax_extension_set_match_block_func(cmark_syntax_extension *extension, 51 | cmark_match_block_func func) { 52 | extension->last_block_matches = func; 53 | } 54 | 55 | void cmark_syntax_extension_set_match_inline_func(cmark_syntax_extension *extension, 56 | cmark_match_inline_func func) { 57 | extension->match_inline = func; 58 | } 59 | 60 | void cmark_syntax_extension_set_inline_from_delim_func(cmark_syntax_extension *extension, 61 | cmark_inline_from_delim_func func) { 62 | extension->insert_inline_from_delim = func; 63 | } 64 | 65 | void cmark_syntax_extension_set_special_inline_chars(cmark_syntax_extension *extension, 66 | cmark_llist *special_chars) { 67 | extension->special_inline_chars = special_chars; 68 | } 69 | 70 | void cmark_syntax_extension_set_get_type_string_func(cmark_syntax_extension *extension, 71 | cmark_get_type_string_func func) { 72 | extension->get_type_string_func = func; 73 | } 74 | 75 | void cmark_syntax_extension_set_can_contain_func(cmark_syntax_extension *extension, 76 | cmark_can_contain_func func) { 77 | extension->can_contain_func = func; 78 | } 79 | 80 | void cmark_syntax_extension_set_contains_inlines_func(cmark_syntax_extension *extension, 81 | cmark_contains_inlines_func func) { 82 | extension->contains_inlines_func = func; 83 | } 84 | 85 | void cmark_syntax_extension_set_commonmark_render_func(cmark_syntax_extension *extension, 86 | cmark_common_render_func func) { 87 | extension->commonmark_render_func = func; 88 | } 89 | 90 | void cmark_syntax_extension_set_plaintext_render_func(cmark_syntax_extension *extension, 91 | cmark_common_render_func func) { 92 | extension->plaintext_render_func = func; 93 | } 94 | 95 | void cmark_syntax_extension_set_latex_render_func(cmark_syntax_extension *extension, 96 | cmark_common_render_func func) { 97 | extension->latex_render_func = func; 98 | } 99 | 100 | void cmark_syntax_extension_set_xml_attr_func(cmark_syntax_extension *extension, 101 | cmark_xml_attr_func func) { 102 | extension->xml_attr_func = func; 103 | } 104 | 105 | void cmark_syntax_extension_set_man_render_func(cmark_syntax_extension *extension, 106 | cmark_common_render_func func) { 107 | extension->man_render_func = func; 108 | } 109 | 110 | void cmark_syntax_extension_set_html_render_func(cmark_syntax_extension *extension, 111 | cmark_html_render_func func) { 112 | extension->html_render_func = func; 113 | } 114 | 115 | void cmark_syntax_extension_set_html_filter_func(cmark_syntax_extension *extension, 116 | cmark_html_filter_func func) { 117 | extension->html_filter_func = func; 118 | } 119 | 120 | void cmark_syntax_extension_set_postprocess_func(cmark_syntax_extension *extension, 121 | cmark_postprocess_func func) { 122 | extension->postprocess_func = func; 123 | } 124 | 125 | void cmark_syntax_extension_set_private(cmark_syntax_extension *extension, 126 | void *priv, 127 | cmark_free_func free_func) { 128 | extension->priv = priv; 129 | extension->free_function = free_func; 130 | } 131 | 132 | void *cmark_syntax_extension_get_private(cmark_syntax_extension *extension) { 133 | return extension->priv; 134 | } 135 | 136 | void cmark_syntax_extension_set_opaque_alloc_func(cmark_syntax_extension *extension, 137 | cmark_opaque_alloc_func func) { 138 | extension->opaque_alloc_func = func; 139 | } 140 | 141 | void cmark_syntax_extension_set_opaque_free_func(cmark_syntax_extension *extension, 142 | cmark_opaque_free_func func) { 143 | extension->opaque_free_func = func; 144 | } 145 | 146 | void cmark_syntax_extension_set_commonmark_escape_func(cmark_syntax_extension *extension, 147 | cmark_commonmark_escape_func func) { 148 | extension->commonmark_escape_func = func; 149 | } 150 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Themes/DefaultTheme.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | /// Theme with some default UIKit colors 4 | open class DefaultTheme: Theme { 5 | 6 | // MARK: - Properties 7 | 8 | open var secondaryForegroundColor: UIColor { 9 | .secondaryLabel 10 | } 11 | 12 | open var linkColor: UIColor { 13 | .link 14 | } 15 | 16 | open var delimiterColor: UIColor { 17 | foregroundColor.withAlphaComponent(0.5) 18 | } 19 | 20 | // MARK: - Theme 21 | 22 | open override var font: UIFont { 23 | return UIFont.monospacedSystemFont(ofSize: UIFont.preferredFont(forTextStyle: .body).pointSize, 24 | weight: .regular) 25 | } 26 | 27 | open override var baseAttributes: [NSAttributedString.Key: Any] { 28 | let paragraph = NSMutableParagraphStyle() 29 | paragraph.lineHeightMultiple = 1.25 30 | 31 | return [ 32 | .font: font, 33 | .foregroundColor: foregroundColor, 34 | .paragraphStyle: paragraph 35 | ] 36 | } 37 | 38 | open override func blockquote(_ node: Node, range: NSRange) -> [Style] { 39 | [Style(range: range, attributes: [.foregroundColor: secondaryForegroundColor])] + delimiterStyles(for: node) 40 | } 41 | 42 | open override func codeBlock(_ node: Node, range: NSRange) -> [Style] { 43 | [Style(range: range, attributes: [.foregroundColor: secondaryForegroundColor])] 44 | } 45 | 46 | open override func htmlBlock(_ node: Node, range: NSRange) -> [Style] { 47 | [Style(range: range, attributes: [.foregroundColor: secondaryForegroundColor])] 48 | } 49 | 50 | open override func heading(_ node: Heading, range: NSRange) -> [Style] { 51 | let attributes: [NSAttributedString.Key: Any] 52 | switch node.level { 53 | case .one: 54 | attributes = [ 55 | .fontTraits: UIFontDescriptor.SymbolicTraits.traitBold, 56 | .foregroundColor: foregroundColor 57 | ] 58 | 59 | case .two: 60 | attributes = [ 61 | .fontTraits: UIFontDescriptor.SymbolicTraits.traitBold 62 | ] 63 | 64 | case .three: 65 | attributes = [ 66 | .fontTraits: UIFontDescriptor.SymbolicTraits.traitBold, 67 | .foregroundColor: secondaryForegroundColor 68 | ] 69 | 70 | case .four: 71 | attributes = [ 72 | .fontTraits: UIFontDescriptor.SymbolicTraits.traitBold, 73 | .foregroundColor: secondaryForegroundColor 74 | ] 75 | 76 | case .five: 77 | attributes = [ 78 | .foregroundColor: secondaryForegroundColor 79 | ] 80 | 81 | case .six: 82 | attributes = [ 83 | .foregroundColor: secondaryForegroundColor 84 | ] 85 | } 86 | 87 | return [Style(range: range, attributes: attributes)] + delimiterStyles(for: node) 88 | } 89 | 90 | open override func item(_ node: ListItem, range: NSRange) -> [Style] { 91 | delimiterStyles(for: node) 92 | } 93 | 94 | open override func thematicBreak(_ node: Node, range: NSRange) -> [Style] { 95 | [ 96 | Style(range: range, attributes: [ 97 | .thematicBreakColor: foregroundColor.withAlphaComponent(0.2) 98 | ]) 99 | ] + delimiterStyles(for: node) 100 | } 101 | 102 | open override func codeInline(_ node: Node, range: NSRange) -> [Style] { 103 | [Style(range: range, attributes: [.foregroundColor: secondaryForegroundColor])] + delimiterStyles(for: node) 104 | } 105 | 106 | open override func htmlInline(_ node: Node, range: NSRange) -> [Style] { 107 | [Style(range: range, attributes: [.foregroundColor: secondaryForegroundColor])] 108 | } 109 | 110 | open override func emphasis(_ node: Node, range: NSRange) -> [Style] { 111 | [Style(range: range, attributes: [.fontTraits: UIFontDescriptor.SymbolicTraits.traitItalic])] 112 | + delimiterStyles(for: node) 113 | } 114 | 115 | open override func strong(_ node: Node, range: NSRange) -> [Style] { 116 | [Style(range: range, attributes: [.fontTraits: UIFontDescriptor.SymbolicTraits.traitBold])] 117 | + delimiterStyles(for: node) 118 | } 119 | 120 | open override func link(_ node: Link, range: NSRange) -> [Style] { 121 | [Style(range: range, attributes: [.foregroundColor: linkColor])] 122 | + delimiterStyles(for: node, attributes: [.foregroundColor: linkColor.withAlphaComponent(0.5)]) 123 | + urlStyles(for: node) 124 | } 125 | 126 | open override func image(_ node: Image, range: NSRange) -> [Style] { 127 | [Style(range: range, attributes: [.foregroundColor: linkColor])] 128 | + delimiterStyles(for: node, attributes: [.foregroundColor: linkColor.withAlphaComponent(0.5)]) 129 | + urlStyles(for: node) 130 | } 131 | 132 | open override func strikethrough(_ node: Node, range: NSRange) -> [Style] { 133 | [ 134 | Style(range: range, attributes: [.foregroundColor: secondaryForegroundColor]), 135 | Style(range: node.firstChild!.range!, attributes: [ 136 | .strikethroughStyle: NSUnderlineStyle.single.rawValue, 137 | .strikethroughColor: foregroundColor 138 | ]) 139 | ] + delimiterStyles(for: node) 140 | } 141 | 142 | // MARK: - Initializers 143 | 144 | public override init() { 145 | super.init() 146 | } 147 | 148 | // MARK: - Utilities 149 | 150 | func delimiterStyles(for node: Node, attributes: [NSAttributedString.Key: Any]? = nil) -> [Style] { 151 | guard let ranges = node.delimiters else { 152 | return [] 153 | } 154 | 155 | let attributes = attributes ?? [.foregroundColor: delimiterColor] 156 | 157 | return ranges.map { range in 158 | Style(range: range, attributes: attributes) 159 | } 160 | } 161 | 162 | func urlStyles(for node: Link, 163 | attributes: [NSAttributedString.Key: Any] = [.underlineStyle: NSUnderlineStyle.single.rawValue]) 164 | -> [Style] 165 | { 166 | guard let urlRange = node.urlRange else { 167 | return [] 168 | } 169 | 170 | return [Style(range: urlRange, attributes: attributes)] 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /Sources/libcmark/src/xml.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "config.h" 7 | #include "cmark-gfm.h" 8 | #include "node.h" 9 | #include "buffer.h" 10 | #include "houdini.h" 11 | #include "syntax_extension.h" 12 | 13 | #define BUFFER_SIZE 100 14 | 15 | // Functions to convert cmark_nodes to XML strings. 16 | 17 | static void escape_xml(cmark_strbuf *dest, const unsigned char *source, 18 | bufsize_t length) { 19 | houdini_escape_html0(dest, source, length, 0); 20 | } 21 | 22 | struct render_state { 23 | cmark_strbuf *xml; 24 | int indent; 25 | }; 26 | 27 | static CMARK_INLINE void indent(struct render_state *state) { 28 | int i; 29 | for (i = 0; i < state->indent; i++) { 30 | cmark_strbuf_putc(state->xml, ' '); 31 | } 32 | } 33 | 34 | static int S_render_node(cmark_node *node, cmark_event_type ev_type, 35 | struct render_state *state, int options) { 36 | cmark_strbuf *xml = state->xml; 37 | bool literal = false; 38 | cmark_delim_type delim; 39 | bool entering = (ev_type == CMARK_EVENT_ENTER); 40 | char buffer[BUFFER_SIZE]; 41 | 42 | if (entering) { 43 | indent(state); 44 | cmark_strbuf_putc(xml, '<'); 45 | cmark_strbuf_puts(xml, cmark_node_get_type_string(node)); 46 | 47 | if (options & CMARK_OPT_SOURCEPOS && node->start_line != 0) { 48 | snprintf(buffer, BUFFER_SIZE, " sourcepos=\"%d:%d-%d:%d\"", 49 | node->start_line, node->start_column, node->end_line, 50 | node->end_column); 51 | cmark_strbuf_puts(xml, buffer); 52 | } 53 | 54 | if (node->extension && node->extension->xml_attr_func) { 55 | const char* r = node->extension->xml_attr_func(node->extension, node); 56 | if (r != NULL) 57 | cmark_strbuf_puts(xml, r); 58 | } 59 | 60 | literal = false; 61 | 62 | switch (node->type) { 63 | case CMARK_NODE_DOCUMENT: 64 | cmark_strbuf_puts(xml, " xmlns=\"http://commonmark.org/xml/1.0\""); 65 | break; 66 | case CMARK_NODE_TEXT: 67 | case CMARK_NODE_CODE: 68 | case CMARK_NODE_HTML_BLOCK: 69 | case CMARK_NODE_HTML_INLINE: 70 | cmark_strbuf_puts(xml, " xml:space=\"preserve\">"); 71 | escape_xml(xml, node->as.literal.data, node->as.literal.len); 72 | cmark_strbuf_puts(xml, "as.heading.level); 102 | cmark_strbuf_puts(xml, buffer); 103 | break; 104 | case CMARK_NODE_CODE_BLOCK: 105 | if (node->as.code.info.len > 0) { 106 | cmark_strbuf_puts(xml, " info=\""); 107 | escape_xml(xml, node->as.code.info.data, node->as.code.info.len); 108 | cmark_strbuf_putc(xml, '"'); 109 | } 110 | cmark_strbuf_puts(xml, " xml:space=\"preserve\">"); 111 | escape_xml(xml, node->as.code.literal.data, node->as.code.literal.len); 112 | cmark_strbuf_puts(xml, "as.custom.on_enter.data, 120 | node->as.custom.on_enter.len); 121 | cmark_strbuf_putc(xml, '"'); 122 | cmark_strbuf_puts(xml, " on_exit=\""); 123 | escape_xml(xml, node->as.custom.on_exit.data, 124 | node->as.custom.on_exit.len); 125 | cmark_strbuf_putc(xml, '"'); 126 | break; 127 | case CMARK_NODE_LINK: 128 | case CMARK_NODE_IMAGE: 129 | cmark_strbuf_puts(xml, " destination=\""); 130 | escape_xml(xml, node->as.link.url.data, node->as.link.url.len); 131 | cmark_strbuf_putc(xml, '"'); 132 | cmark_strbuf_puts(xml, " title=\""); 133 | escape_xml(xml, node->as.link.title.data, node->as.link.title.len); 134 | cmark_strbuf_putc(xml, '"'); 135 | break; 136 | default: 137 | break; 138 | } 139 | if (node->first_child) { 140 | state->indent += 2; 141 | } else if (!literal) { 142 | cmark_strbuf_puts(xml, " /"); 143 | } 144 | cmark_strbuf_puts(xml, ">\n"); 145 | 146 | } else if (node->first_child) { 147 | state->indent -= 2; 148 | indent(state); 149 | cmark_strbuf_puts(xml, "\n"); 152 | } 153 | 154 | return 1; 155 | } 156 | 157 | char *cmark_render_xml(cmark_node *root, int options) { 158 | return cmark_render_xml_with_mem(root, options, cmark_node_mem(root)); 159 | } 160 | 161 | char *cmark_render_xml_with_mem(cmark_node *root, int options, cmark_mem *mem) { 162 | char *result; 163 | cmark_strbuf xml = CMARK_BUF_INIT(mem); 164 | cmark_event_type ev_type; 165 | cmark_node *cur; 166 | struct render_state state = {&xml, 0}; 167 | 168 | cmark_iter *iter = cmark_iter_new(root); 169 | 170 | cmark_strbuf_puts(state.xml, "\n"); 171 | cmark_strbuf_puts(state.xml, 172 | "\n"); 173 | while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { 174 | cur = cmark_iter_get_node(iter); 175 | S_render_node(cur, ev_type, &state, options); 176 | } 177 | result = (char *)cmark_strbuf_detach(&xml); 178 | 179 | cmark_iter_free(iter); 180 | return result; 181 | } 182 | -------------------------------------------------------------------------------- /Sources/libcmark/extensions/strikethrough.c: -------------------------------------------------------------------------------- 1 | #include "strikethrough.h" 2 | #include 3 | #include 4 | 5 | cmark_node_type CMARK_NODE_STRIKETHROUGH; 6 | 7 | static cmark_node *match(cmark_syntax_extension *self, cmark_parser *parser, 8 | cmark_node *parent, unsigned char character, 9 | cmark_inline_parser *inline_parser) { 10 | cmark_node *res = NULL; 11 | int left_flanking, right_flanking, punct_before, punct_after, delims; 12 | char buffer[101]; 13 | 14 | if (character != '~') 15 | return NULL; 16 | 17 | delims = cmark_inline_parser_scan_delimiters( 18 | inline_parser, sizeof(buffer) - 1, '~', 19 | &left_flanking, 20 | &right_flanking, &punct_before, &punct_after); 21 | 22 | memset(buffer, '~', delims); 23 | buffer[delims] = 0; 24 | 25 | res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); 26 | cmark_node_set_literal(res, buffer); 27 | res->start_line = res->end_line = cmark_inline_parser_get_line(inline_parser); 28 | res->start_column = cmark_inline_parser_get_column(inline_parser) - delims; 29 | 30 | if ((left_flanking || right_flanking) && 31 | (delims == 2 || (!(parser->options & CMARK_OPT_STRIKETHROUGH_DOUBLE_TILDE) && delims == 1))) { 32 | cmark_inline_parser_push_delimiter(inline_parser, character, left_flanking, 33 | right_flanking, res); 34 | } 35 | 36 | return res; 37 | } 38 | 39 | static delimiter *insert(cmark_syntax_extension *self, cmark_parser *parser, 40 | cmark_inline_parser *inline_parser, delimiter *opener, 41 | delimiter *closer) { 42 | cmark_node *strikethrough; 43 | cmark_node *tmp, *next; 44 | delimiter *delim, *tmp_delim; 45 | delimiter *res = closer->next; 46 | 47 | strikethrough = opener->inl_text; 48 | 49 | if (opener->inl_text->as.literal.len != closer->inl_text->as.literal.len) 50 | goto done; 51 | 52 | if (!cmark_node_set_type(strikethrough, CMARK_NODE_STRIKETHROUGH)) 53 | goto done; 54 | 55 | cmark_node_set_syntax_extension(strikethrough, self); 56 | 57 | tmp = cmark_node_next(opener->inl_text); 58 | 59 | while (tmp) { 60 | if (tmp == closer->inl_text) 61 | break; 62 | next = cmark_node_next(tmp); 63 | cmark_node_append_child(strikethrough, tmp); 64 | tmp = next; 65 | } 66 | 67 | strikethrough->end_column = closer->inl_text->start_column + closer->inl_text->as.literal.len - 1; 68 | cmark_node_free(closer->inl_text); 69 | 70 | delim = closer; 71 | while (delim != NULL && delim != opener) { 72 | tmp_delim = delim->previous; 73 | cmark_inline_parser_remove_delimiter(inline_parser, delim); 74 | delim = tmp_delim; 75 | } 76 | 77 | cmark_inline_parser_remove_delimiter(inline_parser, opener); 78 | 79 | done: 80 | return res; 81 | } 82 | 83 | static const char *get_type_string(cmark_syntax_extension *extension, 84 | cmark_node *node) { 85 | return node->type == CMARK_NODE_STRIKETHROUGH ? "strikethrough" : ""; 86 | } 87 | 88 | static int can_contain(cmark_syntax_extension *extension, cmark_node *node, 89 | cmark_node_type child_type) { 90 | if (node->type != CMARK_NODE_STRIKETHROUGH) 91 | return false; 92 | 93 | return CMARK_NODE_TYPE_INLINE_P(child_type); 94 | } 95 | 96 | static void commonmark_render(cmark_syntax_extension *extension, 97 | cmark_renderer *renderer, cmark_node *node, 98 | cmark_event_type ev_type, int options) { 99 | renderer->out(renderer, node, "~~", false, LITERAL); 100 | } 101 | 102 | static void latex_render(cmark_syntax_extension *extension, 103 | cmark_renderer *renderer, cmark_node *node, 104 | cmark_event_type ev_type, int options) { 105 | // requires \usepackage{ulem} 106 | bool entering = (ev_type == CMARK_EVENT_ENTER); 107 | if (entering) { 108 | renderer->out(renderer, node, "\\sout{", false, LITERAL); 109 | } else { 110 | renderer->out(renderer, node, "}", false, LITERAL); 111 | } 112 | } 113 | 114 | static void man_render(cmark_syntax_extension *extension, 115 | cmark_renderer *renderer, cmark_node *node, 116 | cmark_event_type ev_type, int options) { 117 | bool entering = (ev_type == CMARK_EVENT_ENTER); 118 | if (entering) { 119 | renderer->cr(renderer); 120 | renderer->out(renderer, node, ".ST \"", false, LITERAL); 121 | } else { 122 | renderer->out(renderer, node, "\"", false, LITERAL); 123 | renderer->cr(renderer); 124 | } 125 | } 126 | 127 | static void html_render(cmark_syntax_extension *extension, 128 | cmark_html_renderer *renderer, cmark_node *node, 129 | cmark_event_type ev_type, int options) { 130 | bool entering = (ev_type == CMARK_EVENT_ENTER); 131 | if (entering) { 132 | cmark_strbuf_puts(renderer->html, ""); 133 | } else { 134 | cmark_strbuf_puts(renderer->html, ""); 135 | } 136 | } 137 | 138 | static void plaintext_render(cmark_syntax_extension *extension, 139 | cmark_renderer *renderer, cmark_node *node, 140 | cmark_event_type ev_type, int options) { 141 | renderer->out(renderer, node, "~", false, LITERAL); 142 | } 143 | 144 | cmark_syntax_extension *create_strikethrough_extension(void) { 145 | cmark_syntax_extension *ext = cmark_syntax_extension_new("strikethrough"); 146 | cmark_llist *special_chars = NULL; 147 | 148 | cmark_syntax_extension_set_get_type_string_func(ext, get_type_string); 149 | cmark_syntax_extension_set_can_contain_func(ext, can_contain); 150 | cmark_syntax_extension_set_commonmark_render_func(ext, commonmark_render); 151 | cmark_syntax_extension_set_latex_render_func(ext, latex_render); 152 | cmark_syntax_extension_set_man_render_func(ext, man_render); 153 | cmark_syntax_extension_set_html_render_func(ext, html_render); 154 | cmark_syntax_extension_set_plaintext_render_func(ext, plaintext_render); 155 | CMARK_NODE_STRIKETHROUGH = cmark_syntax_extension_add_node(1); 156 | 157 | cmark_syntax_extension_set_match_inline_func(ext, match); 158 | cmark_syntax_extension_set_inline_from_delim_func(ext, insert); 159 | 160 | cmark_mem *mem = cmark_get_default_mem_allocator(); 161 | special_chars = cmark_llist_append(mem, special_chars, (void *)'~'); 162 | cmark_syntax_extension_set_special_inline_chars(ext, special_chars); 163 | 164 | cmark_syntax_extension_set_emphasis(ext, 1); 165 | 166 | return ext; 167 | } 168 | -------------------------------------------------------------------------------- /Sources/MarkdownKit/Themes/Theme.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | /// Base theme with very minimal styling 4 | open class Theme { 5 | 6 | // MARK: - Properties 7 | 8 | open var foregroundColor: UIColor { 9 | .label 10 | } 11 | 12 | open var backgroundColor: UIColor { 13 | .systemBackground 14 | } 15 | 16 | open var font: UIFont { 17 | UIFont.preferredFont(forTextStyle: .body) 18 | } 19 | 20 | open var baseAttributes: [NSAttributedString.Key: Any] { 21 | [ 22 | .font: font, 23 | .foregroundColor: foregroundColor 24 | ] 25 | } 26 | 27 | open func styles(for node: Node, range: NSRange) -> [Style] { 28 | switch node.kind { 29 | case .document: 30 | guard let node = node as? Document else { 31 | assertionFailure("Expected `Document` node") 32 | return [] 33 | } 34 | 35 | return document(node, range: range) 36 | 37 | case .blockquote: 38 | return blockquote(node, range: range) 39 | 40 | case .list: 41 | return list(node, range: range) 42 | 43 | case .item: 44 | guard let node = node as? ListItem else { 45 | assertionFailure("Expected `ListItem` node") 46 | return [] 47 | } 48 | return item(node, range: range) 49 | 50 | case .codeBlock: 51 | return codeBlock(node, range: range) 52 | 53 | case .htmlBlock: 54 | return htmlBlock(node, range: range) 55 | 56 | case .customBlock: 57 | return customBlock(node, range: range) 58 | 59 | case .paragraph: 60 | return paragraph(node, range: range) 61 | 62 | case .heading: 63 | guard let node = node as? Heading else { 64 | assertionFailure("Expected `Heading` node") 65 | return [] 66 | } 67 | 68 | return heading(node, range: range) 69 | 70 | case .thematicBreak: 71 | guard let node = node as? ThematicBreak else { 72 | assertionFailure("Expected `ThematicBreak` node") 73 | return [] 74 | } 75 | 76 | return thematicBreak(node, range: range) 77 | 78 | case .table: 79 | return table(node, range: range) 80 | 81 | case .tableRow: 82 | return tableRow(node, range: range) 83 | 84 | case .tableCell: 85 | return tableCell(node, range: range) 86 | 87 | case .text: 88 | return text(node, range: range) 89 | 90 | case .softBreak: 91 | return softBreak(node, range: range) 92 | 93 | case .lineBreak: 94 | return lineBreak(node, range: range) 95 | 96 | case .codeInline: 97 | return codeInline(node, range: range) 98 | 99 | case .htmlInline: 100 | return htmlInline(node, range: range) 101 | 102 | case .customInline: 103 | return customInline(node, range: range) 104 | 105 | case .emphasis: 106 | return emphasis(node, range: range) 107 | 108 | case .strong: 109 | return strong(node, range: range) 110 | 111 | case .link: 112 | guard let node = node as? Link else { 113 | assertionFailure("Expected `Link` node") 114 | return [] 115 | } 116 | 117 | return link(node, range: range) 118 | 119 | case .image: 120 | guard let node = node as? Image else { 121 | assertionFailure("Expected `Image` node") 122 | return [] 123 | } 124 | 125 | return image(node, range: range) 126 | 127 | case .strikethrough: 128 | return strikethrough(node, range: range) 129 | 130 | default: 131 | assertionFailure("Unknown node kind") 132 | return [] 133 | } 134 | } 135 | 136 | open func document(_ node: Document, range: NSRange) -> [Style] { 137 | [] 138 | } 139 | 140 | open func blockquote(_ node: Node, range: NSRange) -> [Style] { 141 | [] 142 | } 143 | 144 | open func list(_ node: Node, range: NSRange) -> [Style] { 145 | [] 146 | } 147 | 148 | open func item(_ node: ListItem, range: NSRange) -> [Style] { 149 | [] 150 | } 151 | 152 | open func codeBlock(_ node: Node, range: NSRange) -> [Style] { 153 | [] 154 | } 155 | 156 | open func htmlBlock(_ node: Node, range: NSRange) -> [Style] { 157 | [] 158 | } 159 | 160 | open func customBlock(_ node: Node, range: NSRange) -> [Style] { 161 | [] 162 | } 163 | 164 | open func paragraph(_ node: Node, range: NSRange) -> [Style] { 165 | [] 166 | } 167 | 168 | open func heading(_ node: Heading, range: NSRange) -> [Style] { 169 | [] 170 | } 171 | 172 | open func thematicBreak(_ node: ThematicBreak, range: NSRange) -> [Style] { 173 | [] 174 | } 175 | 176 | open func table(_ node: Node, range: NSRange) -> [Style] { 177 | [] 178 | } 179 | 180 | open func tableRow(_ node: Node, range: NSRange) -> [Style] { 181 | [] 182 | } 183 | 184 | open func tableCell(_ node: Node, range: NSRange) -> [Style] { 185 | [] 186 | } 187 | 188 | open func text(_ node: Node, range: NSRange) -> [Style] { 189 | [] 190 | } 191 | 192 | open func softBreak(_ node: Node, range: NSRange) -> [Style] { 193 | [] 194 | } 195 | 196 | open func lineBreak(_ node: Node, range: NSRange) -> [Style] { 197 | [] 198 | } 199 | 200 | open func codeInline(_ node: Node, range: NSRange) -> [Style] { 201 | [] 202 | } 203 | 204 | open func htmlInline(_ node: Node, range: NSRange) -> [Style] { 205 | [] 206 | } 207 | 208 | open func customInline(_ node: Node, range: NSRange) -> [Style] { 209 | [] 210 | } 211 | 212 | open func emphasis(_ node: Node, range: NSRange) -> [Style] { 213 | [] 214 | } 215 | 216 | open func strong(_ node: Node, range: NSRange) -> [Style] { 217 | [] 218 | } 219 | 220 | open func link(_ node: Link, range: NSRange) -> [Style] { 221 | [] 222 | } 223 | 224 | open func image(_ node: Image, range: NSRange) -> [Style] { 225 | [] 226 | } 227 | 228 | open func strikethrough(_ node: Node, range: NSRange) -> [Style] { 229 | [] 230 | } 231 | 232 | // MARK: - Initializers 233 | 234 | public init() {} 235 | } 236 | -------------------------------------------------------------------------------- /Sources/libcmark/src/plaintext.c: -------------------------------------------------------------------------------- 1 | #include "node.h" 2 | #include "syntax_extension.h" 3 | #include "render.h" 4 | 5 | #define OUT(s, wrap, escaping) renderer->out(renderer, node, s, wrap, escaping) 6 | #define LIT(s) renderer->out(renderer, node, s, false, LITERAL) 7 | #define CR() renderer->cr(renderer) 8 | #define BLANKLINE() renderer->blankline(renderer) 9 | #define LISTMARKER_SIZE 20 10 | 11 | // Functions to convert cmark_nodes to plain text strings. 12 | 13 | static CMARK_INLINE void outc(cmark_renderer *renderer, cmark_node *node, 14 | cmark_escaping escape, 15 | int32_t c, unsigned char nextc) { 16 | cmark_render_code_point(renderer, c); 17 | } 18 | 19 | // if node is a block node, returns node. 20 | // otherwise returns first block-level node that is an ancestor of node. 21 | // if there is no block-level ancestor, returns NULL. 22 | static cmark_node *get_containing_block(cmark_node *node) { 23 | while (node) { 24 | if (CMARK_NODE_BLOCK_P(node)) { 25 | return node; 26 | } else { 27 | node = node->parent; 28 | } 29 | } 30 | return NULL; 31 | } 32 | 33 | static int S_render_node(cmark_renderer *renderer, cmark_node *node, 34 | cmark_event_type ev_type, int options) { 35 | cmark_node *tmp; 36 | int list_number; 37 | cmark_delim_type list_delim; 38 | int i; 39 | bool entering = (ev_type == CMARK_EVENT_ENTER); 40 | char listmarker[LISTMARKER_SIZE]; 41 | bool first_in_list_item; 42 | bufsize_t marker_width; 43 | bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options) && 44 | !(CMARK_OPT_HARDBREAKS & options); 45 | 46 | // Don't adjust tight list status til we've started the list. 47 | // Otherwise we loose the blank line between a paragraph and 48 | // a following list. 49 | if (!(node->type == CMARK_NODE_ITEM && node->prev == NULL && entering)) { 50 | tmp = get_containing_block(node); 51 | renderer->in_tight_list_item = 52 | tmp && // tmp might be NULL if there is no containing block 53 | ((tmp->type == CMARK_NODE_ITEM && 54 | cmark_node_get_list_tight(tmp->parent)) || 55 | (tmp && tmp->parent && tmp->parent->type == CMARK_NODE_ITEM && 56 | cmark_node_get_list_tight(tmp->parent->parent))); 57 | } 58 | 59 | if (node->extension && node->extension->plaintext_render_func) { 60 | node->extension->plaintext_render_func(node->extension, renderer, node, ev_type, options); 61 | return 1; 62 | } 63 | 64 | switch (node->type) { 65 | case CMARK_NODE_DOCUMENT: 66 | break; 67 | 68 | case CMARK_NODE_BLOCK_QUOTE: 69 | break; 70 | 71 | case CMARK_NODE_LIST: 72 | if (!entering && node->next && (node->next->type == CMARK_NODE_CODE_BLOCK || 73 | node->next->type == CMARK_NODE_LIST)) { 74 | CR(); 75 | } 76 | break; 77 | 78 | case CMARK_NODE_ITEM: 79 | if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { 80 | marker_width = 4; 81 | } else { 82 | list_number = cmark_node_get_list_start(node->parent); 83 | list_delim = cmark_node_get_list_delim(node->parent); 84 | tmp = node; 85 | while (tmp->prev) { 86 | tmp = tmp->prev; 87 | list_number += 1; 88 | } 89 | // we ensure a width of at least 4 so 90 | // we get nice transition from single digits 91 | // to double 92 | snprintf(listmarker, LISTMARKER_SIZE, "%d%s%s", list_number, 93 | list_delim == CMARK_PAREN_DELIM ? ")" : ".", 94 | list_number < 10 ? " " : " "); 95 | marker_width = (bufsize_t)strlen(listmarker); 96 | } 97 | if (entering) { 98 | if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { 99 | LIT(" - "); 100 | renderer->begin_content = true; 101 | } else { 102 | LIT(listmarker); 103 | renderer->begin_content = true; 104 | } 105 | for (i = marker_width; i--;) { 106 | cmark_strbuf_putc(renderer->prefix, ' '); 107 | } 108 | } else { 109 | cmark_strbuf_truncate(renderer->prefix, 110 | renderer->prefix->size - marker_width); 111 | CR(); 112 | } 113 | break; 114 | 115 | case CMARK_NODE_HEADING: 116 | if (entering) { 117 | renderer->begin_content = true; 118 | renderer->no_linebreaks = true; 119 | } else { 120 | renderer->no_linebreaks = false; 121 | BLANKLINE(); 122 | } 123 | break; 124 | 125 | case CMARK_NODE_CODE_BLOCK: 126 | first_in_list_item = node->prev == NULL && node->parent && 127 | node->parent->type == CMARK_NODE_ITEM; 128 | 129 | if (!first_in_list_item) { 130 | BLANKLINE(); 131 | } 132 | OUT(cmark_node_get_literal(node), false, LITERAL); 133 | BLANKLINE(); 134 | break; 135 | 136 | case CMARK_NODE_HTML_BLOCK: 137 | break; 138 | 139 | case CMARK_NODE_CUSTOM_BLOCK: 140 | break; 141 | 142 | case CMARK_NODE_THEMATIC_BREAK: 143 | BLANKLINE(); 144 | break; 145 | 146 | case CMARK_NODE_PARAGRAPH: 147 | if (!entering) { 148 | BLANKLINE(); 149 | } 150 | break; 151 | 152 | case CMARK_NODE_TEXT: 153 | OUT(cmark_node_get_literal(node), allow_wrap, NORMAL); 154 | break; 155 | 156 | case CMARK_NODE_LINEBREAK: 157 | CR(); 158 | break; 159 | 160 | case CMARK_NODE_SOFTBREAK: 161 | if (CMARK_OPT_HARDBREAKS & options) { 162 | CR(); 163 | } else if (!renderer->no_linebreaks && renderer->width == 0 && 164 | !(CMARK_OPT_HARDBREAKS & options) && 165 | !(CMARK_OPT_NOBREAKS & options)) { 166 | CR(); 167 | } else { 168 | OUT(" ", allow_wrap, LITERAL); 169 | } 170 | break; 171 | 172 | case CMARK_NODE_CODE: 173 | OUT(cmark_node_get_literal(node), allow_wrap, LITERAL); 174 | break; 175 | 176 | case CMARK_NODE_HTML_INLINE: 177 | break; 178 | 179 | case CMARK_NODE_CUSTOM_INLINE: 180 | break; 181 | 182 | case CMARK_NODE_STRONG: 183 | break; 184 | 185 | case CMARK_NODE_EMPH: 186 | break; 187 | 188 | case CMARK_NODE_LINK: 189 | break; 190 | 191 | case CMARK_NODE_IMAGE: 192 | break; 193 | 194 | case CMARK_NODE_FOOTNOTE_REFERENCE: 195 | if (entering) { 196 | LIT("[^"); 197 | OUT(cmark_chunk_to_cstr(renderer->mem, &node->as.literal), false, LITERAL); 198 | LIT("]"); 199 | } 200 | break; 201 | 202 | case CMARK_NODE_FOOTNOTE_DEFINITION: 203 | if (entering) { 204 | renderer->footnote_ix += 1; 205 | LIT("[^"); 206 | char n[32]; 207 | snprintf(n, sizeof(n), "%d", renderer->footnote_ix); 208 | OUT(n, false, LITERAL); 209 | LIT("]: "); 210 | 211 | cmark_strbuf_puts(renderer->prefix, " "); 212 | } else { 213 | cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 4); 214 | } 215 | break; 216 | default: 217 | assert(false); 218 | break; 219 | } 220 | 221 | return 1; 222 | } 223 | 224 | char *cmark_render_plaintext(cmark_node *root, int options, int width) { 225 | return cmark_render_plaintext_with_mem(root, options, width, cmark_node_mem(root)); 226 | } 227 | 228 | char *cmark_render_plaintext_with_mem(cmark_node *root, int options, int width, cmark_mem *mem) { 229 | if (options & CMARK_OPT_HARDBREAKS) { 230 | // disable breaking on width, since it has 231 | // a different meaning with OPT_HARDBREAKS 232 | width = 0; 233 | } 234 | return cmark_render(mem, root, options, width, outc, S_render_node); 235 | } 236 | -------------------------------------------------------------------------------- /Sources/libcmark/src/man.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "config.h" 7 | #include "cmark-gfm.h" 8 | #include "node.h" 9 | #include "buffer.h" 10 | #include "utf8.h" 11 | #include "render.h" 12 | #include "syntax_extension.h" 13 | 14 | #define OUT(s, wrap, escaping) renderer->out(renderer, node, s, wrap, escaping) 15 | #define LIT(s) renderer->out(renderer, node, s, false, LITERAL) 16 | #define CR() renderer->cr(renderer) 17 | #define BLANKLINE() renderer->blankline(renderer) 18 | #define LIST_NUMBER_SIZE 20 19 | 20 | // Functions to convert cmark_nodes to groff man strings. 21 | static void S_outc(cmark_renderer *renderer, cmark_node *node, 22 | cmark_escaping escape, int32_t c, 23 | unsigned char nextc) { 24 | (void)(nextc); 25 | 26 | if (escape == LITERAL) { 27 | cmark_render_code_point(renderer, c); 28 | return; 29 | } 30 | 31 | switch (c) { 32 | case 46: 33 | if (renderer->begin_line) { 34 | cmark_render_ascii(renderer, "\\&."); 35 | } else { 36 | cmark_render_code_point(renderer, c); 37 | } 38 | break; 39 | case 39: 40 | if (renderer->begin_line) { 41 | cmark_render_ascii(renderer, "\\&'"); 42 | } else { 43 | cmark_render_code_point(renderer, c); 44 | } 45 | break; 46 | case 45: 47 | cmark_render_ascii(renderer, "\\-"); 48 | break; 49 | case 92: 50 | cmark_render_ascii(renderer, "\\e"); 51 | break; 52 | case 8216: // left single quote 53 | cmark_render_ascii(renderer, "\\[oq]"); 54 | break; 55 | case 8217: // right single quote 56 | cmark_render_ascii(renderer, "\\[cq]"); 57 | break; 58 | case 8220: // left double quote 59 | cmark_render_ascii(renderer, "\\[lq]"); 60 | break; 61 | case 8221: // right double quote 62 | cmark_render_ascii(renderer, "\\[rq]"); 63 | break; 64 | case 8212: // em dash 65 | cmark_render_ascii(renderer, "\\[em]"); 66 | break; 67 | case 8211: // en dash 68 | cmark_render_ascii(renderer, "\\[en]"); 69 | break; 70 | default: 71 | cmark_render_code_point(renderer, c); 72 | } 73 | } 74 | 75 | static int S_render_node(cmark_renderer *renderer, cmark_node *node, 76 | cmark_event_type ev_type, int options) { 77 | cmark_node *tmp; 78 | int list_number; 79 | bool entering = (ev_type == CMARK_EVENT_ENTER); 80 | bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options); 81 | 82 | if (node->extension && node->extension->man_render_func) { 83 | node->extension->man_render_func(node->extension, renderer, node, ev_type, options); 84 | return 1; 85 | } 86 | 87 | switch (node->type) { 88 | case CMARK_NODE_DOCUMENT: 89 | if (entering) { 90 | /* Define a strikethrough macro */ 91 | /* Commenting out because this makes tests fail 92 | LIT(".de ST"); 93 | CR(); 94 | LIT(".nr ww \\w'\\\\$1'"); 95 | CR(); 96 | LIT("\\Z@\\v'-.25m'\\l'\\\\n[ww]u'@\\\\$1"); 97 | CR(); 98 | LIT(".."); 99 | CR(); 100 | */ 101 | } 102 | break; 103 | 104 | case CMARK_NODE_BLOCK_QUOTE: 105 | if (entering) { 106 | CR(); 107 | LIT(".RS"); 108 | CR(); 109 | } else { 110 | CR(); 111 | LIT(".RE"); 112 | CR(); 113 | } 114 | break; 115 | 116 | case CMARK_NODE_LIST: 117 | break; 118 | 119 | case CMARK_NODE_ITEM: 120 | if (entering) { 121 | CR(); 122 | LIT(".IP "); 123 | if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { 124 | LIT("\\[bu] 2"); 125 | } else { 126 | list_number = cmark_node_get_list_start(node->parent); 127 | tmp = node; 128 | while (tmp->prev) { 129 | tmp = tmp->prev; 130 | list_number += 1; 131 | } 132 | char list_number_s[LIST_NUMBER_SIZE]; 133 | snprintf(list_number_s, LIST_NUMBER_SIZE, "\"%d.\" 4", list_number); 134 | LIT(list_number_s); 135 | } 136 | CR(); 137 | } else { 138 | CR(); 139 | } 140 | break; 141 | 142 | case CMARK_NODE_HEADING: 143 | if (entering) { 144 | CR(); 145 | LIT(cmark_node_get_heading_level(node) == 1 ? ".SH" : ".SS"); 146 | CR(); 147 | } else { 148 | CR(); 149 | } 150 | break; 151 | 152 | case CMARK_NODE_CODE_BLOCK: 153 | CR(); 154 | LIT(".IP\n.nf\n\\f[C]\n"); 155 | OUT(cmark_node_get_literal(node), false, NORMAL); 156 | CR(); 157 | LIT("\\f[]\n.fi"); 158 | CR(); 159 | break; 160 | 161 | case CMARK_NODE_HTML_BLOCK: 162 | break; 163 | 164 | case CMARK_NODE_CUSTOM_BLOCK: 165 | CR(); 166 | OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node), 167 | false, LITERAL); 168 | CR(); 169 | break; 170 | 171 | case CMARK_NODE_THEMATIC_BREAK: 172 | CR(); 173 | LIT(".PP\n * * * * *"); 174 | CR(); 175 | break; 176 | 177 | case CMARK_NODE_PARAGRAPH: 178 | if (entering) { 179 | // no blank line if first paragraph in list: 180 | if (node->parent && node->parent->type == CMARK_NODE_ITEM && 181 | node->prev == NULL) { 182 | // no blank line or .PP 183 | } else { 184 | CR(); 185 | LIT(".PP"); 186 | CR(); 187 | } 188 | } else { 189 | CR(); 190 | } 191 | break; 192 | 193 | case CMARK_NODE_TEXT: 194 | OUT(cmark_node_get_literal(node), allow_wrap, NORMAL); 195 | break; 196 | 197 | case CMARK_NODE_LINEBREAK: 198 | LIT(".PD 0\n.P\n.PD"); 199 | CR(); 200 | break; 201 | 202 | case CMARK_NODE_SOFTBREAK: 203 | if (options & CMARK_OPT_HARDBREAKS) { 204 | LIT(".PD 0\n.P\n.PD"); 205 | CR(); 206 | } else if (renderer->width == 0 && !(CMARK_OPT_NOBREAKS & options)) { 207 | CR(); 208 | } else { 209 | OUT(" ", allow_wrap, LITERAL); 210 | } 211 | break; 212 | 213 | case CMARK_NODE_CODE: 214 | LIT("\\f[C]"); 215 | OUT(cmark_node_get_literal(node), allow_wrap, NORMAL); 216 | LIT("\\f[]"); 217 | break; 218 | 219 | case CMARK_NODE_HTML_INLINE: 220 | break; 221 | 222 | case CMARK_NODE_CUSTOM_INLINE: 223 | OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node), 224 | false, LITERAL); 225 | break; 226 | 227 | case CMARK_NODE_STRONG: 228 | if (entering) { 229 | LIT("\\f[B]"); 230 | } else { 231 | LIT("\\f[]"); 232 | } 233 | break; 234 | 235 | case CMARK_NODE_EMPH: 236 | if (entering) { 237 | LIT("\\f[I]"); 238 | } else { 239 | LIT("\\f[]"); 240 | } 241 | break; 242 | 243 | case CMARK_NODE_LINK: 244 | if (!entering) { 245 | LIT(" ("); 246 | OUT(cmark_node_get_url(node), allow_wrap, URL); 247 | LIT(")"); 248 | } 249 | break; 250 | 251 | case CMARK_NODE_IMAGE: 252 | if (entering) { 253 | LIT("[IMAGE: "); 254 | } else { 255 | LIT("]"); 256 | } 257 | break; 258 | 259 | case CMARK_NODE_FOOTNOTE_DEFINITION: 260 | case CMARK_NODE_FOOTNOTE_REFERENCE: 261 | // TODO 262 | break; 263 | 264 | default: 265 | assert(false); 266 | break; 267 | } 268 | 269 | return 1; 270 | } 271 | 272 | char *cmark_render_man(cmark_node *root, int options, int width) { 273 | return cmark_render_man_with_mem(root, options, width, cmark_node_mem(root)); 274 | } 275 | 276 | char *cmark_render_man_with_mem(cmark_node *root, int options, int width, cmark_mem *mem) { 277 | return cmark_render(mem, root, options, width, S_outc, S_render_node); 278 | } 279 | -------------------------------------------------------------------------------- /Sources/libcmark/src/render.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "buffer.h" 3 | #include "chunk.h" 4 | #include "cmark-gfm.h" 5 | #include "utf8.h" 6 | #include "render.h" 7 | #include "node.h" 8 | #include "syntax_extension.h" 9 | 10 | static CMARK_INLINE void S_cr(cmark_renderer *renderer) { 11 | if (renderer->need_cr < 1) { 12 | renderer->need_cr = 1; 13 | } 14 | } 15 | 16 | static CMARK_INLINE void S_blankline(cmark_renderer *renderer) { 17 | if (renderer->need_cr < 2) { 18 | renderer->need_cr = 2; 19 | } 20 | } 21 | 22 | static void S_out(cmark_renderer *renderer, cmark_node *node, 23 | const char *source, bool wrap, 24 | cmark_escaping escape) { 25 | int length = (int)strlen(source); 26 | unsigned char nextc; 27 | int32_t c; 28 | int i = 0; 29 | int last_nonspace; 30 | int len; 31 | cmark_chunk remainder = cmark_chunk_literal(""); 32 | int k = renderer->buffer->size - 1; 33 | 34 | cmark_syntax_extension *ext = NULL; 35 | cmark_node *n = node; 36 | while (n && !ext) { 37 | ext = n->extension; 38 | if (!ext) 39 | n = n->parent; 40 | } 41 | if (ext && !ext->commonmark_escape_func) 42 | ext = NULL; 43 | 44 | wrap = wrap && !renderer->no_linebreaks; 45 | 46 | if (renderer->in_tight_list_item && renderer->need_cr > 1) { 47 | renderer->need_cr = 1; 48 | } 49 | while (renderer->need_cr) { 50 | if (k < 0 || renderer->buffer->ptr[k] == '\n') { 51 | k -= 1; 52 | } else { 53 | cmark_strbuf_putc(renderer->buffer, '\n'); 54 | if (renderer->need_cr > 1) { 55 | cmark_strbuf_put(renderer->buffer, renderer->prefix->ptr, 56 | renderer->prefix->size); 57 | } 58 | } 59 | renderer->column = 0; 60 | renderer->last_breakable = 0; 61 | renderer->begin_line = true; 62 | renderer->begin_content = true; 63 | renderer->need_cr -= 1; 64 | } 65 | 66 | while (i < length) { 67 | if (renderer->begin_line) { 68 | cmark_strbuf_put(renderer->buffer, renderer->prefix->ptr, 69 | renderer->prefix->size); 70 | // note: this assumes prefix is ascii: 71 | renderer->column = renderer->prefix->size; 72 | } 73 | 74 | len = cmark_utf8proc_iterate((const uint8_t *)source + i, length - i, &c); 75 | if (len == -1) { // error condition 76 | return; // return without rendering rest of string 77 | } 78 | 79 | if (ext && ext->commonmark_escape_func(ext, node, c)) 80 | cmark_strbuf_putc(renderer->buffer, '\\'); 81 | 82 | nextc = source[i + len]; 83 | if (c == 32 && wrap) { 84 | if (!renderer->begin_line) { 85 | last_nonspace = renderer->buffer->size; 86 | cmark_strbuf_putc(renderer->buffer, ' '); 87 | renderer->column += 1; 88 | renderer->begin_line = false; 89 | renderer->begin_content = false; 90 | // skip following spaces 91 | while (source[i + 1] == ' ') { 92 | i++; 93 | } 94 | // We don't allow breaks that make a digit the first character 95 | // because this causes problems with commonmark output. 96 | if (!cmark_isdigit(source[i + 1])) { 97 | renderer->last_breakable = last_nonspace; 98 | } 99 | } 100 | 101 | } else if (escape == LITERAL) { 102 | if (c == 10) { 103 | cmark_strbuf_putc(renderer->buffer, '\n'); 104 | renderer->column = 0; 105 | renderer->begin_line = true; 106 | renderer->begin_content = true; 107 | renderer->last_breakable = 0; 108 | } else { 109 | cmark_render_code_point(renderer, c); 110 | renderer->begin_line = false; 111 | // we don't set 'begin_content' to false til we've 112 | // finished parsing a digit. Reason: in commonmark 113 | // we need to escape a potential list marker after 114 | // a digit: 115 | renderer->begin_content = 116 | renderer->begin_content && cmark_isdigit((char)c) == 1; 117 | } 118 | } else { 119 | (renderer->outc)(renderer, node, escape, c, nextc); 120 | renderer->begin_line = false; 121 | renderer->begin_content = 122 | renderer->begin_content && cmark_isdigit((char)c) == 1; 123 | } 124 | 125 | // If adding the character went beyond width, look for an 126 | // earlier place where the line could be broken: 127 | if (renderer->width > 0 && renderer->column > renderer->width && 128 | !renderer->begin_line && renderer->last_breakable > 0) { 129 | 130 | // copy from last_breakable to remainder 131 | cmark_chunk_set_cstr(renderer->mem, &remainder, 132 | (char *)renderer->buffer->ptr + 133 | renderer->last_breakable + 1); 134 | // truncate at last_breakable 135 | cmark_strbuf_truncate(renderer->buffer, renderer->last_breakable); 136 | // add newline, prefix, and remainder 137 | cmark_strbuf_putc(renderer->buffer, '\n'); 138 | cmark_strbuf_put(renderer->buffer, renderer->prefix->ptr, 139 | renderer->prefix->size); 140 | cmark_strbuf_put(renderer->buffer, remainder.data, remainder.len); 141 | renderer->column = renderer->prefix->size + remainder.len; 142 | cmark_chunk_free(renderer->mem, &remainder); 143 | renderer->last_breakable = 0; 144 | renderer->begin_line = false; 145 | renderer->begin_content = false; 146 | } 147 | 148 | i += len; 149 | } 150 | } 151 | 152 | // Assumes no newlines, assumes ascii content: 153 | void cmark_render_ascii(cmark_renderer *renderer, const char *s) { 154 | int origsize = renderer->buffer->size; 155 | cmark_strbuf_puts(renderer->buffer, s); 156 | renderer->column += renderer->buffer->size - origsize; 157 | } 158 | 159 | void cmark_render_code_point(cmark_renderer *renderer, uint32_t c) { 160 | cmark_utf8proc_encode_char(c, renderer->buffer); 161 | renderer->column += 1; 162 | } 163 | 164 | char *cmark_render(cmark_mem *mem, cmark_node *root, int options, int width, 165 | void (*outc)(cmark_renderer *, cmark_node *, 166 | cmark_escaping, int32_t, 167 | unsigned char), 168 | int (*render_node)(cmark_renderer *renderer, 169 | cmark_node *node, 170 | cmark_event_type ev_type, int options)) { 171 | cmark_strbuf pref = CMARK_BUF_INIT(mem); 172 | cmark_strbuf buf = CMARK_BUF_INIT(mem); 173 | cmark_node *cur; 174 | cmark_event_type ev_type; 175 | char *result; 176 | cmark_iter *iter = cmark_iter_new(root); 177 | 178 | cmark_renderer renderer = {mem, &buf, &pref, 0, width, 179 | 0, 0, true, true, false, 180 | false, outc, S_cr, S_blankline, S_out, 181 | 0}; 182 | 183 | while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { 184 | cur = cmark_iter_get_node(iter); 185 | if (!render_node(&renderer, cur, ev_type, options)) { 186 | // a false value causes us to skip processing 187 | // the node's contents. this is used for 188 | // autolinks. 189 | cmark_iter_reset(iter, cur, CMARK_EVENT_EXIT); 190 | } 191 | } 192 | 193 | // ensure final newline 194 | if (renderer.buffer->size == 0 || renderer.buffer->ptr[renderer.buffer->size - 1] != '\n') { 195 | cmark_strbuf_putc(renderer.buffer, '\n'); 196 | } 197 | 198 | result = (char *)cmark_strbuf_detach(renderer.buffer); 199 | 200 | cmark_iter_free(iter); 201 | cmark_strbuf_free(renderer.prefix); 202 | cmark_strbuf_free(renderer.buffer); 203 | 204 | return result; 205 | } 206 | -------------------------------------------------------------------------------- /Tests/MarkdownKitTests/InlineTests.swift: -------------------------------------------------------------------------------- 1 | import MarkdownKit 2 | import XCTest 3 | 4 | final class InlineTests: XCTestCase { 5 | 6 | // MARK: - Features 7 | 8 | func testContent() { 9 | let markdown = "This is **bold**.\n" 10 | let document = Parser.parse(markdown)! 11 | let node = document.children[0].children[1].children[0] 12 | XCTAssertEqual("bold", node.content) 13 | } 14 | 15 | func testMultibyte() { 16 | let markdown = "Ya’ll **bóld**.\n" 17 | let document = Parser.parse(markdown)! 18 | let node = document.children[0].children[1] 19 | XCTAssertEqual(NSRange(location: 6, length: 8), node.range!) 20 | } 21 | 22 | func testEmoji() { 23 | let markdown = "This is 🇺🇸 **bo👨‍👩‍👧‍👦ld**.\n" 24 | let document = Parser.parse(markdown)! 25 | let node = document.children[0].children[1] 26 | XCTAssertEqual(NSRange(location: 13, length: 19), node.range!) 27 | } 28 | 29 | // MARK: - Inlines 30 | 31 | func testInlineCode() { 32 | let markdown = "Hello `world`.\n" 33 | let document = Parser.parse(markdown)! 34 | 35 | let paragraph = document.children[0] 36 | XCTAssertEqual(3, paragraph.children.count) 37 | 38 | let node = paragraph.children[1] 39 | XCTAssertEqual(NSRange(location: 6, length: 7), node.range!) 40 | XCTAssertEqual(.codeInline, node.kind) 41 | } 42 | 43 | func testMultiInlineCode() { 44 | let markdown = "Hello ````world````.\n" 45 | let document = Parser.parse(markdown)! 46 | 47 | let paragraph = document.children[0] 48 | XCTAssertEqual(3, paragraph.children.count) 49 | 50 | let node = paragraph.children[1] 51 | XCTAssertEqual(NSRange(location: 6, length: 13), node.range!) 52 | XCTAssertEqual(.codeInline, node.kind) 53 | } 54 | 55 | func testNotInlineCode() { 56 | let markdown = "Hello `world\n" 57 | let document = Parser.parse(markdown)! 58 | 59 | let paragraph = document.children[0] 60 | XCTAssertEqual(1, paragraph.children.count) 61 | XCTAssertEqual(.text, paragraph.children[0].kind) 62 | } 63 | 64 | func testInlineHTML() { 65 | let markdown = "Hello world.\n" 66 | let document = Parser.parse(markdown)! 67 | 68 | let paragraph = document.children[0] 69 | XCTAssertEqual(5, paragraph.children.count) 70 | 71 | let node = paragraph.children[1] 72 | XCTAssertEqual(NSRange(location: 6, length: 6), node.range!) 73 | XCTAssertEqual(.htmlInline, node.kind) 74 | } 75 | 76 | func testEmphasis() { 77 | let markdown = "Hello *world*.\n" 78 | let document = Parser.parse(markdown)! 79 | 80 | let paragraph = document.children[0] 81 | XCTAssertEqual(3, paragraph.children.count) 82 | 83 | let node = paragraph.children[1] 84 | XCTAssertEqual(NSRange(location: 6, length: 7), node.range!) 85 | XCTAssertEqual(.emphasis, node.kind) 86 | } 87 | 88 | func testStrong() { 89 | let markdown = "Hello **world**.\n" 90 | let document = Parser.parse(markdown)! 91 | 92 | let paragraph = document.children[0] 93 | XCTAssertEqual(3, paragraph.children.count) 94 | 95 | let node = paragraph.children[1] 96 | XCTAssertEqual(NSRange(location: 6, length: 9), node.range!) 97 | XCTAssertEqual(.strong, node.kind) 98 | } 99 | 100 | func testLink() { 101 | let markdown = "Hello [world](http://example.com \"Example\").\n" 102 | let document = Parser.parse(markdown)! 103 | 104 | let paragraph = document.children[0] 105 | XCTAssertEqual(3, paragraph.children.count) 106 | 107 | let node = paragraph.children[1] 108 | XCTAssertEqual(NSRange(location: 6, length: 37), node.range!) 109 | XCTAssertEqual(.link, node.kind) 110 | XCTAssertEqual("http://example.com", (node as? Link)?.url?.absoluteString) 111 | XCTAssertEqual("Example", (node as? Link)?.title) 112 | } 113 | 114 | func testImage() { 115 | let markdown = "Hello ![world](http://example.com/world.jpg).\n" 116 | let document = Parser.parse(markdown)! 117 | 118 | let paragraph = document.children[0] 119 | XCTAssertEqual(3, paragraph.children.count) 120 | 121 | let node = paragraph.children[1] 122 | XCTAssertEqual(NSRange(location: 6, length: 38), node.range!) 123 | XCTAssertEqual(.image, node.kind) 124 | } 125 | 126 | func testStrikethrough() { 127 | let markdown = "Hello ~~world~~.\n" 128 | let document = Parser.parse(markdown)! 129 | 130 | let paragraph = document.children[0] 131 | XCTAssertEqual(3, paragraph.children.count) 132 | 133 | let node = paragraph.children[1] 134 | XCTAssertEqual(NSRange(location: 6, length: 9), node.range!) 135 | XCTAssertEqual(.strikethrough, node.kind) 136 | } 137 | 138 | func testBoldItalic() { 139 | let markdown = "Hello ***world***.\n" 140 | let document = Parser.parse(markdown)! 141 | 142 | let paragraph = document.children[0] 143 | XCTAssertEqual(3, paragraph.children.count) 144 | 145 | let emphasis = paragraph.children[1] 146 | XCTAssertEqual(NSRange(location: 6, length: 11), emphasis.range!) 147 | XCTAssertEqual(.emphasis, emphasis.kind) 148 | 149 | let strong = emphasis.children[0] 150 | XCTAssertEqual(NSRange(location: 6, length: 11), strong.range!) 151 | XCTAssertEqual(.strong, strong.kind) 152 | } 153 | 154 | // MARK: - Delimiters 155 | 156 | func testEmphasisDelimiters() { 157 | let markdown = "Hello *world*.\n" 158 | let document = Parser.parse(markdown)! 159 | let node = document.children[0].children[1] 160 | XCTAssertEqual([NSRange(location: 6, length: 1), NSRange(location: 12, length: 1)], node.delimiters!) 161 | } 162 | 163 | func testStrongDelimiters() { 164 | let markdown = "Hello **world**.\n" 165 | let document = Parser.parse(markdown)! 166 | let node = document.children[0].children[1] 167 | XCTAssertEqual([NSRange(location: 6, length: 2), NSRange(location: 13, length: 2)], node.delimiters!) 168 | } 169 | 170 | func testLinkDelimiters() { 171 | let markdown = "Hello [world](https://example.com).\n" 172 | let document = Parser.parse(markdown)! 173 | let node = document.children[0].children[1] as! Link 174 | XCTAssertFalse(node.isAutolink) 175 | XCTAssertEqual([ 176 | NSRange(location: 6, length: 1), NSRange(location: 12, length: 2), NSRange(location: 33, length: 1) 177 | ], node.delimiters!) 178 | } 179 | 180 | func testAutomaticLinks() { 181 | let markdown = "hello " 182 | let document = Parser.parse(markdown)! 183 | let node = document.children[0].children[1] as! Link 184 | XCTAssertEqual(NSRange(location: 6, length: 20), node.range) 185 | XCTAssert(node.isAutolink) 186 | XCTAssertEqual(node.delimiters, [ 187 | NSRange(location: 6, length: 1), NSRange(location: 25, length: 1) 188 | ]) 189 | } 190 | 191 | func testMail() { 192 | let markdown = "" 193 | let document = Parser.parse(markdown) 194 | XCTAssertNotNil(document) 195 | let node = document!.children[0].children[0] as? Link 196 | XCTAssertNotNil(node) 197 | XCTAssertEqual(NSRange(location: 0, length: 13), node!.range) 198 | XCTAssert(node!.isAutolink) 199 | XCTAssertEqual(node!.delimiters, [ 200 | NSRange(location: 0, length: 1), NSRange(location: 12, length: 1) 201 | ]) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /Sources/libcmark/src/buffer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "config.h" 11 | #include "cmark_ctype.h" 12 | #include "buffer.h" 13 | 14 | /* Used as default value for cmark_strbuf->ptr so that people can always 15 | * assume ptr is non-NULL and zero terminated even for new cmark_strbufs. 16 | */ 17 | unsigned char cmark_strbuf__initbuf[1]; 18 | 19 | #ifndef MIN 20 | #define MIN(x, y) ((x < y) ? x : y) 21 | #endif 22 | 23 | void cmark_strbuf_init(cmark_mem *mem, cmark_strbuf *buf, 24 | bufsize_t initial_size) { 25 | buf->mem = mem; 26 | buf->asize = 0; 27 | buf->size = 0; 28 | buf->ptr = cmark_strbuf__initbuf; 29 | 30 | if (initial_size > 0) 31 | cmark_strbuf_grow(buf, initial_size); 32 | } 33 | 34 | static CMARK_INLINE void S_strbuf_grow_by(cmark_strbuf *buf, bufsize_t add) { 35 | cmark_strbuf_grow(buf, buf->size + add); 36 | } 37 | 38 | void cmark_strbuf_grow(cmark_strbuf *buf, bufsize_t target_size) { 39 | assert(target_size > 0); 40 | 41 | if (target_size < buf->asize) 42 | return; 43 | 44 | if (target_size > (bufsize_t)(INT32_MAX / 2)) { 45 | fprintf(stderr, 46 | "[cmark] cmark_strbuf_grow requests buffer with size > %d, aborting\n", 47 | (INT32_MAX / 2)); 48 | abort(); 49 | } 50 | 51 | /* Oversize the buffer by 50% to guarantee amortized linear time 52 | * complexity on append operations. */ 53 | bufsize_t new_size = target_size + target_size / 2; 54 | new_size += 1; 55 | new_size = (new_size + 7) & ~7; 56 | 57 | buf->ptr = (unsigned char *)buf->mem->realloc(buf->asize ? buf->ptr : NULL, 58 | new_size); 59 | buf->asize = new_size; 60 | } 61 | 62 | bufsize_t cmark_strbuf_len(const cmark_strbuf *buf) { return buf->size; } 63 | 64 | void cmark_strbuf_free(cmark_strbuf *buf) { 65 | if (!buf) 66 | return; 67 | 68 | if (buf->ptr != cmark_strbuf__initbuf) 69 | buf->mem->free(buf->ptr); 70 | 71 | cmark_strbuf_init(buf->mem, buf, 0); 72 | } 73 | 74 | void cmark_strbuf_clear(cmark_strbuf *buf) { 75 | buf->size = 0; 76 | 77 | if (buf->asize > 0) 78 | buf->ptr[0] = '\0'; 79 | } 80 | 81 | void cmark_strbuf_set(cmark_strbuf *buf, const unsigned char *data, 82 | bufsize_t len) { 83 | if (len <= 0 || data == NULL) { 84 | cmark_strbuf_clear(buf); 85 | } else { 86 | if (data != buf->ptr) { 87 | if (len >= buf->asize) 88 | cmark_strbuf_grow(buf, len); 89 | memmove(buf->ptr, data, len); 90 | } 91 | buf->size = len; 92 | buf->ptr[buf->size] = '\0'; 93 | } 94 | } 95 | 96 | void cmark_strbuf_sets(cmark_strbuf *buf, const char *string) { 97 | cmark_strbuf_set(buf, (const unsigned char *)string, 98 | string ? (bufsize_t)strlen(string) : 0); 99 | } 100 | 101 | void cmark_strbuf_putc(cmark_strbuf *buf, int c) { 102 | S_strbuf_grow_by(buf, 1); 103 | buf->ptr[buf->size++] = (unsigned char)(c & 0xFF); 104 | buf->ptr[buf->size] = '\0'; 105 | } 106 | 107 | void cmark_strbuf_put(cmark_strbuf *buf, const unsigned char *data, 108 | bufsize_t len) { 109 | if (len <= 0) 110 | return; 111 | 112 | S_strbuf_grow_by(buf, len); 113 | memmove(buf->ptr + buf->size, data, len); 114 | buf->size += len; 115 | buf->ptr[buf->size] = '\0'; 116 | } 117 | 118 | void cmark_strbuf_puts(cmark_strbuf *buf, const char *string) { 119 | cmark_strbuf_put(buf, (const unsigned char *)string, (bufsize_t)strlen(string)); 120 | } 121 | 122 | void cmark_strbuf_copy_cstr(char *data, bufsize_t datasize, 123 | const cmark_strbuf *buf) { 124 | bufsize_t copylen; 125 | 126 | assert(buf); 127 | if (!data || datasize <= 0) 128 | return; 129 | 130 | data[0] = '\0'; 131 | 132 | if (buf->size == 0 || buf->asize <= 0) 133 | return; 134 | 135 | copylen = buf->size; 136 | if (copylen > datasize - 1) 137 | copylen = datasize - 1; 138 | memmove(data, buf->ptr, copylen); 139 | data[copylen] = '\0'; 140 | } 141 | 142 | void cmark_strbuf_swap(cmark_strbuf *buf_a, cmark_strbuf *buf_b) { 143 | cmark_strbuf t = *buf_a; 144 | *buf_a = *buf_b; 145 | *buf_b = t; 146 | } 147 | 148 | unsigned char *cmark_strbuf_detach(cmark_strbuf *buf) { 149 | unsigned char *data = buf->ptr; 150 | 151 | if (buf->asize == 0) { 152 | /* return an empty string */ 153 | return (unsigned char *)buf->mem->calloc(1, 1); 154 | } 155 | 156 | cmark_strbuf_init(buf->mem, buf, 0); 157 | return data; 158 | } 159 | 160 | int cmark_strbuf_cmp(const cmark_strbuf *a, const cmark_strbuf *b) { 161 | int result = memcmp(a->ptr, b->ptr, MIN(a->size, b->size)); 162 | return (result != 0) ? result 163 | : (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0; 164 | } 165 | 166 | bufsize_t cmark_strbuf_strchr(const cmark_strbuf *buf, int c, bufsize_t pos) { 167 | if (pos >= buf->size) 168 | return -1; 169 | if (pos < 0) 170 | pos = 0; 171 | 172 | const unsigned char *p = 173 | (unsigned char *)memchr(buf->ptr + pos, c, buf->size - pos); 174 | if (!p) 175 | return -1; 176 | 177 | return (bufsize_t)(p - (const unsigned char *)buf->ptr); 178 | } 179 | 180 | bufsize_t cmark_strbuf_strrchr(const cmark_strbuf *buf, int c, bufsize_t pos) { 181 | if (pos < 0 || buf->size == 0) 182 | return -1; 183 | if (pos >= buf->size) 184 | pos = buf->size - 1; 185 | 186 | bufsize_t i; 187 | for (i = pos; i >= 0; i--) { 188 | if (buf->ptr[i] == (unsigned char)c) 189 | return i; 190 | } 191 | 192 | return -1; 193 | } 194 | 195 | void cmark_strbuf_truncate(cmark_strbuf *buf, bufsize_t len) { 196 | if (len < 0) 197 | len = 0; 198 | 199 | if (len < buf->size) { 200 | buf->size = len; 201 | buf->ptr[buf->size] = '\0'; 202 | } 203 | } 204 | 205 | void cmark_strbuf_drop(cmark_strbuf *buf, bufsize_t n) { 206 | if (n > 0) { 207 | if (n > buf->size) 208 | n = buf->size; 209 | buf->size = buf->size - n; 210 | if (buf->size) 211 | memmove(buf->ptr, buf->ptr + n, buf->size); 212 | 213 | buf->ptr[buf->size] = '\0'; 214 | } 215 | } 216 | 217 | void cmark_strbuf_rtrim(cmark_strbuf *buf) { 218 | if (!buf->size) 219 | return; 220 | 221 | while (buf->size > 0) { 222 | if (!cmark_isspace(buf->ptr[buf->size - 1])) 223 | break; 224 | 225 | buf->size--; 226 | } 227 | 228 | buf->ptr[buf->size] = '\0'; 229 | } 230 | 231 | void cmark_strbuf_trim(cmark_strbuf *buf) { 232 | bufsize_t i = 0; 233 | 234 | if (!buf->size) 235 | return; 236 | 237 | while (i < buf->size && cmark_isspace(buf->ptr[i])) 238 | i++; 239 | 240 | cmark_strbuf_drop(buf, i); 241 | 242 | cmark_strbuf_rtrim(buf); 243 | } 244 | 245 | // Destructively modify string, collapsing consecutive 246 | // space and newline characters into a single space. 247 | void cmark_strbuf_normalize_whitespace(cmark_strbuf *s) { 248 | bool last_char_was_space = false; 249 | bufsize_t r, w; 250 | 251 | for (r = 0, w = 0; r < s->size; ++r) { 252 | if (cmark_isspace(s->ptr[r])) { 253 | if (!last_char_was_space) { 254 | s->ptr[w++] = ' '; 255 | last_char_was_space = true; 256 | } 257 | } else { 258 | s->ptr[w++] = s->ptr[r]; 259 | last_char_was_space = false; 260 | } 261 | } 262 | 263 | cmark_strbuf_truncate(s, w); 264 | } 265 | 266 | // Destructively unescape a string: remove backslashes before punctuation chars. 267 | extern void cmark_strbuf_unescape(cmark_strbuf *buf) { 268 | bufsize_t r, w; 269 | 270 | for (r = 0, w = 0; r < buf->size; ++r) { 271 | if (buf->ptr[r] == '\\' && cmark_ispunct(buf->ptr[r + 1])) 272 | r++; 273 | 274 | buf->ptr[w++] = buf->ptr[r]; 275 | } 276 | 277 | cmark_strbuf_truncate(buf, w); 278 | } 279 | --------------------------------------------------------------------------------