Home > other >  How to import local xml file in my Swift program using SPM?
How to import local xml file in my Swift program using SPM?

Time:08-25

I am writing a xml parser library in Swift for a specific kind of data. In my project there are two ways to import xml data, either by using a web URL or by giving a local xml file. The first one works great but I have trouble with the second one. Here is a sample of my code:

import Foundation

class PnmlParser: NSObject, XMLParserDelegate {

  // DOES NOT WORK
  func loadPN(filePath: String) {
    print(Bundle.main)
    let xmlResponseData = Bundle.main.getFileData(filePath)
    let parser = XMLParser(data: xmlResponseData)
    parser.delegate = self
    parser.parse()
  }
  
  // WORK
  func loadPN(url: URL) {
    let parser = XMLParser(contentsOf: url)!
    parser.delegate = self
    parser.parse()
  } 
  
  func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
    // Some code
  }
  func parser(_ parser: XMLParser, foundCharacters string: String) {
    // Some code
  }
  func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
    // Some code
  }
  func parserDidEndDocument(_ parser: XMLParser) {
    // Some code
  } 
}

extension Bundle {
  func getFileData(_ file: String) -> Data {
    guard let url = self.url(forResource: file, withExtension: nil) else {
      fatalError("Failed to locate \(file) in bundle")
    }
    
    guard let data = try? Data(contentsOf: url) else {
      fatalError("Failed to load \(file) in bundle")
    }   
    return data
  }
}

There are two functions loadPN, but it is only the loadPN(filePath: String) that does not work, because it does not find my xml file.

I use the Swift Package Manager and not XCode to insert resources. I have already seen how to do it with XCode using the build phase and copy bundle resources, but it does not apply to my case.

I am working on Swift 5.6, and I have seen that I should adapt my Package.swift to insert the wanted resources. Here is my Package.swift:

// swift-tools-version: 5.6
import PackageDescription

let package = Package(
    name: "swift-xml-import-test",
    ...
    targets: [
        .target(
            name: "swift-xml-import-test",
            dependencies: [],
            resources: [.process("Resources/data.xml")]
        ),
        .testTarget(
            name: "swift-xml-import-testTests",
            dependencies: ["swift-xml-import-test"],
            resources: [.process("ResourcesTests/dataTests.xml")]
        ),
    ]
)

A sample GitHub project shows the data structure and the problem that I encountered: Sample of my GitHub project I created two folders, respectively Resources (Sources/swift-xml-import-test/Resources) and ResourcesTests (Tests/swift-xml-import-testTests/ResourcesTests), where there is an xml data file in each.

I created a test to try it out:

final class swift_xml_import_testTests: XCTestCase {
  func testWork() {
    let p = PnmlParser()
    if let url = URL(string: "https://www.pnml.org/version-2009/examples/philo.pnml") {
      p.loadPN(url: url)
    }
  }
  func testDoesNotWork() {
    let p = PnmlParser()
    p.loadPN(filePath: "dataTests.xml")
  }
}

When I launch my test testDoesNotWork(), I get the following error: Thread 1: Fatal error: Failed to locate dataTests.xml in bundle from my earlier extension. I do not understand what I am missing if someone can help me. If you want to test yourself, you have the GitHub link of the sample project above.

CodePudding user response:

From Swift 5.3, it is possible to add resources using Package.swift, which has been done in the code.

To call the added resources, it is required to use:

Bundle.module.url(forResource: myFile, withExtension: myExtension)

However, this is not possible in the extension made previously, where it was self.url. Thus, I deleted the extension and changed the function loadPN(filePath: String) into:

  func loadPN(filePath: String) {
    if let url = Bundle.module.url(forResource: filePath, withExtension: nil) {
      let parser = XMLParser(contentsOf: url)!
      parser.delegate = self
      parser.parse()
    }
  }

testDoesNotWork test finally passed.

  • Related