├── .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 += "\(key)>"
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 += "\(key)>"
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 |
--------------------------------------------------------------------------------