├── 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 
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, "");
73 | cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
74 | literal = true;
75 | break;
76 | case CMARK_NODE_LIST:
77 | switch (cmark_node_get_list_type(node)) {
78 | case CMARK_ORDERED_LIST:
79 | cmark_strbuf_puts(xml, " type=\"ordered\"");
80 | snprintf(buffer, BUFFER_SIZE, " start=\"%d\"",
81 | cmark_node_get_list_start(node));
82 | cmark_strbuf_puts(xml, buffer);
83 | delim = cmark_node_get_list_delim(node);
84 | if (delim == CMARK_PAREN_DELIM) {
85 | cmark_strbuf_puts(xml, " delim=\"paren\"");
86 | } else if (delim == CMARK_PERIOD_DELIM) {
87 | cmark_strbuf_puts(xml, " delim=\"period\"");
88 | }
89 | break;
90 | case CMARK_BULLET_LIST:
91 | cmark_strbuf_puts(xml, " type=\"bullet\"");
92 | break;
93 | default:
94 | break;
95 | }
96 | snprintf(buffer, BUFFER_SIZE, " tight=\"%s\"",
97 | (cmark_node_get_list_tight(node) ? "true" : "false"));
98 | cmark_strbuf_puts(xml, buffer);
99 | break;
100 | case CMARK_NODE_HEADING:
101 | snprintf(buffer, BUFFER_SIZE, " level=\"%d\"", node->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, "");
113 | cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
114 | literal = true;
115 | break;
116 | case CMARK_NODE_CUSTOM_BLOCK:
117 | case CMARK_NODE_CUSTOM_INLINE:
118 | cmark_strbuf_puts(xml, " on_enter=\"");
119 | escape_xml(xml, node->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, "");
150 | cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
151 | 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 .\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 |
--------------------------------------------------------------------------------