├── .spi.yml ├── Sources ├── CAtomic │ ├── include │ │ ├── module.modulemap │ │ └── CAtomic.h │ ├── CMakeLists.txt │ └── CAtomic.c ├── Markdown │ ├── Markdown.docc │ │ ├── Markdown │ │ │ ├── Infrastructure.md │ │ │ ├── FormatterAndOptions.md │ │ │ ├── VisitMarkup.md │ │ │ ├── InlineMarkup.md │ │ │ ├── BlockMarkup.md │ │ │ └── DoxygenCommands.md │ │ ├── Markdown.md │ │ ├── Snippets.md │ │ ├── Visitors-Walkers-and-Rewriters.md │ │ └── Parsing-Building-and-Modifying Markup-Trees.md │ ├── Structural Restrictions │ │ ├── BlockMarkup.swift │ │ ├── InlineMarkup.swift │ │ ├── BasicInlineContainer.swift │ │ ├── BlockContainer.swift │ │ ├── BasicBlockContainer.swift │ │ ├── InlineContainer.swift │ │ └── ListItemContainer.swift │ ├── Utility │ │ ├── StringExtensions.swift │ │ ├── CharacterExtensions.swift │ │ ├── AtomicCounter.swift │ │ └── CollectionExtensions.swift │ ├── Base │ │ ├── LiteralMarkup.swift │ │ ├── PlainTextConvertibleMarkup.swift │ │ ├── ChildIndexPath.swift │ │ └── Document.swift │ ├── Walker │ │ └── MarkupWalker.swift │ ├── Infrastructure │ │ ├── Replacement.swift │ │ └── SourceLocation.swift │ ├── Parser │ │ ├── RangerTracker.swift │ │ ├── ParseOptions.swift │ │ ├── RangeAdjuster.swift │ │ └── LazySplitLines.swift │ ├── Block Nodes │ │ ├── Leaf Blocks │ │ │ ├── ThematicBreak.swift │ │ │ ├── HTMLBlock.swift │ │ │ ├── Heading.swift │ │ │ └── CodeBlock.swift │ │ ├── Block Container Blocks │ │ │ ├── UnorderedList.swift │ │ │ ├── BlockQuote.swift │ │ │ ├── CustomBlock.swift │ │ │ ├── Doxygen Commands │ │ │ │ ├── DoxygenNote.swift │ │ │ │ ├── DoxygenAbstract.swift │ │ │ │ ├── DoxygenDiscussion.swift │ │ │ │ ├── DoxygenReturns.swift │ │ │ │ └── DoxygenParameter.swift │ │ │ ├── OrderedList.swift │ │ │ └── ListItem.swift │ │ ├── Tables │ │ │ ├── TableHead.swift │ │ │ ├── TableRow.swift │ │ │ ├── TableCellContainer.swift │ │ │ ├── TableBody.swift │ │ │ └── TableCell.swift │ │ └── Inline Container Blocks │ │ │ └── Paragraph.swift │ └── Inline Nodes │ │ ├── Inline Leaves │ │ ├── LineBreak.swift │ │ ├── SoftBreak.swift │ │ ├── Text.swift │ │ ├── InlineHTML.swift │ │ ├── CustomInline.swift │ │ ├── InlineCode.swift │ │ └── SymbolLink.swift │ │ └── Inline Containers │ │ ├── Strong.swift │ │ ├── Emphasis.swift │ │ ├── Strikethrough.swift │ │ ├── InlineAttributes.swift │ │ └── Image.swift └── CMakeLists.txt ├── Snippets ├── Parsing │ ├── test.md │ ├── ParseDocumentString.swift │ └── ParseDocumentFile.swift ├── Formatting │ ├── UnorderedListMarker.swift │ ├── CondenseAutolinks.swift │ ├── CustomLinePrefix.swift │ ├── OrderedListNumerals.swift │ ├── EmphasisMarkers.swift │ ├── DefaultFormatting.swift │ ├── ThematicBreakCharacter.swift │ ├── MaximumWidth.swift │ ├── PreferredHeadingStyle.swift │ └── UseCodeFence.swift ├── Querying │ └── ChildThrough.swift ├── Rewriters │ ├── RemoveElementKind.swift │ └── ReplaceText.swift ├── Walkers │ └── LinkCollector.swift └── Visitors │ └── XMLConverter.swift ├── cmake └── modules │ ├── SwiftMarkdownConfig.cmake.in │ └── CMakeLists.txt ├── .gitignore ├── CONTRIBUTING.md ├── Tests └── MarkdownTests │ ├── Inline Nodes │ ├── SoftBreakTests.swift │ ├── TextTests.swift │ ├── InlineCodeTests.swift │ ├── InlineHTMLTests.swift │ ├── ImageTests.swift │ ├── InlineAttributesTests.swift │ ├── LineBreakTests.swift │ ├── SymbolLinkTests.swift │ └── LinkTests.swift │ ├── Utility │ └── AssertElementDidntChange.swift │ ├── Visitors │ ├── Everything.md │ └── MarkupVisitorTests.swift │ ├── Block Nodes │ ├── HTMLBlockTests.swift │ ├── HeadingTests.swift │ ├── CodeBlockTests.swift │ └── DocumentTests.swift │ ├── Infrastructure │ └── SourceLocationTests.swift │ ├── Base │ ├── StableIdentifierTests.swift │ ├── AtomicCounterTests.swift │ ├── PlainTextConvertibleMarkupTests.swift │ ├── HierarchyTests.swift │ ├── RawMarkupTests.swift │ └── MarkupIdentifierTests.swift │ ├── Parsing │ ├── CommonMarkConverterTests.swift │ └── BacktickTests.swift │ └── Performance │ ├── MarkupChildrenPerformanceTests.swift │ └── EditPerformanceTests.swift ├── .github └── pull_request_template.md ├── bin ├── test └── update-gh-pages-documentation-site ├── Tools ├── Package.swift └── markdown-tool │ ├── MarkdownCommand.swift │ └── Commands │ ├── PrintHTMLCommand.swift │ └── DumpTreeCommand.swift ├── NOTICE.txt ├── CMakeLists.txt ├── Package@swift-5.7.swift ├── Package.swift └── README.md /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [Markdown] 5 | -------------------------------------------------------------------------------- /Sources/CAtomic/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module CAtomic { 2 | header "CAtomic.h" 3 | export * 4 | } 5 | -------------------------------------------------------------------------------- /Snippets/Parsing/test.md: -------------------------------------------------------------------------------- 1 | # Sample document 2 | 3 | This is a *sample document*. 4 | 5 | 6 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Markdown/Infrastructure.md: -------------------------------------------------------------------------------- 1 | # Infrastructure 2 | 3 | ## Topics 4 | 5 | ### Replacement 6 | 7 | - ``Replacement`` 8 | 9 | ### Source 10 | 11 | - ``SourceLocation`` 12 | - ``SourceRange`` 13 | 14 | 15 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Markdown/FormatterAndOptions.md: -------------------------------------------------------------------------------- 1 | # Formatter and Options 2 | 3 | ## Topics 4 | 5 | ### Formatter 6 | 7 | - ``MarkupFormatter`` 8 | - ``HTMLFormatter`` 9 | 10 | ### Options 11 | 12 | - ``MarkupDumpOptions`` 13 | - ``HTMLFormatterOptions`` 14 | 15 | 16 | -------------------------------------------------------------------------------- /Sources/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | add_subdirectory(CAtomic) 11 | add_subdirectory(Markdown) 12 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Markdown/VisitMarkup.md: -------------------------------------------------------------------------------- 1 | # Visiting Markup 2 | 3 | ## Topics 4 | 5 | ### Vistor 6 | 7 | - ``MarkupVisitor`` 8 | 9 | ### Walker 10 | 11 | ``MarkupWalker`` is a default implementation for ``MarkupVisitor``. 12 | 13 | - ``MarkupWalker`` 14 | 15 | ### Rewriter 16 | 17 | - ``MarkupRewriter`` 18 | 19 | 20 | -------------------------------------------------------------------------------- /cmake/modules/SwiftMarkdownConfig.cmake.in: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | if(NOT TARGET SwiftMarkdown) 11 | include("@SWIFT_MARKDOWN_EXPORTS_FILE@") 12 | endif() 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This source file is part of the Swift.org open source project 2 | # 3 | # Copyright (c) 2021 Apple Inc. and the Swift project authors 4 | # Licensed under Apache License v2.0 with Runtime Library Exception 5 | # 6 | # See https://swift.org/LICENSE.txt for license information 7 | # See https://swift.org/CONTRIBUTORS.txt for Swift project authors 8 | 9 | .DS_Store 10 | .build 11 | /Packages 12 | /*.xcodeproj 13 | .swiftpm 14 | Package.resolved 15 | -------------------------------------------------------------------------------- /Sources/Markdown/Structural Restrictions/BlockMarkup.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A block markup element. 12 | public protocol BlockMarkup: Markup {} 13 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Markdown/InlineMarkup.md: -------------------------------------------------------------------------------- 1 | # Markup Inline Nodes 2 | 3 | ## Topics 4 | 5 | ### Inline Container 6 | - ``Emphasis`` 7 | - ``Image`` 8 | - ``Link`` 9 | - ``Strikethrough`` 10 | - ``Strong`` 11 | 12 | ### Inline Leaves 13 | - ``CustomInline`` 14 | - ``InlineCode`` 15 | - ``InlineHTML`` 16 | - ``LineBreak`` 17 | - ``SoftBreak`` 18 | - ``SymbolLink`` 19 | - ``Text`` 20 | 21 | 22 | -------------------------------------------------------------------------------- /Sources/CAtomic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | add_library(CAtomic STATIC 11 | CAtomic.c) 12 | target_include_directories(CAtomic PUBLIC 13 | include) 14 | 15 | set_property(GLOBAL APPEND PROPERTY SWIFT_MARKDOWN_EXPORTS CAtomic) 16 | -------------------------------------------------------------------------------- /Sources/Markdown/Utility/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | extension StringProtocol { 12 | /// `self` surrounded by single quotation marks `'`. 13 | var singleQuoted: String { 14 | return "'\(self)'" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Markdown/Base/LiteralMarkup.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An element that is represented with just some plain text. 12 | public protocol LiteralMarkup: Markup { 13 | /// Create an element from its literal text. 14 | init(_ literalText: String) 15 | } 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | By submitting a pull request, you represent that you have the right to license your 2 | contribution to Apple and the community, and agree by submitting the patch that 3 | your contributions are licensed under the [Swift license](https://swift.org/LICENSE.txt). 4 | 5 | --- 6 | 7 | Before submitting the pull request, please make sure you have tested your changes 8 | and that they follow the Swift project [guidelines for contributing 9 | code](https://swift.org/contributing/#contributing-code). 10 | 11 | 12 | -------------------------------------------------------------------------------- /Sources/Markdown/Utility/CharacterExtensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | extension Character { 12 | /// The character as a ``Swift.String`` surrounded by single quotation marks `'`. 13 | var singleQuoted: String { 14 | return "'\(self)'" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Snippets/Parsing/ParseDocumentString.swift: -------------------------------------------------------------------------------- 1 | // Parse a ``String`` as Markdown. 2 | 3 | import Markdown 4 | 5 | let source = "Some *Markdown* source" 6 | let document = Document(parsing: source) 7 | 8 | print(document.debugDescription()) 9 | // snippet.hide 10 | /* 11 | This source file is part of the Swift.org open source project 12 | 13 | Copyright (c) 2022 Apple Inc. and the Swift project authors 14 | Licensed under Apache License v2.0 with Runtime Library Exception 15 | 16 | See https://swift.org/LICENSE.txt for license information 17 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 18 | */ 19 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/SoftBreakTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | final class SoftBreakTests: XCTestCase { 15 | /// Tests that creation doesn't crash. 16 | func testSoftBreak() { 17 | _ = SoftBreak() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Markdown/Base/PlainTextConvertibleMarkup.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An element that can be converted to plain text without formatting. 12 | public protocol PlainTextConvertibleMarkup: Markup { 13 | /// The plain text content of an element. 14 | var plainText: String { get } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/CAtomic/include/CAtomic.h: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | #include 12 | 13 | /// The current unique atomic identifier. 14 | uint64_t _cmarkup_current_unique_id(void); 15 | 16 | /// Increment the current unique identifier atomically and return it. 17 | uint64_t _cmarkup_increment_and_get_unique_id(void); 18 | -------------------------------------------------------------------------------- /Sources/CAtomic/CAtomic.c: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | #include 12 | 13 | static _Atomic uint64_t _cmarkup_unique_id = 0; 14 | 15 | uint64_t _cmarkup_current_unique_id(void) { 16 | return _cmarkup_unique_id; 17 | } 18 | 19 | uint64_t _cmarkup_increment_and_get_unique_id(void) { 20 | return ++_cmarkup_unique_id; 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Markdown/BlockMarkup.md: -------------------------------------------------------------------------------- 1 | # Markup Block Nodes 2 | 3 | ## Topics 4 | 5 | ### Block Container Blocks 6 | - ``BlockDirective`` 7 | - ``BlockQuote`` 8 | - ``CustomBlock`` 9 | - ``ListItem`` 10 | - ``OrderedList`` 11 | - ``UnorderedList`` 12 | 13 | ### Inline Container Blocks 14 | - ``Paragraph`` 15 | 16 | ### Leaf Blocks 17 | - ``Heading`` 18 | - ``HTMLBlock`` 19 | - ``ThematicBreak`` 20 | - ``CodeBlock`` 21 | 22 | ### Tables 23 | 24 | - ``Table`` 25 | - ``TableCellContainer`` 26 | 27 | ## See Also 28 | - 29 | - 30 | 31 | 32 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Utility/AssertElementDidntChange.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | @testable import Markdown 12 | import XCTest 13 | 14 | func assertElementDidntChange(_ element: Markup, assertedStructure expected: Markup, expectedId: MarkupIdentifier) { 15 | XCTAssertTrue(element.hasSameStructure(as: expected)) 16 | XCTAssertEqual(element._data.id, expectedId) 17 | } 18 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Visitors/Everything.md: -------------------------------------------------------------------------------- 1 | # Header 2 | 3 | *Emphasized* **strong** `inline code` [link](foo) ![image](foo). 4 | 5 | - this 6 | - is 7 | - a 8 | - list 9 | 10 | 1. eggs 11 | 1. milk 12 | 13 | > BlockQuote 14 | 15 | 2. flour 16 | 2. sugar 17 | 18 | - [x] Combine flour and baking soda. 19 | - [ ] Combine sugar and eggs. 20 | 21 | ```swift 22 | func foo() { 23 | let x = 1 24 | } 25 | ``` 26 | 27 | // Is this real code? Or just fantasy? 28 | 29 | This is an . 30 | 31 | --- 32 | 33 | 34 | An HTML Block. 35 | 36 | 37 | This is some

inline html

. 38 | 39 | line 40 | break 41 | 42 | soft 43 | break 44 | 45 | 46 | -------------------------------------------------------------------------------- /cmake/modules/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | set(SWIFT_MARKDOWN_EXPORTS_FILE 11 | ${CMAKE_CURRENT_BINARY_DIR}/SwiftMarkdownExports.cmake) 12 | 13 | configure_file(SwiftMarkdownConfig.cmake.in 14 | ${CMAKE_CURRENT_BINARY_DIR}/SwiftMarkdownConfig.cmake) 15 | 16 | get_property(SWIFT_MARKDOWN_EXPORTS GLOBAL PROPERTY SWIFT_MARKDOWN_EXPORTS) 17 | export(TARGETS ${SWIFT_MARKDOWN_EXPORTS} 18 | NAMESPACE SwiftMarkdown:: 19 | FILE ${SWIFT_MARKDOWN_EXPORTS_FILE} 20 | EXPORT_LINK_INTERFACE_LIBRARIES) 21 | -------------------------------------------------------------------------------- /Snippets/Parsing/ParseDocumentFile.swift: -------------------------------------------------------------------------------- 1 | // Parse the contents of a file by its ``URL`` without having to read its contents yourself. 2 | 3 | import Foundation 4 | import Markdown 5 | 6 | // snippet.hide 7 | let inputFileURL = URL(fileURLWithPath: #file).deletingLastPathComponent().appendingPathComponent("test.md") 8 | // snippet.show 9 | let document = try Document(parsing: inputFileURL) 10 | 11 | print(document.debugDescription()) 12 | // snippet.hide 13 | /* 14 | This source file is part of the Swift.org open source project 15 | 16 | Copyright (c) 2022 Apple Inc. and the Swift project authors 17 | Licensed under Apache License v2.0 with Runtime Library Exception 18 | 19 | See https://swift.org/LICENSE.txt for license information 20 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 21 | */ 22 | -------------------------------------------------------------------------------- /Sources/Markdown/Structural Restrictions/InlineMarkup.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An inline markup element. 12 | public protocol InlineMarkup: PlainTextConvertibleMarkup {} 13 | 14 | /// An inline element that can recur throughout any structure. 15 | /// 16 | /// This is mostly used to prevent some kinds of elements from nesting; for 17 | /// example, you cannot put a ``Link`` inside another ``Link`` or an ``Image`` 18 | /// inside another ``Image``. 19 | public protocol RecurringInlineMarkup: InlineMarkup {} 20 | -------------------------------------------------------------------------------- /Snippets/Formatting/UnorderedListMarker.swift: -------------------------------------------------------------------------------- 1 | // Format the unordered list marker. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | - An 7 | - unordered 8 | - list 9 | """ 10 | 11 | let document = Document(parsing: source) 12 | // Use an star or asterisk `*` as the unordered list marker. 13 | let formattedSource = document.format(options: .init(unorderedListMarker: .star)) 14 | 15 | print(""" 16 | ## Original source: 17 | \(source) 18 | 19 | ## Formatted source: 20 | \(formattedSource) 21 | """) 22 | // snippet.hide 23 | /* 24 | This source file is part of the Swift.org open source project 25 | 26 | Copyright (c) 2022 Apple Inc. and the Swift project authors 27 | Licensed under Apache License v2.0 with Runtime Library Exception 28 | 29 | See https://swift.org/LICENSE.txt for license information 30 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 31 | */ 32 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Bug/issue #, if applicable: 2 | 3 | ## Summary 4 | 5 | _Provide a description of what your PR addresses, explaining the expected user experience. 6 | Also, provide an overview of your implementation._ 7 | 8 | ## Dependencies 9 | 10 | _Describe any dependencies this PR might have, such as an associated branch in another repository._ 11 | 12 | ## Testing 13 | 14 | _Describe how a reviewer can test the functionality of your PR. Provide test content to test with if 15 | applicable._ 16 | 17 | Steps: 18 | 1. _Provide setup instructions._ 19 | 2. _Explain in detail how the functionality can be tested._ 20 | 21 | ## Checklist 22 | 23 | Make sure you check off the following items. If they cannot be completed, provide a reason. 24 | 25 | - [ ] Added tests 26 | - [ ] Ran the `./bin/test` script and it succeeded 27 | - [ ] Updated documentation if necessary 28 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/TextTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class TextTests: XCTestCase { 15 | func testWithText() { 16 | let string = "OK" 17 | let text = Text(string) 18 | XCTAssertEqual(string, text.string) 19 | 20 | let string2 = "Changed" 21 | var newText = text 22 | newText.string = string2 23 | XCTAssertEqual(string2, newText.string) 24 | XCTAssertFalse(text.isIdentical(to: newText)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Block Nodes/HTMLBlockTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class HTMLTestsTests: XCTestCase { 15 | func testHTMLBlockRawHTML() { 16 | let rawHTML = "Hi!" 17 | let html = HTMLBlock(rawHTML) 18 | XCTAssertEqual(rawHTML, html.rawHTML) 19 | 20 | let newRawHTML = "
" 21 | var newHTML = html 22 | newHTML.rawHTML = newRawHTML 23 | XCTAssertEqual(newRawHTML, newHTML.rawHTML) 24 | XCTAssertFalse(html.isIdentical(to: newHTML)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Markdown/Utility/AtomicCounter.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | #if compiler(>=6.0) 12 | internal import CAtomic 13 | #else 14 | @_implementationOnly import CAtomic 15 | #endif 16 | 17 | /// A wrapper for a 64-bit unsigned atomic singleton counter. 18 | struct AtomicCounter { 19 | /// The current counter value. 20 | static var current: UInt64 { 21 | return _cmarkup_current_unique_id() 22 | } 23 | 24 | /// Atomically increment and return the latest counter value. 25 | static func next() -> UInt64 { 26 | return _cmarkup_increment_and_get_unique_id() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Snippets/Formatting/CondenseAutolinks.swift: -------------------------------------------------------------------------------- 1 | // Format links that use URLs as their link text into autolinks. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | This [https://swift.org](https://swift.org) link will become 7 | """ 8 | 9 | let document = Document(parsing: source) 10 | let formattingOptions = MarkupFormatter.Options(condenseAutolinks: true) 11 | let formattedSource = document.format(options: formattingOptions) 12 | 13 | print(""" 14 | ## Original source: 15 | \(source) 16 | 17 | ## Formatted source: 18 | \(formattedSource) 19 | """) 20 | // snippet.hide 21 | /* 22 | This source file is part of the Swift.org open source project 23 | 24 | Copyright (c) 2022 Apple Inc. and the Swift project authors 25 | Licensed under Apache License v2.0 with Runtime Library Exception 26 | 27 | See https://swift.org/LICENSE.txt for license information 28 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 29 | */ 30 | -------------------------------------------------------------------------------- /Snippets/Querying/ChildThrough.swift: -------------------------------------------------------------------------------- 1 | // Find a matching element deep within a ``Markup`` tree. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | Reach into a document to find the *emphasized text*. 7 | """ 8 | let document = Document(parsing: source) 9 | let emphasizedText = document.child(through: [ 10 | (0, Paragraph.self), 11 | (1, Emphasis.self), 12 | (0, Text.self) 13 | ]) as! Text 14 | 15 | print(""" 16 | ## Document structure: 17 | \(document.debugDescription()) 18 | 19 | ## Found element: 20 | \(emphasizedText.detachedFromParent.debugDescription()) 21 | """) 22 | // snippet.hide 23 | /* 24 | This source file is part of the Swift.org open source project 25 | 26 | Copyright (c) 2022 Apple Inc. and the Swift project authors 27 | Licensed under Apache License v2.0 with Runtime Library Exception 28 | 29 | See https://swift.org/LICENSE.txt for license information 30 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 31 | */ 32 | -------------------------------------------------------------------------------- /Snippets/Formatting/CustomLinePrefix.swift: -------------------------------------------------------------------------------- 1 | // Format a Markdown document to have a custom line prefix, such as a comment prefix for use in source code. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | This document's lines 7 | will be prefixed with `//`. 8 | """ 9 | 10 | let document = Document(parsing: source) 11 | let formattingOptions = MarkupFormatter.Options(customLinePrefix: "// ") 12 | let formattedSource = document.format(options: formattingOptions) 13 | 14 | print(""" 15 | ## Original source: 16 | \(source) 17 | 18 | ## Formatted source: 19 | \(formattedSource) 20 | """) 21 | // snippet.hide 22 | /* 23 | This source file is part of the Swift.org open source project 24 | 25 | Copyright (c) 2022 Apple Inc. and the Swift project authors 26 | Licensed under Apache License v2.0 with Runtime Library Exception 27 | 28 | See https://swift.org/LICENSE.txt for license information 29 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 30 | */ 31 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/InlineCodeTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class InlineCodeTests: XCTestCase { 15 | func testInlineCodeString() { 16 | let text = "foo()" 17 | let text2 = "bar()" 18 | let inlineCode = InlineCode(text) 19 | 20 | XCTAssertEqual(text, inlineCode.code) 21 | 22 | var inlineCodeWithText2 = inlineCode 23 | inlineCodeWithText2.code = text2 24 | 25 | XCTAssertEqual(text2, inlineCodeWithText2.code) 26 | XCTAssertFalse(inlineCode.isIdentical(to: inlineCodeWithText2)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Snippets/Formatting/OrderedListNumerals.swift: -------------------------------------------------------------------------------- 1 | // Format the counting behavior of ordered lists. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | 1. An 7 | 2. ordered 8 | 3. list 9 | """ 10 | 11 | let document = Document(parsing: source) 12 | // Use all 0. markers to allow easily reordering ordered list items. 13 | let formattingOptions = MarkupFormatter.Options(orderedListNumerals: .allSame(1)) 14 | let formattedSource = document.format(options: formattingOptions) 15 | 16 | print(""" 17 | ## Original source: 18 | \(source) 19 | 20 | ## Formatted source: 21 | \(formattedSource) 22 | """) 23 | // snippet.hide 24 | /* 25 | This source file is part of the Swift.org open source project 26 | 27 | Copyright (c) 2022 Apple Inc. and the Swift project authors 28 | Licensed under Apache License v2.0 with Runtime Library Exception 29 | 30 | See https://swift.org/LICENSE.txt for license information 31 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 32 | */ 33 | -------------------------------------------------------------------------------- /Snippets/Formatting/EmphasisMarkers.swift: -------------------------------------------------------------------------------- 1 | // Format a consistent style for emphasis markers. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | This document uses a mix of *star* and _underbar_ emphasized elements. 7 | """ 8 | 9 | let document = Document(parsing: source) 10 | // Use only * for emphasis markers. 11 | let emphasisMarker = MarkupFormatter.Options.EmphasisMarker.star 12 | let formattedSource = document.format(options: .init(emphasisMarker: emphasisMarker)) 13 | 14 | print(""" 15 | ## Original source: 16 | \(source) 17 | 18 | ## Formatted source: 19 | \(formattedSource) 20 | """) 21 | // snippet.hide 22 | /* 23 | This source file is part of the Swift.org open source project 24 | 25 | Copyright (c) 2022 Apple Inc. and the Swift project authors 26 | Licensed under Apache License v2.0 with Runtime Library Exception 27 | 28 | See https://swift.org/LICENSE.txt for license information 29 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 30 | */ 31 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/InlineHTMLTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class InlineHTMLTests: XCTestCase { 15 | func testInlineHTMLRawHTML() { 16 | let rawHTML = "bold" 17 | let rawHTML2 = "

para

" 18 | 19 | let inlineHTML = InlineHTML(rawHTML) 20 | XCTAssertEqual(rawHTML, inlineHTML.rawHTML) 21 | 22 | var newInlineHTML = inlineHTML 23 | newInlineHTML.rawHTML = rawHTML2 24 | XCTAssertEqual(rawHTML2, newInlineHTML.rawHTML) 25 | XCTAssertFalse(inlineHTML.isIdentical(to: newInlineHTML)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Markdown/Walker/MarkupWalker.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An interface for walking a `Markup` tree without altering it. 12 | public protocol MarkupWalker: MarkupVisitor where Result == Void {} 13 | 14 | extension MarkupWalker { 15 | /// Continue walking by descending in the given element. 16 | /// 17 | /// - Parameter markup: the element whose children the walker should visit. 18 | public mutating func descendInto(_ markup: Markup) { 19 | for child in markup.children { 20 | visit(child) 21 | } 22 | } 23 | public mutating func defaultVisit(_ markup: Markup) { 24 | descendInto(markup) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Infrastructure/SourceLocationTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2022 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | @testable import Markdown 12 | import XCTest 13 | 14 | class SourceLocationTests: XCTestCase { 15 | func testNonAsciiCharacterColumn() throws { 16 | func assertColumnNumberAssumesUTF8Encoding(text: String) throws { 17 | let document = Document(parsing: text) 18 | let range = try XCTUnwrap(document.range) 19 | XCTAssertEqual(range.upperBound.column - 1, text.utf8.count) 20 | } 21 | 22 | // Emoji 23 | try assertColumnNumberAssumesUTF8Encoding(text: "🇺🇳") 24 | // CJK Character 25 | try assertColumnNumberAssumesUTF8Encoding(text: "叶") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Snippets/Formatting/DefaultFormatting.swift: -------------------------------------------------------------------------------- 1 | // Format Markdown with the default settings. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | |a|b|c| 7 | |-|:-|-:| 8 | |*Some text*|x|| 9 | """ 10 | 11 | // There is not an option for formatting tables per se but is useful to show the behavior for tables. 12 | // Table columns are automatically expanded to fit the column's largest 13 | // cell, making the table easier to read in the Markdown source. 14 | 15 | let document = Document(parsing: source) 16 | let formattedSource = document.format() 17 | 18 | print(""" 19 | ## Original source: 20 | \(source) 21 | 22 | ## Formatted source: 23 | \(formattedSource) 24 | """) 25 | // snippet.hide 26 | /* 27 | This source file is part of the Swift.org open source project 28 | 29 | Copyright (c) 2022 Apple Inc. and the Swift project authors 30 | Licensed under Apache License v2.0 with Runtime Library Exception 31 | 32 | See https://swift.org/LICENSE.txt for license information 33 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 34 | */ 35 | -------------------------------------------------------------------------------- /bin/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This source file is part of the Swift.org open source project 4 | # 5 | # Copyright (c) 2021 Apple Inc. and the Swift project authors 6 | # Licensed under Apache License v2.0 with Runtime Library Exception 7 | # 8 | # See https://swift.org/LICENSE.txt for license information 9 | # See https://swift.org/CONTRIBUTORS.txt for Swift project authors 10 | # 11 | 12 | set -eu 13 | 14 | # A `realpath` alternative using the default C implementation. 15 | filepath() { 16 | [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" 17 | } 18 | 19 | # First get the absolute path to this file so we can get the absolute file path to the SwiftMarkdown root source dir. 20 | SWIFT_MARKDOWN_ROOT="$(dirname $(dirname $(filepath $0)))" 21 | 22 | # Build SwiftMarkdown. 23 | swift test --parallel --package-path "$SWIFT_MARKDOWN_ROOT" 24 | 25 | # Run source code checks for the codebase. 26 | LC_ALL=C "$SWIFT_MARKDOWN_ROOT"/bin/check-source 27 | 28 | # Test utility scripts validity. 29 | printf "=> Validating scripts in bin subdirectory… " 30 | 31 | printf "\033[0;32mokay.\033[0m\n" 32 | 33 | -------------------------------------------------------------------------------- /Snippets/Rewriters/RemoveElementKind.swift: -------------------------------------------------------------------------------- 1 | // Remove all instances of a kind of element using a `MarkupRewriter`. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | The strong emphasis element is **going to be** deleted. 7 | """ 8 | 9 | struct StrongDeleter: MarkupRewriter { 10 | // Delete all ``Strong`` elements. 11 | func visitStrong(_ strong: Strong) -> Markup? { 12 | return nil 13 | } 14 | } 15 | 16 | let document = Document(parsing: source) 17 | var deleter = StrongDeleter() 18 | let newDocument = deleter.visit(document) as! Document 19 | 20 | print(""" 21 | ## Original Markdown structure: 22 | \(document.debugDescription()) 23 | 24 | ## New Markdown structure: 25 | \(newDocument.debugDescription()) 26 | """) 27 | // snippet.hide 28 | /* 29 | This source file is part of the Swift.org open source project 30 | 31 | Copyright (c) 2022 Apple Inc. and the Swift project authors 32 | Licensed under Apache License v2.0 with Runtime Library Exception 33 | 34 | See https://swift.org/LICENSE.txt for license information 35 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 36 | */ 37 | -------------------------------------------------------------------------------- /Snippets/Formatting/ThematicBreakCharacter.swift: -------------------------------------------------------------------------------- 1 | // Format a consistent style for thematic breaks. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | First paragraph. 7 | 8 | ----- 9 | 10 | Second paragraph. 11 | 12 | ***** 13 | """ 14 | 15 | let document = Document(parsing: source) 16 | let thematicBreakCharacter = MarkupFormatter.Options.ThematicBreakCharacter.dash 17 | // Make all thematic breaks 10 dash `-` characters. 18 | let formattingOptions = MarkupFormatter.Options(thematicBreakCharacter: thematicBreakCharacter, thematicBreakLength: 10) 19 | let formattedSource = document.format(options: formattingOptions) 20 | 21 | print(""" 22 | ## Original source: 23 | \(source) 24 | 25 | ## Formatted source: 26 | \(formattedSource) 27 | """) 28 | // snippet.hide 29 | /* 30 | This source file is part of the Swift.org open source project 31 | 32 | Copyright (c) 2022 Apple Inc. and the Swift project authors 33 | Licensed under Apache License v2.0 with Runtime Library Exception 34 | 35 | See https://swift.org/LICENSE.txt for license information 36 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 37 | */ 38 | -------------------------------------------------------------------------------- /Snippets/Formatting/MaximumWidth.swift: -------------------------------------------------------------------------------- 1 | // Format lines to stay under a certain length. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | This is a really, really, really, really, really, really, really, really, really, really, really long line. 7 | """ 8 | 9 | let document = Document(parsing: source) 10 | // Break lines longer than 80 characters in width with a soft break. 11 | let lineLimit = MarkupFormatter.Options.PreferredLineLimit(maxLength: 80, breakWith: .softBreak) 12 | let formattingOptions = MarkupFormatter.Options(preferredLineLimit: lineLimit) 13 | let formattedSource = document.format(options: formattingOptions) 14 | 15 | print(""" 16 | ## Original source: 17 | \(source) 18 | 19 | ## Formatted source: 20 | \(formattedSource) 21 | """) 22 | // snippet.hide 23 | /* 24 | This source file is part of the Swift.org open source project 25 | 26 | Copyright (c) 2022 Apple Inc. and the Swift project authors 27 | Licensed under Apache License v2.0 with Runtime Library Exception 28 | 29 | See https://swift.org/LICENSE.txt for license information 30 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 31 | */ 32 | -------------------------------------------------------------------------------- /Snippets/Formatting/PreferredHeadingStyle.swift: -------------------------------------------------------------------------------- 1 | // Format a Markdown document to use ATX style headings throughout. 2 | 3 | import Markdown 4 | 5 | let source = """ 6 | # Title 7 | 8 | ## Second-level Heading 9 | 10 | Another Second-level Heading 11 | ---------------------------- 12 | 13 | The above heading will be converted to ATX style, using hashes. 14 | """ 15 | 16 | let document = Document(parsing: source) 17 | let headingStyle = MarkupFormatter.Options.PreferredHeadingStyle.atx 18 | let formattingOptions = MarkupFormatter.Options(preferredHeadingStyle: headingStyle) 19 | let formattedSource = document.format(options: formattingOptions) 20 | 21 | print(""" 22 | ## Original source: 23 | \(source) 24 | 25 | ## Formatted source: 26 | \(formattedSource) 27 | """) 28 | // snippet.hide 29 | /* 30 | This source file is part of the Swift.org open source project 31 | 32 | Copyright (c) 2022 Apple Inc. and the Swift project authors 33 | Licensed under Apache License v2.0 with Runtime Library Exception 34 | 35 | See https://swift.org/LICENSE.txt for license information 36 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 37 | */ 38 | -------------------------------------------------------------------------------- /Sources/Markdown/Utility/CollectionExtensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | extension RangeReplaceableCollection { 12 | /// Append filler elements until ``count`` is at least `minCount`. 13 | mutating func ensureCount(atLeast minCount: Int, filler: Element) { 14 | let neededElementCount = minCount - count 15 | if neededElementCount > 0 { 16 | self.append(contentsOf: Array(repeating: filler, count: neededElementCount)) 17 | } 18 | } 19 | 20 | /// Return a copy of `self` with filler elements appended until ``count`` is at least `minCount`. 21 | func ensuringCount(atLeast minCount: Int, filler: Element) -> Self { 22 | var maybeExtend = self 23 | maybeExtend.ensureCount(atLeast: minCount, filler: filler) 24 | return maybeExtend 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tools/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | /* 3 | This source file is part of the Swift.org open source project 4 | 5 | Copyright (c) 2023 Apple Inc. and the Swift project authors 6 | Licensed under Apache License v2.0 with Runtime Library Exception 7 | 8 | See https://swift.org/LICENSE.txt for license information 9 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 10 | */ 11 | 12 | import PackageDescription 13 | 14 | let package = Package( 15 | name: "Tools", 16 | products: [ 17 | .executable(name: "markdown-tool", targets: ["markdown-tool"]), 18 | ], 19 | dependencies: [ 20 | .package(name: "swift-markdown", path: "../."), 21 | .package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.2"), 22 | ], 23 | targets: [ 24 | .executableTarget( 25 | name: "markdown-tool", 26 | dependencies: [ 27 | .product(name: "Markdown", package: "swift-markdown"), 28 | .product(name: "ArgumentParser", package: "swift-argument-parser") 29 | ], 30 | path: "markdown-tool" 31 | ), 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Block Nodes/HeadingTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class HeadingTests: XCTestCase { 15 | func testLevel() { 16 | let heading = Heading(level: 1, [Text("Some text")]) 17 | XCTAssertEqual(1, heading.level) 18 | 19 | var newHeading = heading 20 | newHeading.level = 2 21 | XCTAssertEqual(2, newHeading.level) 22 | XCTAssertFalse(heading.isIdentical(to: newHeading)) 23 | 24 | // If you don't actually change the level, you get the same node back. 25 | var newHeadingUnchanged = heading 26 | newHeadingUnchanged.level = heading.level 27 | XCTAssertTrue(heading.isIdentical(to: newHeadingUnchanged)) 28 | XCTAssertTrue(heading.isIdentical(to: newHeadingUnchanged)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Snippets/Walkers/LinkCollector.swift: -------------------------------------------------------------------------------- 1 | // Collect all links in a Markdown document. 2 | 3 | import Markdown 4 | 5 | struct LinkCollector: MarkupWalker { 6 | var links = [String]() 7 | mutating func visitLink(_ link: Link) { 8 | link.destination.map { links.append($0) } 9 | } 10 | } 11 | 12 | let source = """ 13 | A link to a [non-existent website](https://iqnvodkfjd.com). 14 | 15 | A link to a missing resource at . 16 | 17 | A valid link to . 18 | """ 19 | let document = Document(parsing: source) 20 | // snippet.hide 21 | print("## Checking links in parsed document:") 22 | print(document.debugDescription()) 23 | // snippet.show 24 | var linkCollector = LinkCollector() 25 | linkCollector.visit(document) 26 | print("## Found links:") 27 | print(linkCollector.links.joined(separator: "\n")) 28 | // snippet.hide 29 | /* 30 | This source file is part of the Swift.org open source project 31 | 32 | Copyright (c) 2022 Apple Inc. and the Swift project authors 33 | Licensed under Apache License v2.0 with Runtime Library Exception 34 | 35 | See https://swift.org/LICENSE.txt for license information 36 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 37 | */ 38 | -------------------------------------------------------------------------------- /Snippets/Formatting/UseCodeFence.swift: -------------------------------------------------------------------------------- 1 | // Format all code blocks to use a consistent style for code blocks, 2 | // optionally setting the default info string to declare that they 3 | // have a particular syntax. 4 | 5 | import Markdown 6 | 7 | let source = """ 8 | This document contains a mix of indented and fenced code blocks. 9 | 10 | A code block. 11 | 12 | ``` 13 | func foo() {} 14 | ``` 15 | """ 16 | 17 | let document = Document(parsing: source) 18 | // Always fenced code blocks. 19 | let fencedCodeBlock = MarkupFormatter.Options.UseCodeFence.always 20 | // Use `swift` as the info string on all fenced code blocks. 21 | let defaultCodeBlockLanguage = "swift" 22 | let formattedSource = document.format(options: .init(useCodeFence: fencedCodeBlock, defaultCodeBlockLanguage: defaultCodeBlockLanguage)) 23 | 24 | print(""" 25 | ## Original source: 26 | \(source) 27 | 28 | ## Formatted source: 29 | \(formattedSource) 30 | """) 31 | // snippet.hide 32 | /* 33 | This source file is part of the Swift.org open source project 34 | 35 | Copyright (c) 2022 Apple Inc. and the Swift project authors 36 | Licensed under Apache License v2.0 with Runtime Library Exception 37 | 38 | See https://swift.org/LICENSE.txt for license information 39 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 40 | */ 41 | -------------------------------------------------------------------------------- /Sources/Markdown/Infrastructure/Replacement.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A textual replacement. 12 | public struct Replacement: CustomStringConvertible, CustomDebugStringConvertible, Sendable { 13 | /// The range of source text to replace. 14 | public var range: SourceRange 15 | 16 | /// The text to substitute in the ``range``. 17 | public var replacementText: String 18 | 19 | /// Create a textual replacement. 20 | /// 21 | /// - parameter range: The range of the source text to replace. 22 | /// - parameter replacementText: The text to substitute in the range. 23 | public init(range: SourceRange, replacementText: String) { 24 | self.range = range 25 | self.replacementText = replacementText 26 | } 27 | 28 | public var description: String { 29 | return "\(range.diagnosticDescription()): fixit: \(replacementText)" 30 | } 31 | 32 | public var debugDescription: String { 33 | return description 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Base/StableIdentifierTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | /// Test that unique identifiers aren't recreated for the same elements. 15 | final class StableIdentifierTests: XCTestCase { 16 | /// Children are constructed on the fly; test that each time they are gotten, they have the same identifier. 17 | func testStableIdentifiers() { 18 | let paragraph = Paragraph(Emphasis(Text("OK."))) 19 | 20 | // A copy of a node should have the same identifier. 21 | let paragraphCopy = paragraph 22 | XCTAssertTrue(paragraph.isIdentical(to: paragraphCopy)) 23 | 24 | // A child gotten twice should have the same identifier both times. 25 | XCTAssertTrue(paragraph.child(at: 0)!.isIdentical(to: paragraph.child(at: 0)!)) 26 | 27 | // Similarly, for deeper nodes. 28 | XCTAssertTrue(paragraph.child(at: 0)!.child(at: 0)!.isIdentical(to: paragraph.child(at: 0)!.child(at: 0)!)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Markdown/Parser/RangerTracker.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A structure to automate incrementing child identifiers while 12 | /// adding their source ranges during parsing. 13 | struct RangeTracker { 14 | /// The narrowest range that covers all ranges seen so far. 15 | private(set) var totalRange: SourceRange 16 | 17 | /// Create a range tracker with a starting total range. 18 | /// 19 | /// - parameter totalRange: the narrowest range that covers all ranges seen so far. 20 | init(totalRange: SourceRange) { 21 | self.totalRange = totalRange 22 | } 23 | 24 | /// Add a source range and increment the next child identifier. 25 | /// 26 | /// - parameter range: An optional ``SourceRange``. This may be `nil` for 27 | /// some elements for which cmark doesn't track a range, such as 28 | /// soft breaks. 29 | mutating func add(_ range: SourceRange?) { 30 | if let range = range { 31 | totalRange.widen(toFit: range) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Leaf Blocks/ThematicBreak.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A thematic break. 12 | public struct ThematicBreak: BlockMarkup { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .thematicBreak = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: ThematicBreak.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | init(_ data: _MarkupData) { 22 | self._data = data 23 | } 24 | } 25 | 26 | // MARK: - Public API 27 | 28 | public extension ThematicBreak { 29 | /// Create a thematic break. 30 | init() { 31 | try! self.init(.thematicBreak(parsedRange: nil)) 32 | } 33 | 34 | // MARK: Visitation 35 | 36 | func accept(_ visitor: inout V) -> V.Result { 37 | return visitor.visitThematicBreak(self) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Markdown/Parser/ParseOptions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// Options for parsing Markdown. 12 | public struct ParseOptions: OptionSet, Sendable { 13 | public var rawValue: UInt 14 | 15 | public init(rawValue: UInt) { 16 | self.rawValue = rawValue 17 | } 18 | 19 | /// Enable block directive syntax. 20 | public static let parseBlockDirectives = ParseOptions(rawValue: 1 << 0) 21 | 22 | /// Enable interpretation of symbol links from inline code spans surrounded by two backticks. 23 | public static let parseSymbolLinks = ParseOptions(rawValue: 1 << 1) 24 | 25 | /// Disable converting straight quotes to curly, --- to em dashes, -- to en dashes during parsing. 26 | public static let disableSmartOpts = ParseOptions(rawValue: 1 << 2) 27 | 28 | /// Parse a limited set of Doxygen commands. Requires ``parseBlockDirectives``. 29 | public static let parseMinimalDoxygen = ParseOptions(rawValue: 1 << 3) 30 | 31 | /// Disable including a `data-sourcepos` attribute on all block elements during parsing. 32 | public static let disableSourcePosOpts = ParseOptions(rawValue: 1 << 4) 33 | } 34 | 35 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/UnorderedList.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An unordered list. 12 | public struct UnorderedList: ListItemContainer { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .unorderedList = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: UnorderedList.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | 22 | init(_ data: _MarkupData) { 23 | self._data = data 24 | } 25 | } 26 | 27 | // MARK: - Public API 28 | 29 | public extension UnorderedList { 30 | // MARK: ListItemContainer 31 | 32 | init(_ items: Items) where Items.Element == ListItem { 33 | try! self.init(.unorderedList(parsedRange: nil, items.map { $0.raw.markup })) 34 | } 35 | 36 | // MARK: Visitation 37 | 38 | func accept(_ visitor: inout V) -> V.Result { 39 | return visitor.visitUnorderedList(self) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Leaves/LineBreak.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A line break. 12 | public struct LineBreak: RecurringInlineMarkup { 13 | public var _data: _MarkupData 14 | 15 | init(_ data: _MarkupData) { 16 | self._data = data 17 | } 18 | 19 | init(_ raw: RawMarkup) throws { 20 | guard case .lineBreak = raw.data else { 21 | throw RawMarkup.Error.concreteConversionError(from: raw, to: LineBreak.self) 22 | } 23 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 24 | self.init(_MarkupData(absoluteRaw)) 25 | } 26 | } 27 | 28 | // MARK: - Public API 29 | 30 | public extension LineBreak { 31 | /// Create a hard line break. 32 | init() { 33 | try! self.init(.lineBreak(parsedRange: nil)) 34 | } 35 | 36 | // MARK: PlainTextConvertibleMarkup 37 | 38 | var plainText: String { 39 | return "\n" 40 | } 41 | 42 | // MARK: Visitation 43 | 44 | func accept(_ visitor: inout V) -> V.Result { 45 | return visitor.visitLineBreak(self) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Leaves/SoftBreak.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A soft break. 12 | public struct SoftBreak: RecurringInlineMarkup { 13 | public var _data: _MarkupData 14 | 15 | init(_ raw: RawMarkup) throws { 16 | guard case .softBreak = raw.data else { 17 | throw RawMarkup.Error.concreteConversionError(from: raw, to: SoftBreak.self) 18 | } 19 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 20 | self.init(_MarkupData(absoluteRaw)) 21 | } 22 | 23 | init(_ data: _MarkupData) { 24 | self._data = data 25 | } 26 | } 27 | 28 | // MARK: - Public API 29 | 30 | public extension SoftBreak { 31 | /// Create a soft line break. 32 | init() { 33 | try! self.init(.softBreak(parsedRange: nil)) 34 | } 35 | 36 | // MARK: PlainTextConvertibleMarkup 37 | 38 | var plainText: String { 39 | return " " 40 | } 41 | 42 | // MARK: Visitation 43 | 44 | func accept(_ visitor: inout V) -> V.Result { 45 | return visitor.visitSoftBreak(self) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Parsing/CommonMarkConverterTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | @testable import Markdown 12 | import XCTest 13 | 14 | class CommonMarkConverterTests: XCTestCase { 15 | /// Verify that a link that spans multiple lines does not crash cmark and also returns a valid range 16 | func testMulitlineLinks() { 17 | let text = """ 18 | This is a link to an article on a different domain [link 19 | to an article](https://www.host.com/article). 20 | """ 21 | 22 | let expectedDump = """ 23 | Document @1:1-2:46 24 | └─ Paragraph @1:1-2:46 25 | ├─ Text @1:1-1:52 "This is a link to an article on a different domain " 26 | ├─ Link @1:52-2:45 destination: "https://www.host.com/article" 27 | │ ├─ Text @1:53-1:57 "link" 28 | │ ├─ SoftBreak 29 | │ └─ Text @2:1-2:14 "to an article" 30 | └─ Text @2:45-2:46 "." 31 | """ 32 | 33 | let document = Document(parsing: text, source: nil, options: [.parseBlockDirectives, .parseSymbolLinks]) 34 | XCTAssertEqual(expectedDump, document.debugDescription(options: .printSourceLocations)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | 2 | The Swift Markdown Project 3 | ========================== 4 | 5 | Please visit the Swift Markdown web site for more information: 6 | 7 | * https://github.com/apple/swift-markdown 8 | 9 | Copyright (c) 2021 Apple Inc. and the Swift project authors 10 | 11 | The Swift Project licenses this file to you under the Apache License, 12 | version 2.0 (the "License"); you may not use this file except in compliance 13 | with the License. You may obtain a copy of the License at: 14 | 15 | https://www.apache.org/licenses/LICENSE-2.0 16 | 17 | Unless required by applicable law or agreed to in writing, software 18 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 19 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 20 | License for the specific language governing permissions and limitations 21 | under the License. 22 | 23 | ------------------------------------------------------------------------------- 24 | 25 | This product contains Swift Argument Parser. 26 | 27 | * LICENSE (Apache License 2.0): 28 | * https://www.apache.org/licenses/LICENSE-2.0 29 | * HOMEPAGE: 30 | * https://github.com/apple/swift-argument-parser 31 | 32 | --- 33 | 34 | This product contains a derivation of the cmark-gfm project, available at 35 | https://github.com/apple/swift-cmark. 36 | 37 | * LICENSE (BSD-2): 38 | * https://opensource.org/licenses/BSD-2-Clause 39 | * HOMEPAGE: 40 | * https://github.com/github/cmark-gfm 41 | 42 | -------------------------------------------------------------------------------- /Sources/Markdown/Structural Restrictions/BasicInlineContainer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A block or inline markup element that can contain only `InlineMarkup` elements and doesn't require any other information. 12 | public protocol BasicInlineContainer: InlineContainer { 13 | /// Create this element with a sequence of inline markup elements. 14 | init(_ children: some Sequence) 15 | 16 | /// Create this element with a sequence of inline markup elements, and optionally inherit the source range from those elements. 17 | init(_ children: some Sequence, inheritSourceRange: Bool) 18 | } 19 | 20 | extension BasicInlineContainer { 21 | /// Create this element with a sequence of inline markup elements. 22 | public init(_ children: InlineMarkup...) { 23 | self.init(children) 24 | } 25 | 26 | public init(_ children: InlineMarkup..., inheritSourceRange: Bool) { 27 | self.init(children, inheritSourceRange: inheritSourceRange) 28 | } 29 | 30 | /// Default implementation for `init(_:inheritSourceRange:)` that discards the `inheritSourceRange` parameter. 31 | public init(_ children: some Sequence, inheritSourceRange: Bool) { 32 | self.init(children) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Snippets/Rewriters/ReplaceText.swift: -------------------------------------------------------------------------------- 1 | // Replace some text with a ``MarkupRewriter``. 2 | // 3 | // > Experiment: You can use a similar approach for other kinds of replacements. 4 | // > Try updating link destinations in your document by implementing 5 | // > a `visitLink` method and returning a new ``Link`` element. 6 | 7 | import Markdown 8 | 9 | struct TextReplacer: MarkupRewriter { 10 | var target: String 11 | var replacement: String 12 | 13 | init(replacing target: String, with replacement: String?) { 14 | precondition(!target.isEmpty) 15 | self.target = target 16 | self.replacement = replacement ?? "" 17 | } 18 | 19 | func visitText(_ text: Text) -> Markup? { 20 | return Text(text.string.replacingOccurrences(of: target, with: replacement)) 21 | } 22 | } 23 | 24 | let source = """ 25 | The word "foo" will be replaced with "bar". 26 | """ 27 | let document = Document(parsing: source) 28 | var replacer = TextReplacer(replacing: "foo", with: "bar") 29 | let newDocument = replacer.visit(document) as! Document 30 | 31 | print(""" 32 | ## Original Markdown structure: 33 | \(document.debugDescription()) 34 | 35 | ## New Markdown structure: 36 | \(newDocument.debugDescription()) 37 | """) 38 | // snippet.hide 39 | /* 40 | This source file is part of the Swift.org open source project 41 | 42 | Copyright (c) 2022 Apple Inc. and the Swift project authors 43 | Licensed under Apache License v2.0 with Runtime Library Exception 44 | 45 | See https://swift.org/LICENSE.txt for license information 46 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 47 | */ 48 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Tables/TableHead.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | 12 | extension Table { 13 | /// The head of a table which contains one or more ``Table/Cell`` elements. 14 | public struct Head: Markup, _TableRowProtocol { 15 | public var _data: _MarkupData 16 | init(_ data: _MarkupData) { 17 | self._data = data 18 | } 19 | 20 | init(_ raw: RawMarkup) throws { 21 | guard case .tableHead = raw.data else { 22 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Table.Head.self) 23 | } 24 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 25 | self.init(_MarkupData(absoluteRaw)) 26 | } 27 | } 28 | } 29 | 30 | // MARK: - Public API 31 | 32 | public extension Table.Head { 33 | // MARK: TableCellContainer 34 | 35 | init(_ cells: Cells) where Cells : Sequence, Cells.Element == Table.Cell { 36 | try! self.init(.tableHead(parsedRange: nil, columns: cells.map { $0.raw.markup })) 37 | } 38 | 39 | // MARK: Visitation 40 | 41 | func accept(_ visitor: inout V) -> V.Result where V : MarkupVisitor { 42 | return visitor.visitTableHead(self) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Tables/TableRow.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | public protocol _TableRowProtocol : TableCellContainer {} 12 | 13 | extension Table { 14 | /// A row of cells in a table. 15 | public struct Row: Markup, _TableRowProtocol { 16 | public var _data: _MarkupData 17 | 18 | init(_ data: _MarkupData) { 19 | self._data = data 20 | } 21 | 22 | init(_ raw: RawMarkup) throws { 23 | guard case .tableRow = raw.data else { 24 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Table.Row.self) 25 | } 26 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 27 | self.init(_MarkupData(absoluteRaw)) 28 | } 29 | } 30 | } 31 | 32 | // MARK: - Public API 33 | 34 | public extension Table.Row { 35 | 36 | // MARK: TableCellContainer 37 | 38 | init(_ cells: Cells) where Cells : Sequence, Cells.Element == Table.Cell { 39 | try! self.init(.tableRow(parsedRange: nil, cells.map { $0.raw.markup })) 40 | } 41 | 42 | // MARK: Visitation 43 | 44 | func accept(_ visitor: inout V) -> V.Result where V : MarkupVisitor { 45 | return visitor.visitTableRow(self) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Tools/markdown-tool/MarkdownCommand.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import ArgumentParser 12 | import Foundation 13 | import Markdown 14 | 15 | @main 16 | struct MarkdownCommand: ParsableCommand { 17 | static let configuration = CommandConfiguration(commandName: "markdown", shouldDisplay: false, subcommands: [ 18 | DumpTree.self, 19 | Format.self, 20 | PrintHTML.self, 21 | ]) 22 | 23 | static func parseFile(at path: String, options: ParseOptions) throws -> (source: String, parsed: Document) { 24 | let data = try Data(contentsOf: URL(fileURLWithPath: path)) 25 | let inputString = String(decoding: data, as: UTF8.self) 26 | return (inputString, Document(parsing: inputString, options: options)) 27 | } 28 | 29 | static func parseStandardInput(options: ParseOptions) throws -> (source: String, parsed: Document) { 30 | let stdinData: Data 31 | if #available(macOS 10.15.4, *) { 32 | stdinData = try FileHandle.standardInput.readToEnd() ?? Data() 33 | } else { 34 | stdinData = FileHandle.standardInput.readDataToEndOfFile() 35 | } 36 | let stdinString = String(decoding: stdinData, as: UTF8.self) 37 | return (stdinString, Document(parsing: stdinString, options: options)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Base/AtomicCounterTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | final class AtomicCounterTests: XCTestCase { 15 | func testIncremental() { 16 | XCTAssertEqual(AtomicCounter.current, AtomicCounter.current) 17 | XCTAssertNotEqual(AtomicCounter.next(), AtomicCounter.next()) 18 | } 19 | 20 | func testSimultaneousFetch() { 21 | var counters = Set() 22 | let group = DispatchGroup() 23 | let fetchQueue = DispatchQueue(label: "AtomicCounterTests.testSimultaneousFetch.fetch", attributes: [.concurrent]) 24 | let collectQueue = DispatchQueue(label: "AtomicCounterTests.testSimultaneousFetch.collect") 25 | let numTasks = 4 26 | let idsPerQueue = 200000 27 | for _ in 0.. 18 | 19 | ### Getting Started 20 | 21 | - 22 | - 23 | 24 | ### Essentials 25 | 26 | - ``Markup`` 27 | - ``MarkupChildren`` 28 | - ``ChildIndexPath`` 29 | - ``TypedChildIndexPath`` 30 | - ``DirectiveArgument`` 31 | - ``DirectiveArgumentText`` 32 | - ``Document`` 33 | - ``LiteralMarkup`` 34 | - ``PlainTextConvertibleMarkup`` 35 | 36 | ### Markup Types 37 | 38 | - 39 | - 40 | - ``Aside`` 41 | 42 | ### Infrastructure 43 | 44 | - 45 | 46 | ### Visit Markup 47 | 48 | - 49 | - 50 | 51 | 52 | -------------------------------------------------------------------------------- /Sources/Markdown/Structural Restrictions/BlockContainer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A block element whose children must conform to `BlockMarkup` 12 | public protocol BlockContainer: BlockMarkup {} 13 | 14 | // MARK: - Public API 15 | 16 | public extension BlockContainer { 17 | /// The inline child elements of this element. 18 | /// 19 | /// - Precondition: All children of an `InlineContainer` 20 | /// must conform to `InlineMarkup`. 21 | var blockChildren: LazyMapSequence { 22 | return children.lazy.map { $0 as! BlockMarkup } 23 | } 24 | 25 | /// Replace all inline child elements with a new sequence of inline elements. 26 | mutating func setBlockChildren(_ newChildren: Items) where Items.Element == BlockMarkup { 27 | replaceChildrenInRange(0..(_ range: Range, with incomingItems: Items) where Items.Element == BlockMarkup { 32 | var rawChildren = raw.markup.copyChildren() 33 | rawChildren.replaceSubrange(range, with: incomingItems.map { $0.raw.markup }) 34 | let newRaw = raw.markup.withChildren(rawChildren) 35 | _data = _data.replacingSelf(newRaw) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Markdown/Structural Restrictions/BasicBlockContainer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A block element that can contain only other block elements and doesn't require any other information. 12 | public protocol BasicBlockContainer: BlockContainer { 13 | /// Create this element from a sequence of block markup elements. 14 | init(_ children: some Sequence) 15 | 16 | /// Create this element from a sequence of block markup elements, and optionally inherit the source range from those elements. 17 | init(_ children: some Sequence, inheritSourceRange: Bool) 18 | } 19 | 20 | // MARK: - Public API 21 | 22 | extension BasicBlockContainer { 23 | /// Create this element with a sequence of block markup elements. 24 | public init(_ children: BlockMarkup...) { 25 | self.init(children) 26 | } 27 | 28 | /// Create this element with a sequence of block markup elements, and optionally inherit the source range from those elements. 29 | public init(_ children: BlockMarkup..., inheritSourceRange: Bool) { 30 | self.init(children, inheritSourceRange: inheritSourceRange) 31 | } 32 | 33 | /// Default implementation of `init(_:inheritSourceRange:)` that discards the `inheritSourceRange` parameter. 34 | public init(_ children: some Sequence, inheritSourceRange: Bool) { 35 | self.init(children) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/BlockQuote.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A block quote. 12 | public struct BlockQuote: BlockMarkup, BasicBlockContainer { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .blockQuote = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: BlockQuote.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | 22 | init(_ data: _MarkupData) { 23 | self._data = data 24 | } 25 | } 26 | 27 | // MARK: - Public API 28 | 29 | public extension BlockQuote { 30 | // MARK: BasicBlockContainer 31 | 32 | init(_ children: some Sequence) { 33 | self.init(children, inheritSourceRange: false) 34 | } 35 | 36 | init(_ children: some Sequence, inheritSourceRange: Bool) { 37 | let rawChildren = children.map { $0.raw.markup } 38 | let parsedRange = inheritSourceRange ? rawChildren.parsedRange : nil 39 | try! self.init(.blockQuote(parsedRange: parsedRange, rawChildren)) 40 | } 41 | 42 | // MARK: Visitation 43 | 44 | func accept(_ visitor: inout V) -> V.Result { 45 | return visitor.visitBlockQuote(self) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Inline Container Blocks/Paragraph.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A paragraph. 12 | public struct Paragraph: BlockMarkup, BasicInlineContainer { 13 | public var _data: _MarkupData 14 | init(_ data: _MarkupData) { 15 | self._data = data 16 | } 17 | 18 | init(_ raw: RawMarkup) throws { 19 | guard case .paragraph = raw.data else { 20 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Paragraph.self) 21 | } 22 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 23 | self.init(_MarkupData(absoluteRaw)) 24 | } 25 | } 26 | 27 | // MARK: - Public API 28 | 29 | public extension Paragraph { 30 | // MARK: InlineContainer 31 | 32 | init(_ newChildren: some Sequence) { 33 | self.init(newChildren, inheritSourceRange: false) 34 | } 35 | 36 | init(_ newChildren: some Sequence, inheritSourceRange: Bool) { 37 | let rawChildren = newChildren.map { $0.raw.markup } 38 | let parsedRange = inheritSourceRange ? rawChildren.parsedRange : nil 39 | try! self.init(.paragraph(parsedRange: parsedRange, rawChildren)) 40 | } 41 | 42 | // MARK: Visitation 43 | 44 | func accept(_ visitor: inout V) -> V.Result { 45 | return visitor.visitParagraph(self) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Parsing/BacktickTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | @testable import Markdown 12 | import XCTest 13 | 14 | class BacktickTests: XCTestCase { 15 | func testNormalBackticks() { 16 | let string = "Hello `test` String" 17 | let document = Document(parsing: string) 18 | let expectedDump = """ 19 | Document @1:1-1:20 20 | └─ Paragraph @1:1-1:20 21 | ├─ Text @1:1-1:7 "Hello " 22 | ├─ InlineCode @1:7-1:13 `test` 23 | └─ Text @1:13-1:20 " String" 24 | """ 25 | XCTAssertEqual(expectedDump, document.debugDescription(options: .printSourceLocations)) 26 | } 27 | 28 | func testOpenBacktick() { 29 | let single = "`" 30 | let document = Document(parsing: single) 31 | let expectedDump = """ 32 | Document @1:1-1:2 33 | └─ Paragraph @1:1-1:2 34 | └─ Text @1:1-1:2 "`" 35 | """ 36 | XCTAssertEqual(expectedDump, document.debugDescription(options: .printSourceLocations)) 37 | } 38 | 39 | func testOpenBackticks() { 40 | let double = "``" 41 | let document = Document(parsing: double) 42 | let expectedDump = """ 43 | Document @1:1-1:3 44 | └─ Paragraph @1:1-1:3 45 | └─ Text @1:1-1:3 "``" 46 | """ 47 | XCTAssertEqual(expectedDump, document.debugDescription(options: .printSourceLocations)) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Block Nodes/CodeBlockTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class CodeBlockTests: XCTestCase { 15 | var testCodeBlock: CodeBlock { 16 | let language = "swift" 17 | let code = "func foo() {}" 18 | let codeBlock = CodeBlock(language: language, code) 19 | XCTAssertEqual(.some(language), codeBlock.language) 20 | XCTAssertEqual(code, codeBlock.code) 21 | return codeBlock 22 | } 23 | 24 | func testCodeBlockLanguage() { 25 | let codeBlock = testCodeBlock 26 | var newCodeBlock = codeBlock 27 | newCodeBlock.language = "c" 28 | 29 | XCTAssertEqual(.some("c"), newCodeBlock.language) 30 | XCTAssertFalse(codeBlock.isIdentical(to: newCodeBlock)) 31 | 32 | var codeBlockWithoutLanguage = newCodeBlock 33 | codeBlockWithoutLanguage.language = nil 34 | XCTAssertNil(codeBlockWithoutLanguage.language) 35 | XCTAssertFalse(codeBlock.isIdentical(to: codeBlockWithoutLanguage)) 36 | } 37 | 38 | func testCodeBlockCode() { 39 | let codeBlock = testCodeBlock 40 | let newCode = "func bar() {}" 41 | var newCodeBlock = codeBlock 42 | newCodeBlock.code = newCode 43 | 44 | XCTAssertEqual(newCode, newCodeBlock.code) 45 | XCTAssertEqual(codeBlock.language, newCodeBlock.language) 46 | XCTAssertFalse(codeBlock.isIdentical(to: newCodeBlock)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Performance/MarkupChildrenPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | import Markdown 13 | 14 | final class MarkupChildrenPerformanceTests: XCTestCase { 15 | /// Iteration over the children should be fast: no heap allocation should be necessary. 16 | let paragraph = Paragraph((0..<10000).map { _ in Text("OK") }) 17 | func testIterateChildrenForward() { 18 | measure { 19 | for child in paragraph.children { 20 | _ = child 21 | } 22 | } 23 | } 24 | 25 | /// Iteration over the children in reverse should be fast: no heap allocation should be necessary. 26 | func testIterateChildrenReversed() { 27 | let paragraph = Paragraph((0..<10000).map { _ in Text("OK") }) 28 | measure { 29 | for child in paragraph.children.reversed() { 30 | _ = child 31 | } 32 | } 33 | } 34 | 35 | func testDropFirst() { 36 | let paragraph = Paragraph((0..<10000).map { _ in Text("OK") }) 37 | measure { 38 | for child in paragraph.children.dropFirst(5000) { 39 | _ = child 40 | } 41 | } 42 | } 43 | 44 | func testSuffix() { 45 | let paragraph = Paragraph((0..<10000).map { _ in Text("OK") }) 46 | measure { 47 | for child in paragraph.children.suffix(5000) { 48 | _ = child 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Leaf Blocks/HTMLBlock.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A block element containing raw HTML. 12 | public struct HTMLBlock: BlockMarkup, LiteralMarkup { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .htmlBlock = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: HTMLBlock.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | 22 | init(_ data: _MarkupData) { 23 | self._data = data 24 | } 25 | } 26 | 27 | // MARK: - Public API 28 | 29 | public extension HTMLBlock { 30 | init(_ literalText: String) { 31 | try! self.init(.htmlBlock(parsedRange: nil, html: literalText)) 32 | } 33 | 34 | /// The raw HTML text comprising the block. 35 | var rawHTML: String { 36 | get { 37 | guard case let .htmlBlock(text) = _data.raw.markup.data else { 38 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 39 | } 40 | return text 41 | } 42 | set { 43 | _data = _data.replacingSelf(.htmlBlock(parsedRange: nil, html: newValue)) 44 | } 45 | } 46 | 47 | // MARK: Visitation 48 | 49 | func accept(_ visitor: inout V) -> V.Result { 50 | return visitor.visitHTMLBlock(self) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | cmake_minimum_required(VERSION 3.19.0) 11 | 12 | if(POLICY CMP0077) 13 | cmake_policy(SET CMP0077 NEW) 14 | endif() 15 | 16 | if(POLICY CMP0091) 17 | cmake_policy(SET CMP0091 NEW) 18 | endif() 19 | 20 | project(SwiftMarkdown 21 | LANGUAGES C Swift) 22 | 23 | list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules) 24 | 25 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 26 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 27 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 28 | 29 | set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift) 30 | set(CMAKE_Swift_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY MultiThreadedDLL) 31 | 32 | include(FetchContent) 33 | include(SwiftSupport) 34 | 35 | set(_SM_VENDOR_DEPENDENCIES) 36 | 37 | set(BUILD_EXAMPLES NO) 38 | set(BUILD_TESTING NO) 39 | 40 | find_package(ArgumentParser CONFIG) 41 | if(NOT ArgumentParser_FOUND) 42 | FetchContent_Declare(ArgumentParser 43 | GIT_REPOSITORY https://github.com/apple/swift-argument-parser 44 | GIT_TAG 1.2.3) 45 | list(APPEND _SM_VENDOR_DEPENDENCIES ArgumentParser) 46 | endif() 47 | 48 | find_package(cmark-gfm CONFIG) 49 | if(NOT cmark-gfm_FOUND) 50 | FetchContent_Declare(cmark-gfm 51 | GIT_REPOSITORY https://github.com/apple/swift-cmark 52 | GIT_TAG gfm) 53 | list(APPEND _SM_VENDOR_DEPENDENCIES cmark-gfm) 54 | endif() 55 | 56 | if(_SM_VENDOR_DEPENDENCIES) 57 | FetchContent_MakeAvailable(${_SM_VENDOR_DEPENDENCIES}) 58 | endif() 59 | 60 | add_subdirectory(Sources) 61 | add_subdirectory(cmake/modules) 62 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/CustomBlock.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A custom block markup element. 12 | /// 13 | /// - note: This element does not yet allow for custom information to be appended and is included for backward compatibility with CommonMark. It wraps any block element. 14 | public struct CustomBlock: BlockMarkup, BasicBlockContainer { 15 | public var _data: _MarkupData 16 | init(_ raw: RawMarkup) throws { 17 | guard case .customBlock = raw.data else { 18 | throw RawMarkup.Error.concreteConversionError(from: raw, to: CustomBlock.self) 19 | } 20 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 21 | self.init(_MarkupData(absoluteRaw)) 22 | } 23 | 24 | init(_ data: _MarkupData) { 25 | self._data = data 26 | } 27 | } 28 | 29 | // MARK: - Public API 30 | 31 | public extension CustomBlock { 32 | init(_ children: some Sequence) { 33 | self.init(children, inheritSourceRange: false) 34 | } 35 | 36 | init(_ children: some Sequence, inheritSourceRange: Bool) { 37 | let rawChildren = children.map { $0.raw.markup } 38 | let parsedRange = inheritSourceRange ? rawChildren.parsedRange : nil 39 | try! self.init(.customBlock(parsedRange: parsedRange, rawChildren)) 40 | } 41 | 42 | // MARK: Visitation 43 | 44 | func accept(_ visitor: inout V) -> V.Result { 45 | return visitor.visitCustomBlock(self) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Leaves/Text.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// Plain text. 12 | public struct Text: RecurringInlineMarkup, LiteralMarkup { 13 | public var _data: _MarkupData 14 | 15 | init(_ raw: RawMarkup) throws { 16 | guard case .text = raw.data else { 17 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Text.self) 18 | } 19 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 20 | self.init(_MarkupData(absoluteRaw)) 21 | } 22 | 23 | init(_ data: _MarkupData) { 24 | self._data = data 25 | } 26 | } 27 | 28 | // MARK: - Public API 29 | 30 | public extension Text { 31 | init(_ literalText: String) { 32 | try! self.init(.text(parsedRange: nil, string: literalText)) 33 | } 34 | 35 | /// The raw text of the element. 36 | var string: String { 37 | get { 38 | guard case let .text(string) = _data.raw.markup.data else { 39 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 40 | } 41 | return string 42 | } 43 | set { 44 | _data = _data.replacingSelf(.text(parsedRange: nil, string: newValue)) 45 | } 46 | } 47 | 48 | // MARK: PlainTextConvertibleMarkup 49 | 50 | var plainText: String { 51 | return string 52 | } 53 | 54 | // MARK: Visitation 55 | 56 | func accept(_ visitor: inout V) -> V.Result { 57 | return visitor.visitText(self) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/Markdown/Structural Restrictions/InlineContainer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An element whose children must conform to `InlineMarkup` 12 | public protocol InlineContainer: PlainTextConvertibleMarkup {} 13 | 14 | // MARK: - Public API 15 | 16 | public extension InlineContainer { 17 | /// The inline child elements of this element. 18 | /// 19 | /// - Precondition: All children of an `InlineContainer` 20 | /// must conform to `InlineMarkup`. 21 | var inlineChildren: LazyMapSequence { 22 | return children.lazy.map { $0 as! InlineMarkup } 23 | } 24 | 25 | /// Replace all inline child elements with a new sequence of inline elements. 26 | mutating func setInlineChildren(_ newChildren: Items) where Items.Element == InlineMarkup { 27 | replaceChildrenInRange(0..(_ range: Range, with incomingItems: Items) where Items.Element == InlineMarkup { 32 | var rawChildren = raw.markup.copyChildren() 33 | rawChildren.replaceSubrange(range, with: incomingItems.map { $0.raw.markup }) 34 | let newRaw = raw.markup.withChildren(rawChildren) 35 | _data = _data.replacingSelf(newRaw) 36 | } 37 | 38 | // MARK: PlainTextConvertibleMarkup 39 | 40 | var plainText: String { 41 | return children.compactMap { 42 | return ($0 as? InlineMarkup)?.plainText 43 | }.joined() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/ImageTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class ImageTests: XCTestCase { 15 | func testImageSource() { 16 | let source = "test.png" 17 | let image = Image(source: source, title: "") 18 | XCTAssertEqual(source, image.source) 19 | XCTAssertEqual(0, image.childCount) 20 | 21 | let newSource = "new.png" 22 | var newImage = image 23 | newImage.source = newSource 24 | XCTAssertEqual(newSource, newImage.source) 25 | XCTAssertFalse(image.isIdentical(to: newImage)) 26 | } 27 | 28 | func testImageTitle() { 29 | let title = "title" 30 | let image = Image(source: "_", title: title) 31 | XCTAssertEqual(title, image.title) 32 | XCTAssertEqual(0, image.childCount) 33 | 34 | do { 35 | let source = "![Alt](test.png \"\(title)\")" 36 | let document = Document(parsing: source) 37 | let image = document.child(through:[ 38 | (0, Paragraph.self), 39 | (0, Image.self), 40 | ]) as! Image 41 | XCTAssertEqual(title, image.title) 42 | } 43 | } 44 | 45 | func testLinkFromSequence() { 46 | let children = [Text("Hello, world!")] 47 | let image = Image(source: "test.png", title: "title", children) 48 | let expectedDump = """ 49 | Image source: "test.png" title: "title" 50 | └─ Text "Hello, world!" 51 | """ 52 | XCTAssertEqual(expectedDump, image.debugDescription()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Leaves/InlineHTML.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An inline markup element containing raw HTML. 12 | public struct InlineHTML: RecurringInlineMarkup, LiteralMarkup { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .inlineHTML = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: InlineHTML.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | 22 | init(_ data: _MarkupData) { 23 | self._data = data 24 | } 25 | } 26 | 27 | // MARK: - Public API 28 | 29 | public extension InlineHTML { 30 | init(_ literalText: String) { 31 | try! self.init(.inlineHTML(parsedRange: nil, html: literalText)) 32 | } 33 | 34 | /// The raw HTML text. 35 | var rawHTML: String { 36 | get { 37 | guard case let .inlineHTML(text) = _data.raw.markup.data else { 38 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 39 | } 40 | return text 41 | } 42 | set { 43 | _data = _data.replacingSelf(.inlineHTML(parsedRange: nil, html: newValue)) 44 | } 45 | } 46 | 47 | // MARK: PlainTextConvertibleMarkup 48 | 49 | var plainText: String { 50 | return rawHTML 51 | } 52 | 53 | // MARK: Visitation 54 | 55 | func accept(_ visitor: inout V) -> V.Result { 56 | return visitor.visitInlineHTML(self) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Leaves/CustomInline.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A custom inline markup element. 12 | /// 13 | /// - note: This element does not yet allow for custom information to be appended and is included for backward compatibility with CommonMark. It wraps raw text. 14 | public struct CustomInline: RecurringInlineMarkup { 15 | public var _data: _MarkupData 16 | init(_ raw: RawMarkup) throws { 17 | guard case .customInline = raw.data else { 18 | throw RawMarkup.Error.concreteConversionError(from: raw, to: CustomInline.self) 19 | } 20 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 21 | self.init(_MarkupData(absoluteRaw)) 22 | } 23 | 24 | init(_ data: _MarkupData) { 25 | self._data = data 26 | } 27 | } 28 | 29 | // MARK: - Public API 30 | 31 | public extension CustomInline { 32 | /// Create a custom inline element from raw text. 33 | init(_ text: String) { 34 | try! self.init(.customInline(parsedRange: nil, text: text)) 35 | } 36 | 37 | /// The raw inline text of the element. 38 | var text: String { 39 | guard case let .customInline(text) = _data.raw.markup.data else { 40 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 41 | } 42 | return text 43 | } 44 | 45 | // MARK: PlainTextConvertibleMarkup 46 | 47 | var plainText: String { 48 | return text 49 | } 50 | 51 | func accept(_ visitor: inout V) -> V.Result { 52 | return visitor.visitCustomInline(self) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Visitors/MarkupVisitorTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2023 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | import Markdown 13 | 14 | class MarkupVisitorTests: XCTestCase { 15 | struct IntegerConverter: MarkupVisitor { 16 | var value: Int 17 | 18 | mutating func defaultVisit(_: Markdown.Markup) -> Int { 19 | defer { value += 1 } 20 | return value 21 | } 22 | } 23 | 24 | 25 | // A compile time check for PAT support 26 | func testMarkupVisitorPrimaryAssociatedType() { 27 | var visitor: some MarkupVisitor = IntegerConverter(value: 1) 28 | let markup = Text("") 29 | XCTAssertEqual(visitor.visit(markup), 1) 30 | XCTAssertEqual(visitor.visit(markup), 2) 31 | var mappedVisitor: some MarkupVisitor = visitor.map { $0 * $0 } 32 | XCTAssertEqual(mappedVisitor.visit(markup), 9) 33 | XCTAssertEqual(mappedVisitor.visit(markup), 16) 34 | XCTAssertEqual(visitor.visit(markup), 3) 35 | } 36 | } 37 | 38 | struct _MappVisitor: MarkupVisitor { 39 | typealias Result = B 40 | init(visitor: A, _ transform: @escaping (A.Result) -> B) { 41 | self.visitor = visitor 42 | self.transform = transform 43 | } 44 | private var visitor: A 45 | private let transform: (A.Result) -> B 46 | 47 | mutating func defaultVisit(_ markup: Markdown.Markup) -> B { 48 | transform(visitor.defaultVisit(markup)) 49 | } 50 | } 51 | 52 | extension MarkupVisitor { 53 | func map(_ transform: @escaping (Self.Result) -> U) -> some MarkupVisitor { 54 | _MappVisitor(visitor: self, transform) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Snippets.md: -------------------------------------------------------------------------------- 1 | # Snippets 2 | 3 | ## Parsing 4 | 5 | Parse strings in memory or files on disk into a structured ``Markup`` tree. 6 | 7 | @Snippet(path: "swift-markdown/Snippets/Parsing/ParseDocumentString") 8 | @Snippet(path: "swift-markdown/Snippets/Parsing/ParseDocumentFile") 9 | 10 | ## Querying 11 | 12 | @Snippet(path: "swift-markdown/Snippets/Querying/ChildThrough") 13 | 14 | ## Walkers, Rewriters, and Visitors 15 | 16 | Use ``MarkupWalker`` to collect information about ``Markup`` trees without modifying their contents. 17 | 18 | @Snippet(path: "swift-markdown/Snippets/Walkers/LinkCollector") 19 | 20 | Use ``MarkupRewriter`` to programmatically change the structure and contents of ``Markup`` trees. 21 | 22 | @Snippet(path: "swift-markdown/Snippets/Rewriters/RemoveElementKind") 23 | @Snippet(path: "swift-markdown/Snippets/Rewriters/ReplaceText") 24 | 25 | Use ``MarkupVisitor`` to convert a ``Markup`` tree to another nested structure. 26 | 27 | @Snippet(path: "swift-markdown/Snippets/Visitors/XMLConverter") 28 | 29 | ## Formatting 30 | 31 | Use the following formatting options alone or in combination to format 32 | a Markdown document to a consistent, preferred style. 33 | 34 | @Snippet(path: "swift-markdown/Snippets/Formatting/DefaultFormatting") 35 | @Snippet(path: "swift-markdown/Snippets/Formatting/MaximumWidth") 36 | @Snippet(path: "swift-markdown/Snippets/Formatting/CondenseAutolinks") 37 | @Snippet(path: "swift-markdown/Snippets/Formatting/CustomLinePrefix") 38 | @Snippet(path: "swift-markdown/Snippets/Formatting/EmphasisMarkers") 39 | @Snippet(path: "swift-markdown/Snippets/Formatting/OrderedListNumerals") 40 | @Snippet(path: "swift-markdown/Snippets/Formatting/UnorderedListMarker") 41 | @Snippet(path: "swift-markdown/Snippets/Formatting/PreferredHeadingStyle") 42 | @Snippet(path: "swift-markdown/Snippets/Formatting/ThematicBreakCharacter") 43 | @Snippet(path: "swift-markdown/Snippets/Formatting/UseCodeFence") 44 | 45 | 46 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Containers/Strong.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An element that tags inline elements with strong emphasis. 12 | public struct Strong: RecurringInlineMarkup, BasicInlineContainer { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .strong = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Strong.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | init(_ data: _MarkupData) { 22 | self._data = data 23 | } 24 | } 25 | 26 | // MARK: - Public API 27 | 28 | public extension Strong { 29 | // MARK: BasicInlineContainer 30 | 31 | init(_ newChildren: some Sequence) { 32 | self.init(newChildren, inheritSourceRange: false) 33 | } 34 | 35 | init(_ newChildren: some Sequence, inheritSourceRange: Bool) { 36 | let rawChildren = newChildren.map { $0.raw.markup } 37 | let parsedRange = inheritSourceRange ? rawChildren.parsedRange : nil 38 | try! self.init(.strong(parsedRange: parsedRange, rawChildren)) 39 | } 40 | 41 | // MARK: PlainTextConvertibleMarkup 42 | 43 | var plainText: String { 44 | let childrenPlainText = children.compactMap { 45 | return ($0 as? InlineMarkup)?.plainText 46 | }.joined() 47 | return "\(childrenPlainText)" 48 | } 49 | 50 | // MARK: Visitation 51 | 52 | func accept(_ visitor: inout V) -> V.Result { 53 | return visitor.visitStrong(self) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Leaves/InlineCode.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An inline code markup element, representing some code-like or "code voice" text. 12 | public struct InlineCode: RecurringInlineMarkup { 13 | public var _data: _MarkupData 14 | 15 | init(_ raw: RawMarkup) throws { 16 | guard case .inlineCode = raw.data else { 17 | throw RawMarkup.Error.concreteConversionError(from: raw, to: InlineCode.self) 18 | } 19 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 20 | self.init(_MarkupData(absoluteRaw)) 21 | } 22 | 23 | init(_ data: _MarkupData) { 24 | self._data = data 25 | } 26 | } 27 | 28 | // MARK: - Public API 29 | 30 | public extension InlineCode { 31 | /// Create an inline code element from a string. 32 | init(_ code: String) { 33 | try! self.init(.inlineCode(parsedRange: nil, code: code)) 34 | } 35 | 36 | /// The literal text content. 37 | var code: String { 38 | get { 39 | guard case let .inlineCode(code) = _data.raw.markup.data else { 40 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 41 | } 42 | return code 43 | } 44 | set { 45 | self._data = _data.replacingSelf(.inlineCode(parsedRange: nil, code: newValue)) 46 | } 47 | } 48 | 49 | // MARK: PlainTextConvertibleMarkup 50 | 51 | var plainText: String { 52 | return "`\(code)`" 53 | } 54 | 55 | // MARK: Visitation 56 | 57 | func accept(_ visitor: inout V) -> V.Result { 58 | return visitor.visitInlineCode(self) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Containers/Emphasis.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A markup element that tags inline elements with emphasis. 12 | public struct Emphasis: RecurringInlineMarkup, BasicInlineContainer { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .emphasis = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Emphasis.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | 22 | init(_ data: _MarkupData) { 23 | self._data = data 24 | } 25 | } 26 | 27 | // MARK: - Public API 28 | 29 | public extension Emphasis { 30 | // MARK: BasicInlineContainer 31 | 32 | init(_ newChildren: some Sequence) { 33 | self.init(newChildren, inheritSourceRange: false) 34 | } 35 | 36 | init(_ newChildren: some Sequence, inheritSourceRange: Bool) { 37 | let rawChildren = newChildren.map { $0.raw.markup } 38 | let parsedRange = inheritSourceRange ? rawChildren.parsedRange : nil 39 | try! self.init(.emphasis(parsedRange: parsedRange, rawChildren)) 40 | } 41 | 42 | // MARK: PlainTextConvertibleMarkup 43 | 44 | var plainText: String { 45 | let childrenPlainText = children.compactMap { 46 | return ($0 as? InlineMarkup)?.plainText 47 | }.joined() 48 | return "\(childrenPlainText)" 49 | } 50 | 51 | // MARK: Visitation 52 | 53 | func accept(_ visitor: inout V) -> V.Result { 54 | return visitor.visitEmphasis(self) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenNote.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import Foundation 12 | 13 | /// A parsed Doxygen `\note` command. 14 | /// 15 | /// The Doxygen support in Swift-Markdown parses `\note` commands of the form 16 | /// `\note description`, where `description` continues until the next blank 17 | /// line or parsed command. 18 | /// 19 | /// ```markdown 20 | /// \note This method is only meant to be called an odd number of times. 21 | /// ``` 22 | public struct DoxygenNote: BlockContainer { 23 | public var _data: _MarkupData 24 | 25 | init(_ raw: RawMarkup) throws { 26 | guard case .doxygenNote = raw.data else { 27 | throw RawMarkup.Error.concreteConversionError(from: raw, to: DoxygenNote.self) 28 | } 29 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 30 | self.init(_MarkupData(absoluteRaw)) 31 | } 32 | 33 | init(_ data: _MarkupData) { 34 | self._data = data 35 | } 36 | 37 | public func accept(_ visitor: inout V) -> V.Result { 38 | return visitor.visitDoxygenNote(self) 39 | } 40 | } 41 | 42 | public extension DoxygenNote { 43 | /// Create a new Doxygen note definition. 44 | /// 45 | /// - Parameter children: Block child elements. 46 | init(children: Children) where Children.Element == BlockMarkup { 47 | try! self.init(.doxygenNote(parsedRange: nil, children.map({ $0.raw.markup }))) 48 | } 49 | 50 | /// Create a new Doxygen note definition. 51 | /// 52 | /// - Parameter children: Block child elements. 53 | init(children: BlockMarkup...) { 54 | self.init(children: children) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Containers/Strikethrough.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// Inline elements that should be rendered with a strike through them. 12 | public struct Strikethrough: RecurringInlineMarkup, BasicInlineContainer { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .strikethrough = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Strikethrough.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | init(_ data: _MarkupData) { 22 | self._data = data 23 | } 24 | } 25 | 26 | // MARK: - Public API 27 | 28 | public extension Strikethrough { 29 | // MARK: BasicInlineContainer 30 | 31 | init(_ newChildren: some Sequence) { 32 | self.init(newChildren, inheritSourceRange: false) 33 | } 34 | 35 | init(_ newChildren: some Sequence, inheritSourceRange: Bool) { 36 | let rawChildren = newChildren.map { $0.raw.markup } 37 | let parsedRange = inheritSourceRange ? rawChildren.parsedRange : nil 38 | try! self.init(.strikethrough(parsedRange: parsedRange, rawChildren)) 39 | } 40 | 41 | // MARK: PlainTextConvertibleMarkup 42 | 43 | var plainText: String { 44 | let childrenPlainText = children.compactMap { 45 | return ($0 as? InlineMarkup)?.plainText 46 | }.joined() 47 | return "~\(childrenPlainText)~" 48 | } 49 | 50 | // MARK: Visitation 51 | 52 | func accept(_ visitor: inout V) -> V.Result { 53 | return visitor.visitStrikethrough(self) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Snippets/Visitors/XMLConverter.swift: -------------------------------------------------------------------------------- 1 | // Implement a ``MarkupVisitor`` to transform a ``Markup`` tree 2 | // to another structured data format such as XML. 3 | // 4 | // > Note: This is not a complete example converting 5 | // > the unique and important properties of each kind of ``Markup`` element 6 | // > as XML attributes. 7 | // 8 | // > Experiment: Implement all visitor methods for each type 9 | // > for a more complete XML or HTML converter. 10 | 11 | import Markdown 12 | 13 | struct XMLConverter: MarkupVisitor { 14 | mutating func defaultVisit(_ markup: Markup) -> XML { 15 | return XML(tag: String(describing: type(of: markup)), 16 | children: markup.children.map { defaultVisit($0) }, 17 | text: (markup as? Text).map { $0.string }) 18 | } 19 | } 20 | 21 | let source = """ 22 | A ***basic*** document. 23 | """ 24 | let document = Document(parsing: source) 25 | var xmlConverter = XMLConverter() 26 | let xml = xmlConverter.visit(document) 27 | 28 | print(""" 29 | ## Original document structure: 30 | \(document.debugDescription()) 31 | 32 | ## Resulting XML: 33 | \(xml.format()) 34 | """) 35 | 36 | // A very basic XML tree type. 37 | struct XML { 38 | var tag: String 39 | var children: [XML] 40 | var text: String? 41 | 42 | func format(indent: Int = 0) -> String { 43 | let indentation = String(repeating: " ", count: indent) 44 | if tag == "Text" { 45 | return "\(indentation)<\(tag)>\(text ?? "")" 46 | } else { 47 | var result = "\(indentation)<\(tag)>" 48 | for child in children { 49 | result += "\n\(child.format(indent: indent + 2))" 50 | } 51 | result += "\n\(indentation)<\(tag)>" 52 | return result 53 | } 54 | } 55 | } 56 | 57 | // snippet.hide 58 | /* 59 | This source file is part of the Swift.org open source project 60 | 61 | Copyright (c) 2022 Apple Inc. and the Swift project authors 62 | Licensed under Apache License v2.0 with Runtime Library Exception 63 | 64 | See https://swift.org/LICENSE.txt for license information 65 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 66 | */ 67 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Performance/EditPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | import Markdown 13 | 14 | final class EditPerformanceTests: XCTestCase { 15 | #if os(Windows) 16 | #if DEBUG 17 | static let maxDepth = 625 18 | #else 19 | static let maxDepth = 1250 20 | #endif 21 | #else 22 | static let maxDepth = 5000 23 | #endif 24 | /// Test the performance of changing a leaf in an unrealistically deep markup tree. 25 | func testChangeTextInDeepTree() { 26 | func buildDeepListItem(depth: Int) -> ListItem { 27 | guard depth < EditPerformanceTests.maxDepth else { 28 | return ListItem(Paragraph(Text("A"), Text("B"), Text("C"))) 29 | } 30 | return ListItem(buildDeepList(depth: depth + 1)) 31 | } 32 | 33 | func buildDeepList(depth: Int = 0) -> UnorderedList { 34 | guard depth < EditPerformanceTests.maxDepth else { 35 | return UnorderedList(buildDeepListItem(depth: depth)) 36 | } 37 | return UnorderedList(buildDeepListItem(depth: depth + 1)) 38 | } 39 | 40 | let list = buildDeepList() 41 | var deepChild: Markup = list 42 | while let child = deepChild.child(at: 0) { 43 | deepChild = child 44 | } 45 | 46 | var deepText = (deepChild as! Text) 47 | measure { 48 | deepText.string = "Z" 49 | } 50 | } 51 | 52 | /// Test the performance of change an element among unrealistically many siblings. 53 | func testChangeTextInWideParagraph() { 54 | let paragraph = Paragraph((0..<10000).map { _ in Text("OK") }) 55 | var firstText = paragraph.child(at: 0) as! Text 56 | measure { 57 | firstText.string = "OK" 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/InlineAttributesTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class InlineAttributesTests: XCTestCase { 15 | func testInlineAttributesAttributes() { 16 | let attributes = "rainbow: 'extreme'" 17 | let inlineAttributes = InlineAttributes(attributes: attributes) 18 | XCTAssertEqual(attributes, inlineAttributes.attributes) 19 | XCTAssertEqual(0, inlineAttributes.childCount) 20 | 21 | let newAttributes = "rainbow: 'medium'" 22 | var newInlineAttributes = inlineAttributes 23 | newInlineAttributes.attributes = newAttributes 24 | XCTAssertEqual(newAttributes, newInlineAttributes.attributes) 25 | XCTAssertFalse(inlineAttributes.isIdentical(to: newInlineAttributes)) 26 | } 27 | 28 | func testInlineAttributesFromSequence() { 29 | let children = [Text("Hello, world!")] 30 | let inlineAttributes = InlineAttributes(attributes: "rainbow: 'extreme'", children) 31 | let expectedDump = """ 32 | InlineAttributes attributes: `rainbow: 'extreme'` 33 | └─ Text "Hello, world!" 34 | """ 35 | XCTAssertEqual(expectedDump, inlineAttributes.debugDescription()) 36 | } 37 | 38 | func testParseInlineAttributes() { 39 | let source = "^[Hello, world!](rainbow: 'extreme')" 40 | let document = Document(parsing: source) 41 | let expectedDump = """ 42 | Document @1:1-1:37 43 | └─ Paragraph @1:1-1:37 44 | └─ InlineAttributes @1:1-1:37 attributes: `rainbow: 'extreme'` 45 | └─ Text @1:3-1:16 "Hello, world!" 46 | """ 47 | XCTAssertEqual(expectedDump, document.debugDescription(options: .printSourceLocations)) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenAbstract.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2025 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import Foundation 12 | 13 | /// A parsed Doxygen `\abstract` command. 14 | /// 15 | /// The Doxygen support in Swift-Markdown parses `\abstract` commands of the form 16 | /// `\abstract description`, where `description` continues until the next blank 17 | /// line or parsed command. 18 | /// 19 | /// ```markdown 20 | /// \abstract This object can give other objects in your program magical powers. 21 | /// ``` 22 | public struct DoxygenAbstract: BlockContainer { 23 | public var _data: _MarkupData 24 | 25 | init(_ raw: RawMarkup) throws { 26 | guard case .doxygenAbstract = raw.data else { 27 | throw RawMarkup.Error.concreteConversionError(from: raw, to: DoxygenAbstract.self) 28 | } 29 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 30 | self.init(_MarkupData(absoluteRaw)) 31 | } 32 | 33 | init(_ data: _MarkupData) { 34 | self._data = data 35 | } 36 | 37 | public func accept(_ visitor: inout V) -> V.Result { 38 | return visitor.visitDoxygenAbstract(self) 39 | } 40 | } 41 | 42 | public extension DoxygenAbstract { 43 | /// Create a new Doxygen abstract definition. 44 | /// 45 | /// - Parameter children: Block child elements. 46 | init(children: Children) where Children.Element == BlockMarkup { 47 | try! self.init(.doxygenAbstract(parsedRange: nil, children.map({ $0.raw.markup }))) 48 | } 49 | 50 | /// Create a new Doxygen abstract definition. 51 | /// 52 | /// - Parameter children: Block child elements. 53 | init(children: BlockMarkup...) { 54 | self.init(children: children) 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenDiscussion.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import Foundation 12 | 13 | /// A parsed Doxygen `\discussion` command. 14 | /// 15 | /// The Doxygen support in Swift-Markdown parses `\discussion` commands of the form 16 | /// `\discussion description`, where `description` continues until the next blank 17 | /// line or parsed command. 18 | /// 19 | /// ```markdown 20 | /// \discussion This object can give other objects in your program magical powers. 21 | /// ``` 22 | public struct DoxygenDiscussion: BlockContainer { 23 | public var _data: _MarkupData 24 | 25 | init(_ raw: RawMarkup) throws { 26 | guard case .doxygenDiscussion = raw.data else { 27 | throw RawMarkup.Error.concreteConversionError(from: raw, to: DoxygenDiscussion.self) 28 | } 29 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 30 | self.init(_MarkupData(absoluteRaw)) 31 | } 32 | 33 | init(_ data: _MarkupData) { 34 | self._data = data 35 | } 36 | 37 | public func accept(_ visitor: inout V) -> V.Result { 38 | return visitor.visitDoxygenDiscussion(self) 39 | } 40 | } 41 | 42 | public extension DoxygenDiscussion { 43 | /// Create a new Doxygen discussion definition. 44 | /// 45 | /// - Parameter children: Block child elements. 46 | init(children: Children) where Children.Element == BlockMarkup { 47 | try! self.init(.doxygenDiscussion(parsedRange: nil, children.map({ $0.raw.markup }))) 48 | } 49 | 50 | /// Create a new Doxygen discussion definition. 51 | /// 52 | /// - Parameter children: Block child elements. 53 | init(children: BlockMarkup...) { 54 | self.init(children: children) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Markdown/DoxygenCommands.md: -------------------------------------------------------------------------------- 1 | # Doxygen Commands 2 | 3 | Include a limited set of Doxygen commands in parsed Markdown. 4 | 5 | Swift Markdown includes an option to parse a limited set of Doxygen commands, to facilitate 6 | transitioning from a different Markdown parser. To include these commands in the output, include 7 | the options ``ParseOptions/parseBlockDirectives`` and ``ParseOptions/parseMinimalDoxygen`` when 8 | parsing a ``Document``. In the resulting document, parsed Doxygen commands appear as regular 9 | ``Markup`` types in the hierarchy. 10 | 11 | ## Parsing Strategy 12 | 13 | Doxygen commands are written by using either a backslash (`\`) or an at-sign (`@`) character, 14 | followed by the name of the command. Any parameters are then parsed as whitespace-separated words, 15 | then a "description" argument is taken from the remainder of the line, as well as all lines 16 | immediately after the command, until the parser sees a blank line, another Doxygen command, or a 17 | block directive. The description is then parsed for regular Markdown formatting, which is then 18 | stored as the children of the command type. For example, with Doxygen parsing turned on, the 19 | following document will parse three separate commands and one block directive: 20 | 21 | ```markdown 22 | \param thing The thing. 23 | This is the thing that is modified. 24 | \param otherThing The other thing. 25 | 26 | \returns A thing that has been modified. 27 | @Comment { 28 | This is not part of the `\returns` command. 29 | } 30 | ``` 31 | 32 | Trailing lines in a command's description are allowed to be indented relative to the command. For 33 | example, the description below is parsed as a paragraph, not a code block: 34 | 35 | ```markdown 36 | \param thing 37 | The thing. 38 | This is the thing that is modified. 39 | ``` 40 | 41 | Doxygen commands are not parsed within code blocks or block directive content. 42 | 43 | ## Topics 44 | 45 | ### Commands 46 | 47 | - ``DoxygenAbstract`` 48 | - ``DoxygenDiscussion`` 49 | - ``DoxygenNote`` 50 | - ``DoxygenParameter`` 51 | - ``DoxygenReturns`` 52 | 53 | 54 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenReturns.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2023 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import Foundation 12 | 13 | /// A parsed Doxygen `\returns`, `\return`, or `\result` command. 14 | /// 15 | /// The Doxygen support in Swift-Markdown parses `\returns` commands of the form 16 | /// `\returns description`, where `description` continues until the next blank line or parsed 17 | /// command. The commands `\return` and `\result` are also accepted, with the same format. 18 | /// 19 | /// ```markdown 20 | /// \returns A freshly-created object. 21 | /// ``` 22 | public struct DoxygenReturns: BlockContainer { 23 | public var _data: _MarkupData 24 | 25 | init(_ raw: RawMarkup) throws { 26 | guard case .doxygenReturns = raw.data else { 27 | throw RawMarkup.Error.concreteConversionError(from: raw, to: DoxygenReturns.self) 28 | } 29 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 30 | self.init(_MarkupData(absoluteRaw)) 31 | } 32 | 33 | init(_ data: _MarkupData) { 34 | self._data = data 35 | } 36 | 37 | public func accept(_ visitor: inout V) -> V.Result { 38 | return visitor.visitDoxygenReturns(self) 39 | } 40 | } 41 | 42 | public extension DoxygenReturns { 43 | /// Create a new Doxygen returns definition. 44 | /// 45 | /// - Parameter children: Block child elements. 46 | init(children: Children) where Children.Element == BlockMarkup { 47 | try! self.init(.doxygenReturns(parsedRange: nil, children.map({ $0.raw.markup }))) 48 | } 49 | 50 | /// Create a new Doxygen returns definition. 51 | /// 52 | /// - Parameter children: Block child elements. 53 | init(children: BlockMarkup...) { 54 | self.init(children: children) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tools/markdown-tool/Commands/PrintHTMLCommand.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2023 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import ArgumentParser 12 | import Markdown 13 | 14 | extension MarkdownCommand { 15 | /// A command to render HTML for given Markdown content. 16 | struct PrintHTML: ParsableCommand { 17 | static var configuration = CommandConfiguration(commandName: "print-html", abstract: "Convert Markdown content into HTML") 18 | 19 | @Argument( 20 | help: "Markdown file to print (default: standard input)", 21 | completion: .file() 22 | ) 23 | var inputFile: String? 24 | 25 | @Flag( 26 | inversion: .prefixedNo, 27 | exclusivity: .chooseLast, 28 | help: "Parse block quotes as asides if they have an aside marker" 29 | ) 30 | var parseAsides: Bool = false 31 | 32 | @Flag( 33 | inversion: .prefixedNo, 34 | exclusivity: .chooseLast, 35 | help: "Parse inline attributes as JSON, and use the 'class' property as a 'class' attribute" 36 | ) 37 | var parseInlineAttributeClass: Bool = false 38 | 39 | func run() throws { 40 | let document: Document 41 | if let inputFilePath = inputFile { 42 | (_, document) = try MarkdownCommand.parseFile(at: inputFilePath, options: []) 43 | } else { 44 | (_, document) = try MarkdownCommand.parseStandardInput(options: []) 45 | } 46 | 47 | var formatterOptions = HTMLFormatterOptions() 48 | if parseAsides { 49 | formatterOptions.insert(.parseAsides) 50 | } 51 | if parseInlineAttributeClass { 52 | formatterOptions.insert(.parseInlineAttributeClass) 53 | } 54 | 55 | print(HTMLFormatter.format(document, options: formatterOptions)) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/Markdown/Structural Restrictions/ListItemContainer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A markup element that can contain only `ListItem`s as children and require no other information. 12 | public protocol ListItemContainer: BlockMarkup { 13 | /// Create a list from a sequence of items. 14 | init(_ items: Items) where Items.Element == ListItem 15 | } 16 | 17 | // MARK: - Public API 18 | 19 | public extension ListItemContainer { 20 | /// Create a list with one item. 21 | init(_ item: ListItem) { 22 | self.init(CollectionOfOne(item)) 23 | } 24 | /// Create a list with the given `ListItem`s. 25 | init(_ items: ListItem...) { 26 | self.init(items) 27 | } 28 | 29 | /// The items of the list. 30 | /// 31 | /// - Precondition: All children of a `ListItemContainer` 32 | /// must be a `ListItem`. 33 | var listItems: LazyMapSequence { 34 | return children.lazy.map { $0 as! ListItem } 35 | } 36 | 37 | /// Replace all list items with a sequence of items. 38 | mutating func setListItems(_ newItems: Items) where Items.Element == ListItem { 39 | replaceItemsInRange(0..(_ range: Range, with incomingItems: Items) where Items.Element == ListItem { 44 | var rawChildren = raw.markup.copyChildren() 45 | rawChildren.replaceSubrange(range, with: incomingItems.map { $0.raw.markup }) 46 | let newRaw = raw.markup.withChildren(rawChildren) 47 | _data = _data.replacingSelf(newRaw) 48 | } 49 | 50 | /// Append an item to the list. 51 | mutating func appendItem(_ item: ListItem) { 52 | replaceItemsInRange(childCount..(_ items: Items) where Items.Element == ListItem { 32 | try! self.init(.orderedList(parsedRange: nil, items.map { $0.raw.markup })) 33 | } 34 | 35 | /// The starting index for the list. 36 | /// 37 | /// The default starting index in CommonMark is 1. In this case, clients may use the default 38 | /// ordered-list start index of their desired rendering format. For example, when rendering to 39 | /// HTML, clients may omit the `start` attribute of the rendered list when this returns 1. 40 | var startIndex: UInt { 41 | get { 42 | guard case let .orderedList(start) = _data.raw.markup.data else { 43 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 44 | } 45 | return start 46 | } 47 | set { 48 | guard startIndex != newValue else { 49 | return 50 | } 51 | _data = _data.replacingSelf(.orderedList(parsedRange: nil, _data.raw.markup.copyChildren(), startIndex: newValue)) 52 | } 53 | } 54 | 55 | // MARK: Visitation 56 | 57 | func accept(_ visitor: inout V) -> V.Result { 58 | return visitor.visitOrderedList(self) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Leaf Blocks/Heading.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A heading. 12 | public struct Heading: BlockMarkup, InlineContainer { 13 | public var _data: _MarkupData 14 | 15 | init(_ raw: RawMarkup) throws { 16 | guard case .heading = raw.data else { 17 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Heading.self) 18 | } 19 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 20 | self.init(_MarkupData(absoluteRaw)) 21 | } 22 | 23 | init(_ data: _MarkupData) { 24 | self._data = data 25 | } 26 | } 27 | 28 | // MARK: - Public API 29 | 30 | public extension Heading { 31 | // MARK: Primitive 32 | 33 | /// Create a heading with a level and a sequence of children. 34 | init(level: Int, _ children: Children) where Children.Element == InlineMarkup { 35 | try! self.init(.heading(level: level, parsedRange: nil, children.map { $0.raw.markup })) 36 | } 37 | 38 | /// The level of the heading, starting at `1`. 39 | var level: Int { 40 | get { 41 | guard case let .heading(level) = _data.raw.markup.data else { 42 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 43 | } 44 | return level 45 | } 46 | set { 47 | precondition(newValue > 0, "Heading level must be 1 or greater") 48 | guard level != newValue else { 49 | return 50 | } 51 | _data = _data.replacingSelf(.heading(level: newValue, parsedRange: nil, _data.raw.markup.copyChildren())) 52 | } 53 | } 54 | 55 | // MARK: Secondary 56 | 57 | /// Create a heading with a level and a sequence of children. 58 | init(level: Int, _ children: InlineMarkup...) { 59 | self.init(level: level, children) 60 | } 61 | 62 | // MARK: Visitation 63 | 64 | func accept(_ visitor: inout V) -> V.Result { 65 | return visitor.visitHeading(self) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Containers/InlineAttributes.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A set of one or more inline attributes. 12 | public struct InlineAttributes: InlineMarkup, InlineContainer { 13 | public var _data: _MarkupData 14 | 15 | init(_ raw: RawMarkup) throws { 16 | guard case .inlineAttributes = raw.data else { 17 | throw RawMarkup.Error.concreteConversionError(from: raw, to: InlineAttributes.self) 18 | } 19 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 20 | self.init(_MarkupData(absoluteRaw)) 21 | } 22 | 23 | init(_ data: _MarkupData) { 24 | self._data = data 25 | } 26 | } 27 | 28 | // MARK: - Public API 29 | 30 | public extension InlineAttributes { 31 | /// Create a set of custom inline attributes applied to zero or more child inline elements. 32 | init(attributes: String, _ children: Children) where Children.Element == RecurringInlineMarkup { 33 | try! self.init(.inlineAttributes(attributes: attributes, parsedRange: nil, children.map { $0.raw.markup })) 34 | } 35 | 36 | /// Create a set of custom attributes applied to zero or more child inline elements. 37 | init(attributes: String, _ children: RecurringInlineMarkup...) { 38 | self.init(attributes: attributes, children) 39 | } 40 | 41 | /// The specified attributes in JSON5 format. 42 | var attributes: String { 43 | get { 44 | guard case let .inlineAttributes(attributes) = _data.raw.markup.data else { 45 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 46 | } 47 | return attributes 48 | } 49 | set { 50 | _data = _data.replacingSelf(.inlineAttributes(attributes: newValue, parsedRange: nil, _data.raw.markup.copyChildren())) 51 | } 52 | } 53 | 54 | // MARK: Visitation 55 | 56 | func accept(_ visitor: inout V) -> V.Result { 57 | return visitor.visitInlineAttributes(self) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Package@swift-5.7.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.7 2 | /* 3 | This source file is part of the Swift.org open source project 4 | 5 | Copyright (c) 2021-2023 Apple Inc. and the Swift project authors 6 | Licensed under Apache License v2.0 with Runtime Library Exception 7 | 8 | See https://swift.org/LICENSE.txt for license information 9 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 10 | */ 11 | 12 | import PackageDescription 13 | import class Foundation.ProcessInfo 14 | 15 | let cmarkPackageName = ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil ? "swift-cmark" : "cmark" 16 | 17 | let package = Package( 18 | name: "swift-markdown", 19 | products: [ 20 | .library( 21 | name: "Markdown", 22 | targets: ["Markdown"]), 23 | ], 24 | targets: [ 25 | .target( 26 | name: "Markdown", 27 | dependencies: [ 28 | "CAtomic", 29 | .product(name: "cmark-gfm", package: cmarkPackageName), 30 | .product(name: "cmark-gfm-extensions", package: cmarkPackageName), 31 | ], 32 | exclude: [ 33 | "CMakeLists.txt" 34 | ] 35 | ), 36 | .testTarget( 37 | name: "MarkdownTests", 38 | dependencies: ["Markdown"], 39 | resources: [.process("Visitors/Everything.md")]), 40 | .target(name: "CAtomic"), 41 | ] 42 | ) 43 | 44 | // If the `SWIFTCI_USE_LOCAL_DEPS` environment variable is set, 45 | // we're building in the Swift.org CI system alongside other projects in the Swift toolchain and 46 | // we can depend on local versions of our dependencies instead of fetching them remotely. 47 | if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { 48 | // Building standalone, so fetch all dependencies remotely. 49 | package.dependencies += [ 50 | .package(url: "https://github.com/swiftlang/swift-cmark.git", branch: "gfm"), 51 | ] 52 | 53 | // SwiftPM command plugins are only supported by Swift version 5.6 and later. 54 | #if swift(>=5.6) 55 | package.dependencies += [ 56 | .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.1.0"), 57 | ] 58 | #endif 59 | } else { 60 | // Building in the Swift.org CI system, so rely on local versions of dependencies. 61 | package.dependencies += [ 62 | .package(path: "../cmark"), 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /Sources/Markdown/Parser/RangeAdjuster.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A type for adjusting the columns of elements that are parsed in *line runs* 12 | /// of the block directive parser to their locations before their indentation was trimmed. 13 | struct RangeAdjuster: MarkupWalker { 14 | /// The line number of the first line in the line run that needs adjustment. 15 | var startLine: Int 16 | 17 | /// The tracker that will collect the adjusted ranges. 18 | var ranges: RangeTracker 19 | 20 | /// An array of whitespace spans that were removed for each line, indexed 21 | /// by line number. `nil` means that no whitespace was removed on that line. 22 | var trimmedIndentationPerLine: [Int] 23 | 24 | mutating func defaultVisit(_ markup: Markup) { 25 | /// This should only be used in the parser where ranges are guaranteed 26 | /// to be filled in from cmark. 27 | let adjustedRange = markup.range.map { range -> SourceRange in 28 | // Add back the offset to the column as if the indentation weren't stripped. 29 | let start = SourceLocation(line: startLine + range.lowerBound.line - 1, 30 | column: range.lowerBound.column + (trimmedIndentationPerLine[range.lowerBound.line - 1] ), 31 | source: range.lowerBound.source) 32 | let end = SourceLocation(line: startLine + range.upperBound.line - 1, 33 | column: range.upperBound.column + (trimmedIndentationPerLine[range.upperBound.line - 1]), 34 | source: range.upperBound.source) 35 | return start..(_ elements: S) where S.Element == Element { 44 | self.elements = Array(elements) 45 | } 46 | 47 | /// Create a path from a sequence of index-type pairs. 48 | public init(arrayLiteral elements: ArrayLiteralElement...) { 49 | self.elements = elements.map { Element(index: $0.0, expectedType: $0.1) } 50 | } 51 | 52 | public var startIndex: Int { 53 | return elements.startIndex 54 | } 55 | 56 | public var endIndex: Int { 57 | return elements.endIndex 58 | } 59 | 60 | public subscript(index: Int) -> Element { 61 | return elements[index] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Base/PlainTextConvertibleMarkupTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | final class PlainTextConvertibleMarkupTests: XCTestCase { 15 | func testParagraph() { 16 | let paragraph = Paragraph( 17 | Text("This is a "), 18 | Emphasis(Text("paragraph")), 19 | Text(".")) 20 | 21 | XCTAssertEqual("This is a paragraph.", paragraph.plainText) 22 | } 23 | 24 | func testEmphasis() { 25 | let emphasis = Emphasis(Text("Emphasis")) 26 | XCTAssertEqual("Emphasis", emphasis.plainText) 27 | } 28 | 29 | func testImage() { 30 | let image = Image(source: "test.png", title: "", Text("This "), Text("is "), Text("an "), Text("image.")) 31 | XCTAssertEqual("This is an image.", image.plainText) 32 | } 33 | 34 | func testLink() { 35 | let link = Link(destination: "test.png", 36 | Text("This "), 37 | Text("is "), 38 | Text("a "), 39 | Text("link.")) 40 | XCTAssertEqual("This is a link.", link.plainText) 41 | } 42 | 43 | func testStrong() { 44 | let strong = Strong(Text("Strong")) 45 | XCTAssertEqual("Strong", strong.plainText) 46 | } 47 | 48 | func testCustomInline() { 49 | let customInline = CustomInline("Custom inline") 50 | XCTAssertEqual("Custom inline", customInline.plainText) 51 | } 52 | 53 | func testInlineCode() { 54 | let inlineCode = InlineCode("foo") 55 | XCTAssertEqual("`foo`", inlineCode.plainText) 56 | } 57 | 58 | func testInlineHTML() { 59 | let inlineHTML = InlineHTML("
") 60 | XCTAssertEqual("
", inlineHTML.plainText) 61 | } 62 | 63 | func testLineBreak() { 64 | XCTAssertEqual("\n", LineBreak().plainText) 65 | } 66 | 67 | func testSoftBreak() { 68 | XCTAssertEqual(" ", SoftBreak().plainText) 69 | } 70 | 71 | func testText() { 72 | let text = Text("OK") 73 | XCTAssertEqual("OK", text.plainText) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Leaf Blocks/CodeBlock.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A code block. 12 | public struct CodeBlock: BlockMarkup { 13 | public var _data: _MarkupData 14 | init(_ raw: RawMarkup) throws { 15 | guard case .codeBlock = raw.data else { 16 | throw RawMarkup.Error.concreteConversionError(from: raw, to: CodeBlock.self) 17 | } 18 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 19 | self.init(_MarkupData(absoluteRaw)) 20 | } 21 | 22 | init(_ data: _MarkupData) { 23 | self._data = data 24 | } 25 | } 26 | 27 | // MARK: - Public API 28 | 29 | public extension CodeBlock { 30 | /// Create a code block with raw `code` and optional `language`. 31 | init(language: String? = nil, _ code: String) { 32 | try! self.init(RawMarkup.codeBlock(parsedRange: nil, code: code, language: language)) 33 | } 34 | 35 | /// The name of the syntax or programming language of the code block, which may be `nil` when unspecified. 36 | var language: String? { 37 | get { 38 | guard case let .codeBlock(_, language) = _data.raw.markup.data else { 39 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 40 | } 41 | return language 42 | } 43 | set { 44 | _data = _data.replacingSelf(.codeBlock(parsedRange: nil, code: code, language: newValue)) 45 | } 46 | } 47 | 48 | /// The raw text representing the code of this block. 49 | var code: String { 50 | get { 51 | guard case let .codeBlock(code, _) = _data.raw.markup.data else { 52 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 53 | } 54 | return code 55 | } 56 | set { 57 | _data = _data.replacingSelf(.codeBlock(parsedRange: nil, code: newValue, language: language)) 58 | } 59 | } 60 | 61 | // MARK: Visitation 62 | 63 | func accept(_ visitor: inout V) -> V.Result { 64 | return visitor.visitCodeBlock(self) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/LineBreakTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | final class LineBreakTests: XCTestCase { 15 | /// Tests that creation doesn't crash. 16 | func testLineBreak() { 17 | _ = LineBreak() 18 | } 19 | 20 | /// Test that line breaks are parsed correctly. 21 | /// (Lots of folks have trailing whitespace trimming on). 22 | func testParseLineBreak() { 23 | let source = "Paragraph. \nStill the same paragraph." 24 | let document = Document(parsing: source) 25 | let paragraph = document.child(at: 0) as! Paragraph 26 | XCTAssertTrue(Array(paragraph.children)[1] is LineBreak) 27 | } 28 | 29 | /// Test that hard line breaks work with spaces (two or more). 30 | func testSpaceHardLineBreak() { 31 | let source = """ 32 | Paragraph.\(" ") 33 | Still the same paragraph. 34 | """ 35 | let document = Document(parsing: source) 36 | let paragraph = document.child(at: 0) as! Paragraph 37 | XCTAssertTrue(Array(paragraph.children)[1] is LineBreak) 38 | } 39 | 40 | /// Test that hard line breaks work with a slash. 41 | func testSlashHardLineBreak() { 42 | let source = #""" 43 | Paragraph.\ 44 | Still the same paragraph. 45 | """# 46 | let document = Document(parsing: source) 47 | let paragraph = document.child(at: 0) as! Paragraph 48 | XCTAssertTrue(Array(paragraph.children)[1] is LineBreak) 49 | } 50 | 51 | /// Sanity test that a multiline text without hard breaks doesn't return line breaks. 52 | func testLineBreakWithout() { 53 | let source = """ 54 | Paragraph. 55 | Same line text. 56 | """ 57 | let document = Document(parsing: source) 58 | 59 | let paragraph = document.child(at: 0) as! Paragraph 60 | XCTAssertFalse(Array(paragraph.children)[1] is LineBreak) 61 | XCTAssertEqual(Array(paragraph.children)[1].withoutSoftBreaks?.childCount, nil) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Visitors-Walkers-and-Rewriters.md: -------------------------------------------------------------------------------- 1 | # Visitors, Walkers, and Rewriters 2 | 3 | Use `MarkupVisitor` to transform, walk, and rewrite markup trees. 4 | 5 | ## Markup Visitor 6 | 7 | The core ``MarkupVisitor`` protocol provides the basis for transforming, walking, or rewriting a markup tree. 8 | 9 | ```swift 10 | public protocol MarkupVisitor { 11 | associatedtype Result 12 | } 13 | ``` 14 | 15 | Using its ``MarkupVisitor/Result`` type, you can transform a markup tree into anything: another markup tree, or perhaps a tree of XML or HTML elements. There are two included refinements of `MarkupVisitor` for common uses. 16 | 17 | ## Markup Walker 18 | 19 | The first refinement of `MarkupVisitor`, ``MarkupWalker``, has an associated `Result` type of `Void`, so it's meant for summarizing or detecting aspects of a markup tree. If you wanted to append to a string as elements are visited, this might be a good tool for that. 20 | 21 | ```swift 22 | import Markdown 23 | 24 | /// Counts `Link`s in a `Document`. 25 | struct LinkCounter: MarkupWalker { 26 | var count = 0 27 | mutating func visitLink(_ link: Link) { 28 | if link.destination == "https://swift.org" { 29 | count += 1 30 | } 31 | descendInto(link) 32 | } 33 | } 34 | 35 | let source = "There are [two](https://swift.org) links to here." 36 | let document = Document(parsing: source) 37 | print(document.debugDescription()) 38 | var linkCounter = LinkCounter() 39 | linkCounter.visit(document) 40 | print(linkCounter.count) 41 | // 2 42 | ``` 43 | 44 | ## Markup Rewriter 45 | 46 | The second refinement, ``MarkupRewriter``, has an associated `Result` type of an optional ``Markup`` element, so it's meant to change or even remove elements from a markup tree. You can return `nil` to delete an element, or return another element to substitute in its place. 47 | 48 | ```swift 49 | import Markdown 50 | 51 | /// Delete all **strong** elements in a markup tree. 52 | struct StrongDeleter: MarkupRewriter { 53 | mutating func visitStrong(_ strong: Strong) -> Markup? { 54 | return nil 55 | } 56 | } 57 | 58 | let source = "Now you see me, **now you don't**" 59 | let document = Document(parsing: source) 60 | var strongDeleter = StrongDeleter() 61 | let newDocument = strongDeleter.visit(document) 62 | 63 | print(newDocument!.debugDescription()) 64 | // Document 65 | // └─ Paragraph 66 | // └─ Text "Now you see me, " 67 | ``` 68 | 69 | 70 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Leaves/SymbolLink.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A link to a symbol. 12 | /// 13 | /// Symbol links are written the same as inline code spans but with 14 | /// two backticks `\`` instead of one. The contents inside the backticks become 15 | /// the link's destination. 16 | /// 17 | /// Symbol links should be typically rendered with "code voice", usually 18 | /// monospace. 19 | public struct SymbolLink: InlineMarkup { 20 | public var _data: _MarkupData 21 | 22 | init(_ raw: RawMarkup) throws { 23 | guard case .symbolLink = raw.data else { 24 | throw RawMarkup.Error.concreteConversionError(from: raw, to: SymbolLink.self) 25 | } 26 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 27 | self.init(_MarkupData(absoluteRaw)) 28 | } 29 | 30 | init(_ data: _MarkupData) { 31 | self._data = data 32 | } 33 | } 34 | 35 | // MARK: - Public API 36 | 37 | public extension SymbolLink { 38 | /// Create a symbol link with a destination. 39 | init(destination: String? = nil) { 40 | try! self.init(.symbolLink(parsedRange: nil, destination: destination ?? "")) 41 | } 42 | 43 | /// The link's destination. 44 | var destination: String? { 45 | get { 46 | guard case let .symbolLink(destination) = _data.raw.markup.data else { 47 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 48 | } 49 | return destination 50 | } 51 | set { 52 | if let newDestination = newValue, newDestination.isEmpty { 53 | _data = _data.replacingSelf(.symbolLink(parsedRange: nil, destination: nil)) 54 | } else { 55 | _data = _data.replacingSelf(.symbolLink(parsedRange: nil, destination: newValue)) 56 | } 57 | } 58 | } 59 | 60 | // MARK: Visitation 61 | 62 | func accept(_ visitor: inout V) -> V.Result { 63 | return visitor.visitSymbolLink(self) 64 | } 65 | 66 | // MARK: PlainTextConvertibleMarkup 67 | 68 | var plainText: String { 69 | return "``\(destination ?? "")``" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:6.2 2 | /* 3 | This source file is part of the Swift.org open source project 4 | 5 | Copyright (c) 2021-2023 Apple Inc. and the Swift project authors 6 | Licensed under Apache License v2.0 with Runtime Library Exception 7 | 8 | See https://swift.org/LICENSE.txt for license information 9 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 10 | */ 11 | 12 | import PackageDescription 13 | import class Foundation.ProcessInfo 14 | 15 | let cmarkPackageName = ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil ? "swift-cmark" : "cmark" 16 | 17 | let package = Package( 18 | name: "swift-markdown", 19 | products: [ 20 | .library( 21 | name: "Markdown", 22 | targets: ["Markdown"]), 23 | ], 24 | targets: [ 25 | .target( 26 | name: "Markdown", 27 | dependencies: [ 28 | "CAtomic", 29 | .product(name: "cmark-gfm", package: cmarkPackageName), 30 | .product(name: "cmark-gfm-extensions", package: cmarkPackageName), 31 | ], 32 | exclude: [ 33 | "CMakeLists.txt" 34 | ], 35 | swiftSettings: [.unsafeFlags(["-Xcc", "-DCMARK_GFM_STATIC_DEFINE"], .when(platforms: [.windows]))] 36 | ), 37 | .testTarget( 38 | name: "MarkdownTests", 39 | dependencies: ["Markdown"], 40 | resources: [.process("Visitors/Everything.md")]), 41 | .target(name: "CAtomic"), 42 | ], 43 | swiftLanguageModes: [.v5] 44 | ) 45 | 46 | // If the `SWIFTCI_USE_LOCAL_DEPS` environment variable is set, 47 | // we're building in the Swift.org CI system alongside other projects in the Swift toolchain and 48 | // we can depend on local versions of our dependencies instead of fetching them remotely. 49 | if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { 50 | // Building standalone, so fetch all dependencies remotely. 51 | package.dependencies += [ 52 | .package(url: "https://github.com/swiftlang/swift-cmark.git", branch: "gfm"), 53 | ] 54 | 55 | // SwiftPM command plugins are only supported by Swift version 5.6 and later. 56 | #if swift(>=5.6) 57 | package.dependencies += [ 58 | .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.1.0"), 59 | ] 60 | #endif 61 | } else { 62 | // Building in the Swift.org CI system, so rely on local versions of dependencies. 63 | package.dependencies += [ 64 | .package(path: "../cmark"), 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /Sources/Markdown/Parser/LazySplitLines.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import Foundation 12 | 13 | /// A lazy sequence of split lines that keeps track of initial indentation and 14 | /// consecutive runs of empty lines. 15 | struct LazySplitLines: Sequence { 16 | struct Iterator: IteratorProtocol { 17 | /// The current running count of consecutive empty lines before the current iteration. 18 | private var precedingConsecutiveEmptyLineCount = 0 19 | 20 | /// The raw lines to be iterated. 21 | private let rawLines: [Substring] 22 | 23 | /// The current index of the iteration. 24 | private var index: Array.Index 25 | 26 | /// The source file or resource from which the line came, 27 | /// or `nil` if no such file or resource can be identified. 28 | private var source: URL? 29 | 30 | init(_ input: S, source: URL?) where S.SubSequence == Substring { 31 | self.rawLines = input.split(omittingEmptySubsequences: false, whereSeparator: \.isNewline) 32 | self.index = rawLines.startIndex 33 | self.source = source 34 | } 35 | 36 | mutating func next() -> TrimmedLine? { 37 | guard index != rawLines.endIndex else { 38 | return nil 39 | } 40 | 41 | let segment = TrimmedLine(rawLines[index], source: source, lineNumber: index + 1) 42 | 43 | index = rawLines.index(after: index) 44 | 45 | if segment.text.isEmpty { 46 | precedingConsecutiveEmptyLineCount += 1 47 | } else { 48 | precedingConsecutiveEmptyLineCount = 0 49 | } 50 | 51 | return segment 52 | } 53 | } 54 | 55 | /// The input to be lazily split on newlines. 56 | private let input: Substring 57 | 58 | /// The source file or resource from which the line came, 59 | /// or `nil` if no such file or resource can be identified. 60 | private let source: URL? 61 | 62 | init(_ input: Substring, source: URL?) { 63 | self.input = input 64 | self.source = source 65 | } 66 | 67 | func makeIterator() -> Iterator { 68 | return Iterator(input, source: source) 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Base/HierarchyTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | final class HierarchyTests: XCTestCase { 15 | /// Tests that the actual root element is returned 16 | func testRoot() { 17 | let document = Document(parsing: "*OK*") 18 | let leaf = document 19 | .child(at: 0)! 20 | .child(at: 0)! 21 | .child(at: 0) as! Text 22 | XCTAssertTrue(leaf.root.isIdentical(to: document)) 23 | } 24 | 25 | /// Tests that `root` returns `self` if an element is itself a root. 26 | func testRootSelf() { 27 | let paragraph = Paragraph() 28 | XCTAssert(paragraph.root.isIdentical(to: paragraph)) 29 | } 30 | 31 | /// Tests that the spine of the tree is updated when changing a leaf. 32 | func testTreeAfterLeafChange() { 33 | let paragraph = Paragraph(Strong(Text("OK"))) 34 | let leaf = paragraph.child(at: 0)!.child(at: 0) as! Text 35 | var newLeaf = leaf 36 | newLeaf.string = "Turned over!" 37 | 38 | XCTAssertFalse(newLeaf.parent!.isIdentical(to: leaf.parent!)) 39 | XCTAssertFalse(newLeaf.parent!.parent!.isIdentical(to: leaf.parent!.parent!)) 40 | 41 | let expectedDump = """ 42 | Paragraph 43 | └─ Strong 44 | └─ Text "Turned over!" 45 | """ 46 | XCTAssertEqual(expectedDump, newLeaf.root.debugDescription()) 47 | } 48 | 49 | func testTreeAfterMiddleChange() { 50 | let paragraph = Paragraph(Strong(Text("Turned over!"))) 51 | let middle = paragraph.child(at: 0) as! Strong 52 | let leaf = paragraph.child(at: 0)!.child(at: 0) as! Text 53 | 54 | var newParagraph = paragraph 55 | newParagraph.replaceChildrenInRange(middle.indexInParent..(inversion: .prefixedNo, exclusivity: .chooseLast, help: "Print internal unique identifiers for each element") 26 | var uniqueIdentifiers: Bool = false 27 | 28 | @Flag(inversion: .prefixedNo, exclusivity: .chooseLast, help: "Parse block directives") 29 | var parseBlockDirectives: Bool = false 30 | 31 | @Flag(inversion: .prefixedNo, exclusivity: .chooseLast, help: "Parse a minimal set of Doxygen commands (requires --parse-block-directives)") 32 | var experimentalParseDoxygenCommands: Bool = false 33 | 34 | func run() throws { 35 | var parseOptions = ParseOptions() 36 | if parseBlockDirectives { 37 | parseOptions.insert(.parseBlockDirectives) 38 | } 39 | if experimentalParseDoxygenCommands { 40 | parseOptions.insert(.parseMinimalDoxygen) 41 | } 42 | 43 | let document: Document 44 | if let inputFilePath = inputFilePath { 45 | (_, document) = try MarkdownCommand.parseFile(at: inputFilePath, options: parseOptions) 46 | } else { 47 | (_, document) = try MarkdownCommand.parseStandardInput(options: parseOptions) 48 | } 49 | var dumpOptions = MarkupDumpOptions() 50 | if sourceLocations { 51 | dumpOptions.insert(.printSourceLocations) 52 | } 53 | if uniqueIdentifiers { 54 | dumpOptions.insert(.printUniqueIdentifiers) 55 | } 56 | print(document.debugDescription(options: dumpOptions)) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Tables/TableCellContainer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A container of ``Table/Cell`` elements. 12 | public protocol TableCellContainer: Markup, ExpressibleByArrayLiteral { 13 | /// Create a row from cells. 14 | /// 15 | /// - parameter cells: A sequence of ``Table/Cell`` elements from which to make this row. 16 | init(_ cells: Cells) where Cells.Element == Table.Cell 17 | } 18 | 19 | // MARK: - Public API 20 | 21 | public extension TableCellContainer { 22 | /// Create a row from one cell. 23 | /// 24 | /// - parameter cell: The one cell comprising the row. 25 | init(_ cell: Table.Cell) { 26 | self.init(CollectionOfOne(cell)) 27 | } 28 | 29 | /// Create a row from cells. 30 | /// 31 | /// - parameter cells: A sequence of ``Table/Cell`` elements from which to make this row. 32 | init(_ cells: Table.Cell...) { 33 | self.init(cells) 34 | } 35 | 36 | init(arrayLiteral elements: Table.Cell...) { 37 | self.init(elements) 38 | } 39 | 40 | /// The cells of the row. 41 | /// 42 | /// - Precondition: All children of a ``TableCellContainer`` must be a `Table.Cell`. 43 | var cells: LazyMapSequence { 44 | return children.lazy.map { $0 as! Table.Cell } 45 | } 46 | 47 | /// Replace all cells with a sequence of cells. 48 | /// 49 | /// - parameter newCells: A sequence of ``Table/Cell`` elements that will replace all of the cells in this row. 50 | mutating func setCells(_ newCells: Cells) where Cells.Element == Table.Cell { 51 | replaceCellsInRange(0..(_ range: Range, with incomingCells: Cells) where Cells.Element == Table.Cell { 56 | var rawChildren = raw.markup.copyChildren() 57 | rawChildren.replaceSubrange(range, with: incomingCells.map { $0.raw.markup }) 58 | let newRaw = raw.markup.withChildren(rawChildren) 59 | _data = _data.replacingSelf(newRaw) 60 | } 61 | 62 | /// Append a cell to the row. 63 | /// 64 | /// - parameter cell: The cell to append to the row. 65 | mutating func appendCell(_ cell: Table.Cell) { 66 | replaceCellsInRange(childCount.. 70 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/ListItem.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// A checkbox that can represent an on/off state. 12 | public enum Checkbox: Sendable { 13 | /// The checkbox is checked, representing an "on", "true", or "incomplete" state. 14 | case checked 15 | /// The checkbox is unchecked, representing an "off", "false", or "incomplete" state. 16 | case unchecked 17 | } 18 | 19 | /// A list item in an ordered or unordered list. 20 | public struct ListItem: BlockContainer { 21 | public var _data: _MarkupData 22 | init(_ raw: RawMarkup) throws { 23 | guard case .listItem = raw.data else { 24 | throw RawMarkup.Error.concreteConversionError(from: raw, to: ListItem.self) 25 | } 26 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 27 | self.init(_MarkupData(absoluteRaw)) 28 | } 29 | 30 | init(_ data: _MarkupData) { 31 | self._data = data 32 | } 33 | } 34 | 35 | // MARK: - Public API 36 | 37 | public extension ListItem { 38 | /// Create a list item. 39 | /// - Parameter checkbox: An optional ``Checkbox`` for the list item. 40 | /// - Parameter children: The child block elements of the list item. 41 | init(checkbox: Checkbox? = .none, _ children: BlockMarkup...) { 42 | try! self.init(.listItem(checkbox: checkbox, parsedRange: nil, children.map { $0.raw.markup })) 43 | } 44 | 45 | /// Create a list item. 46 | /// - Parameter checkbox: An optional ``Checkbox`` for the list item. 47 | /// - Parameter children: The child block elements of the list item. 48 | init(checkbox: Checkbox? = .none, _ children: Children) where Children.Element == BlockMarkup { 49 | try! self.init(.listItem(checkbox: checkbox, parsedRange: nil, children.map { $0.raw.markup })) 50 | } 51 | 52 | /// An optional ``Checkbox`` for the list item, which can indicate completion of a task, or some other off/on information. 53 | var checkbox: Checkbox? { 54 | get { 55 | guard case let .listItem(checkbox) = _data.raw.markup.data else { 56 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 57 | } 58 | return checkbox 59 | } 60 | set { 61 | _data = _data.replacingSelf(.listItem(checkbox: newValue, parsedRange: nil, _data.raw.markup.copyChildren())) 62 | } 63 | } 64 | 65 | // MARK: Visitation 66 | 67 | func accept(_ visitor: inout V) -> V.Result { 68 | return visitor.visitListItem(self) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/Markdown/Markdown.docc/Parsing-Building-and-Modifying Markup-Trees.md: -------------------------------------------------------------------------------- 1 | # Parsing, Building, and Modifying Markup Trees 2 | 3 | Get started with Swift-Markdown's markup trees. 4 | 5 | ## Parsing 6 | 7 | To create a new ``Document`` by parsing markdown content, use Document's ``Document/init(parsing:options:)`` initializer, supplying a `String` or `URL`: 8 | 9 | ```swift 10 | import Markdown 11 | 12 | let source = "This is a markup *document*." 13 | let document = Document(parsing: source) 14 | print(document.debugDescription()) 15 | // Document 16 | // └─ Paragraph 17 | // ├─ Text "This is a markup " 18 | // ├─ Emphasis 19 | // │ └─ Text "document" 20 | // └─ Text "." 21 | ``` 22 | 23 | Parsing text is just one way to build a tree of ``Markup`` elements. You can also build them yourself declaratively. 24 | 25 | ## Building Markup Trees 26 | 27 | You can build trees using initializers for the various element types provided. 28 | 29 | ```swift 30 | import Markdown 31 | 32 | let document = Document( 33 | Paragraph( 34 | Text("This is a "), 35 | Emphasis( 36 | Text("paragraph.")))) 37 | ``` 38 | 39 | This would be equivalent to parsing `"This is a *paragraph.*"` but allows you to programmatically insert content from other data sources into individual elements. 40 | 41 | ## Modifying Markup Trees with Persistence 42 | 43 | Swift Markdown uses a [persistent](https://en.wikipedia.org/wiki/Persistent_data_structure) tree for its backing storage, providing effectively immutable, copy-on-write value types that only copy the substructure necessary to create a unique root without affecting the previous version of the tree. 44 | 45 | ### Modifying Elements Directly 46 | 47 | If you just need to make a quick change, you can modify an element anywhere in a tree, and Swift Markdown will create copies of substructure that cannot be shared. 48 | 49 | ```swift 50 | import Markdown 51 | 52 | let source = "This is *emphasized.*" 53 | let document = Document(parsing: source) 54 | print(document.debugDescription()) 55 | // Document 56 | // └─ Paragraph 57 | // ├─ Text "This is " 58 | // └─ Emphasis 59 | // └─ Text "emphasized." 60 | 61 | var text = document.child(through: 62 | 0, // Paragraph 63 | 1, // Emphasis 64 | 0) as! Text // Text 65 | 66 | text.string = "really emphasized!" 67 | print(text.root.debugDescription()) 68 | // Document 69 | // └─ Paragraph 70 | // ├─ Text "This is " 71 | // └─ Emphasis 72 | // └─ Text "really emphasized!" 73 | 74 | // The original document is unchanged: 75 | 76 | print(document.debugDescription()) 77 | // Document 78 | // └─ Paragraph 79 | // ├─ Text "This is " 80 | // └─ Emphasis 81 | // └─ Text "emphasized." 82 | ``` 83 | 84 | If you find yourself needing to systematically change many parts of a tree, or even provide a complete transformation into something else, maybe the familiar [Visitor Pattern](https://en.wikipedia.org/wiki/Visitor_pattern) is what you want. 85 | 86 | 87 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenParameter.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2023 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import Foundation 12 | 13 | /// A parsed Doxygen `\param` command. 14 | /// 15 | /// The Doxygen support in Swift-Markdown parses `\param` commands of the form 16 | /// `\param name description`, where `description` extends until the next blank line or the next 17 | /// parsed command. For example, the following input will return two `DoxygenParam` instances: 18 | /// 19 | /// ```markdown 20 | /// \param coordinate The coordinate used to center the transformation. 21 | /// \param matrix The transformation matrix that describes the transformation. 22 | /// For more information about transformation matrices, refer to the Transformation 23 | /// documentation. 24 | /// ``` 25 | public struct DoxygenParameter: BlockContainer { 26 | public var _data: _MarkupData 27 | 28 | init(_ raw: RawMarkup) throws { 29 | guard case .doxygenParam = raw.data else { 30 | throw RawMarkup.Error.concreteConversionError(from: raw, to: DoxygenParameter.self) 31 | } 32 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 33 | self.init(_MarkupData(absoluteRaw)) 34 | } 35 | 36 | init(_ data: _MarkupData) { 37 | self._data = data 38 | } 39 | 40 | public func accept(_ visitor: inout V) -> V.Result { 41 | return visitor.visitDoxygenParameter(self) 42 | } 43 | } 44 | 45 | public extension DoxygenParameter { 46 | /// Create a new Doxygen parameter definition. 47 | /// 48 | /// - Parameter name: The name of the parameter being described. 49 | /// - Parameter children: Block child elements. 50 | init(name: String, children: Children) where Children.Element == BlockMarkup { 51 | try! self.init(.doxygenParam(name: name, parsedRange: nil, children.map({ $0.raw.markup }))) 52 | } 53 | 54 | /// Create a new Doxygen parameter definition. 55 | /// 56 | /// - Parameter name: The name of the parameter being described. 57 | /// - Parameter children: Block child elements. 58 | init(name: String, children: BlockMarkup...) { 59 | self.init(name: name, children: children) 60 | } 61 | 62 | /// The name of the parameter being described. 63 | var name: String { 64 | get { 65 | guard case let .doxygenParam(name) = _data.raw.markup.data else { 66 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 67 | } 68 | return name 69 | } 70 | set { 71 | _data = _data.replacingSelf(.doxygenParam( 72 | name: newValue, 73 | parsedRange: nil, 74 | _data.raw.markup.copyChildren() 75 | )) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Tables/TableBody.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | extension Table { 12 | /// The body of a table consisting of zero or more ``Table/Row`` elements. 13 | public struct Body : Markup { 14 | public var _data: _MarkupData 15 | 16 | init(_ data: _MarkupData) { 17 | self._data = data 18 | } 19 | 20 | init(_ raw: RawMarkup) throws { 21 | guard case .tableBody = raw.data else { 22 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Table.Body.self) 23 | } 24 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 25 | self.init(_MarkupData(absoluteRaw)) 26 | } 27 | 28 | /// The maximum number of columns seen in all rows. 29 | var maxColumnCount: Int { 30 | return children.reduce(0) { (result, row) -> Int in 31 | return max(result, row.childCount) 32 | } 33 | } 34 | } 35 | } 36 | 37 | // MARK: - Public API 38 | 39 | public extension Table.Body { 40 | /// Create a table body from a sequence of ``Table/Row`` elements. 41 | init(_ rows: Rows) where Rows.Element == Table.Row { 42 | try! self.init(RawMarkup.tableBody(parsedRange: nil, rows: rows.map { $0.raw.markup })) 43 | } 44 | 45 | /// Create a table body from a sequence of ``Table/Row`` elements. 46 | init(_ rows: Table.Row...) { 47 | self.init(rows) 48 | } 49 | 50 | /// The rows of the body. 51 | /// 52 | /// - Precondition: All children of a `ListItemContainer` 53 | /// must be a `ListItem`. 54 | var rows: LazyMapSequence { 55 | return children.lazy.map { $0 as! Table.Row } 56 | } 57 | 58 | /// Replace all list items with a sequence of items. 59 | mutating func setRows(_ newRows: Rows) where Rows.Element == Table.Row { 60 | replaceRowsInRange(0..(_ range: Range, with incomingRows: Rows) where Rows.Element == Table.Row { 65 | var rawChildren = raw.markup.copyChildren() 66 | rawChildren.replaceSubrange(range, with: incomingRows.map { $0.raw.markup }) 67 | let newRaw = raw.markup.withChildren(rawChildren) 68 | _data = _data.replacingSelf(newRaw) 69 | } 70 | 71 | /// Append a row to the list. 72 | mutating func appendRow(_ row: Table.Row) { 73 | replaceRowsInRange(childCount..(_ visitor: inout V) -> V.Result where V : MarkupVisitor { 79 | return visitor.visitTableBody(self) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Base/RawMarkupTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | final class RawMarkupTests: XCTestCase { 15 | func testHasSameStructureAs() { 16 | do { // Identity match 17 | let document = RawMarkup.document(parsedRange: nil, []) 18 | XCTAssert(document.hasSameStructure(as: document)) 19 | } 20 | 21 | do { // Empty match 22 | XCTAssert(RawMarkup.document(parsedRange: nil, []).hasSameStructure(as: .document(parsedRange: nil, []))) 23 | } 24 | 25 | do { // Same child count, but different structure 26 | let document1 = RawMarkup.document(parsedRange: nil, [.paragraph(parsedRange: nil, [])]) 27 | let document2 = RawMarkup.document(parsedRange: nil, [.thematicBreak(parsedRange: nil)]) 28 | XCTAssertFalse(document1.hasSameStructure(as: document2)) 29 | } 30 | 31 | do { // Different child count 32 | let document1 = RawMarkup.document(parsedRange: nil, [.paragraph(parsedRange: nil, [])]) 33 | let document2 = RawMarkup.document(parsedRange: nil, [.paragraph(parsedRange: nil, []), .thematicBreak(parsedRange: nil)]) 34 | XCTAssertFalse(document1.hasSameStructure(as: document2)) 35 | } 36 | 37 | do { // Same child count, different structure, nested 38 | let document1 = RawMarkup.document(parsedRange: nil, [ 39 | .paragraph(parsedRange: nil, [ 40 | .text(parsedRange: nil, string: "Hello") 41 | ]), 42 | .paragraph(parsedRange: nil, [ 43 | .text(parsedRange: nil, string: "World") 44 | ]), 45 | ]) 46 | let document2 = RawMarkup.document(parsedRange: nil, [ 47 | .paragraph(parsedRange: nil, [ 48 | .text(parsedRange: nil, string: "Hello"), 49 | ]), 50 | .paragraph(parsedRange: nil, [ 51 | .emphasis(parsedRange: nil, [ 52 | .text(parsedRange: nil, string: "World"), 53 | ]), 54 | ]), 55 | ]) 56 | XCTAssertFalse(document1.hasSameStructure(as: document2)) 57 | } 58 | } 59 | 60 | /// When an element changes a child, unchanged children should use the same `RawMarkup` as before. 61 | func testSharing() { 62 | let originalRoot = Document( 63 | Paragraph(Text("ChangeMe")), 64 | Paragraph(Text("Unchanged"))) 65 | 66 | let firstText = originalRoot.child(through: [ 67 | 0, // Paragraph 68 | 0, // Text 69 | ]) as! Text 70 | 71 | var newText = firstText 72 | newText.string = "Changed" 73 | let newRoot = newText.root 74 | 75 | XCTAssertFalse(originalRoot.child(at: 0)!.raw.markup === newRoot.child(at: 0)!.raw.markup) 76 | XCTAssertTrue(originalRoot.child(at: 1)!.raw.markup === newRoot.child(at: 1)!.raw.markup) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Base/MarkupIdentifierTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | final class MarkupIdentifierTests: XCTestCase { 15 | func totalElementsInTree(height h: Int, width N: Int) -> Int { 16 | let total = 17 | (pow(Double(N), Double(h + 1)) - 1) 18 | / 19 | Double(N - 1) 20 | return Int(total) 21 | } 22 | 23 | func buildCustomBlock(height: Int, width: Int) -> CustomBlock { 24 | guard height > 0 else { 25 | return CustomBlock() 26 | } 27 | return CustomBlock(Array(repeating: buildCustomBlock(height: height - 1, width: width), count: width)) 28 | } 29 | 30 | /// No two children should have the same child identifier. 31 | func testChildIDsAreUnique() { 32 | let height = 5 33 | let width = 5 34 | 35 | let customBlock = buildCustomBlock(height: height, width: width) 36 | 37 | struct IDCounter: MarkupWalker { 38 | var id = 0 39 | 40 | mutating func defaultVisit(_ markup: Markup) { 41 | XCTAssertEqual(id, markup._data.id.childId) 42 | id += 1 43 | descendInto(markup) 44 | } 45 | } 46 | 47 | var counter = IDCounter() 48 | counter.visit(customBlock) 49 | XCTAssertEqual(totalElementsInTree(height: height, width: width), counter.id) 50 | } 51 | 52 | /// The very first child id shall be 1 greater than that of its parent. 53 | func testFirstChildIdentifier() { 54 | func checkFirstChildOf(_ markup: Markup, expectedId: Int) { 55 | guard let firstChild = markup.child(at: 0) else { 56 | return 57 | } 58 | XCTAssertEqual(expectedId, firstChild.raw.metadata.id.childId) 59 | // As we descend depth-first, each first child identifier shall be one more than the last. 60 | checkFirstChildOf(firstChild, expectedId: expectedId + 1) 61 | } 62 | 63 | checkFirstChildOf(buildCustomBlock(height: 100, width: 1), expectedId: 1) 64 | } 65 | 66 | func testNextSiblingIdentifier() { 67 | let height = 2 68 | let width = 100 69 | let customBlock = buildCustomBlock(height: height, width: width) 70 | 71 | var id = 1 72 | for child in customBlock.children { 73 | // Every branch in the tree should use 1 + 100 identifiers. 74 | XCTAssertEqual(id, child.raw.metadata.id.childId) 75 | id += width + 1 76 | } 77 | } 78 | 79 | func testPreviousSiblingIdentifier() { 80 | let height = 2 81 | let width = 100 82 | let customBlock = buildCustomBlock(height: height, width: width) 83 | 84 | var id = totalElementsInTree(height: height, width: width) 85 | for child in customBlock.children.reversed() { 86 | // Every branch in the tree should use 1 + 100 identifiers. 87 | XCTAssertEqual(id, child.raw.metadata.id.childId) 88 | id -= width + 1 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Tests/MarkdownTests/Inline Nodes/LinkTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021-2023 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import XCTest 12 | @testable import Markdown 13 | 14 | class LinkTests: XCTestCase { 15 | func testLinkDestination() { 16 | let destination = "destination" 17 | let link = Link(destination: destination) 18 | XCTAssertEqual(destination, link.destination) 19 | XCTAssertEqual(0, link.childCount) 20 | 21 | let newDestination = "newdestination" 22 | var newLink = link 23 | newLink.destination = newDestination 24 | XCTAssertEqual(newDestination, newLink.destination) 25 | XCTAssertFalse(link.isIdentical(to: newLink)) 26 | } 27 | 28 | func testLinkFromSequence() { 29 | let children = [Text("Hello, world!")] 30 | let link = Link(destination: "destination", children) 31 | let expectedDump = """ 32 | Link destination: "destination" 33 | └─ Text "Hello, world!" 34 | """ 35 | XCTAssertEqual(expectedDump, link.debugDescription()) 36 | } 37 | 38 | func testAutoLink() { 39 | let children = [Text("example.com")] 40 | var link = Link(destination: "example.com", children) 41 | let expectedDump = """ 42 | Link destination: "example.com" 43 | └─ Text "example.com" 44 | """ 45 | XCTAssertEqual(expectedDump, link.debugDescription()) 46 | XCTAssertTrue(link.isAutolink) 47 | 48 | link.destination = "test.example.com" 49 | XCTAssertFalse(link.isAutolink) 50 | } 51 | 52 | func testTitleLink() throws { 53 | let markdown = #""" 54 | [Example](example.com "The example title") 55 | [Example2](example2.com) 56 | [Example3]() 57 | """# 58 | 59 | let document = Document(parsing: markdown) 60 | XCTAssertEqual(document.childCount, 1) 61 | let paragraph = try XCTUnwrap(document.child(at: 0) as? Paragraph) 62 | XCTAssertEqual(paragraph.childCount, 5) 63 | 64 | XCTAssertTrue(paragraph.child(at: 1) is SoftBreak) 65 | XCTAssertTrue(paragraph.child(at: 3) is SoftBreak) 66 | let linkWithTitle = try XCTUnwrap(paragraph.child(at: 0) as? Link) 67 | let linkWithoutTitle = try XCTUnwrap(paragraph.child(at: 2) as? Link) 68 | let linkWithoutDestination = try XCTUnwrap(paragraph.child(at: 4) as? Link) 69 | 70 | XCTAssertEqual(try XCTUnwrap(linkWithTitle.child(at: 0) as? Text).string, "Example") 71 | XCTAssertEqual(linkWithTitle.destination, "example.com") 72 | XCTAssertEqual(linkWithTitle.title, "The example title") 73 | 74 | XCTAssertEqual(try XCTUnwrap(linkWithoutTitle.child(at: 0) as? Text).string, "Example2") 75 | XCTAssertEqual(linkWithoutTitle.destination, "example2.com") 76 | XCTAssertEqual(linkWithoutTitle.title, nil) 77 | 78 | XCTAssertEqual(try XCTUnwrap(linkWithoutDestination.child(at: 0) as? Text).string, "Example3") 79 | XCTAssertEqual(linkWithoutDestination.destination, nil) 80 | XCTAssertEqual(linkWithoutDestination.title, nil) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/Markdown/Base/Document.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import Foundation 12 | 13 | /// A markup element representing the top level of a whole document. 14 | /// 15 | /// - note: Although this could be considered a block element that can contain block elements, a `Document` itself can't be the child of any other markup, so it is not considered a block element. 16 | public struct Document: Markup, BasicBlockContainer { 17 | public var _data: _MarkupData 18 | 19 | init(_ raw: RawMarkup) throws { 20 | guard case .document = raw.data else { 21 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Document.self) 22 | } 23 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 24 | self.init(_MarkupData(absoluteRaw)) 25 | 26 | } 27 | 28 | init(_ data: _MarkupData) { 29 | self._data = data 30 | } 31 | } 32 | 33 | // MARK: - Public API 34 | 35 | public extension Document { 36 | // MARK: Primitive 37 | 38 | /// Parse a string into a `Document`. 39 | /// 40 | /// - parameter string: the input Markdown text to parse. 41 | /// - parameter options: options for parsing Markdown text. 42 | /// - parameter source: an explicit source URL from which the input `string` came for marking source locations. 43 | /// This need not be a file URL. 44 | init(parsing string: String, source: URL? = nil, options: ParseOptions = []) { 45 | if options.contains(.parseBlockDirectives) { 46 | self = BlockDirectiveParser.parse(string, source: source, 47 | options: options) 48 | } else { 49 | self = MarkupParser.parseString(string, source: source, options: options) 50 | } 51 | } 52 | 53 | /// Parse a file's contents into a `Document`. 54 | /// 55 | /// - parameter file: a file URL from which to load Markdown text to parse. 56 | /// - parameter options: options for parsing Markdown text. 57 | init(parsing file: URL, options: ParseOptions = []) throws { 58 | let string = try String(contentsOf: file) 59 | if options.contains(.parseBlockDirectives) { 60 | self = BlockDirectiveParser.parse(string, source: file, 61 | options: options) 62 | } else { 63 | self = MarkupParser.parseString(string, source: file, options: options) 64 | } 65 | } 66 | 67 | /// Create a document from a sequence of block markup elements. 68 | init(_ children: some Sequence) { 69 | self.init(children, inheritSourceRange: false) 70 | } 71 | 72 | init(_ children: some Sequence, inheritSourceRange: Bool) { 73 | let rawChildren = children.map { $0.raw.markup } 74 | let parsedRange = inheritSourceRange ? rawChildren.parsedRange : nil 75 | try! self.init(.document(parsedRange: parsedRange, rawChildren)) 76 | } 77 | 78 | // MARK: Visitation 79 | 80 | func accept(_ visitor: inout V) -> V.Result { 81 | return visitor.visitDocument(self) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Sources/Markdown/Infrastructure/SourceLocation.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | import Foundation 12 | 13 | /// A location in a source file. 14 | public struct SourceLocation: Hashable, CustomStringConvertible, Comparable, Sendable { 15 | public static func < (lhs: SourceLocation, rhs: SourceLocation) -> Bool { 16 | if lhs.line < rhs.line { 17 | return true 18 | } else if lhs.line == rhs.line { 19 | return lhs.column < rhs.column 20 | } else { 21 | return false 22 | } 23 | } 24 | 25 | /// The line number of the location. 26 | public var line: Int 27 | 28 | /// The number of bytes in UTF-8 encoding from the start of the line to the character at this source location. 29 | public var column: Int 30 | 31 | /// The source file for which this location applies, if it came from an accessible location. 32 | public var source: URL? 33 | 34 | /// Create a source location with line, column, and optional source to which the location applies. 35 | /// 36 | /// - parameter line: The line number of the location, starting with 1. 37 | /// - parameter column: The column of the location, starting with 1. 38 | /// - parameter source: The URL in which the location resides, or `nil` if there is not a specific 39 | /// file or resource that needs to be identified. 40 | public init(line: Int, column: Int, source: URL?) { 41 | self.line = line 42 | self.column = column 43 | self.source = source 44 | } 45 | 46 | public var description: String { 47 | let path = source.map { 48 | $0.path.isEmpty 49 | ? "" 50 | : "\($0.path):" 51 | } ?? "" 52 | return "\(path)\(line):\(column)" 53 | } 54 | } 55 | 56 | extension Range { 57 | /// Widen this range to contain another. 58 | mutating func widen(toFit other: Self) { 59 | self = Swift.min(self.lowerBound, other.lowerBound).. 65 | 66 | extension SourceRange { 67 | @available(*, deprecated, message: "Use lowerBound.. String { 94 | let path = includePath ? lowerBound.source?.path ?? "" : "" 95 | var result = "" 96 | if !path.isEmpty { 97 | result += "\(path):" 98 | } 99 | result += "\(lowerBound)" 100 | if lowerBound != upperBound { 101 | result += "-\(upperBound)" 102 | } 103 | return result 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Sources/Markdown/Inline Nodes/Inline Containers/Image.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | /// An inline image reference. 12 | public struct Image: InlineMarkup, InlineContainer { 13 | public var _data: _MarkupData 14 | 15 | init(_ data: _MarkupData) { 16 | self._data = data 17 | } 18 | 19 | init(_ raw: RawMarkup) throws { 20 | guard case .image = raw.data else { 21 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Image.self) 22 | } 23 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 24 | self.init(_MarkupData(absoluteRaw)) 25 | } 26 | } 27 | 28 | // MARK: - Public API 29 | 30 | public extension Image { 31 | /// Create an image from a source and zero or more child inline elements. 32 | init(source: String? = nil, title: String? = nil, _ children: Children) where Children.Element == RecurringInlineMarkup { 33 | let titleToUse: String? 34 | if let t = title, t.isEmpty { 35 | titleToUse = nil 36 | } else { 37 | titleToUse = title 38 | } 39 | 40 | let sourceToUse: String? 41 | if let s = source, s.isEmpty { 42 | sourceToUse = nil 43 | } else { 44 | sourceToUse = source 45 | } 46 | 47 | try! self.init(.image(source: sourceToUse, title: titleToUse, parsedRange: nil, children.map { $0.raw.markup })) 48 | } 49 | 50 | /// Create an image from a source and zero or more child inline elements. 51 | init(source: String? = nil, title: String? = nil, _ children: RecurringInlineMarkup...) { 52 | self.init(source: source, title: title, children) 53 | } 54 | 55 | /// The image's source. 56 | var source: String? { 57 | get { 58 | guard case let .image(source, _) = _data.raw.markup.data else { 59 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 60 | } 61 | return source 62 | } 63 | set { 64 | guard newValue != source else { 65 | return 66 | } 67 | if let s = newValue, s.isEmpty { 68 | _data = _data.replacingSelf(.image(source: nil, title: title, parsedRange: nil, _data.raw.markup.copyChildren())) 69 | } else { 70 | _data = _data.replacingSelf(.image(source: newValue, title: title, parsedRange: nil, _data.raw.markup.copyChildren())) 71 | } 72 | } 73 | } 74 | 75 | /// The image's title. 76 | var title: String? { 77 | get { 78 | guard case let .image(_, title) = _data.raw.markup.data else { 79 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 80 | } 81 | return title 82 | } 83 | set { 84 | guard newValue != title else { 85 | return 86 | } 87 | 88 | if let t = newValue, t.isEmpty { 89 | _data = _data.replacingSelf(.image(source: source, title: nil, parsedRange: nil, _data.raw.markup.copyChildren())) 90 | } else { 91 | _data = _data.replacingSelf(.image(source: source, title: newValue, parsedRange: nil, _data.raw.markup.copyChildren())) 92 | } 93 | } 94 | } 95 | 96 | // MARK: Visitation 97 | 98 | func accept(_ visitor: inout V) -> V.Result { 99 | return visitor.visitImage(self) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Sources/Markdown/Block Nodes/Tables/TableCell.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift.org open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | 11 | extension Table { 12 | /// A cell in a table. 13 | public struct Cell: Markup, BasicInlineContainer { 14 | public var _data: _MarkupData 15 | init(_ raw: RawMarkup) throws { 16 | guard case .tableCell = raw.data else { 17 | throw RawMarkup.Error.concreteConversionError(from: raw, to: Table.Cell.self) 18 | } 19 | let absoluteRaw = AbsoluteRawMarkup(markup: raw, metadata: MarkupMetadata(id: .newRoot(), indexInParent: 0)) 20 | self.init(_MarkupData(absoluteRaw)) 21 | } 22 | 23 | init(_ data: _MarkupData) { 24 | self._data = data 25 | } 26 | } 27 | } 28 | 29 | // MARK: - Public API 30 | 31 | public extension Table.Cell { 32 | 33 | /// The number of columns this cell spans over. 34 | /// 35 | /// A normal, non-spanning table cell has a `colspan` of 1. A value greater than one indicates 36 | /// that this cell has expanded to cover up that number of columns. A value of zero means that 37 | /// this cell is being covered up by a previous cell in the same row. 38 | var colspan: UInt { 39 | get { 40 | guard case let .tableCell(colspan, _) = _data.raw.markup.data else { 41 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 42 | } 43 | return colspan 44 | } 45 | set { 46 | _data = _data.replacingSelf(.tableCell(parsedRange: nil, colspan: newValue, rowspan: rowspan, _data.raw.markup.copyChildren())) 47 | } 48 | } 49 | 50 | /// The number of rows this cell spans over. 51 | /// 52 | /// A normal, non-spanning table cell has a `rowspan` of 1. A value greater than one indicates 53 | /// that this cell has expanded to cover up that number of rows. A value of zero means that 54 | /// this cell is being covered up by another cell in a row above it. 55 | var rowspan: UInt { 56 | get { 57 | guard case let .tableCell(_, rowspan) = _data.raw.markup.data else { 58 | fatalError("\(self) markup wrapped unexpected \(_data.raw)") 59 | } 60 | return rowspan 61 | } 62 | set { 63 | _data = _data.replacingSelf(.tableCell(parsedRange: nil, colspan: colspan, rowspan: newValue, _data.raw.markup.copyChildren())) 64 | } 65 | } 66 | 67 | // MARK: BasicInlineContainer 68 | 69 | init(_ children: some Sequence) { 70 | self.init(colspan: 1, rowspan: 1, children) 71 | } 72 | 73 | init(_ children: some Sequence, inheritSourceRange: Bool) { 74 | self.init(colspan: 1, rowspan: 1, children, inheritSourceRange: inheritSourceRange) 75 | } 76 | 77 | init(colspan: UInt, rowspan: UInt, _ children: some Sequence) { 78 | self.init(colspan: colspan, rowspan: rowspan, children, inheritSourceRange: false) 79 | } 80 | 81 | init(colspan: UInt, rowspan: UInt, _ children: some Sequence, inheritSourceRange: Bool) { 82 | let rawChildren = children.map { $0.raw.markup } 83 | let parsedRange = inheritSourceRange ? rawChildren.parsedRange : nil 84 | try! self.init(.tableCell(parsedRange: parsedRange, colspan: colspan, rowspan: rowspan, rawChildren)) 85 | } 86 | 87 | // MARK: Visitation 88 | 89 | func accept(_ visitor: inout V) -> V.Result where V : MarkupVisitor { 90 | return visitor.visitTableCell(self) 91 | } 92 | } 93 | --------------------------------------------------------------------------------