├── .gitignore ├── .swift-version ├── LICENSE ├── Package.swift ├── README.md ├── Sample XML ├── Books │ ├── Books.swift │ ├── book.xml │ └── books.xml ├── BreakfastMenu │ ├── breakfast.swift │ └── breakfast.xml ├── CDs │ ├── CDCatalog.swift │ └── cd_catalog.xml ├── Notes │ ├── Notes.swift │ ├── note.xml │ └── note_error.xml ├── Plants │ ├── PlantCatalog.swift │ └── plant_catalog.xml └── RJI │ ├── RJI.swift │ └── RJI_RSS_Sample.xml ├── Sources └── XMLParsing │ ├── Decoder │ ├── DecodingErrorExtension.swift │ ├── XMLDecoder.swift │ ├── XMLDecodingStorage.swift │ ├── XMLKeyedDecodingContainer.swift │ └── XMLUnkeyedDecodingContainer.swift │ ├── Encoder │ ├── EncodingErrorExtension.swift │ ├── XMLEncoder.swift │ ├── XMLEncodingStorage.swift │ └── XMLReferencingEncoder.swift │ ├── ISO8601DateFormatter.swift │ ├── XMLKey.swift │ └── XMLStackParser.swift ├── Tests ├── LinuxMain.swift └── XMLParsingTests │ └── XMLParsingTests.swift ├── XMLParsing.podspec └── XMLParsing.xcodeproj ├── XMLParsingTests_Info.plist ├── XMLParsing_Info.plist ├── project.pbxproj ├── project.xcworkspace ├── contents.xcworkspacedata └── xcuserdata │ └── wilson.xcuserdatad │ └── UserInterfaceState.xcuserstate ├── xcshareddata └── xcschemes │ ├── XMLParsing-Package.xcscheme │ └── xcschememanagement.plist └── xcuserdata └── wilson.xcuserdatad └── xcschemes └── xcschememanagement.plist /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.2 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shawn Moore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "XMLParsing", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "XMLParsing", 12 | targets: ["XMLParsing"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "XMLParsing", 23 | dependencies: []), 24 | .testTarget( 25 | name: "XMLParsingTests", 26 | dependencies: ["XMLParsing"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XMLParsing 2 | Encoder & Decoder for XML using Swift's _codable_ protocol. 3 | 4 | ## Installation 5 | 6 | ### CocoaPods 7 | 8 | [CocoaPods](https://cocoapods.org) is a dependency manager for Swift and Objective-C Cocoa projects. You can install it with the following command: 9 | 10 | ```bash 11 | $ gem install cocoapods 12 | ``` 13 | 14 | Navigate to the project directory and create a _podfile_ with the following command: 15 | 16 | ```bash 17 | $ pod install 18 | ``` 19 | 20 | Inside of your `Podfile`, specify the XMLParsing pod: 21 | 22 | ```ruby 23 | # Uncomment the next line to define a global platform for your project 24 | # platform :ios, '9.0' 25 | 26 | target 'YourApp' do 27 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks 28 | use_frameworks! 29 | 30 | # Pods for Test 31 | pod 'XMLParsing', :git => 'https://github.com/ShawnMoore/XMLParsing.git' 32 | 33 | end 34 | ``` 35 | 36 | Then, run the following command: 37 | 38 | ```bash 39 | $ pod install 40 | ``` 41 | 42 | Open the the _xcworkspace_ file that was created. This should be the file you use everyday to create your app, instead of the _xcodeproj_ file. 43 | 44 | ### Carthage 45 | 46 | [Carthage](https://github.com/Carthage/Carthage) is a dependency manager that builds your dependencies and provides you with binary frameworks. 47 | 48 | Carthage can be installed with [Homebrew](https://brew.sh/) using the following command: 49 | 50 | ```bash 51 | $ brew update 52 | $ brew install carthage 53 | ``` 54 | 55 | Inside of your `Cartfile`, specify XMLParsing: 56 | 57 | ```ogdl 58 | github "ShawmMoore/XMLParsing" 59 | ``` 60 | 61 | Then, run the following command to build the framework: 62 | 63 | ```bash 64 | $ carthage update 65 | ``` 66 | 67 | Drag the built framework into your Xcode project. 68 | 69 | ### Swift Package Manager 70 | 71 | [Swift Package Manager](https://swift.org/package-manager/) is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies. 72 | 73 | Once you have your Swift package set up, adding XMLParsing as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`. 74 | 75 | ```swift 76 | dependencies: [ 77 | .package(url: "https://github.com/ShawnMoore/XMLParsing.git", from: "0.0.3") 78 | ] 79 | ``` 80 | 81 | ## Example 82 | 83 | ```swift 84 | import XMLParsing 85 | 86 | let xmlStr = """ 87 | 88 | Bob 89 | Jane 90 | Reminder 91 | Don't forget to use XMLParsing! 92 | 93 | """ 94 | 95 | struct Note: Codable { 96 | var to: String 97 | var from: String 98 | var heading: String 99 | var body: String 100 | } 101 | 102 | guard let data = xmlStr.data(using: .utf8) else { return } 103 | 104 | let note = try? XMLDecoder().decode(Note.self, from: data) 105 | 106 | let returnData = try? XMLEncoder().encode(note, withRootKey: "note") 107 | ``` 108 | -------------------------------------------------------------------------------- /Sample XML/Books/Books.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Books.swift 3 | // XMLParsing 4 | // 5 | // Created by Shawn Moore on 11/15/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Catalog: Codable { 12 | var books: [Book] 13 | 14 | enum CodingKeys: String, CodingKey { 15 | case books = "book" 16 | } 17 | } 18 | 19 | struct Book: Codable { 20 | var id: String 21 | var author: String 22 | var title: String 23 | var genre: Genre 24 | var price: Double 25 | var publishDate: Date 26 | var description: String 27 | 28 | enum CodingKeys: String, CodingKey { 29 | case id, author, title, genre, price, description 30 | 31 | case publishDate = "publish_date" 32 | } 33 | } 34 | 35 | enum Genre: String, Codable { 36 | case computer = "Computer" 37 | case fantasy = "Fantasy" 38 | case romance = "Romance" 39 | case horror = "Horror" 40 | case sciFi = "Science Fiction" 41 | } 42 | 43 | // TEST FUNCTIONS 44 | extension Book { 45 | static func retrieveBook() -> Book? { 46 | guard let data = Data(forResource: "book", withExtension: "xml") else { return nil } 47 | 48 | let decoder = XMLDecoder() 49 | 50 | let formatter: DateFormatter = { 51 | let formatter = DateFormatter() 52 | formatter.dateFormat = "yyyy-MM-dd" 53 | return formatter 54 | }() 55 | 56 | decoder.dateDecodingStrategy = .formatted(formatter) 57 | 58 | let book: Book? 59 | 60 | do { 61 | book = try decoder.decode(Book.self, from: data) 62 | } catch { 63 | print(error) 64 | 65 | book = nil 66 | } 67 | 68 | return book 69 | } 70 | 71 | func toXML() -> String? { 72 | let encoder = XMLEncoder() 73 | 74 | let formatter: DateFormatter = { 75 | let formatter = DateFormatter() 76 | formatter.dateFormat = "yyyy-MM-dd" 77 | return formatter 78 | }() 79 | 80 | encoder.dateEncodingStrategy = .formatted(formatter) 81 | 82 | do { 83 | let data = try encoder.encode(self, withRootKey: "book", header: XMLHeader(version: 1.0)) 84 | 85 | return String(data: data, encoding: .utf8) 86 | } catch { 87 | print(error) 88 | 89 | return nil 90 | } 91 | } 92 | } 93 | 94 | extension Catalog { 95 | static func retrieveLibrary() -> Catalog? { 96 | guard let data = Data(forResource: "books", withExtension: "xml") else { return nil } 97 | 98 | let decoder = XMLDecoder() 99 | 100 | let formatter: DateFormatter = { 101 | let formatter = DateFormatter() 102 | formatter.dateFormat = "yyyy-MM-dd" 103 | return formatter 104 | }() 105 | 106 | decoder.dateDecodingStrategy = .formatted(formatter) 107 | 108 | let catalog: Catalog? 109 | 110 | do { 111 | catalog = try decoder.decode(Catalog.self, from: data) 112 | } catch { 113 | print(error) 114 | 115 | catalog = nil 116 | } 117 | 118 | return catalog 119 | } 120 | 121 | func toXML() -> String? { 122 | let encoder = XMLEncoder() 123 | 124 | let formatter: DateFormatter = { 125 | let formatter = DateFormatter() 126 | formatter.dateFormat = "yyyy-MM-dd" 127 | return formatter 128 | }() 129 | 130 | encoder.dateEncodingStrategy = .formatted(formatter) 131 | 132 | do { 133 | let data = try encoder.encode(self, withRootKey: "catalog", header: XMLHeader(version: 1.0)) 134 | 135 | return String(data: data, encoding: .utf8) 136 | } catch { 137 | print(error) 138 | 139 | return nil 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Sample XML/Books/book.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Gambardella, Matthew 4 | XML Developer's Guide 5 | Computer 6 | 44.95 7 | 2000-10-01 8 | An in-depth look at creating applications 9 | with XML. 10 | 11 | -------------------------------------------------------------------------------- /Sample XML/Books/books.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Gambardella, Matthew 5 | XML Developer's Guide 6 | Computer 7 | 44.95 8 | 2000-10-01 9 | An in-depth look at creating applications 10 | with XML. 11 | 12 | 13 | Ralls, Kim 14 | Midnight Rain 15 | Fantasy 16 | 5.95 17 | 2000-12-16 18 | A former architect battles corporate zombies, 19 | an evil sorceress, and her own childhood to become queen 20 | of the world. 21 | 22 | 23 | Corets, Eva 24 | Maeve Ascendant 25 | Fantasy 26 | 5.95 27 | 2000-11-17 28 | After the collapse of a nanotechnology 29 | society in England, the young survivors lay the 30 | foundation for a new society. 31 | 32 | 33 | Corets, Eva 34 | Oberon's Legacy 35 | Fantasy 36 | 5.95 37 | 2001-03-10 38 | In post-apocalypse England, the mysterious 39 | agent known only as Oberon helps to create a new life 40 | for the inhabitants of London. Sequel to Maeve 41 | Ascendant. 42 | 43 | 44 | Corets, Eva 45 | The Sundered Grail 46 | Fantasy 47 | 5.95 48 | 2001-09-10 49 | The two daughters of Maeve, half-sisters, 50 | battle one another for control of England. Sequel to 51 | Oberon's Legacy. 52 | 53 | 54 | Randall, Cynthia 55 | Lover Birds 56 | Romance 57 | 4.95 58 | 2000-09-02 59 | When Carla meets Paul at an ornithology 60 | conference, tempers fly as feathers get ruffled. 61 | 62 | 63 | Thurman, Paula 64 | Splish Splash 65 | Romance 66 | 4.95 67 | 2000-11-02 68 | A deep sea diver finds true love twenty 69 | thousand leagues beneath the sea. 70 | 71 | 72 | Knorr, Stefan 73 | Creepy Crawlies 74 | Horror 75 | 4.95 76 | 2000-12-06 77 | An anthology of horror stories about roaches, 78 | centipedes, scorpions and other insects. 79 | 80 | 81 | Kress, Peter 82 | Paradox Lost 83 | Science Fiction 84 | 6.95 85 | 2000-11-02 86 | After an inadvertant trip through a Heisenberg 87 | Uncertainty Device, James Salway discovers the problems 88 | of being quantum. 89 | 90 | 91 | O'Brien, Tim 92 | Microsoft .NET: The Programming Bible 93 | Computer 94 | 36.95 95 | 2000-12-09 96 | Microsoft's .NET initiative is explored in 97 | detail in this deep programmer's reference. 98 | 99 | 100 | O'Brien, Tim 101 | MSXML3: A Comprehensive Guide 102 | Computer 103 | 36.95 104 | 2000-12-01 105 | The Microsoft MSXML3 parser is covered in 106 | detail, with attention to XML DOM interfaces, XSLT processing, 107 | SAX and more. 108 | 109 | 110 | Galos, Mike 111 | Visual Studio 7: A Comprehensive Guide 112 | Computer 113 | 49.95 114 | 2001-04-16 115 | Microsoft Visual Studio 7 is explored in depth, 116 | looking at how Visual Basic, Visual C++, C#, and ASP+ are 117 | integrated into a comprehensive development 118 | environment. 119 | 120 | 121 | -------------------------------------------------------------------------------- /Sample XML/BreakfastMenu/breakfast.swift: -------------------------------------------------------------------------------- 1 | // 2 | // breakfast.swift 3 | // XMLParsing 4 | // 5 | // Created by Shawn Moore on 11/15/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Menu: Codable { 12 | var food: [Food] 13 | } 14 | 15 | struct Food: Codable { 16 | var name: String 17 | var price: String 18 | var description: String 19 | var calories: Int? 20 | } 21 | 22 | extension Menu { 23 | static func retrieveMenu() -> Menu? { 24 | guard let data = Data(forResource: "breakfast", withExtension: "xml") else { return nil } 25 | 26 | let decoder = XMLDecoder() 27 | 28 | let menu: Menu? 29 | 30 | do { 31 | menu = try decoder.decode(Menu.self, from: data) 32 | } catch { 33 | print(error) 34 | 35 | menu = nil 36 | } 37 | 38 | return menu 39 | } 40 | 41 | func toXML() -> String? { 42 | let encoder = XMLEncoder() 43 | 44 | do { 45 | let data = try encoder.encode(self, withRootKey: "breakfast_menu", header: XMLHeader(version: 1.0, encoding: "UTF-8")) 46 | 47 | return String(data: data, encoding: .utf8) 48 | } catch { 49 | print(error) 50 | 51 | return nil 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sample XML/BreakfastMenu/breakfast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Belgian Waffles 5 | $5.95 6 | Two of our famous Belgian Waffles with plenty of real maple syrup 7 | 8 | 9 | 10 | Strawberry Belgian Waffles 11 | $7.95 12 | Light Belgian waffles covered with strawberries and whipped cream 13 | 900 14 | 15 | 16 | Berry-Berry Belgian Waffles 17 | $8.95 18 | Light Belgian waffles covered with an assortment of fresh berries and whipped cream 19 | 900 20 | 21 | 22 | French Toast 23 | $4.50 24 | Thick slices made from our homemade sourdough bread 25 | 600 26 | 27 | 28 | Homestyle Breakfast 29 | $6.95 30 | Two eggs, bacon or sausage, toast, and our ever-popular hash browns 31 | 950 32 | 33 | 34 | -------------------------------------------------------------------------------- /Sample XML/CDs/CDCatalog.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CDCatalog.swift 3 | // XMLParsing 4 | // 5 | // Created by Shawn Moore on 11/15/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct CDCatalog: Codable { 12 | var cds: [CD] 13 | 14 | enum CodingKeys: String, CodingKey { 15 | case cds = "CD" 16 | } 17 | } 18 | 19 | struct CD: Codable { 20 | var title: String 21 | var artist: String 22 | var country: String 23 | var company: String 24 | var price: Double 25 | var year: Int 26 | 27 | enum CodingKeys: String, CodingKey { 28 | case title = "TITLE" 29 | case artist = "ARTIST" 30 | case country = "COUNTRY" 31 | case company = "COMPANY" 32 | case price = "PRICE" 33 | case year = "YEAR" 34 | } 35 | } 36 | 37 | extension CD { 38 | static func parseCDCatalog() -> CDCatalog? { 39 | guard let data = Data(forResource: "cd_catalog", withExtension: "xml") else { return nil } 40 | 41 | let decoder = XMLDecoder() 42 | 43 | let catalog: CDCatalog? 44 | 45 | do { 46 | catalog = try decoder.decode(CDCatalog.self, from: data) 47 | } catch { 48 | print(error) 49 | 50 | catalog = nil 51 | } 52 | 53 | return catalog 54 | } 55 | 56 | func toXML() -> String? { 57 | let encoder = XMLEncoder() 58 | 59 | do { 60 | let data = try encoder.encode(self, withRootKey: "CATALOG", header: XMLHeader(version: 1.0)) 61 | 62 | return String(data: data, encoding: .utf8) 63 | } catch { 64 | print(error) 65 | 66 | return nil 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sample XML/CDs/cd_catalog.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Empire Burlesque 5 | Bob Dylan 6 | USA 7 | Columbia 8 | 10.90 9 | 1985 10 | 11 | 12 | Hide your heart 13 | Bonnie Tyler 14 | UK 15 | CBS Records 16 | 9.90 17 | 1988 18 | 19 | 20 | Greatest Hits 21 | Dolly Parton 22 | USA 23 | RCA 24 | 9.90 25 | 1982 26 | 27 | 28 | Still got the blues 29 | Gary Moore 30 | UK 31 | Virgin records 32 | 10.20 33 | 1990 34 | 35 | 36 | Eros 37 | Eros Ramazzotti 38 | EU 39 | BMG 40 | 9.90 41 | 1997 42 | 43 | 44 | One night only 45 | Bee Gees 46 | UK 47 | Polydor 48 | 10.90 49 | 1998 50 | 51 | 52 | Sylvias Mother 53 | Dr.Hook 54 | UK 55 | CBS 56 | 8.10 57 | 1973 58 | 59 | 60 | Maggie May 61 | Rod Stewart 62 | UK 63 | Pickwick 64 | 8.50 65 | 1990 66 | 67 | 68 | Romanza 69 | Andrea Bocelli 70 | EU 71 | Polydor 72 | 10.80 73 | 1996 74 | 75 | 76 | When a man loves a woman 77 | Percy Sledge 78 | USA 79 | Atlantic 80 | 8.70 81 | 1987 82 | 83 | 84 | Black angel 85 | Savage Rose 86 | EU 87 | Mega 88 | 10.90 89 | 1995 90 | 91 | 92 | 1999 Grammy Nominees 93 | Many 94 | USA 95 | Grammy 96 | 10.20 97 | 1999 98 | 99 | 100 | For the good times 101 | Kenny Rogers 102 | UK 103 | Mucik Master 104 | 8.70 105 | 1995 106 | 107 | 108 | Big Willie style 109 | Will Smith 110 | USA 111 | Columbia 112 | 9.90 113 | 1997 114 | 115 | 116 | Tupelo Honey 117 | Van Morrison 118 | UK 119 | Polydor 120 | 8.20 121 | 1971 122 | 123 | 124 | Soulsville 125 | Jorn Hoel 126 | Norway 127 | WEA 128 | 7.90 129 | 1996 130 | 131 | 132 | The very best of 133 | Cat Stevens 134 | UK 135 | Island 136 | 8.90 137 | 1990 138 | 139 | 140 | Stop 141 | Sam Brown 142 | UK 143 | A and M 144 | 8.90 145 | 1988 146 | 147 | 148 | Bridge of Spies 149 | T'Pau 150 | UK 151 | Siren 152 | 7.90 153 | 1987 154 | 155 | 156 | Private Dancer 157 | Tina Turner 158 | UK 159 | Capitol 160 | 8.90 161 | 1983 162 | 163 | 164 | Midt om natten 165 | Kim Larsen 166 | EU 167 | Medley 168 | 7.80 169 | 1983 170 | 171 | 172 | Pavarotti Gala Concert 173 | Luciano Pavarotti 174 | UK 175 | DECCA 176 | 9.90 177 | 1991 178 | 179 | 180 | The dock of the bay 181 | Otis Redding 182 | USA 183 | Stax Records 184 | 7.90 185 | 1968 186 | 187 | 188 | Picture book 189 | Simply Red 190 | EU 191 | Elektra 192 | 7.20 193 | 1985 194 | 195 | 196 | Red 197 | The Communards 198 | UK 199 | London 200 | 7.80 201 | 1987 202 | 203 | 204 | Unchain my heart 205 | Joe Cocker 206 | USA 207 | EMI 208 | 8.20 209 | 1987 210 | 211 | 212 | -------------------------------------------------------------------------------- /Sample XML/Notes/Notes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notes.swift 3 | // XMLParsing 4 | // 5 | // Created by Shawn Moore on 11/15/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Note: Codable { 12 | var to: String 13 | var from: String 14 | var heading: String 15 | var body: String 16 | } 17 | 18 | // TEST FUNCTIONS 19 | extension Note { 20 | static func retrieveNote() -> Note? { 21 | guard let data = Data(forResource: "note", withExtension: "xml") else { return nil } 22 | 23 | let decoder = XMLDecoder() 24 | 25 | let note: Note? 26 | 27 | do { 28 | note = try decoder.decode(Note.self, from: data) 29 | } catch { 30 | print(error) 31 | 32 | note = nil 33 | } 34 | 35 | return note 36 | } 37 | 38 | static func retrieveNoteError() { 39 | guard let data = Data(forResource: "note_error", withExtension: "xml") else { return } 40 | 41 | let decoder = XMLDecoder() 42 | 43 | do { 44 | _ = try decoder.decode(Note.self, from: data) 45 | } catch { 46 | print(error) 47 | } 48 | } 49 | 50 | func toXML() -> String? { 51 | let encoder = XMLEncoder() 52 | 53 | do { 54 | let data = try encoder.encode(self, withRootKey: "note") 55 | 56 | return String(data: data, encoding: .utf8) 57 | } catch { 58 | print(error) 59 | 60 | return nil 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sample XML/Notes/note.xml: -------------------------------------------------------------------------------- 1 | 2 | Tove 3 | Jani 4 | Reminder 5 | Don't forget me this weekend! 6 | 7 | -------------------------------------------------------------------------------- /Sample XML/Notes/note_error.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Tove 4 | Jani 5 | Reminder 6 | Don't forget me this weekend! 7 | 8 | -------------------------------------------------------------------------------- /Sample XML/Plants/PlantCatalog.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlantCatalog.swift 3 | // XMLParsing 4 | // 5 | // Created by Shawn Moore on 11/15/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct PlantCatalog: Codable { 12 | var plants: [Plant] 13 | 14 | enum CodingKeys: String, CodingKey { 15 | case plants = "PLANT" 16 | } 17 | } 18 | 19 | struct Plant: Codable { 20 | var common: String 21 | var botanical: String 22 | var zone: String 23 | var light: String 24 | var price: Double 25 | var amountAvailable: Int 26 | 27 | enum CodingKeys: String, CodingKey { 28 | case common = "COMMON" 29 | case botanical = "BOTANICAL" 30 | case zone = "ZONE" 31 | case light = "LIGHT" 32 | case price = "PRICE" 33 | case amountAvailable = "AVAILABILITY" 34 | } 35 | 36 | init(common: String, botanical: String, zone: String, light: String, price: Double, amountAvailable: Int) { 37 | self.common = common 38 | self.botanical = botanical 39 | self.zone = zone 40 | self.light = light 41 | self.price = price 42 | self.amountAvailable = amountAvailable 43 | } 44 | 45 | private static let currencyFormatter: NumberFormatter = { 46 | let formatter = NumberFormatter() 47 | formatter.numberStyle = .currency 48 | formatter.locale = Locale(identifier: "en_US") 49 | return formatter 50 | }() 51 | 52 | init(from decoder: Decoder) throws { 53 | let values = try decoder.container(keyedBy: CodingKeys.self) 54 | let common = try values.decode(String.self, forKey: .common) 55 | let botanical = try values.decode(String.self, forKey: .botanical) 56 | let zone = try values.decode(String.self, forKey: .zone) 57 | let light = try values.decode(String.self, forKey: .light) 58 | let priceString = try values.decode(String.self, forKey: .price) 59 | let price = Plant.currencyFormatter.number(from: priceString)?.doubleValue ?? 0.0 60 | let availability = try values.decode(Int.self, forKey: .amountAvailable) 61 | 62 | self.init(common: common, botanical: botanical, zone: zone, light: light, price: price, amountAvailable: availability) 63 | } 64 | } 65 | 66 | extension PlantCatalog { 67 | static func retreievePlantCatalog() -> PlantCatalog? { 68 | guard let data = Data(forResource: "plant_catalog", withExtension: "xml") else { return nil } 69 | 70 | let decoder = XMLDecoder() 71 | 72 | let plantCatalog: PlantCatalog? 73 | 74 | do { 75 | plantCatalog = try decoder.decode(PlantCatalog.self, from: data) 76 | } catch { 77 | print(error) 78 | 79 | plantCatalog = nil 80 | } 81 | 82 | return plantCatalog 83 | } 84 | 85 | func toXML() -> String? { 86 | let encoder = XMLEncoder() 87 | 88 | do { 89 | let data = try encoder.encode(self, withRootKey: "CATALOG", header: XMLHeader(version: 1.0, encoding: "UTF-8")) 90 | 91 | return String(data: data, encoding: .utf8) 92 | } catch { 93 | print(error) 94 | 95 | return nil 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Sample XML/Plants/plant_catalog.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Bloodroot 5 | Sanguinaria canadensis 6 | 4 7 | Mostly Shady 8 | $2.44 9 | 031599 10 | 11 | 12 | Columbine 13 | Aquilegia canadensis 14 | 3 15 | Mostly Shady 16 | $9.37 17 | 030699 18 | 19 | 20 | Marsh Marigold 21 | Caltha palustris 22 | 4 23 | Mostly Sunny 24 | $6.81 25 | 051799 26 | 27 | 28 | Cowslip 29 | Caltha palustris 30 | 4 31 | Mostly Shady 32 | $9.90 33 | 030699 34 | 35 | 36 | Dutchman's-Breeches 37 | Dicentra cucullaria 38 | 3 39 | Mostly Shady 40 | $6.44 41 | 012099 42 | 43 | 44 | Ginger, Wild 45 | Asarum canadense 46 | 3 47 | Mostly Shady 48 | $9.03 49 | 041899 50 | 51 | 52 | Hepatica 53 | Hepatica americana 54 | 4 55 | Mostly Shady 56 | $4.45 57 | 012699 58 | 59 | 60 | Liverleaf 61 | Hepatica americana 62 | 4 63 | Mostly Shady 64 | $3.99 65 | 010299 66 | 67 | 68 | Jack-In-The-Pulpit 69 | Arisaema triphyllum 70 | 4 71 | Mostly Shady 72 | $3.23 73 | 020199 74 | 75 | 76 | Mayapple 77 | Podophyllum peltatum 78 | 3 79 | Mostly Shady 80 | $2.98 81 | 060599 82 | 83 | 84 | Phlox, Woodland 85 | Phlox divaricata 86 | 3 87 | Sun or Shade 88 | $2.80 89 | 012299 90 | 91 | 92 | Phlox, Blue 93 | Phlox divaricata 94 | 3 95 | Sun or Shade 96 | $5.59 97 | 021699 98 | 99 | 100 | Spring-Beauty 101 | Claytonia Virginica 102 | 7 103 | Mostly Shady 104 | $6.59 105 | 020199 106 | 107 | 108 | Trillium 109 | Trillium grandiflorum 110 | 5 111 | Sun or Shade 112 | $3.90 113 | 042999 114 | 115 | 116 | Wake Robin 117 | Trillium grandiflorum 118 | 5 119 | Sun or Shade 120 | $3.20 121 | 022199 122 | 123 | 124 | Violet, Dog-Tooth 125 | Erythronium americanum 126 | 4 127 | Shade 128 | $9.04 129 | 020199 130 | 131 | 132 | Trout Lily 133 | Erythronium americanum 134 | 4 135 | Shade 136 | $6.94 137 | 032499 138 | 139 | 140 | Adder's-Tongue 141 | Erythronium americanum 142 | 4 143 | Shade 144 | $9.58 145 | 041399 146 | 147 | 148 | Anemone 149 | Anemone blanda 150 | 6 151 | Mostly Shady 152 | $8.86 153 | 122698 154 | 155 | 156 | Grecian Windflower 157 | Anemone blanda 158 | 6 159 | Mostly Shady 160 | $9.16 161 | 071099 162 | 163 | 164 | Bee Balm 165 | Monarda didyma 166 | 4 167 | Shade 168 | $4.59 169 | 050399 170 | 171 | 172 | Bergamot 173 | Monarda didyma 174 | 4 175 | Shade 176 | $7.16 177 | 042799 178 | 179 | 180 | Black-Eyed Susan 181 | Rudbeckia hirta 182 | Annual 183 | Sunny 184 | $9.80 185 | 061899 186 | 187 | 188 | Buttercup 189 | Ranunculus 190 | 4 191 | Shade 192 | $2.57 193 | 061099 194 | 195 | 196 | Crowfoot 197 | Ranunculus 198 | 4 199 | Shade 200 | $9.34 201 | 040399 202 | 203 | 204 | Butterfly Weed 205 | Asclepias tuberosa 206 | Annual 207 | Sunny 208 | $2.78 209 | 063099 210 | 211 | 212 | Cinquefoil 213 | Potentilla 214 | Annual 215 | Shade 216 | $7.06 217 | 052599 218 | 219 | 220 | Primrose 221 | Oenothera 222 | 3 - 5 223 | Sunny 224 | $6.56 225 | 013099 226 | 227 | 228 | Gentian 229 | Gentiana 230 | 4 231 | Sun or Shade 232 | $7.81 233 | 051899 234 | 235 | 236 | Blue Gentian 237 | Gentiana 238 | 4 239 | Sun or Shade 240 | $8.56 241 | 050299 242 | 243 | 244 | Jacob's Ladder 245 | Polemonium caeruleum 246 | Annual 247 | Shade 248 | $9.26 249 | 022199 250 | 251 | 252 | Greek Valerian 253 | Polemonium caeruleum 254 | Annual 255 | Shade 256 | $4.36 257 | 071499 258 | 259 | 260 | California Poppy 261 | Eschscholzia californica 262 | Annual 263 | Sun 264 | $7.89 265 | 032799 266 | 267 | 268 | Shooting Star 269 | Dodecatheon 270 | Annual 271 | Mostly Shady 272 | $8.60 273 | 051399 274 | 275 | 276 | Snakeroot 277 | Cimicifuga 278 | Annual 279 | Shade 280 | $5.63 281 | 071199 282 | 283 | 284 | Cardinal Flower 285 | Lobelia cardinalis 286 | 2 287 | Shade 288 | $3.02 289 | 022299 290 | 291 | 292 | -------------------------------------------------------------------------------- /Sample XML/RJI/RJI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RJI.swift 3 | // XMLParsing 4 | // 5 | // Created by Shawn Moore on 11/20/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct RSS: Decodable { 12 | var dc: URL 13 | var sy: URL 14 | var admin: URL 15 | var rdf: URL 16 | var content: URL 17 | var channel: Channel 18 | 19 | enum CodingKeys: String, CodingKey { 20 | case channel = "channel" 21 | 22 | case dc = "xmlns:dc" 23 | case sy = "xmlns:sy" 24 | case admin = "xmlns:admin" 25 | case rdf = "xmlns:rdf" 26 | case content = "xmlns:content" 27 | } 28 | } 29 | 30 | extension RSS { 31 | static func retrieveRSS() -> RSS? { 32 | guard let data = Data(forResource: "RJI_RSS_Sample", withExtension: "xml") else { return nil } 33 | 34 | let decoder = XMLDecoder() 35 | 36 | let dateFormatter = DateFormatter() 37 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" 38 | decoder.dateDecodingStrategy = .formatted(dateFormatter) 39 | 40 | let rss: RSS? 41 | 42 | do { 43 | rss = try decoder.decode(RSS.self, from: data) 44 | } catch { 45 | print(error) 46 | 47 | rss = nil 48 | } 49 | 50 | return rss 51 | } 52 | } 53 | 54 | struct Channel: Decodable { 55 | var title: String 56 | var link: URL 57 | var description: String? 58 | var language: String 59 | var creator: String 60 | var rights: String 61 | var date: Date 62 | var generatorAgentResource: URL 63 | var image: Image 64 | var items: [Item] 65 | 66 | enum CodingKeys: String, CodingKey { 67 | case title, link, description, image 68 | 69 | case language = "dc:language" 70 | case creator = "dc:creator" 71 | case rights = "dc:rights" 72 | case date = "dc:date" 73 | case generatorAgent = "admin:generatorAgent" 74 | case items = "item" 75 | } 76 | 77 | enum GeneratorAgentKeys: String, CodingKey { 78 | case resource = "rdf:resource" 79 | } 80 | 81 | init(title: String, link: URL, description: String, language: String, creator: String, rights: String, date: Date, generatorAgentResource: URL, image: Image, items: [Item]) { 82 | self.title = title 83 | self.link = link 84 | self.description = description 85 | self.language = language 86 | self.creator = creator 87 | self.rights = rights 88 | self.date = date 89 | self.generatorAgentResource = generatorAgentResource 90 | self.image = image 91 | self.items = items 92 | } 93 | 94 | init(from decoder: Decoder) throws { 95 | let values = try decoder.container(keyedBy: CodingKeys.self) 96 | title = try values.decode(String.self, forKey: .title) 97 | link = try values.decode(URL.self, forKey: .link) 98 | description = try values.decodeIfPresent(String.self, forKey: .description) 99 | language = try values.decode(String.self, forKey: .language) 100 | creator = try values.decode(String.self, forKey: .creator) 101 | rights = try values.decode(String.self, forKey: .rights) 102 | date = try values.decode(Date.self, forKey: .date) 103 | 104 | let generatorAgentValues = try values.nestedContainer(keyedBy: GeneratorAgentKeys.self, forKey: .generatorAgent) 105 | generatorAgentResource = try generatorAgentValues.decode(URL.self, forKey: .resource) 106 | 107 | image = try values.decode(Image.self, forKey: .image) 108 | items = try values.decode([Item].self, forKey: .items) 109 | } 110 | } 111 | 112 | struct Image: Decodable { 113 | var url: URL 114 | var height: Int 115 | var width: Int 116 | var link: URL 117 | var title: String 118 | } 119 | 120 | struct Item: Decodable { 121 | var title: String 122 | var link: URL 123 | var guid: URL 124 | var enclosure: Enclosure? 125 | var description: String 126 | var subject: String? 127 | var date: Date 128 | var author: String? 129 | 130 | enum CodingKeys: String, CodingKey { 131 | case title, link, guid, enclosure, description, author 132 | 133 | case subject = "dc:subject" 134 | case date = "dc:date" 135 | } 136 | } 137 | 138 | struct Enclosure: Decodable { 139 | var url: URL 140 | var length: String 141 | var type: String 142 | } 143 | -------------------------------------------------------------------------------- /Sources/XMLParsing/Decoder/DecodingErrorExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DecodingErrorExtension.swift 3 | // XMLParsing 4 | // 5 | // Created by Shawn Moore on 11/21/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | //===----------------------------------------------------------------------===// 12 | // Error Utilities 13 | //===----------------------------------------------------------------------===// 14 | 15 | internal extension DecodingError { 16 | /// Returns a `.typeMismatch` error describing the expected type. 17 | /// 18 | /// - parameter path: The path of `CodingKey`s taken to decode a value of this type. 19 | /// - parameter expectation: The type expected to be encountered. 20 | /// - parameter reality: The value that was encountered instead of the expected type. 21 | /// - returns: A `DecodingError` with the appropriate path and debug description. 22 | internal static func _typeMismatch(at path: [CodingKey], expectation: Any.Type, reality: Any) -> DecodingError { 23 | let description = "Expected to decode \(expectation) but found \(_typeDescription(of: reality)) instead." 24 | return .typeMismatch(expectation, Context(codingPath: path, debugDescription: description)) 25 | } 26 | 27 | /// Returns a description of the type of `value` appropriate for an error message. 28 | /// 29 | /// - parameter value: The value whose type to describe. 30 | /// - returns: A string describing `value`. 31 | /// - precondition: `value` is one of the types below. 32 | internal static func _typeDescription(of value: Any) -> String { 33 | if value is NSNull { 34 | return "a null value" 35 | } else if value is NSNumber /* FIXME: If swift-corelibs-foundation isn't updated to use NSNumber, this check will be necessary: || value is Int || value is Double */ { 36 | return "a number" 37 | } else if value is String { 38 | return "a string/data" 39 | } else if value is [Any] { 40 | return "an array" 41 | } else if value is [String : Any] { 42 | return "a dictionary" 43 | } else { 44 | return "\(type(of: value))" 45 | } 46 | } 47 | } 48 | 49 | 50 | -------------------------------------------------------------------------------- /Sources/XMLParsing/Decoder/XMLDecoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XMLDecoder.swift 3 | // XMLParsing 4 | // 5 | // Created by Shawn Moore on 11/20/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | //===----------------------------------------------------------------------===// 12 | // XML Decoder 13 | //===----------------------------------------------------------------------===// 14 | 15 | /// `XMLDecoder` facilitates the decoding of XML into semantic `Decodable` types. 16 | open class XMLDecoder { 17 | // MARK: Options 18 | /// The strategy to use for decoding `Date` values. 19 | public enum DateDecodingStrategy { 20 | /// Defer to `Date` for decoding. This is the default strategy. 21 | case deferredToDate 22 | 23 | /// Decode the `Date` as a UNIX timestamp from a XML number. This is the default strategy. 24 | case secondsSince1970 25 | 26 | /// Decode the `Date` as UNIX millisecond timestamp from a XML number. 27 | case millisecondsSince1970 28 | 29 | /// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format). 30 | @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) 31 | case iso8601 32 | 33 | /// Decode the `Date` as a string parsed by the given formatter. 34 | case formatted(DateFormatter) 35 | 36 | /// Decode the `Date` as a custom value decoded by the given closure. 37 | case custom((_ decoder: Decoder) throws -> Date) 38 | 39 | /// Decode the `Date` as a string parsed by the given formatter for the give key. 40 | static func keyFormatted(_ formatterForKey: @escaping (CodingKey) throws -> DateFormatter?) -> XMLDecoder.DateDecodingStrategy { 41 | return .custom({ (decoder) -> Date in 42 | guard let codingKey = decoder.codingPath.last else { 43 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No Coding Path Found")) 44 | } 45 | 46 | guard let container = try? decoder.singleValueContainer(), 47 | let text = try? container.decode(String.self) else { 48 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not decode date text")) 49 | } 50 | 51 | guard let dateFormatter = try formatterForKey(codingKey) else { 52 | throw DecodingError.dataCorruptedError(in: container, debugDescription: "No date formatter for date text") 53 | } 54 | 55 | if let date = dateFormatter.date(from: text) { 56 | return date 57 | } else { 58 | throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(text)") 59 | } 60 | }) 61 | } 62 | } 63 | 64 | /// The strategy to use for decoding `Data` values. 65 | public enum DataDecodingStrategy { 66 | /// Defer to `Data` for decoding. 67 | case deferredToData 68 | 69 | /// Decode the `Data` from a Base64-encoded string. This is the default strategy. 70 | case base64 71 | 72 | /// Decode the `Data` as a custom value decoded by the given closure. 73 | case custom((_ decoder: Decoder) throws -> Data) 74 | 75 | /// Decode the `Data` as a custom value by the given closure for the give key. 76 | static func keyFormatted(_ formatterForKey: @escaping (CodingKey) throws -> Data?) -> XMLDecoder.DataDecodingStrategy { 77 | return .custom({ (decoder) -> Data in 78 | guard let codingKey = decoder.codingPath.last else { 79 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No Coding Path Found")) 80 | } 81 | 82 | guard let container = try? decoder.singleValueContainer(), 83 | let text = try? container.decode(String.self) else { 84 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not decode date text")) 85 | } 86 | 87 | guard let data = try formatterForKey(codingKey) else { 88 | throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode data string \(text)") 89 | } 90 | 91 | return data 92 | }) 93 | } 94 | } 95 | 96 | /// The strategy to use for non-XML-conforming floating-point values (IEEE 754 infinity and NaN). 97 | public enum NonConformingFloatDecodingStrategy { 98 | /// Throw upon encountering non-conforming values. This is the default strategy. 99 | case `throw` 100 | 101 | /// Decode the values from the given representation strings. 102 | case convertFromString(positiveInfinity: String, negativeInfinity: String, nan: String) 103 | } 104 | 105 | /// The strategy to use for automatically changing the value of keys before decoding. 106 | public enum KeyDecodingStrategy { 107 | /// Use the keys specified by each type. This is the default strategy. 108 | case useDefaultKeys 109 | 110 | /// Convert from "snake_case_keys" to "camelCaseKeys" before attempting to match a key with the one specified by each type. 111 | /// 112 | /// The conversion to upper case uses `Locale.system`, also known as the ICU "root" locale. This means the result is consistent regardless of the current user's locale and language preferences. 113 | /// 114 | /// Converting from snake case to camel case: 115 | /// 1. Capitalizes the word starting after each `_` 116 | /// 2. Removes all `_` 117 | /// 3. Preserves starting and ending `_` (as these are often used to indicate private variables or other metadata). 118 | /// For example, `one_two_three` becomes `oneTwoThree`. `_one_two_three_` becomes `_oneTwoThree_`. 119 | /// 120 | /// - Note: Using a key decoding strategy has a nominal performance cost, as each string key has to be inspected for the `_` character. 121 | case convertFromSnakeCase 122 | 123 | /// Provide a custom conversion from the key in the encoded JSON to the keys specified by the decoded types. 124 | /// The full path to the current decoding position is provided for context (in case you need to locate this key within the payload). The returned key is used in place of the last component in the coding path before decoding. 125 | /// If the result of the conversion is a duplicate key, then only one value will be present in the container for the type to decode from. 126 | case custom((_ codingPath: [CodingKey]) -> CodingKey) 127 | 128 | internal static func _convertFromSnakeCase(_ stringKey: String) -> String { 129 | guard !stringKey.isEmpty else { return stringKey } 130 | 131 | // Find the first non-underscore character 132 | guard let firstNonUnderscore = stringKey.index(where: { $0 != "_" }) else { 133 | // Reached the end without finding an _ 134 | return stringKey 135 | } 136 | 137 | // Find the last non-underscore character 138 | var lastNonUnderscore = stringKey.index(before: stringKey.endIndex) 139 | while lastNonUnderscore > firstNonUnderscore && stringKey[lastNonUnderscore] == "_" { 140 | stringKey.formIndex(before: &lastNonUnderscore) 141 | } 142 | 143 | let keyRange = firstNonUnderscore...lastNonUnderscore 144 | let leadingUnderscoreRange = stringKey.startIndex..(_ type: T.Type, from data: Data) throws -> T { 220 | let topLevel: [String: Any] 221 | do { 222 | topLevel = try _XMLStackParser.parse(with: data) 223 | } catch { 224 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid XML.", underlyingError: error)) 225 | } 226 | 227 | let decoder = _XMLDecoder(referencing: topLevel, options: self.options) 228 | 229 | guard let value = try decoder.unbox(topLevel, as: type) else { 230 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value.")) 231 | } 232 | 233 | return value 234 | } 235 | } 236 | 237 | // MARK: - _XMLDecoder 238 | 239 | internal class _XMLDecoder : Decoder { 240 | // MARK: Properties 241 | 242 | /// The decoder's storage. 243 | internal var storage: _XMLDecodingStorage 244 | 245 | /// Options set on the top-level decoder. 246 | internal let options: XMLDecoder._Options 247 | 248 | /// The path to the current point in encoding. 249 | internal(set) public var codingPath: [CodingKey] 250 | 251 | /// Contextual user-provided information for use during encoding. 252 | public var userInfo: [CodingUserInfoKey : Any] { 253 | return self.options.userInfo 254 | } 255 | 256 | // MARK: - Initialization 257 | 258 | /// Initializes `self` with the given top-level container and options. 259 | internal init(referencing container: Any, at codingPath: [CodingKey] = [], options: XMLDecoder._Options) { 260 | self.storage = _XMLDecodingStorage() 261 | self.storage.push(container: container) 262 | self.codingPath = codingPath 263 | self.options = options 264 | } 265 | 266 | // MARK: - Decoder Methods 267 | 268 | public func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer { 269 | guard !(self.storage.topContainer is NSNull) else { 270 | throw DecodingError.valueNotFound(KeyedDecodingContainer.self, 271 | DecodingError.Context(codingPath: self.codingPath, 272 | debugDescription: "Cannot get keyed decoding container -- found null value instead.")) 273 | } 274 | 275 | guard let topContainer = self.storage.topContainer as? [String : Any] else { 276 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: self.storage.topContainer) 277 | } 278 | 279 | let container = _XMLKeyedDecodingContainer(referencing: self, wrapping: topContainer) 280 | return KeyedDecodingContainer(container) 281 | } 282 | 283 | public func unkeyedContainer() throws -> UnkeyedDecodingContainer { 284 | guard !(self.storage.topContainer is NSNull) else { 285 | throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, 286 | DecodingError.Context(codingPath: self.codingPath, 287 | debugDescription: "Cannot get unkeyed decoding container -- found null value instead.")) 288 | } 289 | 290 | let topContainer: [Any] 291 | 292 | if let container = self.storage.topContainer as? [Any] { 293 | topContainer = container 294 | } else if let container = self.storage.topContainer as? [AnyHashable: Any] { 295 | topContainer = [container] 296 | } else { 297 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: [Any].self, reality: self.storage.topContainer) 298 | } 299 | 300 | return _XMLUnkeyedDecodingContainer(referencing: self, wrapping: topContainer) 301 | } 302 | 303 | public func singleValueContainer() throws -> SingleValueDecodingContainer { 304 | return self 305 | } 306 | } 307 | 308 | 309 | extension _XMLDecoder : SingleValueDecodingContainer { 310 | // MARK: SingleValueDecodingContainer Methods 311 | 312 | private func expectNonNull(_ type: T.Type) throws { 313 | guard !self.decodeNil() else { 314 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "Expected \(type) but found null value instead.")) 315 | } 316 | } 317 | 318 | public func decodeNil() -> Bool { 319 | return self.storage.topContainer is NSNull 320 | } 321 | 322 | public func decode(_ type: Bool.Type) throws -> Bool { 323 | try expectNonNull(Bool.self) 324 | return try self.unbox(self.storage.topContainer, as: Bool.self)! 325 | } 326 | 327 | public func decode(_ type: Int.Type) throws -> Int { 328 | try expectNonNull(Int.self) 329 | return try self.unbox(self.storage.topContainer, as: Int.self)! 330 | } 331 | 332 | public func decode(_ type: Int8.Type) throws -> Int8 { 333 | try expectNonNull(Int8.self) 334 | return try self.unbox(self.storage.topContainer, as: Int8.self)! 335 | } 336 | 337 | public func decode(_ type: Int16.Type) throws -> Int16 { 338 | try expectNonNull(Int16.self) 339 | return try self.unbox(self.storage.topContainer, as: Int16.self)! 340 | } 341 | 342 | public func decode(_ type: Int32.Type) throws -> Int32 { 343 | try expectNonNull(Int32.self) 344 | return try self.unbox(self.storage.topContainer, as: Int32.self)! 345 | } 346 | 347 | public func decode(_ type: Int64.Type) throws -> Int64 { 348 | try expectNonNull(Int64.self) 349 | return try self.unbox(self.storage.topContainer, as: Int64.self)! 350 | } 351 | 352 | public func decode(_ type: UInt.Type) throws -> UInt { 353 | try expectNonNull(UInt.self) 354 | return try self.unbox(self.storage.topContainer, as: UInt.self)! 355 | } 356 | 357 | public func decode(_ type: UInt8.Type) throws -> UInt8 { 358 | try expectNonNull(UInt8.self) 359 | return try self.unbox(self.storage.topContainer, as: UInt8.self)! 360 | } 361 | 362 | public func decode(_ type: UInt16.Type) throws -> UInt16 { 363 | try expectNonNull(UInt16.self) 364 | return try self.unbox(self.storage.topContainer, as: UInt16.self)! 365 | } 366 | 367 | public func decode(_ type: UInt32.Type) throws -> UInt32 { 368 | try expectNonNull(UInt32.self) 369 | return try self.unbox(self.storage.topContainer, as: UInt32.self)! 370 | } 371 | 372 | public func decode(_ type: UInt64.Type) throws -> UInt64 { 373 | try expectNonNull(UInt64.self) 374 | return try self.unbox(self.storage.topContainer, as: UInt64.self)! 375 | } 376 | 377 | public func decode(_ type: Float.Type) throws -> Float { 378 | try expectNonNull(Float.self) 379 | return try self.unbox(self.storage.topContainer, as: Float.self)! 380 | } 381 | 382 | public func decode(_ type: Double.Type) throws -> Double { 383 | try expectNonNull(Double.self) 384 | return try self.unbox(self.storage.topContainer, as: Double.self)! 385 | } 386 | 387 | public func decode(_ type: String.Type) throws -> String { 388 | try expectNonNull(String.self) 389 | return try self.unbox(self.storage.topContainer, as: String.self)! 390 | } 391 | 392 | public func decode(_ type: T.Type) throws -> T { 393 | try expectNonNull(type) 394 | return try self.unbox(self.storage.topContainer, as: type)! 395 | } 396 | } 397 | 398 | // MARK: - Concrete Value Representations 399 | 400 | extension _XMLDecoder { 401 | /// Returns the given value unboxed from a container. 402 | internal func unbox(_ value: Any, as type: Bool.Type) throws -> Bool? { 403 | guard !(value is NSNull) else { return nil } 404 | 405 | guard let value = value as? String else { return nil } 406 | 407 | if value == "true" || value == "1" { 408 | return true 409 | } else if value == "false" || value == "0" { 410 | return false 411 | } 412 | 413 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 414 | } 415 | 416 | internal func unbox(_ value: Any, as type: Int.Type) throws -> Int? { 417 | guard !(value is NSNull) else { return nil } 418 | 419 | guard let string = value as? String else { return nil } 420 | 421 | guard let value = Float(string) else { 422 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) 423 | } 424 | 425 | let number = NSNumber(value: value) 426 | 427 | guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { 428 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 429 | } 430 | 431 | let int = number.intValue 432 | guard NSNumber(value: int) == number else { 433 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed XML number <\(number)> does not fit in \(type).")) 434 | } 435 | 436 | return int 437 | } 438 | 439 | internal func unbox(_ value: Any, as type: Int8.Type) throws -> Int8? { 440 | guard !(value is NSNull) else { return nil } 441 | 442 | guard let string = value as? String else { return nil } 443 | 444 | guard let value = Float(string) else { 445 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) 446 | } 447 | 448 | let number = NSNumber(value: value) 449 | 450 | guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { 451 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 452 | } 453 | 454 | let int8 = number.int8Value 455 | guard NSNumber(value: int8) == number else { 456 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed XML number <\(number)> does not fit in \(type).")) 457 | } 458 | 459 | return int8 460 | } 461 | 462 | internal func unbox(_ value: Any, as type: Int16.Type) throws -> Int16? { 463 | guard !(value is NSNull) else { return nil } 464 | 465 | guard let string = value as? String else { return nil } 466 | 467 | guard let value = Float(string) else { 468 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) 469 | } 470 | 471 | let number = NSNumber(value: value) 472 | 473 | guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { 474 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 475 | } 476 | 477 | let int16 = number.int16Value 478 | guard NSNumber(value: int16) == number else { 479 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed XML number <\(number)> does not fit in \(type).")) 480 | } 481 | 482 | return int16 483 | } 484 | 485 | internal func unbox(_ value: Any, as type: Int32.Type) throws -> Int32? { 486 | guard !(value is NSNull) else { return nil } 487 | 488 | guard let string = value as? String else { return nil } 489 | 490 | guard let value = Float(string) else { 491 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) 492 | } 493 | 494 | let number = NSNumber(value: value) 495 | 496 | guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { 497 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 498 | } 499 | 500 | let int32 = number.int32Value 501 | guard NSNumber(value: int32) == number else { 502 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed XML number <\(number)> does not fit in \(type).")) 503 | } 504 | 505 | return int32 506 | } 507 | 508 | internal func unbox(_ value: Any, as type: Int64.Type) throws -> Int64? { 509 | guard !(value is NSNull) else { return nil } 510 | 511 | guard let string = value as? String else { return nil } 512 | 513 | guard let value = Float(string) else { 514 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) 515 | } 516 | 517 | let number = NSNumber(value: value) 518 | 519 | guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { 520 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 521 | } 522 | 523 | let int64 = number.int64Value 524 | guard NSNumber(value: int64) == number else { 525 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed XML number <\(number)> does not fit in \(type).")) 526 | } 527 | 528 | return int64 529 | } 530 | 531 | internal func unbox(_ value: Any, as type: UInt.Type) throws -> UInt? { 532 | guard !(value is NSNull) else { return nil } 533 | 534 | guard let string = value as? String else { return nil } 535 | 536 | guard let value = Float(string) else { 537 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) 538 | } 539 | 540 | let number = NSNumber(value: value) 541 | 542 | guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { 543 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 544 | } 545 | 546 | let uint = number.uintValue 547 | guard NSNumber(value: uint) == number else { 548 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed XML number <\(number)> does not fit in \(type).")) 549 | } 550 | 551 | return uint 552 | } 553 | 554 | internal func unbox(_ value: Any, as type: UInt8.Type) throws -> UInt8? { 555 | guard !(value is NSNull) else { return nil } 556 | 557 | guard let string = value as? String else { return nil } 558 | 559 | guard let value = Float(string) else { 560 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) 561 | } 562 | 563 | let number = NSNumber(value: value) 564 | 565 | guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { 566 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 567 | } 568 | 569 | let uint8 = number.uint8Value 570 | guard NSNumber(value: uint8) == number else { 571 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed XML number <\(number)> does not fit in \(type).")) 572 | } 573 | 574 | return uint8 575 | } 576 | 577 | internal func unbox(_ value: Any, as type: UInt16.Type) throws -> UInt16? { 578 | guard !(value is NSNull) else { return nil } 579 | 580 | guard let string = value as? String else { return nil } 581 | 582 | guard let value = Float(string) else { 583 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) 584 | } 585 | 586 | let number = NSNumber(value: value) 587 | 588 | guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { 589 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 590 | } 591 | 592 | let uint16 = number.uint16Value 593 | guard NSNumber(value: uint16) == number else { 594 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed XML number <\(number)> does not fit in \(type).")) 595 | } 596 | 597 | return uint16 598 | } 599 | 600 | internal func unbox(_ value: Any, as type: UInt32.Type) throws -> UInt32? { 601 | guard !(value is NSNull) else { return nil } 602 | 603 | guard let string = value as? String else { return nil } 604 | 605 | guard let value = Float(string) else { 606 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) 607 | } 608 | 609 | let number = NSNumber(value: value) 610 | 611 | guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { 612 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 613 | } 614 | 615 | let uint32 = number.uint32Value 616 | guard NSNumber(value: uint32) == number else { 617 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed XML number <\(number)> does not fit in \(type).")) 618 | } 619 | 620 | return uint32 621 | } 622 | 623 | internal func unbox(_ value: Any, as type: UInt64.Type) throws -> UInt64? { 624 | guard !(value is NSNull) else { return nil } 625 | 626 | guard let string = value as? String else { return nil } 627 | 628 | guard let value = Float(string) else { 629 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) 630 | } 631 | 632 | let number = NSNumber(value: value) 633 | 634 | guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { 635 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 636 | } 637 | 638 | let uint64 = number.uint64Value 639 | guard NSNumber(value: uint64) == number else { 640 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed XML number <\(number)> does not fit in \(type).")) 641 | } 642 | 643 | return uint64 644 | } 645 | 646 | internal func unbox(_ value: Any, as type: Float.Type) throws -> Float? { 647 | guard !(value is NSNull) else { return nil } 648 | 649 | guard let string = value as? String else { return nil } 650 | 651 | if let value = Double(string) { 652 | let number = NSNumber(value: value) 653 | 654 | guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { 655 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 656 | } 657 | 658 | let double = number.doubleValue 659 | guard abs(double) <= Double(Float.greatestFiniteMagnitude) else { 660 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed XML number \(number) does not fit in \(type).")) 661 | } 662 | 663 | return Float(double) 664 | } else if case let .convertFromString(posInfString, negInfString, nanString) = self.options.nonConformingFloatDecodingStrategy { 665 | if string == posInfString { 666 | return Float.infinity 667 | } else if string == negInfString { 668 | return -Float.infinity 669 | } else if string == nanString { 670 | return Float.nan 671 | } 672 | } 673 | 674 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 675 | } 676 | 677 | internal func unbox(_ value: Any, as type: Double.Type) throws -> Double? { 678 | guard !(value is NSNull) else { return nil } 679 | 680 | guard let string = value as? String else { return nil } 681 | 682 | if let number = Decimal(string: string) as NSDecimalNumber? { 683 | 684 | guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { 685 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 686 | } 687 | 688 | return number.doubleValue 689 | } else if case let .convertFromString(posInfString, negInfString, nanString) = self.options.nonConformingFloatDecodingStrategy { 690 | if string == posInfString { 691 | return Double.infinity 692 | } else if string == negInfString { 693 | return -Double.infinity 694 | } else if string == nanString { 695 | return Double.nan 696 | } 697 | } 698 | 699 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 700 | } 701 | 702 | internal func unbox(_ value: Any, as type: String.Type) throws -> String? { 703 | guard !(value is NSNull) else { return nil } 704 | 705 | guard let string = value as? String else { 706 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 707 | } 708 | 709 | return string 710 | } 711 | 712 | internal func unbox(_ value: Any, as type: Date.Type) throws -> Date? { 713 | guard !(value is NSNull) else { return nil } 714 | 715 | switch self.options.dateDecodingStrategy { 716 | case .deferredToDate: 717 | self.storage.push(container: value) 718 | defer { self.storage.popContainer() } 719 | return try Date(from: self) 720 | 721 | case .secondsSince1970: 722 | let double = try self.unbox(value, as: Double.self)! 723 | return Date(timeIntervalSince1970: double) 724 | 725 | case .millisecondsSince1970: 726 | let double = try self.unbox(value, as: Double.self)! 727 | return Date(timeIntervalSince1970: double / 1000.0) 728 | 729 | case .iso8601: 730 | if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) { 731 | let string = try self.unbox(value, as: String.self)! 732 | guard let date = _iso8601Formatter.date(from: string) else { 733 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Expected date string to be ISO8601-formatted.")) 734 | } 735 | 736 | return date 737 | } else { 738 | fatalError("ISO8601DateFormatter is unavailable on this platform.") 739 | } 740 | 741 | case .formatted(let formatter): 742 | let string = try self.unbox(value, as: String.self)! 743 | guard let date = formatter.date(from: string) else { 744 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Date string does not match format expected by formatter.")) 745 | } 746 | 747 | return date 748 | 749 | case .custom(let closure): 750 | self.storage.push(container: value) 751 | defer { self.storage.popContainer() } 752 | return try closure(self) 753 | } 754 | } 755 | 756 | internal func unbox(_ value: Any, as type: Data.Type) throws -> Data? { 757 | guard !(value is NSNull) else { return nil } 758 | 759 | switch self.options.dataDecodingStrategy { 760 | case .deferredToData: 761 | self.storage.push(container: value) 762 | defer { self.storage.popContainer() } 763 | return try Data(from: self) 764 | 765 | case .base64: 766 | guard let string = value as? String else { 767 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) 768 | } 769 | 770 | guard let data = Data(base64Encoded: string) else { 771 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Encountered Data is not valid Base64.")) 772 | } 773 | 774 | return data 775 | 776 | case .custom(let closure): 777 | self.storage.push(container: value) 778 | defer { self.storage.popContainer() } 779 | return try closure(self) 780 | } 781 | } 782 | 783 | internal func unbox(_ value: Any, as type: Decimal.Type) throws -> Decimal? { 784 | guard !(value is NSNull) else { return nil } 785 | 786 | // Attempt to bridge from NSDecimalNumber. 787 | let doubleValue = try self.unbox(value, as: Double.self)! 788 | return Decimal(doubleValue) 789 | } 790 | 791 | internal func unbox(_ value: Any, as type: T.Type) throws -> T? { 792 | let decoded: T 793 | if type == Date.self || type == NSDate.self { 794 | guard let date = try self.unbox(value, as: Date.self) else { return nil } 795 | decoded = date as! T 796 | } else if type == Data.self || type == NSData.self { 797 | guard let data = try self.unbox(value, as: Data.self) else { return nil } 798 | decoded = data as! T 799 | } else if type == URL.self || type == NSURL.self { 800 | guard let urlString = try self.unbox(value, as: String.self) else { 801 | return nil 802 | } 803 | 804 | guard let url = URL(string: urlString) else { 805 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, 806 | debugDescription: "Invalid URL string.")) 807 | } 808 | 809 | decoded = (url as! T) 810 | } else if type == Decimal.self || type == NSDecimalNumber.self { 811 | guard let decimal = try self.unbox(value, as: Decimal.self) else { return nil } 812 | decoded = decimal as! T 813 | } else { 814 | self.storage.push(container: value) 815 | defer { self.storage.popContainer() } 816 | return try type.init(from: self) 817 | } 818 | 819 | return decoded 820 | } 821 | } 822 | 823 | -------------------------------------------------------------------------------- /Sources/XMLParsing/Decoder/XMLDecodingStorage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XMLDecodingStorage.swift 3 | // XMLParsing 4 | // 5 | // Created by Shawn Moore on 11/20/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Decoding Storage 12 | 13 | internal struct _XMLDecodingStorage { 14 | // MARK: Properties 15 | 16 | /// The container stack. 17 | /// Elements may be any one of the XML types (String, [String : Any]). 18 | private(set) internal var containers: [Any] = [] 19 | 20 | // MARK: - Initialization 21 | 22 | /// Initializes `self` with no containers. 23 | internal init() {} 24 | 25 | // MARK: - Modifying the Stack 26 | 27 | internal var count: Int { 28 | return self.containers.count 29 | } 30 | 31 | internal var topContainer: Any { 32 | precondition(!self.containers.isEmpty, "Empty container stack.") 33 | return self.containers.last! 34 | } 35 | 36 | internal mutating func push(container: Any) { 37 | self.containers.append(container) 38 | } 39 | 40 | internal mutating func popContainer() { 41 | precondition(!self.containers.isEmpty, "Empty container stack.") 42 | self.containers.removeLast() 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /Sources/XMLParsing/Decoder/XMLKeyedDecodingContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XMLKeyedDecodingContainer.swift 3 | // XMLParsing 4 | // 5 | // Created by Shawn Moore on 11/21/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: Decoding Containers 12 | 13 | internal struct _XMLKeyedDecodingContainer : KeyedDecodingContainerProtocol { 14 | typealias Key = K 15 | 16 | // MARK: Properties 17 | 18 | /// A reference to the decoder we're reading from. 19 | private let decoder: _XMLDecoder 20 | 21 | /// A reference to the container we're reading from. 22 | private let container: [String : Any] 23 | 24 | /// The path of coding keys taken to get to this point in decoding. 25 | private(set) public var codingPath: [CodingKey] 26 | 27 | // MARK: - Initialization 28 | 29 | /// Initializes `self` by referencing the given decoder and container. 30 | internal init(referencing decoder: _XMLDecoder, wrapping container: [String : Any]) { 31 | self.decoder = decoder 32 | switch decoder.options.keyDecodingStrategy { 33 | case .useDefaultKeys: 34 | self.container = container 35 | case .convertFromSnakeCase: 36 | // Convert the snake case keys in the container to camel case. 37 | // If we hit a duplicate key after conversion, then we'll use the first one we saw. Effectively an undefined behavior with dictionaries. 38 | self.container = Dictionary(container.map { 39 | key, value in (XMLDecoder.KeyDecodingStrategy._convertFromSnakeCase(key), value) 40 | }, uniquingKeysWith: { (first, _) in first }) 41 | case .custom(let converter): 42 | self.container = Dictionary(container.map { 43 | key, value in (converter(decoder.codingPath + [_XMLKey(stringValue: key, intValue: nil)]).stringValue, value) 44 | }, uniquingKeysWith: { (first, _) in first }) 45 | } 46 | self.codingPath = decoder.codingPath 47 | } 48 | 49 | // MARK: - KeyedDecodingContainerProtocol Methods 50 | 51 | public var allKeys: [Key] { 52 | return self.container.keys.compactMap { Key(stringValue: $0) } 53 | } 54 | 55 | public func contains(_ key: Key) -> Bool { 56 | return self.container[key.stringValue] != nil 57 | } 58 | 59 | private func _errorDescription(of key: CodingKey) -> String { 60 | switch decoder.options.keyDecodingStrategy { 61 | case .convertFromSnakeCase: 62 | // In this case we can attempt to recover the original value by reversing the transform 63 | let original = key.stringValue 64 | let converted = XMLEncoder.KeyEncodingStrategy._convertToSnakeCase(original) 65 | if converted == original { 66 | return "\(key) (\"\(original)\")" 67 | } else { 68 | return "\(key) (\"\(original)\"), converted to \(converted)" 69 | } 70 | default: 71 | // Otherwise, just report the converted string 72 | return "\(key) (\"\(key.stringValue)\")" 73 | } 74 | } 75 | 76 | public func decodeNil(forKey key: Key) throws -> Bool { 77 | if let entry = self.container[key.stringValue] { 78 | return entry is NSNull 79 | } else { 80 | return true 81 | } 82 | } 83 | 84 | public func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { 85 | guard let entry = self.container[key.stringValue] else { 86 | throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key)).")) 87 | } 88 | 89 | self.decoder.codingPath.append(key) 90 | defer { self.decoder.codingPath.removeLast() } 91 | 92 | guard let value = try self.decoder.unbox(entry, as: Bool.self) else { 93 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) 94 | } 95 | 96 | return value 97 | } 98 | 99 | public func decode(_ type: Int.Type, forKey key: Key) throws -> Int { 100 | guard let entry = self.container[key.stringValue] else { 101 | throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key)).")) 102 | } 103 | 104 | self.decoder.codingPath.append(key) 105 | defer { self.decoder.codingPath.removeLast() } 106 | 107 | guard let value = try self.decoder.unbox(entry, as: Int.self) else { 108 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) 109 | } 110 | 111 | return value 112 | } 113 | 114 | public func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { 115 | guard let entry = self.container[key.stringValue] else { 116 | throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key)).")) 117 | } 118 | 119 | self.decoder.codingPath.append(key) 120 | defer { self.decoder.codingPath.removeLast() } 121 | 122 | guard let value = try self.decoder.unbox(entry, as: Int8.self) else { 123 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) 124 | } 125 | 126 | return value 127 | } 128 | 129 | public func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { 130 | guard let entry = self.container[key.stringValue] else { 131 | throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key)).")) 132 | } 133 | 134 | self.decoder.codingPath.append(key) 135 | defer { self.decoder.codingPath.removeLast() } 136 | 137 | guard let value = try self.decoder.unbox(entry, as: Int16.self) else { 138 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) 139 | } 140 | 141 | return value 142 | } 143 | 144 | public func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { 145 | guard let entry = self.container[key.stringValue] else { 146 | throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key)).")) 147 | } 148 | 149 | self.decoder.codingPath.append(key) 150 | defer { self.decoder.codingPath.removeLast() } 151 | 152 | guard let value = try self.decoder.unbox(entry, as: Int32.self) else { 153 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) 154 | } 155 | 156 | return value 157 | } 158 | 159 | public func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { 160 | guard let entry = self.container[key.stringValue] else { 161 | throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key)).")) 162 | } 163 | 164 | self.decoder.codingPath.append(key) 165 | defer { self.decoder.codingPath.removeLast() } 166 | 167 | guard let value = try self.decoder.unbox(entry, as: Int64.self) else { 168 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) 169 | } 170 | 171 | return value 172 | } 173 | 174 | public func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { 175 | guard let entry = self.container[key.stringValue] else { 176 | throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key)).")) 177 | } 178 | 179 | self.decoder.codingPath.append(key) 180 | defer { self.decoder.codingPath.removeLast() } 181 | 182 | guard let value = try self.decoder.unbox(entry, as: UInt.self) else { 183 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) 184 | } 185 | 186 | return value 187 | } 188 | 189 | public func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { 190 | guard let entry = self.container[key.stringValue] else { 191 | throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key)).")) 192 | } 193 | 194 | self.decoder.codingPath.append(key) 195 | defer { self.decoder.codingPath.removeLast() } 196 | 197 | guard let value = try self.decoder.unbox(entry, as: UInt8.self) else { 198 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) 199 | } 200 | 201 | return value 202 | } 203 | 204 | public func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { 205 | guard let entry = self.container[key.stringValue] else { 206 | throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key)).")) 207 | } 208 | 209 | self.decoder.codingPath.append(key) 210 | defer { self.decoder.codingPath.removeLast() } 211 | 212 | guard let value = try self.decoder.unbox(entry, as: UInt16.self) else { 213 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) 214 | } 215 | 216 | return value 217 | } 218 | 219 | public func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { 220 | guard let entry = self.container[key.stringValue] else { 221 | throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key)).")) 222 | } 223 | 224 | self.decoder.codingPath.append(key) 225 | defer { self.decoder.codingPath.removeLast() } 226 | 227 | guard let value = try self.decoder.unbox(entry, as: UInt32.self) else { 228 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) 229 | } 230 | 231 | return value 232 | } 233 | 234 | public func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { 235 | guard let entry = self.container[key.stringValue] else { 236 | throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key)).")) 237 | } 238 | 239 | self.decoder.codingPath.append(key) 240 | defer { self.decoder.codingPath.removeLast() } 241 | 242 | guard let value = try self.decoder.unbox(entry, as: UInt64.self) else { 243 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) 244 | } 245 | 246 | return value 247 | } 248 | 249 | public func decode(_ type: Float.Type, forKey key: Key) throws -> Float { 250 | guard let entry = self.container[key.stringValue] else { 251 | throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key)).")) 252 | } 253 | 254 | self.decoder.codingPath.append(key) 255 | defer { self.decoder.codingPath.removeLast() } 256 | 257 | guard let value = try self.decoder.unbox(entry, as: Float.self) else { 258 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) 259 | } 260 | 261 | return value 262 | } 263 | 264 | public func decode(_ type: Double.Type, forKey key: Key) throws -> Double { 265 | guard let entry = self.container[key.stringValue] else { 266 | throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key)).")) 267 | } 268 | 269 | self.decoder.codingPath.append(key) 270 | defer { self.decoder.codingPath.removeLast() } 271 | 272 | guard let value = try self.decoder.unbox(entry, as: Double.self) else { 273 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) 274 | } 275 | 276 | return value 277 | } 278 | 279 | public func decode(_ type: String.Type, forKey key: Key) throws -> String { 280 | guard let entry = self.container[key.stringValue] else { 281 | throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key)).")) 282 | } 283 | 284 | self.decoder.codingPath.append(key) 285 | defer { self.decoder.codingPath.removeLast() } 286 | 287 | guard let value = try self.decoder.unbox(entry, as: String.self) else { 288 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) 289 | } 290 | 291 | return value 292 | } 293 | 294 | public func decode(_ type: T.Type, forKey key: Key) throws -> T { 295 | guard let entry = self.container[key.stringValue] else { 296 | throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key)).")) 297 | } 298 | 299 | self.decoder.codingPath.append(key) 300 | defer { self.decoder.codingPath.removeLast() } 301 | 302 | guard let value = try self.decoder.unbox(entry, as: type) else { 303 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) 304 | } 305 | 306 | return value 307 | } 308 | 309 | public func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer { 310 | self.decoder.codingPath.append(key) 311 | defer { self.decoder.codingPath.removeLast() } 312 | 313 | guard let value = self.container[key.stringValue] else { 314 | throw DecodingError.keyNotFound(key, 315 | DecodingError.Context(codingPath: self.codingPath, 316 | debugDescription: "Cannot get \(KeyedDecodingContainer.self) -- no value found for key \"\(key.stringValue)\"")) 317 | } 318 | 319 | guard let dictionary = value as? [String : Any] else { 320 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: value) 321 | } 322 | 323 | let container = _XMLKeyedDecodingContainer(referencing: self.decoder, wrapping: dictionary) 324 | return KeyedDecodingContainer(container) 325 | } 326 | 327 | public func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { 328 | self.decoder.codingPath.append(key) 329 | defer { self.decoder.codingPath.removeLast() } 330 | 331 | guard let value = self.container[key.stringValue] else { 332 | throw DecodingError.keyNotFound(key, 333 | DecodingError.Context(codingPath: self.codingPath, 334 | debugDescription: "Cannot get UnkeyedDecodingContainer -- no value found for key \"\(key.stringValue)\"")) 335 | } 336 | 337 | guard let array = value as? [Any] else { 338 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: [Any].self, reality: value) 339 | } 340 | 341 | return _XMLUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array) 342 | } 343 | 344 | private func _superDecoder(forKey key: CodingKey) throws -> Decoder { 345 | self.decoder.codingPath.append(key) 346 | defer { self.decoder.codingPath.removeLast() } 347 | 348 | let value: Any = self.container[key.stringValue] ?? NSNull() 349 | return _XMLDecoder(referencing: value, at: self.decoder.codingPath, options: self.decoder.options) 350 | } 351 | 352 | public func superDecoder() throws -> Decoder { 353 | return try _superDecoder(forKey: _XMLKey.super) 354 | } 355 | 356 | public func superDecoder(forKey key: Key) throws -> Decoder { 357 | return try _superDecoder(forKey: key) 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /Sources/XMLParsing/Decoder/XMLUnkeyedDecodingContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XMLUnkeyedDecodingContainer.swift 3 | // XMLParsing 4 | // 5 | // Created by Shawn Moore on 11/21/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal struct _XMLUnkeyedDecodingContainer : UnkeyedDecodingContainer { 12 | // MARK: Properties 13 | 14 | /// A reference to the decoder we're reading from. 15 | private let decoder: _XMLDecoder 16 | 17 | /// A reference to the container we're reading from. 18 | private let container: [Any] 19 | 20 | /// The path of coding keys taken to get to this point in decoding. 21 | private(set) public var codingPath: [CodingKey] 22 | 23 | /// The index of the element we're about to decode. 24 | private(set) public var currentIndex: Int 25 | 26 | // MARK: - Initialization 27 | 28 | /// Initializes `self` by referencing the given decoder and container. 29 | internal init(referencing decoder: _XMLDecoder, wrapping container: [Any]) { 30 | self.decoder = decoder 31 | self.container = container 32 | self.codingPath = decoder.codingPath 33 | self.currentIndex = 0 34 | } 35 | 36 | // MARK: - UnkeyedDecodingContainer Methods 37 | 38 | public var count: Int? { 39 | return self.container.count 40 | } 41 | 42 | public var isAtEnd: Bool { 43 | return self.currentIndex >= self.count! 44 | } 45 | 46 | public mutating func decodeNil() throws -> Bool { 47 | guard !self.isAtEnd else { 48 | throw DecodingError.valueNotFound(Any?.self, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) 49 | } 50 | 51 | if self.container[self.currentIndex] is NSNull { 52 | self.currentIndex += 1 53 | return true 54 | } else { 55 | return false 56 | } 57 | } 58 | 59 | public mutating func decode(_ type: Bool.Type) throws -> Bool { 60 | guard !self.isAtEnd else { 61 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) 62 | } 63 | 64 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 65 | defer { self.decoder.codingPath.removeLast() } 66 | 67 | guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Bool.self) else { 68 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) 69 | } 70 | 71 | self.currentIndex += 1 72 | return decoded 73 | } 74 | 75 | public mutating func decode(_ type: Int.Type) throws -> Int { 76 | guard !self.isAtEnd else { 77 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) 78 | } 79 | 80 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 81 | defer { self.decoder.codingPath.removeLast() } 82 | 83 | guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int.self) else { 84 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) 85 | } 86 | 87 | self.currentIndex += 1 88 | return decoded 89 | } 90 | 91 | public mutating func decode(_ type: Int8.Type) throws -> Int8 { 92 | guard !self.isAtEnd else { 93 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) 94 | } 95 | 96 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 97 | defer { self.decoder.codingPath.removeLast() } 98 | 99 | guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int8.self) else { 100 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) 101 | } 102 | 103 | self.currentIndex += 1 104 | return decoded 105 | } 106 | 107 | public mutating func decode(_ type: Int16.Type) throws -> Int16 { 108 | guard !self.isAtEnd else { 109 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) 110 | } 111 | 112 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 113 | defer { self.decoder.codingPath.removeLast() } 114 | 115 | guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int16.self) else { 116 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) 117 | } 118 | 119 | self.currentIndex += 1 120 | return decoded 121 | } 122 | 123 | public mutating func decode(_ type: Int32.Type) throws -> Int32 { 124 | guard !self.isAtEnd else { 125 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) 126 | } 127 | 128 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 129 | defer { self.decoder.codingPath.removeLast() } 130 | 131 | guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int32.self) else { 132 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) 133 | } 134 | 135 | self.currentIndex += 1 136 | return decoded 137 | } 138 | 139 | public mutating func decode(_ type: Int64.Type) throws -> Int64 { 140 | guard !self.isAtEnd else { 141 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) 142 | } 143 | 144 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 145 | defer { self.decoder.codingPath.removeLast() } 146 | 147 | guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int64.self) else { 148 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) 149 | } 150 | 151 | self.currentIndex += 1 152 | return decoded 153 | } 154 | 155 | public mutating func decode(_ type: UInt.Type) throws -> UInt { 156 | guard !self.isAtEnd else { 157 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) 158 | } 159 | 160 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 161 | defer { self.decoder.codingPath.removeLast() } 162 | 163 | guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt.self) else { 164 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) 165 | } 166 | 167 | self.currentIndex += 1 168 | return decoded 169 | } 170 | 171 | public mutating func decode(_ type: UInt8.Type) throws -> UInt8 { 172 | guard !self.isAtEnd else { 173 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) 174 | } 175 | 176 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 177 | defer { self.decoder.codingPath.removeLast() } 178 | 179 | guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt8.self) else { 180 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) 181 | } 182 | 183 | self.currentIndex += 1 184 | return decoded 185 | } 186 | 187 | public mutating func decode(_ type: UInt16.Type) throws -> UInt16 { 188 | guard !self.isAtEnd else { 189 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) 190 | } 191 | 192 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 193 | defer { self.decoder.codingPath.removeLast() } 194 | 195 | guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt16.self) else { 196 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) 197 | } 198 | 199 | self.currentIndex += 1 200 | return decoded 201 | } 202 | 203 | public mutating func decode(_ type: UInt32.Type) throws -> UInt32 { 204 | guard !self.isAtEnd else { 205 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) 206 | } 207 | 208 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 209 | defer { self.decoder.codingPath.removeLast() } 210 | 211 | guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt32.self) else { 212 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) 213 | } 214 | 215 | self.currentIndex += 1 216 | return decoded 217 | } 218 | 219 | public mutating func decode(_ type: UInt64.Type) throws -> UInt64 { 220 | guard !self.isAtEnd else { 221 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) 222 | } 223 | 224 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 225 | defer { self.decoder.codingPath.removeLast() } 226 | 227 | guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt64.self) else { 228 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) 229 | } 230 | 231 | self.currentIndex += 1 232 | return decoded 233 | } 234 | 235 | public mutating func decode(_ type: Float.Type) throws -> Float { 236 | guard !self.isAtEnd else { 237 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) 238 | } 239 | 240 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 241 | defer { self.decoder.codingPath.removeLast() } 242 | 243 | guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Float.self) else { 244 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) 245 | } 246 | 247 | self.currentIndex += 1 248 | return decoded 249 | } 250 | 251 | public mutating func decode(_ type: Double.Type) throws -> Double { 252 | guard !self.isAtEnd else { 253 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) 254 | } 255 | 256 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 257 | defer { self.decoder.codingPath.removeLast() } 258 | 259 | guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Double.self) else { 260 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) 261 | } 262 | 263 | self.currentIndex += 1 264 | return decoded 265 | } 266 | 267 | public mutating func decode(_ type: String.Type) throws -> String { 268 | guard !self.isAtEnd else { 269 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) 270 | } 271 | 272 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 273 | defer { self.decoder.codingPath.removeLast() } 274 | 275 | guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: String.self) else { 276 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) 277 | } 278 | 279 | self.currentIndex += 1 280 | return decoded 281 | } 282 | 283 | public mutating func decode(_ type: T.Type) throws -> T { 284 | guard !self.isAtEnd else { 285 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) 286 | } 287 | 288 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 289 | defer { self.decoder.codingPath.removeLast() } 290 | 291 | guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: type) else { 292 | throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_XMLKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) 293 | } 294 | 295 | self.currentIndex += 1 296 | return decoded 297 | } 298 | 299 | public mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer { 300 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 301 | defer { self.decoder.codingPath.removeLast() } 302 | 303 | guard !self.isAtEnd else { 304 | throw DecodingError.valueNotFound(KeyedDecodingContainer.self, 305 | DecodingError.Context(codingPath: self.codingPath, 306 | debugDescription: "Cannot get nested keyed container -- unkeyed container is at end.")) 307 | } 308 | 309 | let value = self.container[self.currentIndex] 310 | guard !(value is NSNull) else { 311 | throw DecodingError.valueNotFound(KeyedDecodingContainer.self, 312 | DecodingError.Context(codingPath: self.codingPath, 313 | debugDescription: "Cannot get keyed decoding container -- found null value instead.")) 314 | } 315 | 316 | guard let dictionary = value as? [String : Any] else { 317 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: value) 318 | } 319 | 320 | self.currentIndex += 1 321 | let container = _XMLKeyedDecodingContainer(referencing: self.decoder, wrapping: dictionary) 322 | return KeyedDecodingContainer(container) 323 | } 324 | 325 | public mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { 326 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 327 | defer { self.decoder.codingPath.removeLast() } 328 | 329 | guard !self.isAtEnd else { 330 | throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, 331 | DecodingError.Context(codingPath: self.codingPath, 332 | debugDescription: "Cannot get nested keyed container -- unkeyed container is at end.")) 333 | } 334 | 335 | let value = self.container[self.currentIndex] 336 | guard !(value is NSNull) else { 337 | throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, 338 | DecodingError.Context(codingPath: self.codingPath, 339 | debugDescription: "Cannot get keyed decoding container -- found null value instead.")) 340 | } 341 | 342 | guard let array = value as? [Any] else { 343 | throw DecodingError._typeMismatch(at: self.codingPath, expectation: [Any].self, reality: value) 344 | } 345 | 346 | self.currentIndex += 1 347 | return _XMLUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array) 348 | } 349 | 350 | public mutating func superDecoder() throws -> Decoder { 351 | self.decoder.codingPath.append(_XMLKey(index: self.currentIndex)) 352 | defer { self.decoder.codingPath.removeLast() } 353 | 354 | guard !self.isAtEnd else { 355 | throw DecodingError.valueNotFound(Decoder.self, 356 | DecodingError.Context(codingPath: self.codingPath, 357 | debugDescription: "Cannot get superDecoder() -- unkeyed container is at end.")) 358 | } 359 | 360 | let value = self.container[self.currentIndex] 361 | self.currentIndex += 1 362 | return _XMLDecoder(referencing: value, at: self.decoder.codingPath, options: self.decoder.options) 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /Sources/XMLParsing/Encoder/EncodingErrorExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EncodingErrorExtension.swift 3 | // XMLParsing 4 | // 5 | // Created by Shawn Moore on 11/22/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | //===----------------------------------------------------------------------===// 12 | // Error Utilities 13 | //===----------------------------------------------------------------------===// 14 | internal extension EncodingError { 15 | /// Returns a `.invalidValue` error describing the given invalid floating-point value. 16 | /// 17 | /// 18 | /// - parameter value: The value that was invalid to encode. 19 | /// - parameter path: The path of `CodingKey`s taken to encode this value. 20 | /// - returns: An `EncodingError` with the appropriate path and debug description. 21 | internal static func _invalidFloatingPointValue(_ value: T, at codingPath: [CodingKey]) -> EncodingError { 22 | let valueDescription: String 23 | if value == T.infinity { 24 | valueDescription = "\(T.self).infinity" 25 | } else if value == -T.infinity { 26 | valueDescription = "-\(T.self).infinity" 27 | } else { 28 | valueDescription = "\(T.self).nan" 29 | } 30 | 31 | let debugDescription = "Unable to encode \(valueDescription) directly in XML. Use XMLEncoder.NonConformingFloatEncodingStrategy.convertToString to specify how the value should be encoded." 32 | return .invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: debugDescription)) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/XMLParsing/Encoder/XMLEncoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XMLEncoder.swift 3 | // XMLParsing 4 | // 5 | // Created by Shawn Moore on 11/22/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | //===----------------------------------------------------------------------===// 12 | // XML Encoder 13 | //===----------------------------------------------------------------------===// 14 | 15 | /// `XMLEncoder` facilitates the encoding of `Encodable` values into XML. 16 | open class XMLEncoder { 17 | // MARK: Options 18 | /// The formatting of the output XML data. 19 | public struct OutputFormatting : OptionSet { 20 | /// The format's default value. 21 | public let rawValue: UInt 22 | 23 | /// Creates an OutputFormatting value with the given raw value. 24 | public init(rawValue: UInt) { 25 | self.rawValue = rawValue 26 | } 27 | 28 | /// Produce human-readable XML with indented output. 29 | public static let prettyPrinted = OutputFormatting(rawValue: 1 << 0) 30 | 31 | /// Produce XML with dictionary keys sorted in lexicographic order. 32 | @available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) 33 | public static let sortedKeys = OutputFormatting(rawValue: 1 << 1) 34 | } 35 | 36 | /// The strategy to use for encoding `Date` values. 37 | public enum DateEncodingStrategy { 38 | /// Defer to `Date` for choosing an encoding. This is the default strategy. 39 | case deferredToDate 40 | 41 | /// Encode the `Date` as a UNIX timestamp (as a XML number). 42 | case secondsSince1970 43 | 44 | /// Encode the `Date` as UNIX millisecond timestamp (as a XML number). 45 | case millisecondsSince1970 46 | 47 | /// Encode the `Date` as an ISO-8601-formatted string (in RFC 3339 format). 48 | @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) 49 | case iso8601 50 | 51 | /// Encode the `Date` as a string formatted by the given formatter. 52 | case formatted(DateFormatter) 53 | 54 | /// Encode the `Date` as a custom value encoded by the given closure. 55 | /// 56 | /// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place. 57 | case custom((Date, Encoder) throws -> Void) 58 | } 59 | 60 | /// The strategy to use for encoding `String` values. 61 | public enum StringEncodingStrategy { 62 | /// Defer to `String` for choosing an encoding. This is the default strategy. 63 | case deferredToString 64 | 65 | /// Encoded the `String` as a CData-encoded string. 66 | case cdata 67 | } 68 | 69 | /// The strategy to use for encoding `Data` values. 70 | public enum DataEncodingStrategy { 71 | /// Defer to `Data` for choosing an encoding. 72 | case deferredToData 73 | 74 | /// Encoded the `Data` as a Base64-encoded string. This is the default strategy. 75 | case base64 76 | 77 | /// Encode the `Data` as a custom value encoded by the given closure. 78 | /// 79 | /// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place. 80 | case custom((Data, Encoder) throws -> Void) 81 | } 82 | 83 | /// The strategy to use for non-XML-conforming floating-point values (IEEE 754 infinity and NaN). 84 | public enum NonConformingFloatEncodingStrategy { 85 | /// Throw upon encountering non-conforming values. This is the default strategy. 86 | case `throw` 87 | 88 | /// Encode the values using the given representation strings. 89 | case convertToString(positiveInfinity: String, negativeInfinity: String, nan: String) 90 | } 91 | 92 | /// The strategy to use for automatically changing the value of keys before encoding. 93 | public enum KeyEncodingStrategy { 94 | /// Use the keys specified by each type. This is the default strategy. 95 | case useDefaultKeys 96 | 97 | /// Convert from "camelCaseKeys" to "snake_case_keys" before writing a key to XML payload. 98 | /// 99 | /// Capital characters are determined by testing membership in `CharacterSet.uppercaseLetters` and `CharacterSet.lowercaseLetters` (Unicode General Categories Lu and Lt). 100 | /// The conversion to lower case uses `Locale.system`, also known as the ICU "root" locale. This means the result is consistent regardless of the current user's locale and language preferences. 101 | /// 102 | /// Converting from camel case to snake case: 103 | /// 1. Splits words at the boundary of lower-case to upper-case 104 | /// 2. Inserts `_` between words 105 | /// 3. Lowercases the entire string 106 | /// 4. Preserves starting and ending `_`. 107 | /// 108 | /// For example, `oneTwoThree` becomes `one_two_three`. `_oneTwoThree_` becomes `_one_two_three_`. 109 | /// 110 | /// - Note: Using a key encoding strategy has a nominal performance cost, as each string key has to be converted. 111 | case convertToSnakeCase 112 | 113 | /// Provide a custom conversion to the key in the encoded XML from the keys specified by the encoded types. 114 | /// The full path to the current encoding position is provided for context (in case you need to locate this key within the payload). The returned key is used in place of the last component in the coding path before encoding. 115 | /// If the result of the conversion is a duplicate key, then only one value will be present in the result. 116 | case custom((_ codingPath: [CodingKey]) -> CodingKey) 117 | 118 | internal static func _convertToSnakeCase(_ stringKey: String) -> String { 119 | guard !stringKey.isEmpty else { return stringKey } 120 | 121 | var words : [Range] = [] 122 | // The general idea of this algorithm is to split words on transition from lower to upper case, then on transition of >1 upper case characters to lowercase 123 | // 124 | // myProperty -> my_property 125 | // myURLProperty -> my_url_property 126 | // 127 | // We assume, per Swift naming conventions, that the first character of the key is lowercase. 128 | var wordStart = stringKey.startIndex 129 | var searchRange = stringKey.index(after: wordStart)..1 capital letters. Turn those into a word, stopping at the capital before the lower case character. 152 | let beforeLowerIndex = stringKey.index(before: lowerCaseRange.lowerBound) 153 | words.append(upperCaseRange.lowerBound.. Bool) 175 | } 176 | 177 | /// The output format to produce. Defaults to `[]`. 178 | open var outputFormatting: OutputFormatting = [] 179 | 180 | /// The strategy to use in encoding dates. Defaults to `.deferredToDate`. 181 | open var dateEncodingStrategy: DateEncodingStrategy = .deferredToDate 182 | 183 | /// The strategy to use in encoding binary data. Defaults to `.base64`. 184 | open var dataEncodingStrategy: DataEncodingStrategy = .base64 185 | 186 | /// The strategy to use in encoding non-conforming numbers. Defaults to `.throw`. 187 | open var nonConformingFloatEncodingStrategy: NonConformingFloatEncodingStrategy = .throw 188 | 189 | /// The strategy to use for encoding keys. Defaults to `.useDefaultKeys`. 190 | open var keyEncodingStrategy: KeyEncodingStrategy = .useDefaultKeys 191 | 192 | /// The strategy to use in encoding encoding attributes. Defaults to `.deferredToEncoder`. 193 | open var attributeEncodingStrategy: AttributeEncodingStrategy = .deferredToEncoder 194 | 195 | /// The strategy to use in encoding strings. Defaults to `.deferredToString`. 196 | open var stringEncodingStrategy: StringEncodingStrategy = .deferredToString 197 | 198 | /// Contextual user-provided information for use during encoding. 199 | open var userInfo: [CodingUserInfoKey : Any] = [:] 200 | 201 | /// Options set on the top-level encoder to pass down the encoding hierarchy. 202 | internal struct _Options { 203 | let dateEncodingStrategy: DateEncodingStrategy 204 | let dataEncodingStrategy: DataEncodingStrategy 205 | let nonConformingFloatEncodingStrategy: NonConformingFloatEncodingStrategy 206 | let keyEncodingStrategy: KeyEncodingStrategy 207 | let attributeEncodingStrategy: AttributeEncodingStrategy 208 | let stringEncodingStrategy: StringEncodingStrategy 209 | let userInfo: [CodingUserInfoKey : Any] 210 | } 211 | 212 | /// The options set on the top-level encoder. 213 | internal var options: _Options { 214 | return _Options(dateEncodingStrategy: dateEncodingStrategy, 215 | dataEncodingStrategy: dataEncodingStrategy, 216 | nonConformingFloatEncodingStrategy: nonConformingFloatEncodingStrategy, 217 | keyEncodingStrategy: keyEncodingStrategy, 218 | attributeEncodingStrategy: attributeEncodingStrategy, 219 | stringEncodingStrategy: stringEncodingStrategy, 220 | userInfo: userInfo) 221 | } 222 | 223 | // MARK: - Constructing a XML Encoder 224 | /// Initializes `self` with default strategies. 225 | public init() {} 226 | 227 | // MARK: - Encoding Values 228 | /// Encodes the given top-level value and returns its XML representation. 229 | /// 230 | /// - parameter value: The value to encode. 231 | /// - parameter withRootKey: the key used to wrap the encoded values. 232 | /// - returns: A new `Data` value containing the encoded XML data. 233 | /// - throws: `EncodingError.invalidValue` if a non-conforming floating-point value is encountered during encoding, and the encoding strategy is `.throw`. 234 | /// - throws: An error if any value throws an error during encoding. 235 | open func encode(_ value: T, withRootKey rootKey: String, header: XMLHeader? = nil) throws -> Data { 236 | let encoder = _XMLEncoder(options: self.options) 237 | 238 | guard let topLevel = try encoder.box_(value) else { 239 | throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) did not encode any values.")) 240 | } 241 | 242 | if topLevel is NSNull { 243 | throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) encoded as null XML fragment.")) 244 | } else if topLevel is NSNumber { 245 | throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) encoded as number XML fragment.")) 246 | } else if topLevel is NSString { 247 | throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) encoded as string XML fragment.")) 248 | } 249 | 250 | guard let element = _XMLElement.createRootElement(rootKey: rootKey, object: topLevel) else { 251 | throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value to XML.")) 252 | } 253 | 254 | return element.toXMLString(with: header, withCDATA: stringEncodingStrategy != .deferredToString).data(using: .utf8, allowLossyConversion: true)! 255 | } 256 | } 257 | 258 | internal class _XMLEncoder: Encoder { 259 | // MARK: Properties 260 | 261 | /// The encoder's storage. 262 | internal var storage: _XMLEncodingStorage 263 | 264 | /// Options set on the top-level encoder. 265 | internal let options: XMLEncoder._Options 266 | 267 | /// The path to the current point in encoding. 268 | public var codingPath: [CodingKey] 269 | 270 | /// Contextual user-provided information for use during encoding. 271 | public var userInfo: [CodingUserInfoKey : Any] { 272 | return self.options.userInfo 273 | } 274 | 275 | // MARK: - Initialization 276 | 277 | /// Initializes `self` with the given top-level encoder options. 278 | internal init(options: XMLEncoder._Options, codingPath: [CodingKey] = []) { 279 | self.options = options 280 | self.storage = _XMLEncodingStorage() 281 | self.codingPath = codingPath 282 | } 283 | 284 | /// Returns whether a new element can be encoded at this coding path. 285 | /// 286 | /// `true` if an element has not yet been encoded at this coding path; `false` otherwise. 287 | internal var canEncodeNewValue: Bool { 288 | // Every time a new value gets encoded, the key it's encoded for is pushed onto the coding path (even if it's a nil key from an unkeyed container). 289 | // At the same time, every time a container is requested, a new value gets pushed onto the storage stack. 290 | // If there are more values on the storage stack than on the coding path, it means the value is requesting more than one container, which violates the precondition. 291 | // 292 | // This means that anytime something that can request a new container goes onto the stack, we MUST push a key onto the coding path. 293 | // Things which will not request containers do not need to have the coding path extended for them (but it doesn't matter if it is, because they will not reach here). 294 | return self.storage.count == self.codingPath.count 295 | } 296 | 297 | // MARK: - Encoder Methods 298 | public func container(keyedBy: Key.Type) -> KeyedEncodingContainer { 299 | // If an existing keyed container was already requested, return that one. 300 | let topContainer: NSMutableDictionary 301 | if self.canEncodeNewValue { 302 | // We haven't yet pushed a container at this level; do so here. 303 | topContainer = self.storage.pushKeyedContainer() 304 | } else { 305 | guard let container = self.storage.containers.last as? NSMutableDictionary else { 306 | preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.") 307 | } 308 | 309 | topContainer = container 310 | } 311 | 312 | let container = _XMLKeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) 313 | return KeyedEncodingContainer(container) 314 | } 315 | 316 | public func unkeyedContainer() -> UnkeyedEncodingContainer { 317 | // If an existing unkeyed container was already requested, return that one. 318 | let topContainer: NSMutableArray 319 | if self.canEncodeNewValue { 320 | // We haven't yet pushed a container at this level; do so here. 321 | topContainer = self.storage.pushUnkeyedContainer() 322 | } else { 323 | guard let container = self.storage.containers.last as? NSMutableArray else { 324 | preconditionFailure("Attempt to push new unkeyed encoding container when already previously encoded at this path.") 325 | } 326 | 327 | topContainer = container 328 | } 329 | 330 | return _XMLUnkeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) 331 | } 332 | 333 | public func singleValueContainer() -> SingleValueEncodingContainer { 334 | return self 335 | } 336 | } 337 | 338 | // MARK: - Encoding Containers 339 | 340 | fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingContainerProtocol { 341 | typealias Key = K 342 | 343 | // MARK: Properties 344 | 345 | /// A reference to the encoder we're writing to. 346 | private let encoder: _XMLEncoder 347 | 348 | /// A reference to the container we're writing to. 349 | private let container: NSMutableDictionary 350 | 351 | /// The path of coding keys taken to get to this point in encoding. 352 | private(set) public var codingPath: [CodingKey] 353 | 354 | // MARK: - Initialization 355 | 356 | /// Initializes `self` with the given references. 357 | fileprivate init(referencing encoder: _XMLEncoder, codingPath: [CodingKey], wrapping container: NSMutableDictionary) { 358 | self.encoder = encoder 359 | self.codingPath = codingPath 360 | self.container = container 361 | } 362 | 363 | // MARK: - Coding Path Operations 364 | 365 | private func _converted(_ key: CodingKey) -> CodingKey { 366 | switch encoder.options.keyEncodingStrategy { 367 | case .useDefaultKeys: 368 | return key 369 | case .convertToSnakeCase: 370 | let newKeyString = XMLEncoder.KeyEncodingStrategy._convertToSnakeCase(key.stringValue) 371 | return _XMLKey(stringValue: newKeyString, intValue: key.intValue) 372 | case .custom(let converter): 373 | return converter(codingPath + [key]) 374 | } 375 | } 376 | 377 | // MARK: - KeyedEncodingContainerProtocol Methods 378 | 379 | public mutating func encodeNil(forKey key: Key) throws { 380 | self.container[_converted(key).stringValue] = NSNull() 381 | } 382 | 383 | public mutating func encode(_ value: Bool, forKey key: Key) throws { 384 | self.encoder.codingPath.append(key) 385 | defer { self.encoder.codingPath.removeLast() } 386 | switch self.encoder.options.attributeEncodingStrategy { 387 | case .custom(let closure) where closure(self.encoder): 388 | if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { 389 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 390 | } else { 391 | let attributesContainer = NSMutableDictionary() 392 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 393 | self.container[_XMLElement.attributesKey] = attributesContainer 394 | } 395 | default: 396 | self.container[_converted(key).stringValue] = self.encoder.box(value) 397 | } 398 | } 399 | 400 | public mutating func encode(_ value: Int, forKey key: Key) throws { 401 | self.encoder.codingPath.append(key) 402 | defer { self.encoder.codingPath.removeLast() } 403 | switch self.encoder.options.attributeEncodingStrategy { 404 | case .custom(let closure) where closure(self.encoder): 405 | if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { 406 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 407 | } else { 408 | let attributesContainer = NSMutableDictionary() 409 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 410 | self.container[_XMLElement.attributesKey] = attributesContainer 411 | } 412 | default: 413 | self.container[_converted(key).stringValue] = self.encoder.box(value) 414 | } 415 | } 416 | 417 | public mutating func encode(_ value: Int8, forKey key: Key) throws { 418 | self.encoder.codingPath.append(key) 419 | defer { self.encoder.codingPath.removeLast() } 420 | switch self.encoder.options.attributeEncodingStrategy { 421 | case .custom(let closure) where closure(self.encoder): 422 | if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { 423 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 424 | } else { 425 | let attributesContainer = NSMutableDictionary() 426 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 427 | self.container[_XMLElement.attributesKey] = attributesContainer 428 | } 429 | default: 430 | self.container[_converted(key).stringValue] = self.encoder.box(value) 431 | } 432 | } 433 | 434 | public mutating func encode(_ value: Int16, forKey key: Key) throws { 435 | self.encoder.codingPath.append(key) 436 | defer { self.encoder.codingPath.removeLast() } 437 | switch self.encoder.options.attributeEncodingStrategy { 438 | case .custom(let closure) where closure(self.encoder): 439 | if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { 440 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 441 | } else { 442 | let attributesContainer = NSMutableDictionary() 443 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 444 | self.container[_XMLElement.attributesKey] = attributesContainer 445 | } 446 | default: 447 | self.container[_converted(key).stringValue] = self.encoder.box(value) 448 | } 449 | } 450 | 451 | public mutating func encode(_ value: Int32, forKey key: Key) throws { 452 | self.encoder.codingPath.append(key) 453 | defer { self.encoder.codingPath.removeLast() } 454 | switch self.encoder.options.attributeEncodingStrategy { 455 | case .custom(let closure) where closure(self.encoder): 456 | if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { 457 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 458 | } else { 459 | let attributesContainer = NSMutableDictionary() 460 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 461 | self.container[_XMLElement.attributesKey] = attributesContainer 462 | } 463 | default: 464 | self.container[_converted(key).stringValue] = self.encoder.box(value) 465 | } 466 | } 467 | 468 | public mutating func encode(_ value: Int64, forKey key: Key) throws { 469 | self.encoder.codingPath.append(key) 470 | defer { self.encoder.codingPath.removeLast() } 471 | switch self.encoder.options.attributeEncodingStrategy { 472 | case .custom(let closure) where closure(self.encoder): 473 | if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { 474 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 475 | } else { 476 | let attributesContainer = NSMutableDictionary() 477 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 478 | self.container[_XMLElement.attributesKey] = attributesContainer 479 | } 480 | default: 481 | self.container[_converted(key).stringValue] = self.encoder.box(value) 482 | } 483 | } 484 | 485 | public mutating func encode(_ value: UInt, forKey key: Key) throws { 486 | self.encoder.codingPath.append(key) 487 | defer { self.encoder.codingPath.removeLast() } 488 | switch self.encoder.options.attributeEncodingStrategy { 489 | case .custom(let closure) where closure(self.encoder): 490 | if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { 491 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 492 | } else { 493 | let attributesContainer = NSMutableDictionary() 494 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 495 | self.container[_XMLElement.attributesKey] = attributesContainer 496 | } 497 | default: 498 | self.container[_converted(key).stringValue] = self.encoder.box(value) 499 | } 500 | } 501 | 502 | public mutating func encode(_ value: UInt8, forKey key: Key) throws { 503 | self.encoder.codingPath.append(key) 504 | defer { self.encoder.codingPath.removeLast() } 505 | switch self.encoder.options.attributeEncodingStrategy { 506 | case .custom(let closure) where closure(self.encoder): 507 | if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { 508 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 509 | } else { 510 | let attributesContainer = NSMutableDictionary() 511 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 512 | self.container[_XMLElement.attributesKey] = attributesContainer 513 | } 514 | default: 515 | self.container[_converted(key).stringValue] = self.encoder.box(value) 516 | } 517 | } 518 | 519 | public mutating func encode(_ value: UInt16, forKey key: Key) throws { 520 | self.encoder.codingPath.append(key) 521 | defer { self.encoder.codingPath.removeLast() } 522 | switch self.encoder.options.attributeEncodingStrategy { 523 | case .custom(let closure) where closure(self.encoder): 524 | if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { 525 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 526 | } else { 527 | let attributesContainer = NSMutableDictionary() 528 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 529 | self.container[_XMLElement.attributesKey] = attributesContainer 530 | } 531 | default: 532 | self.container[_converted(key).stringValue] = self.encoder.box(value) 533 | } 534 | } 535 | 536 | public mutating func encode(_ value: UInt32, forKey key: Key) throws { 537 | self.encoder.codingPath.append(key) 538 | defer { self.encoder.codingPath.removeLast() } 539 | switch self.encoder.options.attributeEncodingStrategy { 540 | case .custom(let closure) where closure(self.encoder): 541 | if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { 542 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 543 | } else { 544 | let attributesContainer = NSMutableDictionary() 545 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 546 | self.container[_XMLElement.attributesKey] = attributesContainer 547 | } 548 | default: 549 | self.container[_converted(key).stringValue] = self.encoder.box(value) 550 | } 551 | } 552 | 553 | public mutating func encode(_ value: UInt64, forKey key: Key) throws { 554 | self.encoder.codingPath.append(key) 555 | defer { self.encoder.codingPath.removeLast() } 556 | switch self.encoder.options.attributeEncodingStrategy { 557 | case .custom(let closure) where closure(self.encoder): 558 | if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { 559 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 560 | } else { 561 | let attributesContainer = NSMutableDictionary() 562 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 563 | self.container[_XMLElement.attributesKey] = attributesContainer 564 | } 565 | default: 566 | self.container[_converted(key).stringValue] = self.encoder.box(value) 567 | } 568 | } 569 | 570 | public mutating func encode(_ value: String, forKey key: Key) throws { 571 | self.encoder.codingPath.append(key) 572 | defer { self.encoder.codingPath.removeLast() } 573 | switch self.encoder.options.attributeEncodingStrategy { 574 | case .custom(let closure) where closure(self.encoder): 575 | if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { 576 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 577 | } else { 578 | let attributesContainer = NSMutableDictionary() 579 | attributesContainer[_converted(key).stringValue] = self.encoder.box(value) 580 | self.container[_XMLElement.attributesKey] = attributesContainer 581 | } 582 | default: 583 | self.container[_converted(key).stringValue] = self.encoder.box(value) 584 | } 585 | } 586 | 587 | public mutating func encode(_ value: Float, forKey key: Key) throws { 588 | // Since the float may be invalid and throw, the coding path needs to contain this key. 589 | self.encoder.codingPath.append(key) 590 | defer { self.encoder.codingPath.removeLast() } 591 | self.container[_converted(key).stringValue] = try self.encoder.box(value) 592 | } 593 | 594 | public mutating func encode(_ value: Double, forKey key: Key) throws { 595 | // Since the double may be invalid and throw, the coding path needs to contain this key. 596 | self.encoder.codingPath.append(key) 597 | defer { self.encoder.codingPath.removeLast() } 598 | switch self.encoder.options.attributeEncodingStrategy { 599 | case .custom(let closure) where closure(self.encoder): 600 | if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { 601 | attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) 602 | } else { 603 | let attributesContainer = NSMutableDictionary() 604 | attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) 605 | self.container[_XMLElement.attributesKey] = attributesContainer 606 | } 607 | default: 608 | self.container[_converted(key).stringValue] = try self.encoder.box(value) 609 | } 610 | } 611 | 612 | public mutating func encode(_ value: T, forKey key: Key) throws { 613 | self.encoder.codingPath.append(key) 614 | defer { self.encoder.codingPath.removeLast() } 615 | 616 | if T.self == Date.self || T.self == NSDate.self { 617 | switch self.encoder.options.attributeEncodingStrategy { 618 | case .custom(let closure) where closure(self.encoder): 619 | if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { 620 | attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) 621 | } else { 622 | let attributesContainer = NSMutableDictionary() 623 | attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) 624 | self.container[_XMLElement.attributesKey] = attributesContainer 625 | } 626 | default: 627 | self.container[_converted(key).stringValue] = try self.encoder.box(value) 628 | } 629 | } else { 630 | self.container[_converted(key).stringValue] = try self.encoder.box(value) 631 | } 632 | } 633 | 634 | public mutating func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer { 635 | let dictionary = NSMutableDictionary() 636 | self.container[_converted(key).stringValue] = dictionary 637 | 638 | self.codingPath.append(key) 639 | defer { self.codingPath.removeLast() } 640 | 641 | let container = _XMLKeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary) 642 | return KeyedEncodingContainer(container) 643 | } 644 | 645 | public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { 646 | let array = NSMutableArray() 647 | self.container[_converted(key).stringValue] = array 648 | 649 | self.codingPath.append(key) 650 | defer { self.codingPath.removeLast() } 651 | return _XMLUnkeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: array) 652 | } 653 | 654 | public mutating func superEncoder() -> Encoder { 655 | return _XMLReferencingEncoder(referencing: self.encoder, key: _XMLKey.super, convertedKey: _converted(_XMLKey.super), wrapping: self.container) 656 | } 657 | 658 | public mutating func superEncoder(forKey key: Key) -> Encoder { 659 | return _XMLReferencingEncoder(referencing: self.encoder, key: key, convertedKey: _converted(key), wrapping: self.container) 660 | } 661 | } 662 | 663 | fileprivate struct _XMLUnkeyedEncodingContainer : UnkeyedEncodingContainer { 664 | // MARK: Properties 665 | 666 | /// A reference to the encoder we're writing to. 667 | private let encoder: _XMLEncoder 668 | 669 | /// A reference to the container we're writing to. 670 | private let container: NSMutableArray 671 | 672 | /// The path of coding keys taken to get to this point in encoding. 673 | private(set) public var codingPath: [CodingKey] 674 | 675 | /// The number of elements encoded into the container. 676 | public var count: Int { 677 | return self.container.count 678 | } 679 | 680 | // MARK: - Initialization 681 | 682 | /// Initializes `self` with the given references. 683 | fileprivate init(referencing encoder: _XMLEncoder, codingPath: [CodingKey], wrapping container: NSMutableArray) { 684 | self.encoder = encoder 685 | self.codingPath = codingPath 686 | self.container = container 687 | } 688 | 689 | // MARK: - UnkeyedEncodingContainer Methods 690 | 691 | public mutating func encodeNil() throws { self.container.add(NSNull()) } 692 | public mutating func encode(_ value: Bool) throws { self.container.add(self.encoder.box(value)) } 693 | public mutating func encode(_ value: Int) throws { self.container.add(self.encoder.box(value)) } 694 | public mutating func encode(_ value: Int8) throws { self.container.add(self.encoder.box(value)) } 695 | public mutating func encode(_ value: Int16) throws { self.container.add(self.encoder.box(value)) } 696 | public mutating func encode(_ value: Int32) throws { self.container.add(self.encoder.box(value)) } 697 | public mutating func encode(_ value: Int64) throws { self.container.add(self.encoder.box(value)) } 698 | public mutating func encode(_ value: UInt) throws { self.container.add(self.encoder.box(value)) } 699 | public mutating func encode(_ value: UInt8) throws { self.container.add(self.encoder.box(value)) } 700 | public mutating func encode(_ value: UInt16) throws { self.container.add(self.encoder.box(value)) } 701 | public mutating func encode(_ value: UInt32) throws { self.container.add(self.encoder.box(value)) } 702 | public mutating func encode(_ value: UInt64) throws { self.container.add(self.encoder.box(value)) } 703 | public mutating func encode(_ value: String) throws { self.container.add(self.encoder.box(value)) } 704 | 705 | public mutating func encode(_ value: Float) throws { 706 | // Since the float may be invalid and throw, the coding path needs to contain this key. 707 | self.encoder.codingPath.append(_XMLKey(index: self.count)) 708 | defer { self.encoder.codingPath.removeLast() } 709 | self.container.add(try self.encoder.box(value)) 710 | } 711 | 712 | public mutating func encode(_ value: Double) throws { 713 | // Since the double may be invalid and throw, the coding path needs to contain this key. 714 | self.encoder.codingPath.append(_XMLKey(index: self.count)) 715 | defer { self.encoder.codingPath.removeLast() } 716 | self.container.add(try self.encoder.box(value)) 717 | } 718 | 719 | public mutating func encode(_ value: T) throws { 720 | self.encoder.codingPath.append(_XMLKey(index: self.count)) 721 | defer { self.encoder.codingPath.removeLast() } 722 | self.container.add(try self.encoder.box(value)) 723 | } 724 | 725 | public mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer { 726 | self.codingPath.append(_XMLKey(index: self.count)) 727 | defer { self.codingPath.removeLast() } 728 | 729 | let dictionary = NSMutableDictionary() 730 | self.container.add(dictionary) 731 | 732 | let container = _XMLKeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary) 733 | return KeyedEncodingContainer(container) 734 | } 735 | 736 | public mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { 737 | self.codingPath.append(_XMLKey(index: self.count)) 738 | defer { self.codingPath.removeLast() } 739 | 740 | let array = NSMutableArray() 741 | self.container.add(array) 742 | return _XMLUnkeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: array) 743 | } 744 | 745 | public mutating func superEncoder() -> Encoder { 746 | return _XMLReferencingEncoder(referencing: self.encoder, at: self.container.count, wrapping: self.container) 747 | } 748 | } 749 | 750 | extension _XMLEncoder: SingleValueEncodingContainer { 751 | // MARK: - SingleValueEncodingContainer Methods 752 | 753 | fileprivate func assertCanEncodeNewValue() { 754 | precondition(self.canEncodeNewValue, "Attempt to encode value through single value container when previously value already encoded.") 755 | } 756 | 757 | public func encodeNil() throws { 758 | assertCanEncodeNewValue() 759 | self.storage.push(container: NSNull()) 760 | } 761 | 762 | public func encode(_ value: Bool) throws { 763 | assertCanEncodeNewValue() 764 | self.storage.push(container: self.box(value)) 765 | } 766 | 767 | public func encode(_ value: Int) throws { 768 | assertCanEncodeNewValue() 769 | self.storage.push(container: self.box(value)) 770 | } 771 | 772 | public func encode(_ value: Int8) throws { 773 | assertCanEncodeNewValue() 774 | self.storage.push(container: self.box(value)) 775 | } 776 | 777 | public func encode(_ value: Int16) throws { 778 | assertCanEncodeNewValue() 779 | self.storage.push(container: self.box(value)) 780 | } 781 | 782 | public func encode(_ value: Int32) throws { 783 | assertCanEncodeNewValue() 784 | self.storage.push(container: self.box(value)) 785 | } 786 | 787 | public func encode(_ value: Int64) throws { 788 | assertCanEncodeNewValue() 789 | self.storage.push(container: self.box(value)) 790 | } 791 | 792 | public func encode(_ value: UInt) throws { 793 | assertCanEncodeNewValue() 794 | self.storage.push(container: self.box(value)) 795 | } 796 | 797 | public func encode(_ value: UInt8) throws { 798 | assertCanEncodeNewValue() 799 | self.storage.push(container: self.box(value)) 800 | } 801 | 802 | public func encode(_ value: UInt16) throws { 803 | assertCanEncodeNewValue() 804 | self.storage.push(container: self.box(value)) 805 | } 806 | 807 | public func encode(_ value: UInt32) throws { 808 | assertCanEncodeNewValue() 809 | self.storage.push(container: self.box(value)) 810 | } 811 | 812 | public func encode(_ value: UInt64) throws { 813 | assertCanEncodeNewValue() 814 | self.storage.push(container: self.box(value)) 815 | } 816 | 817 | public func encode(_ value: String) throws { 818 | assertCanEncodeNewValue() 819 | self.storage.push(container: self.box(value)) 820 | } 821 | 822 | public func encode(_ value: Float) throws { 823 | assertCanEncodeNewValue() 824 | try self.storage.push(container: self.box(value)) 825 | } 826 | 827 | public func encode(_ value: Double) throws { 828 | assertCanEncodeNewValue() 829 | try self.storage.push(container: self.box(value)) 830 | } 831 | 832 | public func encode(_ value: T) throws { 833 | assertCanEncodeNewValue() 834 | try self.storage.push(container: self.box(value)) 835 | } 836 | } 837 | 838 | extension _XMLEncoder { 839 | /// Returns the given value boxed in a container appropriate for pushing onto the container stack. 840 | fileprivate func box(_ value: Bool) -> NSObject { return NSNumber(value: value) } 841 | fileprivate func box(_ value: Int) -> NSObject { return NSNumber(value: value) } 842 | fileprivate func box(_ value: Int8) -> NSObject { return NSNumber(value: value) } 843 | fileprivate func box(_ value: Int16) -> NSObject { return NSNumber(value: value) } 844 | fileprivate func box(_ value: Int32) -> NSObject { return NSNumber(value: value) } 845 | fileprivate func box(_ value: Int64) -> NSObject { return NSNumber(value: value) } 846 | fileprivate func box(_ value: UInt) -> NSObject { return NSNumber(value: value) } 847 | fileprivate func box(_ value: UInt8) -> NSObject { return NSNumber(value: value) } 848 | fileprivate func box(_ value: UInt16) -> NSObject { return NSNumber(value: value) } 849 | fileprivate func box(_ value: UInt32) -> NSObject { return NSNumber(value: value) } 850 | fileprivate func box(_ value: UInt64) -> NSObject { return NSNumber(value: value) } 851 | fileprivate func box(_ value: String) -> NSObject { return NSString(string: value) } 852 | 853 | internal func box(_ value: Float) throws -> NSObject { 854 | if value.isInfinite || value.isNaN { 855 | guard case let .convertToString(positiveInfinity: posInfString, negativeInfinity: negInfString, nan: nanString) = self.options.nonConformingFloatEncodingStrategy else { 856 | throw EncodingError._invalidFloatingPointValue(value, at: codingPath) 857 | } 858 | 859 | if value == Float.infinity { 860 | return posInfString as NSObject 861 | } else if value == -Float.infinity { 862 | return negInfString as NSObject 863 | } else { 864 | return nanString as NSObject 865 | } 866 | } else { 867 | return NSNumber(value: value) 868 | } 869 | } 870 | 871 | internal func box(_ value: Double) throws -> NSObject { 872 | if value.isInfinite || value.isNaN { 873 | guard case let .convertToString(positiveInfinity: posInfString, negativeInfinity: negInfString, nan: nanString) = self.options.nonConformingFloatEncodingStrategy else { 874 | throw EncodingError._invalidFloatingPointValue(value, at: codingPath) 875 | } 876 | 877 | if value == Double.infinity { 878 | return posInfString as NSObject 879 | } else if value == -Double.infinity { 880 | return negInfString as NSObject 881 | } else { 882 | return nanString as NSObject 883 | } 884 | } else { 885 | return NSNumber(value: value) 886 | } 887 | } 888 | 889 | internal func box(_ value: Date) throws -> NSObject { 890 | switch self.options.dateEncodingStrategy { 891 | case .deferredToDate: 892 | try value.encode(to: self) 893 | return self.storage.popContainer() 894 | case .secondsSince1970: 895 | return NSNumber(value: value.timeIntervalSince1970) 896 | case .millisecondsSince1970: 897 | return NSNumber(value: value.timeIntervalSince1970 * 1000.0) 898 | case .iso8601: 899 | if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) { 900 | return _iso8601Formatter.string(from: value) as NSObject 901 | } else { 902 | fatalError("ISO8601DateFormatter is unavailable on this platform.") 903 | } 904 | case .formatted(let formatter): 905 | return formatter.string(from: value) as NSObject 906 | case .custom(let closure): 907 | let depth = self.storage.count 908 | try closure(value, self) 909 | 910 | guard self.storage.count > depth else { return NSDictionary() } 911 | 912 | return self.storage.popContainer() 913 | } 914 | } 915 | 916 | internal func box(_ value: Data) throws -> NSObject { 917 | switch self.options.dataEncodingStrategy { 918 | case .deferredToData: 919 | try value.encode(to: self) 920 | return self.storage.popContainer() 921 | case .base64: 922 | return value.base64EncodedString() as NSObject 923 | case .custom(let closure): 924 | let depth = self.storage.count 925 | try closure(value, self) 926 | 927 | guard self.storage.count > depth else { return NSDictionary() } 928 | 929 | return self.storage.popContainer() as NSObject 930 | } 931 | } 932 | 933 | fileprivate func box(_ value: T) throws -> NSObject { 934 | return try self.box_(value) ?? NSDictionary() 935 | } 936 | 937 | // This method is called "box_" instead of "box" to disambiguate it from the overloads. Because the return type here is different from all of the "box" overloads (and is more general), any "box" calls in here would call back into "box" recursively instead of calling the appropriate overload, which is not what we want. 938 | fileprivate func box_(_ value: T) throws -> NSObject? { 939 | if T.self == Date.self || T.self == NSDate.self { 940 | return try self.box((value as! Date)) 941 | } else if T.self == Data.self || T.self == NSData.self { 942 | return try self.box((value as! Data)) 943 | } else if T.self == URL.self || T.self == NSURL.self { 944 | return self.box((value as! URL).absoluteString) 945 | } else if T.self == Decimal.self || T.self == NSDecimalNumber.self { 946 | return (value as! NSDecimalNumber) 947 | } 948 | 949 | let depth = self.storage.count 950 | try value.encode(to: self) 951 | 952 | // The top container should be a new container. 953 | guard self.storage.count > depth else { 954 | return nil 955 | } 956 | 957 | return self.storage.popContainer() 958 | } 959 | } 960 | 961 | -------------------------------------------------------------------------------- /Sources/XMLParsing/Encoder/XMLEncodingStorage.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // XMLEncodingStorage.swift 4 | // XMLParsing 5 | // 6 | // Created by Shawn Moore on 11/22/17. 7 | // Copyright © 2017 Shawn Moore. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | // MARK: - Encoding Storage and Containers 13 | 14 | internal struct _XMLEncodingStorage { 15 | // MARK: Properties 16 | 17 | /// The container stack. 18 | /// Elements may be any one of the XML types (NSNull, NSNumber, NSString, NSArray, NSDictionary). 19 | private(set) internal var containers: [NSObject] = [] 20 | 21 | // MARK: - Initialization 22 | 23 | /// Initializes `self` with no containers. 24 | internal init() {} 25 | 26 | // MARK: - Modifying the Stack 27 | 28 | internal var count: Int { 29 | return self.containers.count 30 | } 31 | 32 | internal mutating func pushKeyedContainer() -> NSMutableDictionary { 33 | let dictionary = NSMutableDictionary() 34 | self.containers.append(dictionary) 35 | return dictionary 36 | } 37 | 38 | internal mutating func pushUnkeyedContainer() -> NSMutableArray { 39 | let array = NSMutableArray() 40 | self.containers.append(array) 41 | return array 42 | } 43 | 44 | internal mutating func push(container: NSObject) { 45 | self.containers.append(container) 46 | } 47 | 48 | internal mutating func popContainer() -> NSObject { 49 | precondition(!self.containers.isEmpty, "Empty container stack.") 50 | return self.containers.popLast()! 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/XMLParsing/Encoder/XMLReferencingEncoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XMLReferencingEncoder.swift 3 | // XMLParsing 4 | // 5 | // Created by Shawn Moore on 11/25/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - _XMLReferencingEncoder 12 | 13 | /// _XMLReferencingEncoder is a special subclass of _XMLEncoder which has its own storage, but references the contents of a different encoder. 14 | /// It's used in superEncoder(), which returns a new encoder for encoding a superclass -- the lifetime of the encoder should not escape the scope it's created in, but it doesn't necessarily know when it's done being used (to write to the original container). 15 | internal class _XMLReferencingEncoder : _XMLEncoder { 16 | // MARK: Reference types. 17 | 18 | /// The type of container we're referencing. 19 | private enum Reference { 20 | /// Referencing a specific index in an array container. 21 | case array(NSMutableArray, Int) 22 | 23 | /// Referencing a specific key in a dictionary container. 24 | case dictionary(NSMutableDictionary, String) 25 | } 26 | 27 | // MARK: - Properties 28 | 29 | /// The encoder we're referencing. 30 | internal let encoder: _XMLEncoder 31 | 32 | /// The container reference itself. 33 | private let reference: Reference 34 | 35 | // MARK: - Initialization 36 | 37 | /// Initializes `self` by referencing the given array container in the given encoder. 38 | internal init(referencing encoder: _XMLEncoder, at index: Int, wrapping array: NSMutableArray) { 39 | self.encoder = encoder 40 | self.reference = .array(array, index) 41 | super.init(options: encoder.options, codingPath: encoder.codingPath) 42 | 43 | self.codingPath.append(_XMLKey(index: index)) 44 | } 45 | 46 | /// Initializes `self` by referencing the given dictionary container in the given encoder. 47 | internal init(referencing encoder: _XMLEncoder, 48 | key: CodingKey, convertedKey: CodingKey, wrapping dictionary: NSMutableDictionary) { 49 | self.encoder = encoder 50 | self.reference = .dictionary(dictionary, convertedKey.stringValue) 51 | super.init(options: encoder.options, codingPath: encoder.codingPath) 52 | 53 | self.codingPath.append(key) 54 | } 55 | 56 | // MARK: - Coding Path Operations 57 | 58 | internal override var canEncodeNewValue: Bool { 59 | // With a regular encoder, the storage and coding path grow together. 60 | // A referencing encoder, however, inherits its parents coding path, as well as the key it was created for. 61 | // We have to take this into account. 62 | return self.storage.count == self.codingPath.count - self.encoder.codingPath.count - 1 63 | } 64 | 65 | // MARK: - Deinitialization 66 | 67 | // Finalizes `self` by writing the contents of our storage to the referenced encoder's storage. 68 | deinit { 69 | let value: Any 70 | switch self.storage.count { 71 | case 0: value = NSDictionary() 72 | case 1: value = self.storage.popContainer() 73 | default: fatalError("Referencing encoder deallocated with multiple containers on stack.") 74 | } 75 | 76 | switch self.reference { 77 | case .array(let array, let index): 78 | array.insert(value, at: index) 79 | 80 | case .dictionary(let dictionary, let key): 81 | dictionary[NSString(string: key)] = value 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Sources/XMLParsing/ISO8601DateFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ISO8601DateFormatter.swift 3 | // XMLParsing 4 | // 5 | // Created by Shawn Moore on 11/21/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | //===----------------------------------------------------------------------===// 12 | // Shared ISO8601 Date Formatter 13 | //===----------------------------------------------------------------------===// 14 | 15 | // NOTE: This value is implicitly lazy and _must_ be lazy. We're compiled against the latest SDK (w/ ISO8601DateFormatter), but linked against whichever Foundation the user has. ISO8601DateFormatter might not exist, so we better not hit this code path on an older OS. 16 | @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) 17 | internal var _iso8601Formatter: ISO8601DateFormatter = { 18 | let formatter = ISO8601DateFormatter() 19 | formatter.formatOptions = .withInternetDateTime 20 | return formatter 21 | }() 22 | 23 | 24 | -------------------------------------------------------------------------------- /Sources/XMLParsing/XMLKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XMLKey.swift 3 | // XMLParsing 4 | // 5 | // Created by Shawn Moore on 11/21/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | //===----------------------------------------------------------------------===// 12 | // Shared Key Types 13 | //===----------------------------------------------------------------------===// 14 | 15 | internal struct _XMLKey : CodingKey { 16 | public var stringValue: String 17 | public var intValue: Int? 18 | 19 | public init?(stringValue: String) { 20 | self.stringValue = stringValue 21 | self.intValue = nil 22 | } 23 | 24 | public init?(intValue: Int) { 25 | self.stringValue = "\(intValue)" 26 | self.intValue = intValue 27 | } 28 | 29 | public init(stringValue: String, intValue: Int?) { 30 | self.stringValue = stringValue 31 | self.intValue = intValue 32 | } 33 | 34 | internal init(index: Int) { 35 | self.stringValue = "Index \(index)" 36 | self.intValue = index 37 | } 38 | 39 | internal static let `super` = _XMLKey(stringValue: "super")! 40 | } 41 | 42 | 43 | -------------------------------------------------------------------------------- /Sources/XMLParsing/XMLStackParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XMLStackParser.swift 3 | // CustomEncoder 4 | // 5 | // Created by Shawn Moore on 11/14/17. 6 | // Copyright © 2017 Shawn Moore. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | //===----------------------------------------------------------------------===// 12 | // Data Representation 13 | //===----------------------------------------------------------------------===// 14 | 15 | public struct XMLHeader { 16 | /// the XML standard that the produced document conforms to. 17 | var version: Double? = nil 18 | /// the encoding standard used to represent the characters in the produced document. 19 | var encoding: String? = nil 20 | /// indicates whetehr a document relies on information from an external source. 21 | var standalone: String? = nil 22 | 23 | init(version: Double? = nil) { 24 | self.version = version 25 | } 26 | 27 | init(version: Double?, encoding: String?, standalone: String? = nil) { 28 | self.version = version 29 | self.encoding = encoding 30 | self.standalone = standalone 31 | } 32 | 33 | internal func isEmpty() -> Bool { 34 | return version == nil && encoding == nil && standalone == nil 35 | } 36 | 37 | internal func toXML() -> String? { 38 | guard !self.isEmpty() else { return nil } 39 | 40 | var string = "\n" 55 | } 56 | } 57 | 58 | internal class _XMLElement { 59 | static let attributesKey = "___ATTRIBUTES" 60 | static let escapedCharacterSet = [("&", "&"), ("<", "<"), (">", ">"), ( "'", "'"), ("\"", """)] 61 | 62 | var key: String 63 | var value: String? = nil 64 | var attributes: [String: String] = [:] 65 | var children: [String: [_XMLElement]] = [:] 66 | 67 | internal init(key: String, value: String? = nil, attributes: [String: String] = [:], children: [String: [_XMLElement]] = [:]) { 68 | self.key = key 69 | self.value = value 70 | self.attributes = attributes 71 | self.children = children 72 | } 73 | 74 | convenience init(key: String, value: Optional, attributes: [String: CustomStringConvertible] = [:]) { 75 | self.init(key: key, value: value?.description, attributes: attributes.mapValues({ $0.description }), children: [:]) 76 | } 77 | 78 | convenience init(key: String, children: [String: [_XMLElement]], attributes: [String: CustomStringConvertible] = [:]) { 79 | self.init(key: key, value: nil, attributes: attributes.mapValues({ $0.description }), children: children) 80 | } 81 | 82 | static func createRootElement(rootKey: String, object: NSObject) -> _XMLElement? { 83 | let element = _XMLElement(key: rootKey) 84 | 85 | if let object = object as? NSDictionary { 86 | _XMLElement.modifyElement(element: element, parentElement: nil, key: nil, object: object) 87 | } else if let object = object as? NSArray { 88 | _XMLElement.createElement(parentElement: element, key: rootKey, object: object) 89 | } 90 | 91 | return element 92 | } 93 | 94 | fileprivate static func createElement(parentElement: _XMLElement?, key: String, object: NSDictionary) { 95 | let element = _XMLElement(key: key) 96 | 97 | modifyElement(element: element, parentElement: parentElement, key: key, object: object) 98 | } 99 | 100 | fileprivate static func modifyElement(element: _XMLElement, parentElement: _XMLElement?, key: String?, object: NSDictionary) { 101 | element.attributes = (object[_XMLElement.attributesKey] as? [String: Any])?.mapValues({ String(describing: $0) }) ?? [:] 102 | 103 | let objects: [(String, NSObject)] = object.compactMap({ 104 | guard let key = $0 as? String, let value = $1 as? NSObject, key != _XMLElement.attributesKey else { return nil } 105 | 106 | return (key, value) 107 | }) 108 | 109 | for (key, value) in objects { 110 | if let dict = value as? NSDictionary { 111 | _XMLElement.createElement(parentElement: element, key: key, object: dict) 112 | } else if let array = value as? NSArray { 113 | _XMLElement.createElement(parentElement: element, key: key, object: array) 114 | } else if let string = value as? NSString { 115 | _XMLElement.createElement(parentElement: element, key: key, object: string) 116 | } else if let number = value as? NSNumber { 117 | _XMLElement.createElement(parentElement: element, key: key, object: number) 118 | } else { 119 | _XMLElement.createElement(parentElement: element, key: key, object: NSNull()) 120 | } 121 | } 122 | 123 | if let parentElement = parentElement, let key = key { 124 | parentElement.children[key] = (parentElement.children[key] ?? []) + [element] 125 | } 126 | } 127 | 128 | fileprivate static func createElement(parentElement: _XMLElement, key: String, object: NSArray) { 129 | let objects = object.compactMap({ $0 as? NSObject }) 130 | objects.forEach({ 131 | if let dict = $0 as? NSDictionary { 132 | _XMLElement.createElement(parentElement: parentElement, key: key, object: dict) 133 | } else if let array = $0 as? NSArray { 134 | _XMLElement.createElement(parentElement: parentElement, key: key, object: array) 135 | } else if let string = $0 as? NSString { 136 | _XMLElement.createElement(parentElement: parentElement, key: key, object: string) 137 | } else if let number = $0 as? NSNumber { 138 | _XMLElement.createElement(parentElement: parentElement, key: key, object: number) 139 | } else { 140 | _XMLElement.createElement(parentElement: parentElement, key: key, object: NSNull()) 141 | } 142 | }) 143 | } 144 | 145 | fileprivate static func createElement(parentElement: _XMLElement, key: String, object: NSNumber) { 146 | let element = _XMLElement(key: key, value: object.description) 147 | parentElement.children[key] = (parentElement.children[key] ?? []) + [element] 148 | } 149 | 150 | fileprivate static func createElement(parentElement: _XMLElement, key: String, object: NSString) { 151 | let element = _XMLElement(key: key, value: object.description) 152 | parentElement.children[key] = (parentElement.children[key] ?? []) + [element] 153 | } 154 | 155 | fileprivate static func createElement(parentElement: _XMLElement, key: String, object: NSNull) { 156 | let element = _XMLElement(key: key) 157 | parentElement.children[key] = (parentElement.children[key] ?? []) + [element] 158 | } 159 | 160 | func flatten() -> [String: Any] { 161 | var node: [String: Any] = attributes 162 | 163 | for childElement in children { 164 | for child in childElement.value { 165 | if let content = child.value { 166 | if let oldContent = node[childElement.key] as? Array { 167 | node[childElement.key] = oldContent + [content] 168 | 169 | } else if let oldContent = node[childElement.key] { 170 | node[childElement.key] = [oldContent, content] 171 | 172 | } else { 173 | node[childElement.key] = content 174 | } 175 | } else if !child.children.isEmpty || !child.attributes.isEmpty { 176 | let newValue = child.flatten() 177 | 178 | if let existingValue = node[childElement.key] { 179 | if var array = existingValue as? Array { 180 | array.append(newValue) 181 | node[childElement.key] = array 182 | } else { 183 | node[childElement.key] = [existingValue, newValue] 184 | } 185 | } else { 186 | node[childElement.key] = newValue 187 | } 188 | } 189 | } 190 | } 191 | 192 | return node 193 | } 194 | 195 | func toXMLString(with header: XMLHeader? = nil, withCDATA cdata: Bool, ignoreEscaping: Bool = false) -> String { 196 | if let header = header, let headerXML = header.toXML() { 197 | return headerXML + _toXMLString(withCDATA: cdata) 198 | } else { 199 | return _toXMLString(withCDATA: cdata) 200 | } 201 | } 202 | 203 | fileprivate func _toXMLString(indented level: Int = 0, withCDATA cdata: Bool, ignoreEscaping: Bool = false) -> String { 204 | var string = String(repeating: " ", count: level * 4) 205 | string += "<\(key)" 206 | 207 | for (key, value) in attributes { 208 | string += " \(key)=\"\(value.escape(_XMLElement.escapedCharacterSet))\"" 209 | } 210 | 211 | if let value = value { 212 | string += ">" 213 | if !ignoreEscaping { 214 | string += (cdata == true ? "" : "\(value.escape(_XMLElement.escapedCharacterSet))" ) 215 | } else { 216 | string += "\(value)" 217 | } 218 | string += "" 219 | } else if !children.isEmpty { 220 | string += ">\n" 221 | 222 | for childElement in children { 223 | for child in childElement.value { 224 | string += child._toXMLString(indented: level + 1, withCDATA: cdata) 225 | string += "\n" 226 | } 227 | } 228 | 229 | string += String(repeating: " ", count: level * 4) 230 | string += "" 231 | } else { 232 | string += " />" 233 | } 234 | 235 | return string 236 | } 237 | } 238 | 239 | extension String { 240 | func escape(_ characterSet: [(character: String, escapedCharacter: String)]) -> String { 241 | var string = self 242 | 243 | for set in characterSet { 244 | string = string.replacingOccurrences(of: set.character, with: set.escapedCharacter, options: .literal) 245 | } 246 | 247 | return string 248 | } 249 | } 250 | 251 | internal class _XMLStackParser: NSObject, XMLParserDelegate { 252 | var root: _XMLElement? 253 | var stack = [_XMLElement]() 254 | var currentNode: _XMLElement? 255 | 256 | var currentElementName: String? 257 | var currentElementData = "" 258 | 259 | static func parse(with data: Data) throws -> [String: Any] { 260 | let parser = _XMLStackParser() 261 | 262 | do { 263 | if let node = try parser.parse(with: data) { 264 | return node.flatten() 265 | } else { 266 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data could not be parsed into XML.")) 267 | } 268 | } catch { 269 | throw error 270 | } 271 | } 272 | 273 | func parse(with data: Data) throws -> _XMLElement? { 274 | let xmlParser = XMLParser(data: data) 275 | xmlParser.delegate = self 276 | 277 | if xmlParser.parse() { 278 | return root 279 | } else if let error = xmlParser.parserError { 280 | throw error 281 | } else { 282 | return nil 283 | } 284 | } 285 | 286 | func parserDidStartDocument(_ parser: XMLParser) { 287 | root = nil 288 | stack = [_XMLElement]() 289 | } 290 | 291 | func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) { 292 | let node = _XMLElement(key: elementName) 293 | node.attributes = attributeDict 294 | stack.append(node) 295 | 296 | if let currentNode = currentNode { 297 | if currentNode.children[elementName] != nil { 298 | currentNode.children[elementName]?.append(node) 299 | } else { 300 | currentNode.children[elementName] = [node] 301 | } 302 | } 303 | currentNode = node 304 | } 305 | 306 | func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { 307 | if let poppedNode = stack.popLast(){ 308 | if let content = poppedNode.value?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) { 309 | if content.isEmpty { 310 | poppedNode.value = nil 311 | } else { 312 | poppedNode.value = content 313 | } 314 | } 315 | 316 | if (stack.isEmpty) { 317 | root = poppedNode 318 | currentNode = nil 319 | } else { 320 | currentNode = stack.last 321 | } 322 | } 323 | } 324 | 325 | func parser(_ parser: XMLParser, foundCharacters string: String) { 326 | currentNode?.value = (currentNode?.value ?? "") + string 327 | } 328 | 329 | func parser(_ parser: XMLParser, foundCDATA CDATABlock: Data) { 330 | if let string = String(data: CDATABlock, encoding: .utf8) { 331 | currentNode?.value = (currentNode?.value ?? "") + string 332 | } 333 | } 334 | 335 | func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) { 336 | print(parseError) 337 | } 338 | } 339 | 340 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import XMLParsingTests 3 | 4 | XCTMain([ 5 | testCase(XMLParsingTests.allTests), 6 | ]) 7 | -------------------------------------------------------------------------------- /Tests/XMLParsingTests/XMLParsingTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import XMLParsing 3 | 4 | class XMLParsingTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | // XCTAssertEqual(XMLParsing().text, "Hello, World!") 10 | } 11 | 12 | 13 | static var allTests = [ 14 | ("testExample", testExample), 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /XMLParsing.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "XMLParsing" 3 | s.version = "0.0.3" 4 | s.summary = "XMLEncoder & XMLDecoder using the Codable protocol in Swift 4" 5 | s.description = "XMLParsing allows Swift 4 Codable-conforming objects to be translated to and from XML" 6 | s.homepage = "https://github.com/ShawnMoore/XMLParsing" 7 | s.license = { :type => "MIT", :file => "LICENSE" } 8 | s.author = { "Shawn Moore" => "sm5@me.com" } 9 | s.ios.deployment_target = "10.0" 10 | s.osx.deployment_target = "10.12" 11 | s.source = { :git => "https://github.com/ShawnMoore/XMLParsing.git", :tag => s.version.to_s } 12 | s.source_files = "Sources/XMLParsing/**/*.swift" 13 | s.requires_arc = true 14 | end 15 | -------------------------------------------------------------------------------- /XMLParsing.xcodeproj/XMLParsingTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /XMLParsing.xcodeproj/XMLParsing_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /XMLParsing.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXAggregateTarget section */ 10 | "XMLParsing::XMLParsingPackageTests::ProductTarget" /* XMLParsingPackageTests */ = { 11 | isa = PBXAggregateTarget; 12 | buildConfigurationList = OBJ_64 /* Build configuration list for PBXAggregateTarget "XMLParsingPackageTests" */; 13 | buildPhases = ( 14 | ); 15 | dependencies = ( 16 | OBJ_67 /* PBXTargetDependency */, 17 | ); 18 | name = XMLParsingPackageTests; 19 | productName = XMLParsingPackageTests; 20 | }; 21 | /* End PBXAggregateTarget section */ 22 | 23 | /* Begin PBXBuildFile section */ 24 | OBJ_35 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; 25 | OBJ_41 /* XMLParsingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* XMLParsingTests.swift */; }; 26 | OBJ_43 /* XMLParsing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "XMLParsing::XMLParsing::Product" /* XMLParsing.framework */; }; 27 | OBJ_50 /* DecodingErrorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* DecodingErrorExtension.swift */; }; 28 | OBJ_51 /* XMLDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* XMLDecoder.swift */; }; 29 | OBJ_52 /* XMLDecodingStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* XMLDecodingStorage.swift */; }; 30 | OBJ_53 /* XMLKeyedDecodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* XMLKeyedDecodingContainer.swift */; }; 31 | OBJ_54 /* XMLUnkeyedDecodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* XMLUnkeyedDecodingContainer.swift */; }; 32 | OBJ_55 /* EncodingErrorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* EncodingErrorExtension.swift */; }; 33 | OBJ_56 /* XMLEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* XMLEncoder.swift */; }; 34 | OBJ_57 /* XMLEncodingStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* XMLEncodingStorage.swift */; }; 35 | OBJ_58 /* XMLReferencingEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* XMLReferencingEncoder.swift */; }; 36 | OBJ_59 /* ISO8601DateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* ISO8601DateFormatter.swift */; }; 37 | OBJ_60 /* XMLKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* XMLKey.swift */; }; 38 | OBJ_61 /* XMLStackParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* XMLStackParser.swift */; }; 39 | /* End PBXBuildFile section */ 40 | 41 | /* Begin PBXContainerItemProxy section */ 42 | 580BB3EC215DDDBC00CA3804 /* PBXContainerItemProxy */ = { 43 | isa = PBXContainerItemProxy; 44 | containerPortal = OBJ_1 /* Project object */; 45 | proxyType = 1; 46 | remoteGlobalIDString = "XMLParsing::XMLParsing"; 47 | remoteInfo = XMLParsing; 48 | }; 49 | 580BB3ED215DDDBC00CA3804 /* PBXContainerItemProxy */ = { 50 | isa = PBXContainerItemProxy; 51 | containerPortal = OBJ_1 /* Project object */; 52 | proxyType = 1; 53 | remoteGlobalIDString = "XMLParsing::XMLParsingTests"; 54 | remoteInfo = XMLParsingTests; 55 | }; 56 | /* End PBXContainerItemProxy section */ 57 | 58 | /* Begin PBXFileReference section */ 59 | OBJ_10 /* DecodingErrorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodingErrorExtension.swift; sourceTree = ""; }; 60 | OBJ_11 /* XMLDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLDecoder.swift; sourceTree = ""; }; 61 | OBJ_12 /* XMLDecodingStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLDecodingStorage.swift; sourceTree = ""; }; 62 | OBJ_13 /* XMLKeyedDecodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLKeyedDecodingContainer.swift; sourceTree = ""; }; 63 | OBJ_14 /* XMLUnkeyedDecodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLUnkeyedDecodingContainer.swift; sourceTree = ""; }; 64 | OBJ_16 /* EncodingErrorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncodingErrorExtension.swift; sourceTree = ""; }; 65 | OBJ_17 /* XMLEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLEncoder.swift; sourceTree = ""; }; 66 | OBJ_18 /* XMLEncodingStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLEncodingStorage.swift; sourceTree = ""; }; 67 | OBJ_19 /* XMLReferencingEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLReferencingEncoder.swift; sourceTree = ""; }; 68 | OBJ_20 /* ISO8601DateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ISO8601DateFormatter.swift; sourceTree = ""; }; 69 | OBJ_21 /* XMLKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLKey.swift; sourceTree = ""; }; 70 | OBJ_22 /* XMLStackParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLStackParser.swift; sourceTree = ""; }; 71 | OBJ_25 /* XMLParsingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLParsingTests.swift; sourceTree = ""; }; 72 | OBJ_26 /* Sample XML */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Sample XML"; sourceTree = SOURCE_ROOT; }; 73 | OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 74 | "XMLParsing::XMLParsing::Product" /* XMLParsing.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = XMLParsing.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 75 | "XMLParsing::XMLParsingTests::Product" /* XMLParsingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = XMLParsingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 76 | /* End PBXFileReference section */ 77 | 78 | /* Begin PBXFrameworksBuildPhase section */ 79 | OBJ_42 /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 0; 82 | files = ( 83 | OBJ_43 /* XMLParsing.framework in Frameworks */, 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | OBJ_62 /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 0; 90 | files = ( 91 | ); 92 | runOnlyForDeploymentPostprocessing = 0; 93 | }; 94 | /* End PBXFrameworksBuildPhase section */ 95 | 96 | /* Begin PBXGroup section */ 97 | OBJ_15 /* Encoder */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | OBJ_16 /* EncodingErrorExtension.swift */, 101 | OBJ_17 /* XMLEncoder.swift */, 102 | OBJ_18 /* XMLEncodingStorage.swift */, 103 | OBJ_19 /* XMLReferencingEncoder.swift */, 104 | ); 105 | path = Encoder; 106 | sourceTree = ""; 107 | }; 108 | OBJ_23 /* Tests */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | OBJ_24 /* XMLParsingTests */, 112 | ); 113 | name = Tests; 114 | sourceTree = SOURCE_ROOT; 115 | }; 116 | OBJ_24 /* XMLParsingTests */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | OBJ_25 /* XMLParsingTests.swift */, 120 | ); 121 | name = XMLParsingTests; 122 | path = Tests/XMLParsingTests; 123 | sourceTree = SOURCE_ROOT; 124 | }; 125 | OBJ_27 /* Products */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | "XMLParsing::XMLParsingTests::Product" /* XMLParsingTests.xctest */, 129 | "XMLParsing::XMLParsing::Product" /* XMLParsing.framework */, 130 | ); 131 | name = Products; 132 | sourceTree = BUILT_PRODUCTS_DIR; 133 | }; 134 | OBJ_5 /* */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | OBJ_6 /* Package.swift */, 138 | OBJ_7 /* Sources */, 139 | OBJ_23 /* Tests */, 140 | OBJ_26 /* Sample XML */, 141 | OBJ_27 /* Products */, 142 | ); 143 | name = ""; 144 | sourceTree = ""; 145 | }; 146 | OBJ_7 /* Sources */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | OBJ_8 /* XMLParsing */, 150 | ); 151 | name = Sources; 152 | sourceTree = SOURCE_ROOT; 153 | }; 154 | OBJ_8 /* XMLParsing */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | OBJ_9 /* Decoder */, 158 | OBJ_15 /* Encoder */, 159 | OBJ_20 /* ISO8601DateFormatter.swift */, 160 | OBJ_21 /* XMLKey.swift */, 161 | OBJ_22 /* XMLStackParser.swift */, 162 | ); 163 | name = XMLParsing; 164 | path = Sources/XMLParsing; 165 | sourceTree = SOURCE_ROOT; 166 | }; 167 | OBJ_9 /* Decoder */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | OBJ_10 /* DecodingErrorExtension.swift */, 171 | OBJ_11 /* XMLDecoder.swift */, 172 | OBJ_12 /* XMLDecodingStorage.swift */, 173 | OBJ_13 /* XMLKeyedDecodingContainer.swift */, 174 | OBJ_14 /* XMLUnkeyedDecodingContainer.swift */, 175 | ); 176 | path = Decoder; 177 | sourceTree = ""; 178 | }; 179 | /* End PBXGroup section */ 180 | 181 | /* Begin PBXNativeTarget section */ 182 | "XMLParsing::SwiftPMPackageDescription" /* XMLParsingPackageDescription */ = { 183 | isa = PBXNativeTarget; 184 | buildConfigurationList = OBJ_31 /* Build configuration list for PBXNativeTarget "XMLParsingPackageDescription" */; 185 | buildPhases = ( 186 | OBJ_34 /* Sources */, 187 | ); 188 | buildRules = ( 189 | ); 190 | dependencies = ( 191 | ); 192 | name = XMLParsingPackageDescription; 193 | productName = XMLParsingPackageDescription; 194 | productType = "com.apple.product-type.framework"; 195 | }; 196 | "XMLParsing::XMLParsing" /* XMLParsing */ = { 197 | isa = PBXNativeTarget; 198 | buildConfigurationList = OBJ_46 /* Build configuration list for PBXNativeTarget "XMLParsing" */; 199 | buildPhases = ( 200 | OBJ_49 /* Sources */, 201 | OBJ_62 /* Frameworks */, 202 | ); 203 | buildRules = ( 204 | ); 205 | dependencies = ( 206 | ); 207 | name = XMLParsing; 208 | productName = XMLParsing; 209 | productReference = "XMLParsing::XMLParsing::Product" /* XMLParsing.framework */; 210 | productType = "com.apple.product-type.framework"; 211 | }; 212 | "XMLParsing::XMLParsingTests" /* XMLParsingTests */ = { 213 | isa = PBXNativeTarget; 214 | buildConfigurationList = OBJ_37 /* Build configuration list for PBXNativeTarget "XMLParsingTests" */; 215 | buildPhases = ( 216 | OBJ_40 /* Sources */, 217 | OBJ_42 /* Frameworks */, 218 | ); 219 | buildRules = ( 220 | ); 221 | dependencies = ( 222 | OBJ_44 /* PBXTargetDependency */, 223 | ); 224 | name = XMLParsingTests; 225 | productName = XMLParsingTests; 226 | productReference = "XMLParsing::XMLParsingTests::Product" /* XMLParsingTests.xctest */; 227 | productType = "com.apple.product-type.bundle.unit-test"; 228 | }; 229 | /* End PBXNativeTarget section */ 230 | 231 | /* Begin PBXProject section */ 232 | OBJ_1 /* Project object */ = { 233 | isa = PBXProject; 234 | attributes = { 235 | LastUpgradeCheck = 9999; 236 | TargetAttributes = { 237 | "XMLParsing::XMLParsing" = { 238 | LastSwiftMigration = 1000; 239 | }; 240 | "XMLParsing::XMLParsingTests" = { 241 | LastSwiftMigration = 1000; 242 | }; 243 | }; 244 | }; 245 | buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "XMLParsing" */; 246 | compatibilityVersion = "Xcode 3.2"; 247 | developmentRegion = English; 248 | hasScannedForEncodings = 0; 249 | knownRegions = ( 250 | en, 251 | ); 252 | mainGroup = OBJ_5 /* */; 253 | productRefGroup = OBJ_27 /* Products */; 254 | projectDirPath = ""; 255 | projectRoot = ""; 256 | targets = ( 257 | "XMLParsing::SwiftPMPackageDescription" /* XMLParsingPackageDescription */, 258 | "XMLParsing::XMLParsingTests" /* XMLParsingTests */, 259 | "XMLParsing::XMLParsing" /* XMLParsing */, 260 | "XMLParsing::XMLParsingPackageTests::ProductTarget" /* XMLParsingPackageTests */, 261 | ); 262 | }; 263 | /* End PBXProject section */ 264 | 265 | /* Begin PBXSourcesBuildPhase section */ 266 | OBJ_34 /* Sources */ = { 267 | isa = PBXSourcesBuildPhase; 268 | buildActionMask = 0; 269 | files = ( 270 | OBJ_35 /* Package.swift in Sources */, 271 | ); 272 | runOnlyForDeploymentPostprocessing = 0; 273 | }; 274 | OBJ_40 /* Sources */ = { 275 | isa = PBXSourcesBuildPhase; 276 | buildActionMask = 0; 277 | files = ( 278 | OBJ_41 /* XMLParsingTests.swift in Sources */, 279 | ); 280 | runOnlyForDeploymentPostprocessing = 0; 281 | }; 282 | OBJ_49 /* Sources */ = { 283 | isa = PBXSourcesBuildPhase; 284 | buildActionMask = 0; 285 | files = ( 286 | OBJ_50 /* DecodingErrorExtension.swift in Sources */, 287 | OBJ_51 /* XMLDecoder.swift in Sources */, 288 | OBJ_52 /* XMLDecodingStorage.swift in Sources */, 289 | OBJ_53 /* XMLKeyedDecodingContainer.swift in Sources */, 290 | OBJ_54 /* XMLUnkeyedDecodingContainer.swift in Sources */, 291 | OBJ_55 /* EncodingErrorExtension.swift in Sources */, 292 | OBJ_56 /* XMLEncoder.swift in Sources */, 293 | OBJ_57 /* XMLEncodingStorage.swift in Sources */, 294 | OBJ_58 /* XMLReferencingEncoder.swift in Sources */, 295 | OBJ_59 /* ISO8601DateFormatter.swift in Sources */, 296 | OBJ_60 /* XMLKey.swift in Sources */, 297 | OBJ_61 /* XMLStackParser.swift in Sources */, 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | }; 301 | /* End PBXSourcesBuildPhase section */ 302 | 303 | /* Begin PBXTargetDependency section */ 304 | OBJ_44 /* PBXTargetDependency */ = { 305 | isa = PBXTargetDependency; 306 | target = "XMLParsing::XMLParsing" /* XMLParsing */; 307 | targetProxy = 580BB3EC215DDDBC00CA3804 /* PBXContainerItemProxy */; 308 | }; 309 | OBJ_67 /* PBXTargetDependency */ = { 310 | isa = PBXTargetDependency; 311 | target = "XMLParsing::XMLParsingTests" /* XMLParsingTests */; 312 | targetProxy = 580BB3ED215DDDBC00CA3804 /* PBXContainerItemProxy */; 313 | }; 314 | /* End PBXTargetDependency section */ 315 | 316 | /* Begin XCBuildConfiguration section */ 317 | OBJ_3 /* Debug */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | CLANG_ENABLE_OBJC_ARC = YES; 321 | COMBINE_HIDPI_IMAGES = YES; 322 | COPY_PHASE_STRIP = NO; 323 | DEBUG_INFORMATION_FORMAT = dwarf; 324 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 325 | ENABLE_NS_ASSERTIONS = YES; 326 | GCC_OPTIMIZATION_LEVEL = 0; 327 | MACOSX_DEPLOYMENT_TARGET = 10.10; 328 | ONLY_ACTIVE_ARCH = YES; 329 | OTHER_SWIFT_FLAGS = "-DXcode"; 330 | PRODUCT_NAME = "$(TARGET_NAME)"; 331 | SDKROOT = macosx; 332 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 333 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE; 334 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 335 | USE_HEADERMAP = NO; 336 | }; 337 | name = Debug; 338 | }; 339 | OBJ_32 /* Debug */ = { 340 | isa = XCBuildConfiguration; 341 | buildSettings = { 342 | LD = /usr/bin/true; 343 | OTHER_SWIFT_FLAGS = "-swift-version 4 -I /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/4 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk"; 344 | SWIFT_VERSION = 4.0; 345 | }; 346 | name = Debug; 347 | }; 348 | OBJ_33 /* Release */ = { 349 | isa = XCBuildConfiguration; 350 | buildSettings = { 351 | LD = /usr/bin/true; 352 | OTHER_SWIFT_FLAGS = "-swift-version 4 -I /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/4 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk"; 353 | SWIFT_VERSION = 4.0; 354 | }; 355 | name = Release; 356 | }; 357 | OBJ_38 /* Debug */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 361 | FRAMEWORK_SEARCH_PATHS = ( 362 | "$(inherited)", 363 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 364 | ); 365 | HEADER_SEARCH_PATHS = "$(inherited)"; 366 | INFOPLIST_FILE = XMLParsing.xcodeproj/XMLParsingTests_Info.plist; 367 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks @loader_path/Frameworks"; 368 | OTHER_LDFLAGS = "$(inherited)"; 369 | OTHER_SWIFT_FLAGS = "$(inherited)"; 370 | SWIFT_VERSION = 4.2; 371 | TARGET_NAME = XMLParsingTests; 372 | }; 373 | name = Debug; 374 | }; 375 | OBJ_39 /* Release */ = { 376 | isa = XCBuildConfiguration; 377 | buildSettings = { 378 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 379 | FRAMEWORK_SEARCH_PATHS = ( 380 | "$(inherited)", 381 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 382 | ); 383 | HEADER_SEARCH_PATHS = "$(inherited)"; 384 | INFOPLIST_FILE = XMLParsing.xcodeproj/XMLParsingTests_Info.plist; 385 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks @loader_path/Frameworks"; 386 | OTHER_LDFLAGS = "$(inherited)"; 387 | OTHER_SWIFT_FLAGS = "$(inherited)"; 388 | SWIFT_VERSION = 4.2; 389 | TARGET_NAME = XMLParsingTests; 390 | }; 391 | name = Release; 392 | }; 393 | OBJ_4 /* Release */ = { 394 | isa = XCBuildConfiguration; 395 | buildSettings = { 396 | CLANG_ENABLE_OBJC_ARC = YES; 397 | COMBINE_HIDPI_IMAGES = YES; 398 | COPY_PHASE_STRIP = YES; 399 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 400 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 401 | GCC_OPTIMIZATION_LEVEL = s; 402 | MACOSX_DEPLOYMENT_TARGET = 10.10; 403 | OTHER_SWIFT_FLAGS = "-DXcode"; 404 | PRODUCT_NAME = "$(TARGET_NAME)"; 405 | SDKROOT = macosx; 406 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 407 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE; 408 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 409 | USE_HEADERMAP = NO; 410 | }; 411 | name = Release; 412 | }; 413 | OBJ_47 /* Debug */ = { 414 | isa = XCBuildConfiguration; 415 | buildSettings = { 416 | ENABLE_TESTABILITY = YES; 417 | FRAMEWORK_SEARCH_PATHS = ( 418 | "$(inherited)", 419 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 420 | ); 421 | HEADER_SEARCH_PATHS = "$(inherited)"; 422 | INFOPLIST_FILE = XMLParsing.xcodeproj/XMLParsing_Info.plist; 423 | LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 424 | OTHER_LDFLAGS = "$(inherited)"; 425 | OTHER_SWIFT_FLAGS = "$(inherited)"; 426 | PRODUCT_BUNDLE_IDENTIFIER = XMLParsing; 427 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 428 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 429 | SKIP_INSTALL = YES; 430 | SWIFT_VERSION = 4.2; 431 | TARGET_NAME = XMLParsing; 432 | }; 433 | name = Debug; 434 | }; 435 | OBJ_48 /* Release */ = { 436 | isa = XCBuildConfiguration; 437 | buildSettings = { 438 | ENABLE_TESTABILITY = YES; 439 | FRAMEWORK_SEARCH_PATHS = ( 440 | "$(inherited)", 441 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 442 | ); 443 | HEADER_SEARCH_PATHS = "$(inherited)"; 444 | INFOPLIST_FILE = XMLParsing.xcodeproj/XMLParsing_Info.plist; 445 | LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 446 | OTHER_LDFLAGS = "$(inherited)"; 447 | OTHER_SWIFT_FLAGS = "$(inherited)"; 448 | PRODUCT_BUNDLE_IDENTIFIER = XMLParsing; 449 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 450 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 451 | SKIP_INSTALL = YES; 452 | SWIFT_VERSION = 4.2; 453 | TARGET_NAME = XMLParsing; 454 | }; 455 | name = Release; 456 | }; 457 | OBJ_65 /* Debug */ = { 458 | isa = XCBuildConfiguration; 459 | buildSettings = { 460 | }; 461 | name = Debug; 462 | }; 463 | OBJ_66 /* Release */ = { 464 | isa = XCBuildConfiguration; 465 | buildSettings = { 466 | }; 467 | name = Release; 468 | }; 469 | /* End XCBuildConfiguration section */ 470 | 471 | /* Begin XCConfigurationList section */ 472 | OBJ_2 /* Build configuration list for PBXProject "XMLParsing" */ = { 473 | isa = XCConfigurationList; 474 | buildConfigurations = ( 475 | OBJ_3 /* Debug */, 476 | OBJ_4 /* Release */, 477 | ); 478 | defaultConfigurationIsVisible = 0; 479 | defaultConfigurationName = Debug; 480 | }; 481 | OBJ_31 /* Build configuration list for PBXNativeTarget "XMLParsingPackageDescription" */ = { 482 | isa = XCConfigurationList; 483 | buildConfigurations = ( 484 | OBJ_32 /* Debug */, 485 | OBJ_33 /* Release */, 486 | ); 487 | defaultConfigurationIsVisible = 0; 488 | defaultConfigurationName = Debug; 489 | }; 490 | OBJ_37 /* Build configuration list for PBXNativeTarget "XMLParsingTests" */ = { 491 | isa = XCConfigurationList; 492 | buildConfigurations = ( 493 | OBJ_38 /* Debug */, 494 | OBJ_39 /* Release */, 495 | ); 496 | defaultConfigurationIsVisible = 0; 497 | defaultConfigurationName = Debug; 498 | }; 499 | OBJ_46 /* Build configuration list for PBXNativeTarget "XMLParsing" */ = { 500 | isa = XCConfigurationList; 501 | buildConfigurations = ( 502 | OBJ_47 /* Debug */, 503 | OBJ_48 /* Release */, 504 | ); 505 | defaultConfigurationIsVisible = 0; 506 | defaultConfigurationName = Debug; 507 | }; 508 | OBJ_64 /* Build configuration list for PBXAggregateTarget "XMLParsingPackageTests" */ = { 509 | isa = XCConfigurationList; 510 | buildConfigurations = ( 511 | OBJ_65 /* Debug */, 512 | OBJ_66 /* Release */, 513 | ); 514 | defaultConfigurationIsVisible = 0; 515 | defaultConfigurationName = Debug; 516 | }; 517 | /* End XCConfigurationList section */ 518 | }; 519 | rootObject = OBJ_1 /* Project object */; 520 | } 521 | -------------------------------------------------------------------------------- /XMLParsing.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /XMLParsing.xcodeproj/project.xcworkspace/xcuserdata/wilson.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShawnMoore/XMLParsing/594272e4df40798c53ae1735ee65f3f387291670/XMLParsing.xcodeproj/project.xcworkspace/xcuserdata/wilson.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /XMLParsing.xcodeproj/xcshareddata/xcschemes/XMLParsing-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /XMLParsing.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SchemeUserState 5 | 6 | XMLParsing-Package.xcscheme 7 | 8 | 9 | SuppressBuildableAutocreation 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /XMLParsing.xcodeproj/xcuserdata/wilson.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | XMLParsing-Package.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | XMLParsingPackageDescription.xcscheme 13 | 14 | orderHint 15 | 1 16 | 17 | XMLParsingPackageTests.xcscheme 18 | 19 | orderHint 20 | 2 21 | 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------