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.)
Related
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.
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)
}
Hi guys I don't know why the array Places returns weird values like 0x6080004b3aa0 instead of displaying my title, coordinate and subtitle out of my JSON url. Thanks for your Help!
import MapKit
#objc class Place: NSObject {
var title: String?
var coordinate: CLLocationCoordinate2D
var subtitle: String?
init(title:String,subtitle:String, coordinate:CLLocationCoordinate2D){
self.title = title
self.coordinate = coordinate
self.subtitle = subtitle
}
static func getPlaces() -> [Place] {
guard let url = NSURL(string: "https://script.googleusercontent.com/macros/echo?user_content_key=Z-LfTMdhgAg_6SRd-iMucSyWu-LFBQO8MLxJZ6DPcL05Rtr3joCCypWD2l46qaegSpVpVINc1DLl5inoDOgGx3p3ANpY1AkGOJmA1Yb3SEsKFZqtv3DaNYcMrmhZHmUMWojr9NvTBuBLhyHCd5hHa1ZsYSbt7G4nMhEEDL32U4DxjO7V7yvmJPXJTBuCiTGh3rUPjpYM_V0PJJG7TIaKp4bydEiKBUZP6fpOyGJIhkmEGneM7ZIlWloTVbXmkjs15vHn8T7HCelqi-5f3gf3-sKiW3k6MDkf31SIMZH6H4k&lib=MbpKbbfePtAVndrs259dhPT7ROjQYJ8yx") else { return [] }
let request = NSMutableURLRequest(url: url as URL!)
var places = [Place]()
let task = URLSession.shared.dataTask(with: request as URLRequest) {data,response,error in
guard error == nil && data != nil else {
print ("Error:",error)
return
}
let httpStatus = response as? HTTPURLResponse
if httpStatus?.statusCode == 200
{ if data?.count != 0
{
let responseString = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! NSDictionary
let contacts = responseString["Sheet1"] as? [AnyObject]
for contact in contacts!{
var places = [Place]()
let title = contact["name"] as! String
let subtitle = contact["description"] as? String
let latitude = contact["latitude"] as? Double ?? 0, longitude = contact["longitude"] as? Double ?? 0
let place = Place(title:title,subtitle:subtitle!,coordinate: CLLocationCoordinate2DMake(latitude, longitude))
places.append(place)
print(latitude)
print(place)
}
}
else {
print("No data got from url")
}
} else {
print("error httpsatus code is:", httpStatus!.statusCode)
}
}
task.resume()
return places as [Place]
}
}
I think the problem is this:
let place = Place(title:title,subtitle:subtitle!,coordinate: CLLocationCoordinate2DMake(latitude, longitude))
When I print(place) it returns the weird results
When you make a class that subclasses from NSObject you're creating a object that is backed by an Objective-c class -- which in some circumstances can be really useful (most common use is when you want to take your object and archive it as a blob of binary data).
I'm guessing that in your case, you probably don't want/need to subclass NSObject.
Here's a simplified example to show what's happening:
Here's a class backed by NSObject:
#objc class ObjCPlace: NSObject {
let name: String
init(name: String) {
self.name = name
}
}
If you create an instance of this object and try to print contents - like you've found, you get the objects location in memory:
let testObjcPlace = ObjCPlace(name: "New York")
print(testObjcPlace)
// prints:
// <__lldb_expr_310.ObjCPlace: 0x600000055060>
On alternative to using print could be to use dump that provides a more detailed look at your object:
let testObjcPlace = ObjCPlace(name: "New York")
dump(testObjcPlace)
// Prints:
// ▿ <__lldb_expr_310.ObjCPlace: 0x600000055060> #0
// - super: NSObject
// - name: "New York"
But instead of making an NSObject subclass, you probably just want to make a Swift class (or in this example a struct take a look at this question and answers for explanations of the differences)
struct Place {
let name: String
init(name: String) {
self.name = name
}
}
Because this object isn't an Objective-c object you can use print and get the internal properties printed as well:
let testPlace = Place(name: "New York")
print(testPlace)
// Prints:
// Place(name: "New York")
p/s welcome to StackOverflow!
I have created a mini translation from English words to Spanish words. I would like to use the englishArray.plist instead of my englishArray = ["the cat"] How can I create this?
I have also used a localizable.strings to retrieve the value "the cat" for "el gato" but I would like to retrieve this from englishArray.plist
I started off with this but not sure if I'm on the right path
let path = NSBundle.mainBundle().pathForResource("englishArray", ofType: "plist")
let plistEnglishArray = NSArray(contentsOfFile: path!)
Here is the rest of my code:
var englishArray: [String] = ["rainbow", "boots", "heart", "leaf", "umbrella", "tired", "angry", "cry", "the cat" ]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.translateTextField.delegate = self
picker.delegate = self
picker.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func translateButtonTapped(sender: UIButton) {
let emptyString = self.translateTextField.text
if (emptyString!.isEmpty) {
print("please enter a word")
}
for transIndex in englishArray.indices {
if englishArray[transIndex] == emptyString!.lowercaseString {
//englishArray
//translateLabel.text = "\(spanishArray[transIndex])"
translateLabel.text = NSLocalizedString(emptyString!.lowercaseString, comment:"")
print(emptyString)
return
}
}
}
Swift 4
The absolute simplest way to do this is
let url = Bundle.main.url(forResource: "Sounds", withExtension: "plist")!
let soundsData = try! Data(contentsOf: url)
let myPlist = try! PropertyListSerialization.propertyList(from: soundsData, options: [], format: nil)
The object myPlist is an Array or Dictionary, whichever you used as the base of your plist.
Change your root object to Array, then
var myEnglishArray: [String] = []
if let URL = NSBundle.mainBundle().URLForResource("englishArray", withExtension: "plist") {
if let englishFromPlist = NSArray(contentsOfURL: URL) as? [String] {
myEnglishArray = englishFromPlist
}
}
Swift 4
You can use Codable which is pure swift type.
Firstly load Plist file from bundle then use PropertyListDecoder
Complete code -
func setData() {
// location of plist file
if let settingsURL = Bundle.main.path(forResource: "JsonPlist", ofType: "plist") {
do {
var settings: MySettings?
let data = try Data(contentsOf: URL(fileURLWithPath: settingsURL))
let decoder = PropertyListDecoder()
settings = try decoder.decode(MySettings.self, from: data)
print("array is \(settings?.englishArray ?? [""])")//prints array is ["Good morning", "Good afternoon"]
} catch {
print(error)
}
}
}
}
struct MySettings: Codable {
var englishArray: [String]?
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
englishArray = try values.decodeIfPresent([String].self, forKey: .englishArray)
}
}
This will read a resource in your bundle with the name "englishArray.plist" and store it in the immutable variable english. It will be an Optional that you should test before using.
It uses a closure to read the file and return the array, this lets you use a immutable value rather than a mutable variable that can be changed. It's a good idea to use immutables wherever you can - they promote stability.
import Foundation
let english:[String]? = {
guard let URL = NSBundle
.mainBundle()
.URLForResource("englishArray", withExtension: "plist") else {
return nil
}
return NSArray(contentsOfURL: URL) as? [String]
}()
Here is the solution for swift 3. For this solution you do not need to change types in your plist structure (keep Dictionary, Array, as is). Also note that since your array's name in plist is also englishArray so the (value for key) argument in the second if statement is also englishArray.
var myEnglishArray: [String] = []
if let URL = Bundle.main.url(forResource: "englishArray", withExtension: "plist") {
guard let englishFromPlist = NSDictionary(contentsOf: URL) else { return [] }
if let englishArray = englishFromPlist.value(forKey: "englishArray") as? [String] {
for myEnglish in englishArray {
myEnglishArray.append(myEnglish)
}
}
}
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 )
}