1+ //
2+ // SWXMLHash.swift
3+ //
4+ // Copyright (c) 2014 David Mohundro
5+ //
6+ // Permission is hereby granted, free of charge, to any person obtaining a copy
7+ // of this software and associated documentation files (the "Software"), to deal
8+ // in the Software without restriction, including without limitation the rights
9+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+ // copies of the Software, and to permit persons to whom the Software is
11+ // furnished to do so, subject to the following conditions:
12+ //
13+ // The above copyright notice and this permission notice shall be included in
14+ // all copies or substantial portions of the Software.
15+ //
16+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+ // THE SOFTWARE.
23+
24+ import Foundation
25+
26+ /// Simple XML parser.
27+ public class SWXMLHash {
28+ /**
29+ Method to parse XML passed in as a string.
30+
31+ :param: xml The XML to be parsed
32+
33+ :returns: An XMLIndexer instance that is used to look up elements in the XML
34+ */
35+ class public func parse( xml: String ) -> XMLIndexer {
36+ return parse ( ( xml as NSString ) . dataUsingEncoding ( NSUTF8StringEncoding) )
37+ }
38+
39+ /**
40+ Method to parse XML passed in as an NSData instance.
41+
42+ :param: xml The XML to be parsed
43+
44+ :returns: An XMLIndexer instance that is used to look up elements in the XML
45+ */
46+ class public func parse( data: NSData ) -> XMLIndexer {
47+ var parser = XMLParser ( )
48+ return parser. parse ( data)
49+ }
50+ }
51+
52+ /// The implementation of NSXMLParserDelegate and where the parsing actually happens.
53+ class XMLParser : NSObject , NSXMLParserDelegate {
54+ var parsingElement : String = " "
55+
56+ override init ( ) {
57+ currentNode = root
58+ super. init ( )
59+ }
60+
61+ var lastResults : String = " "
62+
63+ var root = XMLElement ( name: " root " )
64+ var currentNode : XMLElement
65+ var parentStack = [ XMLElement] ( )
66+
67+ func parse( data: NSData ) -> XMLIndexer {
68+ // clear any prior runs of parse... expected that this won't be necessary, but you never know
69+ parentStack. removeAll ( keepCapacity: false )
70+ root = XMLElement ( name: " root " )
71+
72+ parentStack. append ( root)
73+
74+ let parser = NSXMLParser ( data: data)
75+ parser. delegate = self
76+ parser. parse ( )
77+
78+ return XMLIndexer ( root)
79+ }
80+
81+ func parser( parser: NSXMLParser ! , didStartElement elementName: String ! , namespaceURI: String ! , qualifiedName: String ! , attributes attributeDict: NSDictionary ! ) {
82+
83+ self . parsingElement = elementName
84+
85+ currentNode = parentStack [ parentStack. count - 1 ] . addElement ( elementName, withAttributes: attributeDict)
86+ parentStack. append ( currentNode)
87+
88+ lastResults = " "
89+ }
90+
91+ func parser( parser: NSXMLParser ! , foundCharacters string: String ! ) {
92+ lastResults += string
93+ }
94+
95+ func parser( parser: NSXMLParser ! , didEndElement elementName: String ! , namespaceURI: String ! , qualifiedName qName: String ! ) {
96+ if !lastResults. isEmpty {
97+ currentNode. text = lastResults
98+ }
99+
100+ parentStack. removeLast ( )
101+ }
102+ }
103+
104+ /// Returned from SWXMLHash, allows easy element lookup into XML data.
105+ public enum XMLIndexer {
106+ case Element( XMLElement )
107+ case List( [ XMLElement ] )
108+ case Error( NSError )
109+
110+ /// The underlying XMLElement at the currently indexed level of XML.
111+ public var element : XMLElement ? {
112+ get {
113+ switch self {
114+ case . Element( let elem) :
115+ return elem
116+ default :
117+ return nil
118+ }
119+ }
120+ }
121+
122+ /// The underlying array of XMLElements at the currently indexed level of XML.
123+ public var all : [ XMLIndexer ] {
124+ get {
125+ switch self {
126+ case . List( let list) :
127+ var xmlList = [ XMLIndexer] ( )
128+ for elem in list {
129+ xmlList. append ( XMLIndexer ( elem) )
130+ }
131+ return xmlList
132+ case . Element( let elem) :
133+ return [ XMLIndexer ( elem) ]
134+ default :
135+ return [ ]
136+ }
137+ }
138+ }
139+
140+ /**
141+ Initializes the XMLIndexer
142+
143+ :param: _ should be an instance of XMLElement, but supports other values for error handling
144+
145+ :returns: instance of XMLIndexer
146+ */
147+ public init ( _ rawObject: AnyObject ) {
148+ switch rawObject {
149+ case let value as XMLElement :
150+ self = . Element( value)
151+ default :
152+ self = . Error( NSError ( domain: " SWXMLDomain " , code: 1000 , userInfo: nil ) )
153+ }
154+ }
155+
156+ /**
157+ Find an XML element at the current level by element name
158+
159+ :param: key The element name to index by
160+
161+ :returns: instance of XMLIndexer to match the element (or elements) found by key
162+ */
163+ public subscript( key: String ) -> XMLIndexer {
164+ get {
165+ let userInfo = [ NSLocalizedDescriptionKey: " XML Element Error: Incorrect key [ \" \( key) \" ] " ]
166+ switch self {
167+ case . Element( let elem) :
168+ if let match = elem. elements [ key] {
169+ if match. count == 1 {
170+ return . Element( match [ 0 ] )
171+ }
172+ else {
173+ return . List( match)
174+ }
175+ }
176+ return . Error( NSError ( domain: " SWXMLDomain " , code: 1000 , userInfo: userInfo) )
177+ default :
178+ return . Error( NSError ( domain: " SWXMLDomain " , code: 1000 , userInfo: userInfo) )
179+ }
180+ }
181+ }
182+
183+ /**
184+ Find an XML element by index within a list of XML Elements at the current level
185+
186+ :param: index The 0-based index to index by
187+
188+ :returns: instance of XMLIndexer to match the element (or elements) found by key
189+ */
190+ public subscript( index: Int ) -> XMLIndexer {
191+ get {
192+ let userInfo = [ NSLocalizedDescriptionKey: " XML Element Error: Incorrect index [ \" \( index) \" ] " ]
193+ switch self {
194+ case . List( let list) :
195+ if index <= list. count {
196+ return . Element( list [ index] )
197+ }
198+ return . Error( NSError ( domain: " SWXMLDomain " , code: 1000 , userInfo: userInfo) )
199+ case . Element( let elem) :
200+ if index == 0 {
201+ return . Element( elem)
202+ }
203+ else {
204+ return . Error( NSError ( domain: " SWXMLDomain " , code: 1000 , userInfo: userInfo) )
205+ }
206+ default :
207+ return . Error( NSError ( domain: " SWXMLDomain " , code: 1000 , userInfo: userInfo) )
208+ }
209+ }
210+ }
211+ }
212+
213+ /// XMLIndexer extensions
214+ extension XMLIndexer : BooleanType {
215+ /// True if a valid XMLIndexer, false if an error type
216+ public var boolValue : Bool {
217+ get {
218+ switch self {
219+ case . Error:
220+ return false
221+ default :
222+ return true
223+ }
224+ }
225+ }
226+ }
227+
228+ /// Models an XML element, including name, text and attributes
229+ public class XMLElement {
230+ /// The name of the element
231+ public let name : String
232+ /// The inner text of the element, if it exists
233+ public var text : String ?
234+ /// The attributes of the element
235+ public var attributes = [ String: String] ( )
236+
237+ var elements = [ String: [ XMLElement] ] ( )
238+
239+ /**
240+ Initialize an XMLElement instance
241+
242+ :param: name The name of the element to be initialized
243+
244+ :returns: a new instance of XMLElement
245+ */
246+ init ( name: String ) {
247+ self . name = name
248+ }
249+
250+ /**
251+ Adds a new XMLElement underneath this instance of XMLElement
252+
253+ :param: name The name of the new element to be added
254+ :param: withAttributes The attributes dictionary for the element being added
255+
256+ :returns: The XMLElement that has now been added
257+ */
258+ func addElement( name: String , withAttributes attributes: NSDictionary ) -> XMLElement {
259+ let element = XMLElement ( name: name)
260+
261+ if var group = elements [ name] {
262+ group. append ( element)
263+ elements [ name] = group
264+ }
265+ else {
266+ elements [ name] = [ element]
267+ }
268+
269+ for (keyAny, valueAny) in attributes {
270+ let key = keyAny as String
271+ let value = valueAny as String
272+ element. attributes [ key] = value
273+ }
274+
275+ return element
276+ }
277+ }
0 commit comments