preg_match equivalent in Swift
The array contains one element because the input contains exactly one string "PT00042H42M42S" which matches the pattern.
If you want to retrieve the matching capture groups then you have to
use rangeAtIndex:
on the NSTextCheckingResult
. Example:
let pattern = "P(([0-9]+)Y)?(([0-9]+)M)?(([0-9]+)D)?T?(([0-9]+)H)?(([0-9]+)M)?(([0-9]+)(.[0-9]+)?S)?"
let regex = NSRegularExpression(pattern: pattern, options: nil, error: nil)!
let text = "PT00042H42M42S"
let nsString = text as NSString
if let result = regex.firstMatchInString(text, options: nil, range: NSMakeRange(0, nsString.length)) {
for i in 0 ..< result.numberOfRanges {
let range = result.rangeAtIndex(i)
if range.location != NSNotFound {
let substring = nsString.substringWithRange(result.rangeAtIndex(i))
println("\(i): \(substring)")
}
}
}
Result:
0: PT00042H42M42S
7: 00042H
8: 00042
9: 42M
10: 42
11: 42S
12: 42
Swift extract regex matches
Even if the matchesInString()
method takes a String
as the first argument,
it works internally with NSString
, and the range parameter must be given
using the NSString
length and not as the Swift string length. Otherwise it will
fail for "extended grapheme clusters" such as "flags".
As of Swift 4 (Xcode 9), the Swift standard
library provides functions to convert between Range<String.Index>
and NSRange
.
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 []
}
}
Example:
let string = "€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]
Note: The forced unwrap Range($0.range, in: text)!
is safe because
the NSRange
refers to a substring of the given string text
.
However, if you want to avoid it then use
return results.flatMap {
Range($0.range, in: text).map { String(text[$0]) }
}
instead.
(Older answer for Swift 3 and earlier:)
So you should convert the given Swift string to an NSString
and then extract the
ranges. The result will be converted to a Swift string array automatically.
(The code for Swift 1.2 can be found in the edit history.)
Swift 2 (Xcode 7.3.1) :
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 []
}
}
Example:
let string = "€4€9"
let matches = matchesForRegexInText("[0-9]", text: string)
print(matches)
// ["4", "9"]
Swift 3 (Xcode 8)
func matches(for regex: String, in text: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex)
let nsString = text as NSString
let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
return results.map { nsString.substring(with: $0.range)}
} catch let error {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
Example:
let string = "€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]
Equivalent of preg_match, that returns pretty (multidimensional) array
Interesting question! There's no such thing built-in, you'll have to capture offsets of matches and for each match check if its offset is within some other's bounds: something that reminds me of the nested sets model:
function match_tree($re, $subject) {
preg_match($re, $subject, $ms, PREG_OFFSET_CAPTURE);
$p = [(object)[]];
foreach($ms as $m) {
$m = (object) [
'le' => $m[1],
'ri' => $m[1] + strlen($m[0]) - 1,
'str' => $m[0]];
for($i = count($p) - 1; $i >= 0; $i--) {
if(!$i || ($m->le >= $p[$i]->le && $m->le <= $p[$i]->ri)) {
$p []= $m;
$p[$i]->sub []= $m;
break;
}
}
}
return $p[0];
}
For example,
print_r(match_tree('/(n(e?)c)(o)(f(o(abc)))/', 'XnecofoabcY'));
returns
stdClass Object
(
[sub] => Array
(
[0] => stdClass Object
(
[le] => 1
[ri] => 9
[str] => necofoabc
[sub] => Array
(
[0] => stdClass Object
(
[le] => 1
[ri] => 3
[str] => nec
[sub] => Array
(
[0] => stdClass Object
(
[le] => 2
[ri] => 2
[str] => e
)
)
)
[1] => stdClass Object
(
[le] => 4
[ri] => 4
[str] => o
)
[2] => stdClass Object
(
[le] => 5
[ri] => 9
[str] => foabc
[sub] => Array
(
[0] => stdClass Object
(
[le] => 6
[ri] => 9
[str] => oabc
[sub] => Array
(
[0] => stdClass Object
(
[le] => 7
[ri] => 9
[str] => abc
)
)
)
)
)
)
)
)
)
Check if string contains special characters in Swift
Your code check if no character in the string is from the given set.
What you want is to check if any character is not in the given set:
if (searchTerm!.rangeOfCharacterFromSet(characterSet.invertedSet).location != NSNotFound){
println("Could not handle special characters")
}
You can also achieve this using regular expressions:
let regex = NSRegularExpression(pattern: ".*[^A-Za-z0-9].*", options: nil, error: nil)!
if regex.firstMatchInString(searchTerm!, options: nil, range: NSMakeRange(0, searchTerm!.length)) != nil {
println("could not handle special characters")
}
The pattern [^A-Za-z0-9]
matches a character which is not from the ranges A-Z,
a-z, or 0-9.
Update for Swift 2:
let searchTerm = "a+b"
let characterset = NSCharacterSet(charactersInString: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
if searchTerm.rangeOfCharacterFromSet(characterset.invertedSet) != nil {
print("string contains special characters")
}
Update for Swift 3:
let characterset = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
if searchTerm.rangeOfCharacter(from: characterset.inverted) != nil {
print("string contains special characters")
}
RegularExpression matchesInString issue in Swift
Try assigning to your results
variable like this:
var results = regex.matchesInString(text, options: nil, range: NSMakeRange(0, countElements(text))) as Array<NSTextCheckingResult>
the return type is
Array<AnyObject>[]!
, you can cast here (as in the above example) or later when you check the members of the collectionin Swift options take
nil
to represent an empty option set (vs.0
in Objective-C)
Regex not matching string correctly
The (?=r\/.*?(\w+))
pattern is a positive lookahead that tests each location inside a string and returns a match whenever that position is followed with r/
, any 0+ chars other than line break chars as few as possible and then 1+ word chars that are captured into Group 1. You may actually grab the value if you access Group 1, but you may still keep using your code if you just change the pattern to
"(?<=\\br/)\\w+"
The pattern will check for the r/
(as a whole word) and will only return 1+ word chars that stand to the right of this char sequence.
See the online regex demo.
Pattern details
(?<=\br/)
- a positive lookbehind that requires the presence of a\b
- word boundary, followed withr/
- anr/
char sequence
... immediately to the left of the current location
\w+
- 1 or more word chars (letters, digits or_
char).
Swift Regex for non consecutive numbers and non repeating digits
Instead of a giant Regex, I would suggest:
- Define a protocol:
protocol ValidationRule {
func isValid(for number: Int) -> Bool
}
- Define a String extension to validate a given set of rules:
extension String {
func isValid(rules: [ValidationRule]) -> Bool {
guard let number = Int(self) else { return false }
for rule in rules {
if !rule.isValid(for: number) { return false }
}
return true
}
}
And then you can implement as many rules as needed. Example:
class FourDigitNumberRule: ValidationRule {
let allowedRange = 1000...9999
func isValid(for number: Int) -> Bool {
return allowedRange.contains(number)
}
}
class NoConsecutiveDigitsRule: ValidationRule {
func isValid(for number: Int) -> Bool {
let coef = 10
var remainder = number
var curr: Int? = nil
var prev: Int? = nil
var diff: Int?
while remainder > 0 {
defer { remainder = Int(remainder / coef) }
prev = curr
curr = remainder % coef
guard let p = prev, let c = curr else { continue }
let lastDiff = diff
diff = p - c
guard let ld = lastDiff else { continue }
if ld != diff { return true }
if diff != 1 && diff != -1 { return true }
}
return false
}
}
class NotYearRule: ValidationRule {
func isValid(for number: Int) -> Bool {
let hundreds = number / 100
if hundreds == 19 || hundreds == 20 {
return false
}
return true
}
}
class NonRepeatRule: ValidationRule {
func isValid(for number: Int) -> Bool {
let coef = 10
var map = [Int: Int]()
for i in 0...9 {
map[i] = 0
}
var remainder = number
while remainder > 0 {
let i = remainder % coef
map[i]! += 1
remainder = Int(remainder / coef)
}
for i in 0...9 {
if map[i]! > 2 { return false }
}
return true
}
}
And then use it like this:
let rules: [ValidationRule] = [ FourDigitNumberRule(), NoConsecutiveDigitsRule(), NotYearRule(), NonRepeatRule() ]
string.isValid(rules: rules)
Test:
let rules: [ValidationRule] = [ FourDigitNumberRule(), NoConsecutiveDigitsRule(), NotYearRule(), NonRepeatRule() ]
var test = [ "1234", "1912", "1112", "7845", "274", "14374" ]
for string in test {
let isValid = string.isValid(rules: rules) ? "valid" : "invalid"
print("\(string) is \(isValid)")
}
Output:
1234 is invalid
1912 is invalid
1112 is invalid
7845 is valid
274 is invalid
14374 is invalid
Advantages: this provides a better maintainability, and is easier to read than a giant regex you already have trouble writing and understanding...
Plus you can use the same approach for any other validation you may ever need.
Regex not working for empty string - Swift
If input
is the empty string then leftover
will be the empty string
as well, and therefore your function returns true
. Another case where
your approach fails is
print(test("12345671234567")) // true (expected: false)
An alternative is to use the range(of:)
method of String
with the .regularExpression
option. Then check if the matched range is the entire string.
In order to match 7 digits (and not 7 arbitrary characters), the
pattern should be \d{7}
.
func test(_ input: String) -> Bool {
let pattern = "\\d{7}"
return input.range(of: pattern, options: [.regularExpression, .caseInsensitive])
== input.startIndex..<input.endIndex
}
Related Topics
Make a Path Work Both on Linux and Windows
Checking If Process Still Running
Add PHP Variable Inside Echo Statement as Href Link Address
Download a File from Ftp Using Curl and PHP
Best Way to Avoid Code Injection in PHP
How to Use Composer Packages in Codeigniter
Remotely Destroy a Session in PHP (User Logs in Somewhere Else)
What Is the Best Practice to Use When Using PHP and HTML
Laravel 4 Removing Public from Url
Is Multiple Inheritance Allowed at Class Level in PHP
Getting All Values from H1 Tags Using PHP
Jquery $.Ajax Request of Datatype JSON Will Not Retrieve Data from PHP Script
Deny Ajax File Access Using Htaccess
Phpmyadmin Error: the Mbstring Extension Is Missing. Please Check Your PHP Configuration
Strcmp Equivelant for Integers (Intcmp) in PHP
Malformed Utf-8 Characters, Possibly Incorrectly Encoded' in Laravel
How to Define a Laravel Route with a Parameter That Contains a Slash Character