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.
Related
I want to extract value from a string which has unique starting and ending character. In my case its em
"Fully <em>Furni<\/em>shed |Downtown and Canal Views",
result
Furnished
I guess you want to remove the tags.
If the backslash is only virtual the pattern is pretty simple: Basically <em> with optional slash /?
let trimmedString = string.replacingOccurrences(of: "</?em>", with: "", options: .regularExpression)
Considering also the backslash it's
let trimmedString = string.replacingOccurrences(of: "<\\\\?/?em>", with: "", options: .regularExpression)
If you want to extract only Furnished you have to capture groups: The string between the tags and everything after the closing tag until the next whitespace character.
let string = "Fully <em>Furni<\\/em>shed |Downtown and Canal Views"
let pattern = "<em>(.*)<\\\\?/em>(\\S+)"
do {
let regex = try NSRegularExpression(pattern: pattern)
if let match = regex.firstMatch(in: string, range: NSRange(string.startIndex..., in: string)) {
let part1 = string[Range(match.range(at: 1), in: string)!]
let part2 = string[Range(match.range(at: 2), in: string)!]
print(String(part1 + part2))
}
} catch { print(error) }
Given this string:
let str = "Fully <em>Furni<\\/em>shed |Downtown and Canal Views"
and the corresponding NSRange:
let range = NSRange(location: 0, length: (str as NSString).length)
Let's construct a regular expression that would match letters between <em> and </em>, or preceded by </em>
let regex = try NSRegularExpression(pattern: "(?<=<em>)\\w+(?=<\\\\/em>)|(?<=<\\\\/em>)\\w+")
What it does is :
look for 1 or more letters: \\w+,
that are preceded by <em>: (?<=<em>) (positive lookbehind),
and followed by <\/em>: (?=<\\\\/em>) (positive lookahead),
or : |
letters: \\w+,
that are preceded by <\/em>: (?=<\\\\/em>) (positive lookbehind)
Let's get the matches:
let matches = regex.matches(in: str, range: range)
Which we can turn into substrings:
let strings: [String] = matches.map { match in
let start = str.index(str.startIndex, offsetBy: match.range.location)
let end = str.index(start, offsetBy: match.range.length)
return String(str[start..<end])
}
Now we can join the strings in even indices, with the ones in odd indices:
let evenStride = stride(from: strings.startIndex,
to: strings.index(strings.endIndex, offsetBy: -1),
by: 2)
let result = evenStride.map { strings[$0] + strings[strings.index($0, offsetBy: 1)]}
print(result) //["Furnished"]
We can test it with another string:
let str2 = "<em>Furni<\\/em>shed <em>balc<\\/em>ony <em>gard<\\/em>en"
the result would be:
["Furnished", "balcony", "garden"]
Not a regex but, for obtaining all words in tags, e.g [Furni, sma]:
let text = "Fully <em>Furni<\\/em>shed <em>sma<\\/em>shed |Downtown and Canal Views"
let emphasizedParts = text.components(separatedBy: "<em>").filter { $0.contains("<\\/em>")}.flatMap { $0.components(separatedBy: "<\\/em>").first }
For full words, e.g [Furnished, smashed]:
let emphasizedParts = text.components(separatedBy: " ").filter { $0.contains("<em>")}.map { $0.replacingOccurrences(of: "<\\/em>", with: "").replacingOccurrences(of: "<em>", with: "") }
Regex:
If you want to achieve that by regex, you can use Valexa's answer:
public extension String {
public func capturedGroups(withRegex pattern: String) -> [String] {
var results = [String]()
var regex: NSRegularExpression
do {
regex = try NSRegularExpression(pattern: pattern, options: [])
} catch {
return results
}
let matches = regex.matches(in: self, options: [], range: NSRange(location:0, length: self.count))
guard let match = matches.first else { return results }
let lastRangeIndex = match.numberOfRanges - 1
guard lastRangeIndex >= 1 else { return results }
for i in 1...lastRangeIndex {
let capturedGroupIndex = match.range(at: i)
let matchedString = (self as NSString).substring(with: capturedGroupIndex)
results.append(matchedString)
}
return results
}
}
like this:
let text = "Fully <em>Furni</em>shed |Downtown and Canal Views"
print(text.capturedGroups(withRegex: "<em>([a-zA-z]+)</em>"))
result:
["Furni"]
NSAttributedString:
If you want to do some highlighting or you only need to get rid of tags or any other reason that you can't use the first solution, you can also do that using NSAttributedString:
extension String {
var attributedStringAsHTML: NSAttributedString? {
do{
return try NSAttributedString(data: Data(utf8),
options: [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue],
documentAttributes: nil)
}
catch {
print("error: ", error)
return nil
}
}
}
func getTextSections(_ text:String) -> [String] {
guard let attributedText = text.attributedStringAsHTML else {
return []
}
var sections:[String] = []
let range = NSMakeRange(0, attributedText.length)
// we don't need to enumerate any special attribute here,
// but for example, if you want to just extract links you can use `NSAttributedString.Key.link` instead
let attribute: NSAttributedString.Key = .init(rawValue: "")
attributedText.enumerateAttribute(attribute,
in: range,
options: .longestEffectiveRangeNotRequired) {attribute, range, pointer in
let text = attributedText.attributedSubstring(from: range).string
sections.append(text)
}
return sections
}
let text = "Fully <em>Furni</em>shed |Downtown and Canal Views"
print(getTextSections(text))
result:
["Fully ", "Furni", "shed |Downtown and Canal Views"]
Here is basic implementation in PHP (yes, I know you asked Swift, but it's to demonstrate the regex part):
<?php
$in = "Fully <em>Furni</em>shed |Downtown and Canal Views";
$m = preg_match("/<([^>]+)>([^>]+)<\/\\1>([^ ]+|$)/i", $in, $t);
$s = $t[2] . $t[3];
echo $s;
Output:
ZC-MGMT-04:~ jv$ php -q regex.php
Furnished
Obviously, the most important bit is the regular expression part which would match any tag and find a respective closing tag and reminder afterward
If you just want to extract the text between <em> and <\/em> (note this is not normal HTML tags as then it would have been <em> and </em>) tags, we can simply capture this pattern and replace it with the group 1's value captured. And we don't need to worry about what is present around the matching text and just replace it with whatever got captured between those text which could actually be empty string, because OP hasn't mentioned any constraint for that. The regex for matching this pattern would be this,
<em>(.*?)<\\\/em>
OR to be technically more robust in taking care of optional spaces (as I saw someone pointing out in comment's of other answers) present any where within the tags, we can use this regex,
<\s*em\s*>(.*?)<\s*\\\/em\s*>
And replace it with \1 or $1 depending upon where you are doing it. Now whether these tags contain empty string, or contains some actual string within it, doesn't really matter as shown in my demo on regex101.
Here is the demo
Let me know if this meets your requirements and further, if any of your requirement remains unsatisfied.
I highly recommend the use of regex capture groups.
create your regex putting the name for the desired capture group:
let capturePattern = "(?<=<em>)(?<data1>\\w+)(?=<\\\\/em>)|(?<=<\\\\/em>)(?<data2>\\w+)"
now use the Swift capture pattern to get the data:
let captureRegex = try! NSRegularExpression(
pattern: capturePattern,
options: []
)
let textInput = "Fully <em>Furni<\/em>shed |Downtown and Canal Views"
let textInputRange = NSRange(
textInput.startIndex..<textInput.endIndex,
in: textInput
)
let matches = captureRegex.matches(
in: textInput,
options: [],
range: textInputRange
)
guard let match = matches.first else {
// Handle exception
throw NSError(domain: "", code: 0, userInfo: nil)
}
let data1Range = match.range(withName: "data1")
// Extract the substring matching the named capture group
if let substringRange = Range(data1Range, in: textInput) {
let capture = String(textInput[substringRange])
print(capture)
}
The same can be done to get the data2 group name:
let data2Range = match.range(withName: "data2")
if let substringRange = Range(data2Range, in: textInput) {
let capture = String(textInput[substringRange])
print(capture)
}
This method's main advantage is the group index independency. This makes this use less attached to the regex expression.
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]))
}
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
}
I want to extract substrings from a string that match a regex pattern.
The problem is this code : ("ytplayer.config = {(.*?)};").exec(responseStringC). It must match but return nil.
string.exec function :
extension String {
func exec (str: String) -> Array<String> {
do {
let regex = try NSRegularExpression(pattern: self, options: [.CaseInsensitive,.IgnoreMetacharacters])
let nsstr = str as NSString
let all = NSRange(location: 0, length: nsstr.length)
var matches : Array<String> = Array<String>()
regex.enumerateMatchesInString(str, options: NSMatchingOptions(rawValue: 0), range: all) {
(result : NSTextCheckingResult?, _, _) in
let theResult = nsstr.substringWithRange(result!.range)
matches.append(theResult)
}
return matches
} catch {
print("error")
return Array<String>()
}
}
}
"responseStringC" variable is :
http://pastebin.com/uvvq9ULA
the problem is return nil. Any Clue?
Your exec code contains .IgnoreMetacharacters flag:
Treat the entire pattern as a literal string.
Remove it so that the pattern could be treated as a regex pattern.
Also, a good idea is to use a DOTALL modifier (?s) at the start of the pattern.
Also, remember that a dot matches any character, escape it to match a literal dot.
As for the pattern, I'd recommend
"(?s)ytplayer\\.config = \\{(.*?)\\};"
Or a much faster:
"(?s)ytplayer\\.config = \\{([^}]*(?:\\}(?!;)[^}]*)*)\\};"
See the regex demo
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)!)