将 markdown 的属性捕获到 array/dictionary in Swift

Capturing attributes of markdown into array/dictionary in Swift

我正在使用 Swift 框架将一些 Markdown 转换为 HTML。

我希望能够在大多数框架提供的正常默认元素之外创建适合我需要的自定义 Markdown 元素。

假设我有以下自定义 Markdown:

# My heading

This is normal text with a [link](/).

Below is my custom markdown element called `!file`:

[!file title="This is my title" icon="rocket"](file.txt)

如何将属性提取到数组或字典中,以便将它们转换为 HTML?

例如:

// from this: [!file title="This is my title" icon="rocket"](file.txt)

attributes = [
  "title" : "This is my title",
  "icon" : "rocket",
  "item" : "file.txt"
]

or from this: [!file](../files/docs/terms.pdf)

attributes = [
  "title" : "",
  "icon" : "",
  "item" : "../files/docs/terms.pdf"
]

我最初尝试使用 .split(" "),但由于 title="This is my title" 包含空格,因此它会在这些项目处拆分。

我想标题和图标是可选的,默认情况下是 nil

除了标准 iOS/macOS 用法之外,我还没有真正使用过 Swift,所以当仅依赖 Foundation 时,我有点不知所措。

您可以试试这个正则表达式:

\[!file(?:\s*title="([^"]*?)"\s*icon="([^"]*?)")?]\(([^)]+)\)

这里标题和图标属性是可选的

  • 第 1 组标题
  • 第 2 组用于图标
  • 项目第 3 组

演示:Regex101

如果我没有完全误解这一点,您需要一个正则表达式来匹配文本中的自定义 !file 元素并将结果转换为 swift 集合。

为此,我使用了带有命名组的正则表达式模式

let pattern = #"\[!file\s*(title="(?<title>.*)")?\s*(icon="(?<icon>.*)")?\]\((?<file>.*)\)"#

或者您可以使用@RizwanM.Tuman 的答案中的模式,这可能更有效,并且在使用命名组时看起来像这样

let pattern = #"\[!file(?:\s*title="(?<title>[^"]*?)"\s*icon="(?<icon>[^"]*?)")?]\((?<file>[^)]+)\)"#

那么结果的匹配和提取就是这样进行的

let regex = try NSRegularExpression(pattern: pattern, options: [])
let fullRange = NSRange(text.startIndex..<text.endIndex, in: text)

var components = [String: String]()
if let match = regex.firstMatch(in: text, options: [], range: fullRange) {
    for component in ["title", "icon", "file"] {
        let componentRange = match.range(withName: component)
        if componentRange.location != NSNotFound,
           let range = Range(componentRange, in: text)
        {
            components[component] = String(text[range])
        }
    }
}

这里假设只有一个自定义元素可以匹配,但如果有多个,则需要像这样遍历匹配项

var allMarkdowns = [[String: String]]()
regex.enumerateMatches(in: text, options: [], range: fullRange) { (match, _, _) in
    guard let match = match else { return }

    var components = [String: String]()
    for component in ["title", "icon", "file"] {
        let componentRange = match.range(withName: component)
        if componentRange.location != NSNotFound,
           let range = Range(componentRange, in: text) {
            components[component] = String(text[range])
        }
    }
    allMarkdowns.append(components)
}

一个例子

let text = """
# My heading
This is normal text with a [link](/).
Below is my custom markdown element called `!file`:

[!file title="This is my title" icon="rocket"](file.txt)
bla bla

[!file](../files/docs/terms.pdf)
"""

运行 第二个解决方案会产生

[["file": "file.txt", "title": "This is my title", "icon": "rocket"], ["file": "../files/docs/terms.pdf"]]