├── .swift-version ├── Sources ├── cmark │ ├── include │ │ ├── cmark.h │ │ ├── cmark_version.h │ │ └── cmark_export.h │ ├── cmarkConfig.cmake.in │ ├── cmark_version.h.in │ ├── libcmark.pc.in │ ├── iterator.h │ ├── cmark_ctype.h │ ├── inlines.h │ ├── utf8.h │ ├── parser.h │ ├── references.h │ ├── cmark.c │ ├── render.h │ ├── houdini.h │ ├── config.h │ ├── config.h.in │ ├── cmark_ctype.c │ ├── node.h │ ├── chunk.h │ ├── houdini_html_e.c │ ├── buffer.h │ ├── scanners.h │ ├── houdini_href_e.c │ ├── iterator.c │ ├── houdini_html_u.c │ ├── references.c │ ├── CMakeLists.txt │ ├── main.c │ ├── render.c │ ├── buffer.c │ ├── man.c │ ├── xml.c │ ├── html.c │ ├── scanners.re │ ├── utf8.c │ └── latex.c └── CommonMark │ ├── Document │ ├── ParsingError.swift │ ├── ParsingOptions.swift │ ├── RenderingOptions.swift │ ├── Document+cmark.swift │ ├── CommonMarkNode.swift │ └── Document.swift │ ├── Inlines │ ├── InlineHTML.swift │ ├── InlineCode.swift │ ├── Inline.swift │ ├── Emphasis.swift │ ├── Strong.swift │ ├── Image.swift │ ├── Link.swift │ ├── InlineArrayBuilder.swift │ └── Inline+cmark.swift │ ├── Blocks │ ├── HTMLBlock.swift │ ├── ListItem.swift │ ├── Paragraph.swift │ ├── BlockQuote.swift │ ├── CodeBlock.swift │ ├── Heading.swift │ ├── Block.swift │ ├── ListItemArrayBuilder.swift │ ├── BulletList.swift │ ├── OrderedList.swift │ ├── BlockArrayBuilder.swift │ └── Block+cmark.swift │ └── Internal │ └── Deprecations.swift ├── .swiftpm └── xcode │ ├── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ └── xcschemes │ ├── cmark.xcscheme │ ├── CommonMark.xcscheme │ └── SwiftCommonMark-Package.xcscheme ├── Makefile ├── .github ├── workflows │ ├── ci.yml │ └── format.yml └── CODE_OF_CONDUCT.md ├── Package.swift ├── LICENSE ├── .gitignore ├── Tests └── CommonMarkTests │ ├── ListItemArrayBuilderTests.swift │ ├── InlineArrayBuilderTests.swift │ └── BlockArrayBuilderTests.swift └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 5.3 2 | -------------------------------------------------------------------------------- /Sources/cmark/include/cmark.h: -------------------------------------------------------------------------------- 1 | ../cmark.h -------------------------------------------------------------------------------- /Sources/cmark/cmarkConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/cmark-targets.cmake") 4 | check_required_components("cmark") 5 | -------------------------------------------------------------------------------- /Sources/CommonMark/Document/ParsingError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Document { 4 | public enum ParsingError: Error { 5 | case invalidData 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Sources/cmark/include/cmark_version.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_VERSION_H 2 | #define CMARK_VERSION_H 3 | 4 | #define CMARK_VERSION ((0 << 16) | (30 << 8) | 2) 5 | #define CMARK_VERSION_STRING "0.30.2" 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sources/CommonMark/Inlines/InlineHTML.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct InlineHTML: Hashable { 4 | public var html: String 5 | 6 | public init(_ html: String) { 7 | self.html = html 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Sources/CommonMark/Blocks/HTMLBlock.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct HTMLBlock: Hashable { 4 | public var html: String 5 | 6 | public init(html: String) { 7 | self.html = html 8 | } 9 | 10 | public init(html: () -> String) { 11 | self.init(html: html()) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/cmark/cmark_version.h.in: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_VERSION_H 2 | #define CMARK_VERSION_H 3 | 4 | #define CMARK_VERSION ((@PROJECT_VERSION_MAJOR@ << 16) | (@PROJECT_VERSION_MINOR@ << 8) | @PROJECT_VERSION_PATCH@) 5 | #define CMARK_VERSION_STRING "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@" 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/CommonMark/Inlines/InlineCode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A code span. 4 | public struct InlineCode: Hashable { 5 | /// The code contents. 6 | public var code: String 7 | 8 | /// Creates a code span. 9 | /// - Parameter text: The code contents. 10 | public init(_ code: String) { 11 | self.code = code 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/cmark/libcmark.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=@CMAKE_INSTALL_PREFIX@ 3 | libdir=@CMAKE_INSTALL_FULL_LIBDIR@ 4 | includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ 5 | 6 | Name: libcmark 7 | Description: CommonMark parsing, rendering, and manipulation 8 | Version: @PROJECT_VERSION@ 9 | Libs: -L${libdir} -lcmark 10 | Cflags: -I${includedir} 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test-all: test-linux test-swift 2 | 3 | test-linux: 4 | docker run \ 5 | --rm \ 6 | -v "$(PWD):$(PWD)" \ 7 | -w "$(PWD)" \ 8 | swift:5.3 \ 9 | bash -c 'make test-swift' 10 | 11 | test-swift: 12 | swift test \ 13 | --enable-test-discovery \ 14 | --parallel 15 | 16 | format: 17 | swift format --in-place --recursive . 18 | 19 | .PHONY: format test-all test-linux test-swift 20 | -------------------------------------------------------------------------------- /Sources/cmark/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.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/cmark/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 | /** Locale-independent versions of functions from ctype.h. 9 | * We want cmark to behave the same no matter what the system locale. 10 | */ 11 | 12 | int cmark_isspace(char c); 13 | 14 | int cmark_ispunct(char c); 15 | 16 | int cmark_isalnum(char c); 17 | 18 | int cmark_isdigit(char c); 19 | 20 | int cmark_isalpha(char c); 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /Sources/CommonMark/Document/ParsingOptions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import cmark 3 | 4 | extension Document { 5 | /// Options that affect the parsing of a CommonMark-formatted string into a ``Document``. 6 | public struct ParsingOptions: OptionSet { 7 | public var rawValue: Int32 8 | 9 | public init(rawValue: Int32 = CMARK_OPT_DEFAULT) { 10 | self.rawValue = rawValue 11 | } 12 | 13 | /// Convert straight quotes to curly, --- to em dashes, -- to en dashes. 14 | public static let smart = ParsingOptions(rawValue: CMARK_OPT_SMART) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - '*' 9 | jobs: 10 | test-swift: 11 | runs-on: macos-11 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Select Xcode 12.5 15 | run: sudo xcode-select -s /Applications/Xcode_12.5.app 16 | - name: Run tests 17 | run: make test-swift 18 | test-linux: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Run tests 23 | run: make test-linux 24 | -------------------------------------------------------------------------------- /Sources/CommonMark/Blocks/ListItem.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A list item. 4 | public struct ListItem: Hashable { 5 | /// The blocks contained in this list item. 6 | public var blocks: [Block] 7 | 8 | public init(blocks: [Block]) { 9 | self.blocks = blocks 10 | } 11 | } 12 | 13 | #if swift(>=5.4) 14 | extension ListItem { 15 | /// Creates a list item. 16 | /// - Parameter blocks: A ``BlockArrayBuilder`` that creates the blocks of this list item. 17 | public init(@BlockArrayBuilder blocks: () -> [Block]) { 18 | self.init(blocks: blocks()) 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/CommonMark/Blocks/Paragraph.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A paragraph element. 4 | public struct Paragraph: Hashable { 5 | /// The inlines contained in this paragraph. 6 | public var text: [Inline] 7 | 8 | public init(text: [Inline]) { 9 | self.text = text 10 | } 11 | } 12 | 13 | #if swift(>=5.4) 14 | extension Paragraph { 15 | /// Creates a paragraph element. 16 | /// - Parameter text: An ``InlineArrayBuilder`` that creates the inlines in this paragraph. 17 | public init(@InlineArrayBuilder text: () -> [Inline]) { 18 | self.init(text: text()) 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/CommonMark/Inlines/Inline.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// An inline-level element in a heading or paragraph. 4 | public enum Inline: Hashable { 5 | /// Textual content. 6 | case text(String) 7 | 8 | /// Soft line break. 9 | case softBreak 10 | 11 | /// Hard line break. 12 | case lineBreak 13 | 14 | /// Code span. 15 | case code(InlineCode) 16 | 17 | /// Raw HTML. 18 | case html(InlineHTML) 19 | 20 | /// Emphasis. 21 | case emphasis(Emphasis) 22 | 23 | /// Strong emphasis. 24 | case strong(Strong) 25 | 26 | /// Link. 27 | case link(Link) 28 | 29 | /// Image. 30 | case image(Image) 31 | } 32 | -------------------------------------------------------------------------------- /Sources/CommonMark/Blocks/BlockQuote.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A blockquote element. 4 | public struct BlockQuote: Hashable { 5 | /// The blocks contained in this element. 6 | public var items: [Block] 7 | 8 | public init(items: [Block]) { 9 | self.items = items 10 | } 11 | } 12 | 13 | #if swift(>=5.4) 14 | extension BlockQuote { 15 | /// Creates a blockquote element with the given array of blocks. 16 | /// - Parameter items: A ``BlockArrayBuilder`` that creates the blocks of this blockquoute. 17 | public init(@BlockArrayBuilder items: () -> [Block]) { 18 | self.init(items: items()) 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /.github/workflows/format.yml: -------------------------------------------------------------------------------- 1 | name: SwiftFormat 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | format: 8 | name: swift-format 9 | runs-on: macos-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Tap 13 | run: brew tap pointfreeco/formulae 14 | - name: Install 15 | run: brew install Formulae/swift-format@5.3 16 | - name: Format 17 | run: make format 18 | - uses: stefanzweifel/git-auto-commit-action@v4 19 | with: 20 | commit_message: Run swift format 21 | branch: 'main' 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /Sources/CommonMark/Blocks/CodeBlock.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A code block. 4 | public struct CodeBlock: Hashable { 5 | /// The code contents. 6 | public var code: String 7 | 8 | /// The code block language. 9 | public var language: String? 10 | 11 | public init(code: String, language: String? = nil) { 12 | self.code = code 13 | self.language = language 14 | } 15 | 16 | /// Creates a code block. 17 | /// - Parameters: 18 | /// - language: The code block language. 19 | /// - text: The code block contents. 20 | public init(language: String? = nil, code: () -> String) { 21 | self.init(code: code(), language: language) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/cmark/inlines.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_INLINES_H 2 | #define CMARK_INLINES_H 3 | 4 | #include "chunk.h" 5 | #include "references.h" 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | unsigned char *cmark_clean_url(cmark_mem *mem, cmark_chunk *url); 12 | unsigned char *cmark_clean_title(cmark_mem *mem, cmark_chunk *title); 13 | 14 | void cmark_parse_inlines(cmark_mem *mem, cmark_node *parent, 15 | cmark_reference_map *refmap, int options); 16 | 17 | bufsize_t cmark_parse_reference_inline(cmark_mem *mem, cmark_chunk *input, 18 | cmark_reference_map *refmap); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Sources/cmark/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 | void cmark_utf8proc_case_fold(cmark_strbuf *dest, const uint8_t *str, 12 | bufsize_t len); 13 | void cmark_utf8proc_encode_char(int32_t uc, cmark_strbuf *buf); 14 | int cmark_utf8proc_iterate(const uint8_t *str, bufsize_t str_len, int32_t *dst); 15 | void cmark_utf8proc_check(cmark_strbuf *dest, const uint8_t *line, 16 | bufsize_t size); 17 | int cmark_utf8proc_is_space(int32_t uc); 18 | int cmark_utf8proc_is_punctuation(int32_t uc); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Sources/CommonMark/Blocks/Heading.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A heading element. 4 | public struct Heading: Hashable { 5 | /// The inlines contained in this heading. 6 | public var text: [Inline] 7 | 8 | /// The heading level. 9 | public var level: Int 10 | 11 | public init(text: [Inline], level: Int) { 12 | self.text = text 13 | self.level = level 14 | } 15 | } 16 | 17 | #if swift(>=5.4) 18 | extension Heading { 19 | /// Creates a heading element with the given level. 20 | /// - Parameters: 21 | /// - level: The heading level. 22 | /// - text: An ``InlineArrayBuilder`` that creates the inlines in this heading. 23 | public init(level: Int = 1, @InlineArrayBuilder text: () -> [Inline]) { 24 | self.init(text: text(), level: level) 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /Sources/CommonMark/Inlines/Emphasis.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// An emphasized element. 4 | public struct Emphasis: Hashable { 5 | /// The inlines contained in this element. 6 | public var children: [Inline] 7 | 8 | public init(children: [Inline]) { 9 | self.children = children 10 | } 11 | 12 | /// Creates an emphasized text element. 13 | /// - Parameter text: The emphasized text. 14 | public init(_ text: String) { 15 | self.init(children: [.text(text)]) 16 | } 17 | } 18 | 19 | #if swift(>=5.4) 20 | extension Emphasis { 21 | /// Creates an emphasized element. 22 | /// - Parameter children: An ``InlineArrayBuilder`` that creates the inlines in this element. 23 | public init(@InlineArrayBuilder children: () -> [Inline]) { 24 | self.init(children: children()) 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /Sources/CommonMark/Inlines/Strong.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// An strongly emphasized element. 4 | public struct Strong: Hashable { 5 | /// The inlines contained in this element. 6 | public var children: [Inline] 7 | 8 | public init(children: [Inline]) { 9 | self.children = children 10 | } 11 | 12 | /// Creates an strongly emphasized text element. 13 | /// - Parameter text: The emphasized text. 14 | public init(_ text: String) { 15 | self.init(children: [.text(text)]) 16 | } 17 | } 18 | 19 | #if swift(>=5.4) 20 | extension Strong { 21 | /// Creates an strongly emphasized element. 22 | /// - Parameter children: An ``InlineArrayBuilder`` that creates the inlines in this element. 23 | public init(@InlineArrayBuilder children: () -> [Inline]) { 24 | self.init(children: children()) 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /Sources/CommonMark/Blocks/Block.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The structural element of a CommonMark document. 4 | public enum Block: Hashable { 5 | /// A section that is quoted from another source. 6 | case blockQuote(BlockQuote) 7 | 8 | /// A bullet list. 9 | case bulletList(BulletList) 10 | 11 | /// An ordered list. 12 | case orderedList(OrderedList) 13 | 14 | /// A section containing preformatted code. 15 | case code(CodeBlock) 16 | 17 | /// A group of lines that is treated as raw HTML. 18 | case html(HTMLBlock) 19 | 20 | /// A paragraph. 21 | case paragraph(Paragraph) 22 | 23 | /// A heading. 24 | case heading(Heading) 25 | 26 | /// A thematic break. 27 | case thematicBreak 28 | } 29 | 30 | extension Block { 31 | var isParagraph: Bool { 32 | guard case .paragraph = self else { return false } 33 | return true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SwiftCommonMark", 7 | products: [ 8 | .library( 9 | name: "cmark", 10 | targets: ["cmark"] 11 | ), 12 | .library( 13 | name: "CommonMark", 14 | targets: ["CommonMark"] 15 | ), 16 | ], 17 | dependencies: [], 18 | targets: [ 19 | .target( 20 | name: "cmark", 21 | exclude: [ 22 | "entities.inc", 23 | "case_fold_switch.inc", 24 | "cmark_version.h.in", 25 | "config.h.in", 26 | "libcmark.pc.in", 27 | "CMakeLists.txt", 28 | "scanners.re", 29 | "cmarkConfig.cmake.in", 30 | "main.c", 31 | ] 32 | ), 33 | .target( 34 | name: "CommonMark", 35 | dependencies: ["cmark"] 36 | ), 37 | .testTarget( 38 | name: "CommonMarkTests", 39 | dependencies: ["CommonMark"] 40 | ), 41 | ] 42 | ) 43 | -------------------------------------------------------------------------------- /Sources/cmark/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_AST_H 2 | #define CMARK_AST_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 | struct cmark_reference_map *refmap; 18 | struct cmark_node *root; 19 | struct cmark_node *current; 20 | int line_number; 21 | bufsize_t offset; 22 | bufsize_t column; 23 | bufsize_t first_nonspace; 24 | bufsize_t first_nonspace_column; 25 | bufsize_t thematic_break_kill_pos; 26 | int indent; 27 | bool blank; 28 | bool partially_consumed_tab; 29 | cmark_strbuf curline; 30 | bufsize_t last_line_length; 31 | cmark_strbuf linebuf; 32 | cmark_strbuf content; 33 | int options; 34 | bool last_buffer_ended_with_cr; 35 | unsigned int total_size; 36 | }; 37 | 38 | #ifdef __cplusplus 39 | } 40 | #endif 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /Sources/CommonMark/Internal/Deprecations.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Document { 4 | @available(*, deprecated, message: "This property has been removed and returns an empty set") 5 | public var imageURLs: Set { 6 | [] 7 | } 8 | 9 | @available( 10 | *, deprecated, message: "Use init(markdown:options:) to create a document from a string" 11 | ) 12 | public init(_ content: String) { 13 | try! self.init(markdown: content) 14 | } 15 | 16 | @available( 17 | *, deprecated, 18 | message: "Use init(contentsOf:options:) to create a document from the contents of a URL" 19 | ) 20 | public init(contentsOfFile path: String) throws { 21 | try self.init(markdown: .init(contentsOfFile: path)) 22 | } 23 | 24 | @available( 25 | *, deprecated, 26 | message: "Use init(contentsOf:options:) to create a document from the contents of a URL" 27 | ) 28 | public init(contentsOfFile path: String, encoding: String.Encoding) throws { 29 | try self.init(markdown: .init(contentsOfFile: path, encoding: encoding)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/CommonMark/Blocks/ListItemArrayBuilder.swift: -------------------------------------------------------------------------------- 1 | #if swift(>=5.4) 2 | import Foundation 3 | 4 | /// Constructs list items from multi-expression closures. 5 | @resultBuilder public enum ListItemArrayBuilder { 6 | public static func buildBlock(_ components: [ListItem]...) -> [ListItem] { 7 | components.flatMap { $0 } 8 | } 9 | 10 | public static func buildExpression(_ expression: String) -> [ListItem] { 11 | [ListItem(blocks: [.paragraph(.init(text: [.text(expression)]))])] 12 | } 13 | 14 | public static func buildExpression(_ expression: ListItem) -> [ListItem] { 15 | [expression] 16 | } 17 | 18 | public static func buildArray(_ components: [[ListItem]]) -> [ListItem] { 19 | components.flatMap { $0 } 20 | } 21 | 22 | public static func buildOptional(_ component: [ListItem]?) -> [ListItem] { 23 | component ?? [] 24 | } 25 | 26 | public static func buildEither(first component: [ListItem]) -> [ListItem] { 27 | component 28 | } 29 | 30 | public static func buildEither(second component: [ListItem]) -> [ListItem] { 31 | component 32 | } 33 | } 34 | #endif 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Guillermo Gonzalez 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 | 23 | -------------------------------------------------------------------------------- /Sources/cmark/include/cmark_export.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef CMARK_EXPORT_H 3 | #define CMARK_EXPORT_H 4 | 5 | #ifdef CMARK_STATIC_DEFINE 6 | # define CMARK_EXPORT 7 | # define CMARK_NO_EXPORT 8 | #else 9 | # ifndef CMARK_EXPORT 10 | # ifdef cmark_EXPORTS 11 | /* We are building this library */ 12 | # define CMARK_EXPORT __attribute__((visibility("default"))) 13 | # else 14 | /* We are using this library */ 15 | # define CMARK_EXPORT __attribute__((visibility("default"))) 16 | # endif 17 | # endif 18 | 19 | # ifndef CMARK_NO_EXPORT 20 | # define CMARK_NO_EXPORT __attribute__((visibility("hidden"))) 21 | # endif 22 | #endif 23 | 24 | #ifndef CMARK_DEPRECATED 25 | # define CMARK_DEPRECATED __attribute__ ((__deprecated__)) 26 | #endif 27 | 28 | #ifndef CMARK_DEPRECATED_EXPORT 29 | # define CMARK_DEPRECATED_EXPORT CMARK_EXPORT CMARK_DEPRECATED 30 | #endif 31 | 32 | #ifndef CMARK_DEPRECATED_NO_EXPORT 33 | # define CMARK_DEPRECATED_NO_EXPORT CMARK_NO_EXPORT CMARK_DEPRECATED 34 | #endif 35 | 36 | #if 0 /* DEFINE_NO_DEPRECATED */ 37 | # ifndef CMARK_NO_DEPRECATED 38 | # define CMARK_NO_DEPRECATED 39 | # endif 40 | #endif 41 | 42 | #endif /* CMARK_EXPORT_H */ 43 | -------------------------------------------------------------------------------- /Sources/cmark/references.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_REFERENCES_H 2 | #define CMARK_REFERENCES_H 3 | 4 | #include "chunk.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | struct cmark_reference { 11 | struct cmark_reference *next; 12 | unsigned char *label; 13 | unsigned char *url; 14 | unsigned char *title; 15 | unsigned int age; 16 | unsigned int size; 17 | }; 18 | 19 | typedef struct cmark_reference cmark_reference; 20 | 21 | struct cmark_reference_map { 22 | cmark_mem *mem; 23 | cmark_reference *refs; 24 | cmark_reference **sorted; 25 | unsigned int size; 26 | unsigned int ref_size; 27 | unsigned int max_ref_size; 28 | }; 29 | 30 | typedef struct cmark_reference_map cmark_reference_map; 31 | 32 | cmark_reference_map *cmark_reference_map_new(cmark_mem *mem); 33 | void cmark_reference_map_free(cmark_reference_map *map); 34 | cmark_reference *cmark_reference_lookup(cmark_reference_map *map, 35 | cmark_chunk *label); 36 | extern void cmark_reference_create(cmark_reference_map *map, cmark_chunk *label, 37 | cmark_chunk *url, cmark_chunk *title); 38 | 39 | #ifdef __cplusplus 40 | } 41 | #endif 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /Sources/CommonMark/Blocks/BulletList.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A bullet list. 4 | public struct BulletList: Hashable { 5 | /// The list items. 6 | public var items: [ListItem] 7 | 8 | /// Determines if this list is tight or loose. 9 | public var tight: Bool 10 | 11 | public init(items: [ListItem], tight: Bool) { 12 | self.items = items 13 | 14 | // Force loose spacing if any of the items contains more than one paragraph 15 | let hasItemsWithMultipleParagraphs = self.items.contains { item in 16 | item.blocks.filter(\.isParagraph).count > 1 17 | } 18 | self.tight = hasItemsWithMultipleParagraphs ? false : tight 19 | } 20 | } 21 | 22 | #if swift(>=5.4) 23 | extension BulletList { 24 | /// Creates a bullet list. 25 | /// - Parameters: 26 | /// - tight: A `Boolean` value that indicates if the list is tight or loose. This parameter is ignored if 27 | /// any of the list items contain more than one paragraph. 28 | /// - items: An ``ListItemArrayBuilder`` that creates the items in this list. 29 | public init(tight: Bool = true, @ListItemArrayBuilder items: () -> [ListItem]) { 30 | self.init(items: items(), tight: tight) 31 | } 32 | } 33 | #endif 34 | -------------------------------------------------------------------------------- /Sources/CommonMark/Document/RenderingOptions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import cmark 3 | 4 | extension Document { 5 | /// Options that affect the rendering a ``Document`` as HTML. 6 | public struct RenderingOptions: OptionSet { 7 | public var rawValue: Int32 8 | 9 | public init(rawValue: Int32 = CMARK_OPT_DEFAULT) { 10 | self.rawValue = rawValue 11 | } 12 | 13 | /// Include a `data-sourcepos` attribute on all block elements. 14 | public static let sourcePosition = RenderingOptions(rawValue: CMARK_OPT_SOURCEPOS) 15 | 16 | /// Render `softbreak` elements as hard line breaks. 17 | public static let hardBreaks = RenderingOptions(rawValue: CMARK_OPT_HARDBREAKS) 18 | 19 | /// Render raw HTML and unsafe links (`javascript:`, `vbscript:`, `file:`, and `data:`, except for `image/png`, `image/gif`, 20 | /// `image/jpeg`, or `image/webp` mime types). By default, raw HTML is replaced by a placeholder HTML comment. Unsafe links are 21 | /// replaced by empty strings. 22 | public static let unsafe = RenderingOptions(rawValue: CMARK_OPT_UNSAFE) 23 | 24 | /// Render `softBreak` elements as spaces. 25 | public static let noBreaks = RenderingOptions(rawValue: CMARK_OPT_NOBREAKS) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/CommonMark/Document/Document+cmark.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import cmark 3 | 4 | extension Document { 5 | /// Renders this document as CommonMark-formatted text. 6 | public func renderCommonMark() -> String { 7 | let node = CommonMarkNode(document: self) 8 | return String(cString: cmark_render_commonmark(node.pointer, CMARK_OPT_DEFAULT, 0)) 9 | } 10 | 11 | /// Renders this document as HTML. 12 | /// - Parameter options: Options that affect how the document is rendered as HTML. 13 | public func renderHTML(options: RenderingOptions = .init()) -> String { 14 | let node = CommonMarkNode(document: self) 15 | return String(cString: cmark_render_html(node.pointer, options.rawValue)) 16 | } 17 | } 18 | 19 | extension Document: CustomStringConvertible { 20 | public var description: String { 21 | renderCommonMark() 22 | } 23 | } 24 | 25 | extension CommonMarkNode { 26 | convenience init(document: Document) { 27 | let pointer: OpaquePointer = cmark_node_new(CMARK_NODE_DOCUMENT) 28 | 29 | document.blocks.map { 30 | CommonMarkNode(block: $0, managed: false) 31 | }.forEach { node in 32 | cmark_node_append_child(pointer, node.pointer) 33 | } 34 | 35 | self.init(pointer: pointer, managed: true) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/cmark/cmark.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "node.h" 5 | #include "houdini.h" 6 | #include "cmark.h" 7 | #include "buffer.h" 8 | 9 | int cmark_version() { return CMARK_VERSION; } 10 | 11 | const char *cmark_version_string() { return CMARK_VERSION_STRING; } 12 | 13 | static void *xcalloc(size_t nmem, size_t size) { 14 | void *ptr = calloc(nmem, size); 15 | if (!ptr) { 16 | fprintf(stderr, "[cmark] calloc returned null pointer, aborting\n"); 17 | abort(); 18 | } 19 | return ptr; 20 | } 21 | 22 | static void *xrealloc(void *ptr, size_t size) { 23 | void *new_ptr = realloc(ptr, size); 24 | if (!new_ptr) { 25 | fprintf(stderr, "[cmark] realloc returned null pointer, aborting\n"); 26 | abort(); 27 | } 28 | return new_ptr; 29 | } 30 | 31 | cmark_mem DEFAULT_MEM_ALLOCATOR = {xcalloc, xrealloc, free}; 32 | 33 | cmark_mem *cmark_get_default_mem_allocator() { 34 | return &DEFAULT_MEM_ALLOCATOR; 35 | } 36 | 37 | 38 | char *cmark_markdown_to_html(const char *text, size_t len, int options) { 39 | cmark_node *doc; 40 | char *result; 41 | 42 | doc = cmark_parse_document(text, len, options); 43 | 44 | result = cmark_render_html(doc, options); 45 | cmark_node_free(doc); 46 | 47 | return result; 48 | } 49 | -------------------------------------------------------------------------------- /Sources/CommonMark/Inlines/Image.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// An image element. 4 | public struct Image: Hashable { 5 | /// The inlines contained in this element. 6 | public var children: [Inline] 7 | 8 | /// The image URL. 9 | public var url: URL? 10 | 11 | /// The image title. 12 | public var title: String? 13 | 14 | public init(children: [Inline], url: URL?, title: String? = nil) { 15 | self.children = children 16 | self.url = url 17 | self.title = title 18 | } 19 | 20 | /// Creates an image element. 21 | /// - Parameters: 22 | /// - url: The image URL. 23 | /// - alt: The image alternate text. 24 | /// - title: The image title. 25 | public init(url: URL?, alt: String? = nil, title: String? = nil) { 26 | self.init(children: alt.map { [.text($0)] } ?? [], url: url, title: title) 27 | } 28 | 29 | /// Creates an image element. 30 | /// - Parameters: 31 | /// - destination: The path to the image. 32 | /// - alt: The image alternate text. 33 | /// - title: The image title. 34 | public init(_ destination: StaticString, alt: String? = nil, title: String? = nil) { 35 | self.init( 36 | children: alt.map { [.text($0)] } ?? [], 37 | url: URL(string: "\(destination)"), 38 | title: title 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/CommonMark/Blocks/OrderedList.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// An ordered list. 4 | public struct OrderedList: Hashable { 5 | /// The list items. 6 | public var items: [ListItem] 7 | 8 | /// The start number for this list. 9 | public var start: Int 10 | 11 | /// Determines if this list is tight or loose. 12 | public var tight: Bool 13 | 14 | public init(items: [ListItem], start: Int, tight: Bool) { 15 | self.items = items 16 | self.start = start 17 | // Force loose spacing if any of the items contains more than one paragraph 18 | let hasItemsWithMultipleParagraphs = self.items.contains { item in 19 | item.blocks.filter(\.isParagraph).count > 1 20 | } 21 | self.tight = hasItemsWithMultipleParagraphs ? false : tight 22 | } 23 | } 24 | 25 | #if swift(>=5.4) 26 | extension OrderedList { 27 | /// Creates an ordered list. 28 | /// - Parameters: 29 | /// - start: The start number for this list. 30 | /// - tight: A `Boolean` value that indicates if the list is tight or loose. This parameter is ignored if 31 | /// any of the list items contain more than one paragraph. 32 | /// - items: An ``ListItemArrayBuilder`` that creates the items in this list. 33 | public init(start: Int = 1, tight: Bool = true, @ListItemArrayBuilder items: () -> [ListItem]) { 34 | self.init(items: items(), start: start, tight: tight) 35 | } 36 | } 37 | #endif 38 | -------------------------------------------------------------------------------- /Sources/CommonMark/Inlines/Link.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A link element. 4 | public struct Link: Hashable { 5 | /// The inlines contained in this element. 6 | public var children: [Inline] 7 | 8 | /// The link destination. 9 | public var url: URL? 10 | 11 | /// The link title. 12 | public var title: String? 13 | 14 | public init(children: [Inline], url: URL?, title: String? = nil) { 15 | self.children = children 16 | self.url = url 17 | self.title = title 18 | } 19 | } 20 | 21 | #if swift(>=5.4) 22 | extension Link { 23 | /// Creates a link element. 24 | /// - Parameters: 25 | /// - url: The link destination. 26 | /// - title: The link title. 27 | /// - children: An ``InlineArrayBuilder`` that creates the link text. 28 | public init(url: URL?, title: String? = nil, @InlineArrayBuilder children: () -> [Inline]) { 29 | self.init(children: children(), url: url, title: title) 30 | } 31 | 32 | /// Creates a link element. 33 | /// - Parameters: 34 | /// - destination: The link destination. 35 | /// - title: The link title. 36 | /// - children: An ``InlineArrayBuilder`` that creates the link text. 37 | public init( 38 | _ destination: StaticString, 39 | title: String? = nil, 40 | @InlineArrayBuilder children: () -> [Inline] 41 | ) { 42 | self.init(children: children(), url: URL(string: "\(destination)"), title: title) 43 | } 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /Sources/cmark/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 | 11 | typedef enum { LITERAL, NORMAL, TITLE, URL } cmark_escaping; 12 | 13 | struct block_number { 14 | int number; 15 | struct block_number *parent; 16 | }; 17 | 18 | struct cmark_renderer { 19 | int options; 20 | cmark_mem *mem; 21 | cmark_strbuf *buffer; 22 | cmark_strbuf *prefix; 23 | int column; 24 | int width; 25 | int need_cr; 26 | bufsize_t last_breakable; 27 | bool begin_line; 28 | bool begin_content; 29 | bool no_linebreaks; 30 | bool in_tight_list_item; 31 | struct block_number *block_number_in_list_item; 32 | void (*outc)(struct cmark_renderer *, cmark_escaping, int32_t, unsigned char); 33 | void (*cr)(struct cmark_renderer *); 34 | void (*blankline)(struct cmark_renderer *); 35 | void (*out)(struct cmark_renderer *, const char *, bool, cmark_escaping); 36 | }; 37 | 38 | typedef struct cmark_renderer cmark_renderer; 39 | 40 | void cmark_render_ascii(cmark_renderer *renderer, const char *s); 41 | 42 | void cmark_render_code_point(cmark_renderer *renderer, uint32_t c); 43 | 44 | char *cmark_render(cmark_node *root, int options, int width, 45 | void (*outc)(cmark_renderer *, cmark_escaping, int32_t, 46 | unsigned char), 47 | int (*render_node)(cmark_renderer *renderer, 48 | cmark_node *node, 49 | cmark_event_type ev_type, int options)); 50 | 51 | #ifdef __cplusplus 52 | } 53 | #endif 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /Sources/cmark/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 | extern bufsize_t houdini_unescape_ent(cmark_strbuf *ob, const uint8_t *src, 35 | bufsize_t size); 36 | extern int houdini_escape_html(cmark_strbuf *ob, const uint8_t *src, 37 | bufsize_t size); 38 | extern int houdini_escape_html0(cmark_strbuf *ob, const uint8_t *src, 39 | bufsize_t size, int secure); 40 | extern int houdini_unescape_html(cmark_strbuf *ob, const uint8_t *src, 41 | bufsize_t size); 42 | extern void houdini_unescape_html_f(cmark_strbuf *ob, const uint8_t *src, 43 | bufsize_t size); 44 | extern int houdini_escape_href(cmark_strbuf *ob, const uint8_t *src, 45 | bufsize_t size); 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /Sources/cmark/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 | -------------------------------------------------------------------------------- /Sources/cmark/config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_CONFIG_H 2 | #define CMARK_CONFIG_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #cmakedefine HAVE_STDBOOL_H 9 | 10 | #ifdef HAVE_STDBOOL_H 11 | #include 12 | #elif !defined(__cplusplus) 13 | typedef char bool; 14 | #endif 15 | 16 | #cmakedefine HAVE___BUILTIN_EXPECT 17 | 18 | #cmakedefine 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 | -------------------------------------------------------------------------------- /Sources/cmark/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, 1, 1, 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/cmark/node.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_NODE_H 2 | #define CMARK_NODE_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | #include "config.h" 12 | #include "cmark.h" 13 | #include "buffer.h" 14 | 15 | typedef struct { 16 | int marker_offset; 17 | int padding; 18 | int start; 19 | unsigned char list_type; 20 | unsigned char delimiter; 21 | unsigned char bullet_char; 22 | bool tight; 23 | } cmark_list; 24 | 25 | typedef struct { 26 | unsigned char *info; 27 | uint8_t fence_length; 28 | uint8_t fence_offset; 29 | unsigned char fence_char; 30 | int8_t fenced; 31 | } cmark_code; 32 | 33 | typedef struct { 34 | int level; 35 | bool setext; 36 | } cmark_heading; 37 | 38 | typedef struct { 39 | unsigned char *url; 40 | unsigned char *title; 41 | } cmark_link; 42 | 43 | typedef struct { 44 | unsigned char *on_enter; 45 | unsigned char *on_exit; 46 | } cmark_custom; 47 | 48 | enum cmark_node__internal_flags { 49 | CMARK_NODE__OPEN = (1 << 0), 50 | CMARK_NODE__LAST_LINE_BLANK = (1 << 1), 51 | CMARK_NODE__LAST_LINE_CHECKED = (1 << 2), 52 | }; 53 | 54 | struct cmark_node { 55 | cmark_mem *mem; 56 | 57 | struct cmark_node *next; 58 | struct cmark_node *prev; 59 | struct cmark_node *parent; 60 | struct cmark_node *first_child; 61 | struct cmark_node *last_child; 62 | 63 | void *user_data; 64 | 65 | unsigned char *data; 66 | bufsize_t len; 67 | 68 | int start_line; 69 | int start_column; 70 | int end_line; 71 | int end_column; 72 | int internal_offset; 73 | uint16_t type; 74 | uint16_t flags; 75 | 76 | union { 77 | cmark_list list; 78 | cmark_code code; 79 | cmark_heading heading; 80 | cmark_link link; 81 | cmark_custom custom; 82 | int html_block_type; 83 | } as; 84 | }; 85 | 86 | CMARK_EXPORT int cmark_node_check(cmark_node *node, FILE *out); 87 | 88 | #ifdef __cplusplus 89 | } 90 | #endif 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /Sources/cmark/chunk.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_CHUNK_H 2 | #define CMARK_CHUNK_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "cmark.h" 8 | #include "buffer.h" 9 | #include "cmark_ctype.h" 10 | 11 | #define CMARK_CHUNK_EMPTY \ 12 | { NULL, 0 } 13 | 14 | typedef struct { 15 | const unsigned char *data; 16 | bufsize_t len; 17 | } cmark_chunk; 18 | 19 | // NOLINTNEXTLINE(clang-diagnostic-unused-function) 20 | static CMARK_INLINE void cmark_chunk_free(cmark_chunk *c) { 21 | c->data = NULL; 22 | c->len = 0; 23 | } 24 | 25 | static CMARK_INLINE void cmark_chunk_ltrim(cmark_chunk *c) { 26 | while (c->len && cmark_isspace(c->data[0])) { 27 | c->data++; 28 | c->len--; 29 | } 30 | } 31 | 32 | static CMARK_INLINE void cmark_chunk_rtrim(cmark_chunk *c) { 33 | while (c->len > 0) { 34 | if (!cmark_isspace(c->data[c->len - 1])) 35 | break; 36 | 37 | c->len--; 38 | } 39 | } 40 | 41 | // NOLINTNEXTLINE(clang-diagnostic-unused-function) 42 | static CMARK_INLINE void cmark_chunk_trim(cmark_chunk *c) { 43 | cmark_chunk_ltrim(c); 44 | cmark_chunk_rtrim(c); 45 | } 46 | 47 | // NOLINTNEXTLINE(clang-diagnostic-unused-function) 48 | static CMARK_INLINE bufsize_t cmark_chunk_strchr(cmark_chunk *ch, int c, 49 | bufsize_t offset) { 50 | const unsigned char *p = 51 | (unsigned char *)memchr(ch->data + offset, c, ch->len - offset); 52 | return p ? (bufsize_t)(p - ch->data) : ch->len; 53 | } 54 | 55 | // NOLINTNEXTLINE(clang-diagnostic-unused-function) 56 | static CMARK_INLINE cmark_chunk cmark_chunk_literal(const char *data) { 57 | bufsize_t len = data ? (bufsize_t)strlen(data) : 0; 58 | cmark_chunk c = {(unsigned char *)data, len}; 59 | return c; 60 | } 61 | 62 | // NOLINTNEXTLINE(clang-diagnostic-unused-function) 63 | static CMARK_INLINE cmark_chunk cmark_chunk_dup(const cmark_chunk *ch, 64 | bufsize_t pos, bufsize_t len) { 65 | cmark_chunk c = {ch->data + pos, len}; 66 | return c; 67 | } 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /Sources/CommonMark/Blocks/BlockArrayBuilder.swift: -------------------------------------------------------------------------------- 1 | #if swift(>=5.4) 2 | import Foundation 3 | 4 | /// A thematic break element. 5 | public struct ThematicBreak { 6 | public init() {} 7 | } 8 | 9 | /// Constructs ``Block`` arrays from multi-expression closures. 10 | @resultBuilder public enum BlockArrayBuilder { 11 | public static func buildBlock(_ components: [Block]...) -> [Block] { 12 | components.flatMap { $0 } 13 | } 14 | 15 | public static func buildExpression(_ expression: String) -> [Block] { 16 | [.paragraph(.init(text: [.text(expression)]))] 17 | } 18 | 19 | public static func buildExpression(_ expression: BlockQuote) -> [Block] { 20 | [.blockQuote(expression)] 21 | } 22 | 23 | public static func buildExpression(_ expression: BulletList) -> [Block] { 24 | [.bulletList(expression)] 25 | } 26 | 27 | public static func buildExpression(_ expression: OrderedList) -> [Block] { 28 | [.orderedList(expression)] 29 | } 30 | 31 | public static func buildExpression(_ expression: CodeBlock) -> [Block] { 32 | [.code(expression)] 33 | } 34 | 35 | public static func buildExpression(_ expression: HTMLBlock) -> [Block] { 36 | [.html(expression)] 37 | } 38 | 39 | public static func buildExpression(_ expression: Paragraph) -> [Block] { 40 | [.paragraph(expression)] 41 | } 42 | 43 | public static func buildExpression(_ expression: Heading) -> [Block] { 44 | [.heading(expression)] 45 | } 46 | 47 | public static func buildExpression(_: ThematicBreak) -> [Block] { 48 | [.thematicBreak] 49 | } 50 | 51 | public static func buildArray(_ components: [[Block]]) -> [Block] { 52 | components.flatMap { $0 } 53 | } 54 | 55 | public static func buildOptional(_ component: [Block]?) -> [Block] { 56 | component ?? [] 57 | } 58 | 59 | public static func buildEither(first component: [Block]) -> [Block] { 60 | component 61 | } 62 | 63 | public static func buildEither(second component: [Block]) -> [Block] { 64 | component 65 | } 66 | } 67 | #endif 68 | -------------------------------------------------------------------------------- /Sources/CommonMark/Inlines/InlineArrayBuilder.swift: -------------------------------------------------------------------------------- 1 | #if swift(>=5.4) 2 | import Foundation 3 | 4 | /// A soft line break. 5 | public struct SoftBreak { 6 | public init() {} 7 | } 8 | 9 | /// A hard line break. 10 | public struct LineBreak { 11 | public init() {} 12 | } 13 | 14 | /// Constructs ``Inline`` arrays from multi-expression closures. 15 | @resultBuilder public enum InlineArrayBuilder { 16 | public static func buildBlock(_ components: [Inline]...) -> [Inline] { 17 | components.flatMap { $0 } 18 | } 19 | 20 | public static func buildExpression(_ expression: String) -> [Inline] { 21 | [.text(expression)] 22 | } 23 | 24 | public static func buildExpression(_: SoftBreak) -> [Inline] { 25 | [.softBreak] 26 | } 27 | 28 | public static func buildExpression(_: LineBreak) -> [Inline] { 29 | [.lineBreak] 30 | } 31 | 32 | public static func buildExpression(_ expression: InlineCode) -> [Inline] { 33 | [.code(expression)] 34 | } 35 | 36 | public static func buildExpression(_ expression: InlineHTML) -> [Inline] { 37 | [.html(expression)] 38 | } 39 | 40 | public static func buildExpression(_ expression: Emphasis) -> [Inline] { 41 | [.emphasis(expression)] 42 | } 43 | 44 | public static func buildExpression(_ expression: Strong) -> [Inline] { 45 | [.strong(expression)] 46 | } 47 | 48 | public static func buildExpression(_ expression: Link) -> [Inline] { 49 | [.link(expression)] 50 | } 51 | 52 | public static func buildExpression(_ expression: Image) -> [Inline] { 53 | [.image(expression)] 54 | } 55 | 56 | public static func buildArray(_ components: [[Inline]]) -> [Inline] { 57 | components.flatMap { $0 } 58 | } 59 | 60 | public static func buildOptional(_ component: [Inline]?) -> [Inline] { 61 | component ?? [] 62 | } 63 | 64 | public static func buildEither(first component: [Inline]) -> [Inline] { 65 | component 66 | } 67 | 68 | public static func buildEither(second component: [Inline]) -> [Inline] { 69 | component 70 | } 71 | } 72 | #endif 73 | -------------------------------------------------------------------------------- /Sources/CommonMark/Document/CommonMarkNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import cmark 3 | 4 | internal class CommonMarkNode { 5 | let pointer: OpaquePointer 6 | let managed: Bool 7 | 8 | var type: cmark_node_type { 9 | cmark_node_get_type(pointer) 10 | } 11 | 12 | var typeString: String { 13 | String(cString: cmark_node_get_type_string(pointer)) 14 | } 15 | 16 | var literal: String? { 17 | guard let literal = cmark_node_get_literal(pointer) else { return nil } 18 | return String(cString: literal) 19 | } 20 | 21 | var url: String? { 22 | guard let url = cmark_node_get_url(pointer) else { return nil } 23 | return String(cString: url) 24 | } 25 | 26 | var title: String? { 27 | guard let title = cmark_node_get_title(pointer) else { return nil } 28 | return String(cString: title) 29 | } 30 | 31 | var fenceInfo: String? { 32 | guard let fenceInfo = cmark_node_get_fence_info(pointer) else { return nil } 33 | return String(cString: fenceInfo) 34 | } 35 | 36 | var headingLevel: Int { 37 | Int(cmark_node_get_heading_level(pointer)) 38 | } 39 | 40 | var listType: cmark_list_type { 41 | cmark_node_get_list_type(pointer) 42 | } 43 | 44 | var listStart: Int { 45 | Int(cmark_node_get_list_start(pointer)) 46 | } 47 | 48 | var listTight: Bool { 49 | cmark_node_get_list_tight(pointer) != 0 50 | } 51 | 52 | var children: [CommonMarkNode] { 53 | var result: [CommonMarkNode] = [] 54 | 55 | var child = cmark_node_first_child(pointer) 56 | while let unwrapped = child { 57 | result.append(CommonMarkNode(pointer: unwrapped, managed: false)) 58 | child = cmark_node_next(child) 59 | } 60 | return result 61 | } 62 | 63 | init(pointer: OpaquePointer, managed: Bool) { 64 | self.pointer = pointer 65 | self.managed = managed 66 | } 67 | 68 | convenience init?(markdown: String, options: Int32) { 69 | guard let pointer = cmark_parse_document(markdown, markdown.utf8.count, options) else { 70 | return nil 71 | } 72 | self.init(pointer: pointer, managed: true) 73 | } 74 | 75 | deinit { 76 | guard managed else { return } 77 | cmark_node_free(pointer) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Sources/cmark/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 is 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/cmark.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/CommonMark.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Sources/cmark/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.h" 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | typedef int32_t bufsize_t; 17 | 18 | typedef struct { 19 | cmark_mem *mem; 20 | unsigned char *ptr; 21 | bufsize_t asize, size; 22 | } cmark_strbuf; 23 | 24 | extern unsigned char cmark_strbuf__initbuf[]; 25 | 26 | #define CMARK_BUF_INIT(mem) \ 27 | { mem, cmark_strbuf__initbuf, 0, 0 } 28 | 29 | /** 30 | * Initialize a cmark_strbuf structure. 31 | * 32 | * For the cases where CMARK_BUF_INIT cannot be used to do static 33 | * initialization. 34 | */ 35 | void cmark_strbuf_init(cmark_mem *mem, cmark_strbuf *buf, 36 | bufsize_t initial_size); 37 | 38 | /** 39 | * Grow the buffer to hold at least `target_size` bytes. 40 | */ 41 | void cmark_strbuf_grow(cmark_strbuf *buf, bufsize_t target_size); 42 | 43 | void cmark_strbuf_free(cmark_strbuf *buf); 44 | void cmark_strbuf_swap(cmark_strbuf *buf_a, cmark_strbuf *buf_b); 45 | 46 | bufsize_t cmark_strbuf_len(const cmark_strbuf *buf); 47 | 48 | int cmark_strbuf_cmp(const cmark_strbuf *a, const cmark_strbuf *b); 49 | 50 | unsigned char *cmark_strbuf_detach(cmark_strbuf *buf); 51 | void cmark_strbuf_copy_cstr(char *data, bufsize_t datasize, 52 | const cmark_strbuf *buf); 53 | 54 | /* 55 | static CMARK_INLINE const char *cmark_strbuf_cstr(const cmark_strbuf *buf) { 56 | return (char *)buf->ptr; 57 | } 58 | */ 59 | 60 | #define cmark_strbuf_at(buf, n) ((buf)->ptr[n]) 61 | 62 | void cmark_strbuf_set(cmark_strbuf *buf, const unsigned char *data, 63 | bufsize_t len); 64 | void cmark_strbuf_sets(cmark_strbuf *buf, const char *string); 65 | void cmark_strbuf_putc(cmark_strbuf *buf, int c); 66 | void cmark_strbuf_put(cmark_strbuf *buf, const unsigned char *data, 67 | bufsize_t len); 68 | void cmark_strbuf_puts(cmark_strbuf *buf, const char *string); 69 | void cmark_strbuf_clear(cmark_strbuf *buf); 70 | 71 | bufsize_t cmark_strbuf_strchr(const cmark_strbuf *buf, int c, bufsize_t pos); 72 | bufsize_t cmark_strbuf_strrchr(const cmark_strbuf *buf, int c, bufsize_t pos); 73 | void cmark_strbuf_drop(cmark_strbuf *buf, bufsize_t n); 74 | void cmark_strbuf_truncate(cmark_strbuf *buf, bufsize_t len); 75 | void cmark_strbuf_rtrim(cmark_strbuf *buf); 76 | void cmark_strbuf_trim(cmark_strbuf *buf); 77 | void cmark_strbuf_normalize_whitespace(cmark_strbuf *s); 78 | void cmark_strbuf_unescape(cmark_strbuf *s); 79 | 80 | #ifdef __cplusplus 81 | } 82 | #endif 83 | 84 | #endif 85 | -------------------------------------------------------------------------------- /Sources/cmark/scanners.h: -------------------------------------------------------------------------------- 1 | #include "cmark.h" 2 | #include "chunk.h" 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | bufsize_t _scan_at(bufsize_t (*scanner)(const unsigned char *), cmark_chunk *c, 9 | bufsize_t offset); 10 | bufsize_t _scan_scheme(const unsigned char *p); 11 | bufsize_t _scan_autolink_uri(const unsigned char *p); 12 | bufsize_t _scan_autolink_email(const unsigned char *p); 13 | bufsize_t _scan_html_tag(const unsigned char *p); 14 | bufsize_t _scan_html_comment(const unsigned char *p); 15 | bufsize_t _scan_html_pi(const unsigned char *p); 16 | bufsize_t _scan_html_declaration(const unsigned char *p); 17 | bufsize_t _scan_html_cdata(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 | 34 | #define scan_scheme(c, n) _scan_at(&_scan_scheme, c, n) 35 | #define scan_autolink_uri(c, n) _scan_at(&_scan_autolink_uri, c, n) 36 | #define scan_autolink_email(c, n) _scan_at(&_scan_autolink_email, c, n) 37 | #define scan_html_tag(c, n) _scan_at(&_scan_html_tag, c, n) 38 | #define scan_html_comment(c, n) _scan_at(&_scan_html_comment, c, n) 39 | #define scan_html_pi(c, n) _scan_at(&_scan_html_pi, c, n) 40 | #define scan_html_declaration(c, n) _scan_at(&_scan_html_declaration, c, n) 41 | #define scan_html_cdata(c, n) _scan_at(&_scan_html_cdata, c, n) 42 | #define scan_html_block_start(c, n) _scan_at(&_scan_html_block_start, c, n) 43 | #define scan_html_block_start_7(c, n) _scan_at(&_scan_html_block_start_7, c, n) 44 | #define scan_html_block_end_1(c, n) _scan_at(&_scan_html_block_end_1, c, n) 45 | #define scan_html_block_end_2(c, n) _scan_at(&_scan_html_block_end_2, c, n) 46 | #define scan_html_block_end_3(c, n) _scan_at(&_scan_html_block_end_3, c, n) 47 | #define scan_html_block_end_4(c, n) _scan_at(&_scan_html_block_end_4, c, n) 48 | #define scan_html_block_end_5(c, n) _scan_at(&_scan_html_block_end_5, c, n) 49 | #define scan_link_title(c, n) _scan_at(&_scan_link_title, c, n) 50 | #define scan_spacechars(c, n) _scan_at(&_scan_spacechars, c, n) 51 | #define scan_atx_heading_start(c, n) _scan_at(&_scan_atx_heading_start, c, n) 52 | #define scan_setext_heading_line(c, n) \ 53 | _scan_at(&_scan_setext_heading_line, c, n) 54 | #define scan_open_code_fence(c, n) _scan_at(&_scan_open_code_fence, c, n) 55 | #define scan_close_code_fence(c, n) _scan_at(&_scan_close_code_fence, c, n) 56 | #define scan_entity(c, n) _scan_at(&_scan_entity, c, n) 57 | #define scan_dangerous_url(c, n) _scan_at(&_scan_dangerous_url, c, n) 58 | 59 | #ifdef __cplusplus 60 | } 61 | #endif 62 | -------------------------------------------------------------------------------- /Sources/cmark/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 characters & (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 | -------------------------------------------------------------------------------- /Tests/CommonMarkTests/ListItemArrayBuilderTests.swift: -------------------------------------------------------------------------------- 1 | #if swift(>=5.4) 2 | import CommonMark 3 | import XCTest 4 | 5 | final class ListItemArrayBuilderTests: XCTestCase { 6 | func testExpressions() { 7 | // given 8 | @ListItemArrayBuilder func build() -> [ListItem] { 9 | "Flour" 10 | ListItem { 11 | "Cheese" 12 | } 13 | ListItem { 14 | Paragraph { 15 | "Tomatoes" 16 | } 17 | } 18 | } 19 | 20 | // when 21 | let result = build() 22 | 23 | // then 24 | XCTAssertEqual( 25 | [ 26 | .init(blocks: [.paragraph(.init(text: [.text("Flour")]))]), 27 | .init(blocks: [.paragraph(.init(text: [.text("Cheese")]))]), 28 | .init(blocks: [.paragraph(.init(text: [.text("Tomatoes")]))]), 29 | ], 30 | result 31 | ) 32 | } 33 | 34 | func testForLoops() { 35 | // given 36 | @ListItemArrayBuilder func build() -> [ListItem] { 37 | for i in 0...3 { 38 | "\(i)" 39 | } 40 | } 41 | 42 | // when 43 | let result = build() 44 | 45 | // then 46 | XCTAssertEqual( 47 | [ 48 | .init(blocks: [.paragraph(.init(text: [.text("0")]))]), 49 | .init(blocks: [.paragraph(.init(text: [.text("1")]))]), 50 | .init(blocks: [.paragraph(.init(text: [.text("2")]))]), 51 | .init(blocks: [.paragraph(.init(text: [.text("3")]))]), 52 | ], 53 | result 54 | ) 55 | } 56 | 57 | func testIf() { 58 | @ListItemArrayBuilder func build() -> [ListItem] { 59 | "Something is:" 60 | if true { 61 | ListItem { 62 | BlockQuote { 63 | "true" 64 | } 65 | } 66 | } 67 | } 68 | 69 | // when 70 | let result = build() 71 | 72 | // then 73 | XCTAssertEqual( 74 | [ 75 | .init(blocks: [.paragraph(.init(text: [.text("Something is:")]))]), 76 | .init( 77 | blocks: [ 78 | .blockQuote( 79 | .init( 80 | items: [.paragraph(.init(text: [.text("true")]))] 81 | ) 82 | ) 83 | ] 84 | ), 85 | ], 86 | result 87 | ) 88 | } 89 | 90 | func testIfElse() { 91 | @ListItemArrayBuilder func build(_ value: Bool) -> [ListItem] { 92 | "Something is:" 93 | if value { 94 | ListItem { 95 | BlockQuote { 96 | "true" 97 | } 98 | } 99 | } else { 100 | "false" 101 | } 102 | } 103 | 104 | // when 105 | let result1 = build(true) 106 | let result2 = build(false) 107 | 108 | // then 109 | XCTAssertEqual( 110 | [ 111 | .init(blocks: [.paragraph(.init(text: [.text("Something is:")]))]), 112 | .init( 113 | blocks: [ 114 | .blockQuote( 115 | .init( 116 | items: [.paragraph(.init(text: [.text("true")]))] 117 | ) 118 | ) 119 | ] 120 | ), 121 | ], 122 | result1 123 | ) 124 | XCTAssertEqual( 125 | [ 126 | .init(blocks: [.paragraph(.init(text: [.text("Something is:")]))]), 127 | .init(blocks: [.paragraph(.init(text: [.text("false")]))]), 128 | ], 129 | result2 130 | ) 131 | } 132 | } 133 | #endif 134 | -------------------------------------------------------------------------------- /Sources/cmark/iterator.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "config.h" 5 | #include "node.h" 6 | #include "cmark.h" 7 | #include "iterator.h" 8 | 9 | static const int S_leaf_mask = 10 | (1 << CMARK_NODE_HTML_BLOCK) | (1 << CMARK_NODE_THEMATIC_BREAK) | 11 | (1 << CMARK_NODE_CODE_BLOCK) | (1 << CMARK_NODE_TEXT) | 12 | (1 << CMARK_NODE_SOFTBREAK) | (1 << CMARK_NODE_LINEBREAK) | 13 | (1 << CMARK_NODE_CODE) | (1 << CMARK_NODE_HTML_INLINE); 14 | 15 | cmark_iter *cmark_iter_new(cmark_node *root) { 16 | if (root == NULL) { 17 | return NULL; 18 | } 19 | cmark_mem *mem = root->mem; 20 | cmark_iter *iter = (cmark_iter *)mem->calloc(1, sizeof(cmark_iter)); 21 | iter->mem = mem; 22 | iter->root = root; 23 | iter->cur.ev_type = CMARK_EVENT_NONE; 24 | iter->cur.node = NULL; 25 | iter->next.ev_type = CMARK_EVENT_ENTER; 26 | iter->next.node = root; 27 | return iter; 28 | } 29 | 30 | void cmark_iter_free(cmark_iter *iter) { iter->mem->free(iter); } 31 | 32 | static bool S_is_leaf(cmark_node *node) { 33 | return ((1 << node->type) & S_leaf_mask) != 0; 34 | } 35 | 36 | cmark_event_type cmark_iter_next(cmark_iter *iter) { 37 | cmark_event_type ev_type = iter->next.ev_type; 38 | cmark_node *node = iter->next.node; 39 | 40 | iter->cur.ev_type = ev_type; 41 | iter->cur.node = node; 42 | 43 | if (ev_type == CMARK_EVENT_DONE) { 44 | return ev_type; 45 | } 46 | 47 | /* roll forward to next item, setting both fields */ 48 | if (ev_type == CMARK_EVENT_ENTER && !S_is_leaf(node)) { 49 | if (node->first_child == NULL) { 50 | /* stay on this node but exit */ 51 | iter->next.ev_type = CMARK_EVENT_EXIT; 52 | } else { 53 | iter->next.ev_type = CMARK_EVENT_ENTER; 54 | iter->next.node = node->first_child; 55 | } 56 | } else if (node == iter->root) { 57 | /* don't move past root */ 58 | iter->next.ev_type = CMARK_EVENT_DONE; 59 | iter->next.node = NULL; 60 | } else if (node->next) { 61 | iter->next.ev_type = CMARK_EVENT_ENTER; 62 | iter->next.node = node->next; 63 | } else if (node->parent) { 64 | iter->next.ev_type = CMARK_EVENT_EXIT; 65 | iter->next.node = node->parent; 66 | } else { 67 | assert(false); 68 | iter->next.ev_type = CMARK_EVENT_DONE; 69 | iter->next.node = NULL; 70 | } 71 | 72 | return ev_type; 73 | } 74 | 75 | void cmark_iter_reset(cmark_iter *iter, cmark_node *current, 76 | cmark_event_type event_type) { 77 | iter->next.ev_type = event_type; 78 | iter->next.node = current; 79 | cmark_iter_next(iter); 80 | } 81 | 82 | cmark_node *cmark_iter_get_node(cmark_iter *iter) { return iter->cur.node; } 83 | 84 | cmark_event_type cmark_iter_get_event_type(cmark_iter *iter) { 85 | return iter->cur.ev_type; 86 | } 87 | 88 | cmark_node *cmark_iter_get_root(cmark_iter *iter) { return iter->root; } 89 | 90 | void cmark_consolidate_text_nodes(cmark_node *root) { 91 | if (root == NULL) { 92 | return; 93 | } 94 | cmark_iter *iter = cmark_iter_new(root); 95 | cmark_strbuf buf = CMARK_BUF_INIT(iter->mem); 96 | cmark_event_type ev_type; 97 | cmark_node *cur, *tmp, *next; 98 | 99 | while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { 100 | cur = cmark_iter_get_node(iter); 101 | if (ev_type == CMARK_EVENT_ENTER && cur->type == CMARK_NODE_TEXT && 102 | cur->next && cur->next->type == CMARK_NODE_TEXT) { 103 | cmark_strbuf_clear(&buf); 104 | cmark_strbuf_put(&buf, cur->data, cur->len); 105 | tmp = cur->next; 106 | while (tmp && tmp->type == CMARK_NODE_TEXT) { 107 | cmark_iter_next(iter); // advance pointer 108 | cmark_strbuf_put(&buf, tmp->data, tmp->len); 109 | cur->end_column = tmp->end_column; 110 | next = tmp->next; 111 | cmark_node_free(tmp); 112 | tmp = next; 113 | } 114 | iter->mem->free(cur->data); 115 | cur->len = buf.size; 116 | cur->data = cmark_strbuf_detach(&buf); 117 | } 118 | } 119 | 120 | cmark_strbuf_free(&buf); 121 | cmark_iter_free(iter); 122 | } 123 | -------------------------------------------------------------------------------- /Tests/CommonMarkTests/InlineArrayBuilderTests.swift: -------------------------------------------------------------------------------- 1 | #if swift(>=5.4) 2 | import CommonMark 3 | import XCTest 4 | 5 | final class InlineArrayBuilderTests: XCTestCase { 6 | func testExpressions() { 7 | // given 8 | @InlineArrayBuilder func build() -> [Inline] { 9 | "Hello" 10 | SoftBreak() 11 | "world!" 12 | LineBreak() 13 | InlineCode("let a = b") 14 | Strong { 15 | "Everyone " 16 | Emphasis("must") 17 | " attend the meeting at 5 o’clock today." 18 | } 19 | Link("https://w.wiki/qYn") { 20 | "Hurricane" 21 | } 22 | " Erika was the strongest and longest-lasting tropical cyclone in the 1997 Atlantic " 23 | Link("https://w.wiki/qYn") { 24 | "hurricane" 25 | } 26 | " season." 27 | Image("https://commonmark.org/help/images/favicon.png", alt: "CommonMark") 28 | InlineHTML("
") 29 | } 30 | 31 | // when 32 | let result = build() 33 | 34 | // then 35 | XCTAssertEqual( 36 | [ 37 | .text("Hello"), 38 | .softBreak, 39 | .text("world!"), 40 | .lineBreak, 41 | .code(.init("let a = b")), 42 | .strong( 43 | .init(children: [ 44 | .text("Everyone "), 45 | .emphasis( 46 | .init(children: [ 47 | .text("must") 48 | ]) 49 | ), 50 | .text(" attend the meeting at 5 o’clock today."), 51 | ]) 52 | ), 53 | .link( 54 | .init( 55 | children: [.text("Hurricane")], 56 | url: URL(string: "https://w.wiki/qYn")! 57 | ) 58 | ), 59 | .text( 60 | " Erika was the strongest and longest-lasting tropical cyclone in the 1997 Atlantic " 61 | ), 62 | .link( 63 | .init( 64 | children: [.text("hurricane")], 65 | url: URL(string: "https://w.wiki/qYn")! 66 | ) 67 | ), 68 | .text(" season."), 69 | .image( 70 | .init("https://commonmark.org/help/images/favicon.png", alt: "CommonMark") 71 | ), 72 | .html(.init("
")), 73 | ], 74 | result 75 | ) 76 | } 77 | 78 | func testForLoops() { 79 | // given 80 | @InlineArrayBuilder func build() -> [Inline] { 81 | for i in 0...3 { 82 | "\(i) " 83 | } 84 | } 85 | 86 | // when 87 | let result = build() 88 | 89 | // then 90 | XCTAssertEqual( 91 | [ 92 | .text("0 "), 93 | .text("1 "), 94 | .text("2 "), 95 | .text("3 "), 96 | ], 97 | result 98 | ) 99 | } 100 | 101 | func testIf() { 102 | @InlineArrayBuilder func build() -> [Inline] { 103 | "Something is " 104 | if true { 105 | Emphasis { 106 | "true" 107 | } 108 | } 109 | } 110 | 111 | // when 112 | let result = build() 113 | 114 | // then 115 | XCTAssertEqual( 116 | [ 117 | .text("Something is "), 118 | .emphasis( 119 | .init(children: [.text("true")]) 120 | ), 121 | ], 122 | result 123 | ) 124 | } 125 | 126 | func testIfElse() { 127 | @InlineArrayBuilder func build(_ value: Bool) -> [Inline] { 128 | "Something is " 129 | if value { 130 | Emphasis { 131 | "true" 132 | } 133 | } else { 134 | "false" 135 | } 136 | } 137 | 138 | // when 139 | let result1 = build(true) 140 | let result2 = build(false) 141 | 142 | // then 143 | XCTAssertEqual( 144 | [ 145 | .text("Something is "), 146 | .emphasis( 147 | .init(children: [.text("true")]) 148 | ), 149 | ], 150 | result1 151 | ) 152 | XCTAssertEqual( 153 | [ 154 | .text("Something is "), 155 | .text("false"), 156 | ], 157 | result2 158 | ) 159 | } 160 | } 161 | #endif 162 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/SwiftCommonMark-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 70 | 71 | 81 | 82 | 88 | 89 | 95 | 96 | 97 | 98 | 100 | 101 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /Sources/cmark/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 | int max_digits = 7; 46 | 47 | if (_isdigit(src[1])) { 48 | for (i = 1; i < size && _isdigit(src[i]); ++i) { 49 | codepoint = (codepoint * 10) + (src[i] - '0'); 50 | 51 | if (codepoint >= 0x110000) { 52 | // Keep counting digits but 53 | // avoid integer overflow. 54 | codepoint = 0x110000; 55 | } 56 | } 57 | 58 | num_digits = i - 1; 59 | max_digits = 7; 60 | } 61 | 62 | else if (src[1] == 'x' || src[1] == 'X') { 63 | for (i = 2; i < size && _isxdigit(src[i]); ++i) { 64 | codepoint = (codepoint * 16) + ((src[i] | 32) % 39 - 9); 65 | 66 | if (codepoint >= 0x110000) { 67 | // Keep counting digits but 68 | // avoid integer overflow. 69 | codepoint = 0x110000; 70 | } 71 | } 72 | 73 | num_digits = i - 2; 74 | max_digits = 6; 75 | } 76 | 77 | if (num_digits >= 1 && num_digits <= max_digits && 78 | i < size && src[i] == ';') { 79 | if (codepoint == 0 || (codepoint >= 0xD800 && codepoint < 0xE000) || 80 | codepoint >= 0x110000) { 81 | codepoint = 0xFFFD; 82 | } 83 | cmark_utf8proc_encode_char(codepoint, ob); 84 | return i + 1; 85 | } 86 | } 87 | 88 | else { 89 | if (size > CMARK_ENTITY_MAX_LENGTH) 90 | size = CMARK_ENTITY_MAX_LENGTH; 91 | 92 | for (i = CMARK_ENTITY_MIN_LENGTH; i < size; ++i) { 93 | if (src[i] == ' ') 94 | break; 95 | 96 | if (src[i] == ';') { 97 | const unsigned char *entity = S_lookup_entity(src, i); 98 | 99 | if (entity != NULL) { 100 | cmark_strbuf_puts(ob, (const char *)entity); 101 | return i + 1; 102 | } 103 | 104 | break; 105 | } 106 | } 107 | } 108 | 109 | return 0; 110 | } 111 | 112 | int houdini_unescape_html(cmark_strbuf *ob, const uint8_t *src, 113 | bufsize_t size) { 114 | bufsize_t i = 0, org, ent; 115 | 116 | while (i < size) { 117 | org = i; 118 | while (i < size && src[i] != '&') 119 | i++; 120 | 121 | if (likely(i > org)) { 122 | if (unlikely(org == 0)) { 123 | if (i >= size) 124 | return 0; 125 | 126 | cmark_strbuf_grow(ob, HOUDINI_UNESCAPED_SIZE(size)); 127 | } 128 | 129 | cmark_strbuf_put(ob, src + org, i - org); 130 | } 131 | 132 | /* escaping */ 133 | if (i >= size) 134 | break; 135 | 136 | i++; 137 | 138 | ent = houdini_unescape_ent(ob, src + i, size - i); 139 | i += ent; 140 | 141 | /* not really an entity */ 142 | if (ent == 0) 143 | cmark_strbuf_putc(ob, '&'); 144 | } 145 | 146 | return 1; 147 | } 148 | 149 | void houdini_unescape_html_f(cmark_strbuf *ob, const uint8_t *src, 150 | bufsize_t size) { 151 | if (!houdini_unescape_html(ob, src, size)) 152 | cmark_strbuf_put(ob, src, size); 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > **Warning** 2 | > 3 | > **This repo has been archived.** 4 | > 5 | > Consider using https://github.com/apple/swift-markdown if you need to parse and transform Markdown using Swift or 6 | > https://github.com/gonzalezreal/swift-markdown-ui if you need to render Markdown in SwiftUI. 7 | 8 | # SwiftCommonMark 9 | [![CI](https://github.com/gonzalezreal/SwiftCommonMark/workflows/CI/badge.svg)](https://github.com/gonzalezreal/SwiftCommonMark/actions?query=workflow%3ACI) 10 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fgonzalezreal%2FSwiftCommonMark%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/gonzalezreal/SwiftCommonMark) 11 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fgonzalezreal%2FSwiftCommonMark%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/gonzalezreal/SwiftCommonMark) 12 | [![contact: @gonzalezreal](https://img.shields.io/badge/contact-@gonzalezreal-blue.svg?style=flat)](https://twitter.com/gonzalezreal) 13 | 14 | SwiftCommonMark is a library for parsing and creating Markdown documents in Swift, fully compliant with the [CommonMark Spec](https://spec.commonmark.org/current/). 15 | 16 | ## Usage 17 | 18 | A CommonMark `Document` consists of a sequence of blocks—structural elements like paragraphs, 19 | block quotations, lists, headings, rules, and code blocks. Some blocks, like blockquotes and 20 | list items, contain other blocks; others, like headings and paragraphs, contain inline text, 21 | links, emphasized text, images, code spans, etc. 22 | 23 | You can create a `Document` by passing a CommonMark-formatted `String` or `Data` instance to 24 | initializers like `init(markdown:options:)`. 25 | 26 | ```swift 27 | do { 28 | let document = try Document( 29 | markdown: "You can try **CommonMark** [here](https://spec.commonmark.org/dingus/)." 30 | ) 31 | } catch { 32 | print("Couldn't parse document.") 33 | } 34 | ``` 35 | 36 | From Swift 5.4 onwards, you can create a `Document` by passing an array of `Block`s 37 | constructed with a `BlockArrayBuilder`. 38 | 39 | ```swift 40 | let document = Document { 41 | Heading(level: 2) { 42 | "Markdown lists" 43 | } 44 | Paragraph { 45 | "Sometimes you want numbered lists:" 46 | } 47 | OrderedList { 48 | "One" 49 | "Two" 50 | "Three" 51 | } 52 | Paragraph { 53 | "Sometimes you want bullet points:" 54 | } 55 | BulletList { 56 | ListItem { 57 | Paragraph { 58 | "Start a line with a " 59 | Strong("star") 60 | } 61 | } 62 | ListItem { 63 | "Profit!" 64 | } 65 | ListItem { 66 | "And you can have sub points:" 67 | BulletList { 68 | "Like this" 69 | "And this" 70 | } 71 | } 72 | } 73 | } 74 | ``` 75 | 76 | You can inspect the elements of a `Document` by accessing its `blocks` property. 77 | 78 | ```swift 79 | for block in document.blocks { 80 | switch block { 81 | case .blockQuote(let blockQuote): 82 | for item in blockQuote.items { 83 | // Inspect the item 84 | } 85 | case .bulletList(let bulletList): 86 | for item in bulletList.items { 87 | // Inspect the list item 88 | } 89 | case .orderedList(let orderedList): 90 | for item in orderedList.items { 91 | // Inspect the list item 92 | } 93 | case .code(let codeBlock): 94 | print(codeBlock.language) 95 | print(codeBlock.code) 96 | case .html(let htmlBlock): 97 | print(htmlBlock.html) 98 | case .paragraph(let paragraph): 99 | for inline in paragraph.text { 100 | // Inspect the inline 101 | } 102 | case .heading(let heading): 103 | print(heading.level) 104 | for inline in heading.text { 105 | // Inspect the inline 106 | } 107 | case .thematicBreak: 108 | // A thematic break 109 | } 110 | } 111 | ``` 112 | 113 | You can get back the CommonMark formatted text for a `Document` or render it as HTML. 114 | 115 | ```swift 116 | let markdown = document.renderCommonMark() 117 | let html = document.renderHTML() 118 | ``` 119 | 120 | ## Installation 121 | You can add SwiftCommonMark to an Xcode project by adding it as a package dependency. 122 | 1. From the **File** menu, select **Swift Packages › Add Package Dependency…** 123 | 1. Enter `https://github.com/gonzalezreal/SwiftCommonMark` into the package repository URL text field 124 | 1. Link **CommonMark** to your application target 125 | 126 | ## Other Libraries 127 | - [SwiftDocOrg/CommonMark](https://github.com/SwiftDocOrg/CommonMark) 128 | -------------------------------------------------------------------------------- /Sources/CommonMark/Inlines/Inline+cmark.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import cmark 3 | 4 | extension Inline { 5 | /// Renders this inline as CommonMark-formatted text. 6 | public func renderCommonMark() -> String { 7 | let node = CommonMarkNode(inline: self, managed: true) 8 | return String(cString: cmark_render_commonmark(node.pointer, CMARK_OPT_DEFAULT, 0)) 9 | } 10 | 11 | /// Renders this inline as HTML. 12 | /// - Parameter options: Options that affect how the inline is rendered as HTML. 13 | public func renderHTML(options: Document.RenderingOptions = .init()) -> String { 14 | let node = CommonMarkNode(inline: self, managed: true) 15 | return String(cString: cmark_render_html(node.pointer, options.rawValue)) 16 | } 17 | } 18 | 19 | extension Inline { 20 | init?(commonMarkNode: CommonMarkNode) { 21 | switch commonMarkNode.type { 22 | case CMARK_NODE_TEXT: 23 | self = .text(commonMarkNode.literal ?? "") 24 | case CMARK_NODE_SOFTBREAK: 25 | self = .softBreak 26 | case CMARK_NODE_LINEBREAK: 27 | self = .lineBreak 28 | case CMARK_NODE_CODE: 29 | self = .code(.init(commonMarkNode.literal!)) 30 | case CMARK_NODE_HTML_INLINE: 31 | self = .html(.init(commonMarkNode.literal!)) 32 | case CMARK_NODE_EMPH: 33 | self = .emphasis(.init(children: commonMarkNode.children.compactMap(Inline.init))) 34 | case CMARK_NODE_STRONG: 35 | self = .strong(.init(children: commonMarkNode.children.compactMap(Inline.init))) 36 | case CMARK_NODE_LINK: 37 | self = .link( 38 | .init( 39 | children: commonMarkNode.children.compactMap(Inline.init), 40 | url: commonMarkNode.url.flatMap(URL.init(string:)), 41 | title: commonMarkNode.title?.isEmpty ?? true ? nil : commonMarkNode.title 42 | ) 43 | ) 44 | case CMARK_NODE_IMAGE: 45 | self = .image( 46 | .init( 47 | children: commonMarkNode.children.compactMap(Inline.init), 48 | url: commonMarkNode.url.flatMap(URL.init(string:)), 49 | title: commonMarkNode.title?.isEmpty ?? true ? nil : commonMarkNode.title 50 | ) 51 | ) 52 | default: 53 | assertionFailure("Unknown inline type '\(commonMarkNode.typeString)'") 54 | return nil 55 | } 56 | } 57 | } 58 | 59 | extension CommonMarkNode { 60 | convenience init(inline: Inline, managed: Bool) { 61 | let pointer: OpaquePointer 62 | 63 | switch inline { 64 | case let .text(literal): 65 | pointer = cmark_node_new(CMARK_NODE_TEXT) 66 | cmark_node_set_literal(pointer, literal) 67 | case .softBreak: 68 | pointer = cmark_node_new(CMARK_NODE_SOFTBREAK) 69 | case .lineBreak: 70 | pointer = cmark_node_new(CMARK_NODE_LINEBREAK) 71 | case let .code(inlineCode): 72 | pointer = cmark_node_new(CMARK_NODE_CODE) 73 | cmark_node_set_literal(pointer, inlineCode.code) 74 | case let .html(inlineHTML): 75 | pointer = cmark_node_new(CMARK_NODE_HTML_INLINE) 76 | cmark_node_set_literal(pointer, inlineHTML.html) 77 | case let .emphasis(emphasis): 78 | pointer = cmark_node_new(CMARK_NODE_EMPH) 79 | emphasis.children.map { 80 | CommonMarkNode(inline: $0, managed: false) 81 | }.forEach { node in 82 | cmark_node_append_child(pointer, node.pointer) 83 | } 84 | case let .strong(strong): 85 | pointer = cmark_node_new(CMARK_NODE_STRONG) 86 | strong.children.map { 87 | CommonMarkNode(inline: $0, managed: false) 88 | }.forEach { node in 89 | cmark_node_append_child(pointer, node.pointer) 90 | } 91 | case let .link(link): 92 | pointer = cmark_node_new(CMARK_NODE_LINK) 93 | if let url = link.url { 94 | cmark_node_set_url(pointer, url.absoluteString) 95 | } 96 | if let title = link.title { 97 | cmark_node_set_title(pointer, title) 98 | } 99 | link.children.map { 100 | CommonMarkNode(inline: $0, managed: false) 101 | }.forEach { node in 102 | cmark_node_append_child(pointer, node.pointer) 103 | } 104 | case let .image(image): 105 | pointer = cmark_node_new(CMARK_NODE_IMAGE) 106 | if let url = image.url { 107 | cmark_node_set_url(pointer, url.absoluteString) 108 | } 109 | if let title = image.title { 110 | cmark_node_set_title(pointer, title) 111 | } 112 | image.children.map { 113 | CommonMarkNode(inline: $0, managed: false) 114 | }.forEach { node in 115 | cmark_node_append_child(pointer, node.pointer) 116 | } 117 | } 118 | 119 | self.init(pointer: pointer, managed: managed) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Sources/cmark/references.c: -------------------------------------------------------------------------------- 1 | #include "cmark.h" 2 | #include "utf8.h" 3 | #include "parser.h" 4 | #include "references.h" 5 | #include "inlines.h" 6 | #include "chunk.h" 7 | 8 | static void reference_free(cmark_reference_map *map, cmark_reference *ref) { 9 | cmark_mem *mem = map->mem; 10 | if (ref != NULL) { 11 | mem->free(ref->label); 12 | mem->free(ref->url); 13 | mem->free(ref->title); 14 | mem->free(ref); 15 | } 16 | } 17 | 18 | // normalize reference: collapse internal whitespace to single space, 19 | // remove leading/trailing whitespace, case fold 20 | // Return NULL if the reference name is actually empty (i.e. composed 21 | // solely from whitespace) 22 | static unsigned char *normalize_reference(cmark_mem *mem, cmark_chunk *ref) { 23 | cmark_strbuf normalized = CMARK_BUF_INIT(mem); 24 | unsigned char *result; 25 | 26 | if (ref == NULL) 27 | return NULL; 28 | 29 | if (ref->len == 0) 30 | return NULL; 31 | 32 | cmark_utf8proc_case_fold(&normalized, ref->data, ref->len); 33 | cmark_strbuf_trim(&normalized); 34 | cmark_strbuf_normalize_whitespace(&normalized); 35 | 36 | result = cmark_strbuf_detach(&normalized); 37 | assert(result); 38 | 39 | if (result[0] == '\0') { 40 | mem->free(result); 41 | return NULL; 42 | } 43 | 44 | return result; 45 | } 46 | 47 | void cmark_reference_create(cmark_reference_map *map, cmark_chunk *label, 48 | cmark_chunk *url, cmark_chunk *title) { 49 | cmark_reference *ref; 50 | unsigned char *reflabel = normalize_reference(map->mem, label); 51 | 52 | /* empty reference name, or composed from only whitespace */ 53 | if (reflabel == NULL) 54 | return; 55 | 56 | assert(map->sorted == NULL); 57 | 58 | ref = (cmark_reference *)map->mem->calloc(1, sizeof(*ref)); 59 | ref->label = reflabel; 60 | ref->url = cmark_clean_url(map->mem, url); 61 | ref->title = cmark_clean_title(map->mem, title); 62 | ref->age = map->size; 63 | ref->next = map->refs; 64 | 65 | if (ref->url != NULL) 66 | ref->size += strlen((char*)ref->url); 67 | if (ref->title != NULL) 68 | ref->size += strlen((char*)ref->title); 69 | 70 | map->refs = ref; 71 | map->size++; 72 | } 73 | 74 | static int 75 | labelcmp(const unsigned char *a, const unsigned char *b) { 76 | return strcmp((const char *)a, (const char *)b); 77 | } 78 | 79 | static int 80 | refcmp(const void *p1, const void *p2) { 81 | cmark_reference *r1 = *(cmark_reference **)p1; 82 | cmark_reference *r2 = *(cmark_reference **)p2; 83 | int res = labelcmp(r1->label, r2->label); 84 | return res ? res : ((int)r1->age - (int)r2->age); 85 | } 86 | 87 | static int 88 | refsearch(const void *label, const void *p2) { 89 | cmark_reference *ref = *(cmark_reference **)p2; 90 | return labelcmp((const unsigned char *)label, ref->label); 91 | } 92 | 93 | static void sort_references(cmark_reference_map *map) { 94 | unsigned int i = 0, last = 0, size = map->size; 95 | cmark_reference *r = map->refs, **sorted = NULL; 96 | 97 | sorted = (cmark_reference **)map->mem->calloc(size, sizeof(cmark_reference *)); 98 | while (r) { 99 | sorted[i++] = r; 100 | r = r->next; 101 | } 102 | 103 | qsort(sorted, size, sizeof(cmark_reference *), refcmp); 104 | 105 | for (i = 1; i < size; i++) { 106 | if (labelcmp(sorted[i]->label, sorted[last]->label) != 0) 107 | sorted[++last] = sorted[i]; 108 | } 109 | map->sorted = sorted; 110 | map->size = last + 1; 111 | } 112 | 113 | // Returns reference if refmap contains a reference with matching 114 | // label, otherwise NULL. 115 | cmark_reference *cmark_reference_lookup(cmark_reference_map *map, 116 | cmark_chunk *label) { 117 | cmark_reference **ref = NULL; 118 | cmark_reference *r = NULL; 119 | unsigned char *norm; 120 | 121 | if (label->len < 1 || label->len > MAX_LINK_LABEL_LENGTH) 122 | return NULL; 123 | 124 | if (map == NULL || !map->size) 125 | return NULL; 126 | 127 | norm = normalize_reference(map->mem, label); 128 | if (norm == NULL) 129 | return NULL; 130 | 131 | if (!map->sorted) 132 | sort_references(map); 133 | 134 | ref = (cmark_reference **)bsearch(norm, map->sorted, map->size, sizeof(cmark_reference *), 135 | refsearch); 136 | map->mem->free(norm); 137 | 138 | if (ref != NULL) { 139 | r = ref[0]; 140 | /* Check for expansion limit */ 141 | if (map->max_ref_size && r->size > map->max_ref_size - map->ref_size) 142 | return NULL; 143 | map->ref_size += r->size; 144 | } 145 | 146 | return r; 147 | } 148 | 149 | void cmark_reference_map_free(cmark_reference_map *map) { 150 | cmark_reference *ref; 151 | 152 | if (map == NULL) 153 | return; 154 | 155 | ref = map->refs; 156 | while (ref) { 157 | cmark_reference *next = ref->next; 158 | reference_free(map, ref); 159 | ref = next; 160 | } 161 | 162 | map->mem->free(map->sorted); 163 | map->mem->free(map); 164 | } 165 | 166 | cmark_reference_map *cmark_reference_map_new(cmark_mem *mem) { 167 | cmark_reference_map *map = 168 | (cmark_reference_map *)mem->calloc(1, sizeof(cmark_reference_map)); 169 | map->mem = mem; 170 | return map; 171 | } 172 | -------------------------------------------------------------------------------- /Sources/CommonMark/Blocks/Block+cmark.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import cmark 3 | 4 | extension Block { 5 | /// Renders this block as CommonMark-formatted text. 6 | public func renderCommonMark() -> String { 7 | let node = CommonMarkNode(block: self, managed: true) 8 | return String(cString: cmark_render_commonmark(node.pointer, CMARK_OPT_DEFAULT, 0)) 9 | } 10 | 11 | /// Renders this block as HTML. 12 | /// - Parameter options: Options that affect how the block is rendered as HTML. 13 | public func renderHTML(options: Document.RenderingOptions = .init()) -> String { 14 | let node = CommonMarkNode(block: self, managed: true) 15 | return String(cString: cmark_render_html(node.pointer, options.rawValue)) 16 | } 17 | } 18 | 19 | extension Block { 20 | init?(commonMarkNode: CommonMarkNode) { 21 | switch commonMarkNode.type { 22 | case CMARK_NODE_BLOCK_QUOTE: 23 | self = .blockQuote(.init(items: commonMarkNode.children.compactMap(Block.init))) 24 | case CMARK_NODE_LIST where commonMarkNode.listType == CMARK_ORDERED_LIST: 25 | self = .orderedList( 26 | .init( 27 | items: commonMarkNode.children.compactMap(ListItem.init(commonMarkNode:)), 28 | start: commonMarkNode.listStart, 29 | tight: commonMarkNode.listTight 30 | ) 31 | ) 32 | case CMARK_NODE_LIST: 33 | self = .bulletList( 34 | .init( 35 | items: commonMarkNode.children.compactMap(ListItem.init(commonMarkNode:)), 36 | tight: commonMarkNode.listTight 37 | ) 38 | ) 39 | case CMARK_NODE_CODE_BLOCK: 40 | self = .code( 41 | .init(language: commonMarkNode.fenceInfo, code: { commonMarkNode.literal ?? "" }) 42 | ) 43 | case CMARK_NODE_HTML_BLOCK: 44 | self = .html(.init(html: commonMarkNode.literal ?? "")) 45 | case CMARK_NODE_PARAGRAPH: 46 | self = .paragraph(.init(text: commonMarkNode.children.compactMap(Inline.init))) 47 | case CMARK_NODE_HEADING: 48 | self = .heading( 49 | .init( 50 | text: commonMarkNode.children.compactMap(Inline.init), 51 | level: commonMarkNode.headingLevel 52 | ) 53 | ) 54 | case CMARK_NODE_THEMATIC_BREAK: 55 | self = .thematicBreak 56 | default: 57 | assertionFailure("Unknown block type '\(commonMarkNode.typeString)'") 58 | return nil 59 | } 60 | } 61 | } 62 | 63 | extension ListItem { 64 | fileprivate init?(commonMarkNode: CommonMarkNode) { 65 | guard case CMARK_NODE_ITEM = commonMarkNode.type else { 66 | assertionFailure("Expecting 'CMARK_NODE_ITEM' but instead got '\(commonMarkNode.typeString)'") 67 | return nil 68 | } 69 | self.init(blocks: commonMarkNode.children.compactMap(Block.init)) 70 | } 71 | } 72 | 73 | extension CommonMarkNode { 74 | convenience init(block: Block, managed: Bool) { 75 | let pointer: OpaquePointer 76 | 77 | switch block { 78 | case let .blockQuote(blockQuote): 79 | pointer = cmark_node_new(CMARK_NODE_BLOCK_QUOTE) 80 | blockQuote.items.map { 81 | CommonMarkNode(block: $0, managed: false) 82 | }.forEach { node in 83 | cmark_node_append_child(pointer, node.pointer) 84 | } 85 | case let .bulletList(list): 86 | pointer = cmark_node_new(CMARK_NODE_LIST) 87 | cmark_node_set_list_type(pointer, CMARK_BULLET_LIST) 88 | cmark_node_set_list_tight(pointer, list.tight ? 1 : 0) 89 | list.items.map(CommonMarkNode.init(listItem:)).forEach { childNode in 90 | cmark_node_append_child(pointer, childNode.pointer) 91 | } 92 | case let .orderedList(list): 93 | pointer = cmark_node_new(CMARK_NODE_LIST) 94 | cmark_node_set_list_type(pointer, CMARK_ORDERED_LIST) 95 | cmark_node_set_list_start(pointer, Int32(list.start)) 96 | cmark_node_set_list_tight(pointer, list.tight ? 1 : 0) 97 | list.items.map(CommonMarkNode.init(listItem:)).forEach { childNode in 98 | cmark_node_append_child(pointer, childNode.pointer) 99 | } 100 | case let .code(codeBlock): 101 | pointer = cmark_node_new(CMARK_NODE_CODE_BLOCK) 102 | cmark_node_set_literal(pointer, codeBlock.code) 103 | if let language = codeBlock.language, !language.isEmpty { 104 | cmark_node_set_fence_info(pointer, language) 105 | } 106 | case let .html(htmlBlock): 107 | pointer = cmark_node_new(CMARK_NODE_HTML_BLOCK) 108 | cmark_node_set_literal(pointer, htmlBlock.html) 109 | case let .paragraph(paragraph): 110 | pointer = cmark_node_new(CMARK_NODE_PARAGRAPH) 111 | paragraph.text.map { 112 | CommonMarkNode(inline: $0, managed: false) 113 | }.forEach { node in 114 | cmark_node_append_child(pointer, node.pointer) 115 | } 116 | case let .heading(heading): 117 | pointer = cmark_node_new(CMARK_NODE_HEADING) 118 | cmark_node_set_heading_level(pointer, Int32(heading.level)) 119 | heading.text.map { 120 | CommonMarkNode(inline: $0, managed: false) 121 | }.forEach { node in 122 | cmark_node_append_child(pointer, node.pointer) 123 | } 124 | case .thematicBreak: 125 | pointer = cmark_node_new(CMARK_NODE_THEMATIC_BREAK) 126 | } 127 | 128 | self.init(pointer: pointer, managed: managed) 129 | } 130 | } 131 | 132 | extension CommonMarkNode { 133 | fileprivate convenience init(listItem: ListItem) { 134 | let pointer: OpaquePointer = cmark_node_new(CMARK_NODE_ITEM) 135 | listItem.blocks.map { 136 | CommonMarkNode(block: $0, managed: false) 137 | }.forEach { node in 138 | cmark_node_append_child(pointer, node.pointer) 139 | } 140 | 141 | self.init(pointer: pointer, managed: false) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [INSERT CONTACT METHOD]. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /Sources/CommonMark/Document/Document.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A value type that stores a CommonMark document as a sequence of blocks. 4 | /// 5 | /// A CommonMark document consists of a sequence of blocks—structural elements like paragraphs, 6 | /// block quotations, lists, headings, rules, and code blocks. Some blocks, like blockquotes and list 7 | /// items, contain other blocks; others, like headings and paragraphs, contain inline text, links, 8 | /// emphasized text, images, code spans, etc. 9 | /// 10 | /// You can create a ``Document`` by passing a CommonMark-formatted `String` or `Data` 11 | /// instance to initializers like ``init(markdown:options:)``. 12 | /// ```swift 13 | /// do { 14 | /// let document = try Document( 15 | /// markdown: "You can try **CommonMark** [here](https://spec.commonmark.org/dingus/)." 16 | /// ) 17 | /// } catch { 18 | /// print("Couldn't parse document.") 19 | /// } 20 | /// ``` 21 | /// 22 | /// From Swift 5.4 onwards, you can create a ``Document`` by passing an array of ``Block``s 23 | /// constructed with a ``BlockArrayBuilder``. 24 | /// ```swift 25 | /// let document = Document { 26 | /// Heading(level: 2) { 27 | /// "Markdown lists" 28 | /// } 29 | /// Paragraph { 30 | /// "Sometimes you want numbered lists:" 31 | /// } 32 | /// OrderedList { 33 | /// "One" 34 | /// "Two" 35 | /// "Three" 36 | /// } 37 | /// Paragraph { 38 | /// "Sometimes you want bullet points:" 39 | /// } 40 | /// BulletList { 41 | /// ListItem { 42 | /// Paragraph { 43 | /// "Start a line with a " 44 | /// Strong("star") 45 | /// } 46 | /// } 47 | /// ListItem { 48 | /// "Profit!" 49 | /// } 50 | /// ListItem { 51 | /// "And you can have sub points:" 52 | /// BulletList { 53 | /// "Like this" 54 | /// "And this" 55 | /// } 56 | /// } 57 | /// } 58 | /// } 59 | /// ``` 60 | /// 61 | /// You can inspect the elements of a ``Document`` by accessing its ``blocks`` property. 62 | /// ```swift 63 | /// for block in document.blocks { 64 | /// switch block { 65 | /// case .blockQuote(let blockQuote): 66 | /// for item in blockQuote.items { 67 | /// // Inspect the item 68 | /// } 69 | /// case .bulletList(let bulletList): 70 | /// for item in bulletList.items { 71 | /// // Inspect the list item 72 | /// } 73 | /// case .orderedList(let orderedList): 74 | /// for item in orderedList.items { 75 | /// // Inspect the list item 76 | /// } 77 | /// case .code(let codeBlock): 78 | /// print(codeBlock.language) 79 | /// print(codeBlock.code) 80 | /// case .html(let htmlBlock): 81 | /// print(htmlBlock.html) 82 | /// case .paragraph(let paragraph): 83 | /// for inline in paragraph.text { 84 | /// // Inspect the inline 85 | /// } 86 | /// case .heading(let heading): 87 | /// print(heading.level) 88 | /// for inline in heading.text { 89 | /// // Inspect the inline 90 | /// } 91 | /// case .thematicBreak: 92 | /// // A thematic break 93 | /// } 94 | /// } 95 | /// ``` 96 | /// 97 | /// You can get back the CommonMark formatted text for a ``Document`` or render it as HTML. 98 | /// ```swift 99 | /// let markdown = document.renderCommonMark() 100 | /// let html = document.renderHTML() 101 | /// ``` 102 | public struct Document: Hashable { 103 | /// The blocks that form this document. 104 | public var blocks: [Block] 105 | 106 | /// Creates a document from a CommonMark-formatted string using the provided options. 107 | /// - Parameters: 108 | /// - markdown: The string that contains the CommonMark formatting. 109 | /// - options: Options that affect how the initializer parses the CommonMark string. Defaults to no options. 110 | public init(markdown: String, options: ParsingOptions = .init()) throws { 111 | guard let node = CommonMarkNode(markdown: markdown, options: options.rawValue) else { 112 | throw ParsingError.invalidData 113 | } 114 | self.init(blocks: node.children.compactMap(Block.init)) 115 | } 116 | 117 | /// Creates a document from a CommonMark-formatted data using the provided options. 118 | /// - Parameters: 119 | /// - markdown: The `Data` instance that contains the CommonMark formatting. 120 | /// - options: Options that affect how the initializer parses the CommonMark string. Defaults to no options. 121 | public init(markdown: Data, options: ParsingOptions = .init()) throws { 122 | try self.init(markdown: String(decoding: markdown, as: UTF8.self), options: options) 123 | } 124 | 125 | /// Creates a document from the contents of a specified URL that contains CommonMark-formatted data, using the provided options. 126 | /// - Parameters: 127 | /// - url: The URL to load CommonMark-formatted data from. 128 | /// - options: Options that affect how the initializer parses the CommonMark string. Defaults to no options. 129 | public init(contentsOf url: URL, options: ParsingOptions = .init()) throws { 130 | try self.init(markdown: Data(contentsOf: url), options: options) 131 | } 132 | 133 | /// Creates a document with the given array of blocks. 134 | /// - Parameter blocks: The blocks that will form the document. 135 | public init(blocks: [Block]) { 136 | self.blocks = blocks 137 | } 138 | 139 | #if swift(>=5.4) 140 | /// Creates a document with the given array of blocks. 141 | /// - Parameter blocks: A ``BlockArrayBuilder`` that creates the blocks of this document. 142 | public init(@BlockArrayBuilder blocks: () -> [Block]) { 143 | self.init(blocks: blocks()) 144 | } 145 | #endif 146 | } 147 | 148 | extension Document: Codable { 149 | public init(from decoder: Decoder) throws { 150 | let container = try decoder.singleValueContainer() 151 | let markdown = try container.decode(String.self) 152 | 153 | try self.init(markdown: markdown) 154 | } 155 | 156 | public func encode(to encoder: Encoder) throws { 157 | var container = encoder.singleValueContainer() 158 | try container.encode(description) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Sources/cmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(${CMAKE_VERSION} VERSION_GREATER "3.3") 2 | cmake_policy(SET CMP0063 NEW) 3 | endif() 4 | 5 | set(LIBRARY "cmark") 6 | set(STATICLIBRARY "cmark_static") 7 | set(HEADERS 8 | cmark.h 9 | parser.h 10 | buffer.h 11 | node.h 12 | iterator.h 13 | chunk.h 14 | references.h 15 | utf8.h 16 | scanners.h 17 | inlines.h 18 | houdini.h 19 | cmark_ctype.h 20 | render.h 21 | ) 22 | set(LIBRARY_SOURCES 23 | cmark.c 24 | node.c 25 | iterator.c 26 | blocks.c 27 | inlines.c 28 | scanners.c 29 | scanners.re 30 | utf8.c 31 | buffer.c 32 | references.c 33 | render.c 34 | man.c 35 | xml.c 36 | html.c 37 | commonmark.c 38 | latex.c 39 | houdini_href_e.c 40 | houdini_html_e.c 41 | houdini_html_u.c 42 | cmark_ctype.c 43 | ${HEADERS} 44 | ) 45 | 46 | set(PROGRAM "cmark_exe") 47 | set(PROGRAM_SOURCES main.c) 48 | 49 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmark_version.h.in 50 | ${CMAKE_CURRENT_BINARY_DIR}/cmark_version.h) 51 | 52 | include(GNUInstallDirs) 53 | include (GenerateExportHeader) 54 | 55 | add_executable(${PROGRAM} ${PROGRAM_SOURCES}) 56 | cmark_add_compile_options(${PROGRAM}) 57 | set_target_properties(${PROGRAM} PROPERTIES 58 | OUTPUT_NAME "cmark") 59 | 60 | if (CMARK_STATIC) 61 | target_link_libraries(${PROGRAM} ${STATICLIBRARY}) 62 | # Disable the PUBLIC declarations when compiling the executable: 63 | set_target_properties(${PROGRAM} PROPERTIES 64 | COMPILE_FLAGS -DCMARK_STATIC_DEFINE) 65 | elseif (CMARK_SHARED) 66 | target_link_libraries(${PROGRAM} ${LIBRARY}) 67 | endif() 68 | 69 | # -fvisibility=hidden 70 | set(CMAKE_C_VISIBILITY_PRESET hidden) 71 | set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) 72 | 73 | if (CMARK_SHARED) 74 | add_library(${LIBRARY} SHARED ${LIBRARY_SOURCES}) 75 | cmark_add_compile_options(${LIBRARY}) 76 | set_target_properties(${LIBRARY} PROPERTIES 77 | MACOSX_RPATH TRUE 78 | OUTPUT_NAME "cmark" 79 | # Avoid name clash between PROGRAM and LIBRARY pdb files. 80 | PDB_NAME cmark_dll 81 | # Include minor version and patch level in soname for now. 82 | SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH} 83 | VERSION ${PROJECT_VERSION}) 84 | target_include_directories(${LIBRARY} INTERFACE 85 | $ 86 | $ 87 | $) 88 | add_library(cmark::cmark ALIAS ${LIBRARY}) 89 | 90 | generate_export_header(${LIBRARY} 91 | BASE_NAME ${PROJECT_NAME}) 92 | 93 | list(APPEND CMARK_INSTALL ${LIBRARY}) 94 | endif() 95 | 96 | if (CMARK_STATIC) 97 | add_library(${STATICLIBRARY} STATIC ${LIBRARY_SOURCES}) 98 | cmark_add_compile_options(${STATICLIBRARY}) 99 | set_target_properties(${STATICLIBRARY} PROPERTIES 100 | COMPILE_FLAGS -DCMARK_STATIC_DEFINE 101 | POSITION_INDEPENDENT_CODE ON 102 | VERSION ${PROJECT_VERSION}) 103 | if(MSVC) 104 | set_target_properties(${STATICLIBRARY} PROPERTIES 105 | OUTPUT_NAME cmark_static) 106 | else() 107 | set_target_properties(${STATICLIBRARY} PROPERTIES 108 | OUTPUT_NAME cmark) 109 | endif() 110 | target_include_directories(${STATICLIBRARY} INTERFACE 111 | $ 112 | $ 113 | $) 114 | add_library(cmark::cmark_static ALIAS ${STATICLIBRARY}) 115 | 116 | if (NOT CMARK_SHARED) 117 | generate_export_header(${STATICLIBRARY} 118 | BASE_NAME ${PROJECT_NAME}) 119 | endif() 120 | 121 | list(APPEND CMARK_INSTALL ${STATICLIBRARY}) 122 | endif() 123 | 124 | if (MSVC) 125 | set_property(TARGET ${PROGRAM} 126 | APPEND PROPERTY LINK_FLAGS /INCREMENTAL:NO) 127 | endif(MSVC) 128 | 129 | if(NOT MSVC OR CMAKE_HOST_SYSTEM_NAME STREQUAL Windows) 130 | set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_NO_WARNINGS ON) 131 | include(InstallRequiredSystemLibraries) 132 | endif() 133 | 134 | install(TARGETS ${PROGRAM} ${CMARK_INSTALL} 135 | EXPORT cmark-targets 136 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 137 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 138 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 139 | ) 140 | 141 | if(CMARK_SHARED OR CMARK_STATIC) 142 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libcmark.pc.in 143 | ${CMAKE_CURRENT_BINARY_DIR}/libcmark.pc @ONLY) 144 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libcmark.pc 145 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) 146 | 147 | install(FILES 148 | cmark.h 149 | ${CMAKE_CURRENT_BINARY_DIR}/cmark_export.h 150 | ${CMAKE_CURRENT_BINARY_DIR}/cmark_version.h 151 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 152 | ) 153 | 154 | # Include module for fuctions 155 | # - 'write_basic_package_version_file' 156 | # - 'configure_package_config_file' 157 | include(CMakePackageConfigHelpers) 158 | # generate cmark-config.cmake and cmark-config-version.cmake files 159 | configure_package_config_file( 160 | "cmarkConfig.cmake.in" 161 | "${CMAKE_CURRENT_BINARY_DIR}/generated/cmark-config.cmake" 162 | INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/cmark") 163 | write_basic_package_version_file( 164 | "${CMAKE_CURRENT_BINARY_DIR}/generated/cmark-config-version.cmake" 165 | VERSION ${PROJECT_VERSION} 166 | COMPATIBILITY SameMajorVersion) 167 | # install config and version file 168 | install( 169 | FILES "${CMAKE_CURRENT_BINARY_DIR}/generated/cmark-config.cmake" 170 | "${CMAKE_CURRENT_BINARY_DIR}/generated/cmark-config-version.cmake" 171 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/cmark" 172 | ) 173 | # install targets file 174 | install( 175 | EXPORT "cmark-targets" 176 | NAMESPACE "cmark::" 177 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/cmark" 178 | ) 179 | 180 | endif() 181 | 182 | # Feature tests 183 | include(CheckIncludeFile) 184 | include(CheckCSourceCompiles) 185 | CHECK_INCLUDE_FILE(stdbool.h HAVE_STDBOOL_H) 186 | CHECK_C_SOURCE_COMPILES( 187 | "int main() { __builtin_expect(0,0); return 0; }" 188 | HAVE___BUILTIN_EXPECT) 189 | CHECK_C_SOURCE_COMPILES(" 190 | int f(void) __attribute__ (()); 191 | int main() { return 0; } 192 | " HAVE___ATTRIBUTE__) 193 | 194 | CONFIGURE_FILE( 195 | ${CMAKE_CURRENT_SOURCE_DIR}/config.h.in 196 | ${CMAKE_CURRENT_BINARY_DIR}/config.h) 197 | 198 | if(CMARK_LIB_FUZZER) 199 | add_executable(cmark-fuzz ../test/cmark-fuzz.c ${LIBRARY_SOURCES}) 200 | cmark_add_compile_options(cmark-fuzz) 201 | target_link_libraries(cmark-fuzz "${CMAKE_LIB_FUZZER_PATH}") 202 | 203 | # cmark is written in C but the libFuzzer runtime is written in C++ which 204 | # needs to link against the C++ runtime. 205 | set_target_properties(cmark-fuzz PROPERTIES 206 | LINKER_LANGUAGE CXX) 207 | endif() 208 | -------------------------------------------------------------------------------- /Sources/cmark/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "config.h" 6 | #include "cmark.h" 7 | #include "node.h" 8 | 9 | #if defined(__OpenBSD__) 10 | # include 11 | # if OpenBSD >= 201605 12 | # define USE_PLEDGE 13 | # include 14 | # endif 15 | #endif 16 | 17 | #if defined(_WIN32) && !defined(__CYGWIN__) 18 | #include 19 | #include 20 | #endif 21 | 22 | typedef enum { 23 | FORMAT_NONE, 24 | FORMAT_HTML, 25 | FORMAT_XML, 26 | FORMAT_MAN, 27 | FORMAT_COMMONMARK, 28 | FORMAT_LATEX 29 | } writer_format; 30 | 31 | void print_usage() { 32 | printf("Usage: cmark [FILE*]\n"); 33 | printf("Options:\n"); 34 | printf(" --to, -t FORMAT Specify output format (html, xml, man, " 35 | "commonmark, latex)\n"); 36 | printf(" --width WIDTH Specify wrap width (default 0 = nowrap)\n"); 37 | printf(" --sourcepos Include source position attribute\n"); 38 | printf(" --hardbreaks Treat newlines as hard line breaks\n"); 39 | printf(" --nobreaks Render soft line breaks as spaces\n"); 40 | printf(" --safe Omit raw HTML and dangerous URLs\n"); 41 | printf(" --unsafe Render raw HTML and dangerous URLs\n"); 42 | printf(" --smart Use smart punctuation\n"); 43 | printf(" --validate-utf8 Replace invalid UTF-8 sequences with U+FFFD\n"); 44 | printf(" --help, -h Print usage information\n"); 45 | printf(" --version Print version\n"); 46 | } 47 | 48 | static void print_document(cmark_node *document, writer_format writer, 49 | int options, int width) { 50 | char *result; 51 | 52 | switch (writer) { 53 | case FORMAT_HTML: 54 | result = cmark_render_html(document, options); 55 | break; 56 | case FORMAT_XML: 57 | result = cmark_render_xml(document, options); 58 | break; 59 | case FORMAT_MAN: 60 | result = cmark_render_man(document, options, width); 61 | break; 62 | case FORMAT_COMMONMARK: 63 | result = cmark_render_commonmark(document, options, width); 64 | break; 65 | case FORMAT_LATEX: 66 | result = cmark_render_latex(document, options, width); 67 | break; 68 | default: 69 | fprintf(stderr, "Unknown format %d\n", writer); 70 | exit(1); 71 | } 72 | printf("%s", result); 73 | document->mem->free(result); 74 | } 75 | 76 | int main(int argc, char *argv[]) { 77 | int i, numfps = 0; 78 | int *files; 79 | char buffer[4096]; 80 | cmark_parser *parser; 81 | size_t bytes; 82 | cmark_node *document; 83 | int width = 0; 84 | char *unparsed; 85 | writer_format writer = FORMAT_HTML; 86 | int options = CMARK_OPT_DEFAULT; 87 | 88 | #ifdef USE_PLEDGE 89 | if (pledge("stdio rpath", NULL) != 0) { 90 | perror("pledge"); 91 | return 1; 92 | } 93 | #endif 94 | 95 | #if defined(_WIN32) && !defined(__CYGWIN__) 96 | _setmode(_fileno(stdin), _O_BINARY); 97 | _setmode(_fileno(stdout), _O_BINARY); 98 | #endif 99 | 100 | files = (int *)calloc(argc, sizeof(*files)); 101 | 102 | for (i = 1; i < argc; i++) { 103 | if (strcmp(argv[i], "--version") == 0) { 104 | printf("cmark %s", CMARK_VERSION_STRING); 105 | printf(" - CommonMark converter\n(C) 2014-2016 John MacFarlane\n"); 106 | exit(0); 107 | } else if (strcmp(argv[i], "--sourcepos") == 0) { 108 | options |= CMARK_OPT_SOURCEPOS; 109 | } else if (strcmp(argv[i], "--hardbreaks") == 0) { 110 | options |= CMARK_OPT_HARDBREAKS; 111 | } else if (strcmp(argv[i], "--nobreaks") == 0) { 112 | options |= CMARK_OPT_NOBREAKS; 113 | } else if (strcmp(argv[i], "--smart") == 0) { 114 | options |= CMARK_OPT_SMART; 115 | } else if (strcmp(argv[i], "--safe") == 0) { 116 | options |= CMARK_OPT_SAFE; 117 | } else if (strcmp(argv[i], "--unsafe") == 0) { 118 | options |= CMARK_OPT_UNSAFE; 119 | } else if (strcmp(argv[i], "--validate-utf8") == 0) { 120 | options |= CMARK_OPT_VALIDATE_UTF8; 121 | } else if ((strcmp(argv[i], "--help") == 0) || 122 | (strcmp(argv[i], "-h") == 0)) { 123 | print_usage(); 124 | exit(0); 125 | } else if (strcmp(argv[i], "--width") == 0) { 126 | i += 1; 127 | if (i < argc) { 128 | width = (int)strtol(argv[i], &unparsed, 10); 129 | if (unparsed && strlen(unparsed) > 0) { 130 | fprintf(stderr, "failed parsing width '%s' at '%s'\n", argv[i], 131 | unparsed); 132 | exit(1); 133 | } 134 | } else { 135 | fprintf(stderr, "--width requires an argument\n"); 136 | exit(1); 137 | } 138 | } else if ((strcmp(argv[i], "-t") == 0) || (strcmp(argv[i], "--to") == 0)) { 139 | i += 1; 140 | if (i < argc) { 141 | if (strcmp(argv[i], "man") == 0) { 142 | writer = FORMAT_MAN; 143 | } else if (strcmp(argv[i], "html") == 0) { 144 | writer = FORMAT_HTML; 145 | } else if (strcmp(argv[i], "xml") == 0) { 146 | writer = FORMAT_XML; 147 | } else if (strcmp(argv[i], "commonmark") == 0) { 148 | writer = FORMAT_COMMONMARK; 149 | } else if (strcmp(argv[i], "latex") == 0) { 150 | writer = FORMAT_LATEX; 151 | } else { 152 | fprintf(stderr, "Unknown format %s\n", argv[i]); 153 | exit(1); 154 | } 155 | } else { 156 | fprintf(stderr, "No argument provided for %s\n", argv[i - 1]); 157 | exit(1); 158 | } 159 | } else if (*argv[i] == '-') { 160 | print_usage(); 161 | exit(1); 162 | } else { // treat as file argument 163 | files[numfps++] = i; 164 | } 165 | } 166 | 167 | parser = cmark_parser_new(options); 168 | for (i = 0; i < numfps; i++) { 169 | FILE *fp = fopen(argv[files[i]], "rb"); 170 | if (fp == NULL) { 171 | fprintf(stderr, "Error opening file %s: %s\n", argv[files[i]], 172 | strerror(errno)); 173 | exit(1); 174 | } 175 | 176 | while ((bytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) { 177 | cmark_parser_feed(parser, buffer, bytes); 178 | if (bytes < sizeof(buffer)) { 179 | break; 180 | } 181 | } 182 | 183 | fclose(fp); 184 | } 185 | 186 | if (numfps == 0) { 187 | 188 | while ((bytes = fread(buffer, 1, sizeof(buffer), stdin)) > 0) { 189 | cmark_parser_feed(parser, buffer, bytes); 190 | if (bytes < sizeof(buffer)) { 191 | break; 192 | } 193 | } 194 | } 195 | 196 | #ifdef USE_PLEDGE 197 | if (pledge("stdio", NULL) != 0) { 198 | perror("pledge"); 199 | return 1; 200 | } 201 | #endif 202 | 203 | document = cmark_parser_finish(parser); 204 | cmark_parser_free(parser); 205 | 206 | print_document(document, writer, options, width); 207 | 208 | cmark_node_free(document); 209 | 210 | free(files); 211 | 212 | return 0; 213 | } 214 | -------------------------------------------------------------------------------- /Tests/CommonMarkTests/BlockArrayBuilderTests.swift: -------------------------------------------------------------------------------- 1 | #if swift(>=5.4) 2 | import CommonMark 3 | import XCTest 4 | 5 | final class BlockArrayBuilderTests: XCTestCase { 6 | func testExpressions() { 7 | // given 8 | @BlockArrayBuilder func build() -> [Block] { 9 | "This is the first paragraph." 10 | Paragraph { 11 | "This is the " 12 | Strong("second") 13 | " paragraph." 14 | } 15 | BlockQuote { 16 | "You say hello." 17 | BlockQuote { 18 | "I say goodbye." 19 | } 20 | } 21 | OrderedList(start: 1) { 22 | ListItem { "One" } 23 | ListItem { 24 | "Two" 25 | BulletList(tight: false) { 26 | "Two 1" 27 | "Two 2" 28 | } 29 | } 30 | } 31 | // The tight parameter should be ignored because there are items with multiple paragraphs 32 | BulletList(tight: true) { 33 | ListItem { 34 | "First paragraph." 35 | "Second paragraph." 36 | } 37 | ListItem { 38 | "Two" 39 | BulletList { 40 | "Two 1" 41 | "Two 2" 42 | } 43 | } 44 | } 45 | CodeBlock(language: "swift") { 46 | """ 47 | let a = 5 48 | let b = 42 49 | """ 50 | } 51 | HTMLBlock { 52 | "

Hello world!

" 53 | } 54 | Heading(level: 2) { 55 | "Chapter 1" 56 | } 57 | ThematicBreak() 58 | } 59 | 60 | // when 61 | let result = build() 62 | 63 | // then 64 | XCTAssertEqual( 65 | [ 66 | .paragraph(.init(text: [.text("This is the first paragraph.")])), 67 | .paragraph( 68 | .init( 69 | text: [ 70 | .text("This is the "), 71 | .strong(.init(children: [.text("second")])), 72 | .text(" paragraph."), 73 | ] 74 | ) 75 | ), 76 | .blockQuote( 77 | .init( 78 | items: [ 79 | .paragraph(.init(text: [.text("You say hello.")])), 80 | .blockQuote( 81 | .init( 82 | items: [ 83 | .paragraph(.init(text: [.text("I say goodbye.")])) 84 | ] 85 | ) 86 | ), 87 | ] 88 | ) 89 | ), 90 | .orderedList( 91 | .init( 92 | items: [ 93 | .init(blocks: [.paragraph(.init(text: [.text("One")]))]), 94 | .init( 95 | blocks: [ 96 | .paragraph(.init(text: [.text("Two")])), 97 | .bulletList( 98 | .init( 99 | items: [ 100 | .init(blocks: [.paragraph(.init(text: [.text("Two 1")]))]), 101 | .init(blocks: [.paragraph(.init(text: [.text("Two 2")]))]), 102 | ], 103 | tight: false 104 | ) 105 | ), 106 | ] 107 | ), 108 | ], 109 | start: 1, 110 | tight: true 111 | ) 112 | ), 113 | .bulletList( 114 | .init( 115 | items: [ 116 | .init( 117 | blocks: [ 118 | .paragraph(.init(text: [.text("First paragraph.")])), 119 | .paragraph(.init(text: [.text("Second paragraph.")])), 120 | ] 121 | ), 122 | .init( 123 | blocks: [ 124 | .paragraph(.init(text: [.text("Two")])), 125 | .bulletList( 126 | .init( 127 | items: [ 128 | .init(blocks: [.paragraph(.init(text: [.text("Two 1")]))]), 129 | .init(blocks: [.paragraph(.init(text: [.text("Two 2")]))]), 130 | ], 131 | tight: true 132 | ) 133 | ), 134 | ] 135 | ), 136 | ], 137 | tight: false 138 | ) 139 | ), 140 | .code(.init(language: "swift", code: { "let a = 5\nlet b = 42" })), 141 | .html(.init(html: "

Hello world!

")), 142 | .heading(.init(text: [.text("Chapter 1")], level: 2)), 143 | .thematicBreak, 144 | ], 145 | result 146 | ) 147 | } 148 | 149 | func testForLoops() { 150 | // given 151 | @BlockArrayBuilder func build() -> [Block] { 152 | for i in 0...3 { 153 | "\(i)" 154 | } 155 | } 156 | 157 | // when 158 | let result = build() 159 | 160 | // then 161 | XCTAssertEqual( 162 | [ 163 | .paragraph(.init(text: [.text("0")])), 164 | .paragraph(.init(text: [.text("1")])), 165 | .paragraph(.init(text: [.text("2")])), 166 | .paragraph(.init(text: [.text("3")])), 167 | ], 168 | result 169 | ) 170 | } 171 | 172 | func testIf() { 173 | @BlockArrayBuilder func build() -> [Block] { 174 | "Something is:" 175 | if true { 176 | BlockQuote { 177 | "true" 178 | } 179 | } 180 | } 181 | 182 | // when 183 | let result = build() 184 | 185 | // then 186 | XCTAssertEqual( 187 | [ 188 | .paragraph(.init(text: [.text("Something is:")])), 189 | .blockQuote( 190 | .init( 191 | items: [.paragraph(.init(text: [.text("true")]))] 192 | ) 193 | ), 194 | ], 195 | result 196 | ) 197 | } 198 | 199 | func testIfElse() { 200 | @BlockArrayBuilder func build(_ value: Bool) -> [Block] { 201 | "Something is:" 202 | if value { 203 | BlockQuote { 204 | "true" 205 | } 206 | } else { 207 | "false" 208 | } 209 | } 210 | 211 | // when 212 | let result1 = build(true) 213 | let result2 = build(false) 214 | 215 | // then 216 | XCTAssertEqual( 217 | [ 218 | .paragraph(.init(text: [.text("Something is:")])), 219 | .blockQuote( 220 | .init( 221 | items: [.paragraph(.init(text: [.text("true")]))] 222 | ) 223 | ), 224 | ], 225 | result1 226 | ) 227 | XCTAssertEqual( 228 | [ 229 | .paragraph(.init(text: [.text("Something is:")])), 230 | .paragraph(.init(text: [.text("false")])), 231 | ], 232 | result2 233 | ) 234 | } 235 | } 236 | #endif 237 | -------------------------------------------------------------------------------- /Sources/cmark/render.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "buffer.h" 3 | #include "cmark.h" 4 | #include "utf8.h" 5 | #include "render.h" 6 | #include "node.h" 7 | #include "cmark_ctype.h" 8 | 9 | static CMARK_INLINE void S_cr(cmark_renderer *renderer) { 10 | if (renderer->need_cr < 1) { 11 | renderer->need_cr = 1; 12 | } 13 | } 14 | 15 | static CMARK_INLINE void S_blankline(cmark_renderer *renderer) { 16 | if (renderer->need_cr < 2) { 17 | renderer->need_cr = 2; 18 | } 19 | } 20 | 21 | static void S_out(cmark_renderer *renderer, const char *source, bool wrap, 22 | cmark_escaping escape) { 23 | int length = strlen(source); 24 | unsigned char nextc; 25 | int32_t c; 26 | int i = 0; 27 | int last_nonspace; 28 | int len; 29 | int k = renderer->buffer->size - 1; 30 | 31 | wrap = wrap && !renderer->no_linebreaks; 32 | 33 | if (renderer->in_tight_list_item && renderer->need_cr > 1) { 34 | renderer->need_cr = 1; 35 | } 36 | while (renderer->need_cr) { 37 | if (k < 0 || renderer->buffer->ptr[k] == '\n') { 38 | k -= 1; 39 | } else { 40 | cmark_strbuf_putc(renderer->buffer, '\n'); 41 | if (renderer->need_cr > 1) { 42 | cmark_strbuf_put(renderer->buffer, renderer->prefix->ptr, 43 | renderer->prefix->size); 44 | } 45 | } 46 | renderer->column = 0; 47 | renderer->last_breakable = 0; 48 | renderer->begin_line = true; 49 | renderer->begin_content = true; 50 | renderer->need_cr -= 1; 51 | } 52 | 53 | while (i < length) { 54 | if (renderer->begin_line) { 55 | cmark_strbuf_put(renderer->buffer, renderer->prefix->ptr, 56 | renderer->prefix->size); 57 | // note: this assumes prefix is ascii: 58 | renderer->column = renderer->prefix->size; 59 | } 60 | 61 | len = cmark_utf8proc_iterate((const uint8_t *)source + i, length - i, &c); 62 | if (len == -1) { // error condition 63 | return; // return without rendering rest of string 64 | } 65 | nextc = source[i + len]; 66 | if (c == 32 && wrap) { 67 | if (!renderer->begin_line) { 68 | last_nonspace = renderer->buffer->size; 69 | cmark_strbuf_putc(renderer->buffer, ' '); 70 | renderer->column += 1; 71 | renderer->begin_line = false; 72 | renderer->begin_content = false; 73 | // skip following spaces 74 | while (source[i + 1] == ' ') { 75 | i++; 76 | } 77 | // We don't allow breaks that make a digit the first character 78 | // because this causes problems with commonmark output. 79 | if (!cmark_isdigit(source[i + 1])) { 80 | renderer->last_breakable = last_nonspace; 81 | } 82 | } 83 | 84 | } else if (escape == LITERAL) { 85 | if (c == 10) { 86 | cmark_strbuf_putc(renderer->buffer, '\n'); 87 | renderer->column = 0; 88 | renderer->begin_line = true; 89 | renderer->begin_content = true; 90 | renderer->last_breakable = 0; 91 | } else { 92 | cmark_render_code_point(renderer, c); 93 | renderer->begin_line = false; 94 | // we don't set 'begin_content' to false til we've 95 | // finished parsing a digit. Reason: in commonmark 96 | // we need to escape a potential list marker after 97 | // a digit: 98 | renderer->begin_content = 99 | renderer->begin_content && cmark_isdigit(c) == 1; 100 | } 101 | } else { 102 | (renderer->outc)(renderer, escape, c, nextc); 103 | renderer->begin_line = false; 104 | renderer->begin_content = 105 | renderer->begin_content && cmark_isdigit(c) == 1; 106 | } 107 | 108 | // If adding the character went beyond width, look for an 109 | // earlier place where the line could be broken: 110 | if (renderer->width > 0 && renderer->column > renderer->width && 111 | !renderer->begin_line && renderer->last_breakable > 0) { 112 | 113 | // copy from last_breakable to remainder 114 | unsigned char *src = renderer->buffer->ptr + 115 | renderer->last_breakable + 1; 116 | bufsize_t remainder_len = renderer->buffer->size - 117 | renderer->last_breakable - 1; 118 | unsigned char *remainder = 119 | (unsigned char *)renderer->mem->realloc(NULL, remainder_len); 120 | memcpy(remainder, src, remainder_len); 121 | // truncate at last_breakable 122 | cmark_strbuf_truncate(renderer->buffer, renderer->last_breakable); 123 | // add newline, prefix, and remainder 124 | cmark_strbuf_putc(renderer->buffer, '\n'); 125 | cmark_strbuf_put(renderer->buffer, renderer->prefix->ptr, 126 | renderer->prefix->size); 127 | cmark_strbuf_put(renderer->buffer, remainder, remainder_len); 128 | renderer->column = renderer->prefix->size + remainder_len; 129 | renderer->mem->free(remainder); 130 | renderer->last_breakable = 0; 131 | renderer->begin_line = false; 132 | renderer->begin_content = false; 133 | } 134 | 135 | i += len; 136 | } 137 | } 138 | 139 | // Assumes no newlines, assumes ascii content: 140 | void cmark_render_ascii(cmark_renderer *renderer, const char *s) { 141 | int origsize = renderer->buffer->size; 142 | cmark_strbuf_puts(renderer->buffer, s); 143 | renderer->column += renderer->buffer->size - origsize; 144 | } 145 | 146 | void cmark_render_code_point(cmark_renderer *renderer, uint32_t c) { 147 | cmark_utf8proc_encode_char(c, renderer->buffer); 148 | renderer->column += 1; 149 | } 150 | 151 | char *cmark_render(cmark_node *root, int options, int width, 152 | void (*outc)(cmark_renderer *, cmark_escaping, int32_t, 153 | unsigned char), 154 | int (*render_node)(cmark_renderer *renderer, 155 | cmark_node *node, 156 | cmark_event_type ev_type, int options)) { 157 | cmark_mem *mem = root->mem; 158 | cmark_strbuf pref = CMARK_BUF_INIT(mem); 159 | cmark_strbuf buf = CMARK_BUF_INIT(mem); 160 | cmark_node *cur; 161 | cmark_event_type ev_type; 162 | char *result; 163 | cmark_iter *iter = cmark_iter_new(root); 164 | 165 | cmark_renderer renderer = {options, 166 | mem, &buf, &pref, 0, width, 167 | 0, 0, true, true, false, 168 | false, NULL, 169 | outc, S_cr, S_blankline, S_out}; 170 | 171 | while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { 172 | cur = cmark_iter_get_node(iter); 173 | if (!render_node(&renderer, cur, ev_type, options)) { 174 | // a false value causes us to skip processing 175 | // the node's contents. this is used for 176 | // autolinks. 177 | cmark_iter_reset(iter, cur, CMARK_EVENT_EXIT); 178 | } 179 | } 180 | 181 | // ensure final newline 182 | if (renderer.buffer->size == 0 || renderer.buffer->ptr[renderer.buffer->size - 1] != '\n') { 183 | cmark_strbuf_putc(renderer.buffer, '\n'); 184 | } 185 | 186 | result = (char *)cmark_strbuf_detach(renderer.buffer); 187 | 188 | cmark_iter_free(iter); 189 | cmark_strbuf_free(renderer.prefix); 190 | cmark_strbuf_free(renderer.buffer); 191 | 192 | return result; 193 | } 194 | -------------------------------------------------------------------------------- /Sources/cmark/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 ? 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, 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 | -------------------------------------------------------------------------------- /Sources/cmark/man.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "config.h" 7 | #include "cmark.h" 8 | #include "node.h" 9 | #include "buffer.h" 10 | #include "utf8.h" 11 | #include "render.h" 12 | 13 | #define OUT(s, wrap, escaping) renderer->out(renderer, s, wrap, escaping) 14 | #define LIT(s) renderer->out(renderer, s, false, LITERAL) 15 | #define CR() renderer->cr(renderer) 16 | #define BLANKLINE() renderer->blankline(renderer) 17 | #define LIST_NUMBER_SIZE 20 18 | 19 | // Functions to convert cmark_nodes to groff man strings. 20 | static void S_outc(cmark_renderer *renderer, cmark_escaping escape, int32_t c, 21 | unsigned char nextc) { 22 | (void)(nextc); 23 | 24 | if (escape == LITERAL) { 25 | cmark_render_code_point(renderer, c); 26 | return; 27 | } 28 | 29 | switch (c) { 30 | case 46: 31 | if (renderer->begin_line) { 32 | cmark_render_ascii(renderer, "\\&."); 33 | } else { 34 | cmark_render_code_point(renderer, c); 35 | } 36 | break; 37 | case 39: 38 | if (renderer->begin_line) { 39 | cmark_render_ascii(renderer, "\\&'"); 40 | } else { 41 | cmark_render_code_point(renderer, c); 42 | } 43 | break; 44 | case 45: 45 | cmark_render_ascii(renderer, "\\-"); 46 | break; 47 | case 92: 48 | cmark_render_ascii(renderer, "\\e"); 49 | break; 50 | case 8216: // left single quote 51 | cmark_render_ascii(renderer, "\\[oq]"); 52 | break; 53 | case 8217: // right single quote 54 | cmark_render_ascii(renderer, "\\[cq]"); 55 | break; 56 | case 8220: // left double quote 57 | cmark_render_ascii(renderer, "\\[lq]"); 58 | break; 59 | case 8221: // right double quote 60 | cmark_render_ascii(renderer, "\\[rq]"); 61 | break; 62 | case 8212: // em dash 63 | cmark_render_ascii(renderer, "\\[em]"); 64 | break; 65 | case 8211: // en dash 66 | cmark_render_ascii(renderer, "\\[en]"); 67 | break; 68 | default: 69 | cmark_render_code_point(renderer, c); 70 | } 71 | } 72 | 73 | static int S_render_node(cmark_renderer *renderer, cmark_node *node, 74 | cmark_event_type ev_type, int options) { 75 | cmark_node *tmp; 76 | int list_number; 77 | bool entering = (ev_type == CMARK_EVENT_ENTER); 78 | bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options); 79 | struct block_number *new_block_number; 80 | cmark_mem *allocator = cmark_get_default_mem_allocator(); 81 | 82 | // avoid unused parameter error: 83 | (void)(options); 84 | 85 | // indent inside nested lists 86 | if (renderer->block_number_in_list_item && 87 | node->type < CMARK_NODE_FIRST_INLINE) { 88 | if (entering) { 89 | renderer->block_number_in_list_item->number += 1; 90 | if (renderer->block_number_in_list_item->number == 2) { 91 | CR(); 92 | LIT(".RS"); // indent 93 | CR(); 94 | } 95 | } 96 | } 97 | 98 | switch (node->type) { 99 | case CMARK_NODE_DOCUMENT: 100 | break; 101 | 102 | case CMARK_NODE_BLOCK_QUOTE: 103 | if (entering) { 104 | CR(); 105 | LIT(".RS"); 106 | CR(); 107 | } else { 108 | CR(); 109 | LIT(".RE"); 110 | CR(); 111 | } 112 | break; 113 | 114 | case CMARK_NODE_LIST: 115 | break; 116 | 117 | case CMARK_NODE_ITEM: 118 | if (entering) { 119 | new_block_number = allocator->calloc(1, sizeof(struct block_number)); 120 | new_block_number->number = 0; 121 | new_block_number->parent = renderer->block_number_in_list_item; 122 | renderer->block_number_in_list_item = new_block_number; 123 | CR(); 124 | LIT(".IP "); 125 | if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { 126 | LIT("\\[bu] 2"); 127 | } else { 128 | list_number = cmark_node_get_list_start(node->parent); 129 | tmp = node; 130 | while (tmp->prev) { 131 | tmp = tmp->prev; 132 | list_number += 1; 133 | } 134 | char list_number_s[LIST_NUMBER_SIZE]; 135 | snprintf(list_number_s, LIST_NUMBER_SIZE, "\"%d.\" 4", list_number); 136 | LIT(list_number_s); 137 | } 138 | CR(); 139 | } else { 140 | if (renderer->block_number_in_list_item) { 141 | if (renderer->block_number_in_list_item->number >= 2) { 142 | CR(); 143 | LIT(".RE"); // de-indent 144 | } 145 | new_block_number = renderer->block_number_in_list_item; 146 | renderer->block_number_in_list_item = 147 | renderer->block_number_in_list_item->parent; 148 | allocator->free(new_block_number); 149 | } 150 | CR(); 151 | } 152 | break; 153 | 154 | case CMARK_NODE_HEADING: 155 | if (entering) { 156 | CR(); 157 | LIT(cmark_node_get_heading_level(node) == 1 ? ".SH" : ".SS"); 158 | CR(); 159 | } else { 160 | CR(); 161 | } 162 | break; 163 | 164 | case CMARK_NODE_CODE_BLOCK: 165 | CR(); 166 | LIT(".IP\n.nf\n\\f[C]\n"); 167 | OUT(cmark_node_get_literal(node), false, NORMAL); 168 | CR(); 169 | LIT("\\f[]\n.fi"); 170 | CR(); 171 | break; 172 | 173 | case CMARK_NODE_HTML_BLOCK: 174 | break; 175 | 176 | case CMARK_NODE_CUSTOM_BLOCK: 177 | CR(); 178 | OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node), 179 | false, LITERAL); 180 | CR(); 181 | break; 182 | 183 | case CMARK_NODE_THEMATIC_BREAK: 184 | CR(); 185 | LIT(".PP\n * * * * *"); 186 | CR(); 187 | break; 188 | 189 | case CMARK_NODE_PARAGRAPH: 190 | if (entering) { 191 | // no blank line if first paragraph in list: 192 | if (node->parent && node->parent->type == CMARK_NODE_ITEM && 193 | node->prev == NULL) { 194 | // no blank line or .PP 195 | } else { 196 | CR(); 197 | LIT(".PP"); 198 | CR(); 199 | } 200 | } else { 201 | CR(); 202 | } 203 | break; 204 | 205 | case CMARK_NODE_TEXT: 206 | OUT(cmark_node_get_literal(node), allow_wrap, NORMAL); 207 | break; 208 | 209 | case CMARK_NODE_LINEBREAK: 210 | LIT(".PD 0\n.P\n.PD"); 211 | CR(); 212 | break; 213 | 214 | case CMARK_NODE_SOFTBREAK: 215 | if (options & CMARK_OPT_HARDBREAKS) { 216 | LIT(".PD 0\n.P\n.PD"); 217 | CR(); 218 | } else if (renderer->width == 0 && !(CMARK_OPT_NOBREAKS & options)) { 219 | CR(); 220 | } else { 221 | OUT(" ", allow_wrap, LITERAL); 222 | } 223 | break; 224 | 225 | case CMARK_NODE_CODE: 226 | LIT("\\f[C]"); 227 | OUT(cmark_node_get_literal(node), allow_wrap, NORMAL); 228 | LIT("\\f[]"); 229 | break; 230 | 231 | case CMARK_NODE_HTML_INLINE: 232 | break; 233 | 234 | case CMARK_NODE_CUSTOM_INLINE: 235 | OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node), 236 | false, LITERAL); 237 | break; 238 | 239 | case CMARK_NODE_STRONG: 240 | if (entering) { 241 | LIT("\\f[B]"); 242 | } else { 243 | LIT("\\f[]"); 244 | } 245 | break; 246 | 247 | case CMARK_NODE_EMPH: 248 | if (entering) { 249 | LIT("\\f[I]"); 250 | } else { 251 | LIT("\\f[]"); 252 | } 253 | break; 254 | 255 | case CMARK_NODE_LINK: 256 | if (!entering) { 257 | LIT(" ("); 258 | OUT(cmark_node_get_url(node), allow_wrap, URL); 259 | LIT(")"); 260 | } 261 | break; 262 | 263 | case CMARK_NODE_IMAGE: 264 | if (entering) { 265 | LIT("[IMAGE: "); 266 | } else { 267 | LIT("]"); 268 | } 269 | break; 270 | 271 | default: 272 | assert(false); 273 | break; 274 | } 275 | 276 | return 1; 277 | } 278 | 279 | char *cmark_render_man(cmark_node *root, int options, int width) { 280 | return cmark_render(root, options, width, S_outc, S_render_node); 281 | } 282 | -------------------------------------------------------------------------------- /Sources/cmark/xml.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "config.h" 7 | #include "cmark.h" 8 | #include "node.h" 9 | #include "buffer.h" 10 | 11 | #define BUFFER_SIZE 100 12 | #define MAX_INDENT 40 13 | 14 | // Functions to convert cmark_nodes to XML strings. 15 | 16 | // C0 control characters, U+FFFE and U+FFF aren't allowed in XML. 17 | static const char XML_ESCAPE_TABLE[256] = { 18 | /* 0x00 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 19 | /* 0x10 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20 | /* 0x20 */ 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21 | /* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 5, 0, 22 | /* 0x40 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23 | /* 0x50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24 | /* 0x60 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25 | /* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26 | /* 0x80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27 | /* 0x90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | /* 0xA0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29 | /* 0xB0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 30 | /* 0xC0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31 | /* 0xD0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32 | /* 0xE0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33 | /* 0xF0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34 | }; 35 | 36 | // U+FFFD Replacement Character encoded in UTF-8 37 | #define UTF8_REPL "\xEF\xBF\xBD" 38 | 39 | static const char *XML_ESCAPES[] = { 40 | "", UTF8_REPL, """, "&", "<", ">" 41 | }; 42 | 43 | static void escape_xml(cmark_strbuf *ob, const unsigned char *src, 44 | bufsize_t size) { 45 | bufsize_t i = 0, org, esc = 0; 46 | 47 | while (i < size) { 48 | org = i; 49 | while (i < size && (esc = XML_ESCAPE_TABLE[src[i]]) == 0) 50 | i++; 51 | 52 | if (i > org) 53 | cmark_strbuf_put(ob, src + org, i - org); 54 | 55 | if (i >= size) 56 | break; 57 | 58 | if (esc == 9) { 59 | // To replace U+FFFE and U+FFFF with U+FFFD, only the last byte has to 60 | // be changed. 61 | // We know that src[i] is 0xBE or 0xBF. 62 | if (i >= 2 && src[i-2] == 0xEF && src[i-1] == 0xBF) { 63 | cmark_strbuf_putc(ob, 0xBD); 64 | } else { 65 | cmark_strbuf_putc(ob, src[i]); 66 | } 67 | } else { 68 | cmark_strbuf_puts(ob, XML_ESCAPES[esc]); 69 | } 70 | 71 | i++; 72 | } 73 | } 74 | 75 | static void escape_xml_str(cmark_strbuf *dest, const unsigned char *source) { 76 | if (source) 77 | escape_xml(dest, source, strlen((char *)source)); 78 | } 79 | 80 | struct render_state { 81 | cmark_strbuf *xml; 82 | int indent; 83 | }; 84 | 85 | static CMARK_INLINE void indent(struct render_state *state) { 86 | int i; 87 | for (i = 0; i < state->indent && i < MAX_INDENT; i++) { 88 | cmark_strbuf_putc(state->xml, ' '); 89 | } 90 | } 91 | 92 | static int S_render_node(cmark_node *node, cmark_event_type ev_type, 93 | struct render_state *state, int options) { 94 | cmark_strbuf *xml = state->xml; 95 | bool literal = false; 96 | cmark_delim_type delim; 97 | bool entering = (ev_type == CMARK_EVENT_ENTER); 98 | char buffer[BUFFER_SIZE]; 99 | 100 | if (entering) { 101 | indent(state); 102 | cmark_strbuf_putc(xml, '<'); 103 | cmark_strbuf_puts(xml, cmark_node_get_type_string(node)); 104 | 105 | if (options & CMARK_OPT_SOURCEPOS && node->start_line != 0) { 106 | snprintf(buffer, BUFFER_SIZE, " sourcepos=\"%d:%d-%d:%d\"", 107 | node->start_line, node->start_column, node->end_line, 108 | node->end_column); 109 | cmark_strbuf_puts(xml, buffer); 110 | } 111 | 112 | literal = false; 113 | 114 | switch (node->type) { 115 | case CMARK_NODE_DOCUMENT: 116 | cmark_strbuf_puts(xml, " xmlns=\"http://commonmark.org/xml/1.0\""); 117 | break; 118 | case CMARK_NODE_TEXT: 119 | case CMARK_NODE_CODE: 120 | case CMARK_NODE_HTML_BLOCK: 121 | case CMARK_NODE_HTML_INLINE: 122 | cmark_strbuf_puts(xml, " xml:space=\"preserve\">"); 123 | escape_xml(xml, node->data, node->len); 124 | cmark_strbuf_puts(xml, "as.heading.level); 154 | cmark_strbuf_puts(xml, buffer); 155 | break; 156 | case CMARK_NODE_CODE_BLOCK: 157 | if (node->as.code.info) { 158 | cmark_strbuf_puts(xml, " info=\""); 159 | escape_xml_str(xml, node->as.code.info); 160 | cmark_strbuf_putc(xml, '"'); 161 | } 162 | cmark_strbuf_puts(xml, " xml:space=\"preserve\">"); 163 | escape_xml(xml, node->data, node->len); 164 | cmark_strbuf_puts(xml, "as.custom.on_enter); 172 | cmark_strbuf_putc(xml, '"'); 173 | cmark_strbuf_puts(xml, " on_exit=\""); 174 | escape_xml_str(xml, node->as.custom.on_exit); 175 | cmark_strbuf_putc(xml, '"'); 176 | break; 177 | case CMARK_NODE_LINK: 178 | case CMARK_NODE_IMAGE: 179 | cmark_strbuf_puts(xml, " destination=\""); 180 | escape_xml_str(xml, node->as.link.url); 181 | cmark_strbuf_putc(xml, '"'); 182 | if (node->as.link.title) { 183 | cmark_strbuf_puts(xml, " title=\""); 184 | escape_xml_str(xml, node->as.link.title); 185 | cmark_strbuf_putc(xml, '"'); 186 | } 187 | break; 188 | default: 189 | break; 190 | } 191 | if (node->first_child) { 192 | state->indent += 2; 193 | } else if (!literal) { 194 | cmark_strbuf_puts(xml, " /"); 195 | } 196 | cmark_strbuf_puts(xml, ">\n"); 197 | 198 | } else if (node->first_child) { 199 | state->indent -= 2; 200 | indent(state); 201 | cmark_strbuf_puts(xml, "\n"); 204 | } 205 | 206 | return 1; 207 | } 208 | 209 | char *cmark_render_xml(cmark_node *root, int options) { 210 | char *result; 211 | cmark_strbuf xml = CMARK_BUF_INIT(root->mem); 212 | cmark_event_type ev_type; 213 | cmark_node *cur; 214 | struct render_state state = {&xml, 0}; 215 | 216 | cmark_iter *iter = cmark_iter_new(root); 217 | 218 | cmark_strbuf_puts(state.xml, "\n"); 219 | cmark_strbuf_puts(state.xml, 220 | "\n"); 221 | while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { 222 | cur = cmark_iter_get_node(iter); 223 | S_render_node(cur, ev_type, &state, options); 224 | } 225 | result = (char *)cmark_strbuf_detach(&xml); 226 | 227 | cmark_iter_free(iter); 228 | return result; 229 | } 230 | -------------------------------------------------------------------------------- /Sources/cmark/html.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "cmark_ctype.h" 6 | #include "config.h" 7 | #include "cmark.h" 8 | #include "node.h" 9 | #include "buffer.h" 10 | #include "houdini.h" 11 | #include "scanners.h" 12 | 13 | #define BUFFER_SIZE 100 14 | 15 | // Functions to convert cmark_nodes to HTML strings. 16 | 17 | static void escape_html(cmark_strbuf *dest, const unsigned char *source, 18 | bufsize_t length) { 19 | houdini_escape_html0(dest, source, length, 0); 20 | } 21 | 22 | static CMARK_INLINE void cr(cmark_strbuf *html) { 23 | if (html->size && html->ptr[html->size - 1] != '\n') 24 | cmark_strbuf_putc(html, '\n'); 25 | } 26 | 27 | struct render_state { 28 | cmark_strbuf *html; 29 | cmark_node *plain; 30 | }; 31 | 32 | static void S_render_sourcepos(cmark_node *node, cmark_strbuf *html, 33 | int options) { 34 | char buffer[BUFFER_SIZE]; 35 | if (CMARK_OPT_SOURCEPOS & options) { 36 | snprintf(buffer, BUFFER_SIZE, " data-sourcepos=\"%d:%d-%d:%d\"", 37 | cmark_node_get_start_line(node), cmark_node_get_start_column(node), 38 | cmark_node_get_end_line(node), cmark_node_get_end_column(node)); 39 | cmark_strbuf_puts(html, buffer); 40 | } 41 | } 42 | 43 | static int S_render_node(cmark_node *node, cmark_event_type ev_type, 44 | struct render_state *state, int options) { 45 | cmark_node *parent; 46 | cmark_node *grandparent; 47 | cmark_strbuf *html = state->html; 48 | char start_heading[] = "plain == node) { // back at original node 56 | state->plain = NULL; 57 | } 58 | 59 | if (state->plain != NULL) { 60 | switch (node->type) { 61 | case CMARK_NODE_TEXT: 62 | case CMARK_NODE_CODE: 63 | case CMARK_NODE_HTML_INLINE: 64 | escape_html(html, node->data, node->len); 65 | break; 66 | 67 | case CMARK_NODE_LINEBREAK: 68 | case CMARK_NODE_SOFTBREAK: 69 | cmark_strbuf_putc(html, ' '); 70 | break; 71 | 72 | default: 73 | break; 74 | } 75 | return 1; 76 | } 77 | 78 | switch (node->type) { 79 | case CMARK_NODE_DOCUMENT: 80 | break; 81 | 82 | case CMARK_NODE_BLOCK_QUOTE: 83 | if (entering) { 84 | cr(html); 85 | cmark_strbuf_puts(html, "\n"); 88 | } else { 89 | cr(html); 90 | cmark_strbuf_puts(html, "\n"); 91 | } 92 | break; 93 | 94 | case CMARK_NODE_LIST: { 95 | cmark_list_type list_type = (cmark_list_type)node->as.list.list_type; 96 | int start = node->as.list.start; 97 | 98 | if (entering) { 99 | cr(html); 100 | if (list_type == CMARK_BULLET_LIST) { 101 | cmark_strbuf_puts(html, "\n"); 104 | } else if (start == 1) { 105 | cmark_strbuf_puts(html, "\n"); 108 | } else { 109 | snprintf(buffer, BUFFER_SIZE, "
    \n"); 113 | } 114 | } else { 115 | cmark_strbuf_puts(html, 116 | list_type == CMARK_BULLET_LIST ? "\n" : "
\n"); 117 | } 118 | break; 119 | } 120 | 121 | case CMARK_NODE_ITEM: 122 | if (entering) { 123 | cr(html); 124 | cmark_strbuf_puts(html, "'); 127 | } else { 128 | cmark_strbuf_puts(html, "\n"); 129 | } 130 | break; 131 | 132 | case CMARK_NODE_HEADING: 133 | if (entering) { 134 | cr(html); 135 | start_heading[2] = (char)('0' + node->as.heading.level); 136 | cmark_strbuf_puts(html, start_heading); 137 | S_render_sourcepos(node, html, options); 138 | cmark_strbuf_putc(html, '>'); 139 | } else { 140 | end_heading[3] = (char)('0' + node->as.heading.level); 141 | cmark_strbuf_puts(html, end_heading); 142 | cmark_strbuf_puts(html, ">\n"); 143 | } 144 | break; 145 | 146 | case CMARK_NODE_CODE_BLOCK: 147 | cr(html); 148 | 149 | if (node->as.code.info == NULL || node->as.code.info[0] == 0) { 150 | cmark_strbuf_puts(html, ""); 153 | } else { 154 | bufsize_t first_tag = 0; 155 | while (node->as.code.info[first_tag] && 156 | !cmark_isspace(node->as.code.info[first_tag])) { 157 | first_tag += 1; 158 | } 159 | 160 | cmark_strbuf_puts(html, "as.code.info, first_tag); 164 | cmark_strbuf_puts(html, "\">"); 165 | } 166 | 167 | escape_html(html, node->data, node->len); 168 | cmark_strbuf_puts(html, "\n"); 169 | break; 170 | 171 | case CMARK_NODE_HTML_BLOCK: 172 | cr(html); 173 | if (!(options & CMARK_OPT_UNSAFE)) { 174 | cmark_strbuf_puts(html, ""); 175 | } else { 176 | cmark_strbuf_put(html, node->data, node->len); 177 | } 178 | cr(html); 179 | break; 180 | 181 | case CMARK_NODE_CUSTOM_BLOCK: { 182 | unsigned char *block = entering ? node->as.custom.on_enter : 183 | node->as.custom.on_exit; 184 | cr(html); 185 | if (block) { 186 | cmark_strbuf_puts(html, (char *)block); 187 | } 188 | cr(html); 189 | break; 190 | } 191 | 192 | case CMARK_NODE_THEMATIC_BREAK: 193 | cr(html); 194 | cmark_strbuf_puts(html, "\n"); 197 | break; 198 | 199 | case CMARK_NODE_PARAGRAPH: 200 | parent = cmark_node_parent(node); 201 | grandparent = cmark_node_parent(parent); 202 | if (grandparent != NULL && grandparent->type == CMARK_NODE_LIST) { 203 | tight = grandparent->as.list.tight; 204 | } else { 205 | tight = false; 206 | } 207 | if (!tight) { 208 | if (entering) { 209 | cr(html); 210 | cmark_strbuf_puts(html, "'); 213 | } else { 214 | cmark_strbuf_puts(html, "

\n"); 215 | } 216 | } 217 | break; 218 | 219 | case CMARK_NODE_TEXT: 220 | escape_html(html, node->data, node->len); 221 | break; 222 | 223 | case CMARK_NODE_LINEBREAK: 224 | cmark_strbuf_puts(html, "
\n"); 225 | break; 226 | 227 | case CMARK_NODE_SOFTBREAK: 228 | if (options & CMARK_OPT_HARDBREAKS) { 229 | cmark_strbuf_puts(html, "
\n"); 230 | } else if (options & CMARK_OPT_NOBREAKS) { 231 | cmark_strbuf_putc(html, ' '); 232 | } else { 233 | cmark_strbuf_putc(html, '\n'); 234 | } 235 | break; 236 | 237 | case CMARK_NODE_CODE: 238 | cmark_strbuf_puts(html, ""); 239 | escape_html(html, node->data, node->len); 240 | cmark_strbuf_puts(html, ""); 241 | break; 242 | 243 | case CMARK_NODE_HTML_INLINE: 244 | if (!(options & CMARK_OPT_UNSAFE)) { 245 | cmark_strbuf_puts(html, ""); 246 | } else { 247 | cmark_strbuf_put(html, node->data, node->len); 248 | } 249 | break; 250 | 251 | case CMARK_NODE_CUSTOM_INLINE: { 252 | unsigned char *block = entering ? node->as.custom.on_enter : 253 | node->as.custom.on_exit; 254 | if (block) { 255 | cmark_strbuf_puts(html, (char *)block); 256 | } 257 | break; 258 | } 259 | 260 | case CMARK_NODE_STRONG: 261 | if (entering) { 262 | cmark_strbuf_puts(html, ""); 263 | } else { 264 | cmark_strbuf_puts(html, ""); 265 | } 266 | break; 267 | 268 | case CMARK_NODE_EMPH: 269 | if (entering) { 270 | cmark_strbuf_puts(html, ""); 271 | } else { 272 | cmark_strbuf_puts(html, ""); 273 | } 274 | break; 275 | 276 | case CMARK_NODE_LINK: 277 | if (entering) { 278 | cmark_strbuf_puts(html, "as.link.url && ((options & CMARK_OPT_UNSAFE) || 280 | !(_scan_dangerous_url(node->as.link.url)))) { 281 | houdini_escape_href(html, node->as.link.url, 282 | strlen((char *)node->as.link.url)); 283 | } 284 | if (node->as.link.title) { 285 | cmark_strbuf_puts(html, "\" title=\""); 286 | escape_html(html, node->as.link.title, 287 | strlen((char *)node->as.link.title)); 288 | } 289 | cmark_strbuf_puts(html, "\">"); 290 | } else { 291 | cmark_strbuf_puts(html, ""); 292 | } 293 | break; 294 | 295 | case CMARK_NODE_IMAGE: 296 | if (entering) { 297 | cmark_strbuf_puts(html, "as.link.url && ((options & CMARK_OPT_UNSAFE) || 299 | !(_scan_dangerous_url(node->as.link.url)))) { 300 | houdini_escape_href(html, node->as.link.url, 301 | strlen((char *)node->as.link.url)); 302 | } 303 | cmark_strbuf_puts(html, "\" alt=\""); 304 | state->plain = node; 305 | } else { 306 | if (node->as.link.title) { 307 | cmark_strbuf_puts(html, "\" title=\""); 308 | escape_html(html, node->as.link.title, 309 | strlen((char *)node->as.link.title)); 310 | } 311 | 312 | cmark_strbuf_puts(html, "\" />"); 313 | } 314 | break; 315 | 316 | default: 317 | assert(false); 318 | break; 319 | } 320 | 321 | // cmark_strbuf_putc(html, 'x'); 322 | return 1; 323 | } 324 | 325 | char *cmark_render_html(cmark_node *root, int options) { 326 | char *result; 327 | cmark_strbuf html = CMARK_BUF_INIT(root->mem); 328 | cmark_event_type ev_type; 329 | cmark_node *cur; 330 | struct render_state state = {&html, NULL}; 331 | cmark_iter *iter = cmark_iter_new(root); 332 | 333 | while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { 334 | cur = cmark_iter_get_node(iter); 335 | S_render_node(cur, ev_type, &state, options); 336 | } 337 | result = (char *)cmark_strbuf_detach(&html); 338 | 339 | cmark_iter_free(iter); 340 | return result; 341 | } 342 | -------------------------------------------------------------------------------- /Sources/cmark/scanners.re: -------------------------------------------------------------------------------- 1 | #include 2 | #include "chunk.h" 3 | #include "scanners.h" 4 | 5 | bufsize_t _scan_at(bufsize_t (*scanner)(const unsigned char *), cmark_chunk *c, bufsize_t offset) 6 | { 7 | bufsize_t res; 8 | unsigned char *ptr = (unsigned char *)c->data; 9 | 10 | if (ptr == NULL || offset > c->len) { 11 | return 0; 12 | } else { 13 | unsigned char lim = ptr[c->len]; 14 | 15 | ptr[c->len] = '\0'; 16 | res = scanner(ptr + offset); 17 | ptr[c->len] = lim; 18 | } 19 | 20 | return res; 21 | } 22 | 23 | /*!re2c 24 | re2c:define:YYCTYPE = "unsigned char"; 25 | re2c:define:YYCURSOR = p; 26 | re2c:define:YYMARKER = marker; 27 | re2c:define:YYCTXMARKER = marker; 28 | re2c:yyfill:enable = 0; 29 | 30 | wordchar = [^\x00-\x20]; 31 | 32 | spacechar = [ \t\v\f\r\n]; 33 | 34 | reg_char = [^\\()\x00-\x20]; 35 | 36 | escaped_char = [\\][!"#$%&'()*+,./:;<=>?@[\\\]^_`{|}~-]; 37 | 38 | tagname = [A-Za-z][A-Za-z0-9-]*; 39 | 40 | blocktagname = 'address'|'article'|'aside'|'base'|'basefont'|'blockquote'|'body'|'caption'|'center'|'col'|'colgroup'|'dd'|'details'|'dialog'|'dir'|'div'|'dl'|'dt'|'fieldset'|'figcaption'|'figure'|'footer'|'form'|'frame'|'frameset'|'h1'|'h2'|'h3'|'h4'|'h5'|'h6'|'head'|'header'|'hr'|'html'|'iframe'|'legend'|'li'|'link'|'main'|'menu'|'menuitem'|'nav'|'noframes'|'ol'|'optgroup'|'option'|'p'|'param'|'section'|'source'|'title'|'summary'|'table'|'tbody'|'td'|'tfoot'|'th'|'thead'|'title'|'tr'|'track'|'ul'; 41 | 42 | attributename = [a-zA-Z_:][a-zA-Z0-9:._-]*; 43 | 44 | unquotedvalue = [^ \t\r\n\v\f"'=<>`\x00]+; 45 | singlequotedvalue = ['][^'\x00]*[']; 46 | doublequotedvalue = ["][^"\x00]*["]; 47 | 48 | attributevalue = unquotedvalue | singlequotedvalue | doublequotedvalue; 49 | 50 | attributevaluespec = spacechar* [=] spacechar* attributevalue; 51 | 52 | attribute = spacechar+ attributename attributevaluespec?; 53 | 54 | opentag = tagname attribute* spacechar* [/]? [>]; 55 | closetag = [/] tagname spacechar* [>]; 56 | 57 | htmlcomment = "--->" | ("-" ([-]? [^\x00>-]) ([-]? [^\x00-])* "-->"); 58 | 59 | processinginstruction = ([^?>\x00]+ | [?][^>\x00] | [>])+; 60 | 61 | declaration = [A-Z]+ spacechar+ [^>\x00]*; 62 | 63 | cdata = "CDATA[" ([^\]\x00]+ | "]" [^\]\x00] | "]]" [^>\x00])*; 64 | 65 | htmltag = opentag | closetag; 66 | 67 | in_parens_nosp = [(] (reg_char|escaped_char|[\\])* [)]; 68 | 69 | in_double_quotes = ["] (escaped_char|[^"\x00])* ["]; 70 | in_single_quotes = ['] (escaped_char|[^'\x00])* [']; 71 | in_parens = [(] (escaped_char|[^)\x00])* [)]; 72 | 73 | scheme = [A-Za-z][A-Za-z0-9.+-]{1,31}; 74 | */ 75 | 76 | // Try to match a scheme including colon. 77 | bufsize_t _scan_scheme(const unsigned char *p) 78 | { 79 | const unsigned char *marker = NULL; 80 | const unsigned char *start = p; 81 | /*!re2c 82 | scheme [:] { return (bufsize_t)(p - start); } 83 | * { return 0; } 84 | */ 85 | } 86 | 87 | // Try to match URI autolink after first <, returning number of chars matched. 88 | bufsize_t _scan_autolink_uri(const unsigned char *p) 89 | { 90 | const unsigned char *marker = NULL; 91 | const unsigned char *start = p; 92 | /*!re2c 93 | scheme [:][^\x00-\x20<>]*[>] { return (bufsize_t)(p - start); } 94 | * { return 0; } 95 | */ 96 | } 97 | 98 | // Try to match email autolink after first <, returning num of chars matched. 99 | bufsize_t _scan_autolink_email(const unsigned char *p) 100 | { 101 | const unsigned char *marker = NULL; 102 | const unsigned char *start = p; 103 | /*!re2c 104 | [a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+ 105 | [@] 106 | [a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])? 107 | ([.][a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)* 108 | [>] { return (bufsize_t)(p - start); } 109 | * { return 0; } 110 | */ 111 | } 112 | 113 | // Try to match an HTML tag after first <, returning num of chars matched. 114 | bufsize_t _scan_html_tag(const unsigned char *p) 115 | { 116 | const unsigned char *marker = NULL; 117 | const unsigned char *start = p; 118 | /*!re2c 119 | htmltag { return (bufsize_t)(p - start); } 120 | * { return 0; } 121 | */ 122 | } 123 | 124 | bufsize_t _scan_html_comment(const unsigned char *p) 125 | { 126 | const unsigned char *marker = NULL; 127 | const unsigned char *start = p; 128 | /*!re2c 129 | htmlcomment { return (bufsize_t)(p - start); } 130 | * { return 0; } 131 | */ 132 | } 133 | 134 | bufsize_t _scan_html_pi(const unsigned char *p) 135 | { 136 | const unsigned char *marker = NULL; 137 | const unsigned char *start = p; 138 | /*!re2c 139 | processinginstruction { return (bufsize_t)(p - start); } 140 | * { return 0; } 141 | */ 142 | } 143 | 144 | bufsize_t _scan_html_declaration(const unsigned char *p) 145 | { 146 | const unsigned char *marker = NULL; 147 | const unsigned char *start = p; 148 | /*!re2c 149 | declaration { return (bufsize_t)(p - start); } 150 | * { return 0; } 151 | */ 152 | } 153 | 154 | bufsize_t _scan_html_cdata(const unsigned char *p) 155 | { 156 | const unsigned char *marker = NULL; 157 | const unsigned char *start = p; 158 | /*!re2c 159 | cdata { return (bufsize_t)(p - start); } 160 | * { return 0; } 161 | */ 162 | } 163 | 164 | // Try to match an HTML block tag start line, returning 165 | // an integer code for the type of block (1-6, matching the spec). 166 | // #7 is handled by a separate function, below. 167 | bufsize_t _scan_html_block_start(const unsigned char *p) 168 | { 169 | const unsigned char *marker = NULL; 170 | /*!re2c 171 | [<] ('script'|'pre'|'textarea'|'style') (spacechar | [>]) { return 1; } 172 | '' { return (bufsize_t)(p - start); } 210 | * { return 0; } 211 | */ 212 | } 213 | 214 | // Try to match an HTML block end line of type 3 215 | bufsize_t _scan_html_block_end_3(const unsigned char *p) 216 | { 217 | const unsigned char *marker = NULL; 218 | const unsigned char *start = p; 219 | /*!re2c 220 | [^\n\x00]* '?>' { return (bufsize_t)(p - start); } 221 | * { return 0; } 222 | */ 223 | } 224 | 225 | // Try to match an HTML block end line of type 4 226 | bufsize_t _scan_html_block_end_4(const unsigned char *p) 227 | { 228 | const unsigned char *marker = NULL; 229 | const unsigned char *start = p; 230 | /*!re2c 231 | [^\n\x00]* '>' { return (bufsize_t)(p - start); } 232 | * { return 0; } 233 | */ 234 | } 235 | 236 | // Try to match an HTML block end line of type 5 237 | bufsize_t _scan_html_block_end_5(const unsigned char *p) 238 | { 239 | const unsigned char *marker = NULL; 240 | const unsigned char *start = p; 241 | /*!re2c 242 | [^\n\x00]* ']]>' { return (bufsize_t)(p - start); } 243 | * { return 0; } 244 | */ 245 | } 246 | 247 | // Try to match a link title (in single quotes, in double quotes, or 248 | // in parentheses), returning number of chars matched. Allow one 249 | // level of internal nesting (quotes within quotes). 250 | bufsize_t _scan_link_title(const unsigned char *p) 251 | { 252 | const unsigned char *marker = NULL; 253 | const unsigned char *start = p; 254 | /*!re2c 255 | ["] (escaped_char|[^"\x00])* ["] { return (bufsize_t)(p - start); } 256 | ['] (escaped_char|[^'\x00])* ['] { return (bufsize_t)(p - start); } 257 | [(] (escaped_char|[^()\x00])* [)] { return (bufsize_t)(p - start); } 258 | * { return 0; } 259 | */ 260 | } 261 | 262 | // Match space characters, including newlines. 263 | bufsize_t _scan_spacechars(const unsigned char *p) 264 | { 265 | const unsigned char *start = p; \ 266 | /*!re2c 267 | [ \t\v\f\r\n]+ { return (bufsize_t)(p - start); } 268 | * { return 0; } 269 | */ 270 | } 271 | 272 | // Match ATX heading start. 273 | bufsize_t _scan_atx_heading_start(const unsigned char *p) 274 | { 275 | const unsigned char *marker = NULL; 276 | const unsigned char *start = p; 277 | /*!re2c 278 | [#]{1,6} ([ \t]+|[\r\n]) { return (bufsize_t)(p - start); } 279 | * { return 0; } 280 | */ 281 | } 282 | 283 | // Match setext heading line. Return 1 for level-1 heading, 284 | // 2 for level-2, 0 for no match. 285 | bufsize_t _scan_setext_heading_line(const unsigned char *p) 286 | { 287 | const unsigned char *marker = NULL; 288 | /*!re2c 289 | [=]+ [ \t]* [\r\n] { return 1; } 290 | [-]+ [ \t]* [\r\n] { return 2; } 291 | * { return 0; } 292 | */ 293 | } 294 | 295 | // Scan an opening code fence. 296 | bufsize_t _scan_open_code_fence(const unsigned char *p) 297 | { 298 | const unsigned char *marker = NULL; 299 | const unsigned char *start = p; 300 | /*!re2c 301 | [`]{3,} / [^`\r\n\x00]*[\r\n] { return (bufsize_t)(p - start); } 302 | [~]{3,} / [^\r\n\x00]*[\r\n] { return (bufsize_t)(p - start); } 303 | * { return 0; } 304 | */ 305 | } 306 | 307 | // Scan a closing code fence with length at least len. 308 | bufsize_t _scan_close_code_fence(const unsigned char *p) 309 | { 310 | const unsigned char *marker = NULL; 311 | const unsigned char *start = p; 312 | /*!re2c 313 | [`]{3,} / [ \t]*[\r\n] { return (bufsize_t)(p - start); } 314 | [~]{3,} / [ \t]*[\r\n] { return (bufsize_t)(p - start); } 315 | * { return 0; } 316 | */ 317 | } 318 | 319 | // Scans an entity. 320 | // Returns number of chars matched. 321 | bufsize_t _scan_entity(const unsigned char *p) 322 | { 323 | const unsigned char *marker = NULL; 324 | const unsigned char *start = p; 325 | /*!re2c 326 | [&] ([#] ([Xx][A-Fa-f0-9]{1,6}|[0-9]{1,7}) |[A-Za-z][A-Za-z0-9]{1,31} ) [;] 327 | { return (bufsize_t)(p - start); } 328 | * { return 0; } 329 | */ 330 | } 331 | 332 | // Returns positive value if a URL begins in a way that is potentially 333 | // dangerous, with javascript:, vbscript:, file:, or data:, otherwise 0. 334 | bufsize_t _scan_dangerous_url(const unsigned char *p) 335 | { 336 | const unsigned char *marker = NULL; 337 | const unsigned char *start = p; 338 | /*!re2c 339 | 'data:image/' ('png'|'gif'|'jpeg'|'webp') { return 0; } 340 | 'javascript:' | 'vbscript:' | 'file:' | 'data:' { return (bufsize_t)(p - start); } 341 | * { return 0; } 342 | */ 343 | } 344 | 345 | -------------------------------------------------------------------------------- /Sources/cmark/utf8.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "cmark_ctype.h" 6 | #include "utf8.h" 7 | 8 | static const int8_t utf8proc_utf8class[256] = { 9 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14 | 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17 | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 18 | 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 19 | 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0}; 20 | 21 | static void encode_unknown(cmark_strbuf *buf) { 22 | static const uint8_t repl[] = {239, 191, 189}; 23 | cmark_strbuf_put(buf, repl, 3); 24 | } 25 | 26 | static int utf8proc_charlen(const uint8_t *str, bufsize_t str_len) { 27 | int length, i; 28 | 29 | if (!str_len) 30 | return 0; 31 | 32 | length = utf8proc_utf8class[str[0]]; 33 | 34 | if (!length) 35 | return -1; 36 | 37 | if (str_len >= 0 && (bufsize_t)length > str_len) 38 | return -str_len; 39 | 40 | for (i = 1; i < length; i++) { 41 | if ((str[i] & 0xC0) != 0x80) 42 | return -i; 43 | } 44 | 45 | return length; 46 | } 47 | 48 | // Validate a single UTF-8 character according to RFC 3629. 49 | static int utf8proc_valid(const uint8_t *str, bufsize_t str_len) { 50 | int length = utf8proc_utf8class[str[0]]; 51 | 52 | if (!length) 53 | return -1; 54 | 55 | if ((bufsize_t)length > str_len) 56 | return -str_len; 57 | 58 | switch (length) { 59 | case 2: 60 | if ((str[1] & 0xC0) != 0x80) 61 | return -1; 62 | if (str[0] < 0xC2) { 63 | // Overlong 64 | return -length; 65 | } 66 | break; 67 | 68 | case 3: 69 | if ((str[1] & 0xC0) != 0x80) 70 | return -1; 71 | if ((str[2] & 0xC0) != 0x80) 72 | return -2; 73 | if (str[0] == 0xE0) { 74 | if (str[1] < 0xA0) { 75 | // Overlong 76 | return -length; 77 | } 78 | } else if (str[0] == 0xED) { 79 | if (str[1] >= 0xA0) { 80 | // Surrogate 81 | return -length; 82 | } 83 | } 84 | break; 85 | 86 | case 4: 87 | if ((str[1] & 0xC0) != 0x80) 88 | return -1; 89 | if ((str[2] & 0xC0) != 0x80) 90 | return -2; 91 | if ((str[3] & 0xC0) != 0x80) 92 | return -3; 93 | if (str[0] == 0xF0) { 94 | if (str[1] < 0x90) { 95 | // Overlong 96 | return -length; 97 | } 98 | } else if (str[0] >= 0xF4) { 99 | if (str[0] > 0xF4 || str[1] >= 0x90) { 100 | // Above 0x10FFFF 101 | return -length; 102 | } 103 | } 104 | break; 105 | } 106 | 107 | return length; 108 | } 109 | 110 | void cmark_utf8proc_check(cmark_strbuf *ob, const uint8_t *line, 111 | bufsize_t size) { 112 | bufsize_t i = 0; 113 | 114 | while (i < size) { 115 | bufsize_t org = i; 116 | int charlen = 0; 117 | 118 | while (i < size) { 119 | if (line[i] < 0x80 && line[i] != 0) { 120 | i++; 121 | } else if (line[i] >= 0x80) { 122 | charlen = utf8proc_valid(line + i, size - i); 123 | if (charlen < 0) { 124 | charlen = -charlen; 125 | break; 126 | } 127 | i += charlen; 128 | } else if (line[i] == 0) { 129 | // ASCII NUL is technically valid but rejected 130 | // for security reasons. 131 | charlen = 1; 132 | break; 133 | } 134 | } 135 | 136 | if (i > org) { 137 | cmark_strbuf_put(ob, line + org, i - org); 138 | } 139 | 140 | if (i >= size) { 141 | break; 142 | } else { 143 | // Invalid UTF-8 144 | encode_unknown(ob); 145 | i += charlen; 146 | } 147 | } 148 | } 149 | 150 | int cmark_utf8proc_iterate(const uint8_t *str, bufsize_t str_len, 151 | int32_t *dst) { 152 | int length; 153 | int32_t uc = -1; 154 | 155 | *dst = -1; 156 | length = utf8proc_charlen(str, str_len); 157 | if (length < 0) 158 | return -1; 159 | 160 | switch (length) { 161 | case 1: 162 | uc = str[0]; 163 | break; 164 | case 2: 165 | uc = ((str[0] & 0x1F) << 6) + (str[1] & 0x3F); 166 | if (uc < 0x80) 167 | uc = -1; 168 | break; 169 | case 3: 170 | uc = ((str[0] & 0x0F) << 12) + ((str[1] & 0x3F) << 6) + (str[2] & 0x3F); 171 | if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000)) 172 | uc = -1; 173 | break; 174 | case 4: 175 | uc = ((str[0] & 0x07) << 18) + ((str[1] & 0x3F) << 12) + 176 | ((str[2] & 0x3F) << 6) + (str[3] & 0x3F); 177 | if (uc < 0x10000 || uc >= 0x110000) 178 | uc = -1; 179 | break; 180 | } 181 | 182 | if (uc < 0) 183 | return -1; 184 | 185 | *dst = uc; 186 | return length; 187 | } 188 | 189 | void cmark_utf8proc_encode_char(int32_t uc, cmark_strbuf *buf) { 190 | uint8_t dst[4]; 191 | bufsize_t len = 0; 192 | 193 | assert(uc >= 0); 194 | 195 | if (uc < 0x80) { 196 | dst[0] = (uint8_t)(uc); 197 | len = 1; 198 | } else if (uc < 0x800) { 199 | dst[0] = (uint8_t)(0xC0 + (uc >> 6)); 200 | dst[1] = 0x80 + (uc & 0x3F); 201 | len = 2; 202 | } else if (uc == 0xFFFF) { 203 | dst[0] = 0xFF; 204 | len = 1; 205 | } else if (uc == 0xFFFE) { 206 | dst[0] = 0xFE; 207 | len = 1; 208 | } else if (uc < 0x10000) { 209 | dst[0] = (uint8_t)(0xE0 + (uc >> 12)); 210 | dst[1] = 0x80 + ((uc >> 6) & 0x3F); 211 | dst[2] = 0x80 + (uc & 0x3F); 212 | len = 3; 213 | } else if (uc < 0x110000) { 214 | dst[0] = (uint8_t)(0xF0 + (uc >> 18)); 215 | dst[1] = 0x80 + ((uc >> 12) & 0x3F); 216 | dst[2] = 0x80 + ((uc >> 6) & 0x3F); 217 | dst[3] = 0x80 + (uc & 0x3F); 218 | len = 4; 219 | } else { 220 | encode_unknown(buf); 221 | return; 222 | } 223 | 224 | cmark_strbuf_put(buf, dst, len); 225 | } 226 | 227 | void cmark_utf8proc_case_fold(cmark_strbuf *dest, const uint8_t *str, 228 | bufsize_t len) { 229 | int32_t c; 230 | 231 | #define bufpush(x) cmark_utf8proc_encode_char(x, dest) 232 | 233 | while (len > 0) { 234 | bufsize_t char_len = cmark_utf8proc_iterate(str, len, &c); 235 | 236 | if (char_len >= 0) { 237 | #include "case_fold_switch.inc" 238 | } else { 239 | encode_unknown(dest); 240 | char_len = -char_len; 241 | } 242 | 243 | str += char_len; 244 | len -= char_len; 245 | } 246 | } 247 | 248 | // matches anything in the Zs class, plus LF, CR, TAB, FF. 249 | int cmark_utf8proc_is_space(int32_t uc) { 250 | return (uc == 9 || uc == 10 || uc == 12 || uc == 13 || uc == 32 || 251 | uc == 160 || uc == 5760 || (uc >= 8192 && uc <= 8202) || uc == 8239 || 252 | uc == 8287 || uc == 12288); 253 | } 254 | 255 | // matches anything in the P[cdefios] classes. 256 | int cmark_utf8proc_is_punctuation(int32_t uc) { 257 | return ( 258 | (uc < 128 && cmark_ispunct((char)uc)) || uc == 161 || uc == 167 || 259 | uc == 171 || uc == 182 || uc == 183 || uc == 187 || uc == 191 || 260 | uc == 894 || uc == 903 || (uc >= 1370 && uc <= 1375) || uc == 1417 || 261 | uc == 1418 || uc == 1470 || uc == 1472 || uc == 1475 || uc == 1478 || 262 | uc == 1523 || uc == 1524 || uc == 1545 || uc == 1546 || uc == 1548 || 263 | uc == 1549 || uc == 1563 || uc == 1566 || uc == 1567 || 264 | (uc >= 1642 && uc <= 1645) || uc == 1748 || (uc >= 1792 && uc <= 1805) || 265 | (uc >= 2039 && uc <= 2041) || (uc >= 2096 && uc <= 2110) || uc == 2142 || 266 | uc == 2404 || uc == 2405 || uc == 2416 || uc == 2800 || uc == 3572 || 267 | uc == 3663 || uc == 3674 || uc == 3675 || (uc >= 3844 && uc <= 3858) || 268 | uc == 3860 || (uc >= 3898 && uc <= 3901) || uc == 3973 || 269 | (uc >= 4048 && uc <= 4052) || uc == 4057 || uc == 4058 || 270 | (uc >= 4170 && uc <= 4175) || uc == 4347 || (uc >= 4960 && uc <= 4968) || 271 | uc == 5120 || uc == 5741 || uc == 5742 || uc == 5787 || uc == 5788 || 272 | (uc >= 5867 && uc <= 5869) || uc == 5941 || uc == 5942 || 273 | (uc >= 6100 && uc <= 6102) || (uc >= 6104 && uc <= 6106) || 274 | (uc >= 6144 && uc <= 6154) || uc == 6468 || uc == 6469 || uc == 6686 || 275 | uc == 6687 || (uc >= 6816 && uc <= 6822) || (uc >= 6824 && uc <= 6829) || 276 | (uc >= 7002 && uc <= 7008) || (uc >= 7164 && uc <= 7167) || 277 | (uc >= 7227 && uc <= 7231) || uc == 7294 || uc == 7295 || 278 | (uc >= 7360 && uc <= 7367) || uc == 7379 || (uc >= 8208 && uc <= 8231) || 279 | (uc >= 8240 && uc <= 8259) || (uc >= 8261 && uc <= 8273) || 280 | (uc >= 8275 && uc <= 8286) || uc == 8317 || uc == 8318 || uc == 8333 || 281 | uc == 8334 || (uc >= 8968 && uc <= 8971) || uc == 9001 || uc == 9002 || 282 | (uc >= 10088 && uc <= 10101) || uc == 10181 || uc == 10182 || 283 | (uc >= 10214 && uc <= 10223) || (uc >= 10627 && uc <= 10648) || 284 | (uc >= 10712 && uc <= 10715) || uc == 10748 || uc == 10749 || 285 | (uc >= 11513 && uc <= 11516) || uc == 11518 || uc == 11519 || 286 | uc == 11632 || (uc >= 11776 && uc <= 11822) || 287 | (uc >= 11824 && uc <= 11842) || (uc >= 12289 && uc <= 12291) || 288 | (uc >= 12296 && uc <= 12305) || (uc >= 12308 && uc <= 12319) || 289 | uc == 12336 || uc == 12349 || uc == 12448 || uc == 12539 || uc == 42238 || 290 | uc == 42239 || (uc >= 42509 && uc <= 42511) || uc == 42611 || 291 | uc == 42622 || (uc >= 42738 && uc <= 42743) || 292 | (uc >= 43124 && uc <= 43127) || uc == 43214 || uc == 43215 || 293 | (uc >= 43256 && uc <= 43258) || uc == 43310 || uc == 43311 || 294 | uc == 43359 || (uc >= 43457 && uc <= 43469) || uc == 43486 || 295 | uc == 43487 || (uc >= 43612 && uc <= 43615) || uc == 43742 || 296 | uc == 43743 || uc == 43760 || uc == 43761 || uc == 44011 || uc == 64830 || 297 | uc == 64831 || (uc >= 65040 && uc <= 65049) || 298 | (uc >= 65072 && uc <= 65106) || (uc >= 65108 && uc <= 65121) || 299 | uc == 65123 || uc == 65128 || uc == 65130 || uc == 65131 || 300 | (uc >= 65281 && uc <= 65283) || (uc >= 65285 && uc <= 65290) || 301 | (uc >= 65292 && uc <= 65295) || uc == 65306 || uc == 65307 || 302 | uc == 65311 || uc == 65312 || (uc >= 65339 && uc <= 65341) || 303 | uc == 65343 || uc == 65371 || uc == 65373 || 304 | (uc >= 65375 && uc <= 65381) || (uc >= 65792 && uc <= 65794) || 305 | uc == 66463 || uc == 66512 || uc == 66927 || uc == 67671 || uc == 67871 || 306 | uc == 67903 || (uc >= 68176 && uc <= 68184) || uc == 68223 || 307 | (uc >= 68336 && uc <= 68342) || (uc >= 68409 && uc <= 68415) || 308 | (uc >= 68505 && uc <= 68508) || (uc >= 69703 && uc <= 69709) || 309 | uc == 69819 || uc == 69820 || (uc >= 69822 && uc <= 69825) || 310 | (uc >= 69952 && uc <= 69955) || uc == 70004 || uc == 70005 || 311 | (uc >= 70085 && uc <= 70088) || uc == 70093 || 312 | (uc >= 70200 && uc <= 70205) || uc == 70854 || 313 | (uc >= 71105 && uc <= 71113) || (uc >= 71233 && uc <= 71235) || 314 | (uc >= 74864 && uc <= 74868) || uc == 92782 || uc == 92783 || 315 | uc == 92917 || (uc >= 92983 && uc <= 92987) || uc == 92996 || 316 | uc == 113823); 317 | } 318 | -------------------------------------------------------------------------------- /Sources/cmark/latex.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "config.h" 7 | #include "cmark.h" 8 | #include "node.h" 9 | #include "buffer.h" 10 | #include "utf8.h" 11 | #include "scanners.h" 12 | #include "render.h" 13 | 14 | #define OUT(s, wrap, escaping) renderer->out(renderer, s, wrap, escaping) 15 | #define LIT(s) renderer->out(renderer, s, false, LITERAL) 16 | #define CR() renderer->cr(renderer) 17 | #define BLANKLINE() renderer->blankline(renderer) 18 | #define LIST_NUMBER_STRING_SIZE 20 19 | 20 | static CMARK_INLINE void outc(cmark_renderer *renderer, cmark_escaping escape, 21 | int32_t c, unsigned char nextc) { 22 | if (escape == LITERAL) { 23 | cmark_render_code_point(renderer, c); 24 | return; 25 | } 26 | 27 | switch (c) { 28 | case 123: // '{' 29 | case 125: // '}' 30 | case 35: // '#' 31 | case 37: // '%' 32 | case 38: // '&' 33 | cmark_render_ascii(renderer, "\\"); 34 | cmark_render_code_point(renderer, c); 35 | break; 36 | case 36: // '$' 37 | case 95: // '_' 38 | if (escape == NORMAL) { 39 | cmark_render_ascii(renderer, "\\"); 40 | } 41 | cmark_render_code_point(renderer, c); 42 | break; 43 | case 45: // '-' 44 | if (nextc == 45) { // prevent ligature 45 | cmark_render_ascii(renderer, "-{}"); 46 | } else { 47 | cmark_render_ascii(renderer, "-"); 48 | } 49 | break; 50 | case 126: // '~' 51 | if (escape == NORMAL) { 52 | cmark_render_ascii(renderer, "\\textasciitilde{}"); 53 | } else { 54 | cmark_render_code_point(renderer, c); 55 | } 56 | break; 57 | case 94: // '^' 58 | cmark_render_ascii(renderer, "\\^{}"); 59 | break; 60 | case 92: // '\\' 61 | if (escape == URL) { 62 | // / acts as path sep even on windows: 63 | cmark_render_ascii(renderer, "/"); 64 | } else { 65 | cmark_render_ascii(renderer, "\\textbackslash{}"); 66 | } 67 | break; 68 | case 124: // '|' 69 | cmark_render_ascii(renderer, "\\textbar{}"); 70 | break; 71 | case 60: // '<' 72 | cmark_render_ascii(renderer, "\\textless{}"); 73 | break; 74 | case 62: // '>' 75 | cmark_render_ascii(renderer, "\\textgreater{}"); 76 | break; 77 | case 91: // '[' 78 | case 93: // ']' 79 | cmark_render_ascii(renderer, "{"); 80 | cmark_render_code_point(renderer, c); 81 | cmark_render_ascii(renderer, "}"); 82 | break; 83 | case 34: // '"' 84 | cmark_render_ascii(renderer, "\\textquotedbl{}"); 85 | // requires \usepackage[T1]{fontenc} 86 | break; 87 | case 39: // '\'' 88 | cmark_render_ascii(renderer, "\\textquotesingle{}"); 89 | // requires \usepackage{textcomp} 90 | break; 91 | case 160: // nbsp 92 | cmark_render_ascii(renderer, "~"); 93 | break; 94 | case 8230: // hellip 95 | cmark_render_ascii(renderer, "\\ldots{}"); 96 | break; 97 | case 8216: // lsquo 98 | if (escape == NORMAL) { 99 | cmark_render_ascii(renderer, "`"); 100 | } else { 101 | cmark_render_code_point(renderer, c); 102 | } 103 | break; 104 | case 8217: // rsquo 105 | if (escape == NORMAL) { 106 | cmark_render_ascii(renderer, "\'"); 107 | } else { 108 | cmark_render_code_point(renderer, c); 109 | } 110 | break; 111 | case 8220: // ldquo 112 | if (escape == NORMAL) { 113 | cmark_render_ascii(renderer, "``"); 114 | } else { 115 | cmark_render_code_point(renderer, c); 116 | } 117 | break; 118 | case 8221: // rdquo 119 | if (escape == NORMAL) { 120 | cmark_render_ascii(renderer, "''"); 121 | } else { 122 | cmark_render_code_point(renderer, c); 123 | } 124 | break; 125 | case 8212: // emdash 126 | if (escape == NORMAL) { 127 | cmark_render_ascii(renderer, "---"); 128 | } else { 129 | cmark_render_code_point(renderer, c); 130 | } 131 | break; 132 | case 8211: // endash 133 | if (escape == NORMAL) { 134 | cmark_render_ascii(renderer, "--"); 135 | } else { 136 | cmark_render_code_point(renderer, c); 137 | } 138 | break; 139 | default: 140 | cmark_render_code_point(renderer, c); 141 | } 142 | } 143 | 144 | typedef enum { 145 | NO_LINK, 146 | URL_AUTOLINK, 147 | EMAIL_AUTOLINK, 148 | NORMAL_LINK, 149 | INTERNAL_LINK 150 | } link_type; 151 | 152 | static link_type get_link_type(cmark_node *node) { 153 | size_t title_len, url_len; 154 | cmark_node *link_text; 155 | char *realurl; 156 | int realurllen; 157 | bool isemail = false; 158 | 159 | if (node->type != CMARK_NODE_LINK) { 160 | return NO_LINK; 161 | } 162 | 163 | const char *url = cmark_node_get_url(node); 164 | cmark_chunk url_chunk = cmark_chunk_literal(url); 165 | 166 | if (url && *url == '#') { 167 | return INTERNAL_LINK; 168 | } 169 | 170 | url_len = strlen(url); 171 | if (url_len == 0 || scan_scheme(&url_chunk, 0) == 0) { 172 | return NO_LINK; 173 | } 174 | 175 | const char *title = cmark_node_get_title(node); 176 | title_len = strlen(title); 177 | // if it has a title, we can't treat it as an autolink: 178 | if (title_len == 0) { 179 | 180 | link_text = node->first_child; 181 | cmark_consolidate_text_nodes(link_text); 182 | 183 | if (!link_text) 184 | return NO_LINK; 185 | 186 | realurl = (char *)url; 187 | realurllen = (int)url_len; 188 | if (strncmp(realurl, "mailto:", 7) == 0) { 189 | realurl += 7; 190 | realurllen -= 7; 191 | isemail = true; 192 | } 193 | if (realurllen == link_text->len && 194 | strncmp(realurl, (char *)link_text->data, 195 | link_text->len) == 0) { 196 | if (isemail) { 197 | return EMAIL_AUTOLINK; 198 | } else { 199 | return URL_AUTOLINK; 200 | } 201 | } 202 | } 203 | 204 | return NORMAL_LINK; 205 | } 206 | 207 | static int S_get_enumlevel(cmark_node *node) { 208 | int enumlevel = 0; 209 | cmark_node *tmp = node; 210 | while (tmp) { 211 | if (tmp->type == CMARK_NODE_LIST && 212 | cmark_node_get_list_type(node) == CMARK_ORDERED_LIST) { 213 | enumlevel++; 214 | } 215 | tmp = tmp->parent; 216 | } 217 | return enumlevel; 218 | } 219 | 220 | static int S_render_node(cmark_renderer *renderer, cmark_node *node, 221 | cmark_event_type ev_type, int options) { 222 | int list_number; 223 | int enumlevel; 224 | char list_number_string[LIST_NUMBER_STRING_SIZE]; 225 | bool entering = (ev_type == CMARK_EVENT_ENTER); 226 | cmark_list_type list_type; 227 | bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options); 228 | 229 | // avoid warning about unused parameter: 230 | (void)(options); 231 | 232 | switch (node->type) { 233 | case CMARK_NODE_DOCUMENT: 234 | break; 235 | 236 | case CMARK_NODE_BLOCK_QUOTE: 237 | if (entering) { 238 | LIT("\\begin{quote}"); 239 | CR(); 240 | } else { 241 | LIT("\\end{quote}"); 242 | BLANKLINE(); 243 | } 244 | break; 245 | 246 | case CMARK_NODE_LIST: 247 | list_type = cmark_node_get_list_type(node); 248 | if (entering) { 249 | LIT("\\begin{"); 250 | LIT(list_type == CMARK_ORDERED_LIST ? "enumerate" : "itemize"); 251 | LIT("}"); 252 | CR(); 253 | list_number = cmark_node_get_list_start(node); 254 | if (list_number > 1) { 255 | enumlevel = S_get_enumlevel(node); 256 | // latex normally supports only five levels 257 | if (enumlevel >= 1 && enumlevel <= 5) { 258 | snprintf(list_number_string, LIST_NUMBER_STRING_SIZE, "%d", 259 | list_number); 260 | LIT("\\setcounter{enum"); 261 | switch (enumlevel) { 262 | case 1: LIT("i"); break; 263 | case 2: LIT("ii"); break; 264 | case 3: LIT("iii"); break; 265 | case 4: LIT("iv"); break; 266 | case 5: LIT("v"); break; 267 | default: LIT("i"); break; 268 | } 269 | LIT("}{"); 270 | OUT(list_number_string, false, NORMAL); 271 | LIT("}"); 272 | } 273 | CR(); 274 | } 275 | } else { 276 | LIT("\\end{"); 277 | LIT(list_type == CMARK_ORDERED_LIST ? "enumerate" : "itemize"); 278 | LIT("}"); 279 | BLANKLINE(); 280 | } 281 | break; 282 | 283 | case CMARK_NODE_ITEM: 284 | if (entering) { 285 | LIT("\\item "); 286 | } else { 287 | CR(); 288 | } 289 | break; 290 | 291 | case CMARK_NODE_HEADING: 292 | if (entering) { 293 | switch (cmark_node_get_heading_level(node)) { 294 | case 1: 295 | LIT("\\section"); 296 | break; 297 | case 2: 298 | LIT("\\subsection"); 299 | break; 300 | case 3: 301 | LIT("\\subsubsection"); 302 | break; 303 | case 4: 304 | LIT("\\paragraph"); 305 | break; 306 | case 5: 307 | LIT("\\subparagraph"); 308 | break; 309 | } 310 | LIT("{"); 311 | } else { 312 | LIT("}"); 313 | BLANKLINE(); 314 | } 315 | break; 316 | 317 | case CMARK_NODE_CODE_BLOCK: 318 | CR(); 319 | LIT("\\begin{verbatim}"); 320 | CR(); 321 | OUT(cmark_node_get_literal(node), false, LITERAL); 322 | CR(); 323 | LIT("\\end{verbatim}"); 324 | BLANKLINE(); 325 | break; 326 | 327 | case CMARK_NODE_HTML_BLOCK: 328 | break; 329 | 330 | case CMARK_NODE_CUSTOM_BLOCK: 331 | CR(); 332 | OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node), 333 | false, LITERAL); 334 | CR(); 335 | break; 336 | 337 | case CMARK_NODE_THEMATIC_BREAK: 338 | BLANKLINE(); 339 | LIT("\\begin{center}\\rule{0.5\\linewidth}{\\linethickness}\\end{center}"); 340 | BLANKLINE(); 341 | break; 342 | 343 | case CMARK_NODE_PARAGRAPH: 344 | if (!entering) { 345 | BLANKLINE(); 346 | } 347 | break; 348 | 349 | case CMARK_NODE_TEXT: 350 | OUT(cmark_node_get_literal(node), allow_wrap, NORMAL); 351 | break; 352 | 353 | case CMARK_NODE_LINEBREAK: 354 | LIT("\\\\"); 355 | CR(); 356 | break; 357 | 358 | case CMARK_NODE_SOFTBREAK: 359 | if (options & CMARK_OPT_HARDBREAKS) { 360 | LIT("\\\\"); 361 | CR(); 362 | } else if (renderer->width == 0 && !(CMARK_OPT_NOBREAKS & options)) { 363 | CR(); 364 | } else { 365 | OUT(" ", allow_wrap, NORMAL); 366 | } 367 | break; 368 | 369 | case CMARK_NODE_CODE: 370 | LIT("\\texttt{"); 371 | OUT(cmark_node_get_literal(node), false, NORMAL); 372 | LIT("}"); 373 | break; 374 | 375 | case CMARK_NODE_HTML_INLINE: 376 | break; 377 | 378 | case CMARK_NODE_CUSTOM_INLINE: 379 | OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node), 380 | false, LITERAL); 381 | break; 382 | 383 | case CMARK_NODE_STRONG: 384 | if (entering) { 385 | LIT("\\textbf{"); 386 | } else { 387 | LIT("}"); 388 | } 389 | break; 390 | 391 | case CMARK_NODE_EMPH: 392 | if (entering) { 393 | LIT("\\emph{"); 394 | } else { 395 | LIT("}"); 396 | } 397 | break; 398 | 399 | case CMARK_NODE_LINK: 400 | if (entering) { 401 | const char *url = cmark_node_get_url(node); 402 | // requires \usepackage{hyperref} 403 | switch (get_link_type(node)) { 404 | case URL_AUTOLINK: 405 | LIT("\\url{"); 406 | OUT(url, false, URL); 407 | LIT("}"); 408 | return 0; // Don't process further nodes to avoid double-rendering artefacts 409 | case EMAIL_AUTOLINK: 410 | LIT("\\href{"); 411 | OUT(url, false, URL); 412 | LIT("}\\nolinkurl{"); 413 | break; 414 | case NORMAL_LINK: 415 | LIT("\\href{"); 416 | OUT(url, false, URL); 417 | LIT("}{"); 418 | break; 419 | case INTERNAL_LINK: 420 | LIT("\\protect\\hyperlink{"); 421 | OUT(url + 1, false, URL); 422 | LIT("}{"); 423 | break; 424 | case NO_LINK: 425 | LIT("{"); // error? 426 | } 427 | } else { 428 | LIT("}"); 429 | } 430 | 431 | break; 432 | 433 | case CMARK_NODE_IMAGE: 434 | if (entering) { 435 | LIT("\\protect\\includegraphics{"); 436 | // requires \include{graphicx} 437 | OUT(cmark_node_get_url(node), false, URL); 438 | LIT("}"); 439 | return 0; 440 | } 441 | break; 442 | 443 | default: 444 | assert(false); 445 | break; 446 | } 447 | 448 | return 1; 449 | } 450 | 451 | char *cmark_render_latex(cmark_node *root, int options, int width) { 452 | return cmark_render(root, options, width, outc, S_render_node); 453 | } 454 | --------------------------------------------------------------------------------