├── .gitignore ├── LICENSE ├── PADockerfile ├── Package.swift ├── README.md ├── Sources └── PerfectXML │ ├── Codable.swift │ ├── SAX.swift │ ├── XMLDOM.swift │ ├── XMLStream.swift │ └── XPath.swift └── Tests ├── LinuxMain.swift └── PerfectXMLTests └── PerfectXMLTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | Packages/ 67 | *.xcodeproj/ 68 | .DS_Store 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /PADockerfile: -------------------------------------------------------------------------------- 1 | RUN apt-get -y update && apt-get install -y libxml2-dev pkg-config 2 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | // Generated automatically by Perfect Assistant 2 3 | // Date: 2018-03-31 16:19:33 +0000 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "PerfectXML", 8 | products: [ 9 | .library(name: "PerfectXML", targets: ["PerfectXML"]) 10 | ], 11 | dependencies: [ 12 | .package(url: "https://github.com/PerfectlySoft/Perfect-libxml2.git", "3.0.0"..<"4.0.0") 13 | ], 14 | targets: [ 15 | .target(name: "PerfectXML", dependencies: []), 16 | .testTarget(name: "PerfectXMLTests", dependencies: ["PerfectXML"]) 17 | ] 18 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Perfect-XML 2 | 3 |

4 | 5 | Get Involed with Perfect! 6 | 7 |

8 | 9 |

10 | 11 | Star Perfect On Github 12 | 13 | 14 | Stack Overflow 15 | 16 | 17 | Follow Perfect on Twitter 18 | 19 | 20 | Join the Perfect Slack 21 | 22 |

23 | 24 |

25 | 26 | Swift 4.0 27 | 28 | 29 | Platforms OS X | Linux 30 | 31 | 32 | License Apache 33 | 34 | 35 | PerfectlySoft Twitter 36 | 37 | 38 | Slack Status 39 | 40 |

41 | 42 | 43 | XML & HTML parsing support for Perfect 44 | 45 | It currently contains most of the DOM Core level 2 *read-only* APIs and includes XPath support. 46 | 47 | ## Building 48 | 49 | Add this project as a dependency in your Package.swift file. 50 | 51 | ``` 52 | .Package(url:"https://github.com/PerfectlySoft/Perfect-XML.git", majorVersion: 3) 53 | ``` 54 | 55 | ## macOS Build Notes 56 | 57 | If you receive a compile error that says the following, you need to install and link libxml2 58 | 59 | ``` 60 | note: you may be able to install libxml-2.0 using your system-packager: 61 | 62 | brew install libxml2 63 | 64 | Compile Swift Module 'PerfectXML' (2 sources) 65 | :1:9: note: in file included from :1: 66 | #import "libxml2.h" 67 | ``` 68 | 69 | To install and link libxml2 with homebrew, use the following two commands 70 | 71 | ``` 72 | brew install libxml2 73 | brew link --force libxml2 74 | ``` 75 | 76 | ## Linux Build Notes 77 | 78 | Ensure that you have installed libxml2-dev and pkg-config. 79 | 80 | ``` 81 | sudo apt-get install libxml2-dev pkg-config 82 | ``` 83 | 84 | ## Usage 85 | 86 | Instantiate an XDocument object with your XML string 87 | 88 | ```swift 89 | import PerfectXML 90 | let document = XDocument(fromSource: xmlString) 91 | ``` 92 | 93 | Instantiate an HTMLDocument object with your HTML string 94 | 95 | ```swift 96 | import PerfectXML 97 | let document = HTMLDocument(fromSource: htmlString) 98 | ``` 99 | 100 | Now you can get the root node of the document structure by using the documentElement property 101 | 102 | ```swift 103 | print(document.documentElement?.string(pretty: true)) 104 | ``` 105 | 106 | Each node has several important properties 107 | 108 | - nodeValue 109 | - nodeName 110 | - parentNode 111 | - childNodes 112 | 113 | Each node also has a getElementsByTagName: method that recursively searches through it and its children to return an array of all nodes that have that name. This method makes it easy to find a single value in the XML file. 114 | 115 | ```swift 116 | import PerfectXML 117 | 118 | let serverResponseXML = "\n" + 119 | "" + 120 | " " + 121 | " " + 122 | " 600" + 123 | " Invalid Request" + 124 | " " + 125 | " " + 126 | " " + 127 | "" 128 | 129 | let serverResponseDocument = XDocument(fromSource: serverResponseXML) 130 | let serverMessage = serverResponseDocument?.documentElement?.getElementsByTagName("Message").first?.nodeValue 131 | print(serverMessage) // prints Optional("Invalid Request") 132 | ``` 133 | 134 | Constructing model types from XML feeds is easy with getElementsByTagName 135 | 136 | ```swift 137 | import PerfectXML 138 | 139 | let rssXML = "" + 140 | "" + 141 | " " + 142 | " W3Schools Home Page" + 143 | " http://www.w3schools.com" + 144 | " Free web building tutorials" + 145 | " " + 146 | " RSS Tutorial" + 147 | " http://www.w3schools.com/xml/xml_rss.asp" + 148 | " New RSS tutorial on W3Schools" + 149 | " " + 150 | " " + 151 | " XML Tutorial" + 152 | " http://www.w3schools.com/xml" + 153 | " New XML tutorial on W3Schools" + 154 | " " + 155 | " " + 156 | "" 157 | 158 | let rssDocument = XDocument(fromSource: rssXML) 159 | let feedItems = rssDocument?.documentElement?.getElementsByTagName("item") 160 | print(feedItems?.count) // prints 2 161 | let items = feedItems?.map({ MyCustomStruct(xmlNode: $0) }) 162 | ``` 163 | 164 | Sometimes tag names are a bit too generic in order to meaningfully search for them, such as "title" in the example above. If we wanted to get the Channel title, link, and description, we could visit each of its children in a more deliberate way. 165 | 166 | ```swift 167 | import PerfectXML 168 | 169 | let rssXML = "" + 170 | "" + 171 | " " + 172 | " W3Schools Home Page" + 173 | " http://www.w3schools.com" + 174 | " Free web building tutorials" + 175 | " " + 176 | " RSS Tutorial" + 177 | " http://www.w3schools.com/xml/xml_rss.asp" + 178 | " New RSS tutorial on W3Schools" + 179 | " " + 180 | " " + 181 | " XML Tutorial" + 182 | " http://www.w3schools.com/xml" + 183 | " New XML tutorial on W3Schools" + 184 | " " + 185 | " " + 186 | "" 187 | 188 | let rssDocument = XDocument(fromSource: rssXML) 189 | let channelNode = rssDocument?.documentElement?.getElementsByTagName("channel").first 190 | let channelTitle = channelNode?.childNodes.filter({ $0.nodeName == "title" }).first?.nodeValue 191 | let channelLink = channelNode?.childNodes.filter({ $0.nodeName == "link" }).first?.nodeValue 192 | let channelDescription = channelNode?.childNodes.filter({ $0.nodeName == "description" }).first?.nodeValue 193 | 194 | print(channelTitle) // Optional("W3Schools Home Page") 195 | print(channelLink) // Optional("http://www.w3schools.com") 196 | print(channelDescription) // Optional("Free web building tutorials") 197 | ``` 198 | 199 | ### Parse XML Source 200 | 201 | This snippet will parse an XML source string and then convert it back into a string. 202 | 203 | ```swift 204 | let docSrc = "\nHI\n" 205 | let doc = XDocument(fromSource: docSrc) 206 | let str = doc?.string(pretty: false) 207 | XCTAssert(str == docSrc, "\(str)") 208 | ``` 209 | 210 | ### Parse HTML Source 211 | 212 | This snippet will parse an HTML source string. 213 | 214 | ```swift 215 | let docSrc = "\n\ntitle\n\n
hi
\n\n\n" 216 | let doc = HTMLDocument(fromSource: docSrc) 217 | let nodeName = doc?.documentElement?.nodeName 218 | XCTAssert(nodeName == "html") 219 | ``` 220 | 221 | ### Inspect Node Names 222 | 223 | ```swift 224 | let docSrc = "\n\n" 225 | let doc = XDocument(fromSource: docSrc) 226 | XCTAssert(doc?.nodeName == "#document") 227 | guard let children = doc?.documentElement else { 228 | return XCTAssert(false, "No children") 229 | } 230 | XCTAssert(children.nodeName == "a") 231 | let names = ["b", "c", "d"] 232 | for (n, v) in zip(children.childNodes, names) { 233 | guard let _ = n as? XElement else { 234 | return XCTAssert(false) 235 | } 236 | XCTAssert(n.nodeName == v, "\(n.nodeName) != \(v)") 237 | } 238 | ``` 239 | 240 | ### Inspect Text Node 241 | 242 | ```swift 243 | let value = "ABCD" 244 | let docSrc = "\n\(value)\n" 245 | let doc = XDocument(fromSource: docSrc) 246 | XCTAssert(doc?.nodeName == "#document") 247 | guard let children = doc?.documentElement else { 248 | return XCTAssert(false, "No children") 249 | } 250 | XCTAssert(children.nodeName == "a") 251 | do { 252 | let children = children.childNodes 253 | XCTAssert(children.count == 1) 254 | guard let textChild = children.first as? XText else { 255 | return XCTAssert(false) 256 | } 257 | XCTAssert(textChild.nodeValue == value) 258 | } 259 | ``` 260 | 261 | ### Check Node Type 262 | 263 | ```swift 264 | let value = "ABCD" 265 | let docSrc = "\n\(value)\n" 266 | let doc = XDocument(fromSource: docSrc) 267 | XCTAssert(doc?.nodeName == "#document") 268 | guard let children = doc?.documentElement else { 269 | return XCTAssert(false, "No children") 270 | } 271 | XCTAssert(children.nodeName == "a") 272 | let nodeType = children.nodeType 273 | if case .elementNode = nodeType { 274 | XCTAssert(true) 275 | } else { 276 | XCTAssert(false, "\(nodeType)") 277 | } 278 | ``` 279 | 280 | ### First & Last Child 281 | 282 | ```swift 283 | let docSrc = "\n\n" 284 | let doc = XDocument(fromSource: docSrc) 285 | XCTAssert(doc?.nodeName == "#document") 286 | guard let children = doc?.documentElement else { 287 | return XCTAssert(false, "No children") 288 | } 289 | XCTAssert(children.nodeName == "a") 290 | guard let firstChild = children.firstChild else { 291 | return XCTAssert(false) 292 | } 293 | guard let lastChild = children.lastChild else { 294 | return XCTAssert(false) 295 | } 296 | XCTAssert(firstChild.nodeName == "b") 297 | XCTAssert(lastChild.nodeName == "d") 298 | ``` 299 | 300 | ### Next & Previous Sibling 301 | 302 | ```swift 303 | let docSrc = "\n\n" 304 | let doc = XDocument(fromSource: docSrc) 305 | XCTAssert(doc?.nodeName == "#document") 306 | guard let children = doc?.documentElement else { 307 | return XCTAssert(false, "No children") 308 | } 309 | XCTAssert(children.nodeName == "a") 310 | guard let firstChild = children.firstChild else { 311 | return XCTAssert(false) 312 | } 313 | XCTAssert(firstChild.nodeName == "b") 314 | guard let nextSib = firstChild.nextSibling else { 315 | return XCTAssert(false) 316 | } 317 | guard let prevSib = nextSib.previousSibling else { 318 | return XCTAssert(false) 319 | } 320 | XCTAssert(nextSib.nodeName == "c") 321 | XCTAssert(prevSib.nodeName == "b") 322 | ``` 323 | 324 | ### Element Attributes 325 | 326 | ```swift 327 | let docSrc = "\n\n" 328 | let doc = XDocument(fromSource: docSrc) 329 | XCTAssert(doc?.nodeName == "#document") 330 | guard let children = doc?.documentElement else { 331 | return XCTAssert(false, "No children") 332 | } 333 | XCTAssert(children.nodeName == "a") 334 | guard let firstChild = children.firstChild as? XElement else { 335 | return XCTAssert(false) 336 | } 337 | XCTAssert(firstChild.nodeName == "b") 338 | guard let atr1 = firstChild.getAttribute(name: "atr1") else { 339 | return XCTAssert(false) 340 | } 341 | XCTAssert(atr1 == "the value") 342 | guard let atr2 = firstChild.getAttributeNode(name: "atr2") else { 343 | return XCTAssert(false) 344 | } 345 | XCTAssert(atr2.value == "the other value") 346 | ``` 347 | 348 | With namespaces: 349 | 350 | ```swift 351 | let names = ["atr1", "atr2"] 352 | let docSrc = "\n\n" 353 | let doc = XDocument(fromSource: docSrc) 354 | XCTAssert(doc?.nodeName == "#document") 355 | guard let children = doc?.documentElement else { 356 | return XCTAssert(false, "No children") 357 | } 358 | XCTAssert(children.nodeName == "a") 359 | guard let firstChild = children.firstChild else { 360 | return XCTAssert(false) 361 | } 362 | XCTAssert(firstChild.nodeName == "b") 363 | guard let attrs = firstChild.attributes else { 364 | return XCTAssert(false, "nil attributes") 365 | } 366 | XCTAssert(attrs.length == 2) 367 | for name in names { 368 | guard let item = attrs.getNamedItemNS(namespaceURI: "foo:bar", localName: name) else { 369 | return XCTAssert(false) 370 | } 371 | XCTAssert(item.nodeName == name) 372 | } 373 | ``` 374 | 375 | ### Get Elements By Name 376 | 377 | ```swift 378 | let docSrc = "\n\n" 379 | let doc = XDocument(fromSource: docSrc) 380 | XCTAssert(doc?.nodeName == "#document") 381 | guard let elements = doc?.documentElement?.getElementsByTagName("b") else { 382 | return XCTAssert(false) 383 | } 384 | XCTAssert(elements.count == 3) 385 | for node in elements { 386 | XCTAssert(node.nodeName == "b") 387 | } 388 | ``` 389 | 390 | With namespaces: 391 | 392 | ```swift 393 | let docSrc = "\nFOO\n" 394 | let doc = XDocument(fromSource: docSrc) 395 | XCTAssert(doc?.nodeName == "#document") 396 | do { 397 | guard let elements = doc?.getElementsByTagNameNS(namespaceURI: "foo:bar", localName: "a") else { 398 | return XCTAssert(false) 399 | } 400 | XCTAssert(elements.count == 1) 401 | for node in elements { 402 | XCTAssert(node.nodeName == "a") 403 | XCTAssert(node.localName == "a") 404 | XCTAssert(node.prefix == "foo") 405 | XCTAssert(node.namespaceURI == "foo:bar") 406 | } 407 | } 408 | do { 409 | guard let elements = doc?.documentElement?.getElementsByTagNameNS(namespaceURI: "foo:bar", localName: "a") else { 410 | return XCTAssert(false) 411 | } 412 | XCTAssert(elements.count == 1) 413 | for node in elements { 414 | XCTAssert(node.nodeName == "a") 415 | XCTAssert(node.localName == "a") 416 | XCTAssert(node.prefix == "foo") 417 | XCTAssert(node.namespaceURI == "foo:bar") 418 | } 419 | } 420 | do { 421 | guard let elements = doc?.getElementsByTagNameNS(namespaceURI: "foo:barz", localName: "a") else { 422 | return XCTAssert(false) 423 | } 424 | XCTAssert(elements.count == 0) 425 | } 426 | do { 427 | guard let elements = doc?.documentElement?.getElementsByTagNameNS(namespaceURI: "foo:barz", localName: "a") else { 428 | return XCTAssert(false) 429 | } 430 | XCTAssert(elements.count == 0) 431 | } 432 | ``` 433 | 434 | ### Get Element By ID 435 | 436 | ```swift 437 | let docSrc = "\nFOO\n" 438 | let doc = XDocument(fromSource: docSrc) 439 | XCTAssert(doc?.nodeName == "#document") 440 | guard let element = doc?.getElementById("foo") else { 441 | return XCTAssert(false) 442 | } 443 | XCTAssert(element.tagName == "b") 444 | ``` 445 | 446 | ### XPath 447 | 448 | Elements: 449 | 450 | ```swift 451 | let docSrc = "\nFOO\n" 452 | guard let doc = XDocument(fromSource: docSrc) else { 453 | return XCTAssert(false) 454 | } 455 | XCTAssert(doc.nodeName == "#document") 456 | let pathRes = doc.extract(path: "/a/b") 457 | guard case .nodeSet(let set) = pathRes else { 458 | return XCTAssert(false, "\(pathRes)") 459 | } 460 | for node in set { 461 | guard let b = node as? XElement else { 462 | return XCTAssert(false, "\(node)") 463 | } 464 | XCTAssert(b.tagName == "b") 465 | } 466 | ``` 467 | 468 | Attributes: 469 | 470 | ```swift 471 | let docSrc = "\nFOO\n" 472 | guard let doc = XDocument(fromSource: docSrc) else { 473 | return XCTAssert(false) 474 | } 475 | XCTAssert(doc.nodeName == "#document") 476 | let pathRes = doc.extract(path: "/a/b/@id") 477 | guard case .nodeSet(let set) = pathRes else { 478 | return XCTAssert(false, "\(pathRes)") 479 | } 480 | for node in set { 481 | guard let b = node as? XAttr else { 482 | return XCTAssert(false, "\(node)") 483 | } 484 | XCTAssert(b.name == "id") 485 | XCTAssert(b.value == "foo") 486 | } 487 | ``` 488 | 489 | Text: 490 | 491 | ```swift 492 | let docSrc = "\nFOO\n" 493 | guard let doc = XDocument(fromSource: docSrc) else { 494 | return XCTAssert(false) 495 | } 496 | XCTAssert(doc.nodeName == "#document") 497 | let pathRes = doc.extract(path: "/a/a/b/text()") 498 | guard case .nodeSet(let set) = pathRes else { 499 | return XCTAssert(false, "\(pathRes)") 500 | } 501 | for node in set { 502 | guard let b = node as? XText else { 503 | return XCTAssert(false, "\(node)") 504 | } 505 | guard let nodeValue = b.nodeValue else { 506 | return XCTAssert(false, "\(b)") 507 | } 508 | XCTAssert(nodeValue == "FOO") 509 | } 510 | ``` 511 | 512 | Namespaces: 513 | 514 | ```swift 515 | let docSrc = "\nFOO\n" 516 | guard let doc = XDocument(fromSource: docSrc) else { 517 | return XCTAssert(false) 518 | } 519 | let namespaces = [("f", "foo:bar")] 520 | let pathRes = doc.extract(path: "/a/f:a", namespaces: namespaces) 521 | guard case .nodeSet(let set) = pathRes else { 522 | return XCTAssert(false, "\(pathRes)") 523 | } 524 | for node in set { 525 | guard let e = node as? XElement else { 526 | return XCTAssert(false, "\(node)") 527 | } 528 | XCTAssert(e.tagName == "a") 529 | XCTAssert(e.namespaceURI == "foo:bar") 530 | XCTAssert(e.prefix == "foo") 531 | } 532 | ``` 533 | -------------------------------------------------------------------------------- /Sources/PerfectXML/Codable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Codable.swift 3 | // PerfectXML 4 | // 5 | // Created by Kyle Jessup on 2018-03-12. 6 | // 7 | 8 | import Foundation 9 | 10 | func die() -> Never { 11 | fatalError("Unimplemented") 12 | } 13 | 14 | public struct XMLDecoderError: Error { 15 | public let msg: String 16 | public init(_ m: String) { 17 | msg = m 18 | } 19 | } 20 | 21 | public struct XMLEncoderError: Error { 22 | public let msg: String 23 | public init(_ m: String) { 24 | msg = m 25 | } 26 | } 27 | 28 | struct XMLCodingKey: CodingKey { 29 | let stringValue: String 30 | let intValue: Int? = nil 31 | init?(stringValue s: String) { 32 | stringValue = s 33 | } 34 | init(_ s: String) { 35 | stringValue = s 36 | } 37 | init?(intValue: Int) { 38 | return nil 39 | } 40 | } 41 | 42 | public class XMLEncoder: Encoder { 43 | public let codingPath: [CodingKey] 44 | public let userInfo: [CodingUserInfoKey : Any] 45 | var encoded: String = "" 46 | 47 | init(rootName: String, codingPath cp: [CodingKey] = []) { 48 | codingPath = cp + [XMLCodingKey(rootName)] 49 | userInfo = [:] 50 | } 51 | 52 | public init() { 53 | codingPath = [] 54 | userInfo = [:] 55 | } 56 | 57 | public func encode(_ value: A, rootName: String, namespace: String? = nil) throws -> Data { 58 | encoded = "" 59 | try value.encode(to: self) 60 | let namePrefix: String 61 | if let ns = namespace { 62 | namePrefix = ":\(ns)" 63 | } else { 64 | namePrefix = "" 65 | } 66 | if encoded.isEmpty { 67 | encoded = "<\(rootName)\(namePrefix)/>" 68 | } else { 69 | encoded = "<\(rootName)\(namePrefix)>\(encoded)" 70 | } 71 | guard let data = encoded.data(using: .utf8) else { 72 | throw XMLEncoderError("Invalid encoding was generated.") 73 | } 74 | return data 75 | } 76 | 77 | public func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { 78 | return KeyedEncodingContainer(XMLEncodingContainer(codingPath: codingPath, parent: self)) 79 | } 80 | 81 | public func unkeyedContainer() -> UnkeyedEncodingContainer { 82 | die() 83 | } 84 | 85 | public func singleValueContainer() -> SingleValueEncodingContainer { 86 | die() 87 | } 88 | } 89 | 90 | class XMLEncodingContainer: KeyedEncodingContainerProtocol { 91 | typealias Key = K 92 | let codingPath: [CodingKey] 93 | let parent: XMLEncoder 94 | 95 | init(codingPath c: [CodingKey], parent p: XMLEncoder) { 96 | codingPath = c 97 | parent = p 98 | } 99 | 100 | private func escapeEntities(_ s: String) -> String { 101 | return s.replacingOccurrences(of: "&", with: "&") 102 | .replacingOccurrences(of: "<", with: "<") 103 | .replacingOccurrences(of: ">", with: ">") 104 | } 105 | 106 | private func append(_ s: String) { 107 | parent.encoded.append(s) 108 | } 109 | 110 | private func append(_ key: K, _ value: CustomStringConvertible) { 111 | append("<\(key.stringValue)>\(value)") 112 | } 113 | 114 | func encodeNil(forKey key: K) throws { 115 | append("<\(key.stringValue)/>") 116 | } 117 | 118 | func encode(_ value: Bool, forKey key: K) throws { 119 | append(key, value) 120 | } 121 | 122 | func encode(_ value: Int, forKey key: K) throws { 123 | append(key, value) 124 | } 125 | 126 | func encode(_ value: Int8, forKey key: K) throws { 127 | append(key, value) 128 | } 129 | 130 | func encode(_ value: Int16, forKey key: K) throws { 131 | append(key, value) 132 | } 133 | 134 | func encode(_ value: Int32, forKey key: K) throws { 135 | append(key, value) 136 | } 137 | 138 | func encode(_ value: Int64, forKey key: K) throws { 139 | append(key, value) 140 | } 141 | 142 | func encode(_ value: UInt, forKey key: K) throws { 143 | append(key, value) 144 | } 145 | 146 | func encode(_ value: UInt8, forKey key: K) throws { 147 | append(key, value) 148 | } 149 | 150 | func encode(_ value: UInt16, forKey key: K) throws { 151 | append(key, value) 152 | } 153 | 154 | func encode(_ value: UInt32, forKey key: K) throws { 155 | append(key, value) 156 | } 157 | 158 | func encode(_ value: UInt64, forKey key: K) throws { 159 | append(key, value) 160 | } 161 | 162 | func encode(_ value: Float, forKey key: K) throws { 163 | append(key, value) 164 | } 165 | 166 | func encode(_ value: Double, forKey key: K) throws { 167 | append(key, value) 168 | } 169 | 170 | func encode(_ value: String, forKey key: K) throws { 171 | append(key, escapeEntities(value)) 172 | } 173 | 174 | func encode(_ value: T, forKey key: K) throws where T : Encodable { 175 | die() 176 | } 177 | 178 | func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: K) -> KeyedEncodingContainer where NestedKey : CodingKey { 179 | die() 180 | } 181 | 182 | func nestedUnkeyedContainer(forKey key: K) -> UnkeyedEncodingContainer { 183 | die() 184 | } 185 | 186 | func superEncoder() -> Encoder { 187 | return parent 188 | } 189 | 190 | func superEncoder(forKey key: K) -> Encoder { 191 | return parent 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /Sources/PerfectXML/SAX.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SAX.swift 3 | // PerfectXML 4 | // 5 | // Created by Kyle Jessup on 2018-03-13. 6 | // 7 | 8 | import perfectxml2 9 | 10 | public struct SAXError: Error { 11 | public let description: String 12 | public init(_ d: String) { 13 | description = d 14 | } 15 | } 16 | 17 | public protocol SAXDelegate { 18 | func startDocument() 19 | func endDocument() 20 | func processingInstruction(target: String, data: String) 21 | func entityDecl(name: String, type: Int, pubicId: String, systemId: String, content: String) 22 | func unparsedEntityDecl(name: String, pubicId: String, systemId: String, notationName: String) 23 | func notationDecl(name: String, pubicId: String, systemId: String) 24 | func attributeDecl(elem: String, fullName: String, type: Int, def: Int, defaultValue: String?, tree: xmlEnumerationPtr?) 25 | func elementDecl(name: String, type: Int, content: xmlElementContentPtr?) 26 | func reference(name: String) 27 | func comment(_ c: String) 28 | func startElementNs(localName: String, 29 | prefix: String?, 30 | uri: String?, 31 | namespaces: [SAXDelegateNamespace], 32 | attributes: [SAXDelegateAttribute]) 33 | func endElementNs(localName: String, 34 | prefix: String?, 35 | uri: String?) 36 | func characters(_ c: String) 37 | func ignorableWhitespace(_ c: String) 38 | func cdataBlock(_ c: String) 39 | } 40 | 41 | public struct SAXDelegateNamespace { 42 | public let prefix: String? 43 | public let uri: String 44 | } 45 | 46 | public struct SAXDelegateAttribute { 47 | public let localName: String 48 | public let prefix: String? 49 | public let nsUri: String? 50 | public let value: String 51 | } 52 | 53 | /// Default implimentations do nothing 54 | public extension SAXDelegate { 55 | func startDocument() {} 56 | func endDocument() {} 57 | func processingInstruction(target: String, data: String) {} 58 | func entityDecl(name: String, type: Int, pubicId: String, systemId: String, content: String) {} 59 | func unparsedEntityDecl(name: String, pubicId: String, systemId: String, notationName: String) {} 60 | func notationDecl(name: String, pubicId: String, systemId: String) {} 61 | func attributeDecl(elem: String, fullName: String, type: Int, def: Int, defaultValue: String?, tree: xmlEnumerationPtr?) {} 62 | func elementDecl(name: String, type: Int, content: xmlElementContentPtr?) {} 63 | func reference(name: String) {} 64 | func comment(_ c: String) {} 65 | func startElementNs(localName: String, 66 | prefix: String?, 67 | uri: String?, 68 | namespaces: [SAXDelegateNamespace], 69 | attributes: [SAXDelegateAttribute]) {} 70 | func endElementNs(localName: String, 71 | prefix: String?, 72 | uri: String?) {} 73 | func characters(_ c: String) {} 74 | func ignorableWhitespace(_ c: String) {} 75 | func cdataBlock(_ c: String) {} 76 | } 77 | 78 | public class SAXParser { 79 | var handler = xmlSAXHandler() 80 | var delegate: SAXDelegate 81 | var parserCtxt: xmlParserCtxtPtr? 82 | public init(delegate d: SAXDelegate) { 83 | delegate = d 84 | 85 | } 86 | deinit { 87 | if let c = parserCtxt { 88 | xmlFreeParserCtxt(c) 89 | } 90 | } 91 | private func getCtxt() throws -> xmlParserCtxtPtr { 92 | if let c = parserCtxt { 93 | return c 94 | } 95 | try setHandlerFuncs() 96 | guard let c = xmlCreatePushParserCtxt(&handler, 97 | asContext(self), nil, 0, nil) else { 98 | throw SAXError("Unable to allocate XML parser.") 99 | } 100 | parserCtxt = c 101 | return c 102 | } 103 | public func pushData(_ d: [UInt8]) throws { 104 | let ctx = try getCtxt() 105 | let code = UnsafePointer(d).withMemoryRebound(to: Int8.self, capacity: d.count) { 106 | return xmlParseChunk(ctx, $0, Int32(d.count), 0) 107 | } 108 | guard 0 == code else { 109 | throw SAXError("Error parsing chunk: \(code).") 110 | } 111 | } 112 | public func finish() throws { 113 | xmlParseChunk(try getCtxt(), nil, 0, 0) 114 | } 115 | 116 | private static func ptr2AryNamespaces(_ ptr: UnsafeMutablePointer?>?, count: Int) -> [SAXDelegateNamespace] { 117 | guard let ptr = ptr else { 118 | return [] 119 | } 120 | var ret: [SAXDelegateNamespace] = [] 121 | for i in stride(from: 0, to: count*2, by: 2) { 122 | let ptr1 = ptr[i] 123 | let ptr2 = ptr[i+1] 124 | ret.append(.init(prefix: String(ptr1), uri: String(ptr2, default: ""))) 125 | } 126 | return ret 127 | } 128 | 129 | private static func ptr2AryAttributes(_ ptr: UnsafeMutablePointer?>?, count: Int) -> [SAXDelegateAttribute] { 130 | guard let ptr = ptr else { 131 | return [] 132 | } 133 | var ret: [SAXDelegateAttribute] = [] 134 | for i in stride(from: 0, to: count*5, by: 5) { 135 | let namePtr = ptr[i] 136 | let prefixPtr = ptr[i+1] 137 | let nsUri = ptr[i+2] 138 | let value: String 139 | if let valueStartPtr = ptr[i+3], 140 | let valueEndPtr = ptr[i+4] { 141 | if valueEndPtr.pointee == 0 { 142 | value = String(valueStartPtr, default: "") 143 | } else { 144 | let valueLen = valueEndPtr - valueStartPtr 145 | value = String(valueStartPtr, count: valueLen, default: "") 146 | } 147 | } else { 148 | value = "" 149 | } 150 | ret.append(.init( 151 | localName: String(namePtr, default: ""), 152 | prefix: String(prefixPtr), 153 | nsUri: String(nsUri), 154 | value: value)) 155 | } 156 | return ret 157 | } 158 | 159 | private func setHandlerFuncs() throws { 160 | handler.startElement = nil 161 | handler.endElement = nil 162 | handler.startElementNs = { 163 | a, b, c, d, e, f, g, h, i in 164 | fromContext(SAXParser.self, a)?.delegate.startElementNs(localName: String(b, default: ""), 165 | prefix: String(c), 166 | uri: String(d), 167 | namespaces: SAXParser.ptr2AryNamespaces(f, count: Int(e)), 168 | attributes: SAXParser.ptr2AryAttributes(i, count: Int(g))) 169 | } 170 | handler.endElementNs = { 171 | fromContext(SAXParser.self, $0)?.delegate.endElementNs( 172 | localName: String($1) ?? "no name", 173 | prefix: String($2), 174 | uri: String($3)) 175 | } 176 | handler.serror = nil 177 | handler.internalSubset = { _, _, _, _ in } 178 | handler.externalSubset = { _, _, _, _ in } 179 | handler.isStandalone = { _ in return 1 } 180 | handler.hasInternalSubset = { _ in return 0 } 181 | handler.hasExternalSubset = { _ in return 0 } 182 | handler.resolveEntity = { _, _, _ in return nil } 183 | handler.getEntity = { xmlGetPredefinedEntity($1) } 184 | handler.getParameterEntity = { _, _ in return nil } 185 | handler.entityDecl = { 186 | fromContext(SAXParser.self, $0)?.delegate.entityDecl(name: String($1, default: ""), 187 | type: Int($2), 188 | pubicId: String($3, default: ""), 189 | systemId: String($4, default: ""), 190 | content: String($5, default: "")) 191 | } 192 | handler.attributeDecl = { 193 | fromContext(SAXParser.self, $0)?.delegate.attributeDecl(elem: String($1, default: ""), 194 | fullName: String($2, default: ""), 195 | type: Int($3), 196 | def: Int($4), 197 | defaultValue: String($5), 198 | tree: $6) 199 | } 200 | handler.elementDecl = { 201 | fromContext(SAXParser.self, $0)?.delegate.elementDecl(name: String($1, default: ""), type: Int($2), content: $3) 202 | } 203 | handler.notationDecl = { 204 | fromContext(SAXParser.self, $0)?.delegate.notationDecl(name: String($1, default: ""), pubicId: String($2, default: ""), systemId: String($3, default: "")) 205 | } 206 | handler.unparsedEntityDecl = { 207 | fromContext(SAXParser.self, $0)?.delegate.unparsedEntityDecl(name: String($1, default: ""), 208 | pubicId: String($2, default: ""), 209 | systemId: String($3, default: ""), 210 | notationName: String($4, default: "")) 211 | } 212 | handler.setDocumentLocator = { _, _ in } 213 | handler.startDocument = { fromContext(SAXParser.self, $0)?.delegate.startDocument() } 214 | handler.endDocument = { fromContext(SAXParser.self, $0)?.delegate.endDocument() } 215 | handler.reference = { fromContext(SAXParser.self, $0)?.delegate.reference(name: String($1, default: "")) } 216 | handler.characters = { fromContext(SAXParser.self, $0)?.delegate.characters(String($1, count: Int($2), default: "")) } 217 | handler.cdataBlock = { fromContext(SAXParser.self, $0)?.delegate.cdataBlock(String($1, count: Int($2), default: "")) } 218 | handler.ignorableWhitespace = { fromContext(SAXParser.self, $0)?.delegate.ignorableWhitespace(String($1, default: "")) ; _ = $2 }; 219 | handler.processingInstruction = { fromContext(SAXParser.self, $0)?.delegate.processingInstruction(target: String($1, default: ""), data: String($2, default: "")) } 220 | handler.comment = { fromContext(SAXParser.self, $0)?.delegate.comment(String($1, default: "")) } 221 | handler.warning = nil//xmlParserWarning; 222 | handler.error = nil//xmlParserError; 223 | handler.fatalError = nil//xmlParserError; 224 | 225 | handler.initialized = 0xDEEDBEAF 226 | } 227 | } 228 | 229 | 230 | -------------------------------------------------------------------------------- /Sources/PerfectXML/XMLDOM.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Package.swift 3 | // PerfectXML 4 | // 5 | // Created by Kyle Jessup on 2016-07-20. 6 | // Copyright (C) 2016 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import perfectxml2 21 | 22 | func toNodePtr(_ p: T) -> xmlNodePtr { 23 | return unsafeBitCast(p, to: UnsafeMutablePointer.self) 24 | } 25 | 26 | func fromNodePtr(_ nodePtr: xmlNodePtr) -> UnsafeMutablePointer { 27 | return nodePtr.withMemoryRebound(to: T.self, capacity: 1) { return $0 } 28 | } 29 | 30 | private typealias ForEachFunc = (_ node: xmlNodePtr) -> Bool 31 | private func forEach(node: xmlNodePtr, childrenOnly: Bool, continueFunc: ForEachFunc) -> Bool { 32 | if !childrenOnly && !continueFunc(node) { 33 | return false 34 | } 35 | var c = node.pointee.children 36 | while let cnode = c { 37 | guard forEach(node: cnode, childrenOnly: false, continueFunc: continueFunc) else { 38 | return false 39 | } 40 | c = cnode.pointee.next 41 | } 42 | return true 43 | } 44 | 45 | final class XErrorTracker { 46 | var errorMsgs = [String]() 47 | } 48 | 49 | /// Supported XML node types. 50 | public enum XNodeType { 51 | case elementNode, attributeNode, textNode, cDataSection, entityReferenceNode 52 | case entityNode, processingInstruction, commentNode, documentNode, documentTypeNode 53 | case documentFragmentNode, notationNode 54 | case unknownNodeType 55 | init(_ i: Int32) { 56 | self.init(xmlElementType(UInt32(i))) 57 | } 58 | init(_ type: xmlElementType) { 59 | switch type { 60 | case XML_ELEMENT_NODE: self = .elementNode 61 | case XML_ATTRIBUTE_NODE: self = .attributeNode 62 | case XML_TEXT_NODE: self = .textNode 63 | case XML_CDATA_SECTION_NODE: self = .cDataSection 64 | case XML_ENTITY_REF_NODE: self = .entityReferenceNode 65 | case XML_ENTITY_NODE: self = .entityNode 66 | case XML_PI_NODE: self = .processingInstruction 67 | case XML_COMMENT_NODE: self = .commentNode 68 | case XML_DOCUMENT_NODE: self = .documentNode 69 | case XML_DOCUMENT_TYPE_NODE: self = .documentTypeNode 70 | case XML_DOCUMENT_FRAG_NODE: self = .documentFragmentNode 71 | case XML_NOTATION_NODE: self = .notationNode 72 | default: self = .unknownNodeType 73 | // XML_HTML_DOCUMENT_NODE = 13 74 | // XML_DTD_NODE = 14 75 | // XML_ELEMENT_DECL = 15 76 | // XML_ATTRIBUTE_DECL = 16 77 | // XML_ENTITY_DECL = 17 78 | // XML_NAMESPACE_DECL = 18 79 | // XML_XINCLUDE_START = 19 80 | // XML_XINCLUDE_END = 20 81 | // XML_DOCB_DOCUMENT_NODE 82 | } 83 | } 84 | } 85 | 86 | /// Base class for all XML nodes. 87 | /// This is intended to track the DOM Core level 2 specification as much as is practically possible. 88 | /// http://www.w3.org/TR/DOM-Level-2-Core/core.html 89 | public class XNode: CustomStringConvertible { 90 | 91 | let nodePtr: xmlNodePtr 92 | /// The name of this node, depending on its type. 93 | public var nodeName: String { 94 | guard let name = nodePtr.pointee.name else { 95 | return "" 96 | } 97 | return String(validatingUTF8: UnsafeRawPointer(name).assumingMemoryBound(to: Int8.self)) ?? "" 98 | } 99 | /// The value of this node, depending on its type. When it is defined to be null, setting it has no effect. 100 | public var nodeValue: String? { 101 | guard let content = xmlNodeGetContent(nodePtr) else { 102 | return nil 103 | } 104 | defer { 105 | xmlFree(content) 106 | } 107 | return String(validatingUTF8: UnsafeMutableRawPointer(content).assumingMemoryBound(to: Int8.self)) 108 | } 109 | /// A code representing the type of the underlying object. 110 | public var nodeType: XNodeType { return XNodeType(nodePtr.pointee.type) } 111 | 112 | /// The parent of this node. All nodes, except Attr, Document, DocumentFragment, Entity, and Notation may have a parent. However, if a node has just been created and not yet added to the tree, or if it has been removed from the tree, this is null. 113 | public var parentNode: XNode? { 114 | guard let parentNode = nodePtr.pointee.parent else { 115 | return nil 116 | } 117 | return asConcreteNode(parentNode) 118 | } 119 | /// A NodeList that contains all children of this node. If there are no children, this is a NodeList containing no nodes. 120 | public var childNodes: [XNode] { 121 | var c = nodePtr.pointee.children 122 | var ary = [XNode]() 123 | while let child = c { 124 | let concrete = asConcreteNode(child) 125 | ary.append(concrete) 126 | c = c?.pointee.next 127 | } 128 | return ary 129 | } 130 | /// The first child of this node. If there is no such node, this returns null. 131 | public var firstChild: XNode? { 132 | guard let child = nodePtr.pointee.children else { 133 | return nil 134 | } 135 | return asConcreteNode(child) 136 | } 137 | /// The last child of this node. If there is no such node, this returns null. 138 | public var lastChild: XNode? { 139 | guard let child = xmlGetLastChild(nodePtr) else { 140 | return nil 141 | } 142 | return asConcreteNode(child) 143 | } 144 | /// The node immediately preceding this node. If there is no such node, this returns null. 145 | public var previousSibling: XNode? { 146 | guard let sib = nodePtr.pointee.prev else { 147 | return nil 148 | } 149 | return asConcreteNode(sib) 150 | } 151 | /// The node immediately following this node. If there is no such node, this returns null. 152 | public var nextSibling: XNode? { 153 | guard let sib = nodePtr.pointee.next else { 154 | return nil 155 | } 156 | return asConcreteNode(sib) 157 | } 158 | /// The Document object associated with this node. This is also the Document object used to create new nodes. When this node is a Document or a DocumentType which is not used with any Document yet, this is null. 159 | public var ownerDocument: XDocument? 160 | /// A NamedNodeMap containing the attributes of this node (if it is an Element) or null otherwise. 161 | public var attributes: XNamedNodeMap? { 162 | guard case .elementNode = nodeType else { 163 | return nil 164 | } 165 | return XNamedNodeMapAttr(node: self) 166 | } 167 | /// The namespace URI of this node, or null if it is unspecified. 168 | /// This is not a computed value that is the result of a namespace lookup based on an examination of the namespace declarations in scope. It is merely the namespace URI given at creation time. 169 | /// For nodes of any type other than ELEMENT_NODE and ATTRIBUTE_NODE and nodes created with a DOM Level 1 method, such as createElement from the Document interface, this is always null. 170 | public var namespaceURI: String? { 171 | guard let ns = nodePtr.pointee.ns else { 172 | return nil 173 | } 174 | guard let chars = ns.pointee.href else { 175 | return nil 176 | } 177 | return String(validatingUTF8: UnsafeRawPointer(chars).assumingMemoryBound(to: Int8.self)) 178 | } 179 | /// The namespace prefix of this node, or null if it is unspecified. 180 | public var prefix: String? { 181 | guard let ns = nodePtr.pointee.ns else { 182 | return nil 183 | } 184 | guard let chars = ns.pointee.prefix else { 185 | return nil 186 | } 187 | return String(validatingUTF8: UnsafeRawPointer(chars).assumingMemoryBound(to: Int8.self)) 188 | } 189 | /// Returns the local part of the qualified name of this node. 190 | /// For nodes of any type other than ELEMENT_NODE and ATTRIBUTE_NODE and nodes created with a DOM Level 1 method, such as createElement from the Document interface, this is always null. 191 | public var localName: String? { 192 | guard let name = nodePtr.pointee.name else { 193 | return nil 194 | } 195 | var prefix = UnsafeMutablePointer(nil as OpaquePointer?) 196 | guard let localPart = xmlSplitQName2(name, &prefix) else { 197 | return nodeName 198 | } 199 | defer { 200 | xmlFree(localPart) 201 | if nil != prefix { 202 | xmlFree(prefix) 203 | } 204 | } 205 | return String(validatingUTF8: UnsafeRawPointer(localPart).assumingMemoryBound(to: Int8.self)) 206 | } 207 | 208 | init(_ node: xmlNodePtr, document: XDocument?) { 209 | self.nodePtr = node 210 | self.ownerDocument = document 211 | } 212 | 213 | deinit { 214 | if nodePtr.pointee.type == XML_DOCUMENT_NODE { 215 | xmlFreeDoc(nodePtr.pointee.doc) 216 | } 217 | } 218 | 219 | func asConcreteNode(_ ptr: xmlNodePtr) -> XNode { 220 | switch ptr.pointee.type { 221 | case XML_ELEMENT_NODE: return XElement(fromNodePtr(ptr), document: self.ownerDocument) 222 | case XML_ATTRIBUTE_NODE: return XAttr(fromNodePtr(ptr), document: self.ownerDocument) 223 | case XML_TEXT_NODE: return XText(ptr, document: self.ownerDocument) 224 | case XML_CDATA_SECTION_NODE: return XCData(ptr, document: self.ownerDocument) 225 | // case XML_ENTITY_REF_NODE: 226 | // case XML_ENTITY_NODE: 227 | // case XML_PI_NODE: 228 | case XML_COMMENT_NODE: return XComment(ptr, document: self.ownerDocument) 229 | // case XML_DOCUMENT_NODE: 230 | // case XML_DOCUMENT_TYPE_NODE: 231 | // case XML_DOCUMENT_FRAG_NODE: 232 | // case XML_NOTATION_NODE: 233 | // case XML_HTML_DOCUMENT_NODE: 234 | // case XML_DTD_NODE: 235 | // case XML_ELEMENT_DECL: 236 | // case XML_ATTRIBUTE_DECL: 237 | // case XML_ENTITY_DECL: 238 | // case XML_NAMESPACE_DECL: 239 | // case XML_XINCLUDE_START: 240 | // case XML_XINCLUDE_END: 241 | // case XML_DOCB_DOCUMENT_NODE: 242 | default: () 243 | // print("Unhandled node type \(ptr.pointee.type)") 244 | return XNode(ptr, document: self.ownerDocument) 245 | } 246 | } 247 | /// Convert the node tree to String. Optionally pretty-print. 248 | public func string(pretty: Bool = false) -> String { 249 | let buff = xmlBufferCreate() 250 | defer { xmlBufferFree(buff) } 251 | var newNodePtr = self.nodePtr.pointee 252 | _ = xmlNodeDump(buff, self.nodePtr.pointee.doc, &newNodePtr, 0, pretty ? 1 : 0) 253 | guard let content = xmlBufferContent(buff) else { 254 | return "" 255 | } 256 | return String(validatingUTF8: UnsafeRawPointer(content).assumingMemoryBound(to: Int8.self)) ?? "" 257 | } 258 | /// The non-pretty printed string value. 259 | public var description: String { 260 | return self.string() 261 | } 262 | } 263 | 264 | 265 | 266 | /// An XML document. 267 | public class XDocument: XNode { 268 | 269 | static var initialize: Bool = { 270 | xmlInitParser() 271 | xmlXPathInit() 272 | return true 273 | }() 274 | 275 | override public var nodeName: String { 276 | return "#document" 277 | } 278 | 279 | /// This is a convenience attribute that allows direct access to the child node that is the root element of the document. 280 | public var documentElement: XElement? { 281 | guard let e = xmlDocGetRootElement(fromNodePtr(nodePtr)) else { 282 | return nil 283 | } 284 | return XElement(fromNodePtr(e), document: self) 285 | } 286 | 287 | /// Parse the XML source string and create the document, if possible. 288 | public init?(fromSource: String) { 289 | _ = XDocument.initialize 290 | guard let doc = xmlParseDoc(fromSource) else { 291 | return nil 292 | } 293 | super.init(toNodePtr(doc), document: nil) 294 | } 295 | 296 | init(_ ptr: xmlDocPtr) { 297 | super.init(toNodePtr(ptr), document: nil) 298 | } 299 | 300 | /// Returns a NodeList of all the Elements with a given tag name in the order in which they are encountered in a preorder traversal of the Document tree. 301 | public func getElementsByTagName(_ name: String) -> [XElement] { 302 | guard let element = documentElement else { 303 | return [XElement]() 304 | } 305 | return element.getElementsByTagName(name, childrenOnly: false) 306 | } 307 | 308 | /// Returns a NodeList of all the Elements with a given local name and namespace URI in the order in which they are encountered in a preorder traversal of the Document tree. 309 | public func getElementsByTagNameNS(namespaceURI: String, localName: String) -> [XElement] { 310 | guard let element = documentElement else { 311 | return [XElement]() 312 | } 313 | return element.getElementsByTagNameNS(namespaceURI: namespaceURI, localName: localName, childrenOnly: false) 314 | } 315 | 316 | /// Returns the Element whose ID is given by elementId. If no such element exists, returns null. Behavior is not defined if more than one element has this ID. 317 | /// Note that this implimentation looks explicitly for an "id" attribute. 318 | public func getElementById(_ elementId: String) -> XElement? { 319 | guard let element = documentElement else { 320 | return nil 321 | } 322 | return element.getElementById(elementId, childrenOnly: false) 323 | } 324 | } 325 | 326 | public class HTMLDocument: XDocument { 327 | /// Parse the HTML source string and create the document, if possible. 328 | public init?(fromSource: String, encoding: String = "UTF-8") { 329 | _ = XDocument.initialize 330 | let src = Array(fromSource.utf8) 331 | let p = UnsafeMutablePointer(mutating: UnsafePointer(src)) 332 | guard let doc = htmlParseDoc(p, encoding) else { 333 | return nil 334 | } 335 | super.init(doc) 336 | } 337 | } 338 | 339 | /// An XML element node. 340 | public class XElement: XNode { 341 | /// The name of the element. 342 | public var tagName: String { 343 | return nodeName 344 | } 345 | 346 | init(_ node: xmlElementPtr, document: XDocument?) { 347 | super.init(toNodePtr(node), document: document) 348 | } 349 | /// Retrieves an attribute value by name. 350 | public func getAttribute(name: String) -> String? { 351 | guard let node = getAttributeNode(name: name) else { 352 | return nil 353 | } 354 | return node.value 355 | } 356 | /// Retrieves an attribute node by name. 357 | /// To retrieve an attribute node by qualified name and namespace URI, use the getAttributeNodeNS method. 358 | public func getAttributeNode(name: String) -> XAttr? { 359 | var n = nodePtr.pointee.properties 360 | while let attr = n { 361 | guard let namePtr = attr.pointee.name else { 362 | continue 363 | } 364 | if String(validatingUTF8: UnsafeRawPointer(namePtr).assumingMemoryBound(to: Int8.self)) == name { 365 | return asConcreteNode(UnsafeMutableRawPointer(attr).assumingMemoryBound(to: xmlNode.self)) as? XAttr 366 | } 367 | n = n?.pointee.next 368 | } 369 | return nil 370 | } 371 | /// Retrieves an Attr node by local name and namespace URI. HTML-only DOM implementations do not need to implement this method. 372 | public func getAttributeNodeNS(namespaceURI: String, localName: String) -> XAttr? { 373 | var n = nodePtr.pointee.properties 374 | while let attr = n { 375 | defer { 376 | n = n?.pointee.next 377 | } 378 | guard let cname = attr.pointee.name else { 379 | continue 380 | } 381 | guard let ns = attr.pointee.ns else { 382 | continue 383 | } 384 | guard let nameTest = String(validatingUTF8: UnsafeRawPointer(cname).assumingMemoryBound(to: Int8.self)), 385 | let href = ns.pointee.href, 386 | let nsNameTest = String(validatingUTF8: UnsafeRawPointer(href).assumingMemoryBound(to: Int8.self)) else { 387 | continue 388 | } 389 | 390 | if nameTest == localName && nsNameTest == namespaceURI { 391 | return asConcreteNode(UnsafeMutableRawPointer(attr).assumingMemoryBound(to: xmlNode.self)) as? XAttr 392 | } 393 | } 394 | return nil 395 | } 396 | /// Returns true when an attribute with a given name is specified on this element or has a default value, false otherwise. 397 | public func hasAttribute(name: String) -> Bool { 398 | return nil != getAttributeNode(name: name) 399 | } 400 | /// Returns true when an attribute with a given local name and namespace URI is specified on this element or has a default value, false otherwise. 401 | public func hasAttributeNS(namespaceURI: String, localName: String) -> Bool { 402 | return nil != getAttributeNodeNS(namespaceURI: namespaceURI, localName: localName) 403 | } 404 | /// Returns a NodeList of all descendant Elements with a given tag name, in the order in which they are encountered in a preorder traversal of this Element tree. 405 | public func getElementsByTagName(_ name: String) -> [XElement] { 406 | return getElementsByTagName(name, childrenOnly: true) 407 | } 408 | /// Returns a NodeList of all the descendant Elements with a given local name and namespace URI in the order in which they are encountered in a preorder traversal of this Element tree. 409 | public func getElementsByTagNameNS(namespaceURI: String, localName: String) -> [XElement] { 410 | return getElementsByTagNameNS(namespaceURI: namespaceURI, localName: localName, childrenOnly: true) 411 | } 412 | 413 | func getElementsByTagNameNS(namespaceURI: String, localName: String, childrenOnly: Bool) -> [XElement] { 414 | var ret = [XElement]() 415 | _ = forEach(node: nodePtr, childrenOnly: childrenOnly) { 416 | node in 417 | 418 | guard let name = node.pointee.name else { 419 | return true 420 | } 421 | guard localName == "*" || String(validatingUTF8: UnsafeRawPointer(name).assumingMemoryBound(to: Int8.self)) == localName else { 422 | return true 423 | } 424 | guard let ns = node.pointee.ns else { 425 | return true 426 | } 427 | guard let chars = ns.pointee.href else { 428 | return true 429 | } 430 | guard namespaceURI == "*" || String(validatingUTF8: UnsafeRawPointer(chars).assumingMemoryBound(to: Int8.self)) == namespaceURI else { 431 | return true 432 | } 433 | guard let element = self.asConcreteNode(node) as? XElement else { 434 | return true 435 | } 436 | ret.append(element) 437 | return true 438 | } 439 | return ret 440 | } 441 | 442 | func getElementById(_ elementId: String, childrenOnly: Bool) -> XElement? { 443 | var ret: XElement? 444 | _ = forEach(node: nodePtr, childrenOnly: childrenOnly) { 445 | node in 446 | guard node.pointee.type == XML_ELEMENT_NODE else { 447 | return true 448 | } 449 | let element = XElement(fromNodePtr(node), document: self.ownerDocument) 450 | guard let attrVal = element.getAttribute(name: "id") else { 451 | return true 452 | } 453 | if attrVal == elementId { 454 | ret = element 455 | } 456 | return nil == ret 457 | } 458 | return ret 459 | } 460 | 461 | func getElementsByTagName(_ name: String, childrenOnly: Bool) -> [XElement] { 462 | var ret = [XElement]() 463 | _ = forEach(node: nodePtr, childrenOnly: childrenOnly) { 464 | node in 465 | 466 | guard let namePtr = node.pointee.name else { 467 | return true 468 | } 469 | guard name == "*" || String(validatingUTF8: UnsafeRawPointer(namePtr).assumingMemoryBound(to: Int8.self)) == name else { 470 | return true 471 | } 472 | guard let element = self.asConcreteNode(node) as? XElement else { 473 | return true 474 | } 475 | ret.append(element) 476 | return true 477 | } 478 | return ret 479 | } 480 | } 481 | 482 | /// A single XML element attribute node. 483 | public class XAttr: XNode { 484 | /// Returns the name of this attribute. 485 | public var name: String { 486 | return nodeName 487 | } 488 | /// On retrieval, the value of the attribute is returned as a string. Character and general entity references are replaced with their values. See also the method getAttribute on the Element interface. 489 | public var value: String { 490 | return nodeValue ?? "" 491 | } 492 | /// The Element node this attribute is attached to or null if this attribute is not in use. 493 | public var ownerElement: XElement? { 494 | return parentNode as? XElement 495 | } 496 | 497 | init(_ node: xmlAttrPtr, document: XDocument?) { 498 | super.init(toNodePtr(node), document: document) 499 | } 500 | } 501 | 502 | /// An XML text node. 503 | public class XText: XNode { 504 | override public var nodeName: String { 505 | return "#text" 506 | } 507 | } 508 | 509 | /// An XML CData node. 510 | public class XCData: XText { 511 | override public var nodeName: String { 512 | return "#cdata-section" 513 | } 514 | } 515 | 516 | /// An XML comment node. 517 | public class XComment: XText { 518 | override public var nodeName: String { 519 | return "#comment" 520 | } 521 | } 522 | 523 | /// A NamedNodeMap protocol. 524 | public protocol XNamedNodeMap { 525 | var length: Int { get } 526 | func getNamedItem(name: String) -> XNode? 527 | func getNamedItemNS(namespaceURI: String, localName: String) -> XNode? 528 | func item(index: Int) -> XNode? 529 | } 530 | 531 | /// Subscript operators for NamedNodeMap 532 | public extension XNamedNodeMap { 533 | subscript(index: Int) -> XNode? { 534 | return item(index: index) 535 | } 536 | subscript(name: String) -> XNode? { 537 | return getNamedItem(name: name) 538 | } 539 | } 540 | 541 | struct XNamedNodeMapAttr: XNamedNodeMap { 542 | let node: XNode 543 | 544 | var length: Int { 545 | var c = 0 546 | var n = node.nodePtr.pointee.properties 547 | while let _ = n { 548 | c += 1 549 | n = n?.pointee.next 550 | } 551 | return c 552 | } 553 | 554 | func getNamedItem(name: String) -> XNode? { 555 | var n = node.nodePtr.pointee.properties 556 | while let attr = n { 557 | defer { 558 | n = n?.pointee.next 559 | } 560 | guard let cname = attr.pointee.name else { 561 | continue 562 | } 563 | guard let nameTest = String(validatingUTF8: UnsafeRawPointer(cname).assumingMemoryBound(to: Int8.self)) else { 564 | continue 565 | } 566 | if nameTest == name { 567 | return node.asConcreteNode(UnsafeMutableRawPointer(attr).assumingMemoryBound(to: xmlNode.self)) 568 | } 569 | } 570 | return nil 571 | } 572 | 573 | func getNamedItemNS(namespaceURI: String, localName: String) -> XNode? { 574 | var n = node.nodePtr.pointee.properties 575 | while let attr = n { 576 | defer { 577 | n = n?.pointee.next 578 | } 579 | guard let cname = attr.pointee.name else { 580 | continue 581 | } 582 | guard let ns = attr.pointee.ns else { 583 | continue 584 | } 585 | guard let nameTest = String(validatingUTF8: UnsafeRawPointer(cname).assumingMemoryBound(to: Int8.self)), 586 | let href = ns.pointee.href, 587 | let nsNameTest = String(validatingUTF8: UnsafeRawPointer(href).assumingMemoryBound(to: Int8.self)) else { 588 | continue 589 | } 590 | 591 | if nameTest == localName && nsNameTest == namespaceURI { 592 | return node.asConcreteNode(UnsafeMutableRawPointer(attr).assumingMemoryBound(to: xmlNode.self)) 593 | } 594 | } 595 | return nil 596 | } 597 | 598 | func item(index: Int) -> XNode? { 599 | var c = index 600 | var n = node.nodePtr.pointee.properties 601 | while let attr = n { 602 | if c == 0 { 603 | return node.asConcreteNode(UnsafeMutableRawPointer(attr).assumingMemoryBound(to: xmlNode.self)) 604 | } 605 | c -= 1 606 | n = n?.pointee.next 607 | } 608 | return nil 609 | } 610 | } 611 | 612 | 613 | 614 | -------------------------------------------------------------------------------- /Sources/PerfectXML/XMLStream.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XMLStream.swift 3 | // PerfectXML 4 | // 5 | // Created by Kyle Jessup on 2018-03-12. 6 | // 7 | 8 | import Foundation 9 | import perfectxml2 10 | 11 | public protocol XMLStreamDataProvider { 12 | mutating func getData(maxCount: Int) throws -> Data? 13 | mutating func close() 14 | } 15 | 16 | public struct XMLStreamError: Error { 17 | public let description: String 18 | public init(_ d: String) { 19 | description = d 20 | } 21 | } 22 | 23 | extension String { 24 | init?(_ p: UnsafePointer?) { 25 | guard let n = p else { 26 | return nil 27 | } 28 | guard let s = n.withMemoryRebound(to: Int8.self, capacity: 0, { String(validatingUTF8: $0) }) else { 29 | return nil 30 | } 31 | self = s 32 | } 33 | init(_ p: UnsafePointer?, default: String) { 34 | guard let n = p else { 35 | self = `default` 36 | return 37 | } 38 | guard let s = n.withMemoryRebound(to: Int8.self, capacity: 0, { String(validatingUTF8: $0) }) else { 39 | self = `default` 40 | return 41 | } 42 | self = s 43 | } 44 | init(_ p: UnsafePointer?, count: Int, default: String) { 45 | guard let n = p else { 46 | self = `default` 47 | return 48 | } 49 | let a = (0.. UnsafeMutableRawPointer { 60 | return Unmanaged.passUnretained(a).toOpaque() 61 | } 62 | 63 | func fromContext(_ type: A.Type, _ context: UnsafeMutableRawPointer) -> A { 64 | return Unmanaged.fromOpaque(context).takeUnretainedValue() 65 | } 66 | 67 | func fromContext(_ type: A.Type, _ context: UnsafeMutableRawPointer?) -> A? { 68 | guard let context = context else { 69 | return nil 70 | } 71 | return Unmanaged.fromOpaque(context).takeUnretainedValue() 72 | } 73 | 74 | //xmlTextReaderGetParserColumnNumber 75 | 76 | public class XMLStream { 77 | public enum NodeType: Int { 78 | case none = 0, element, attribute, text, cdata, entityReference, 79 | entity, processingInstruction, comment, document, documentType, fragment, 80 | notation, whitespace, significantWhitespace, endElement, endEntity, xmlDeclaration 81 | } 82 | public struct NodeDescriptor { 83 | let readerPtr: xmlTextReaderPtr 84 | init(_ r: xmlTextReaderPtr) { 85 | readerPtr = r 86 | } 87 | } 88 | 89 | var dataProvider: XMLStreamDataProvider 90 | var readerPtr: xmlTextReaderPtr? 91 | public init(provider: XMLStreamDataProvider) { 92 | dataProvider = provider 93 | } 94 | deinit { 95 | if let r = readerPtr { 96 | xmlFreeTextReader(r) 97 | } 98 | } 99 | private func getReaderPtr() throws -> xmlTextReaderPtr { 100 | if let r = readerPtr { 101 | return r 102 | } 103 | let readCallback: xmlInputReadCallback = { 104 | context, buffer, bufferSize -> Int32 in 105 | guard let context = context, let buffer = buffer else { 106 | return -1 107 | } 108 | let me = fromContext(XMLStream.self, context) 109 | do { 110 | guard let data = try me.dataProvider.getData(maxCount: Int(bufferSize)) else { 111 | return 0 112 | } 113 | _ = data.withUnsafeBytes { 114 | memcpy(buffer, $0, data.count) 115 | } 116 | return Int32(data.count) 117 | } catch { 118 | return -1 119 | } 120 | } 121 | let closeCallback: xmlInputCloseCallback = { 122 | context in 123 | guard let context = context else { 124 | return -1 125 | } 126 | let me = fromContext(XMLStream.self, context) 127 | me.dataProvider.close() 128 | return 0 129 | } 130 | guard let reader = xmlReaderForIO(readCallback, 131 | closeCallback, 132 | asContext(self), 133 | "/", 134 | "utf8", 135 | Int32(XML_PARSE_NONET.rawValue | XML_PARSE_NOCDATA.rawValue | XML_PARSER_SUBST_ENTITIES.rawValue)) else { 136 | throw XMLStreamError("Unable to allocate XML reader.") 137 | } 138 | readerPtr = reader 139 | return reader 140 | } 141 | 142 | public func next() throws -> NodeDescriptor? { 143 | let reader = try getReaderPtr() 144 | let readRes = xmlTextReaderRead(reader) 145 | switch readRes { 146 | case 0: 147 | return nil 148 | case -1: 149 | throw XMLStreamError("Error calling xmlTextReaderRead.") 150 | default: 151 | return NodeDescriptor(reader) 152 | } 153 | } 154 | public func nextSibling() throws -> NodeDescriptor? { 155 | let reader = try getReaderPtr() 156 | let readRes = xmlTextReaderNext(reader) 157 | switch readRes { 158 | case 0: 159 | return nil 160 | case -1: 161 | throw XMLStreamError("Error calling xmlTextReaderNext.") 162 | default: 163 | return NodeDescriptor(reader) 164 | } 165 | } 166 | public func namespaceURI(prefix: String) -> String? { 167 | return String(xmlTextReaderLookupNamespace(readerPtr, prefix)) 168 | } 169 | } 170 | 171 | public extension XMLStream.NodeDescriptor { 172 | var type: XMLStream.NodeType? { 173 | return XMLStream.NodeType(rawValue: Int(xmlTextReaderNodeType(readerPtr))) 174 | } 175 | var localName: String? { 176 | return String(xmlTextReaderConstLocalName(readerPtr)) 177 | } 178 | var name: String? { 179 | return String(xmlTextReaderConstName(readerPtr)) 180 | } 181 | var namespaceURI: String? { 182 | return String(xmlTextReaderConstNamespaceUri(readerPtr)) 183 | } 184 | var prefix: String? { 185 | return String(xmlTextReaderConstPrefix(readerPtr)) 186 | } 187 | var value: String? { 188 | return String(xmlTextReaderConstValue(readerPtr)) 189 | } 190 | var content: String? { 191 | guard let n = xmlTextReaderReadString(readerPtr) else { 192 | return nil 193 | } 194 | defer { 195 | xmlFree(n) 196 | } 197 | return n.withMemoryRebound(to: Int8.self, capacity: 0) { 198 | return String(validatingUTF8: $0) 199 | } 200 | } 201 | var isEmpty: Bool { 202 | return xmlTextReaderIsEmptyElement(readerPtr) == 1 203 | } 204 | var attributeCount: Int { 205 | return Int(xmlTextReaderAttributeCount(readerPtr)) 206 | } 207 | var depth: Int { 208 | return Int(xmlTextReaderDepth(readerPtr)) 209 | } 210 | } 211 | 212 | public extension XMLStream.NodeDescriptor { 213 | func getAttribute(_ name: String, namespaceURI: String? = nil) -> String? { 214 | if let ns = namespaceURI { 215 | return String(xmlTextReaderGetAttributeNs(readerPtr, name, ns)) 216 | } 217 | return String(xmlTextReaderGetAttribute(readerPtr, name)) 218 | } 219 | } 220 | 221 | 222 | 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /Sources/PerfectXML/XPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PerfectXPath.swift 3 | // PerfectXML 4 | // 5 | // Created by Kyle Jessup on 2016-07-21. 6 | // 7 | // 8 | 9 | import perfectxml2 10 | 11 | /// An XPath result object type. 12 | public enum XPathObject { 13 | case none 14 | case nodeSet([XNode]) 15 | case boolean(Bool) 16 | case number(Double) 17 | case string(String) 18 | case invalidExpression 19 | } 20 | 21 | /// XPath related functions. 22 | public extension XNode { 23 | 24 | private var xPathTargetNode: xmlNodePtr { 25 | if case .documentNode = self.nodeType { 26 | return xmlDocGetRootElement(fromNodePtr(nodePtr)) 27 | } 28 | return nodePtr 29 | } 30 | 31 | private func initializeContext() -> xmlXPathContextPtr! { 32 | let targetNode = xPathTargetNode 33 | guard let ctx = xmlXPathNewContext(targetNode.pointee.doc) else { 34 | return nil 35 | } 36 | ctx.pointee.node = targetNode 37 | return ctx 38 | } 39 | 40 | private func translateXPath(result: xmlXPathObjectPtr) -> XPathObject { 41 | switch result.pointee.type { 42 | case XPATH_BOOLEAN: 43 | return .boolean(1 == xmlXPathCastToBoolean(result)) 44 | case XPATH_NUMBER: 45 | return .number(xmlXPathCastToNumber(result)) 46 | case XPATH_NODESET: 47 | var ary = [XNode]() 48 | guard let nodeSet = result.pointee.nodesetval else { 49 | return .nodeSet(ary) 50 | } 51 | for index in 0.. XPathObject { 85 | guard let ctx = initializeContext() else { 86 | return .none 87 | } 88 | defer { 89 | xmlXPathFreeContext(ctx) 90 | } 91 | 92 | let errorTracker = XErrorTracker() 93 | ctx.pointee.userData = Unmanaged.passUnretained(errorTracker).toOpaque() 94 | ctx.pointee.error = { 95 | userData, error in 96 | guard let userData = userData else { 97 | return 98 | } 99 | let errorTracker: XErrorTracker = Unmanaged.fromOpaque(userData).takeUnretainedValue() 100 | 101 | print("help") 102 | } 103 | 104 | for (prefix, uri) in namespaces { 105 | xmlXPathRegisterNs(ctx, prefix, uri) 106 | } 107 | 108 | if let result = xmlXPathEval(path, ctx) { 109 | defer { 110 | xmlXPathFreeObject(result) 111 | } 112 | return translateXPath(result: result) 113 | } 114 | return .none 115 | } 116 | /// Execute the XPath and return a single resul tnode or nil. 117 | /// Accepts and array of tuples holding namespace prefixes and uris. 118 | public func extractOne(path: String, namespaces: [(String, String)] = [(String, String)]()) -> XNode? { 119 | guard case .nodeSet(let nodes) = extract(path: path, namespaces: namespaces) else { 120 | return nil 121 | } 122 | return nodes.first 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PerfectXMLTests 3 | 4 | XCTMain([ 5 | testCase(PerfectXMLTests.allTests), 6 | ]) 7 | -------------------------------------------------------------------------------- /Tests/PerfectXMLTests/PerfectXMLTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PerfectXML 3 | 4 | class PerfectXMLTests: XCTestCase { 5 | 6 | func testDocParse1() { 7 | let docSrc = "\nHI\n" 8 | let doc = XDocument(fromSource: docSrc) 9 | let str = doc?.string(pretty: false) 10 | XCTAssert(str == docSrc, "\(String(describing: str))") 11 | } 12 | 13 | func testHTMLParse1() { 14 | let docSrc = "\n\ntitle\n\n
hi
\n\n\n" 15 | let doc = HTMLDocument(fromSource: docSrc) 16 | let nodeName = doc?.documentElement?.nodeName 17 | XCTAssert(nodeName == "html") 18 | } 19 | 20 | func testNodeName1() { 21 | let docSrc = "\n\n" 22 | let doc = XDocument(fromSource: docSrc) 23 | 24 | XCTAssert(doc?.nodeName == "#document") 25 | 26 | guard let children = doc?.documentElement else { 27 | return XCTAssert(false, "No children") 28 | } 29 | XCTAssert(children.nodeName == "a") 30 | let names = ["b", "c", "d"] 31 | for (n, v) in zip(children.childNodes, names) { 32 | guard let _ = n as? XElement else { 33 | return XCTAssert(false) 34 | } 35 | XCTAssert(n.nodeName == v, "\(n.nodeName) != \(v)") 36 | } 37 | } 38 | 39 | func testText1() { 40 | let value = "ABCD" 41 | let docSrc = "\n\(value)\n" 42 | let doc = XDocument(fromSource: docSrc) 43 | XCTAssert(doc?.nodeName == "#document") 44 | guard let children = doc?.documentElement else { 45 | return XCTAssert(false, "No children") 46 | } 47 | XCTAssert(children.nodeName == "a") 48 | do { 49 | let children = children.childNodes 50 | XCTAssert(children.count == 1) 51 | guard let textChild = children.first as? XText else { 52 | return XCTAssert(false) 53 | } 54 | XCTAssert(textChild.nodeValue == value) 55 | } 56 | } 57 | 58 | func testNodeValue1() { 59 | let value = "ABCD" 60 | let docSrc = "\n\(value)\n" 61 | let doc = XDocument(fromSource: docSrc) 62 | XCTAssert(doc?.nodeName == "#document") 63 | guard let children = doc?.documentElement else { 64 | return XCTAssert(false, "No children") 65 | } 66 | XCTAssert(children.nodeName == "a") 67 | do { 68 | let children = children.childNodes 69 | XCTAssert(children.count == 1) 70 | guard let text = children.first?.nodeValue else { 71 | return XCTAssert(false) 72 | } 73 | XCTAssert(text == value) 74 | } 75 | } 76 | 77 | func testNodeType1() { 78 | let value = "ABCD" 79 | let docSrc = "\n\(value)\n" 80 | let doc = XDocument(fromSource: docSrc) 81 | XCTAssert(doc?.nodeName == "#document") 82 | guard let children = doc?.documentElement else { 83 | return XCTAssert(false, "No children") 84 | } 85 | XCTAssert(children.nodeName == "a") 86 | let nodeType = children.nodeType 87 | if case .elementNode = nodeType { 88 | 89 | } else { 90 | XCTAssert(false, "\(nodeType)") 91 | } 92 | } 93 | 94 | func testFirstLastChild1() { 95 | let docSrc = "\n\n" 96 | let doc = XDocument(fromSource: docSrc) 97 | XCTAssert(doc?.nodeName == "#document") 98 | guard let children = doc?.documentElement else { 99 | return XCTAssert(false, "No children") 100 | } 101 | XCTAssert(children.nodeName == "a") 102 | 103 | guard let firstChild = children.firstChild else { 104 | return XCTAssert(false) 105 | } 106 | guard let lastChild = children.lastChild else { 107 | return XCTAssert(false) 108 | } 109 | XCTAssert(firstChild.nodeName == "b") 110 | XCTAssert(lastChild.nodeName == "d") 111 | } 112 | 113 | func testPrevNextSibling1() { 114 | let docSrc = "\n\n" 115 | let doc = XDocument(fromSource: docSrc) 116 | XCTAssert(doc?.nodeName == "#document") 117 | guard let children = doc?.documentElement else { 118 | return XCTAssert(false, "No children") 119 | } 120 | XCTAssert(children.nodeName == "a") 121 | 122 | guard let firstChild = children.firstChild else { 123 | return XCTAssert(false) 124 | } 125 | XCTAssert(firstChild.nodeName == "b") 126 | 127 | guard let nextSib = firstChild.nextSibling else { 128 | return XCTAssert(false) 129 | } 130 | guard let prevSib = nextSib.previousSibling else { 131 | return XCTAssert(false) 132 | } 133 | XCTAssert(nextSib.nodeName == "c") 134 | XCTAssert(prevSib.nodeName == "b") 135 | } 136 | 137 | func testAttributes1() { 138 | let names = ["atr1", "atr2"] 139 | let docSrc = "\n\n" 140 | let doc = XDocument(fromSource: docSrc) 141 | XCTAssert(doc?.nodeName == "#document") 142 | guard let children = doc?.documentElement else { 143 | return XCTAssert(false, "No children") 144 | } 145 | XCTAssert(children.nodeName == "a") 146 | 147 | guard let firstChild = children.firstChild else { 148 | return XCTAssert(false) 149 | } 150 | XCTAssert(firstChild.nodeName == "b") 151 | guard let attrs = firstChild.attributes else { 152 | return XCTAssert(false, "nil attributes") 153 | } 154 | XCTAssert(attrs.length == 2) 155 | for index in 0..\n\n" 171 | let doc = XDocument(fromSource: docSrc) 172 | XCTAssert(doc?.nodeName == "#document") 173 | guard let children = doc?.documentElement else { 174 | return XCTAssert(false, "No children") 175 | } 176 | XCTAssert(children.nodeName == "a") 177 | 178 | guard let firstChild = children.firstChild as? XElement else { 179 | return XCTAssert(false) 180 | } 181 | XCTAssert(firstChild.nodeName == "b") 182 | guard let atr1 = firstChild.getAttribute(name: "atr1") else { 183 | return XCTAssert(false) 184 | } 185 | XCTAssert(atr1 == "the value") 186 | guard let atr2 = firstChild.getAttributeNode(name: "atr2") else { 187 | return XCTAssert(false) 188 | } 189 | XCTAssert(atr2.value == "the other value") 190 | } 191 | 192 | func testAttributes3() { 193 | let names = ["atr1", "atr2"] 194 | let docSrc = "\n\n" 195 | let doc = XDocument(fromSource: docSrc) 196 | XCTAssert(doc?.nodeName == "#document") 197 | guard let children = doc?.documentElement else { 198 | return XCTAssert(false, "No children") 199 | } 200 | XCTAssert(children.nodeName == "a") 201 | 202 | guard let firstChild = children.firstChild else { 203 | return XCTAssert(false) 204 | } 205 | XCTAssert(firstChild.nodeName == "b") 206 | guard let attrs = firstChild.attributes else { 207 | return XCTAssert(false, "nil attributes") 208 | } 209 | XCTAssert(attrs.length == 2) 210 | for name in names { 211 | guard let item = attrs.getNamedItemNS(namespaceURI: "foo:bar", localName: name) else { 212 | return XCTAssert(false) 213 | } 214 | XCTAssert(item.nodeName == name) 215 | } 216 | } 217 | 218 | func testAttributes4() { 219 | let docSrc = "\n\n" 220 | let doc = XDocument(fromSource: docSrc) 221 | XCTAssert(doc?.nodeName == "#document") 222 | guard let children = doc?.documentElement else { 223 | return XCTAssert(false, "No children") 224 | } 225 | XCTAssert(children.nodeName == "a") 226 | guard let firstChild = children.firstChild as? XElement else { 227 | return XCTAssert(false) 228 | } 229 | XCTAssert(firstChild.nodeName == "b") 230 | guard let atr2 = firstChild.getAttributeNodeNS(namespaceURI: "foo:bar", localName: "atr2") else { 231 | return XCTAssert(false) 232 | } 233 | XCTAssert(atr2.value == "the other value") 234 | XCTAssert(firstChild.hasAttributeNS(namespaceURI: "foo:bar", localName: "atr2")) 235 | XCTAssert(firstChild.hasAttribute(name: "atr1")) 236 | XCTAssert(!firstChild.hasAttributeNS(namespaceURI: "foo:bar", localName: "atr1")) 237 | XCTAssert(!firstChild.hasAttribute(name: "atr3")) 238 | } 239 | 240 | func testDocElementByName1() { 241 | let docSrc = "\n\n" 242 | let doc = XDocument(fromSource: docSrc) 243 | XCTAssert(doc?.nodeName == "#document") 244 | 245 | guard let elements = doc?.getElementsByTagName("b") else { 246 | return XCTAssert(false) 247 | } 248 | XCTAssert(elements.count == 3) 249 | for node in elements { 250 | XCTAssert(node.nodeName == "b") 251 | } 252 | } 253 | 254 | func testDocElementByName2() { 255 | let docSrc = "\n\n" 256 | let doc = XDocument(fromSource: docSrc) 257 | XCTAssert(doc?.nodeName == "#document") 258 | 259 | guard let elements = doc?.documentElement?.getElementsByTagName("b") else { 260 | return XCTAssert(false) 261 | } 262 | XCTAssert(elements.count == 3) 263 | for node in elements { 264 | XCTAssert(node.nodeName == "b") 265 | } 266 | } 267 | 268 | func testDocElementByName3() { 269 | let docSrc = "\nFOO\n" 270 | let doc = XDocument(fromSource: docSrc) 271 | XCTAssert(doc?.nodeName == "#document") 272 | 273 | do { 274 | guard let elements = doc?.getElementsByTagName("a") else { 275 | return XCTAssert(false) 276 | } 277 | XCTAssert(elements.count == 2) 278 | for node in elements { 279 | XCTAssert(node.nodeName == "a") 280 | } 281 | } 282 | 283 | do { 284 | guard let elements = doc?.documentElement?.getElementsByTagName("a") else { 285 | return XCTAssert(false) 286 | } 287 | XCTAssert(elements.count == 1) 288 | for node in elements { 289 | XCTAssert(node.nodeName == "a") 290 | } 291 | } 292 | } 293 | 294 | func testDocElementByName4() { 295 | let docSrc = "\nFOO\n" 296 | let doc = XDocument(fromSource: docSrc) 297 | XCTAssert(doc?.nodeName == "#document") 298 | 299 | do { 300 | guard let elements = doc?.getElementsByTagNameNS(namespaceURI: "foo:bar", localName: "a") else { 301 | return XCTAssert(false) 302 | } 303 | XCTAssert(elements.count == 1) 304 | for node in elements { 305 | XCTAssert(node.nodeName == "a") 306 | XCTAssert(node.localName == "a") 307 | XCTAssert(node.prefix == "foo") 308 | XCTAssert(node.namespaceURI == "foo:bar") 309 | } 310 | } 311 | 312 | do { 313 | guard let elements = doc?.documentElement?.getElementsByTagNameNS(namespaceURI: "foo:bar", localName: "a") else { 314 | return XCTAssert(false) 315 | } 316 | XCTAssert(elements.count == 1) 317 | for node in elements { 318 | XCTAssert(node.nodeName == "a") 319 | XCTAssert(node.localName == "a") 320 | XCTAssert(node.prefix == "foo") 321 | XCTAssert(node.namespaceURI == "foo:bar") 322 | } 323 | } 324 | 325 | do { 326 | guard let elements = doc?.getElementsByTagNameNS(namespaceURI: "foo:barz", localName: "a") else { 327 | return XCTAssert(false) 328 | } 329 | XCTAssert(elements.count == 0) 330 | } 331 | 332 | do { 333 | guard let elements = doc?.documentElement?.getElementsByTagNameNS(namespaceURI: "foo:barz", localName: "a") else { 334 | return XCTAssert(false) 335 | } 336 | XCTAssert(elements.count == 0) 337 | } 338 | } 339 | 340 | func testDocElementById1() { 341 | let docSrc = "\nFOO\n" 342 | let doc = XDocument(fromSource: docSrc) 343 | XCTAssert(doc?.nodeName == "#document") 344 | guard let element = doc?.getElementById("foo") else { 345 | return XCTAssert(false) 346 | } 347 | XCTAssert(element.tagName == "b") 348 | } 349 | 350 | func testXPath1() { 351 | let docSrc = "\nFOO\n" 352 | guard let doc = XDocument(fromSource: docSrc) else { 353 | return XCTAssert(false) 354 | } 355 | XCTAssert(doc.nodeName == "#document") 356 | 357 | let pathRes = doc.extract(path: "/a/b") 358 | guard case .nodeSet(let set) = pathRes else { 359 | return XCTAssert(false, "\(pathRes)") 360 | } 361 | for node in set { 362 | guard let b = node as? XElement else { 363 | return XCTAssert(false, "\(node)") 364 | } 365 | XCTAssert(b.tagName == "b") 366 | } 367 | } 368 | 369 | func testXPath2() { 370 | let docSrc = "\nFOO\n" 371 | guard let doc = XDocument(fromSource: docSrc) else { 372 | return XCTAssert(false) 373 | } 374 | XCTAssert(doc.nodeName == "#document") 375 | 376 | let pathRes = doc.extract(path: "/a/b/@id") 377 | guard case .nodeSet(let set) = pathRes else { 378 | return XCTAssert(false, "\(pathRes)") 379 | } 380 | for node in set { 381 | guard let b = node as? XAttr else { 382 | return XCTAssert(false, "\(node)") 383 | } 384 | XCTAssert(b.name == "id") 385 | XCTAssert(b.value == "foo") 386 | } 387 | } 388 | 389 | func testXPath3() { 390 | let docSrc = "\nFOO\n" 391 | guard let doc = XDocument(fromSource: docSrc) else { 392 | return XCTAssert(false) 393 | } 394 | XCTAssert(doc.nodeName == "#document") 395 | 396 | let pathRes = doc.extract(path: "/a/a/b/text()") 397 | guard case .nodeSet(let set) = pathRes else { 398 | return XCTAssert(false, "\(pathRes)") 399 | } 400 | for node in set { 401 | guard let b = node as? XText else { 402 | return XCTAssert(false, "\(node)") 403 | } 404 | guard let nodeValue = b.nodeValue else { 405 | return XCTAssert(false, "\(b)") 406 | } 407 | XCTAssert(nodeValue == "FOO") 408 | } 409 | } 410 | 411 | func testXPath4() { 412 | let docSrc = "\nFOO\n" 413 | guard let doc = XDocument(fromSource: docSrc) else { 414 | return XCTAssert(false) 415 | } 416 | XCTAssert(doc.nodeName == "#document") 417 | guard let node = doc.extractOne(path: "/a/a/b/text()") else { 418 | return XCTAssert(false, "no result") 419 | } 420 | guard let b = node as? XText else { 421 | return XCTAssert(false, "\(node)") 422 | } 423 | guard let nodeValue = b.nodeValue else { 424 | return XCTAssert(false, "\(b)") 425 | } 426 | XCTAssert(nodeValue == "FOO") 427 | } 428 | 429 | func testXPath5() { 430 | let docSrc = "\nFOO\n" 431 | guard let doc = XDocument(fromSource: docSrc) else { 432 | return XCTAssert(false) 433 | } 434 | let namespaces = [("f", "foo:bar")] 435 | let pathRes = doc.extract(path: "/a/f:a", namespaces: namespaces) 436 | guard case .nodeSet(let set) = pathRes else { 437 | return XCTAssert(false, "\(pathRes)") 438 | } 439 | for node in set { 440 | guard let e = node as? XElement else { 441 | return XCTAssert(false, "\(node)") 442 | } 443 | XCTAssert(e.tagName == "a") 444 | XCTAssert(e.namespaceURI == "foo:bar") 445 | XCTAssert(e.prefix == "foo") 446 | } 447 | } 448 | 449 | // func testXMLEncode() { 450 | // struct ChildType: Codable { 451 | // 452 | // } 453 | // struct DocumentType: Codable { 454 | // let id: Int 455 | // let str: String 456 | // let double: Double 457 | // } 458 | // do { 459 | // let doc = DocumentType(id: 42, str: "This is the string & stuff.", double: 42.3) 460 | // let data = try XMLEncoder().encode(doc, rootName: "document") 461 | // guard let str = String(data: data, encoding: .utf8) else { 462 | // return XCTFail("Bad data from encoding.") 463 | // } 464 | // let testAgainst = "42This is the string & stuff." 465 | // XCTAssertEqual(str, testAgainst) 466 | // } catch { 467 | // XCTFail("\(error)") 468 | // } 469 | // } 470 | 471 | func testXMLStream() { 472 | struct ChunkyProvider: XMLStreamDataProvider { 473 | let source: [UInt8] 474 | let maxReturn = 4 475 | var offset: Int = 0 476 | init(_ s: String) { 477 | source = Array(s.utf8) 478 | } 479 | mutating func getData(maxCount: Int) throws -> Data? { 480 | let remaining = source.count - offset 481 | guard remaining > 0 else { 482 | return nil 483 | } 484 | let a = source[offset..<(offset + min(maxReturn, min(remaining, maxCount)))] 485 | offset += a.count 486 | return Data(bytes: a) 487 | } 488 | func close() { 489 | print("ChunkyProvider.close()") 490 | } 491 | } 492 | do { 493 | let provider = ChunkyProvider("CONTENT") 494 | let stream = XMLStream(provider: provider) 495 | 496 | let checks: [(XMLStream.NodeType, String, String?, Bool, Int, String?)] = [ 497 | (.element, "A", nil, false, 0, nil), 498 | (.element, "B", nil, false, 1, "value"), 499 | (.text, "#text", "CONTENT", false, 0, nil), 500 | (.endElement, "B", nil, false, 0, "value"), 501 | (.element, "C", nil, true, 0, nil), 502 | (.element, "D", nil, false, 0, nil), 503 | (.element, "E", nil, true, 0, nil), 504 | (.endElement, "D", nil, false, 0, nil), 505 | (.endElement, "A", nil, false, 0, nil), 506 | ] 507 | 508 | for check in checks { 509 | guard let item = try stream.next() else { 510 | return XCTFail("No item") 511 | } 512 | XCTAssertEqual(item.type!, check.0) 513 | XCTAssertEqual(item.localName, check.1) 514 | XCTAssertEqual(item.value, check.2) 515 | XCTAssertEqual(item.isEmpty, check.3) 516 | XCTAssertEqual(item.attributeCount, check.4) 517 | XCTAssertEqual(item.getAttribute("a"), check.5) 518 | } 519 | } catch { 520 | XCTFail("\(error)") 521 | } 522 | } 523 | 524 | func testSAX() { 525 | do { 526 | class TestDelegate: SAXDelegate { 527 | var opens = ["A", "B", "C", "D", "E"] 528 | var closes = ["B", "C", "E", "D", "A"] 529 | 530 | func startElementNs(localName: String, prefix: String?, uri: String?, namespaces: [SAXDelegateNamespace], attributes: [SAXDelegateAttribute]) { 531 | XCTAssertEqual(opens.removeFirst(), localName) 532 | if localName == "B" { 533 | XCTAssertEqual("a", attributes.first?.localName) 534 | } 535 | } 536 | func endElementNs(localName: String, prefix: String?, uri: String?) { 537 | XCTAssertEqual(closes.removeFirst(), localName) 538 | } 539 | } 540 | let d = TestDelegate() 541 | let sax = SAXParser(delegate: d) 542 | let bytes = Array("CONTENT".utf8) 543 | for n in stride(from: 0, to: bytes.count, by: 4) { 544 | let upper = min(n+4, bytes.count) 545 | let range = bytes[n.. () throws -> Void)] { 557 | return [ 558 | ("testDocParse1", testDocParse1), 559 | ("testNodeName1", testNodeName1), 560 | ("testText1", testText1), 561 | ("testNodeValue1", testNodeValue1), 562 | ("testNodeType1", testNodeType1), 563 | ("testFirstLastChild1", testFirstLastChild1), 564 | ("testPrevNextSibling1", testPrevNextSibling1), 565 | ("testAttributes1", testAttributes1), 566 | ("testAttributes2", testAttributes2), 567 | ("testAttributes3", testAttributes3), 568 | ("testAttributes4", testAttributes4), 569 | ("testDocElementByName1", testDocElementByName1), 570 | ("testDocElementByName2", testDocElementByName2), 571 | ("testDocElementByName3", testDocElementByName3), 572 | ("testDocElementByName4", testDocElementByName4), 573 | ("testDocElementById1", testDocElementById1), 574 | ("testXPath1", testXPath1), 575 | ("testXPath2", testXPath2), 576 | ("testXPath3", testXPath3), 577 | ("testXPath4", testXPath4), 578 | ("testXPath5", testXPath5), 579 | ("testXMLStream", testXMLStream) 580 | 581 | ] 582 | } 583 | } 584 | --------------------------------------------------------------------------------