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.
Related
I am writing a xml parser library in Swift for a specific kind of data.
In my project there are two ways to import xml data, either by using a web URL or by giving a local xml file.
The first one works great but I have trouble with the second one.
Here is a sample of my code:
import Foundation
class PnmlParser: NSObject, XMLParserDelegate {
// DOES NOT WORK
func loadPN(filePath: String) {
print(Bundle.main)
let xmlResponseData = Bundle.main.getFileData(filePath)
let parser = XMLParser(data: xmlResponseData)
parser.delegate = self
parser.parse()
}
// WORK
func loadPN(url: URL) {
let parser = XMLParser(contentsOf: url)!
parser.delegate = self
parser.parse()
}
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
// Some code
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
// Some code
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
// Some code
}
func parserDidEndDocument(_ parser: XMLParser) {
// Some code
}
}
extension Bundle {
func getFileData(_ file: String) -> Data {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) in bundle")
}
return data
}
}
There are two functions loadPN, but it is only the loadPN(filePath: String) that does not work, because it does not find my xml file.
I use the Swift Package Manager and not XCode to insert resources. I have already seen how to do it with XCode using the build phase and copy bundle resources, but it does not apply to my case.
I am working on Swift 5.6, and I have seen that I should adapt my Package.swift to insert the wanted resources.
Here is my Package.swift:
// swift-tools-version: 5.6
import PackageDescription
let package = Package(
name: "swift-xml-import-test",
...
targets: [
.target(
name: "swift-xml-import-test",
dependencies: [],
resources: [.process("Resources/data.xml")]
),
.testTarget(
name: "swift-xml-import-testTests",
dependencies: ["swift-xml-import-test"],
resources: [.process("ResourcesTests/dataTests.xml")]
),
]
)
A sample GitHub project shows the data structure and the problem that I encountered:
Sample of my GitHub project
I created two folders, respectively Resources (Sources/swift-xml-import-test/Resources) and ResourcesTests (Tests/swift-xml-import-testTests/ResourcesTests), where there is an xml data file in each.
I created a test to try it out:
final class swift_xml_import_testTests: XCTestCase {
func testWork() {
let p = PnmlParser()
if let url = URL(string: "https://www.pnml.org/version-2009/examples/philo.pnml") {
p.loadPN(url: url)
}
}
func testDoesNotWork() {
let p = PnmlParser()
p.loadPN(filePath: "dataTests.xml")
}
}
When I launch my test testDoesNotWork(), I get the following error: Thread 1: Fatal error: Failed to locate dataTests.xml in bundle from my earlier extension.
I do not understand what I am missing if someone can help me.
If you want to test yourself, you have the GitHub link of the sample project above.
From Swift 5.3, it is possible to add resources using Package.swift, which has been done in the code.
To call the added resources, it is required to use:
Bundle.module.url(forResource: myFile, withExtension: myExtension)
However, this is not possible in the extension made previously, where it was self.url.
Thus, I deleted the extension and changed the function loadPN(filePath: String) into:
func loadPN(filePath: String) {
if let url = Bundle.module.url(forResource: filePath, withExtension: nil) {
let parser = XMLParser(contentsOf: url)!
parser.delegate = self
parser.parse()
}
}
testDoesNotWork test finally passed.
I am trying to send a multipart/form-data request via URLSession I have written a helper function to create the form body as Data for me, however my request keeps failing with a 500 response code.
I was trying to print out the string representation of my request to ensure the body is correct, however simply trying to decode it now is throwing an error as The data couldn’t be read because it isn’t in the correct format.
This leads me to believe something is wrong with how my request is being encoded.
This is essentially my setup -
struct Media {
let key: String
let filename: String
let data: Data
let mimeType: String
init?(withImage image: UIImage, forKey key: String) {
self.key = key
self.mimeType = "image/jpeg"
self.filename = "avatar.jpg"
guard let data = image.jpegData(compressionQuality: 0.7) else { return nil }
self.data = data
}
}
typealias Parameters = [String: String]
func createFormData(usingParams params: Parameters?, media: [Media]?, boundary: String) -> Data {
let lineBreak = "\r\n"
var body = Data()
if let parameters = params {
for (key, value) in parameters {
body.append(string:"--\(boundary + lineBreak)")
body.append(string:"Content-Disposition: form-data; name=\"\(key)\"\(lineBreak + lineBreak)")
body.append(string:"\(value + lineBreak)")
}
}
if let media = media {
for photo in media {
body.append(string:"--\(boundary + lineBreak)")
body.append(string:"Content-Disposition: form-data; name=\"\(photo.key)\"; filename=\"\(photo.filename)\"\(lineBreak)")
body.append(string:"Content-Type: \(photo.mimeType + lineBreak + lineBreak)")
body.append(photo.data)
body.append(string: lineBreak)
}
}
body.append(string: "--\(boundary)--\(lineBreak)")
return body
}
extension Data {
mutating func append(string: String) {
if let data = string.data(using: .utf8) {
append(data)
}
}
}
Which I invoke something like this -
let boundary = UUID().uuidString
guard let profilePic = Media(withImage:image, forKey: "file") else { return }
let data = createFormData(usingParams: nil, media: [profilePic], boundary: boundary)
I have tried to print my data using the following -
do {
print(data)
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]
print(json)
} catch let error {
print(error.localizedDescription)
}
This throws the error mentioned earlier.
I have also tried
let string = String(data: data, encoding: .utf8)
print(string)
This however just returns nil.
I noticed if I remove body.append(photo.data) the data can print out now as JSON, however I am not sure why this matters
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.
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.
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)
}