Find characters inside quotation marks in String - swift

I'm trying to pull out the parts of a string that are in quotation marks, i.e. in "Rouge One" is an awesome movie I want to extract Rouge One.
This is what I have so far but can't figure out where to go from here: I create a copy of the text so that I can remove the first quotation mark so that I can get the index of the second.
if text.contains("\"") {
guard let firstQuoteMarkIndex = text.range(of: "\"") else {return}
var textCopy = text
let textWithoutFirstQuoteMark = textCopy.replacingCharacters(in: firstQuoteMarkIndex, with: "")
let secondQuoteMarkIndex = textCopy.range(of: "\"")
let stringBetweenQuotes = text.substring(with: Range(start: firstQuoteMarkIndex, end: secondQuoteMarkIndex))
}

There is no need to create copies or to replace substrings for this task.
Here is a possible approach:
Use text.range(of: "\"") to find the first quotation mark.
Use text.range(of: "\"", range:...) to find the second quotation mark, i.e. the first one after the range found in step 1.
Extract the substring between the two ranges.
Example:
let text = " \"Rouge One\" is an awesome movie"
if let r1 = text.range(of: "\""),
let r2 = text.range(of: "\"", range: r1.upperBound..<text.endIndex) {
let stringBetweenQuotes = text.substring(with: r1.upperBound..<r2.lowerBound)
print(stringBetweenQuotes) // "Rouge One"
}
Another option is a regular expression search with "positive lookbehind" and "positive lookahead" patterns:
if let range = text.range(of: "(?<=\\\").*?(?=\\\")", options: .regularExpression) {
let stringBetweenQuotes = text.substring(with: range)
print(stringBetweenQuotes)
}

var rouge = "\"Rouge One\" is an awesome movie"
var separated = rouge.components(separatedBy: "\"") // ["", "Rouge One", " is an awesome movie"]
separated.dropFirst().first

I would use .components(separatedBy:)
let stringArray = text.components(separatedBy: "\"")
Check if stringArray count is > 2 (there is at least 2 quotes).
Check if stringArray count is odd, aka count % 2 == 1.
If it is odd, all the even indices are between 2 quotes and they are what you want.
If it is even, all the even indices - 1 are between 2 quotes (the last one doesn't have an end quote).
This will allow you to also capture multiple sets of quoted strings, like:
"Rogue One" is a "Star Wars" movie.

Another option is to use regular expressions to find pairs of quotes:
let pattern = try! NSRegularExpression(pattern: "\\\"([^\"]+)\\\"")
// Small helper methods making it easier to work with enumerateMatches(in:...)
extension String {
subscript(utf16Range range: Range<Int>) -> String? {
get {
let start = utf16.index(utf16.startIndex, offsetBy: range.lowerBound)
let end = utf16.index(utf16.startIndex, offsetBy: range.upperBound)
return String(utf16[start..<end])
}
}
var fullUTF16Range: NSRange {
return NSRange(location: 0, length: utf16.count)
}
}
// Loop through *all* quoted substrings in the original string.
let str = "\"Rogue One\" is an awesome movie"
pattern.enumerateMatches(in: str, range: str.fullUTF16Range) { (result, flags, stop) in
// rangeAt(1) is the range representing the characters in the 1st
// capture group of the regular expression: ([^"]+)
if let result = result, let range = result.rangeAt(1).toRange() {
print("This was in quotes: \(str[utf16Range: range] ?? "<bad range>")")
}
}

Related

Replace in string with regex

I am struggling to modify captured value with regex.
For example, I wanna change "Hello, he is hero" to "HEllo, HE is HEro" using Regex.
I know there are ways to change this without regex, but it is just an example to show the problem. I actually use the regex instead of just he, but I cannot provide it here. That is why using regex is required.
The code below somehow does not work. Are there any ways to make it work?
"Hello, he is hero".replacingOccurrences(
of: #"(he)"#,
with: "$1".uppercased(), // <- uppercased is not applied
options: .regularExpression
)
You need to use your regex in combination with Range (range(of:)) to find matches and then replace each found range separately
Here is a function as an extension to String that does this by using range(of:) starting from the start of the string and then moving the start index to match from forward to after the last match. The actual replacement is done inside a separate function that is passed as an argument
extension String {
func replace(regex: String, with replace: (Substring) -> String) -> String {
var string = self
var startIndex = self.startIndex
let endIndex = self.endIndex
while let range = string.range(of: regex, options: [.regularExpression] , range: startIndex..<endIndex) {
if range.isEmpty {
startIndex = string.index(startIndex, offsetBy: 1)
if startIndex >= endIndex { break }
continue
}
string.replaceSubrange(range, with: replace(string[range]))
startIndex = range.upperBound
}
return string
}
}
Example where we do an case insensitive search for words starting with "he" and replace each match with the uppercased version
let result = "Hello, he is hero. There he is".replace(regex: #"(?i)\bhe"#) {
$0.uppercased()
}
Output
HEllo, HE is HEro. There HE is
You can try NSRegularExpression. Something like:
import Foundation
var sourceStr = "Hello, he is hero"
let regex = try! NSRegularExpression(pattern: "(he)")
let matches = regex.matches(in: sourceStr, range: NSRange(sourceStr.startIndex..., in: sourceStr))
regex.enumerateMatches(in: sourceStr, range: NSRange(sourceStr.startIndex..., in: sourceStr)) { (match, _, _) in
guard let match = match else { return }
guard let range = Range(match.range, in: sourceStr) else { return }
let sub = sourceStr[range]
sourceStr = sourceStr.replacingOccurrences(of: sub, with: sub.uppercased(), options: [], range: range)
}
print(sourceStr)
this is the solution i can provide
var string = "Hello, he is hero"
let occurrence = "he"
string = string.lowercased().replacingOccurrences(
of: occurrence,
with: occurrence.uppercased(),
options: .regularExpression
)
print(string)

How to trim first 3 character from a string in swift

I have a dropdown(userCphList) in which there are 2 value : 66/001/0004, 66/002/9765. I want to trim the selected value of dropdown from 66/001/0004 to 001/0004.
Given below is my code:
userCphList.didSelect{(selectedText , index ,id) in
let cphid = selectedText
let url = self.appDelegate.BaseUrl + "geojson/proj_4326?cph_id=" + cphid
self.get_wl_geojsondata(url: url)
}
I want to get cphid as 001/0004.
Any help will be highly appreciated!
Thank You!
Rutuparna Panda
You can split your string where separator is a slash, drop the first component and then join it again:
let str = "66/001/0004"
let trimmed = str.split { $0 == "/" }
.dropFirst()
.joined(separator: "/") // "001/0004"
Another option is to find the first slash index and get the substring after it:
if let index = str.firstIndex(of: "/") {
let trimmed = str[str.index(after: index)...] // "001/0004"
// or simply dropping the first character
// let trimmed = str[index...].dropFirst()
}
If the number of characters to be dropped is fixed the easiest way is dropFirst
let string = "66/001/0004"
let trimmedString = String(string.dropFirst(3))
Other ways are Regular Expression
let trimmedString = string.replacingOccurrences(of: "^\\d+/", with: "", options: .regularExpression)
and removing the substring by range
if let range = string.range(of: "/") {
let trimmedString = String(string[range.upperBound...])
}

Regular expressions in swift

I'm bit confused by NSRegularExpression in swift, can any one help me?
task:1 given ("name","john","name of john")
then I should get ["name","john","name of john"]. Here I should avoid the brackets.
task:2 given ("name"," john","name of john")
then I should get ["name","john","name of john"]. Here I should avoid the brackets and extra spaces and finally get array of strings.
task:3 given key = value // comment
then I should get ["key","value","comment"]. Here I should get only strings in the line by avoiding = and //
I have tried below code for task 1 but not passed.
let string = "(name,john,string for user name)"
let pattern = "(?:\\w.*)"
do {
let regex = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
let matches = regex.matches(in: string, options: [], range: NSRange(location: 0, length: string.utf16.count))
for match in matches {
if let range = Range(match.range, in: string) {
let name = string[range]
print(name)
}
}
} catch {
print("Regex was bad!")
}
Thanks in advance.
RegEx in Swift
These posts might help you to explore regular expressions in swift:
Does a string match a pattern?
Swift extract regex matches
How can I use String slicing subscripts in Swift 4?
How to use regex with Swift?
Swift 3 - How do I extract captured groups in regular expressions?
How to group search regular expressions using swift?
Task 1 & 2
This expression might help you to match your desired outputs for both Task 1 and 2:
"(\s+)?([a-z\s]+?)(\s+)?"
Based on Rob's advice, you could much reduce the boundaries, such as the char list [a-z\s]. For example, here, we can also use:
"(\s+)?(.*?)(\s+)?"
or
"(\s+)?(.+?)(\s+)?"
to simply pass everything in between two " and/or space.
RegEx
If this wasn't your desired expression, you can modify/change your expressions in regex101.com.
RegEx Circuit
You can also visualize your expressions in jex.im:
JavaScript Demo
const regex = /"(\s+)?([a-z\s]+?)(\s+)?"/gm;
const str = `"name","john","name of john"
"name"," john","name of john"
" name "," john","name of john "
" name "," john"," name of john "`;
const subst = `\n$2`;
// The substituted value will be contained in the result variable
const result = str.replace(regex, subst);
console.log('Substitution result: ', result);
Task 3
This expression might help you to design an expression for the third task:
(.*?)([a-z\s]+)(.*?)
const regex = /(.*?)([a-z\s]+)(.*?)/gm;
const str = `key = value // comment
key = value with some text // comment`;
const subst = `$2,`;
// The substituted value will be contained in the result variable
const result = str.replace(regex, subst);
console.log('Substitution result: ', result);
Separate the string by non alpha numeric characters except white spaces. Then trim the elements with white spaces.
extension String {
func words() -> [String] {
return self.components(separatedBy: CharacterSet.alphanumerics.inverted.subtracting(.whitespaces))
.filter({ !$0.isEmpty })
.map({ $0.trimmingCharacters(in: .whitespaces) })
}
}
let string1 = "(name,john,string for user name)"
let string2 = "(name, john,name of john)"
let string3 = "key = value // comment"
print(string1.words())//["name", "john", "string for user name"]
print(string2.words())//["name", "john", "name of john"]
print(string3.words())//["key", "value", "comment"]
Here I have done with after understanding all of above comments.
let text = """
Capturing and non-capturing groups are somewhat advanced topics. You’ll encounter examples of capturing and non-capturing groups later on in the tutorial
"""
extension String {
func rex (_ expr : String)->[String] {
return try! NSRegularExpression(pattern: expr, options: [.caseInsensitive])
.matches(in: self, options: [], range: NSRange(location: 0, length: self.count))
.map {
String(self[Range($0.range, in: self)!])
}
}
}
let r = text.rex("(?:\\w+-\\w+)") // pass any rex
A single pattern, works for test:1...3, in Swift.
let string =
//"(name,john,string for user name)" //test:1
//#"("name"," john","name of john")"# //test:2
"key = value // comment" //test:3
let pattern = #"(?:\w+)(?:\s+\w+)*"# //Swift 5+ only
//let pattern = "(?:\\w+)(?:\\s+\\w+)*"
do {
let regex = try NSRegularExpression(pattern: pattern)
let matches = regex.matches(in: string, range: NSRange(0..<string.utf16.count))
let matchingWords = matches.map {
String(string[Range($0.range, in: string)!])
}
print(matchingWords) //(test:3)->["key", "value", "comment"]
} catch {
print("Regex was bad!")
}
Let’s consider:
let string = "(name,José,name is José)"
I’d suggest a regex that looks for strings where:
It’s the substring either after the ( at the start of the full string or after a comma, i.e., look behind assertion of (?<=^\(|,);
It’s the substring that does not contain , within it, i.e., [^,]+?;
It’s the substring that is terminated by either a comma or ) at the end of the full string, i.e., look ahead assertion of (?=,|\)$), and
If you want to have it skip white space before and after the substrings, throw in the \s*+, too.
Thus:
let pattern = #"(?<=^\(|,)\s*+([^,]+?)\s*+(?=,|\)$)"#
let regex = try! NSRegularExpression(pattern: pattern)
regex.enumerateMatches(in: string, range: NSRange(string.startIndex..., in: string)) { match, _, _ in
if let nsRange = match?.range(at: 1), let range = Range(nsRange, in: string) {
let substring = String(string[range])
// do something with `substring` here
}
}
Note, I’m using the Swift 5 extended string delimiters (starting with #" and ending with "#) so that I don’t have to escape my backslashes within the string. If you’re using Swift 4 or earlier, you’ll want to escape those back slashes:
let pattern = "(?<=^\\(|,)\\s*+([^,]+?)\\s*+(?=,|\\)$)"

Converting numbers to string in a given string in Swift

I am given a string like 4eysg22yl3kk and my output should be like this:
foureysgtweny-twoylthreekk or if I am given 0123 it should be output as one hundred twenty-three. So basically, as I scan the string, I need to convert numbers to string.
I do not know how to implement this in Swift as I iterate through the string? Any idea?
You actually have two basic problems.
The first is convert a "number" to "spelt out" value (ie 1 to one). This is actually easy to solve, as NumberFormatter has a spellOut style property
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
let text = formatter.string(from: NSNumber(value: 1))
which will result in "one", neat.
The other issue though, is how to you separate the numbers from the text?
While I can find any number of solutions for "extract" numbers or characters from a mixed String, I can't find one which return both, split on their boundaries, so, based on your input, we'd end up with ["4", "eysg", "22", "yl", "3", "kk"].
So, time to role our own...
func breakApart(_ text: String, withPattern pattern: String) throws -> [String]? {
do {
let regex = try NSRegularExpression(pattern: "[0-9]+", options: .caseInsensitive)
var previousRange: Range<String.Index>? = nil
var parts: [String] = []
for match in regex.matches(in: text, options: [], range: NSRange(location: 0, length: text.count)) {
guard let range = Range(match.range, in: text) else {
return nil
}
let part = text[range]
if let previousRange = previousRange {
let textRange = Range<String.Index>(uncheckedBounds: (lower: previousRange.upperBound, upper: range.lowerBound))
parts.append(String(text[textRange]))
}
parts.append(String(part))
previousRange = range
}
if let range = previousRange, range.upperBound != text.endIndex {
let textRange = Range<String.Index>(uncheckedBounds: (lower: range.upperBound, upper: text.endIndex))
parts.append(String(text[textRange]))
}
return parts
} catch {
}
return nil
}
Okay, so this is a little "dirty" (IMHO), but I can't seem to think of a better approach, hopefully someone will be kind enough to provide some hints towards one ;)
Basically what it does is uses a regular expression to find all the groups of numbers, it then builds an array, cutting the string apart around the matching boundaries - like I said, it's crude, but it gets the job done.
From there, we just need to map the results, spelling out the numbers as we go...
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
let value = "4eysg22yl3kk"
if let parts = try breakApart(value, withPattern: pattern) {
let result = parts.map { (part) -> String in
if let number = Int(part), let text = formatter.string(from: NSNumber(value: number)) {
return text
}
return part
}.joined(separator: " ")
print(result)
}
This will end up printing four eysg twenty-two yl three kk, if you don't want the spaces, just get rid of separator in the join function
I did this in Playgrounds, so it probably needs some cleaning up
I was able to solve my question without dealing with anything extra than converting my String to an array and check char by char. If I found a digit I was saving it in a temp String and as soon as I found out the next char is not digit, I converted my digit to its text.
let inputString = Array(string.lowercased())

How can I substring this string?

how can I substring the next 2 characters of a string after a certian character. For example I have a strings str1 = "12:34" and other like str2 = "12:345. I want to get the next 2 characters after : the colons.
I want a same code that will work for str1 and str2.
Swift's substring is complicated:
let str = "12:345"
if let range = str.range(of: ":") {
let startIndex = str.index(range.lowerBound, offsetBy: 1)
let endIndex = str.index(startIndex, offsetBy: 2)
print(str[startIndex..<endIndex])
}
It is very easy to use str.index() method as shown in #MikeHenderson's answer, but an alternative to that, without using that method is iterating through the string's characters and creating a new string for holding the first two characters after the ":", like so:
var string1="12:458676"
var nr=0
var newString=""
for c in string1.characters{
if nr>0{
newString+=String(c)
nr-=1
}
if c==":" {nr=2}
}
print(newString) // prints 45
Hope this helps!
A possible solution is Regular Expression,
The pattern checks for a colon followed by two digits and captures the two digits:
let string = "12:34"
let pattern = ":(\\d{2})"
let regex = try! NSRegularExpression(pattern: pattern, options: [])
if let match = regex.firstMatch(in: string, range: NSRange(location: 0, length: string.characters.count)) {
print((string as NSString).substring(with: match.rangeAt(1)))
}