I'm reasonably new to Swift and I'm trying to read data from a web service. The data is returned and according to the parser it was parsed successfully. All I have at the moment is when the data is returned it's to be placed in a textview and it does do this successfully. But the returned XML is not being parsed. I have breakpoints in all 4 parser functions but none of them are being hit. It's as though they are not being fired. Here's my code (this is just playing at the moment so please be nice :) )
import UIKit
import Foundation
class ViewController: UIViewController, XMLParserDelegate{
var currentElementName:String = ""
var foundCharacters = ""
var parser = XMLParser()
var is_SoapMessage: String = "<?xml version='1.0' encoding='utf-8'?><soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'><soap:Body></soap:Body></soap:Envelope>"
override func viewDidLoad() {
super.viewDidLoad()
parser.delegate = self
//clear all arrays
let Emp = EmployeeDetails()
Emp.ID.removeAll()
Emp.Name.removeAll()
Emp.Mobile.removeAll()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func GetServiceBtn(_ sender: Any) {
Test1()
}
//Text box to see what's returned
#IBOutlet weak var OutputTxt: UITextView!
//First test of using web services
func Test1(){
let URL: String = "http://192.168.1.106:8080/Service.asmx"
let WebRequ = NSMutableURLRequest(url: NSURL(string: URL)! as URL)
let session = URLSession.shared
WebRequ.httpMethod = "POST"
WebRequ.httpBody = is_SoapMessage.data(using: String.Encoding.utf8)
WebRequ.addValue("text/xml; charset=utf-8", forHTTPHeaderField: "Content-Type")
WebRequ.addValue(String(is_SoapMessage), forHTTPHeaderField: "Content-Length")
WebRequ.addValue("MyServices/GetEmployeesFullNames", forHTTPHeaderField: "SOAPAction")
var Str: String = ""
let task = session.dataTask(with: WebRequ as URLRequest, completionHandler: {data, response, error -> Void in
let strData = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
Str = String(strData!) as String
if Str != ""{
self.PopulateTxt(Detail: Str)
self.ReadEmployeeResults(Data: data!)
print(Str)
}
if error != nil
{
print("Error: " + error.debugDescription)
}
})
task.resume()
}
//Process returned data
func ReadEmployeeResults(Data: Data){
self.parser = XMLParser(data: Data)
let success:Bool = parser.parse()
if success {
print("parse success!")
let Emp = EmployeeDetails()
print("Employee Name count \(Emp.Name.count)")
print("Employee ID count \(Emp.ID.count)")
print("Employee Mobile count \(Emp.ID.count)")
print(Emp.Name[0])
} else {
print("parse failure!")
}
}
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
print("In Parser")
currentElementName = elementName
if(elementName=="Table")
{
let Emp = EmployeeDetails()
for string in attributeDict {
let strvalue = string.value as NSString
switch string.key {
case "Emp_ID":
Emp.ID.append(Int(strvalue as String)!)
break
case "Emp_FullName":
Emp.Name.append(strvalue as String)
break
case "Emp_Mobile":
Emp.Mobile.append(strvalue as String)
break
default:
break
}
}
}
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
print("In didEndElement Parser")
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
if currentElementName == "Emp_ID" {
print(string)
}
}
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
print("failure error: ", parseError)
}
func PopulateTxt(Detail: String){
DispatchQueue.main.async {
self.OutputTxt.text = Detail //code that caused error goes here
}
}
}
class EmployeeDetails {
var Name = [String()]
var ID = [Int()]
var Mobile = [String()]
}
You are not setting the parser's delegate in your ReadEmployeeResults function. The XMLParser instance you create there is not the same one you create initially.
There is no need for your parser property or setting its delegate in viewDidLoad. Simply create the one you need in the function.
func ReadEmployeeResults(Data: Data){
let parser = XMLParser(data: Data)
parser.delegate = self
let success = parser.parse()
if success {
print("parse success!")
let Emp = EmployeeDetails()
print("Employee Name count \(Emp.Name.count)")
print("Employee ID count \(Emp.ID.count)")
print("Employee Mobile count \(Emp.ID.count)")
print(Emp.Name[0])
} else {
print("parse failure!")
}
}
Also note that variable and function names should start with lowercase letters.
Related
I have a homework needs to read some rss feeds and build user profile etc.
My problem is when i use XMLParser from foundation, I will encounter "The operation couldn’t be completed. (NSXMLParserErrorDomain error 9.)"
I checked documentation and it seems that I have the invalidCharacterError. I don't think my code have problem since it works well for another url feeds. So what should i do to overcome this problem?
Here is url: http://halley.exp.sis.pitt.edu/comet/utils/_rss.jsp?v=bookmark&user_id=3600
P.S. this feeds contains CDATA so i comment out title and description but it should display date, but it is still show that error. So my concern is that during parsing the xml, it encountered any invalid character and report the error. Anyway to fix it? I have to use this url though.
and some related code are here:
func parseFeed(url: String, completionHandler: (([RSSItem]) -> Void)?)
{
self.parserCompletionHandler = completionHandler
let request = URLRequest(url: URL(string: url)!)
let urlSession = URLSession.shared
let task = urlSession.dataTask(with: request) { (data, response, error) in
guard let data = data else {
if let error = error {
print(error.localizedDescription)
}
return
}
/// parse our xml data
let parser = XMLParser(data: data)
parser.delegate = self
parser.parse()
}
task.resume()
}
// MARK: - XML Parser Delegate
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:])
{
currentElement = elementName
if currentElement == "item" {
currentTitle = ""
currentDescription = ""
currentPubDate = ""
}
}
func parser(_ parser: XMLParser, foundCharacters string: String)
{
switch currentElement {
// case "title": currentTitle += string
// case "description" : currentDescription += string
case "pubDate" : currentPubDate += string
default: break
}
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?)
{
if elementName == "item" {
let rssItem = RSSItem(title: currentTitle, description: currentDescription, pubDate: currentPubDate)
self.rssItems.append(rssItem)
}
}
func parserDidEndDocument(_ parser: XMLParser) {
parserCompletionHandler?(rssItems)
}
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error)
{
print(parseError.localizedDescription)
}
I found an invalid byte 0xFC inside one of the CDATA element in the response of the URL you have shown.
This is invalid as a UTF-8 byte in a document declaring encoding="UTF-8".
You should better tell the server engineer of the URL, that the XML of the RSS feed is invalid.
If you need to work with this sort of ill-formed XML, you need to convert it to the valid UTF-8 data.
0xFC represents ü in ISO-LATIN-1, so you can write something like this.
func parseFeed(url: String, completionHandler: (([RSSItem]) -> Void)?)
{
self.parserCompletionHandler = completionHandler
let request = URLRequest(url: URL(string: url)!)
let urlSession = URLSession.shared
let task = urlSession.dataTask(with: request) { (data, response, error) in
guard var data = data else { //###<-- `var` here
if let error = error {
print(error.localizedDescription)
}
return
}
//### When the input `data` cannot be decoded as a UTF-8 String,
if String(data: data, encoding: .utf8) == nil {
//Interpret the data as an ISO-LATIN-1 String,
let isoLatin1 = String(data: data, encoding: .isoLatin1)!
//And re-encode it as a valid UTF-8
data = isoLatin1.data(using: .utf8)!
}
/// parse our xml data
let parser = XMLParser(data: data)
parser.delegate = self
parser.parse()
}
task.resume()
}
If you need to work other encodings, the problem would be far more difficult, as it is hard to estimate the text encoding properly.
You may need to implement func parser(_ parser: XMLParser, foundCDATA CDATABlock: Data), but that seems to be another issue.
It's my first iOS application, and I kinda have some trouble with getting data from a XML. I need to get the song name and the artist from a XML file that looks like this:
<?xml version="1.0" encoding="utf-8"?>
<Schedule System="Jazler">
<Event status="happening" startTime="19:14:30" eventType="song">
<Announcement Display="Now On Air:"/>
<Song title="E timpul">
<Artist name="Revers">
</Artist>
<Jazler ID="16490"/>
<PlayLister ID=""/>
<Media runTime="03:03"/>
<Expire Time="19:17:33"/>
</Song>
</Event>
</Schedule>
Until now I think I created the parser, but I have no idea how to get the data from it, and the online tutorials are confusing me a bit...
self.parser = XMLParser(contentsOf: URL(string:"http://localhost/jazler/NowOnAir.xml")!)!
self.parser.delegate = self as? XMLParserDelegate
let success:Bool = self.parser.parse()
if success {
print("success")
} else {
print("parse failure!")
}
Your help is much appreciated & thanks in advance.
Because your XML contains all of the values with attributes of the element, you don't need to implement foundCharacters. Just didStartElement, e.g., your parser delegate might look as simple as:
var song: String?
var artist: String?
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
switch elementName {
case "Song": song = attributeDict["title"]
case "Artist": artist = attributeDict["name"]
default: break
}
}
Two observations:
I'd be inclined to pull this parsing code out of the view controller, though, and put it in a dedicated object, to help prevent "view controller bloat".
I'd also use URLSession in case the response to request happens to be a little slow. Generally, one should avoid using XMLParser(contentsOf:), because that performs the request synchronously.
In your case, since you’re requesting the data from localhost, perhaps that’s less of a concern. But, still, it’s prudent to always perform HTTP requests asynchronously.
Anyway, that might yield something like:
class SongParser: NSObject {
var song: String?
var artist: String?
class func requestSong(completionHandler: #escaping (String?, String?, Error?) -> Void) {
let url = URL(string: "http://localhost/jazler/NowOnAir.xml")!
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else {
DispatchQueue.main.async {
completionHandler(nil, nil, error)
}
return
}
let delegate = SongParser()
let parser = XMLParser(data: data)
parser.delegate = delegate
parser.parse()
DispatchQueue.main.async {
completionHandler(delegate.song, delegate.artist, parser.parserError)
}
}
task.resume()
}
}
extension SongParser: XMLParserDelegate {
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
switch elementName {
case "Song": song = attributeDict["title"]
case "Artist": artist = attributeDict["name"]
default: break
}
}
}
And you'd use it like so:
SongParser.requestSong { song, artist, error in
guard let song = song, let artist = artist, error == nil else {
print(error ?? "Unknown error")
return
}
print("Song:", song)
print("Artist:", artist)
}
First convert your xml into NSData and call the parser to parse it.
//converting into NSData
var data: Data? = theXML.data(using: .utf8)
//initiate NSXMLParser with this data
var parser: XMLParser? = XMLParser(data: data ?? Data())
//setting delegate
parser?.delegate = self
//call the method to parse
var result: Bool? = parser?.parse()
parser?.shouldResolveExternalEntities = true
Now, you need to implement the NSXMLParser delegate into your class.
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
currentElement = elementName
print("CurrentElementl: [\(elementName)]")
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
print("foundCharacters: [\(string)]")
}
You will find the value under key of your xml.
I want to consume a SOAP API but I am facing problem.
My delegate methods are not called and URL variable shows unable to read data which is of NSURL type.
import UIKit
class ViewController: UIViewController, UITextFieldDelegate, NSURLConnectionDelegate, XMLParserDelegate {
var mutableData:NSMutableData = NSMutableData.init()
var currentElementName:NSString = ""
#IBOutlet var txtCelsius : UITextField!
#IBOutlet var txtFahrenheit : UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let celcius = "24"
let soapMessage = "<?xml version='1.0' encoding='utf-8'?><soap:Envelope xmlns:xsi='https://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='https://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'><soap:Body><CelsiusToFahrenheit xmlns='https://www.w3schools.com/xml/'><Celsius>\(String(describing: celcius))</Celsius></CelsiusToFahrenheit></soap:Body></soap:Envelope>"
let urlString = "https://www.w3schools.com/xml/tempconvert.asmx"
print(urlString)
if let url = NSURL(string: urlString)
{
print(url)
let theRequest = NSMutableURLRequest(url: url as URL)
theRequest.addValue("text/xml; charset=utf-8", forHTTPHeaderField: "Content-Type")
theRequest.addValue((soapMessage), forHTTPHeaderField: "Content-Length")
theRequest.httpMethod = "POST"
theRequest.httpBody = soapMessage.data(using: String.Encoding.utf8, allowLossyConversion: false)
let connection = NSURLConnection(request: theRequest as URLRequest, delegate: self, startImmediately: true)
connection!.start()
}
}
private func connection(connection: NSURLConnection!, didReceiveResponse response: URLResponse!) {
mutableData.length = 0;
}
private func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
mutableData.append(data as Data)
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
let xmlParser = XMLParser(data: mutableData as Data)
xmlParser.delegate = self
xmlParser.parse()
xmlParser.shouldResolveExternalEntities = true
}
func parser(_ parser: XMLParser, foundCharacters string: String)
{
if currentElementName == "CelsiusToFahrenheit"
{
txtFahrenheit.text = string
}
}
}
im new to ios. I'm making simple login app using soap request.
so the problem is when I get a response from the server it looks like
Optional(<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<CreateLoginResponse xmlns="http://tempuri.org/">
<CreateLoginResult>[{"DelBoyId":36,"DelBoy_Fname":"abc-xyz","Password":"123","DelBoy_Email":"abc#gmail.com","DelBoy_Contact":"123","RoleName":"Admin"}]
</CreateLoginResult>
</CreateLoginResponse>
</soap:Body>
</soap:Envelope>).
and I want to store this key value [{"DelBoyId":36,"DelBoy_Fname":"abc-xyz","Password":"123","DelBoy_Email":"abc#gmail.com","DelBoy_Contact":"123","RoleName":"Admin"}] in session.
how can I do this ? please help me.
thank you.
here is my code
import UIKit
class LoginPageViewController: UIViewController, NSXMLParserDelegate {
#IBOutlet var txtUserEmail: UITextField!
#IBOutlet var txtUserPassword: UITextField!
var webData : NSMutableData?
var parser : NSXMLParser?
var flag : Bool?
var capturedString : String?
func processTerrorSaftyTips(data : NSData){
parser = NSXMLParser(data: data)
parser!.delegate = self
parser!.parse()
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func loginButtonTapped(sender: AnyObject) {
let mobile = txtUserEmail.text;
let password = txtUserPassword.text;
let soapMessage = String(format: "<?xml version='1.0' encoding='utf-8'?><soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http:/www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'><soap:Body><CreateLogin xmlns='http://tempuri.org/'><mobile>"+mobile!+"</mobile><pasword>"+password!+"</pasword></CreateLogin></soap:Body></soap:Envelope>");
let url = NSURL(string: "http://23.91.115.57/nagifts/NaGiftsWebService.asmx");
let theRequest = NSMutableURLRequest (URL: url!);
let soapLength = String(soapMessage.characters.count)
theRequest.addValue("text/xml", forHTTPHeaderField: "Content-Type");
theRequest.addValue(soapLength, forHTTPHeaderField: "Content-Length")
theRequest.HTTPMethod = "POST";
theRequest.HTTPBody = soapMessage.dataUsingEncoding(NSUTF8StringEncoding,allowLossyConversion: false);
let urlConnection = NSURLConnection(request: theRequest , delegate: self);
}
func connection(connection: NSURLConnection, didFailWithError error: NSError){
}
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse){
webData = NSMutableData()
}
func connection(connection: NSURLConnection, didReceiveData data: NSData){
webData!.appendData(data)
}
func connectionDidFinishLoading(connection: NSURLConnection){
processTerrorSaftyTips(webData!)
}
func parserDidStartDocument(parser: NSXMLParser){
flag = false
capturedString = ""
}
func parserDidEndDocument(parser: NSXMLParser){
}
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]){
flag = false
capturedString = ""
if elementName == "CreateLoginResult"
{
flag = true
}
}
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?){
flag = false
if elementName == "CreateLoginResult"
{
print((capturedString!))
}
}
func parser(parser: NSXMLParser, foundCharacters string: String){
if flag! {
capturedString = capturedString! + string
}
}
func parser(parser: NSXMLParser, parseErrorOccurred parseError: NSError){
}
}
Swift3:
Declare any array because your response is in array
var arrayFromResult = NSMutableArray()
Next go to Parse DidEndElement and add foundCharacters to Array do like this
if elementName == "CreateLoginResult"
{
print((capturedString!))
arrayFromResult.add(capturedString)
}
Usage Of array Or retrieve Dictionary Values from Array
for var i in 0..<arrayFromResult.count {
let dictFromArray = arrayFromResult.object(at: i) as! NSDictionary
let name = dictFromArray.value(forKey: "DelBoy_Fname") as! String
print(name)
let password = dictFromArray.value(forKey: "Password") as! String
print(password)
}
I want to call web service for Swift 2. But it never works. This is my code.
import UIKit
class ViewController: UIViewController, UITextFieldDelegate, NSURLConnectionDelegate, NSXMLParserDelegate {
var mutableData:NSMutableData = NSMutableData.init()
var currentElementName:NSString = ""
#IBOutlet var txtCelsius : UITextField!
#IBOutlet var txtFahrenheit : UITextField!
#IBAction func actionConvert(sender : AnyObject) {
let celcius = txtCelsius.text
let soapMessage = "<?xml version='1.0' encoding='utf-8'?><soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'><soap:Body><CelsiusToFahrenheit xmlns='http://www.w3schools.com/xml/'><Celsius>\(celcius)</Celsius></CelsiusToFahrenheit></soap:Body></soap:Envelope>"
let urlString = "http://www.w3schools.com/xml/tempconvert.asmx"
let url = NSURL(string: urlString)
let theRequest = NSMutableURLRequest(URL: url!)
theRequest.addValue("text/xml; charset=utf-8", forHTTPHeaderField: "Content-Type")
theRequest.addValue((soapMessage), forHTTPHeaderField: "Content-Length")
theRequest.HTTPMethod = "POST"
theRequest.HTTPBody = soapMessage.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) // or false
let connection = NSURLConnection(request: theRequest, delegate: self, startImmediately: true)
connection!.start()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func connection(connection: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
mutableData.length = 0;
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
mutableData.appendData(data)
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
let xmlParser = NSXMLParser(data: mutableData)
xmlParser.delegate = self
xmlParser.parse()
xmlParser.shouldResolveExternalEntities = true
}
func parser(parser: NSXMLParser, foundCharacters string: String) {
if currentElementName == "CelsiusToFahrenheit" {
txtFahrenheit.text = string
}
}
NSURLConnection is deprecated, use NSURLSession instead.
Here's an example of a function doing what you want with NSURLSession and a callback:
func getFarenheit(celsius celsius: Int, completion: (result: String) -> Void) {
let soapMessage = "<?xml version='1.0' encoding='utf-8'?><soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'><soap:Body><CelsiusToFahrenheit xmlns='http://www.w3schools.com/xml/'><Celsius>\(celsius)</Celsius></CelsiusToFahrenheit></soap:Body></soap:Envelope>"
let urlString = "http://www.w3schools.com/xml/tempconvert.asmx"
if let url = NSURL(string: urlString) {
let theRequest = NSMutableURLRequest(URL: url)
theRequest.addValue("text/xml; charset=utf-8", forHTTPHeaderField: "Content-Type")
theRequest.addValue((soapMessage), forHTTPHeaderField: "Content-Length")
theRequest.HTTPMethod = "POST"
theRequest.HTTPBody = soapMessage.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
NSURLSession.sharedSession().dataTaskWithRequest(theRequest) { (data, response, error) in
if error == nil {
if let data = data, result = String(data: data, encoding: NSUTF8StringEncoding) {
completion(result: result)
}
} else {
print(error!.debugDescription)
}
}.resume()
}
}
Use it like this with a "trailing closure":
getFarenheit(celsius: 42) { (result) in
print(result)
}
It prints the data containing the XML and the converted value:
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CelsiusToFahrenheitResponse xmlns="http://www.w3schools.com/xml/"><CelsiusToFahrenheitResult>107.6</CelsiusToFahrenheitResult></CelsiusToFahrenheitResponse></soap:Body></soap:Envelope>