Is it possible to take just a part of the HTML string that I have obtained passing the URL?
Example code below:
let myURLString = "https://myUrl.something"
guard let myURL = NSURL(string: myURLString) else {
print("Error: \(myURLString) doesn't seem to be a valid URL")
return
}
do {
let myHTMLString = try String(contentsOf: myURL as URL)
let htmlString = String(myHTMLString)
print("HTML: \(myHTMLString)")
} catch let error as NSError {
print("Error: \(error)")
}
I want to take what's inside the tag <h3 class="post-title"> to </h3>.
I know that I should use the regular expressions but I don't really know how to set it in the right way. I tried something like this:
let myURLString = "https://www.fvgtech.it/category/podcast/"
guard let myURL = NSURL(string: myURLString) else {
print("Error: \(myURLString) doesn't seem to be a valid URL")
return
}
do {
let myHTMLString = try String(contentsOf: myURL as URL)
let htmlString = String(myHTMLString)
if let match = htmlString.range(of: "(<h3.+)", options: .regularExpression) {
print("Found",htmlString.substring(with: match))
}
print("HTML: \(myHTMLString)")
} catch let error as NSError {
print("Error: \(error)")
}
But it's printing just <h3 class="post-title"> and not what's in the middle. Thanks in advance!
Just we need to search all substrings between start String and end String See Extension of String
let myURLString = "https://www.fvgtech.it/category/podcast/"
guard let myURL = NSURL(string: myURLString) else {
print("Error: \(myURLString) doesn't seem to be a valid URL")
return
}
do {
let myHTMLString = try String(contentsOf: myURL as URL)
let htmlString = String(myHTMLString)
print(htmlString.allStringsBetween("<h3 class=\"post-title\">", andString: "</h3>"))
} catch let error as NSError {
print("Error: \(error)")
}
Extension for String
extension String{
func allStringsBetween(start: String, end: String) -> [Any] {
var strings = [Any]()
var startRange: NSRange = (self as NSString).range(of: start)
while true {
if startRange.location != NSNotFound {
var targetRange = NSRange()
targetRange.location = startRange.location + startRange.length
targetRange.length = self.count - targetRange.location
let endRange: NSRange = (self as NSString).range(of: end, options: [], range: targetRange)
if endRange.location != NSNotFound {
targetRange.length = endRange.location - targetRange.location
strings.append((self as NSString).substring(with: targetRange))
var restOfString = NSRange()
restOfString.location = endRange.location + endRange.length
restOfString.length = self.count - restOfString.location
startRange = (self as NSString).range(of: start, options: [], range: restOfString)
}
else {
break
}
}
else {
break
}
}
return strings
}
}
Related
I have a ShareExtension in which I like need to get the current URL. This is my function for it:
var html: String?
if let item = extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = item.attachments?.first,
itemProvider.hasItemConformingToTypeIdentifier("public.url") {
itemProvider.loadItem(forTypeIdentifier: "public.url", options: nil) { (url, error) in
if (url as? URL) != nil {
html = (self.getHTMLfromURL(url: url as? URL))
}
}
}
My problem is that I need the html but when using that variable right after that function html is still empty. I think I need some sort of completion handler but I tried different things now and can not get it right...
This is how my whole function looks like at the moment (not working, as html becomes an empty String)
#objc func actionButtonTapped(){
do {
var html: String?
if let item = extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = item.attachments?.first,
itemProvider.hasItemConformingToTypeIdentifier("public.url") {
itemProvider.loadItem(forTypeIdentifier: "public.url", options: nil) { (url, error) in
if (url as? URL) != nil {
html = (self.getHTMLfromURL(url: url as? URL))
}
}
}
let doc: Document = try SwiftSoup.parse(html ?? "")
let priceClasses: Elements = try doc.select("[class~=(?i)price]")
for priceClass: Element in priceClasses.array() {
let priceText : String = try priceClass.text()
print(try priceClass.className())
print("pricetext: \(priceText)")
}
let srcs: Elements = try doc.select("img[src]")
let srcsStringArray: [String?] = srcs.array().map { try? $0.attr("src").description }
for imageName in srcsStringArray {
print(imageName!)
}
} catch Exception.Error( _, let message) {
print(message)
} catch {
print("error")
}
}
The Goal is to have a extra function to get the url (1st code example) with a completion handler in which I can work with the created html.
the problem was that I didn't realize that I already had a completionHandler with loadItems. So what I did now was to put the whole do & catch block in another method and called it in the completion handler like this:
#objc func actionButtonTapped(){
var html: String?
if let item = extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = item.attachments?.first,
itemProvider.hasItemConformingToTypeIdentifier("public.url") {
itemProvider.loadItem(forTypeIdentifier: "public.url", options: nil) { (url, error) in
if (url as? URL) != nil {
html = (self.getHTMLfromURL(url: url as? URL))
print("bruh")
self.doStuff(html: html)
}
}
}
}
func doStuff(html: String?){
do {
let doc: Document = try SwiftSoup.parse(html ?? "")
let priceClasses: Elements? = try doc.select("[class~=(?i)price]")
for priceClass: Element in priceClasses!.array() {
let priceText : String = try priceClass.text()
print(try priceClass.className())
print("pricetext: \(priceText)")
}
let srcs: Elements = try doc.select("img[src]")
let srcsStringArray: [String?] = srcs.array().map { try? $0.attr("src").description }
for imageName in srcsStringArray {
print(imageName!)
}
} catch Exception.Error( _, let message) {
print(message)
} catch {
print("error")
}
}
I am trying to get parameters for urls and I try to get only the date not time the URL.
http://aa.no-ip.biz:8001/hf_tracker/api/history.php?accesskey=12345&Vehilce=1618&FromDate=2018-05-10 13:11&ToDate=2018-05-14 12:11
Code:
extension URL {
func valueOf(_ queryParameterName: String) -> String? {
guard let url = URLComponents(string: self.absoluteString) else {
return nil
}
return url.queryItems?.first(where: { $0.name == queryParameterName})?.value
}
}
let newURL = URL(string: "assetlinkasia.no-ip.biz:8001/hf_tracker/api/…)!
newURL.valueOf("toDate")
newURL.valueOf("fromDate")
How can I only get the date and not time?
This is the way you can do it,
Your URL extension here, from here
extension URL {
func valueOf(_ queryParamaterName: String) -> String? {
guard let url = URLComponents(string: self.absoluteString) else { return nil }
return url.queryItems?.first(where: { $0.name == queryParamaterName })?.value
}
}
Your code goes here,
let string = "http://aa.no-ip.biz:8001/hf_tracker/api/history.php?accesskey=12345&Vehilce=1618&FromDate=2018-05-10 13:11&ToDate=2018-05-14 12:11"
let test = string.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
let url = URL(string: test!)!
let fromDate = url.valueOf("FromDate")
let toDate = url.valueOf("ToDate")
let date1 = fromDate?.components(separatedBy: " ").first
print(date1)
let date2 = toDate?.components(separatedBy: " ").first
print(date2 )
Output will be below,
2018-05-10
2018-05-14
In my apps a use a helper function that parses the url and returns an optional dictionary:
func parameters(for url: URL) -> [String: String]? {
guard let urlQuery = url.query else { return nil }
// Create all parameters dictionary
let queryArray = urlQuery.split { $0 == "&" }.map(String.init)
var parametersDict: [String: String] = [:]
for queryParameter in queryArray {
// split the queryParam into key / value
let keyValueArray = queryParameter.split{ $0 == "=" }.map(String.init)
let key = keyValueArray.first!
let value = keyValueArray.last!.removingPercentEncoding!
parametersDict.updateValue(value, forKey: key)
}
return parametersDict
}
And use it like this:
let params = parameters(for: URL(string: "https://someurl.com?name=john")!)
if let name = params?["name"] {
print(name)
}
Or you can add a var to URL extension:
extension URL {
var parameters: [String: String]? {
guard let urlQuery = self.query else { return nil }
// Create all parameters dictionary
let queryArray = urlQuery.split { $0 == "&" }.map(String.init)
var parametersDict: [String: String] = [:]
for queryParameter in queryArray {
// split the queryParam into key / value
let keyValueArray = queryParameter.split{ $0 == "=" }.map(String.init)
let key = keyValueArray.first!
let value = keyValueArray.last!.removingPercentEncoding!
parametersDict.updateValue(value, forKey: key)
}
return parametersDict
}
}
And get the parameter:
let params = URL(string: "https://someurl.com?name=john")!.parameters
if let name = params?["name"] {
print(name)
}
I have a URL String "http:///blaBla?id=Testid851211" and I just want to get "851211".
Below is my code :-
let url: NSURL = NSURL(string: urlString)!
Helper.sharedInstance.Print(url.query as AnyObject)
if (url.query?.localizedStandardContains("testKey"))! {
//TestKey
Helper.sharedInstance.Print(url.query as AnyObject)
let testValue = getQueryStringParameter(url: urlString, param: "testKey")
Helper.sharedInstance.Print(testValue as AnyObject)
}
else if (url.query?.localizedStandardContains("testID"))! {
//TestID
Helper.sharedInstance.Print(url.query as AnyObject)
}
func getQueryStringParameter(url: String, param: String) -> String? {
guard let url = URLComponents(string: url) else { return nil }
return url.queryItems?.first(where: { $0.name == param })?.value
}
I am getting id = Testid851211 but I want only "851211".
Use a regular expression filtering out numbers only:
let urlString = "http:///blaBla?id=Testid851211"
let pattern = "[0-9]+"
if let matchRange = urlString.range(of: pattern, options: .regularExpression) {
print(urlString[matchRange])
}
Works as long as your URLs don’t have numbers anywhere else.
Try this extension:
extension String {
func getNeededText(for url: String) -> String {
guard range(of: url) != nil else { return "" }
return replacingOccurrences(of: url, with: "")
}
}
Usage:
let predefinedHost = "http:///blaBla?id=Testid"
let url = "http:///blaBla?id=Testid851211"
url.getNeededText(for: predefinedHost) // prints "851211"
I've written a function that should return a value but the value comes from a closure. The problem is if I try to return a value from inside the closure it treats this as being the return value from the completion handler.
private func loadData() throws -> [Item] {
var items = [Item]()
let jsonUrl = "http://api.openweathermap.org/data/2.5/forecast/daily?units=metric&cnt=7&q=coventry,uk"
print(jsonUrl)
let session = NSURLSession.sharedSession()
guard let shotsUrl = NSURL(string: jsonUrl) else {
throw JSONError.InvalidURL(jsonUrl)
}
session.dataTaskWithURL(shotsUrl, completionHandler: {(data, response, error) -> Void in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: [])
print(json)
guard let days:[AnyObject] = (json["list"] as! [AnyObject]) else {
throw JSONError.InvalidArray
}
for day in days {
guard let timestamp:Double = day["dt"] as? Double else {
throw JSONError.InvalidKey("dt")
}
print(timestamp)
let date = NSDate(timeIntervalSince1970: NSTimeInterval(timestamp))
guard let weather:[AnyObject] = day["weather"] as? [AnyObject] else {
throw JSONError.InvalidArray
}
guard let desc:String = weather[0]["description"] as? String else {
throw JSONError.InvalidKey("description")
}
guard let icon:String = weather[0]["icon"] as? String else {
throw JSONError.InvalidKey("icon")
}
guard let url = NSURL(string: "http://openweathermap.org/img/w/\(icon).png") else {
throw JSONError.InvalidURL("http://openweathermap.org/img/w/\(icon).png")
}
guard let data = NSData(contentsOfURL: url) else {
throw JSONError.InvalidData
}
guard let image = UIImage(data: data) else {
throw JSONError.InvalidImage
}
guard let temp:AnyObject = day["temp"] else {
throw JSONError.InvalidKey("temp")
}
guard let max:Float = temp["max"] as? Float else {
throw JSONError.InvalidKey("max")
}
let newDay = Item(date: date, description: desc, maxTemp: max, icon: image)
print(newDay)
items.append(newDay)
}
return items // this line fails because I'm in the closure. I want this to be the value returned by the loadData() function.
} catch {
print("Fetch failed: \((error as NSError).localizedDescription)")
}
})
}
Add a completion handler (named dataHandler in my example) to your loadData function:
private func loadData(dataHandler: ([Item])->()) throws {
var items = [Item]()
let jsonUrl = "http://api.openweathermap.org/data/2.5/forecast/daily?units=metric&cnt=7&q=coventry,uk"
print(jsonUrl)
let session = NSURLSession.sharedSession()
guard let shotsUrl = NSURL(string: jsonUrl) else {
throw JSONError.InvalidURL(jsonUrl)
}
session.dataTaskWithURL(shotsUrl, completionHandler: {(data, response, error) -> Void in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: [])
print(json)
guard let days:[AnyObject] = (json["list"] as! [AnyObject]) else {
throw JSONError.InvalidArray
}
for day in days {
guard let timestamp:Double = day["dt"] as? Double else {
throw JSONError.InvalidKey("dt")
}
print(timestamp)
let date = NSDate(timeIntervalSince1970: NSTimeInterval(timestamp))
guard let weather:[AnyObject] = day["weather"] as? [AnyObject] else {
throw JSONError.InvalidArray
}
guard let desc:String = weather[0]["description"] as? String else {
throw JSONError.InvalidKey("description")
}
guard let icon:String = weather[0]["icon"] as? String else {
throw JSONError.InvalidKey("icon")
}
guard let url = NSURL(string: "http://openweathermap.org/img/w/\(icon).png") else {
throw JSONError.InvalidURL("http://openweathermap.org/img/w/\(icon).png")
}
guard let data = NSData(contentsOfURL: url) else {
throw JSONError.InvalidData
}
guard let image = UIImage(data: data) else {
throw JSONError.InvalidImage
}
guard let temp:AnyObject = day["temp"] else {
throw JSONError.InvalidKey("temp")
}
guard let max:Float = temp["max"] as? Float else {
throw JSONError.InvalidKey("max")
}
let newDay = Item(date: date, description: desc, maxTemp: max, icon: image)
print(newDay)
items.append(newDay)
}
dataHandler(items)
} catch {
print("Fetch failed: \((error as NSError).localizedDescription)")
}
}).resume()
}
do {
try loadData { itemsArray in
print(itemsArray)
}
} catch {
print(error)
}
I've tested it in a Playground and it works without errors:
I try to get URLs in text. So, before, I used such an expression:
let re = NSRegularExpression(pattern: "https?:\\/.*", options: nil, error: nil)!
But I had a problem when a user input URLs with Capitalized symbols (like Http://Google.com, it doesn't match it).
I tried:
let re = NSRegularExpression(pattern: "(h|H)(t|T)(t|T)(p|P)s?:\\/.*", options: nil, error: nil)!
But nothing happened.
You turn off case sensitivity using an i inline flag in regex, see Foundation Framework Reference for more information on available regex features.
(?ismwx-ismwx)
Flag settings. Change the flag settings. Changes apply to the portion of the pattern following the setting. For example, (?i) changes to a case insensitive match.The flags are defined in Flag Options.
For readers:
Matching an URL inside larger texts is already a solved problem, but for this case, a simple regex like
(?i)https?://(?:www\\.)?\\S+(?:/|\\b)
will do as OP requires to match only the URLs that start with http or https or HTTPs, etc.
Swift 4
1. Create String extension
import Foundation
extension String {
var isValidURL: Bool {
guard !contains("..") else { return false }
let head = "((http|https)://)?([(w|W)]{3}+\\.)?"
let tail = "\\.+[A-Za-z]{2,3}+(\\.)?+(/(.)*)?"
let urlRegEx = head+"+(.)+"+tail
let urlTest = NSPredicate(format:"SELF MATCHES %#", urlRegEx)
return urlTest.evaluate(with: trimmingCharacters(in: .whitespaces))
}
}
2. Usage
"www.google.com".isValidURL
Try this - http?://([-\w\.]+)+(:\d+)?(/([\w/_\.]*(\?\S+)?)?)?
let pattern = "http?://([-\\w\\.]+)+(:\\d+)?(/([\\w/_\\.]*(\\?\\S+)?)?)?"
var matches = [String]()
do {
let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions(rawValue: 0))
let nsstr = text as NSString
let all = NSRange(location: 0, length: nsstr.length)
regex.enumerateMatchesInString(text, options: NSMatchingOptions.init(rawValue: 0), range: all, usingBlock: { (result, flags, _) in
matches.append(nsstr.substringWithRange(result!.range))
})
} catch {
return [String]()
}
return matches
Make an exension of string
extension String {
var isAlphanumeric: Bool {
return rangeOfString( "^[wW]{3}+.[a-zA-Z]{3,}+.[a-z]{2,}", options: .RegularExpressionSearch) != nil
}
}
call using like this
"www.testsite.edu".isAlphanumeric // true
"flsd.testsite.com".isAlphanumeric //false
My complex solution for Swift 5.x
ViewController:
private func loadUrl(_ urlString: String) {
guard let url = URL(string: urlString) else { return }
let request = URLRequest(url: url)
webView.load(request)
}
UISearchBarDelegate:
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let text = searchBar.text else { return }
if !text.isUrl() {
let finalUrl = String(format: "%#%#", "https://www.google.com/search?q=", text)
loadUrl(finalUrl)
return
}
if text.starts(with: "https://") || text.starts(with: "http://") {
loadUrl(text)
return
}
let finalUrl = String(format: "%#%#", "https://", text)
loadUrl(finalUrl)
}
String extension:
extension String {
func isUrl() -> Bool {
guard !contains("..") else { return false }
let regex = "((http|https)://)?([(w|W)]{3}+\\.)?+(.)+\\.+[A-Za-z]{2,3}+(\\.)?+(/(.)*)?"
let urlTest = NSPredicate(format:"SELF MATCHES %#", regex)
return urlTest.evaluate(with: trimmingCharacters(in: .whitespaces))
}
}