Extracting text from a string - ios

Hi I have this error I keep coming up with that says Value of type '[String]' has no member 'characters'
Here is my code:
let sentence = "the mans states of this game are Jumping: 132.4 and Speed: 142.192"
func matchesForRegexInText(regex: String!, text: String!) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex, options: [])
let nsString = text as NSString
let results = regex.matchesInString(text,
options: [], range: NSMakeRange(0, nsString.length))
return results.map { nsString.substringWithRange($0.range)}
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
let sentJumping = matchesForRegexInText("Jumping:\\s+\\d+.\\d+", text: sentence)
print(sentJumping)
let gallNumb = sentJumping.characters.split(":").map{ String($0) } // The error I'm getting is on this line

EDITED
You need to transform the string array sentJumping in to a String prior to trying to using the string method characters. As it is now, sentJumping is of type [String] (array). One method could be to reduce the array into one string, as the sum of all string entries in the string array.
Try replacing your last line of code with the following
let gallNumb = sentJumping.reduce("", combine: +).characters.split(":").map{ String($0) }
Note that by using sentJumping[0] solution as recommended in the other answer, you will just get the first entry of the array (in your specific example: the array has only one entry, ok), and, if the array is empty, give you a runtime exception.

You can try
let gallNumb = sentJumping[0].characters.split(":").map{ String($0) }
Your function returns an array. You need to use [0] to retrieve the result you want.

Related

Slicing Strings Swift

I want to slice a very long string from one word to another. I want to get the substring between those words.
For that, I use the following string extension:
extension String {
func slice(from: String, to: String) -> String? {
guard let rangeFrom = range(of: from)?.upperBound else { return nil }
guard let rangeTo = self[rangeFrom...].range(of: to)?.lowerBound else { return nil }
return String(self[rangeFrom..<rangeTo])
}
That works really good, but my raw-string contains a few of the "from" "to"-words and I need every substring that is between of these two words, but with my extension I can ony get the first substring.
Example:
let raw = "id:244476end36475677id:383848448end334566777788id:55678900end543"
I want to get the following substrings from this raw string example:
sub1 = "244476"
sub2 = "383848448"
sub3 = "55678900"
If I call:
var text = raw.slice(from: "id:" , to: "end")
I only get the first occurence (text = "244476")
Thank you for reading. Every answer would be nice.
PS: I get always an error by making code snippets in stackoverflow.
You can get the ranges of your substrings using a while loop to repeat the search from that point to the end of your string and use map to get the substrings from the resulting ranges:
extension StringProtocol {
func ranges<S:StringProtocol,T:StringProtocol>(between start: S, and end: T, options: String.CompareOptions = []) -> [Range<Index>] {
var ranges: [Range<Index>] = []
var startIndex = self.startIndex
while startIndex < endIndex,
let lower = self[startIndex...].range(of: start, options: options)?.upperBound,
let range = self[lower...].range(of: end, options: options) {
let upper = range.lowerBound
ranges.append(lower..<upper)
startIndex = range.upperBound
}
return ranges
}
func substrings<S:StringProtocol,T:StringProtocol>(between start: S, and end: T, options: String.CompareOptions = []) -> [SubSequence] {
ranges(between: start, and: end, options: options).map{self[$0]}
}
}
Playground testing:
let string = """
your text
id:244476end
id:383848448end
id:55678900end
the end
"""
let substrings = string.substrings(between: "id:", and: "end") // ["244476", "383848448", "55678900"]
Rather thant trying to parse the string from start to end, I would use a combination of existing methods to transform it into the desire result. Here's How I would do this:
import Foundation
let raw = "id:244476end36475677id:383848448end334566777788id:55678900end543"
let result = raw
.components(separatedBy: "id:")
.filter{ !$0.isEmpty }
.map { segment -> String in
let slices = segment.components(separatedBy: "end")
return slices.first! // Removes the `end` and everything thereafter
}
print(result) // => ["244476", "383848448", "55678900"]

Get specific values from a string and create array

From some URL I create an array of strings, and I would like to grab some data from those strings and turn them into another array of variables.
My array of strings looks like this:
#EXTINF:-1 tvg-logo="https://www.thetvdb.com/banners/posters/248741-9.jpg" group-title="Broke Girls", trailer
#EXTINF:-1 tvg-logo="https://www.thetvdb.com/banners/posters/210841-10.jpg" group-title="Alphas", Alphas trailer
#EXTINF:-1 tvg-logo="https://www.thetvdb.com/banners/posters/309053-2.jpg" group-title="American Gothic", trailer
Every line represents a new string item from my array.
I am trying to create a function to do it, but until now, I only have this:
func grabValuesFromUrl(savedUrl: String) {
var trailersArray = []()
if let url = URL(string: savedUrl) {
do {
let contents = try String(contentsOf: url)
contents.enumerateLines { (line, stop) in
// here i need to grab the values from every string inside tvg-logo="", group-title="", and the last one after "," that's the title, and put them into trailersArray[], afterwards i will make some model class to get the data like trailersArray.logo and trailersArray.group and trailersArray.title
}
} else {
print("no url added")
}
}
Thanks in advance
I'd use regex for anything related to extracting data from a string with known format. For this, lets first define helper function:
func matches(for regex: String, inText text: String) -> [String] {
guard let regex = try? NSRegularExpression(pattern: regex, options: [.caseInsensitive]) else { return [] }
let nsString = text as NSString
let results = regex.matches(in: text, options: [], range: NSMakeRange(0, nsString.length))
return results.flatMap { result in
(0..<result.numberOfRanges).map {
result.range(at: $0).location != NSNotFound ? nsString.substring(with: result.range(at: $0)) : ""
}
}
}
And then define the regular expression that will extract required data:
let regex = "^.*tvg-logo=\"(.+)\".*group-title=\"(.+)\".*, (.+)$"
Beware that this regex is sensitive to data format so you'll have to adapt it to new one in case of changes.
Finally, in your line enumeration closure you can extract the data:
let parts = matches(for: regex, inText: line).dropFirst()
parts is now an array with three corresponding items (we drop the first one because it is the line itself) if the line matches the regex, so we can, for example, append a tuple with values to the array:
if parts.count == 3 {
trailersArray.append((logo: parts[0], group: parts[1], title: parts[2]))
}

Use of rangeOfCharacter in Swift 3.0

I am attempting to use rangeOfCharacter to create an app, but am unable to understand its documentation:
func rangeOfCharacter(from: CharacterSet, options:
String.CompareOptions, range: Range<String.Index>?)
-Finds and returns the range in the String of the first character from
a given character set found in a given range with given options.
Documentation link: https://developer.apple.com/documentation/swift/string#symbols
I am working on an exercise to create a function which will take in a name and return the name, minus any consonants before the first vowel. The name should be returned unchanged if there are no consonants before the first vowel.
Below is the code I have so far:
func shortNameFromName(name: String) -> String {
var shortName = name.lowercased()
let vowels = "aeiou"
let vowelRange = CharacterSet(charactersIn: vowels)
rangeOfCharacter(from: vowels, options: shortName,
range: substring(from: shortName[0]))
Any help is much appreciated. Apologies for the newbie mistakes.
I hate Swift ranges. But hopefully things will get better with Swift 4.
let name = "Michael"
var shortName = name.lowercased()
let vowels = "aeiou"
let vowelSet = CharacterSet(charactersIn: vowels)
let stringSet = shortName
if let range = stringSet.rangeOfCharacter(from: vowelSet, options: String.CompareOptions.caseInsensitive)
{
let startIndex = range.lowerBound
let substring = name.substring(from: range.lowerBound)
print(substring)
}
Use this code with a regular expression your problem is solved
Improved
func shortNameFromName(name: String) -> String {
do{
let regex2 = try NSRegularExpression(pattern: "[a|e|i|o|u].*", options:[.dotMatchesLineSeparators])
if let result = regex2.firstMatch(in: name.lowercased(), options: .init(rawValue: 0), range: NSRange(location: 0, length: NSString(string: name).length))
{
return String(NSString(string: name).substring(with: result.range))
}
}
catch
{
debugPrint(error.localizedDescription)
}
return ""
}
Tested
debugPrint(self.shortNameFromName(name: "yhcasid")) //test1
debugPrint(self.shortNameFromName(name: "ayhcasid")) //test2
debugPrint(self.shortNameFromName(name: "😀abc")) // working thanks to #MartinR
Console Log
test1 result
"asid"
test2 result
"ayhcasid"
test3 result
"abc"
Hope this helps
You are passing completely wrong arguments to the method.
rangeOfCharacter accepts 3 arguments. You passed in the character set correctly, but the last two arguments you passed makes no sense. You should pass a bunch of options as the second argument, instead you passed in a string. The third argument is supposed to be a Range but you passed the return value of a substring call.
I think rangeOfCharacter isn't suitable here. There are lots more better ways to do this. For example:
func shortNameFromName(name: String) -> String {
return String(name.characters.drop(while: {!"aeiou".characters.contains($0)}))
}
Swift 3
replace your code here..
func shortNameFromName(name: String) -> String {
var shortName = name.lowercased()
let newstring = shortName
let vowels: [Character] = ["a","e","i","o","u"]
for i in shortName.lowercased().characters {
if vowels.contains(i) {
break
}
else {
shortName = shortName.replacingOccurrences(of: "\(i)", with: "")
}
}
if shortName != "" {
return shortName
}
else
{
return newstring
}

Why Regex returns me Range value?

I have a String Add "ABC" here and I want to extract ABC from those string. For this I do:
text.rangeOfString("(?<=\")[^\"]+", options: .RegularExpressionSearch)
but it returns me
Optional(Range(5..<7))
How can I extract those text from there?
You firstly need to unwrap the resulted range, and call substringWithRange. You can do this via conditional binding
let text = "Add \"ABC\""
let range = text.rangeOfString("(?<=\")[^\"]+", options: .RegularExpressionSearch, range: nil, locale: nil)
if let nonNilRange = range {
print(text.substringWithRange(nonNilRange))
}
You can use the "([^"]+)" regex to extract any matches and any captured groups with the following code:
func regMatchGroup(regex: String, text: String) -> [[String]] {
do {
var resultsFinal = [[String]]()
let regex = try NSRegularExpression(pattern: regex, options: [])
let nsString = text as NSString
let results = regex.matchesInString(text,
options: [], range: NSMakeRange(0, nsString.length))
for result in results {
var internalString = [String]()
for var i = 0; i < result.numberOfRanges; ++i{
internalString.append(nsString.substringWithRange(result.rangeAtIndex(i)))
}
resultsFinal.append(internalString)
}
return resultsFinal
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
return [[]]
}
}
// USAGE:
let string = "Add \"ABC\" \"ABD\""
let matches = regMatchGroup("\"([^\"]+)\"", text: string)
if (matches.count > 0) // If we have matches....
{
print(matches[0][1]) // Print the first one, Group 1.
}
See SwiftStub demo
Due to error handling added, no crash should occur when no match is found.
The solution is:
let regex = myText.rangeOfString("(?<=\")[^\"]+")
myText.substringWithRange(regex, options: .RegularExpressionSearch)!)

NSRegularExpression cannot find capturing group matches

I'm trying to parse a string using one regular expression pattern.
Here is the pattern:
(\")(.+)(\")\s*(\{)
Here is the text to be parsed:
"base" {
I want to find these 4 capturing groups:
1. "
2. base
3. "
4. {
I am using the following code trying to capture those groups
class func matchesInCapturingGroups(text: String, pattern: String) -> [String] {
var results = [String]()
let textRange = NSMakeRange(0, count(text))
var index = 0
if let matches = regexp(pattern)?.matchesInString(text, options: NSMatchingOptions.ReportCompletion, range: textRange) as? [NSTextCheckingResult] {
for match in matches {
// this match = <NSExtendedRegularExpressionCheckingResult: 0x7fac3b601fd0>{0, 8}{<NSRegularExpression: 0x7fac3b70b5b0> (")(.+)(")\s*(\{) 0x1}
results.append(self.substring(text, range: match.range))
}
}
return results
}
Unfortunately it is able to find only one group with range (0, 8) which is equal to: "base" {. So it finds one group which is the entire string instead of 4 groups.
Is that even possible to get those groups using NSRegularExpression?
Yes, of course it is possible. You just have to change your current logic for finding the actual groups:
func matchesInCapturingGroups(text: String, pattern: String) -> [String] {
var results = [String]()
let textRange = NSMakeRange(0, text.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
do {
let regex = try NSRegularExpression(pattern: pattern, options: [])
let matches = regex.matchesInString(text, options: NSMatchingOptions.ReportCompletion, range: textRange)
for index in 1..<matches[0].numberOfRanges {
results.append((text as NSString).substringWithRange(matches[0].rangeAtIndex(index)))
}
return results
} catch {
return []
}
}
let pattern = "(\")(.+)(\")\\s*(\\{)"
print(matchesInCapturingGroups("\"base\" {", pattern: pattern))
You actually only get 1 match. You have to go into that match and in there you will find the captured groups. Note that I omit the first group since the first group represents the entire match.
This will output
[""", "base", """, "{"]
Note the escaped regex string and make sure that you are using the same one.

Resources