/**
* https://github.com/tadija/AEXML
* Copyright © Marko Tadić 2014-2020
* Licensed under the MIT license
*/
import Foundation
#if canImport(FoundationXML)
import FoundationXML
#endif
import XCTest
@testable import XML
class XMLTests: XCTestCase {
// MARK: - Properties
var exampleDocument = Document()
var plantsDocument = Document()
// MARK: - Helpers
func URLForResource(fileName: String, withExtension ext: String) -> URL {
guard let url = Bundle.module.url(forResource: fileName, withExtension: ext) else {
fatalError("can't find resource named: '\(fileName)'")
}
return url
}
func xmlDocumentFromURL(url: URL) -> Document {
var xmlDocument = Document()
do {
let data = try Data.init(contentsOf: url)
xmlDocument = try Document(xml: data)
} catch {
print(error)
}
return xmlDocument
}
func readXMLFromFile(filename: String) -> Document {
let url = URLForResource(fileName: filename, withExtension: "xml")
return xmlDocumentFromURL(url: url)
}
// MARK: - Setup & Teardown
override func setUp() {
super.setUp()
// create some sample xml documents
exampleDocument = readXMLFromFile(filename: "example")
plantsDocument = readXMLFromFile(filename: "plant_catalog")
}
override func tearDown() {
// reset sample xml document
exampleDocument = Document()
plantsDocument = Document()
super.tearDown()
}
// MARK: - XML Document
func testXMLDocumentManualDataLoading() {
do {
let url = URLForResource(fileName: "example", withExtension: "xml")
let data = try Data.init(contentsOf: url)
let testDocument = Document()
try testDocument.load(data)
XCTAssertEqual(testDocument.root.name, "animals", "Should be able to find root element.")
} catch {
XCTFail("Should be able to load XML Document with given Data.")
}
}
func testXMLDocumentInitFromString() {
do {
let testDocument = try Document(xml: exampleDocument.xml)
XCTAssertEqual(testDocument.xml, exampleDocument.xml)
} catch {
XCTFail("Should be able to initialize XML Document from XML String.")
}
}
func testXMLOptions() {
do {
var options = Options()
options.documentHeader.version = 2.0
options.documentHeader.encoding = "utf-16"
options.documentHeader.standalone = "yes"
let testDocument = try Document(xml: "hello", options: options)
XCTAssertEqual(testDocument.xml, "\n\n\thello\n")
XCTAssertEqual(try testDocument.root.bar.first?.get(), "hello")
} catch {
XCTFail("Should be able to initialize XML Document with custom Options.")
}
}
func testXMLParser() {
do {
let testDocument = Document()
let url = URLForResource(fileName: "example", withExtension: "xml")
let data = try Data.init(contentsOf: url)
let parser = Parser(document: testDocument, data: data)
try parser.parse()
XCTAssertEqual(testDocument.root.name, "animals", "Should be able to find root element.")
} catch {
XCTFail("Should be able to parse XML Data into XML Document without throwing error.")
}
}
func testXMLParserTrimsWhitespace() {
let result = whitespaceResult(shouldTrimWhitespace: true)
XCTAssertEqual(result, "Hello,")
}
func testXMLParserWithoutTrimmingWhitespace() {
let result = whitespaceResult(shouldTrimWhitespace: false)
XCTAssertEqual(result, "Hello, ")
}
private func whitespaceResult(shouldTrimWhitespace: Bool) -> String? {
do {
var options = Options()
options.parserSettings.shouldTrimWhitespace = shouldTrimWhitespace
let testDocument = Document(options: options)
let url = URLForResource(fileName: "whitespace_examples", withExtension: "xml")
let data = try Data.init(contentsOf: url)
let parser = Parser(document: testDocument, data: data)
try parser.parse()
return testDocument.root.text.first?.value
} catch {
XCTFail("Should be able to parse XML without throwing an error")
}
return nil
}
func testXMLParserError() {
do {
let testDocument = Document()
let testData = Data()
let parser = Parser(document: testDocument, data: testData)
try parser.parse()
} catch {
XCTAssertEqual(error.localizedDescription, XMLError.parsingFailed.localizedDescription)
}
}
// MARK: - XML Read
func testRootElement() {
XCTAssertEqual(exampleDocument.root.name, "animals", "Should be able to find root element.")
let documentWithoutRootElement = Document()
let rootElement = documentWithoutRootElement.root
XCTAssertEqual(rootElement.error, XMLError.rootElementMissing, "Should have RootElementMissing error.")
}
func testParentElement() {
XCTAssertEqual(exampleDocument.root.cats.parent?.name, "animals", "Should be able to find parent element.")
}
func testChildrenElements() {
var count = 0
for _ in exampleDocument.root.cats.children {
count += 1
}
XCTAssertEqual(count, 4, "Should be able to iterate children elements")
}
func testName() {
let secondChildElementName = exampleDocument.root.children[1].name
XCTAssertEqual(secondChildElementName, "dogs", "Should be able to return element name.")
}
func testAttributes() {
let firstCatAttributes = exampleDocument.root.cats.cat.attributes
XCTAssertEqual(firstCatAttributes["breed"], "Siberian", "Should be able to return attribute value.")
}
func testValue() {
let firstPlant = plantsDocument.root.PLANT
XCTAssertEqual(firstPlant.COMMON, "Bloodroot", "Should be able to return element value as optional string.")
XCTAssertNil(firstPlant.ELEMENTWITHOUTVALUE.value, "Should be able to have nil value.")
XCTAssertNil(firstPlant.EMPTYELEMENT.value, "Should be able to have nil value.")
}
func testStringValue() {
let firstPlant = plantsDocument.root.PLANT
XCTAssertEqual(firstPlant.COMMON.value, "Bloodroot", "Should be able to return element value as string.")
XCTAssertNil(firstPlant.ELEMENTWITHOUTVALUE.value, "Should be able to return nil if element has no value.")
XCTAssertNil(firstPlant.EMPTYELEMENT.value, "Should be able to return nil if element has no value.")
}
func testBoolValue() {
XCTAssertEqual(plantsDocument.root.PLANT.TRUESTRING, true, "Should be able to cast element value as Bool.")
XCTAssertEqual(plantsDocument.root.PLANT.FALSESTRING, false, "Should be able to cast element value as Bool.")
// XCTAssertEqual(plantsDocument.root.PLANT.TRUESTRING2, true, "Should be able to cast element value as Bool.")
// XCTAssertEqual(plantsDocument.root.PLANT.FALSESTRING2, false, "Should be able to cast element value as Bool.")
// XCTAssertEqual(plantsDocument.root.PLANT.TRUEINT, true, "Should be able to cast element value as Bool.")
// XCTAssertEqual(plantsDocument.root.PLANT.FALSEINT, false, "Should be able to cast element value as Bool.")
XCTAssertNil(plantsDocument.root.ELEMENTWITHOUTVALUE as Bool?, "Should be able to return nil if value can't be represented as Bool.")
}
func testIntValue() throws {
XCTAssertEqual(plantsDocument.root.PLANT.ZONE, 4, "Should be able to cast element value as Integer.")
XCTAssertNil(plantsDocument.root.PLANT.ELEMENTWITHOUTVALUE as Int?, "Should be able to return nil if value can't be represented as Integer.")
}
func testDoubleValue() throws {
XCTAssertEqual(plantsDocument.root.PLANT.PRICE, 2.44, "Should be able to cast element value as Double.")
XCTAssertNil(plantsDocument.root.PLANT.ELEMENTWITHOUTVALUE as Double?, "Should be able to return nil if value can't be represented as Double.")
}
func testNotExistingElement() throws {
// non-optional
XCTAssertNotNil(exampleDocument.root.ducks.duck.error, "Should contain error inside element which does not exist.")
XCTAssertEqual(exampleDocument.root.ducks.duck.error, XMLError.elementNotFound, "Should have ElementNotFound error.")
XCTAssertNil(exampleDocument.root.ducks.duck.value, "Should have empty value.")
XCTAssertNil(exampleDocument.root.ducks.duck.first, "Should not be able to find ducks here.")
}
func testAllElements() {
var count = 0
if let cats = exampleDocument.root.cats.cat.all {
for cat in cats {
XCTAssertNotNil(cat.parent, "Each child element should have its parent element.")
count += 1
}
}
XCTAssertEqual(count, 4, "Should be able to iterate all elements")
}
func testFirstElement() throws {
let catElement = exampleDocument.root.cats.cat
let firstCatExpectedValue = "Tinna"
// non-optional
XCTAssertEqual(try catElement.get(), firstCatExpectedValue, "Should be able to find the first element as non-optional.")
// optional
if let cat = catElement.first {
XCTAssertEqual(try cat.get(), firstCatExpectedValue, "Should be able to find the first element as optional.")
} else {
XCTFail("Should be able to find the first element.")
}
}
func testLastElement() throws {
if let dog = exampleDocument.root.dogs.dog.last {
XCTAssertEqual(try dog.get(), "Kika", "Should be able to find the last element.")
} else {
XCTFail("Should be able to find the last element.")
}
}
func testCountElements() {
let dogsCount = exampleDocument.root.dogs.dog.count
XCTAssertEqual(dogsCount, 4, "Should be able to count elements.")
}
func testAllWithValue() {
let cats = exampleDocument.root.cats
cats.addChild(name: "cat", value: "Tinna")
var count = 0
if let tinnas = cats["cat"].all(withValue: "Tinna") {
for _ in tinnas {
count += 1
}
}
XCTAssertEqual(count, 2, "Should be able to return elements with given value.")
}
func testAllWithAttributes() {
var count = 0
if let bulls = exampleDocument.root.dogs.dog.all(withAttributes: ["color" : "white"]) {
for _ in bulls {
count += 1
}
}
XCTAssertEqual(count, 2, "Should be able to return elements with given attributes.")
}
func testAllContainingAttributes() {
var count = 0
if let bulls = exampleDocument.root.dogs.dog.all(containingAttributeKeys: ["gender"]) {
for _ in bulls {
count += 1
}
}
XCTAssertEqual(count, 2, "Should be able to return elements with given attribute keys.")
}
func testAllDescendantsWherePredicate() {
let children = exampleDocument.allDescendants { $0.attributes["color"] == "yellow" }
XCTAssertEqual(children.count, 2, "Should be able to return elements matching predicate.")
}
func testFirstDescendantWherePredicate() {
let descendant = plantsDocument.root.firstDescendant { $0.hasDescendant { $0.name == "LIGHT" && $0.value == "Sunny" } }
let plantName = descendant?["COMMON"].value
XCTAssertEqual(plantName, "Black-Eyed Susan", "Should be able to find first child satisfying predicate.")
}
func testHasDescendantWherePredicate() throws {
let hasDescendant = plantsDocument.hasDescendant { $0.name == "AVAILABILITY" && $0.value == "030699" }
XCTAssert(hasDescendant, "Should be able to determine that document has a child satisfying predicate.")
}
func testSpecialCharacterTrimRead() {
let expected = "\n\n\tThis & that\n"
let readerDocument = try! Document(xml: expected)
let readerXml = readerDocument.xml
XCTAssertEqual(readerXml, expected, "Should be able to print XML formatted string.")
}
// MARK: - XML Write
func testAddChild() throws {
let ducks = exampleDocument.root.addChild(name: "ducks")
ducks.addChild(name: "duck", value: "Donald")
ducks.addChild(name: "duck", value: "Daisy")
ducks.addChild(name: "duck", value: "Scrooge")
let animalsCount = exampleDocument.root.children.count
XCTAssertEqual(animalsCount, 3, "Should be able to add child elements to an element.")
XCTAssertEqual(try exampleDocument.root["ducks"]["duck"].last?.get(), "Scrooge", "Should be able to iterate ducks now.")
}
func testAddChildWithAttributes() throws {
let cats = exampleDocument.root.cats
let dogs = exampleDocument.root.dogs
cats.addChild(name: "cat", value: "Garfield", attributes: ["breed" : "tabby", "color" : "orange"])
dogs.addChild(name: "dog", value: "Snoopy", attributes: ["breed" : "beagle", "color" : "white"])
let catsCount = cats["cat"].count
let dogsCount = dogs["dog"].count
let lastCat = cats["cat"].last!
let penultDog = dogs.children[3]
XCTAssertEqual(catsCount, 5, "Should be able to add child element with attributes to an element.")
XCTAssertEqual(dogsCount, 5, "Should be able to add child element with attributes to an element.")
XCTAssertEqual(lastCat.attributes["color"], "orange", "Should be able to get attribute value from added element.")
XCTAssertEqual(try penultDog.get(), "Kika", "Should be able to add child with attributes without overwrites existing elements. (Github Issue #28)")
}
func testAddChildren() {
let animals: [Element] = [
Element(name: "dinosaurs"),
Element(name: "birds"),
Element(name: "bugs"),
]
exampleDocument.root.addChildren(animals)
let animalsCount = exampleDocument.root.children.count
XCTAssertEqual(animalsCount, 5, "Should be able to add children elements to an element.")
}
func testAddAttributes() {
let firstCat = exampleDocument.root.cats.cat
firstCat.attributes["funny"] = "true"
firstCat.attributes["speed"] = "fast"
firstCat.attributes["years"] = "7"
XCTAssertEqual(firstCat.attributes.count, 5, "Should be able to add attributes to an element.")
XCTAssertEqual(Int(firstCat.attributes["years"]!), 7, "Should be able to get any attribute value now.")
}
func testRemoveChild() throws {
let cats = exampleDocument.root.cats
let lastCat = cats["cat"].last!
let duplicateCat = cats.addChild(name: "cat", value: "Tinna", attributes: ["breed" : "Siberian", "color" : "lightgray"])
lastCat.removeFromParent()
duplicateCat.removeFromParent()
let catsCount = cats["cat"].count
let firstCat = cats["cat"]
XCTAssertEqual(catsCount, 3, "Should be able to remove element from parent.")
XCTAssertEqual(try firstCat.get(), "Tinna", "Should be able to remove the exact element from parent.")
}
func testXMLEscapedString() {
let string = "&<>'\""
let escapedString = string.xmlEscaped
XCTAssertEqual(escapedString, "&<>'"")
}
func testXMLString() {
let testDocument = Document()
let children = testDocument.addChild(name: "children")
children.addChild(name: "child", value: "value", attributes: ["attribute" : "attributeValue<&>"])
children.addChild(name: "child")
children.addChild(name: "child", value: "&<>'\"")
XCTAssertEqual(testDocument.xml, "\n\n\tvalue\n\t\n\t&<>'"\n", "Should be able to print XML formatted string.")
XCTAssertEqual(testDocument.xmlCompact, "value&<>'"", "Should be able to print compact XML string.")
XCTAssertEqual(testDocument.xmlSpaces, "\n\n value\n \n &<>'"\n", "Should be able to print XML formatted string.")
}
// MARK: - XML Parse Performance
func testReadXMLPerformance() {
self.measure() {
_ = self.readXMLFromFile(filename: "plant_catalog")
}
}
func testWriteXMLPerformance() {
self.measure() {
_ = self.plantsDocument.xml
}
}
}