Swift 4.4
I want to create NSAttributedString using NSRegularExpression
~This two~ are *bold text* and different <textcolor> and ~strikethrough~ and _italic (word*)_ ~abc~
Look like this,

I can create and assign attributes but the problem is i don't know how to use NSRegularExpression but i tried many logic with the help of rayenderlich article.
Some logics that i tried so far.
(1) "(^|\\s|\\b)*(\\S).*(\\S)*($|\\s|\\b)"
(2) "(\\~(.*?)\\~)"
(3) "\\sand\\b"
(4) "(?:^|\\s|$)#[\\p{L}0-9_]*"
(5) "(?:^\\s+)|(?:\\s+$)"
I need just one pattern of bold or italic or strikethrough
Case 1:
let string = "~This two~ are *bold text* and different <textcolor> and ~strikethrough~ and _italic (word*)_ ~abc~"
let pattern = "((^|)~(.*?)(\\S+)~($|\\s))"
Output is bellow,
Pattern: ((^|)~(.*?)(\S+)~($|\s))
~This two~
~strikethrough~
~abc~
Case 2:
let string = "~This two~ are *bold text* and different <textcolor> and ~strikethrough~ and _italic (word*)_ ~abc~"
let pattern = "((^|)~(.?)(\\S+)~($|\\s))" //here '*' removed
Output is bellow,
Pattern: ((^|)~(.?)(\S+)~($|\s))
~strikethrough~
~abc~
Thanks in advance.
UPDATE 2
i found the answer,
This code should work:
let string = "~This two~ are *bold text* and different <textcolor> and ~strikethrough~ and _italic (word*)_ ~abc~"
let embedded = ".*?"
let strikeThroughGroupName = "strikethroughGroupName"
let boldGroupName = "boldGroupName"
let colorGroupName = "colorGroupName"
let italicGroupName = "italicGroupName"
let groupNames = [strikeThroughGroupName, boldGroupName, italicGroupName, colorGroupName]
let pattern = "(~(?<\(strikeThroughGroupName)>\(embedded))~)|(<(?<\(colorGroupName)>\(embedded))>)|(_(?<\(italicGroupName)>\(embedded))_)|(\\*(?<\(boldGroupName)>\(embedded))\\*)"
print("Pattern: \(pattern)")
do {
let regex = try NSRegularExpression(pattern: pattern, options: [])
let matches = regex.matches(in: string, options: [], range: NSRange(location: 0, length: string.utf16.count))
matches.forEach { (aMatch) in
if let groupFound = groupNames.first(where: { aMatch.range(withName: $0).location != NSNotFound }),
let range = Range(aMatch.range(withName: groupFound), in: string) {
let textFound = string[range]
print("Found: \(textFound) with effect: \(groupFound) at NSRange: \(aMatch.range(withName: groupFound))")
}
let fullNSRange = aMatch.range
if let fullRange = Range(fullNSRange, in: string) {
let textFound = string[fullRange]
print("Full Text Found: \(textFound)")
}
}
} catch {
print("Regex error: \(error)")
}
Output:
$>Pattern: (~(?<strikethroughGroupName>.*?)~)|(<(?<colorGroupName>.*?)>)|(_(?<italicGroupName>.*?)_)|(\*(?<boldGroupName>.*?)\*)
$>Found: This two with effect: strikethroughGroupName at NSRange: {1, 8}
$>Full Text Found: ~This two~
$>Found: bold text with effect: boldGroupName at NSRange: {16, 9}
$>Full Text Found: *bold text*
$>Found: strikethrough with effect: strikethroughGroupName at NSRange: {59, 13}
$>Full Text Found: ~strikethrough~
$>Found: italic (word*) with effect: italicGroupName at NSRange: {79, 14}
$>Full Text Found: _italic (word*)_
$>Found: abc with effect: strikethroughGroupName at NSRange: {96, 3}
$>Full Text Found: ~abc~
Side Note:
• I used the group naming (that can be used in regex).
It's constructions is (?<groupName>groupToCapture).
• I used embedded as such, for quick testing. It might not cover all the cases. For instance, it doesn't cover the case where there are a change of line in the string. If it's <text\ncolor> (there is a break line), it won't match. There, you can use the regex you want to include breaklines if needed.
I could not combine the patterns into one with or (|) and get them to match properly but if I matched them one by one it works
let string = "~This two~ are *bold text* and different <textcolor> and ~strikethrough~ and _italic (word*)_ ~abc~"
let textPattern = "[:alpha:][:punct:][:space:]"
let range = NSRange(location: 0, length: string.utf16.count)
let patterns:[String] = ["(?:~([\(textPattern)]*)~)", "(?:\\*([\(textPattern)]*)\\*)", "(?:<([\(textPattern)]*)>)", "(?:_([\(textPattern)]*)_)"]
for pattern in patterns {
let regex = try! NSRegularExpression(pattern: pattern)
regex.enumerateMatches(in: string, range: range, using: { (result, flag, pointer) in
if let result = result {
for i in 1..<result.numberOfRanges {
let srange = Range(result.range(at: i))!
let start = String.Index(encodedOffset: srange.lowerBound)
let end = String.Index(encodedOffset: srange.upperBound)
let substr = String(string[start..<end])
print("\(result.range(at: i)) => \(substr)")
}
}
})
}
Output
{1, 8} => This two
{58, 13} => strikethrough
{95, 3} => abc
{16, 9} => bold text
{42, 9} => textcolor
{78, 14} => italic (word*)
Update: Changed my pattern to not include the format characters, ~,* etc and improved output in the example to more clearly show correct number of matches and what is matched.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With