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