【问题标题】:Store XML data as an array, Swift将 XML 数据存储为数组,Swift
【发布时间】:2018-06-20 14:20:24
【问题描述】:

所以我正在开发一个允许用户进行多个不同测验的项目。

我的在线托管 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 个变量,question、answerA、answerB 和 answerC:

import Foundation

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

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

我想知道是否可以在数组中存储以下值:线索、信息、位置线索、选项a、b、c...,我将从数组开始制定实际的测验功能。

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

我知道这很长,可能很难理解我想要做什么,但如果有人能提供帮助,我将不胜感激。还应该注意的是,是的,我对 Swift 和 iOS 开发作为一个整体来说还很陌生。

【问题讨论】:

    标签: arrays swift xml parsing struct


    【解决方案1】:

    是的,你可以很容易地做到这一点,虽然我有一段时间没有使用 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 文档。一旦你遇到一个开始的问题元素,创建一个问题对象,填写其余部分,然后当你遇到一个结束的问题元素时,将当前问题添加到列表中。

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

    编辑:

    所以我不得不做一些改变,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)
    

    【讨论】:

    • 嘿伙计,感谢您提供非常详细的回复。当我添加您的代码时,一切似乎都工作正常,但是当我尝试打印您使用 print(quiz) 创建的 quiz 变量时,我收到的唯一输出是 []。有什么帮助吗?对此感到抱歉
    • 嘿抱歉,我刚发帖就离开了办公室。您可能需要单步执行代码或添加一些打印语句,以便您可以看到它发生了什么。这应该可以,我看看能不能把一个例子放在一起
    • 我已经用一个工作示例和更多解释对其进行了更新。可能有助于理解的指南:leaks.wanari.com/2016/08/24/xml-parsing-swift
    • 您好,再次感谢您的回复,并对延迟回复感到抱歉。我在 init.super() 行方面遇到了一些麻烦,我对此只是新手,所以我不确定我是否真的在正确的地方做这件事,我可以将你添加到不和谐的服务器或其他东西寻求帮助?如果不只是需要一点帮助,不用担心
    • 我不在 discord mate,但你应该有一个完整的解决方案。如果需要,可以在此处添加您的聊天记录。
    【解决方案2】:

    如果你不介意使用外部库,可以试试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"]
        }
    }
    

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

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

    希望这会有所帮助。

    【讨论】:

    • 我的价值为零
    • @KrunalNagvadia 你能说得更具体些吗?在什么时候你会得到nil 的价值?如果您遇到问题,请在 XMLMapper 的 github repo 上打开一个新问题。
    猜你喜欢
    • 2011-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-25
    • 2022-01-22
    • 2016-02-09
    • 2012-08-03
    • 1970-01-01
    相关资源
    最近更新 更多