Swift + Markdown: plain text is changed after reading and writing to file

Viewed 41

I'm using Apples own Markdown framework (https://github.com/apple/swift-markdown) and when I have a markdown file like the following:

## this is my heading 2
- this is some unordered list entry
- and another one
- and still anther one
    - and an indented one

When I read and write without any changes:

let data = FileManager.default.contents(atPath: "myfile.md")!
var document = Document(parsing: String(data: data, encoding: .utf8)!)
let url = URL(filePath: "myfile.md")
try document.format(options: .init()).data(using: .utf8)!.write(to: url)

the content changes:

## this is my heading 2

- this is some unordered list entry
- and another one
- and still anther one
  - and an indented one

i.e. a newline is printed after the heading and the indention of the unordered list is changed to two spaces instead of 4/tab. Is there a way to prevent/configure this?

1 Answers

No, you cannot configure either of these without creating your own fork of swift-markdown. For the extra newline, take a look at this code:

    mutating public func visitUnorderedList(_ unorderedList: UnorderedList) {
        if unorderedList.indexInParent > 0 && !(unorderedList.parent?.parent is ListItemContainer) {
            ensurePrecedingNewlineCount(atLeast: 2)
        }
        descendInto(unorderedList)
    }


    mutating public func visitOrderedList(_ orderedList: OrderedList) {
        if orderedList.indexInParent > 0 && !(orderedList.parent?.parent is ListItemContainer) {
            ensurePrecedingNewlineCount(atLeast: 2)
        }
        descendInto(orderedList)
    }

Observe the ensurePrecedingNewlineCount(atLeast: 2) call.

For the indentation of lists, take a look at this code from the swift-markdown repository:

            } else if element is UnorderedList {
                if unorderedListCount > 0 {
                    prefix += "  "
                }
                unorderedListCount += 1
            } else if element is OrderedList {
                if orderedListCount > 0 {
                    prefix += "   "
                }
                orderedListCount += 1
            } else if !(element is ListItem),
                let parentListItem = element.parent as? ListItem {
                /*
                 Align contents with list item markers.

                 Example, unordered lists:

                 - First line
                   Second line, aligned.

                 Example, ordered lists:

                 1. First line
                    Second line, aligned.
                 1000. First line
                       Second line, aligned.
                 */


                if parentListItem.parent is UnorderedList {
                    // Unordered list markers are of fixed length.
                    prefix += "  "
                } else if let numeralPrefix = numeralPrefix(for: parentListItem) {
                    prefix += String(repeating: " ", count: numeralPrefix.count)
                }

Note the hardcoded spaces to add to the prefix variable when elements are children of lists.


It's worth noting that at least the extra newline is required by the Markdown spec, and some parsers will not properly read the markdown unless there are two newlines between each logical "section" (paragraph, list, block quote, code block, etc.). I'm not sure whether the indentation is required by the spec, but that may also be why the team at Apple made that decision.

Also, one thing that may make this more understandable is that when you parse your markdown document, it represents the document in a tree structure like this:

// Document
// └─ Paragraph
//    ├─ Text "This is a markup "
//    ├─ Emphasis
//    │  └─ Text "document"
//    └─ Text "."

and so it has completely forgotten about your formatting. When you then write it back out, it generates Markdown text from this tree. So although you made no changes to the document in your code, it has been parsed and then the Markdown is regenerated, rather than keeping your original document in memory exactly as-is and modifying it on the fly.

Related