Store XML data as an array, Swift - arrays

So I am working on a project which will allow users to do multiple different quizzes.
My XML, which is hosted online, is of the following format:
<questions>
<question>
<clue> sample clue 1 </clue>
<correct_answer>2</correct_answer>
<enumeration>1</enumeration>
<info> sample info 1 </info>
<location_clue>Sample locationClue (5,5)</location_clue>
<option_a>Ans1</option_a>
<option_b>Ans2</option_b>
<option_c>Ans3</option_c>
</question>
<question>
<clue> sample clue 2 </clue>
<correct_answer>3</correct_answer>
<enumeration>2</enumeration>
<info> sample info 2 </info>
<location_clue>Sample locationClue (4,2)</location_clue>
<option_a>Ans1</option_a>
<option_b>Ans2</option_b>
<option_c>Ans3</option_c>
</question>
</questions>
my parser initiation looks like this:
if let urlString = URL(string: "realURL goes here.xml -- This has an actual url in my code obviously.")
{
if let parser = XMLParser(contentsOf: urlString)
{
parser.delegate = self
parser.parse()
}
}
parserDidStartElement:
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:])
{
thisName = elementName
if thisName == "hunt"
{
}
}
ParserFoundCharacter:
func parser(_ parser: XMLParser, foundCharacters string: String)
{
let data = string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if data.count != 0
{
switch thisName
{
case "clue": questionClue = data
break
case "info": questionInfo = data
break
case "location_clue": locationClue = data
break
case "option_a": questionAnswerA = data
break
case "option_b": questionAnswerB = data
break
case "option_c": questionAnswerC = data
default:
break
}
}
}
This is the HuntDetail.swift class which creates a scruct named QUIZ, inside of this scruct there are currently 4 variables, question, answerA, answerB and answerC:
import Foundation
struct QUIZ {
var question = ""
var answerA = ""
var answerB = ""
var answerC = ""
}
Essentially the app will allow users to take multi-choice quizzes. When answers are selected a progress bar at the top of the interface will indicate their progress through the current quiz.
I was wondering if it would be possible to store the value of: clue,info,location_clue,option_a,b,c... inside of an array, from the array I will start to formulate the actual quiz functionality.
As it stands right now, the application will only display the last element of the data previously mentioned.
I know this is long and probably hard to understand what I'm trying to do but if anyone can help with this it would be greatly appreciated. It should also be noted that, yes, I am fairly new to Swift and iOS development as a whole.

Yes you can do this fairly easily, although I haven't used XMLParser in a while.
Note: In my code below I have renamed your QUIZ to Question as the struct represents a single question, not a whole quiz (list of questions)
So you want an empty array to each item to as you parse it:
var quiz = [Question]() // quiz is a list of questions.
Then you want to track the current question you are working on
var currentQuestion: Question?
So then each time you start and end a Question element, you know you have finished parsing a single question, so add it to the list.
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
if elementName == "Question" {
currentQuestion = Question()
}
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "Question", let question = currentQuestion {
quiz.append(question)
}
}
So you are just parsing the XML document top to bottom. Once you come upon a opening Question element, create a question object, fill the rest in and then when you encounter a closing question element, add the current question to the list.
By the end of the document your quiz variable should contain all of the questions in the document.
EDIT:
So I had to make a few changes, the foundCharacters can come in parts so we need to keep track of it.
Here is a working playground which returns 2 questions (based on your sample XML above). Answer C is always blank, It looks like this is because of a newline char and the trim is cutting of the text, you may need to remove newlines and then just trim whitespace but this code will give you a good start.
import Foundation
let xmlData = """
<questions>
<question>
<clue> sample clue 1 </clue>
<correct_answer>2</correct_answer>
<enumeration>1</enumeration>
<info> sample info 1 </info>
<location_clue>Sample locationClue (5,5)</location_clue>
<option_a>Ans1</option_a>
<option_b>Ans2</option_b>
<option_c>Ans3</option_c>
</question>
<question>
<clue> sample clue 2 </clue>
<correct_answer>3</correct_answer>
<enumeration>2</enumeration>
<info> sample info 2 </info>
<location_clue>Sample locationClue (4,2)</location_clue>
<option_a>Ans1</option_a>
<option_b>Ans2</option_b>
<option_c>Ans3</option_c>
</question>
</questions>
""".data(using: .utf8)!
struct Question {
var question: String?
var clue: String?
var info: String?
var locationClue: String?
var answerA: String?
var answerB: String?
var answerC: String?
}
class MySuperXMLParser: NSObject, XMLParserDelegate {
private let parser: XMLParser
private var quiz = [Question]()
private var currentQuestion: Question?
private var currentElement: String?
private var foundCharacters = ""
init(data: Data) {
parser = XMLParser(data: data)
super.init()
parser.delegate = self
}
func parse() -> [Question] {
parser.parse()
return quiz
}
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
if elementName == "question" {
currentQuestion = Question()
}
print("Started element: \(elementName)")
currentElement = elementName
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
print("found characters: \(string)")
foundCharacters += string
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
print("ended element: \(elementName), text = \(foundCharacters)")
let text = foundCharacters.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
switch currentElement
{
case "clue":
currentQuestion?.clue = text
break
case "info":
currentQuestion?.info = text
break
case "location_clue":
currentQuestion?.locationClue = text
break
case "option_a":
currentQuestion?.answerA = text
break
case "option_b":
currentQuestion?.answerB = text
break
case "option_c": currentQuestion?.answerC = text
default:
break
}
foundCharacters = ""
if elementName == "question", let question = currentQuestion {
print("adding question: \(question)")
quiz.append(question)
}
}
}
let parser = MySuperXMLParser(data: xmlData)
let quiz = parser.parse()
print(quiz.count, quiz)

If you don't mind using an external library, you can try XMLMapper.
Just use the following model classes:
class Questions: XMLMappable {
var nodeName: String!
var questions: [Question]?
required init(map: XMLMap) {
}
func mapping(map: XMLMap) {
questions <- map["question"]
}
}
class Question: XMLMappable {
var nodeName: String!
var clue: String?
var correct_answer: Int?
var enumeration: Int?
var info: String?
var location_clue: String?
var option_a: String?
var option_b: String?
var option_c: String?
required init(map: XMLMap) {
}
func mapping(map: XMLMap) {
clue <- map["clue"]
correct_answer <- map["correct_answer"]
enumeration <- map["enumeration"]
info <- map["info"]
location_clue <- map["location_clue"]
option_a <- map["option_a"]
option_b <- map["option_b"]
option_c <- map["option_c"]
}
}
And map your XML by calling map(XMLString:) function in XMLMapper:
let object = XMLMapper<Questions>().map(XMLString: xmlString)
print(object?.questions?.first?.clue ?? "nil")
Hope this helps.

Related

Problem decode string from xml file in Swift

I have an xml file with content look like below :
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="array_ngontay_kq">
<item>Bạn là người đáng tin cậy.</item>
<item>Bạn là người có óc xét đoán. </item>
</string-array>
</resources>
And I used HTMLReader to get this string-array but my output look like this:
Bạn là ngÆ°á»i Äáng tin cậy.
Bạn là ngÆ°á»i có óc xét Äoán.
Here is my code :
let fileURL = Bundle.main.url(forResource: "BoiTay", withExtension: "xml")
let xmlData = try! Data(contentsOf: fileURL!)
let topic = "array_ngontay_kq"
let document = HTMLDocument(data: xmlData, contentTypeHeader: "text/xml")
for item in document.nodes(matchingSelector: "string-array[name='\(topic)'] item") {
print(item.textContent)
}
Is there anyway to fix this or any other solution can do this without using HTMLReader. Sorry Im newer in XMLParse and I couldn't find any answer or tutorial about this type of xml file in Swift.
First of all, you should better check if your BoiTay.xml is really in UTF-8. I'm not familiar with Vietnamese encodings, but some tools may generate XMLs with other encodings than UTF-8, even if the xml header states encoding="utf-8".
The result seems to be an encoding issue, rather than a bug of the library or your code.
Please show hex dump of your xmlData including the first item element.
print(xmlData as NSData)
Maybe the first 256 bytes would be enough.
By the way, using XMLParser is not so difficult. (Though it is not super-easy.)
Here is an example you can test in the Playground.
import Foundation
class ResoucesParsingDelegate: NSObject, XMLParserDelegate {
//Property to keey all `string-array`s by name
var stringArrays: [String: [String]] = [:]
var nameParsing: String? = nil
var stringArrayParsing: [String]? = nil
var currentText: String? = nil
func parserDidStartDocument(_ parser: XMLParser) {
print(#function)
}
func parserDidEndDocument(_ parser: XMLParser) {
print(#function)
}
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
print(#function, parseError)
}
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
switch elementName {
case "string-array":
guard let name = attributeDict["name"] else {
print("`string-array` element needs `name` attribute")
return
}
//When you find `<string-array name="...">`, prepare a string array to keep items with its name
nameParsing = name
stringArrayParsing = []
case "item":
if stringArrayParsing == nil {
print("invalid `item` element")
return
}
//When you find `<item>`, prepare a string to keep the content text of the element
currentText = ""
//Prodess other elements
//...
default:
print("Unknown element `\(elementName)`, ignored")
}
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
switch elementName {
case "string-array":
if stringArrayParsing == nil || nameParsing == nil {
print("invalid end tag `string-array`")
return
}
//When you find `</string-array>`, add the current string array to `stringArrays` with its name
stringArrays[nameParsing!] = stringArrayParsing!
//Clear string array for next use
stringArrayParsing = nil
case "item":
if stringArrayParsing == nil || currentText == nil {
print("invalid end tag `item`")
return
}
//When you find `</item>` add the content text to `stringArrayParsing`
stringArrayParsing!.append(currentText!)
//Clear content text for next use
currentText = nil
//Prodess other elements
//...
default:
print("Unknown element `\(elementName)`, ignored")
}
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
if currentText == nil {
//Silently igonore characters while content string is not ready
return
}
currentText! += string
}
}
let xmlText = """
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="array_ngontay_kq">
<item>Bạn là người đáng tin cậy.</item>
<item>Bạn là người có óc xét đoán. </item>
</string-array>
</resources>
"""
let xmlData = xmlText.data(using: .utf8)!
print(xmlData, xmlData as NSData)
let parser = XMLParser(data: xmlData)
let resoucesParsingDelegate = ResoucesParsingDelegate()
parser.delegate = resoucesParsingDelegate
parser.parse()
print(resoucesParsingDelegate.stringArrays)
Output:
246 bytes <3c3f786d 6c207665 7273696f 6e3d2231 2e302220 656e636f 64696e67 3d227574 662d3822 3f3e0a3c 7265736f 75726365 733e0a20 2020203c 73747269 6e672d61 72726179 206e616d 653d2261 72726179 5f6e676f 6e746179 5f6b7122 3e0a2020 20202020 20203c69 74656d3e 42e1baa1 6e206cc3 a0206e67 c6b0e1bb 9d6920c4 91c3a16e 67207469 6e2063e1 baad792e 3c2f6974 656d3e0a 20202020 20202020 3c697465 6d3e42e1 baa16e20 6cc3a020 6e67c6b0 e1bb9d69 2063c3b3 20c3b363 2078c3a9 7420c491 6fc3a16e 2e203c2f 6974656d 3e0a2020 20203c2f 73747269 6e672d61 72726179 3e0a3c2f 7265736f 75726365 733e>
parserDidStartDocument
Unknown element `resources`, ignored
Unknown element `resources`, ignored
parserDidEndDocument
["array_ngontay_kq": ["Bạn là người đáng tin cậy.", "Bạn là người có óc xét đoán. "]]
If you test this code with the contents of your BoiTay.xml and get the similar result as HTMLReader, the problem definitely is an issue of encoding.
(You may need to modify this code if your actual xml is more complex than the example.)

XML only displays last item in array in Swift

Here is an example of the XML file I have. I have everything parsed, but can't get the array to display all items.
<dish>
<title> Pancakes </title>
<calories> 350 calories </calories>
<ingredients>
<item>ingredient 1</item>
<item>ingredient 2</item>
<item>ingredient 3</item>
</ingredients>
</dish>
I have a struct of Recipes and a struct of Ingredients. The ingredients only has a variable of "item" in it. I've removed some of the recipe code to include the ingredients portion of the parsing code, since everything else works and prints.
struct Ingredients {
var item = ""
}
var tableViewDataSource = [Recipes]()
var tableViewIngSource = [Ingredients]()
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
thisName = elementName
// elementName == "recipe" code
if elementName == "ingredients" {
var recipeItem = ""
}
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
let data = string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if data.count != 0 {
switch thisName
{
case "title": recipeTitle = data
case "calories": recipeCalories = data
case "ingredients": recipeIngredients = data
case "item": recipeItem = data
default:
break
}
}
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "dish" {
var recipe = Recipes()
recipe.title = recipeTitle
recipe.duration = recipeDuration
recipe.calories = recipeCalories
recipe.ingredients = recipeIngredients
recipe.description = recipeDescription
//print(recipe)
tableViewDataSource.append(recipe)
}
if elementName == "ingredients" {
var ingredients = Ingredients()
ingredients.item = recipeItem
for item in tableViewIngSource {
print(item)
}
print(ingredients)
print(tableViewIngSource.count)
tableViewIngSource.append(ingredients)
}
}
This is the output. It results in the last two print statement outputs, the for loop does not print anything
Ingredients(item: "ingredient 3")
0
From what I've seen, a custom XML parser needs to be used. I've looked into SWXMLHash, but it seems the XML code needs to be in the swift file
Your code needs a good bit of reorganizing to properly parse the XML data.
The following is more like what you want to have:
Your Recipe structure (Note it is Recipe, not Recipes since it represents one recipe):
struct Recipe {
let title: String
let calories: String
let ingredients: [String]
}
Your parsing code:
var currentText: String?
var recipeTitle: String?
var recipeCalories: String?
var recipeIngredients: [String]?
var tableViewDataSource = [Recipe]()
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
switch elementName {
case "dish":
recipeTitle = nil
recipeCalories = nil
recipeIngredients = nil
case "ingredients":
recipeIngredients = []
case "title", "calories", "item":
currentText = ""
default:
break
}
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
switch elementName {
case "dish":
if let recipeTitle = recipeTitle, let recipeCalories = recipeCalories, let recipeIngredients = recipeIngredients {
let recipe = Recipe(title: recipeTitle, calories: recipeCalories, ingredients: recipeIngredients)
tableViewDataSource.append(recipe)
}
recipeTitle = nil
recipeCalories = nil
recipeIngredients = nil
case "title":
recipeTitle = currentText?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
currentText = nil
case "calories":
recipeCalories = currentText?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
currentText = nil
case "item":
if let currentText = currentText {
recipeIngredients?.append(currentText.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines))
}
currentText = nil
default:
break
}
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
currentText?.append(string)
}

Swift: Can't insert NSObject into Array as it wants a [String] instead

I have a model object called Hashtag. This simply contains a optional String variable called hashtagName. I fetch the data from my Firebase Database and append the hashtags to my fashionHashtags, which is a [Hashtag]. The issue I have is that I want to append that to my other categoriesArray by using the insertElementAtIndexPath function. I cannot do this as it wants an array of Strings and not an array of Hashtag. When I autocorrect it, it replaces it with fashionHashtags as! [String] but that creates another error. How do I fix this so it allows me to do so? I would like to stick to the Model Object way of doing things. Thank you guys. An answer would be highly appreciated. Code is below:
class Hashtag: NSObject {
vvar hashtagName: String?
}
private let reuseIdentifier = "Cell"
class HashtagView: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var catagoriesArray: [String] = ["FASHION", "FOOD", "HOBBIES", "MUSIC"]
var fashionHashtags = [Hashtag]()
var foodHashtags = [Hashtag]()
var hobbiesHashtags = [Hashtag]()
var musicHashtags = [Hashtag]()
var hashtagsArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
self.hashtagsArray.removeAll()
self.navigationController?.navigationBar.tintColor = .white
navigationItem.title = "Hashtag"
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Finished", style: .plain, target: self, action: #selector(finishSelectingHashtags))
self.collectionView?.backgroundColor = .white
self.collectionView?.register(HashtagCell.self, forCellWithReuseIdentifier: reuseIdentifier)
self.collectionView?.contentInset = UIEdgeInsetsMake(10, 0, 0, 0)
handleFetchFashionHashtags()
}
func insertElementAtIndexPath(element: [String], index: Int) {
catagoriesArray.insert(contentsOf: element, at: index)
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.item == 0 {
insertElementAtIndexPath(element: fashionHashtags, index: indexPath.item + 1)
self.collectionView?.performBatchUpdates(
{
self.collectionView?.reloadSections(NSIndexSet(index: 0) as IndexSet)
}, completion: { (finished:Bool) -> Void in
})
}
}
Based upon my understanding, there could be a couple of different approaches. Here would be the approach of looping through the array of Hashtag objects and appending the hashtagName string property to the categoriesArray of strings.
for hashTagItem in fashionHashtags {
if let hashTag = hashTagItem.hashtagName {
// Appends to categoriesArray as a string
categoriesArray.append(hashTag)
}
}
Another approach would be to build a set of strings and then insert it as it makes sense.
var hashTagString: [Strings] = []
for hashTagItem in fashionHashtags {
if let hashTag = hashTagItem.hashtagName {
hashTagStrings.append(hashTag)
}
}
// Insert or add the hash tag strings as it makes sense
categoriesArray += hashTagStrings // Add all at once if it makes sense

Type '___' has no member 'array'

I am working on this quote app, and I keep running into two errors that just don't want to cooperate with me. It says "Type 'businessQuote' has no member ('array'/'dict')". In the following screen shot, you will see the error on the line. The whole point is to get the app to show a random quote in the text fields provided. Could you please help me? Thank you in advance.
Code with the error
My goal is to get "ImportList" to work
'ImportList' Swift file
If there is another question like this that I have overlooked, I would appreciate it if you could link me to it. But I just really need an answer. Thank you again.
Here's the code with the error:
import Foundation
import UIKit
import Social
class businessQuote: UIViewController {
//============================//
//********** Outlets *********//
//============================//
let utility = Utility()
#IBOutlet weak var quoteDisplay: UILabel!
#IBOutlet weak var authorDisplay: UILabel!
#IBOutlet weak var quoteBackground: UIImageView! //GET BACK TO THIS
//============================//
//********** General *********//
//============================//
let date = NSDate()
var Author: String = ""
var Quote: String = ""
override func viewDidLoad() {
super.viewDidLoad()
// Checks if time is greater then 3pm to change background
let currentTime = utility.currentTime()
if (currentTime >= 15 ) {
quoteBackground.image = UIImage(named: "quote_background.png")
} else {
quoteBackground.image = UIImage(named:"morning_quote_background.png")
}
}
//============================//
//********* New Quote ********//
//============================//
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// Generates Random Number
func randomNumber(arrayLength: Int) -> Int {
let unsignedArrayCount = UInt32(arrayLength)
let unsignedRandomNumber = arc4random_uniform(unsignedArrayCount)
let randomNumber = Int(unsignedRandomNumber)
return randomNumber
}
// Importing Quotes plist File
let businessQuotes = ImportList(FileName: "BusinessList")
// Selects Quote
let chosenQuote: String = businessQuote.array[randomNumber(businessQuote
.count())] as! String
let chosenAuthor = businessQuote.dict[chosenQuote]! as String
// Assigns Quote & Author to IBOutlet
Author = chosenAuthor
Quote = chosenQuote
quoteDisplay.text = Quote
authorDisplay.text = Author.uppercaseString
}
}
This is the code with the 'array' and 'dict'
import Foundation
struct ImportList {
let path: String
init(FileName: String) {
self.path = NSBundle.mainBundle().pathForResource("\(FileName)", ofType: "plist")!
}
var dict: Dictionary<String, String> {
return NSDictionary(contentsOfFile: path)! as! Dictionary
}
var array: Array<AnyObject> {
return [String](arrayLiteral: String(dict.keys) { $0 as AnyObject as! String })
}
func count() -> Int {
return array.count
}
}
Thank you!
You have declared variable businessQuotes as:
// Importing Quotes plist File
let businessQuotes = ImportList(FileName: "BusinessList")
But using businessQuote instead, see you are missing "s" at the end. Spelling mistake. Following lines should be:
// Selects Quote
let chosenQuote: String = businessQuotes.array[randomNumber(businessQuotes
.count())] as! String
let chosenAuthor = businessQuotes.dict[chosenQuote]! as String

Parse the xml array and store the data into array or dictionary using swift

I need to parse the xml data using nsxmlparser.But i am facing issue getting the required data and storing it to array or dictionary using swift.I have implemented the the delegate function and able to find the element name like this
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [NSObject : AnyObject])
{
println(elementName)
if(elementName == "subCategories")
{
categoryName: String = [attributeDict valueForKey:"category"];
id = [attributeDict valueForKey:"id"];
name = [attributeDict valueForKey:"name"];
// Now You can store these in your preferable data models - data objects/array/dictionary
}
however m not able to implement the same using swift.My xml looks like this
<category>
<id>cat00000</id>
<name>Best Buy</name>
<active>true</active>
<path>
<category>
<id>cat00000</id>
<name>Best Buy</name>
</category>
</path>
<subCategories>
<category>
<id>abcat0100000</id>
<name>TV & Home Theater</name>
</category>
<category>
<id>abcat0200000</id>
<name>Audio</name>
</category>
<category>
<id>abcat0300000</id>
<name>Car Electronics & GPS</name>
</category>
That's a weird mix of Swift and Objective-C you posted, and it looks like you're trying to read the attributes of each <category>, but really you need to get the value of each child node of <category>. Try this:
class CategoryParser: NSObject, NSXMLParserDelegate {
var subcategories = [[String : String]]()
var currentSubcategory: [String : String]?
var currentElementName: String?
var completion: (([[String : String]]) -> ())?
func parseXML( string: String, completion: (([[String : String]]) -> ())? ) {
guard let data = string.dataUsingEncoding( NSUTF8StringEncoding ) else {
fatalError( "Base XML data" )
}
self.completion = completion
let parser = NSXMLParser(data: data )
parser.delegate = self
parser.parse()
}
func parserDidEndDocument(parser: NSXMLParser) {
self.completion?( self.subcategories )
}
func parser(parser: NSXMLParser, parseErrorOccurred parseError: NSError) {
print( "Parse error: \(parseError)" )
}
func parser(parser: NSXMLParser, foundCharacters string: String) {
if let elementName = self.currentElementName {
if [ "id", "name" ].contains( elementName ) {
self.currentSubcategory?[ elementName ] = string
}
}
}
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "category", let subcategory = self.currentSubcategory {
self.subcategories.append( subcategory )
self.currentSubcategory = nil
}
}
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
if elementName == "category" {
self.currentSubcategory = [String : String]()
}
else {
self.currentElementName = elementName
}
}
}
let categoryParser = CategoryParser()
let xmlString = "<subCategories><category><id>someId</id><name>someName</name></category></subCategories>"
categoryParser.parseXML( xmlString ) { (categories) in
print( categories )
}

Resources