I am converting some Markdown into HTML using a Swift framework.
I want to be able to create custom Markdown elements that suit my needs outside of the normal default ones most frameworks provide.
Say I have the following custom 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)
How would I be able to extract the attributes into an array or dictionary so I can convert them into HTML?
For example:
// 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"
]
I originally tried using .split(" ")
but since the title="This is my title"
contains spaces then it splits at those items.
I imagine the title and the icon are optionals and are nil
by default.
I haven't really used Swift outside the standard iOS/macOS usage so when relying on only Foundation I'm a bit lost.
CodePudding user response:
You may try this regex:
\[!file(?:\s*title="([^"]*?)"\s*icon="([^"]*?)")?]\(([^)] )\)
Here title and icon attributes are optional
- group 1 for title
- group 2 is for icon
- group 3 for item
Demo: Regex101
CodePudding user response:
If I haven't misunderstood this completely you need a regex to match the custom !file element in a text and convert the result into a swift collection.
For this I used a regex pattern with named groups
let pattern = #"\[!file\s*(title="(?<title>.*)")?\s*(icon="(?<icon>.*)")?\]\((?<file>.*)\)"#
or you could use the pattern from the answer by @RizwanM.Tuman which might be more effective and looks like this when using named groups
let pattern = #"\[!file(?:\s*title="(?<title>[^"]*?)"\s*icon="(?<icon>[^"]*?)")?]\((?<file>[^)] )\)"#
Then the matching and extracting of the result would be done like this
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])
}
}
}
This assumes there is only one of this custom element to match but if you have several you need to iterate over the matches like this
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)
}
An example
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)
"""
Running the second solution on this would yield
[["file": "file.txt", "title": "This is my title", "icon": "rocket"], ["file": "../files/docs/terms.pdf"]]