将XML数据存储为数组,Swift

时间:2018-06-20 14:20:24

标签: arrays swift xml parsing struct

所以我正在研究一个项目,该项目将允许用户进行多个不同的测验。

我的XML(在线托管)具有以下格式:

<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>

我的解析器启动看起来像这样:

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

        }
    }

}

这是HuntDetail.swift类,它创建一个名为QUIZ的脚本,该脚本内部目前有4个变量,即问题,答案A,答案B和答案C:

import Foundation

struct QUIZ {
    var question = ""
    var answerA = ""
    var answerB = ""
    var answerC = ""
}

从本质上讲,该应用程序将允许用户进行多项选择测验。选择答案后,界面顶部的进度条将显示当前测验的进度。

我想知道是否可以在一个数组内存储以下内容:clue,info,location_clue,option_a,b,c ...,我将从该数组开始制定实际的测验功能。 / p>

就目前而言,该应用程序将仅显示前面提到的数据的最后一个元素。

我知道这很长,可能很难理解我要做什么,但是如果有人可以提供帮助,将不胜感激。还应该指出的是,是的,我对Swift和iOS的整体开发还很陌生。

2 个答案:

答案 0 :(得分:0)

是的,尽管我已经有一段时间没有使用XMLParser了,但是您可以轻松地做到这一点。

注意::在下面的代码中,我将您的QUIZ重命名为Question,因为该结构代表一个问题,而不是整个测验(问题列表)

因此,您希望在解析每个项目时使用一个空数组:

var quiz = [Question]() // quiz is a list of questions. 

然后您要跟踪当前正在处理的问题

var currentQuestion: Question?

因此,每次您开始和结束一个Question元素时,您都知道您已经完成了对单个问题的解析,因此将其添加到列表中。

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)
     }
} 

因此,您只是从上至下解析XML文档。一旦遇到一个开头的Question元素,就创建一个Question对象,填充其余的内容,然后在遇到一个封闭的Question元素时,将当前问题添加到列表中。

在文档末尾,您的测验变量应包含文档中的所有问题。

编辑:

因此,我必须进行一些更改,foundCharacters可以分成几部分,因此我们需要对其进行跟踪。

这是一个工作场所,它会返回2个问题(基于上面的示例XML)。答案C始终为空白,这似乎是因为换行符,并且修剪会剪切文本,您可能需要先删除换行符,然后再修剪空格,但是此代码将为您提供一个良好的开始。

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)

答案 1 :(得分:0)

如果您不介意使用外部库,则可以尝试XMLMapper

只需使用以下模型类:

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"]
    }
}

并通过在map(XMLString:)中调用XMLMapper函数来映射XML:

let object = XMLMapper<Questions>().map(XMLString: xmlString)
print(object?.questions?.first?.clue ?? "nil")

希望这会有所帮助。