I'm trying to abstract an XML parser into a custom class to run it from a VC. It compiles perfectly and my error handler shows up success. However, the actual delegate methods are skipped over. No data is getting parsed.
It all ran fine when I had every running it the VC, but I am now try to get away from spaghetti code.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let parser = XMLParserHelper()
//try create file for persistent data
//CreatePlist.createPlist()
parser.runParser()
}
}
class XMLParserHelper: NSObject, XMLParserDelegate {
//list type variables to hold XML values (update list base on XML structure):
static var station: String = ""
static var latitude: String = ""
static var longitude: String = ""
private static var code: String = ""
private static var id: String = ""
//reusable method type veriales (do not touch)
static var strXMLData:String = ""
static var currentElement:String = ""
static var passData:Bool=false
static var passName:Bool=false
static var xmlParser = XMLParser()
//parser methods
func runParser(){
let xmlPath = Bundle.main.url(forResource: "station", withExtension: "xml")
let xmlParser = XMLParser(contentsOf: (xmlPath)!)
xmlParser?.delegate = self
let success:Bool = xmlParser!.parse()
xmlParser?.parse()
if success {
print("parse success!")
print(XMLParserHelper.currentElement)
} else {
print("parse failure!")
}
}
private static func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
XMLParserHelper.currentElement=elementName;
if (elementName=="StationDesc" || elementName=="StationLatitude" || elementName=="StationLongitude" || elementName=="StationCode" || elementName=="StationId" ) {
if (elementName=="StationDesc") {
XMLParserHelper.passName=true;
}
XMLParserHelper.passData=true;
}
}
private static func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
XMLParserHelper.currentElement="";
if (elementName=="StationDesc" || elementName=="StationLatitude" || elementName=="StationLongitude" || elementName=="StationCode" || elementName=="StationId" ) {
if(elementName=="StationDesc") {
XMLParserHelper.passName=false;
}
XMLParserHelper.passData=false;
}
}
private static func parser(_ parser: XMLParser, foundCharacters string: String) {
if (XMLParserHelper.passName) {
XMLParserHelper.strXMLData=XMLParserHelper.strXMLData+"\n\n"+string
}
if (XMLParserHelper.passData) {
//ready content for codable struct
switch XMLParserHelper.currentElement {
case "StationDesc":
XMLParserHelper.station = string
case "StationLatitude":
XMLParserHelper.latitude = string
case "StationLongitude":
XMLParserHelper.longitude = string
case "StationCode":
XMLParserHelper.code = string
case "StationId":
XMLParserHelper.id = string
print(string)
default:
XMLParserHelper.id = string
}
}
}
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
print("failure error: ", parseError)
}
}
To make XMLParserDelegate methods work, all the methods needs to be non-static, non-private methods.
So, all the properties should also be non-static.
class XMLParserHelper: NSObject, XMLParserDelegate {
//list type variables to hold XML values (update list base on XML structure):
var station: String = ""
var latitude: String = ""
var longitude: String = ""
private var code: String = ""
private var id: String = ""
//reusable method type veriales (do not touch)
var strXMLData: String = ""
var currentElement: String = ""
var passData: Bool = false
var passName: Bool = false
//parser methods
func runParser() {
let xmlURL = Bundle.main.url(forResource: "station", withExtension: "xml")!
let xmlParser = XMLParser(contentsOf: xmlURL)!
xmlParser.delegate = self
let success = xmlParser.parse()
if success {
print("parse success!")
print(currentElement)
} else {
print("parse failure!")
}
}
//MARK: XMLParserDelegate methods
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
currentElement = elementName
if elementName == "StationDesc"
|| elementName == "StationLatitude"
|| elementName == "StationLongitude"
|| elementName == "StationCode"
|| elementName == "StationId"
{
if elementName == "StationDesc" {
passName = true
}
passData = true
}
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
currentElement = ""
if elementName == "StationDesc"
|| elementName == "StationLatitude"
|| elementName == "StationLongitude"
|| elementName == "StationCode"
|| elementName == "StationId"
{
if elementName == "StationDesc" {
passName = false
}
passData = false
}
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
if passName {
strXMLData = strXMLData+"\n\n"+string
}
if passData {
//ready content for codable struct
switch currentElement {
case "StationDesc":
station = string
case "StationLatitude":
latitude = string
case "StationLongitude":
longitude = string
case "StationCode":
code = string
case "StationId":
id = string
print(string)
default:
id = string
}
}
}
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
print("failure error: ", parseError)
}
}
The XMLParser just needs to be held while parse() is running, so you have no need to declare xmlParser as a property of XMLParserHelper.
You need a strong reference
class ViewController: UIViewController{
var parser:XMLParserHelper!
override func viewDidLoad() {
super.viewDidLoad()
parser = XMLParserHelper()
parser.runParser()
}
}
Related
I'm trying to parse a xml file from a server with XMLparser in swift,
I have a var m_items that is a NSMutableArray and a CItemData object that has
var m_id:String = ""
var m_type:CLong = CLong(0)
var m_title:String = ""
var m_description:String = ""
var m_data:String = ""
override init()
{
super.init();
}
init(id: String, title:String, type:CLong) {
self.m_id = id;
self.m_title = title;
self.m_type = type;
this is the xml with a items root a item id and some values like title, description and data.
<items>
<item id="1" type="1">
<title>
<![CDATA[ Definition of Hacker ]]>
</title>
<description>
<![CDATA[ Description 1 ]]>
</description>
<data>
<![CDATA[ Data 1 ]]>
</data>
</item>
</items>
I'm trying to parse like this, but I don't understand how to use didstartelement correctly. Any help? My code is from an example but doesn't fits with my needs. The problem is
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String])
{
var attr:String = ""
if(elementName == "items")
{
self.m_root = CItemData();
self.m_last_node = self.m_root;
}
else if(elementName == "item")
{
attr = attributeDict["id"]!
let item:CItemData = CItemData()
item.m_id.setObject(attr , forKey: "id" as NSCopying)
self.m_root?.m_id.add(item)
self.m_last_item = item;
self.m_last_node = item;
}
else if(elementName == "child")
{
attr = attributeDict["id"]!
let child:CItemData = CItemData()
child.m_type.setObject(attr , forKey: "id" as NSCopying)
self.m_last_item?.m_type.add(child)
self.m_last_node = item;
}
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if(elementName == "title")
{
self.m_last_node?.m_title = self.m_last_string
}
else if(elementName == "description"){
self.m_last_node?.m_description = self.m_last_string
}
else if(elementName == "data"){
self.m_last_node?.m_data = self.m_last_string
}
}
func parser(_ parser: XMLParser, foundCharacters string: String)
{
self.m_last_string = string;
}
}
I have a xml file that I have parsed with XMLParser and appended the values to a struc. I'm having difficulty with getting the values, however. Here's what I've done so far that I understand as a beginner.
Here's my Struct. It has 5 empty variables that I'll be getting later.
struct CalendarDates {
var month = ""
var date = ""
var datenumber = ""
var holiday = ""
var description = "" }
Next, here is where I use XMLParser to assign value to the struct
class ViewController: UIViewController, XMLParserDelegate {
var myCalendarDatesStrut = [CalendarDates]()
var calendarEventsFromXML = ""
var monthsFromXML = ""
var datesFromXML = ""
var datenumbersFromXML = ""
var holidaysFromXML = ""
var descriptionsFromXML = ""
//Mark: XML Parse Delegate
extension CalendarViewController {
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
calendarEventsFromXML = elementName
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
let data = string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if data.count != 0 {
switch calendarEventsFromXML
{
case "month": monthsFromXML = data
case "date": datesFromXML = data
case "datenumber": datenumbersFromXML = data
case "holiday": holidaysFromXML = data
default: break
}}}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "calendarevent"
{
var myCalendarDates = CalendarDates()
myCalendarDates.month = monthsFromXML
myCalendarDates.date = datesFromXML
myCalendarDates.datenumber = datenumbersFromXML
myCalendarDates.holiday = holidaysFromXML
myCalendarDatesStrut.append(myCalendarDates)
}}}}
Alright, so here's where I'm running into a block. I want to get all of the dates (datesFromXML) so that I can match them to some visible Dates and do some method.
I tried
let datesFromCalendarXML = myCalendarDatesStrut.date
But this gives me an error Value of type '[CalendarDates]' has no member 'date'. Didn't I append these to this this struc when I parsed the xml? Did I do it wrong?
You can try
let datesFromCalendarXML = myCalendarDatesStrut.map {$0.date} // this gives [Date]
since myCalendarDatesStrut is an array and not a single object you can't use dot operator with it , after that you can filter this array
let date1 = //
let date2 = //
let result = datesFromCalendarXML.filter { $0 > date1 && $0 < date2 } // filter to whatever you want
I am having bar or QR code scanning of Aadhar card.I am getting the response as the following xml format.How to convert this into dictionary format using xml parsing?
<?xml version="1.0" encoding="UTF-8"?><PrintLetterBarcodeData uid="685860050795" name="Sangeetha D" gender="F" yob="1989" co="W/O: Dhanansekaran" house="632" street="saradhambal nagar" lm="agaramel" vtc="Nazarathpettai" po="Nazarethpettai" dist="Tiruvallur" subdist="Poonamallee" state="Tamil Nadu" pc="600123" dob="03/06/1989"/>
I tried the following code for parsing
public func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
currentElement=elementName;
print(currentElement)
}
public func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
currentElement="";
}
public func parser(parser: NSXMLParser, foundCharacters string: String) {
}
But its returning always the currentElement as "PrintLetterBarcodeData"
Here's some parsing code I wrote in Swift 3 based off of a Google News RSS reader I previously wrote in Swift 2.0. I have this code modified to handle a list of PrintLetterBarcodeData elements as well as a single one:
class BarcodeData {
var uid: String
var name: String
var gender: String
var yob: String
var co: String
var house: String
var street: String
var lm: String
var vtc: String
var po: String
var dist: String
var subdist: String
var state: String
var pc: String
var dob: String
init?(dictionary: [String : String]) {
guard let uid = dictionary["uid"],
let name = dictionary["name"],
let gender = dictionary["gender"],
let yob = dictionary["yob"],
let co = dictionary["co"],
let house = dictionary["house"],
let street = dictionary["street"],
let lm = dictionary["lm"],
let vtc = dictionary["vtc"],
let po = dictionary["po"],
let dist = dictionary["dist"],
let subdist = dictionary["subdist"],
let state = dictionary["state"],
let pc = dictionary["pc"],
let dob = dictionary["dob"] else {
return nil
}
self.uid = uid
self.name = name
self.gender = gender
self.yob = yob
self.co = co
self.house = house
self.street = street
self.lm = lm
self.vtc = vtc
self.po = po
self.dist = dist
self.subdist = subdist
self.state = state
self.pc = pc
self.dob = dob
}
}
class MyParser: NSObject {
var parser: XMLParser
var barcodes = [BarcodeData]()
init(xml: String) {
parser = XMLParser(data: xml.data(using: String.Encoding.utf8)!)
super.init()
parser.delegate = self
}
func parseXML() -> [BarcodeData] {
parser.parse()
return barcodes
}
}
extension MyParser: XMLParserDelegate {
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
if elementName == "PrintLetterBarcodeData" {
if let barcode = BarcodeData(dictionary: attributeDict) {
barcodes.append(barcode)
}
}
}
}
Usage:
let xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><PrintLetterBarcodeData uid=\"685860050795\" name=\"Sangeetha D\" gender=\"F\" yob=\"1989\" co=\"W/O: Dhanansekaran\" house=\"632\" street=\"saradhambal nagar\" lm=\"agaramel\" vtc=\"Nazarathpettai\" po=\"Nazarethpettai\" dist=\"Tiruvallur\" subdist=\"Poonamallee\" state=\"Tamil Nadu\" pc=\"600123\" dob=\"03/06/1989\"/>"
let parser = MyParser(xml: xmlString)
let barcodes = parser.parseXML() // array of barcodes
barcodes.first // your barcode
It appears as though your expected XML structure only consists of the root element PrintLetterBarcodeData and its attributes.
You will find the attributes of your root element in the attributeDict property in the didStartElement delegate method.
For example, to extract the name property, you would do:
public func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
currentElement=elementName;
print(currentElement)
//print name
if let name = attributeDict["name"] {
print(name) //prints Sangeetha D
}
}
I am having an issue in which I am parsing a String value from an API and I am trying to assign it to an UILabel. I have tested updating the label in the exact same position as it is now by setting the label (lblNamedata) to a String using the format:
self.lblNameData.text = "hello"
and it has worked fine.
I currently have:
if success {
print("parse success!")
print(strXMLData)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.lblNameData.text=self.strXMLData
})
} else {
print("parse failure!")
}
and this isn't working.
Below is my complete current code:
import UIKit
class ViewController: UIViewController,NSXMLParserDelegate {
var strXMLData:String = ""
var currentElement:String = ""
var passData:Bool=false
var passName:Bool=false
var parser = NSXMLParser()
#IBOutlet weak var lblNameData: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let url:String="http://www.stands4.com/services/v2/quotes.php?uid=idhere&tokenid=tokenhere&searchtype=xxxx&query=xxxx"
let urlToSend: NSURL = NSURL(string: url)!
// Parse the XML
parser = NSXMLParser(contentsOfURL: urlToSend)!
parser.delegate = self
let success:Bool = parser.parse()
if success {
print("parse success!")
print(strXMLData)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.lblNameData.text=self.strXMLData
})
} else {
print("parse failure!")
}
}
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
currentElement=elementName;
if(elementName=="quote" || elementName=="author" || elementName=="result")
{
if(elementName=="quote"){
passName=true;
}
passData=true;
}
}
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
currentElement="";
if(elementName=="quote" || elementName=="author" || elementName=="result")
{
if(elementName=="quote"){
passName=false;
}
passData=false;
}
}
func parser(parser: NSXMLParser, foundCharacters string: String) {
if(passName){
strXMLData=strXMLData+"\n\n"+string
}
if(passData)
{
// print(string)
print("work")
}
}
func parser(parser: NSXMLParser, parseErrorOccurred parseError: NSError) {
NSLog("failure error: %#", parseError)
}
}
UPDATE: So I believe I found the error: in my parser function I'm setting strXMLData = strXMLData + "\n\n" + string but after I delete the \n\n I can see that my label updates...why is this?
Alright, so after many instances of trial and error, I think I figured out the answer.
Something was up in my Main.storyboard that didn't like me creating multiple lines in the UILabel, so what I did was click on the label and navigated to the Attributes Inspector. I set the "Lines" parameter to 0 and I was able to see my text!
I have the following XML structure which I want to parse:
<plist version="1.0">
<dict>
<key>
PALABRA
</key>
<array>
<string>
CATEGORY
</string>
<string>
WORD
</string>
</array>
</dict>
</plist>
I am using NSXMLParser in this way to parse the XML:
var posts = NSMutableArray()
var parser = NSXMLParser()
var elements = NSMutableDictionary()
var element = NSString()
var CATEGORY = NSMutableString()
var WORD = NSMutableString()
func parseXML() {
posts = []
parser = NSXMLParser(contentsOfURL:(NSURL(string:"http://www.serverbpw.com/cm/2016-1/hangman.php"))!)!
parser.delegate = self
parser.parse()
}
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
element = elementName
if (elementName as NSString).isEqualToString("array") {
elements = NSMutableDictionary()
elements = [:]
categoria = NSMutableString()
categoria = ""
}
if (elementName as NSString).isEqualToString("string") {
palabra = NSMutableString()
palabra = ""
}
}
func parser(parser: NSXMLParser, foundCharacters string: String) {
if element.isEqualToString("string") {
categoria.appendString(string)
}
if element.isEqualToString("string") {
palabra.appendString(string)
}
}
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if (elementName as NSString).isEqualToString("array") {
if !categoria.isEqual(nil) {
elements.setObject(categoria, forKey: "CATEGORIA")
}
if !palabra.isEqual(nil) {
elements.setObject(palabra, forKey: "PALABRA")
}
posts.addObject(elements)
}
}
The value I get from the server for CATEGORY is the content of CATEGORY, but also the content of WORD, and the value for WORD is only WORD content.
I know this is not the right way to parse the XML. The problem is that I donĀ“t know how to get the CATEGORY and WORD elements, since both of them have the same ID ("String"). What would be the right way to get this information?
The server address is: http://www.serverbpw.com/cm/2016-1/hangman.php
Thanks in advance!
If you're going to parse this yourself, the trick is that you have two occurrences of <string>, and you have to differentiate them somehow. You could have your own counter to keep track of which one was which:
var posts: [[String: String]]!
var element: String!
var categoria: String?
var palabra: String?
var stringValue: String?
var counter: Int!
func parseXML(url: NSURL) {
posts = [[:]]
let parser = NSXMLParser(contentsOfURL: url)!
parser.delegate = self
parser.parse()
}
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
element = elementName
if elementName == "array" {
categoria = nil
palabra = nil
counter = 0
}
}
func parser(parser: NSXMLParser, foundCharacters string: String) {
if element == "string" {
if stringValue == nil {
stringValue = string
} else {
stringValue! += string
}
}
}
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "string" {
stringValue = stringValue?.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
if counter == 0 {
categoria = stringValue
} else if counter == 1 {
palabra = stringValue
}
stringValue = nil
counter!++
}
if elementName == "array" {
var element = [String: String]()
if categoria != nil {
element["CATEGORIA"] = categoria
}
if palabra != nil {
element["PALABRA"] = palabra
}
posts.append(element)
}
}
But I agree with cezheng that this appears to by a NSDictionary plist, and as such, you should just use NSDictionary(contentsOfFile) or what have you. And if this is really a XML that you're building, I'd suggest a different format, e.g.:
<posts>
<post>
<category>
CATEGORY
</category>
<word>
WORD
</word>
</post>
</posts>
Then you could parse this far more easily, without the silliness of the counter.
This is a plist so you can just use NSDictionary(contentsOfFile:).
if let dict = NSDictionary(contentsOfFile: filePath) {
let array = dict["PALABRA"] as? [String]
let category = array?[0]
let word = array?[1]
}
if you don't have the data as a file, use this method
if let dict = try NSPropertyListSerialization.propertyListWithData(xmlData, options: .Immutable, format: nil) as? NSDictionary {
//......
}