I am needing help parsing XML from a URL. I have read on this a lot and cannot figure out how to get this. All the examples I have found are a little ambiguous with the examples and how to grab the info from parsed XML data.
How would I parse this XML
<menu>
<menuitems>
<item price="$10">Pizza</item>
<item price="$5">Salad</item>
<item price="$3">Bread</item>
</menuitems>
</menu>
This is the tutorial I was going off of but I couldn't understand how this tutorial was accessing the data in the arrays and dictionary.
http://www.theappguruz.com/blog/xml-parsing-using-nsxmlparse-swift
In Xcode 7 (Swift 2), how would I parse and access this info. I know this is not how it would be accessed but I am looking for something like the code below.
print ("\(item[0].data)") // Pizza //
print ("\(item[0].price)") // $10 //
Here is the code from the tutorial but I couldn't figure out how to access all the data parsed from the URL. How would I edit this code to fit my structure?
import UIKit
import Foundation
class ViewController: UIViewController, NSXMLParserDelegate
{
var parser = NSXMLParser()
var posts = NSMutableArray()
var elements = NSMutableDictionary()
var element = NSString()
var title1 = NSMutableString()
var date = NSMutableString()
func beginParsing()
{
posts = []
parser = NSXMLParser(contentsOfURL:(NSURL(string:"http://images.apple.com/main/rss/hotnews/hotnews.rss"))!)!
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("item")
{
elements = NSMutableDictionary()
elements = [:]
title1 = NSMutableString()
title1 = ""
date = NSMutableString()
date = ""
}
}
func parser(parser: NSXMLParser, foundCharacters string: String)
{
if element.isEqualToString("title") {
title1.appendString(string)
} else if element.isEqualToString("pubDate") {
date.appendString(string)
}
}
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?)
{
if (elementName as NSString).isEqualToString("item") {
if !title1.isEqual(nil) {
elements.setObject(title1, forKey: "title")
}
if !date.isEqual(nil) {
elements.setObject(date, forKey: "date")
}
posts.addObject(elements)
}
}
}
It looks like that parser code was borrowed from something else. If this really is your XML, think it's simpler than that. The only trick in your case is that price is not an element, but rather just an attributeDict of the item element. And I might create my own custom type, Item to capture the name and price of an item.
Anyway, you might end up with something like:
struct Item {
let name: String
let price: String
}
var itemName: String?
var itemPrice: String?
var items: [Item]!
func parserDidStartDocument(parser: NSXMLParser) {
items = []
}
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
if elementName == "item" {
itemPrice = attributeDict["price"]
itemName = ""
}
}
func parser(parser: NSXMLParser, foundCharacters string: String) {
itemName?.appendContentsOf(string)
}
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "item" {
items.append(Item(name: itemName!, price: itemPrice!))
itemName = nil
itemPrice = nil
}
}
When I do that, and print items, I get:
[MyApp.ViewController.Item(name: "Pizza", price: "$10"),
MyApp.ViewController.Item(name: "Salad", price: "$5"),
MyApp.ViewController.Item(name: "Bread", price: "$3")]
Related
I am trying to do XML Parsing of below file :
<bgmusic>
<genre title=“Title1”>
<track title=“SubTitle1” path="https://test/Background_Track%20-%20Ambient%20-%20ANW2856_01_ Animation.ogg" />
<track title="SubTitle2” path="https://test/Background_Track%20-%20Ambient%20-%20ANW2860_02_Entanglement.ogg" />
</genre>
<genre title="Title2”>
<track title="SubTitle3” path="https://test/Background_Track%20-%20Ambient%20-%20ANW2856_01_Animate.ogg" />
<track title="SubTitle4” path="https://test/Background_Track%20-%20Ambient%20-%20ANW2860_02_ SubTitle4.ogg" />
</genre>
</bgmusic>
Basically I have created two ViewController, one for displaying Genre Title and second VC for displaying the details of this. For this, I have created below two Modal classes:
class GenreModel {
var title: String = ""
var genreSongsArray = [GenreSong]()
var genreSongs = GenreSong()
}
class GenreSong {
var songTitle: String = ""
var path: String = ""
}
Here is my code:
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
let genreModel = GenreModel()
if(elementName == "genre"){
if let id = attributeDict["title"] {
genreModel.title = id
}
}
if(elementName == "track")
{
let genreSong = GenreSong()
for string in attributeDict {
let strvalue = string.value as NSString
switch string.key {
case "title":
genreSong.songTitle = strvalue as String
break
case "path":
genreSong.path = strvalue as String
break
default:
break
}
}
genreModel.genreSongsArray.append(genreSong)
}
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
}
My problem is when I am giving "elementName == genre" then it's only parsing genre title and not parsing track details. How can I save both Genre title and Songs details in one custom Array?
Can anyone please suggest me on this? Thank you!
You need to rethink your parsing strategy.
The XMLParser fires didStartElement whenever a new element starts. So the first occurence is on the "genre" element.
The next two elements are "track" so the parser again fires didStartElement for each of them.
It is your responsibility to keep track of the hierarchy so you need to store the current element in a variable and process the subelements.
So whenever you retrieve a new element with didStartElement you have to check what element it is and what child elements you expect further. This all depends on the structure of your XML document so there is no all in one solution one can give you.
...
var genre = ""
var tracks = [String]()
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
if(elementName == "genre") {
// new genre
tracks = []
if let id = attributeDict["title"] {
genre = id
}
}
if(elementName == "track") {
// new subElement track, add it to the tracks array here
}
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if(elementName == "genre") {
// genre complete with all tracks, process it
}
}
Here is a part of my URL
<cities>
<country name="Абхазия">
<city id="37188" region="27028" head="" type="3" country="Абхазия" part="" resort="" climate="">Новый Афон</city>
<city id="37178" region="10282" head="" type="3" country="Абхазия" part="" resort="" climate="">Пицунда</city>
<city id="37187" region="37187" head="" type="3" country="Абхазия" part="" resort="" climate="">Гудаута</city>
<city id="37172" region="10280" head="" type="3" country="Абхазия" part="" resort="" climate="">Гагра</city>
<city id="37189" region="10281" head="0" type="3" country="Абхазия" part="" resort="0" climate="">Сухум</city>
</country>
User types the name of the city, for example: "Пицунда" and I want to get its id. For "Пицунда" id is "10282".
Below I've posted my not-working code.
var parser: NSXMLParser!
var city: String = String()
var ifDirOK = false
var ifCityNameOK = false
override func viewDidLoad() {
super.viewDidLoad()
let url: NSURL = NSURL(string: "https://pogoda.yandex.ru/static/cities.xml")!
parser = NSXMLParser(contentsOfURL: url)
parser.delegate = self
parser.parse()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func parser(parser: NSXMLParser!, didStartElement elementName: String!, namespaceURI: String!, qualifiedName qName: String!, attributes attributeDict: [NSObject : AnyObject]!) {
//let cityID = attributeDict ["id"] as? NSString
if (elementName == "city"){
ifDirOK = true
}
}
func parser(parser: NSXMLParser!, foundCharacters string: String!) {
var data = string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
if (data == city){
ifCityNameOK = true
}
}
func parser(parser: NSXMLParser!, foundAttributeDeclarationWithName attributeName: String!, forElement elementName: String!, type: String!, defaultValue: String!) {
if (ifDirOK && ifCityNameOK){
println("\(attributeName)")
}
}
func parser(parser: NSXMLParser!, didEndElement elementName: String!, namespaceURI: String!, qualifiedName qName: String!) {
}
After all, I want to pass id to another URL file (export.yandex.ru/weather-ng/forecasts/{id of the city}.xml) and parse it. Do I need to create another Swift class and somehow connect it with first one?
Building a dictionary of [city:id] can be a solution for you.
I have implemented a simple solution based on the article about lifecycle of NSXMLParser at http://www.codeproject.com/Articles/248883/Objective-C-Fundamentals-NSXMLParser .
Following method is called when when an element is starting.
You can retrieve city id attribute and save it in an instance level variable so that you can use it in the next method.
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [NSObject : AnyObject])
And then, Following method is called when the parser see anything between starting and ending.
func parser(parser: NSXMLParser!, foundCharacters string: String!)
So, you can get the city name from here.
Now we have city id and city name to add a new item into the [city:id] dictionary.
Once you build the dictionary, searching would be very simple.
here is my working test code.
class ViewController: UIViewController ,NSXMLParserDelegate{
var parser: NSXMLParser!
var city: String = String()
var ifDirOK = false
var ifCityNameOK = false
var element : String?
var value: String=String()
var dic = Dictionary<String,String>()
var currentCityId:String?
#IBOutlet weak var result: UILabel!
#IBOutlet weak var search: UITextField! //search text
#IBAction func ActionGoGetIt(sender: AnyObject) {
self.result.text=dic[self.search.text]
}
override func viewDidLoad() {
super.viewDidLoad()
let url: NSURL = NSURL(string: "https://pogoda.yandex.ru/static/cities.xml")!
parser = NSXMLParser(contentsOfURL: url)
parser.delegate = self
parser.parse()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [NSObject : AnyObject]) {
element = elementName
if (element == "city"){
ifDirOK = true
let cityID = attributeDict ["id"] as? NSString
self.currentCityId = cityID as? String
}
}
func parser(parser: NSXMLParser!, foundCharacters string: String!) {
var data = string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
if (!data.isEmpty){
if (element == "city"){
dic[data] = self.currentCityId as String?
}
}
}
func parser(parser: NSXMLParser, foundAttributeDeclarationWithName attributeName: String, forElement elementName: String, type: String?, defaultValue: String?) {
if (ifDirOK && ifCityNameOK){
println("\(attributeName)")
}
}
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
}
}
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 would just like to get "first subitem" from the XML document. So far everything works but it always seems to store the last subItem. Basically the program is reading straight through the XML doc and stopping once it finds what I want. Maybe I am not clear on how to stop the parsing after it has found the first item. Do I need to tell the program to stop after it finds the the first item or add them to an array and then pick out a specific part? Any help would be great.
XML Doc
<file>main file
<item>
<subItem>first subitem</subItem>
</item>
</file>
<file>main file
<item>
<subItem>second subitem</subItem>
</item>
</file>
<file>main file
<item>
<subItem>third subitem</subItem>
</item>
</file>
Swift file
class parseThis {
var strXMLData:String = ""
var currentElement:String = ""
var passData:Bool=false
var passName:Bool=false
var itemNeeded = ""
// set up for parsing
func parser(parser: NSXMLParser, didStartElement elementName: String,
namespaceURI: String?, qualifiedName qName: String?,
attributes attributeDict: [String : String]) {
currentElement=elementName;
if(elementName=="file" || elementName=="item" ||
elementName=="subItem")
{
if(elementName=="subItem"){
passName=false;
}
passData=true;
}
}
func parser(parser: NSXMLParser, didEndElement elementName: String,
namespaceURI: String?, qualifiedName qName: String?) {
currentElement="";
currentElement=elementName;
if(elementName=="file" || elementName=="item" ||
elementName=="subItem"){
if(elementName=="subItem"){
passName=false;
}
passData=false;
}
}
func parser(parser: NSXMLParser, foundCharacters string: String) {
if(passName){
strXMLData=strXMLData+"\n\n"+string
}
if(passData)
{
itemNeeded = string
}
A super simplistic way of looking at this is that you only need the didFindCharacters and didEndElement. Every time you get into didFindCharacters you save them to a variable. Then every time you end an element you check whether this is the correct element name and whether it's the first of that element name type. Something like this:
var resultString : String?
var finalResultObject : String?
public func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
}
public func parser(parser: NSXMLParser, foundCDATA CDATABlock: NSData)
{
resultString = String(data: CDATABlock, encoding: NSUTF8StringEncoding)
}
public func parser(parser: NSXMLParser, foundCharacters string: String)
{
if(resultString == nil)
{
resultString = ""
}
resultString! += string
}
public func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?)
{
if(elementName == "subItem" && finalResultObject == nil)
{
//This is your first element of this type, save whatever you want.
finalResultObject = resultString
}
resultString = nil
}
public func parser(parser: NSXMLParser, parseErrorOccurred parseError: NSError)
{
//Log something in debug if you would like to.
}
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 {
//......
}