Convert a JavaScript Regex to a Swift Regex - regex

I'm learning Swift, and I'm trying to convert a small bit of JavaScript code to Swift. The JavaScript code uses a Regex to split up a string, as shown below:
var text = "blah.clah##something_else";
var parts = text.match(/(^.*?)\#\#(.+$)/);
after execution, the parts array will then contain the following:
["blah.clah##something_else", "blah.clah", "something_else"]
I would like to replicate the same behavior in Swift. Below is the Swift code I've written to do split up a String into a String array using a Regex:
func matchesForRegexInText(regex: String!, text: String!) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex, options: NSRegularExpressionOptions.CaseInsensitive)
let nsString = text as NSString
let results = regex.matchesInString(text,
options: NSMatchingOptions.ReportCompletion , range: NSMakeRange(0, nsString.length))
as [NSTextCheckingResult]
return results.map({
nsString.substringWithRange($0.range)
})
} catch {
print("exception")
return [""]
}
}
When I call the above function with the following:
matchesForRegexInText("(^.*?)\\#\\#(.+$)", text: "blah.clah##something_else")
I get the following:
["blah.clah##something_else"]
I've tried a number of different Regex's without success. Is the Regex (^.*?)\#\#(.+$) correct, or is there a problem with the matchesForRegexInText() function? I appreciate any insight.
I'm using Swift 2, and Xcode Version 7.0 beta (7A120f)

As already mentioned in a comment, your pattern matches the entire
string, so regex.matchesInString() returns a single
NSTextCheckingResult whose range describes the entire string.
What you are looking for are the substrings matching the capture groups
in your pattern. These are available as rangeAtIndex(i) with i >= 1:
func matchesForRegexInText(regex: String!, text: String!) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex, options: [])
let nsString = text as NSString
guard let result = regex.firstMatchInString(text, options: [], range: NSMakeRange(0, nsString.length)) else {
return [] // pattern does not match the string
}
return (1 ..< result.numberOfRanges).map {
nsString.substringWithRange(result.rangeAtIndex($0))
}
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
Example:
let matches = matchesForRegexInText("(^.*?)##(.+$)", text: "blah.clah##something_else")
print(matches)
// [blah.clah, something_else]

Related

Cannot call value of non function type '[String:AnyObject]'

I'm facing the issue in Swift 3
I have following piece of code:
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject]
if let datasFromJson = json["blog"] as? [[String:AnyObject]] {
for dataFromJson in datasFromJson{
if let title = dataFromJson("title")! as? String {
article.author = author
}
self.articles?.append(article)
}
}
I get this error when I try to cast title as string
Typo (brackets, not parentheses):
dataFromJson["title"] as? String // no exclamation mark after the closing bracket
Notes:
.mutableContainers is useless in Swift.
In Swift 3 JSON dictionary is [String:Any]
Is title used at all? Or is it another typo title vs. author?

Need help creating a regex pattern for getting an image

I made an RSS reader and I'm trying to get to display a preview image too.
Here's what I'm using to get the image and the only thing that's not working is the pattern
if item?.content != nil {
print("works until here")
let htmlContent = item!.content as NSString
var imageSource = ""
let rangeOfString = NSMakeRange(0, htmlContent.length)
let regex = try! NSRegularExpression(pattern: "(http[^\\s]+(jpg|jpeg|png|tiff)\\b)", options: .caseInsensitive)
if htmlContent.length > 0 {
let match = regex.firstMatch(in: htmlContent as String, options: [], range: rangeOfString)
if match != nil {
let imageURL = htmlContent.substring(with: (match!.rangeAt(2))) as NSString
print(imageURL)
if NSString(string: imageURL.lowercased).range(of: "feedburner").location == NSNotFound {
imageSource = imageURL as String
}
}
}
if imageSource != "" {
cell.itemImageView.setImageWith(NSURL(string: imageSource) as URL!, placeholderImage: UIImage(named: "thumbnail"))
}else {
cell.itemImageView.image = UIImage(named: "thumbnail")
}
}
I need help creating a good pattern for getting the image from "st-gallery" class of the travelator.ro website.
Many thanks in advance. :)
Regular expressions can't parse HTML. Regular expressions recognize the set of Regular Languages. HTML is a context-free language, which is higher on the Chomsky Hierarchy. Regular expressions can't recognize context free languages.
You would need to use a more complicated parser. HTML parsing libraries have done this, I suggest you look at using one of those.

allow only letters for User Name

I'm trying to limit the users First_Name to allow only letters so it should respond with an error for numbers or special characters after clicking on send_button. I found some examples here but because they are build on older swift version I'm having problems to make it work. Until now I've managed to read the First Name and throw an error if the first element on this textfield is not a letter but the code allow things like this (First name = "J123g") or ("Mark##$") and I don't want this to be the case.
func isOneLetter(in text: String) -> Bool {
do {
let regex = try NSRegularExpression(pattern: "[a-zA-Z]")
let nsString = text as NSString
let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
let temp = results.map { nsString.substring(with: $0.range)}
return temp.isEmpty
} catch let error {
print("invalid regex: \(error.localizedDescription)")
return false
}
}
#IBAction func Send_Button(_ sender: AnyObject) {
let UserName = First_Name.text
if (isOneLetter(in: UserName!))
{
DisplayMyAlertMessage(userMessage: "First Name must contain only letter and spaces")
}
}
Your regex pattern checks for one alphanumeric character which matches both unwanted examples.
This regex checks for from beginning (^) to the end ($) of the string there must be one or more (+) alphanumeric characters ([a-zA-Z]). The benefit is that it treats an empty string also as bad.
^[a-zA-Z]+$
1st you have to inherit the UITextViewDelegate class with you own
class
class ViewController: UIViewController, UITextViewDelegate {
2nd add an IBOutlet
#IBOutlet weak var firstName: UITextField!
3rd you have to assure this object is using
override func viewDidLoad() {
super.viewDidLoad()
firstName.delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == firstName {
let allowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
let allowedCharacterSet = CharacterSet(charactersIn: allowedCharacters)
let typedCharacterSet = CharacterSet(charactersIn: string)
let alphabet = allowedCharacterSet.isSuperset(of: typedCharacterSet)
return alphabet
}
}
Another way could be:
let userInput = ""
let set = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLKMNOPQRSTUVWXYZ")
if userInput.rangeOfCharacter(from: set.inverted) != nil {
print("ERROR: There are numbers included!")
}
Updated for swift 3:
if you want to validate name and allow only letters for User Name then used below simple lines of code :
// function definition:
func isValidName(_ nameString: String) -> Bool {
var returnValue = true
let mobileRegEx = "[A-Za-z]{3}" // {3} -> at least 3 alphabet are compulsory.
do {
let regex = try NSRegularExpression(pattern: mobileRegEx)
let nsString = nameString as NSString
let results = regex.matches(in: nameString, range: NSRange(location: 0, length: nsString.length))
if results.count == 0
{
returnValue = false
}
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
returnValue = false
}
return returnValue
}
// function call :
var firstName = mEnterFirstNameTextField.text!
let isFirstNameValid = isValidName(firstName)
if isFirstNameValid{
// do user logic
}else{
// show error msg: -> "Enter name is not valid, please enter again..."
}

Finding text between parentheses in Swift

How do you get an array of string values for the text between parentheses in Swift?
For example from: MyFileName(2015)(Type)(createdBy).zip
I would like: [2015,Type,createdBy]
Just updating the chosen answer to Swift 3:
func matchesForRegexInText(regex: String!, text: String!) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex, options: [])
let nsString = text as NSString
let results = regex.matches(in: text,
options: [], range: NSMakeRange(0, nsString.length))
return results.map { nsString.substring(with: $0.range)}
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
return []
}}
The usage remains the same.
Here is a complete example in Swift 4.2
func matchesForRegexInText(regex: String!, text: String!) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex, options: [])
let nsString = text as NSString
let results = regex.matches(in: text,
options: [], range: NSMakeRange(0, nsString.length))
return results.map { nsString.substring(with: $0.range)}
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
return []
}}
and usage :
let regex = "\\((.*?)\\)"
mmatchesForRegexInText(regex: regex, text: " exmaple (android) of (qwe123) text (heart) between parentheses")
You can use a regex for this
Thomas had a good example: \((.*?)\)
How to use a regex with Swift you can look up at: http://www.raywenderlich.com/86205/nsregularexpression-swift-tutorial
Here is my RegEx
which is actually trying to get the words between parentheses. E.g. (smile)
NSRegularExpression(pattern: "\\\\(\\\w+\\\\)",options:NSRegularExpressionOptions.CaseInsensitive)
it works for me!

Swift-Rss project-Can't get img src link inside CDATA blocks

I have a very annoying problem. I am developing an RSS Reader for Swift(with Xcode 7.1). i want each cell of my tableview show images for each news. Here is my code:
cell.itemImageView.image = UIImage(named: "placeholder")
let news = items[indexPath.row] as MWFeedItem?
if news?.content != nil {
let htmlContent = news!.content as NSString
var imageSource = ""
let rangeOfString = NSMakeRange(0, htmlContent.length)
let regex = try? NSRegularExpression(pattern: "(<img.*?src=\")(.*?)(\".*?>)", options: [])
if htmlContent.length > 0 {
let match = regex?.firstMatchInString(htmlContent as String, options: [], range: rangeOfString)
if match != nil {
let imageURL = htmlContent.substringWithRange(match!.rangeAtIndex(2)) as NSString
print(imageURL)
if NSString(string: imageURL.lowercaseString).rangeOfString("feedburner").location == NSNotFound {
imageSource = imageURL as String
}
}
}
if imageSource != "" {
cell.itemImageView.setImageWithURL(NSURL(string: imageSource)!, placeholderImage: UIImage(named: "placeholder"))
}
else{
cell.itemImageView.image = UIImage(named: "placeholder")
}
}
So, the problem is that: when the rss feed xml file doesn't have CDATA blocks, my code works perfectly; in other most cases it doesn't work because inside xml file there is a structure like this:
<![CDATA[<p> <img src="http://www.repstatic.it/content/nazionale/img/2015/11/12/115530091-51ce67c2-7b38-41c1-8aa5-21d51b157335.jpg" width="140" align="left" hspace="10">I genitori contro la scelta del consiglio interclasse delle terze elementari dell'istituto Matteotti di fermare la gita all'esposizione "Divina Bellezza" sul...</p>]]></description><guid isPermaLink="true"><!
It's clear that CDATA block doesn't let me read img src link. What can i do?
Thank in advance for your help!
I run the following code in the PlayGround using your regex and successfully got all the img src urls from the xml.
import Foundation
let url = NSURL(string: "http://www.repubblica.it/rss/homepage/rss2.0.xml")!
let xml = try String(contentsOfURL: url)
let regex = try NSRegularExpression(pattern: "(<img.*?src=\")(.*?)(\".*?>)", options: [])
let range = NSMakeRange(0, xml.characters.count)
regex.enumerateMatchesInString(xml, options: [], range: range) { (result, _, _) -> Void in
let nsrange = result!.rangeAtIndex(2)
let start = xml.startIndex.advancedBy(nsrange.location)
let end = start.advancedBy(nsrange.length)
print(xml[start..<end])
}