Swift Parsing Attribute Name for Given Elementname

Swift parsing attribute name for given elementName

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?) {

}

}

Swift XMLParser // Select node based on element & attribute

When I've needed to deal with this I've used a few instance variables to keep track of where I am.

var inInputs = false
var inOverlays = false

Then update your didStartElement to look for "inputs" and set inInputs = true. And look for overlays and set inOverlays = true. Do the same for didEndElement but set the flags back to false.

Then you can update your if elementName == "overlay" { to if elementName == "overlay" && inOverlays {. Make a similar change for input.

func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
if elementName == "overlays" {
inOverlays = true
} else if elementName == "inputs" {
inInputs = true
} else if elementName == "overlay" && inOverlays {
OverlayValue = ""
}
}

func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "overlays" {
inOverlays = false
} else if elementName == "inputs" {
inInputs = false
} else if elementName == "overlay" && inOverlays {
OverlayXML.append(OverlayArray(value: OverlayValue!))
OverlayValue = nil
}
}

Swift XML attribute parsing

A couple of observations:

  1. Your didStartElement should simply look at attributeDict to get the attributes for an element.

  2. You don't need a foundCharacters method in this case, because you're only parsing elements tags, and not parsing anything between the open and close tags.

  3. The foundCharacters method, if you even needed it, shouldn't have a private didStartElement implemented within it.

  4. The only trick in your example is that you have very confusing XML with nested day1 tags. I'd really suggest changing the XML to something that makes sense.

    But, if you're stuck with this, one fairly flexible solution is to keep a stack of element names (implemented as simple array, elementNames), push an elementName onto the stack in didStartElement (by adding to the end of the array of elementNames) and popping one off in didEndElement (by calling removeLast). So, for example, by the time you hit the inner day1 tag inside your XML, the array of elementNames will be ["root", "day1", "day1"].

    Now that you have that, you can check to see if you are looking at day1 inside another day1 by seeing if the last two items in elementNames are both day1.

For example:

func beginParsing(URL: NSURL) {
let parser = NSXMLParser(contentsOfURL: URL)!
parser.delegate = self
parser.parse()
print(schedule)
}

var schedule: [String: String]?
var elementNames = [String]()

func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
elementNames.append(elementName)
let count = elementNames.count

if count >= 2 && elementNames[count - 2] == "day1" && elementNames[count - 1] == "day1" {
schedule = attributeDict
}
}

func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
elementNames.removeLast()
}

func parser(parser: NSXMLParser, parseErrorOccurred parseError: NSError) {
print(parseError)
}

Parse XML properties in Swift

You should compare the element name in parser:didStartElement:qualifiedName:attributes: and extract attributes from it to get url. It should be something like this. The attributes dict contains all the attributes that are in that element.

For example look at this particular element,

<media:thumbnail height="540" width="960" url="https://i.vimeocdn.com/video/611645334_960.webp"/>

If you read attributes dict for this, it should be like this,

["height": "540", "url": "https://i.vimeocdn.com/video/611645334_960.webp", "width": "960"]

Here is code for extracting url that you want,

func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
if elementName == "media:thumbnail" {
if let url = attributeDict["url"] {
print(url)
}
}
}

How can I get a specific attribute from a XML in SWIFT with NSXMLParser

I figured it out by myself. Here is what I did in order to retrieve the value in case there's someone who encounter the same problem as me.

func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
if elementName == "reading"{
if attributeDict["type"] == "NPSI_PM25_3HR"{
let PSIValue = attributeDict["value"]! as String
print(i)
switch i {
case 0:
area = "NRS"
nationalPSI = PSIValue
case 1:
area = "North"
case 2:
area = "South"
case 3:
area = "Central"
case 4:
area = "West"
case 5:
area = "East"
default:
area = ""
}
i += 1
print(area, ":", PSIValue)
}

}
}

Accessing values of elements when using NSXMLParser in Swift

Parsing an XML file with NSXMLParser is not that easy as you expect here. You have to implement several methods from the NSXMLParserDelegate protocol to catch events. You already did that for didStartElement which gives the (for me) expected results: The element name and its attributes (there are no attributes in the XML file you linked above).

Everything works fine... so far.

Now that you catched the start of an element you have to take care on the further processing:

  • The protocol method foundCharacters is fired between the start and the end of an element. Maybe several times. You have to implement that method and append the characters found to a string variable, depending on the name of the element.

  • When didEndElement is fired, your string variable is filled completely with the content of the element

If the XML file has a deep hierachically structure things may get complicated more and more.

Sorry for this broad answer but unfortunately there is no take that XML file and give me a deeply nested dictionary method with NSXMLParser.

XML Parse swift accessing attributeDict

import Fuzi

func parse() {
do {
// Read the xml file as String
guard
let path = NSBundle.mainBundle().pathForResource("arvestusDays", ofType: "xml")
else {
print("XML file does not exist.")
return
}
let xmlString = try String(contentsOfFile: path, encoding: NSUTF8StringEncoding)

// Parse the xml string
let doc = try XMLDocument(string: xmlString)

if let days = doc.root.firstChild(tag: "days") {
for day in days.children {
print(day["name"])
print(day["short"])
print(day["day"])
}
}
} catch let error {
print(error)
}
}

I don't have a Mac with me right now so I can't run to test it, but this code should work. Comment in case it doesn't.

UPDATE:

In case you have encoding problems, try creating the XMLDocument from NSData rather than from String:

        let xmlData = NSData(contentsOfFile: path)

// Parse the xml string
let doc = try XMLDocument(data: xmlData!)


Related Topics



Leave a reply



Submit