web-dev-qa-db-ja.com

XMLの解析Swift 4

SwiftでXML解析を行うのは初めてで、このコードは SwiftのURLからXMLを解析する で見つかりましたが、試してみるとEXC_BAD_INSTRUCTIONエラーが発生します。コードを実行します。エラーの説明は次のとおりです。fatal error: unexpectedly found nil while unwrapping an Optional value

これは私の単純なXMLファイルです。

<xml>
    <book>
        <title>Book Title</title>
        <author>Book Author</author>
    </book>
</xml>

次のコードは、XMLParserオブジェクトを作成し、ドキュメントにあるXMLファイルを解析します。

// get xml file path from Documents and parse

let filePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last?.appendingPathComponent("example.xml")

let parser = XMLParser(contentsOf: filePath!)
parser?.delegate = self

if (parser?.parse())! {
    print(self.results)
}

ここでは、XMLParserDelegateメソッドを実装し、辞書を定義します。

// a few constants that identify what element names we're looking for inside the XML

let recordKey = "book"
let dictionaryKeys = ["title","author"]

// a few variables to hold the results as we parse the XML

var results: [[String: String]]!          // the whole array of dictionaries
var currentDictionary: [String: String]!  // the current dictionary
var currentValue: String?                 // the current value for one of the keys in the dictionary

// start element
//
// - If we're starting a "record" create the dictionary that will hold the results
// - If we're starting one of our dictionary keys, initialize `currentValue` (otherwise leave `nil`)


func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {

    if elementName == recordKey {

        currentDictionary = [String : String]()

    } else if dictionaryKeys.contains(elementName) {

        currentValue = String()

    }
}

// found characters
//
// - If this is an element we care about, append those characters.
// - If `currentValue` still `nil`, then do nothing.

func parser(_ parser: XMLParser, foundCharacters string: String) {

    currentValue? += string

}

// end element
//
// - If we're at the end of the whole dictionary, then save that dictionary in our array
// - If we're at the end of an element that belongs in the dictionary, then save that value in the dictionary


func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {

    if elementName == recordKey {

        results.append(currentDictionary)
        currentDictionary = nil

    } else if dictionaryKeys.contains(elementName) {

        currentDictionary[elementName] = currentValue
        currentValue = nil

    }
}

// Just in case, if there's an error, report it. (We don't want to fly blind here.)

func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {

    print(parseError)

    currentValue = nil
    currentDictionary = nil
    results = nil

}

エラーは、didEndElementcurrentDictionaryディクショナリに追加されたときにresultsメソッドで見つかります。

func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {

    if elementName == recordKey {

        results.append(currentDictionary)    // Line with Error
        currentDictionary = nil

    } else if dictionaryKeys.contains(elementName) {

        currentDictionary[elementName] = currentValue
        currentValue = nil

    }
}

この問題の解決にご協力ください。 SwiftのURLからXMLを解析する で提供されているものとまったく同じコードを使用していますが、問題はないようです。私は何か間違ったことをしていますか?

3
Ricky

コードが実際にresultsを初期化することはないため、初めてコードを使用しようとすると、nilオプション値を強制的にアンラップしようとします。良くないね。そして、それを暗黙的にアンラップされたオプションとして宣言する理由はありません。

変更する必要があります:

_var results: [[String: String]]!
_

に:

_var results = [[String: String]]()
_

また、次の行を削除する必要があります。

_results = nil
_

parser(_:parseErrorOccurred:)メソッドから。

resultsをオプションにしたい場合は、コードに次の変更を加えることができます。

resultsの宣言を次のように変更します。

_var results: [[String: String]]? = [[String: String]]()
_

変化する:

_results.append(currentDictionary)
_

に:

_results?.append(currentDictionary)
_

そして、parser(_:parseErrorOccurred:)に_results = nil_行を残します。

5
rmaddy

rmaddyは問題を正しく診断しました(+1)。 resultsは初期化されませんでした。

コードはほとんどそのままにしておくことをお勧めしますが、次のようにparserDidStartDocumentを初期化する results メソッドを追加するだけです。

func parserDidStartDocument(_ parser: XMLParser) {
    results = [[:]]
}
0
Rob