2

My app supports 5 languages. I have a string which has some double quotes in it. This string is translated into 5 languages in the localizable.strings files.

Example:

title_identifier = "Hi \"how\", are \"you\"";

I would like to bold out "how" and "you" in this string by finding the range of these words. So I am trying to fetch these quoted words out of the string and the result would be an array containing "how" and "you" or their range.

func matches(for regex: String, in text: String) -> [String] {
  do {
        let regex = try NSRegularExpression(pattern: regex)
        let results = regex.matches(in: text,
                                    range: NSRange(text.startIndex..., in: text))
        return results.map {
            String(text[Range($0.range, in: text)!])
        }
    } catch let error {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

matches(for: "(?<=\")[^\"]*(?=\")", in: str)

The result is: ["how", ", are ", "you"] rather than ["how","you"]. I think this regex needs some addition to allow it to search for next quote once two quotes are found, so to avoid the words in between quotes.

Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
nr5
  • 4,228
  • 8
  • 42
  • 82
  • @wiktor-stribiżew the question which you suggested as duplicate is not same. The main problem is with Swift language syntax to find the correct regex with escaped quotes not in any other language. – nr5 Sep 09 '19 at 11:28
  • It is the same regex. Just put into the code as a valid Swift string literal. There are a lot of valid solutions in the linked post, see https://stackoverflow.com/a/1016356/3832970, https://stackoverflow.com/a/10786066/3832970. – Wiktor Stribiżew Sep 09 '19 at 11:30
  • Do you have trouble with expressing a [double quote](https://stackoverflow.com/questions/30167848/how-to-print-double-quotes-inside) or [backslash](https://stackoverflow.com/questions/30170908/swift-how-to-print-character-in-a-string) in Swift code? This has been asked already, too. – Wiktor Stribiżew Sep 09 '19 at 11:37
  • Nope, My string is like that only: `var str = "They said \"Its okay\", \"well\" didn't ah ey?"`. The main problem is I have to supply a regex pattern to the swift class: `NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\b(a|b)(c|d)\\b" options:NSRegularExpressionCaseInsensitive error:&error];` Any regex which I try to put inside this, returns a syntactical error – nr5 Sep 09 '19 at 11:40
  • If your string is `They said "Its okay", "well" didn't ah ey?` why do you think `\b(a|b)(c|d)\b` regex will help you? The simplest regex to match a string between two quotes is `"([^"]*)"`. Just use it and replace with `$1` if you want to put tags around the value inside two quotes. – Wiktor Stribiżew Sep 09 '19 at 11:43
  • No that was the example from Apple's site. I have used every regex from the duplicate answers by escaping double quotes. So, `"(?:\\"|.)*?" `will become `"(?:\\\"|.)*?"` for swift, by escaping the quote. But I think it messes up with the regex. – nr5 Sep 09 '19 at 11:48
  • No, to use a slash, you need `"\\"`, so the ``\\`` is `"\\\\"`. `"(?:[^\\"]|\\.)*"` will look like ``let pattern = "\"(?:[^\\\\\"]|\\\\.)*\""`` – Wiktor Stribiżew Sep 09 '19 at 11:50
  • So, you did not know how to escape a backslash, right? – Wiktor Stribiżew Sep 09 '19 at 11:58
  • I did know, even with escaping it is not working. That's why I made a separate question. – nr5 Sep 09 '19 at 11:59
  • Now, if you still need help, taking into account what I wrote in the comments above, please check your current question and let know what is not working, update the question. – Wiktor Stribiżew Sep 09 '19 at 12:01
  • I simplified it so it can be understood by anyone one now. – nr5 Sep 09 '19 at 12:13
  • So, there are two common issues: 1) matching a backlash with regex (a literal backslash needs to be matched with two backslashes, and in a Swift string literal, it needs to be coded with 4 backslashes, `let pattern = "\"(?:\\\\.|[^\"\\\\])*\""`), 2) getting all matches from a string with regex. I updated the close reasons. – Wiktor Stribiżew Sep 09 '19 at 12:21
  • I am able to make it work for a single quoted word now and not able to find a solution for multiple quoted words. – nr5 Sep 10 '19 at 09:32
  • Why do you use a greedy dot (`.*`)? Use `let pattern = "(?<=\")[^\"]*(?=\")"` or - I bet it will match undesired strings - use ``let pattern = "\"([^\"]*)\""`` and grab Group 1. – Wiktor Stribiżew Sep 10 '19 at 09:34
  • Yes I see your point, it atleast will return the first occurrence only, even if there are many and not a faulty result. – nr5 Sep 10 '19 at 09:36
  • A solution for extracting all matches is already linked to, see https://stackoverflow.com/questions/27880650/swift-extract-regex-matches. – Wiktor Stribiżew Sep 10 '19 at 09:39
  • Tried with a "all matches" solution. It returns the words which are not in quotes, i.e the one in between. See my updated question, you'll get the idea.. – nr5 Sep 10 '19 at 10:42
  • That is why I wrote *I bet it will match undesired strings*. Use `let pattern = "\"([^\"]*)\""` and grab Group 1 – Wiktor Stribiżew Sep 10 '19 at 10:47
  • 1
    `matches(for: "\"([^\"]*)\"", in: "Hi \"how\", are \"you\"").map {$0.trimmingCharacters(in: ["\""])}` will do the job – Wiktor Stribiżew Sep 10 '19 at 11:28
  • It worked my man. Thanks a ton ! – nr5 Sep 10 '19 at 11:39

1 Answers1

1

Your problem is in the use of lookarounds that do not consume text but check if their patterns match and return either true or false. See your regex in action, the , are matches because the last " in the previous match was not consumed, the regex index remained right after w, so the next match could start with ". You need to use a consuming pattern here, "([^"]*)".

However, your code will only return full matches. You can just trim the first and last "s here with .map {$0.trimmingCharacters(in: ["\""])}, as the regex only matches one quote at the start and end:

matches(for: "\"[^\"]*\"", in: str).map {$0.trimmingCharacters(in: ["\""])}

Here is the regex demo.

Alternatively, access Group 1 value by appending (at: 1) after $0.range:

func matches(for regex: String, in text: String) -> [String] {
  do {
        let regex = try NSRegularExpression(pattern: regex)
        let results = regex.matches(in: text,
                                    range: NSRange(text.startIndex..., in: text))
        return results.map {
            String(text[Range($0.range(at: 1), in: text)!])
        }
    } catch let error {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

let str = "Hi \"how\", are \"you\""
print(matches(for: "\"([^\"]*)\"", in: str))
// => ["how", "you"]
Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563