XMLTests.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. /**
  2. * https://github.com/tadija/AEXML
  3. * Copyright © Marko Tadić 2014-2020
  4. * Licensed under the MIT license
  5. */
  6. import Foundation
  7. #if canImport(FoundationXML)
  8. import FoundationXML
  9. #endif
  10. import XCTest
  11. @testable import XML
  12. class XMLTests: XCTestCase {
  13. // MARK: - Properties
  14. var exampleDocument = Document()
  15. var plantsDocument = Document()
  16. // MARK: - Helpers
  17. func URLForResource(fileName: String, withExtension ext: String) -> URL {
  18. guard let url = Bundle.module.url(forResource: fileName, withExtension: ext) else {
  19. fatalError("can't find resource named: '\(fileName)'")
  20. }
  21. return url
  22. }
  23. func xmlDocumentFromURL(url: URL) -> Document {
  24. var xmlDocument = Document()
  25. do {
  26. let data = try Data.init(contentsOf: url)
  27. xmlDocument = try Document(xml: data)
  28. } catch {
  29. print(error)
  30. }
  31. return xmlDocument
  32. }
  33. func readXMLFromFile(filename: String) -> Document {
  34. let url = URLForResource(fileName: filename, withExtension: "xml")
  35. return xmlDocumentFromURL(url: url)
  36. }
  37. // MARK: - Setup & Teardown
  38. override func setUp() {
  39. super.setUp()
  40. // create some sample xml documents
  41. exampleDocument = readXMLFromFile(filename: "example")
  42. plantsDocument = readXMLFromFile(filename: "plant_catalog")
  43. }
  44. override func tearDown() {
  45. // reset sample xml document
  46. exampleDocument = Document()
  47. plantsDocument = Document()
  48. super.tearDown()
  49. }
  50. // MARK: - XML Document
  51. func testXMLDocumentManualDataLoading() {
  52. do {
  53. let url = URLForResource(fileName: "example", withExtension: "xml")
  54. let data = try Data.init(contentsOf: url)
  55. let testDocument = Document()
  56. try testDocument.load(data)
  57. XCTAssertEqual(testDocument.root.name, "animals", "Should be able to find root element.")
  58. } catch {
  59. XCTFail("Should be able to load XML Document with given Data.")
  60. }
  61. }
  62. func testXMLDocumentInitFromString() {
  63. do {
  64. let testDocument = try Document(xml: exampleDocument.xml)
  65. XCTAssertEqual(testDocument.xml, exampleDocument.xml)
  66. } catch {
  67. XCTFail("Should be able to initialize XML Document from XML String.")
  68. }
  69. }
  70. func testXMLOptions() {
  71. do {
  72. var options = Options()
  73. options.documentHeader.version = 2.0
  74. options.documentHeader.encoding = "utf-16"
  75. options.documentHeader.standalone = "yes"
  76. let testDocument = try Document(xml: "<foo><bar>hello</bar></foo>", options: options)
  77. XCTAssertEqual(testDocument.xml, "<?xml version=\"2.0\" encoding=\"utf-16\" standalone=\"yes\"?>\n<foo>\n\t<bar>hello</bar>\n</foo>")
  78. XCTAssertEqual(try testDocument.root.bar.first?.get(), "hello")
  79. } catch {
  80. XCTFail("Should be able to initialize XML Document with custom Options.")
  81. }
  82. }
  83. func testXMLParser() {
  84. do {
  85. let testDocument = Document()
  86. let url = URLForResource(fileName: "example", withExtension: "xml")
  87. let data = try Data.init(contentsOf: url)
  88. let parser = Parser(document: testDocument, data: data)
  89. try parser.parse()
  90. XCTAssertEqual(testDocument.root.name, "animals", "Should be able to find root element.")
  91. } catch {
  92. XCTFail("Should be able to parse XML Data into XML Document without throwing error.")
  93. }
  94. }
  95. func testXMLParserTrimsWhitespace() {
  96. let result = whitespaceResult(shouldTrimWhitespace: true)
  97. XCTAssertEqual(result, "Hello,")
  98. }
  99. func testXMLParserWithoutTrimmingWhitespace() {
  100. let result = whitespaceResult(shouldTrimWhitespace: false)
  101. XCTAssertEqual(result, "Hello, ")
  102. }
  103. private func whitespaceResult(shouldTrimWhitespace: Bool) -> String? {
  104. do {
  105. var options = Options()
  106. options.parserSettings.shouldTrimWhitespace = shouldTrimWhitespace
  107. let testDocument = Document(options: options)
  108. let url = URLForResource(fileName: "whitespace_examples", withExtension: "xml")
  109. let data = try Data.init(contentsOf: url)
  110. let parser = Parser(document: testDocument, data: data)
  111. try parser.parse()
  112. return testDocument.root.text.first?.value
  113. } catch {
  114. XCTFail("Should be able to parse XML without throwing an error")
  115. }
  116. return nil
  117. }
  118. func testXMLParserError() {
  119. do {
  120. let testDocument = Document()
  121. let testData = Data()
  122. let parser = Parser(document: testDocument, data: testData)
  123. try parser.parse()
  124. } catch {
  125. XCTAssertEqual(error.localizedDescription, XMLError.parsingFailed.localizedDescription)
  126. }
  127. }
  128. // MARK: - XML Read
  129. func testRootElement() {
  130. XCTAssertEqual(exampleDocument.root.name, "animals", "Should be able to find root element.")
  131. let documentWithoutRootElement = Document()
  132. let rootElement = documentWithoutRootElement.root
  133. XCTAssertEqual(rootElement.error, XMLError.rootElementMissing, "Should have RootElementMissing error.")
  134. }
  135. func testParentElement() {
  136. XCTAssertEqual(exampleDocument.root.cats.parent?.name, "animals", "Should be able to find parent element.")
  137. }
  138. func testChildrenElements() {
  139. var count = 0
  140. for _ in exampleDocument.root.cats.children {
  141. count += 1
  142. }
  143. XCTAssertEqual(count, 4, "Should be able to iterate children elements")
  144. }
  145. func testName() {
  146. let secondChildElementName = exampleDocument.root.children[1].name
  147. XCTAssertEqual(secondChildElementName, "dogs", "Should be able to return element name.")
  148. }
  149. func testAttributes() {
  150. let firstCatAttributes = exampleDocument.root.cats.cat.attributes
  151. XCTAssertEqual(firstCatAttributes["breed"], "Siberian", "Should be able to return attribute value.")
  152. }
  153. func testValue() {
  154. let firstPlant = plantsDocument.root.PLANT
  155. XCTAssertEqual(firstPlant.COMMON, "Bloodroot", "Should be able to return element value as optional string.")
  156. XCTAssertNil(firstPlant.ELEMENTWITHOUTVALUE.value, "Should be able to have nil value.")
  157. XCTAssertNil(firstPlant.EMPTYELEMENT.value, "Should be able to have nil value.")
  158. }
  159. func testStringValue() {
  160. let firstPlant = plantsDocument.root.PLANT
  161. XCTAssertEqual(firstPlant.COMMON.value, "Bloodroot", "Should be able to return element value as string.")
  162. XCTAssertNil(firstPlant.ELEMENTWITHOUTVALUE.value, "Should be able to return nil if element has no value.")
  163. XCTAssertNil(firstPlant.EMPTYELEMENT.value, "Should be able to return nil if element has no value.")
  164. }
  165. func testBoolValue() {
  166. XCTAssertEqual(plantsDocument.root.PLANT.TRUESTRING, true, "Should be able to cast element value as Bool.")
  167. XCTAssertEqual(plantsDocument.root.PLANT.FALSESTRING, false, "Should be able to cast element value as Bool.")
  168. // XCTAssertEqual(plantsDocument.root.PLANT.TRUESTRING2, true, "Should be able to cast element value as Bool.")
  169. // XCTAssertEqual(plantsDocument.root.PLANT.FALSESTRING2, false, "Should be able to cast element value as Bool.")
  170. // XCTAssertEqual(plantsDocument.root.PLANT.TRUEINT, true, "Should be able to cast element value as Bool.")
  171. // XCTAssertEqual(plantsDocument.root.PLANT.FALSEINT, false, "Should be able to cast element value as Bool.")
  172. XCTAssertNil(plantsDocument.root.ELEMENTWITHOUTVALUE as Bool?, "Should be able to return nil if value can't be represented as Bool.")
  173. }
  174. func testIntValue() throws {
  175. XCTAssertEqual(plantsDocument.root.PLANT.ZONE, 4, "Should be able to cast element value as Integer.")
  176. XCTAssertNil(plantsDocument.root.PLANT.ELEMENTWITHOUTVALUE as Int?, "Should be able to return nil if value can't be represented as Integer.")
  177. }
  178. func testDoubleValue() throws {
  179. XCTAssertEqual(plantsDocument.root.PLANT.PRICE, 2.44, "Should be able to cast element value as Double.")
  180. XCTAssertNil(plantsDocument.root.PLANT.ELEMENTWITHOUTVALUE as Double?, "Should be able to return nil if value can't be represented as Double.")
  181. }
  182. func testNotExistingElement() throws {
  183. // non-optional
  184. XCTAssertNotNil(exampleDocument.root.ducks.duck.error, "Should contain error inside element which does not exist.")
  185. XCTAssertEqual(exampleDocument.root.ducks.duck.error, XMLError.elementNotFound, "Should have ElementNotFound error.")
  186. XCTAssertNil(exampleDocument.root.ducks.duck.value, "Should have empty value.")
  187. XCTAssertNil(exampleDocument.root.ducks.duck.first, "Should not be able to find ducks here.")
  188. }
  189. func testAllElements() {
  190. var count = 0
  191. if let cats = exampleDocument.root.cats.cat.all {
  192. for cat in cats {
  193. XCTAssertNotNil(cat.parent, "Each child element should have its parent element.")
  194. count += 1
  195. }
  196. }
  197. XCTAssertEqual(count, 4, "Should be able to iterate all elements")
  198. }
  199. func testFirstElement() throws {
  200. let catElement = exampleDocument.root.cats.cat
  201. let firstCatExpectedValue = "Tinna"
  202. // non-optional
  203. XCTAssertEqual(try catElement.get(), firstCatExpectedValue, "Should be able to find the first element as non-optional.")
  204. // optional
  205. if let cat = catElement.first {
  206. XCTAssertEqual(try cat.get(), firstCatExpectedValue, "Should be able to find the first element as optional.")
  207. } else {
  208. XCTFail("Should be able to find the first element.")
  209. }
  210. }
  211. func testLastElement() throws {
  212. if let dog = exampleDocument.root.dogs.dog.last {
  213. XCTAssertEqual(try dog.get(), "Kika", "Should be able to find the last element.")
  214. } else {
  215. XCTFail("Should be able to find the last element.")
  216. }
  217. }
  218. func testCountElements() {
  219. let dogsCount = exampleDocument.root.dogs.dog.count
  220. XCTAssertEqual(dogsCount, 4, "Should be able to count elements.")
  221. }
  222. func testAllWithValue() {
  223. let cats = exampleDocument.root.cats
  224. cats.addChild(name: "cat", value: "Tinna")
  225. var count = 0
  226. if let tinnas = cats["cat"].all(withValue: "Tinna") {
  227. for _ in tinnas {
  228. count += 1
  229. }
  230. }
  231. XCTAssertEqual(count, 2, "Should be able to return elements with given value.")
  232. }
  233. func testAllWithAttributes() {
  234. var count = 0
  235. if let bulls = exampleDocument.root.dogs.dog.all(withAttributes: ["color" : "white"]) {
  236. for _ in bulls {
  237. count += 1
  238. }
  239. }
  240. XCTAssertEqual(count, 2, "Should be able to return elements with given attributes.")
  241. }
  242. func testAllContainingAttributes() {
  243. var count = 0
  244. if let bulls = exampleDocument.root.dogs.dog.all(containingAttributeKeys: ["gender"]) {
  245. for _ in bulls {
  246. count += 1
  247. }
  248. }
  249. XCTAssertEqual(count, 2, "Should be able to return elements with given attribute keys.")
  250. }
  251. func testAllDescendantsWherePredicate() {
  252. let children = exampleDocument.allDescendants { $0.attributes["color"] == "yellow" }
  253. XCTAssertEqual(children.count, 2, "Should be able to return elements matching predicate.")
  254. }
  255. func testFirstDescendantWherePredicate() {
  256. let descendant = plantsDocument.root.firstDescendant { $0.hasDescendant { $0.name == "LIGHT" && $0.value == "Sunny" } }
  257. let plantName = descendant?["COMMON"].value
  258. XCTAssertEqual(plantName, "Black-Eyed Susan", "Should be able to find first child satisfying predicate.")
  259. }
  260. func testHasDescendantWherePredicate() throws {
  261. let hasDescendant = plantsDocument.hasDescendant { $0.name == "AVAILABILITY" && $0.value == "030699" }
  262. XCTAssert(hasDescendant, "Should be able to determine that document has a child satisfying predicate.")
  263. }
  264. func testSpecialCharacterTrimRead() {
  265. let expected = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n<elements>\n\t<string name=\"this_and_that\">This &amp; that</string>\n</elements>"
  266. let readerDocument = try! Document(xml: expected)
  267. let readerXml = readerDocument.xml
  268. XCTAssertEqual(readerXml, expected, "Should be able to print XML formatted string.")
  269. }
  270. // MARK: - XML Write
  271. func testAddChild() throws {
  272. let ducks = exampleDocument.root.addChild(name: "ducks")
  273. ducks.addChild(name: "duck", value: "Donald")
  274. ducks.addChild(name: "duck", value: "Daisy")
  275. ducks.addChild(name: "duck", value: "Scrooge")
  276. let animalsCount = exampleDocument.root.children.count
  277. XCTAssertEqual(animalsCount, 3, "Should be able to add child elements to an element.")
  278. XCTAssertEqual(try exampleDocument.root["ducks"]["duck"].last?.get(), "Scrooge", "Should be able to iterate ducks now.")
  279. }
  280. func testAddChildWithAttributes() throws {
  281. let cats = exampleDocument.root.cats
  282. let dogs = exampleDocument.root.dogs
  283. cats.addChild(name: "cat", value: "Garfield", attributes: ["breed" : "tabby", "color" : "orange"])
  284. dogs.addChild(name: "dog", value: "Snoopy", attributes: ["breed" : "beagle", "color" : "white"])
  285. let catsCount = cats["cat"].count
  286. let dogsCount = dogs["dog"].count
  287. let lastCat = cats["cat"].last!
  288. let penultDog = dogs.children[3]
  289. XCTAssertEqual(catsCount, 5, "Should be able to add child element with attributes to an element.")
  290. XCTAssertEqual(dogsCount, 5, "Should be able to add child element with attributes to an element.")
  291. XCTAssertEqual(lastCat.attributes["color"], "orange", "Should be able to get attribute value from added element.")
  292. XCTAssertEqual(try penultDog.get(), "Kika", "Should be able to add child with attributes without overwrites existing elements. (Github Issue #28)")
  293. }
  294. func testAddChildren() {
  295. let animals: [Element] = [
  296. Element(name: "dinosaurs"),
  297. Element(name: "birds"),
  298. Element(name: "bugs"),
  299. ]
  300. exampleDocument.root.addChildren(animals)
  301. let animalsCount = exampleDocument.root.children.count
  302. XCTAssertEqual(animalsCount, 5, "Should be able to add children elements to an element.")
  303. }
  304. func testAddAttributes() {
  305. let firstCat = exampleDocument.root.cats.cat
  306. firstCat.attributes["funny"] = "true"
  307. firstCat.attributes["speed"] = "fast"
  308. firstCat.attributes["years"] = "7"
  309. XCTAssertEqual(firstCat.attributes.count, 5, "Should be able to add attributes to an element.")
  310. XCTAssertEqual(Int(firstCat.attributes["years"]!), 7, "Should be able to get any attribute value now.")
  311. }
  312. func testRemoveChild() throws {
  313. let cats = exampleDocument.root.cats
  314. let lastCat = cats["cat"].last!
  315. let duplicateCat = cats.addChild(name: "cat", value: "Tinna", attributes: ["breed" : "Siberian", "color" : "lightgray"])
  316. lastCat.removeFromParent()
  317. duplicateCat.removeFromParent()
  318. let catsCount = cats["cat"].count
  319. let firstCat = cats["cat"]
  320. XCTAssertEqual(catsCount, 3, "Should be able to remove element from parent.")
  321. XCTAssertEqual(try firstCat.get(), "Tinna", "Should be able to remove the exact element from parent.")
  322. }
  323. func testXMLEscapedString() {
  324. let string = "&<>'\""
  325. let escapedString = string.xmlEscaped
  326. XCTAssertEqual(escapedString, "&amp;&lt;&gt;&apos;&quot;")
  327. }
  328. func testXMLString() {
  329. let testDocument = Document()
  330. let children = testDocument.addChild(name: "children")
  331. children.addChild(name: "child", value: "value", attributes: ["attribute" : "attributeValue<&>"])
  332. children.addChild(name: "child")
  333. children.addChild(name: "child", value: "&<>'\"")
  334. XCTAssertEqual(testDocument.xml, "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n<children>\n\t<child attribute=\"attributeValue&lt;&amp;&gt;\">value</child>\n\t<child />\n\t<child>&amp;&lt;&gt;&apos;&quot;</child>\n</children>", "Should be able to print XML formatted string.")
  335. XCTAssertEqual(testDocument.xmlCompact, "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?><children><child attribute=\"attributeValue&lt;&amp;&gt;\">value</child><child /><child>&amp;&lt;&gt;&apos;&quot;</child></children>", "Should be able to print compact XML string.")
  336. XCTAssertEqual(testDocument.xmlSpaces, "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n<children>\n <child attribute=\"attributeValue&lt;&amp;&gt;\">value</child>\n <child />\n <child>&amp;&lt;&gt;&apos;&quot;</child>\n</children>", "Should be able to print XML formatted string.")
  337. }
  338. // MARK: - XML Parse Performance
  339. func testReadXMLPerformance() {
  340. self.measure() {
  341. _ = self.readXMLFromFile(filename: "plant_catalog")
  342. }
  343. }
  344. func testWriteXMLPerformance() {
  345. self.measure() {
  346. _ = self.plantsDocument.xml
  347. }
  348. }
  349. }