2

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,

Vatsal Shukla
  • 1,274
  • 12
  • 25

3 Answers3

3

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.

Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
3

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.

Larme
  • 24,190
  • 6
  • 51
  • 81
  • Note: if it's for replace, then the whole range for the replacement is `aMatch.range(at: 0)`, or simply `aMatch.range`, the `range` I give is the one of the text without the tags (`~`, `<`, etc.). – Larme Jan 10 '19 at 13:05
  • I don't understand your question update. Is that target output? Current output? What's the question? What should it be? – Larme Jan 10 '19 at 13:47
  • I edited the answer, because that might seems to be what you want, I think, I'm not sure of what you want exactly. – Larme Jan 10 '19 at 14:09
  • i don't want it all together. i need just one pattern at a time. while i'm trying to learn NSRegularExpression, your answer seem more confusing to me. but i'm really thankful to you. – Vatsal Shukla Jan 11 '19 at 07:16
  • Aaah. Totally misunderstood your question. When I asked in comment: "You need 3 patterns, or one?", you said "one", so I though one combining them all. – Larme Jan 11 '19 at 07:18
  • @Vats Any special reason you're completely ignoring my answer? – Joakim Danielson Jan 11 '19 at 07:29
  • Please check my answer. – Vatsal Shukla Jan 12 '19 at 07:07
2

I found an answer for my own question, i will explain it by an example so you can try yourself.

here it is,

    //Some randome bunch of words
let string = "1. *うちに* comes from the ~kanji~ *内* which mean “inside” or “within” and has _two distinct_ meanings in Japanese. 2. Firstly, it shows that something happens within ~a~ period of time. “While” something is happening, you do an action.~Very minimal as~ far as features tha *t* are supported in the Web version. It works in a pinch, if you’re in a hurry and ne. Nice~"

//to find words which are suppose to be stricked using '~' character
let pattern = "~[^\\s](.*?[^\\s])??~"

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
    let matchedRange = aMatch.range(at: 0)
    let matchingText = (string as NSString).substring(with: matchedRange)
    print(matchingText)
}


A very 'Big Thanks' to

, (1) larme (2) Joakim Danielson (3) The Coding Train
Vatsal Shukla
  • 1,274
  • 12
  • 25