Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make patterns to use NSRegularExpression

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, enter image description here

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

UPDATE

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,

like image 550
Vatsal Shukla Avatar asked Jun 13 '26 02:06

Vatsal Shukla


2 Answers

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.

like image 59
Larme Avatar answered Jun 18 '26 01:06

Larme


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.

like image 39
Joakim Danielson Avatar answered Jun 18 '26 00:06

Joakim Danielson



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!