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:
Your
didStartElement
should simply look atattributeDict
to get the attributes for an element.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.The
foundCharacters
method, if you even needed it, shouldn't have a privatedidStartElement
implemented within it.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 anelementName
onto the stack indidStartElement
(by adding to the end of the array ofelementNames
) and popping one off indidEndElement
(by callingremoveLast
). So, for example, by the time you hit the innerday1
tag inside your XML, the array ofelementNames
will be["root", "day1", "day1"]
.Now that you have that, you can check to see if you are looking at
day1
inside anotherday1
by seeing if the last two items inelementNames
are bothday1
.
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
How to Capture Depth Data from Camera in iOS 11 and Swift 4
Why in Swift We Cannot Adopt a Protocol Without Inheritance a Class from Nsobject
Using Stringbyreplacingcharactersinrange in Swift
Convert Character to Integer in Swift
Swiftui Customized Text Field in Osx
Swift - Cast Int64 to Anyobject for Nsmutablearray
How to Remove Optional from String Value Swift
How to Add Kerning to a Textfield in Swiftui
Child View Controller to Rotate While Parent View Controller Does Not
How to Create Enum with Raw Type of Cgpoint
Writing Swift Dictionary to File
Storing Different Types of Value in Array in Swift
In Swift, How to Get Memory Back to Normal After an Skscene Is Removed
Get Current Time as String Swift 3.0
Getting Country Name from Country Code
Trying to Stream Audio from Microphone to Another Phone via Multipeer Connectivity