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]))
}
Related
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"]
I got json data from server like this.
[
"http:\/\/helloWord.com\/user\/data\/000001.jpg?1497514193433",
"http:\/\/helloWord.com\/user\/data\/000002.jpg?1500626693722"
]
And What should I do to get each user url?
I try to use removingPercentEncoding, but it doesn't work.
What should I do?
Thanks.
let string:String = chatroom.avatar
let tempArr = string.components(separatedBy: ",")
var stringArr = Array<String>()
print("**tempArr\(tempArr)")
for a in tempArr {
var b = a.replacingOccurrences(of: "\"", with: "")
b = b.replacingOccurrences(of: "[", with: "")
b = b.replacingOccurrences(of: "]", with: "")
b = b.removingPercentEncoding //not working!!!!
print("b: \(b)")
//b: http:\/\/helloWord.com\/user\/data\/000001.jpg?1497514193433
//b: http:\/\/helloWord.com\/user\/data\/000002.jpg?1500626693722
}
I use swiftyJson
class User : Model {
var url:String = ""
func fromJson(_ json:JSON) {
url = json["url"].zipString
saveSqlite()
}
}
extension JSON {
var prettyString: String {
if let string = rawString() {
return string
}
return ""
}
var zipString: String {
if let string = rawString(.utf8, options: JSONSerialization.WritingOptions.init(rawValue: 0)) {
return string
}
return ""
}
}
Err, you shouldn't try to write your own JSON parser.
Try: https://github.com/SwiftyJSON/SwiftyJSON
It is one file of Swift code and makes using JSON much easier in Swift.
In swift 4 you will be able to use Structs to directly decode, but we're not there yet.
The apple way:
func read(payload: Data) throws -> [String]? {
guard let result = try JSONSerialization.jsonObject(with: payload, options: JSONSerialization.ReadingOptions.allowFragments) as? [String] else { return nil }
return result
}
Then for example you could read them out this way:
var userURLs: [URL] = []
for jsonURL in result {
guard let userURL = URL(string: jsonURL) else { continue }
userURLs.append(userURL)
}
This way you get only valid URL objects in your last result array.
If you get problems using the JSONSerialization code I described above it might be that it expects a different type. Then you'd have to use [Any] as a cast instead, or in case you have objects [String: Any] usually works. Keep in mind that in that case you will have to cast the objects you get from the array like so:
URL(string: (jsonURL as? String) ?? "")
Swifty JSON makes it easier to go about with the nullability, as it provides an easy way to safely traverse an object tree and return empty but non nil values!
It seems to me that you want to use removingPercentEncoding to remove escape characters - those backslashes? While removingPercentEncoding is to work on percent encoded characters, e.g. to convert http%3A%2F%2Fwww.url-encode-decode.com%2F to http://www.url-encode-decode.com/. So you are using it at the wrong place. Make sure to call this method only on the URLs that are percent encoded.
For this scenario, like others already suggested, use JSONSerialization is the way to go.
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.
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.
Im using Swiftcsv library to parse CSV file.
How to ignore Comma delimiter for strings within DoubleQuotes e.g "Abcd, went to Apple" ?
here the parser takes Abcd as one Value and went to Apple as another value.
Code :
func parseRows(fromLines lines: [String]) -> [Dictionary<String, String>] {
var rows: [Dictionary<String, String>] = []
for (lineNumber, line) in enumerate(lines) {
if lineNumber == 0 {
continue
}
var row = Dictionary<String, String>()
let values = line.componentsSeparatedByCharactersInSet(self.delimiter)
for (index, header) in enumerate(self.headers) {
let value = values[index]
row[header] = value
}
rows.append(row)
}
return rows
}
How can i change line.componentsSeparatedByCharactersInSet(self.delimiter) to ignore commas within Doublequotes?
Use this instead https://github.com/Daniel1of1/CSwiftV
Had the same issue. It took me an hour or so to figure out the problem is that SwiftCSV doesn't, erm, work.
Text in CSV files is inside quotes so commas and newlines in a CSV don't screw up the parser. I looked at the SwiftCSV source and there is no support for that - meaning that any commas or newlines screw up the parsing.
You could patch up SwiftCSV, or just go with CSwiftV I linked above.
I don't know if you are still looking for a solution, but I just came up with a quick way to do this as it is a problem that just came up for me.
The code isn't full proof because I am only using it for a side project so if you want to handle more cases you probably will need to make some changes.
func parseRows(fromLines lines: [String]) -> [Dictionary<String, String>] {
var rows: [Dictionary<String, String>] = []
for (lineNumber, line) in enumerate(lines) {
if lineNumber == 0 {
continue
}
var row = Dictionary<String, String>()
// escape commas in the string when it is surrounded by quotes
let convertedLine = NSString(string: line) // have to convert string to NSString because string does not have all NSString API
var escapedLine = line
var searchRange = NSMakeRange(1,convertedLine.length)
var foundRange:NSRange
if NSString(string: line).containsString("\"")
{
while (searchRange.location < convertedLine.length) {
searchRange.length = convertedLine.length-searchRange.location
foundRange = convertedLine.rangeOfString("\"", options: nil, range: searchRange)
if (foundRange.location != NSNotFound) {
// found a quotation mark
searchRange.location = foundRange.location+foundRange.length
let movieTitle = convertedLine.substringToIndex(foundRange.location)
escapedLine = convertedLine.stringByReplacingOccurrencesOfString(",", withString: "&c", options: nil, range: NSMakeRange(0,foundRange.location))
} else {
// no more substring to find
break
}
}
}
var values = escapedLine.componentsSeparatedByCharactersInSet(self.delimiter)
for (index, header) in enumerate(self.headers) {
var value = values[index]
//reinsert commas if they were escaped and get rid of quotation marks
value = value.stringByReplacingOccurrencesOfString("\"", withString: "")
value = value.stringByReplacingOccurrencesOfString("&c", withString: ",")
row[header] = value
}
rows.append(row)
}
return rows
}