diff --git a/.swift-version b/.swift-version index 819e07a..3659ea2 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -5.0 +5.8 diff --git a/Package.swift b/Package.swift index ad049f8..f9effd3 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.0 +// swift-tools-version:5.8 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -15,7 +15,8 @@ let package = Package( ), .testTarget(name: "FuziTests", dependencies: ["Fuzi"], - path: "Tests" + path: "Tests", + resources: [.process("Resources")] ) ] ) diff --git a/Sources/Document.swift b/Sources/Document.swift index 299150f..fa18d3e 100644 --- a/Sources/Document.swift +++ b/Sources/Document.swift @@ -74,11 +74,15 @@ open class XMLDocument { - returns: An `XMLDocument` with the contents of the specified XML string. */ - public convenience init(string: String, encoding: String.Encoding = String.Encoding.utf8) throws { + public convenience init( + string: String, + encoding: String.Encoding = String.Encoding.utf8, + parseOptions: XmlParseOptions = .defaultOptions + ) throws { guard let cChars = string.cString(using: encoding) else { throw XMLError.invalidData } - try self.init(cChars: cChars) + try self.init(cChars: cChars, parseOptions: parseOptions) } /** @@ -90,9 +94,9 @@ open class XMLDocument { - returns: An `XMLDocument` with the contents of the specified XML string. */ - public convenience init(data: Data) throws { + public convenience init(data: Data, parseOptions: XmlParseOptions = .defaultOptions) throws { let buffer = data.withUnsafeBytes { $0.bindMemory(to: Int8.self) } - try self.init(buffer: buffer) + try self.init(buffer: buffer, parseOptions: parseOptions) } /** @@ -104,11 +108,11 @@ open class XMLDocument { - returns: An `XMLDocument` with the contents of the specified XML string. */ - public convenience init(cChars: [CChar]) throws { + public convenience init(cChars: [CChar], parseOptions: XmlParseOptions = .defaultOptions) throws { let buffer = cChars.withUnsafeBufferPointer { buffer in UnsafeBufferPointer(rebasing: buffer[0..) throws { - let options = Int32(XML_PARSE_NOWARNING.rawValue | XML_PARSE_NOERROR.rawValue | XML_PARSE_RECOVER.rawValue) - try self.init(buffer: buffer, options: options) - } + public convenience init( + buffer: UnsafeBufferPointer, + parseOptions: XmlParseOptions = .defaultOptions + ) throws { + try self.init(buffer: buffer, options: Int32(parseOptions.rawValue)) + } fileprivate convenience init(buffer: UnsafeBufferPointer, options: Int32) throws { guard let document = type(of: self).parse(buffer: buffer, options: options) else { diff --git a/Sources/Error.swift b/Sources/Error.swift index 0c91cda..ccc5d55 100644 --- a/Sources/Error.swift +++ b/Sources/Error.swift @@ -25,7 +25,7 @@ import libxml2 /** * XMLError enumeration. */ -public enum XMLError: Error { +public enum XMLError: Error, Equatable { /// No error case noError /// Contains a libxml2 error with error code and message diff --git a/Sources/XmlOptionsSet.swift b/Sources/XmlOptionsSet.swift new file mode 100644 index 0000000..ea7c1e5 --- /dev/null +++ b/Sources/XmlOptionsSet.swift @@ -0,0 +1,36 @@ +// XmlParseOptions.swift +// Copyright (c) 2024 BPerlakiH +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation +import libxml2 + + +public struct XmlParseOptions: OptionSet { + public let rawValue: UInt32 + public init(rawValue: UInt32) { + self.rawValue = rawValue + } + public static let recover = XmlParseOptions(rawValue: 1 << 0) + public static let noError = XmlParseOptions(rawValue: 1 << 5) + public static let noWarning = XmlParseOptions(rawValue: 1 << 6) + + public static let defaultOptions: XmlParseOptions = [.recover, .noError, .noWarning] +} diff --git a/Tests/AtomTests.swift b/Tests/AtomTests.swift index 3ead8db..f36696c 100644 --- a/Tests/AtomTests.swift +++ b/Tests/AtomTests.swift @@ -26,7 +26,7 @@ class AtomTests: XCTestCase { var document: Fuzi.XMLDocument! override func setUp() { super.setUp() - let filePath = Bundle(for: AtomTests.self).url(forResource: "atom", withExtension: "xml")! + let filePath = Bundle.testResources.url(forResource: "atom", withExtension: "xml")! do { document = try XMLDocument(data: Data(contentsOf: filePath)) } catch { diff --git a/Tests/Bundle+Ext.swift b/Tests/Bundle+Ext.swift new file mode 100644 index 0000000..c277117 --- /dev/null +++ b/Tests/Bundle+Ext.swift @@ -0,0 +1,37 @@ +// XmlOptionsTests.swift +// Copyright (c) 2024 BPerlakiH +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +import Foundation + +extension Bundle { + static let testResources: Bundle = { + let bundle = Bundle(for: BundleFinder.self) + if let moduleName = bundle.bundleIdentifier, + let baseURL = bundle.resourceURL, + let testBundlePath = ProcessInfo.processInfo.environment["XCTestBundlePath"] { + if let resourceBundle = Bundle(path: baseURL.appending(path: "/\(moduleName)_\(moduleName).bundle").absoluteString) { + return resourceBundle + } + } + return Bundle.module + }() + + private final class BundleFinder {} +} diff --git a/Tests/DefaultNamespaceXPathTests.swift b/Tests/DefaultNamespaceXPathTests.swift index 704774d..618d42b 100644 --- a/Tests/DefaultNamespaceXPathTests.swift +++ b/Tests/DefaultNamespaceXPathTests.swift @@ -26,7 +26,7 @@ class DefaultNamespaceXPathTests: XCTestCase { var document: Fuzi.XMLDocument! override func setUp() { super.setUp() - let filePath = Bundle(for: DefaultNamespaceXPathTests.self).url(forResource: "ocf", withExtension: "xml")! + let filePath = Bundle.testResources.url(forResource: "ocf", withExtension: "xml")! do { document = try XMLDocument(data: Data(contentsOf: filePath)) } catch { diff --git a/Tests/HTMLTests.swift b/Tests/HTMLTests.swift index 05c8afa..b04ef8b 100644 --- a/Tests/HTMLTests.swift +++ b/Tests/HTMLTests.swift @@ -26,7 +26,7 @@ class HTMLTests: XCTestCase { var document: HTMLDocument! override func setUp() { super.setUp() - let filePath = Bundle(for: HTMLTests.self).url(forResource: "web", withExtension: "html")! + let filePath = Bundle.testResources.url(forResource: "web", withExtension: "html")! do { document = try HTMLDocument(data: Data(contentsOf: filePath)) } catch { diff --git a/Tests/OptionsTests.swift b/Tests/OptionsTests.swift new file mode 100644 index 0000000..5ede62f --- /dev/null +++ b/Tests/OptionsTests.swift @@ -0,0 +1,40 @@ +// XmlOptionsTests.swift +// Copyright (c) 2024 BPerlakiH +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import XCTest +import Fuzi +import libxml2 + +final class XmlOptionsTests: XCTestCase { + + func testXmlParseOptionValues() { + XCTAssertEqual(XML_PARSE_NOWARNING.rawValue, XmlParseOptions.noWarning.rawValue) + XCTAssertEqual(XML_PARSE_NOERROR.rawValue, XmlParseOptions.noError.rawValue) + XCTAssertEqual(XML_PARSE_RECOVER.rawValue, XmlParseOptions.recover.rawValue) + } + + func testXmlParseOptionSet() { + let defaultOptions = UInt32(XML_PARSE_NOWARNING.rawValue | XML_PARSE_NOERROR.rawValue | XML_PARSE_RECOVER.rawValue) + let options: XmlParseOptions = [.noWarning, .noError, .recover] + XCTAssertEqual(options.rawValue, defaultOptions) + XCTAssertEqual(XmlParseOptions.defaultOptions.rawValue, defaultOptions) + } +} diff --git a/Tests/VMAPTests.swift b/Tests/VMAPTests.swift index 6b666ca..47f6d5e 100644 --- a/Tests/VMAPTests.swift +++ b/Tests/VMAPTests.swift @@ -26,7 +26,7 @@ class VMAPTests: XCTestCase { var document: Fuzi.XMLDocument! override func setUp() { super.setUp() - let filePath = Bundle(for: VMAPTests.self).url(forResource: "vmap", withExtension: "xml")! + let filePath = Bundle.testResources.url(forResource: "vmap", withExtension: "xml")! do { document = try XMLDocument(data: Data(contentsOf: filePath)) } catch { diff --git a/Tests/XMLTests.swift b/Tests/XMLTests.swift index e921fd7..5f4f214 100644 --- a/Tests/XMLTests.swift +++ b/Tests/XMLTests.swift @@ -26,7 +26,7 @@ class XMLTests: XCTestCase { var document: Fuzi.XMLDocument! override func setUp() { super.setUp() - let filePath = Bundle(for: XMLTests.self).url(forResource: "xml", withExtension: "xml")! + let filePath = Bundle.testResources.url(forResource: "xml", withExtension: "xml")! do { document = try XMLDocument(data: Data(contentsOf: filePath)) } catch { @@ -81,8 +81,8 @@ class XMLTests: XCTestCase { do { _ = try document.tryXPath("//*[unknown()]") XCTAssertFalse(true, "error should have been thrown") - } catch XMLError.libXMLError(code: 1223, message: "Stack usage error") { - + } catch XMLError.libXMLError(code: 1209, message: "Unregistered function") { + } catch { XCTAssertFalse(true, "error type should be libXMLError \(error)") } @@ -91,7 +91,7 @@ class XMLTests: XCTestCase { func testLineNumber() { let headerElement = document.root!.firstChild(tag: "header") XCTAssertNotNil(headerElement, "header element should not be nil") - XCTAssertEqual(headerElement?.lineNumber, 123, "header line number should be correct") + XCTAssertEqual(headerElement?.lineNumber, 120, "header line number should be correct") } func testThrowsError() { diff --git a/Tests/XPathFunctionResultTests.swift b/Tests/XPathFunctionResultTests.swift index 9d961fc..bf4c5ab 100644 --- a/Tests/XPathFunctionResultTests.swift +++ b/Tests/XPathFunctionResultTests.swift @@ -26,7 +26,7 @@ class XPathFunctionResultTests: XCTestCase { var document: Fuzi.XMLDocument! override func setUp() { super.setUp() - let filePath = Bundle(for: AtomTests.self).url(forResource: "atom", withExtension: "xml")! + let filePath = Bundle.testResources.url(forResource: "atom", withExtension: "xml")! do { document = try XMLDocument(data: Data(contentsOf: filePath)) } catch {