added iOS source code
[wl-app.git] / iOS / Pods / AEXML / Sources / Element.swift
1 import Foundation
2
3 /**
4     This is base class for holding XML structure.
5
6     You can access its structure by using subscript like this: `element["foo"]["bar"]` which would
7     return `<bar></bar>` element from `<element><foo><bar></bar></foo></element>` XML as an `AEXMLElement` object.
8 */
9 open class AEXMLElement {
10     
11     // MARK: - Properties
12     
13     /// Every `AEXMLElement` should have its parent element instead of `AEXMLDocument` which parent is `nil`.
14     open internal(set) weak var parent: AEXMLElement?
15     
16     /// Child XML elements.
17     open internal(set) var children = [AEXMLElement]()
18     
19     /// XML Element name.
20     open var name: String
21     
22     /// XML Element value.
23     open var value: String?
24     
25     /// XML Element attributes.
26     open var attributes: [String : String]
27     
28     /// Error value (`nil` if there is no error).
29     open var error: AEXMLError?
30     
31     /// String representation of `value` property (if `value` is `nil` this is empty String).
32     open var string: String { return value ?? String() }
33     
34     /// Boolean representation of `value` property (`nil` if `value` can't be represented as Bool).
35     open var bool: Bool? { return Bool(string) }
36     
37     /// Integer representation of `value` property (`nil` if `value` can't be represented as Integer).
38     open var int: Int? { return Int(string) }
39     
40     /// Double representation of `value` property (`nil` if `value` can't be represented as Double).
41     open var double: Double? { return Double(string) }
42     
43     // MARK: - Lifecycle
44     
45     /**
46         Designated initializer - all parameters are optional.
47     
48         - parameter name: XML element name.
49         - parameter value: XML element value (defaults to `nil`).
50         - parameter attributes: XML element attributes (defaults to empty dictionary).
51     
52         - returns: An initialized `AEXMLElement` object.
53     */
54     public init(name: String, value: String? = nil, attributes: [String : String] = [String : String]()) {
55         self.name = name
56         self.value = value
57         self.attributes = attributes
58     }
59     
60     // MARK: - XML Read
61     
62     /// The first element with given name **(Empty element with error if not exists)**.
63     open subscript(key: String) -> AEXMLElement {
64         guard let
65             first = children.first(where: { $0.name == key })
66         else {
67             let errorElement = AEXMLElement(name: key)
68             errorElement.error = AEXMLError.elementNotFound
69             return errorElement
70         }
71         return first
72     }
73     
74     /// Returns all of the elements with equal name as `self` **(nil if not exists)**.
75     open var all: [AEXMLElement]? { return parent?.children.filter { $0.name == self.name } }
76     
77     /// Returns the first element with equal name as `self` **(nil if not exists)**.
78     open var first: AEXMLElement? { return all?.first }
79     
80     /// Returns the last element with equal name as `self` **(nil if not exists)**.
81     open var last: AEXMLElement? { return all?.last }
82     
83     /// Returns number of all elements with equal name as `self`.
84     open var count: Int { return all?.count ?? 0 }
85     
86     /**
87         Returns all elements with given value.
88         
89         - parameter value: XML element value.
90         
91         - returns: Optional Array of found XML elements.
92     */
93     open func all(withValue value: String) -> [AEXMLElement]? {
94         let found = all?.flatMap {
95             $0.value == value ? $0 : nil
96         }
97         return found
98     }
99     
100     /**
101         Returns all elements containing given attributes.
102
103         - parameter attributes: Array of attribute names.
104
105         - returns: Optional Array of found XML elements.
106     */
107     open func all(containingAttributeKeys keys: [String]) -> [AEXMLElement]? {
108         let found = all?.flatMap { element in
109             keys.reduce(true) { (result, key) in
110                 result && Array(element.attributes.keys).contains(key)
111             } ? element : nil
112         }
113         return found
114     }
115     
116     /**
117         Returns all elements with given attributes.
118     
119         - parameter attributes: Dictionary of Keys and Values of attributes.
120     
121         - returns: Optional Array of found XML elements.
122     */
123     open func all(withAttributes attributes: [String : String]) -> [AEXMLElement]? {
124         let keys = Array(attributes.keys)
125         let found = all(containingAttributeKeys: keys)?.flatMap { element in
126             attributes.reduce(true) { (result, attribute) in
127                 result && element.attributes[attribute.key] == attribute.value
128             } ? element : nil
129         }
130         return found
131     }
132     
133     /**
134         Returns all descendant elements which satisfy the given predicate.
135      
136         Searching is done vertically; children are tested before siblings. Elements appear in the list
137         in the order in which they are found.
138      
139         - parameter predicate: Function which returns `true` for a desired element and `false` otherwise.
140      
141         - returns: Array of found XML elements.
142     */
143     open func allDescendants(where predicate: (AEXMLElement) -> Bool) -> [AEXMLElement] {
144         var result: [AEXMLElement] = []
145         
146         for child in children {
147             if predicate(child) {
148                 result.append(child)
149             }
150             result.append(contentsOf: child.allDescendants(where: predicate))
151         }
152         
153         return result
154     }
155     
156     /**
157         Returns the first descendant element which satisfies the given predicate, or nil if no such element is found.
158      
159         Searching is done vertically; children are tested before siblings.
160      
161         - parameter predicate: Function which returns `true` for the desired element and `false` otherwise.
162      
163         - returns: Optional AEXMLElement.
164     */
165     open func firstDescendant(where predicate: (AEXMLElement) -> Bool) -> AEXMLElement? {
166         for child in children {
167             if predicate(child) {
168                 return child
169             } else if let descendant = child.firstDescendant(where: predicate) {
170                 return descendant
171             }
172         }
173         return nil
174     }
175     
176     /**
177         Indicates whether the element has a descendant satisfying the given predicate.
178      
179         - parameter predicate: Function which returns `true` for the desired element and `false` otherwise.
180      
181         - returns: Bool.
182     */
183     open func hasDescendant(where predicate: (AEXMLElement) -> Bool) -> Bool {
184         return firstDescendant(where: predicate) != nil
185     }
186     
187     // MARK: - XML Write
188     
189     /**
190         Adds child XML element to `self`.
191     
192         - parameter child: Child XML element to add.
193     
194         - returns: Child XML element with `self` as `parent`.
195     */
196     @discardableResult open func addChild(_ child: AEXMLElement) -> AEXMLElement {
197         child.parent = self
198         children.append(child)
199         return child
200     }
201     
202     /**
203         Adds child XML element to `self`.
204         
205         - parameter name: Child XML element name.
206         - parameter value: Child XML element value (defaults to `nil`).
207         - parameter attributes: Child XML element attributes (defaults to empty dictionary).
208         
209         - returns: Child XML element with `self` as `parent`.
210     */
211     @discardableResult open func addChild(name: String,
212                        value: String? = nil,
213                        attributes: [String : String] = [String : String]()) -> AEXMLElement
214     {
215         let child = AEXMLElement(name: name, value: value, attributes: attributes)
216         return addChild(child)
217     }
218     
219     /**
220         Adds an array of XML elements to `self`.
221     
222         - parameter children: Child XML element array to add.
223     
224         - returns: Child XML elements with `self` as `parent`.
225     */
226     @discardableResult open func addChildren(_ children: [AEXMLElement]) -> [AEXMLElement] {
227         children.forEach{ addChild($0) }
228         return children
229     }
230     
231     /// Removes `self` from `parent` XML element.
232     open func removeFromParent() {
233         parent?.removeChild(self)
234     }
235     
236     fileprivate func removeChild(_ child: AEXMLElement) {
237         if let childIndex = children.index(where: { $0 === child }) {
238             children.remove(at: childIndex)
239         }
240     }
241     
242     fileprivate var parentsCount: Int {
243         var count = 0
244         var element = self
245         
246         while let parent = element.parent {
247             count += 1
248             element = parent
249         }
250         
251         return count
252     }
253     
254     fileprivate func indent(withDepth depth: Int) -> String {
255         var count = depth
256         var indent = String()
257         
258         while count > 0 {
259             indent += "\t"
260             count -= 1
261         }
262         
263         return indent
264     }
265     
266     /// Complete hierarchy of `self` and `children` in **XML** escaped and formatted String
267     open var xml: String {
268         var xml = String()
269         
270         // open element
271         xml += indent(withDepth: parentsCount - 1)
272         xml += "<\(name)"
273         
274         if attributes.count > 0 {
275             // insert attributes
276             for (key, value) in attributes {
277                 xml += " \(key)=\"\(value.xmlEscaped)\""
278             }
279         }
280         
281         if value == nil && children.count == 0 {
282             // close element
283             xml += " />"
284         } else {
285             if children.count > 0 {
286                 // add children
287                 xml += ">\n"
288                 for child in children {
289                     xml += "\(child.xml)\n"
290                 }
291                 // add indentation
292                 xml += indent(withDepth: parentsCount - 1)
293                 xml += "</\(name)>"
294             } else {
295                 // insert string value and close element
296                 xml += ">\(string.xmlEscaped)</\(name)>"
297             }
298         }
299         
300         return xml
301     }
302     
303     /// Same as `xmlString` but without `\n` and `\t` characters
304     open var xmlCompact: String {
305         let chars = CharacterSet(charactersIn: "\n\t")
306         return xml.components(separatedBy: chars).joined(separator: "")
307     }
308     
309 }
310
311 public extension String {
312     
313     /// String representation of self with XML special characters escaped.
314     public var xmlEscaped: String {
315         // we need to make sure "&" is escaped first. Not doing this may break escaping the other characters
316         var escaped = replacingOccurrences(of: "&", with: "&amp;", options: .literal)
317         
318         // replace the other four special characters
319         let escapeChars = ["<" : "&lt;", ">" : "&gt;", "'" : "&apos;", "\"" : "&quot;"]
320         for (char, echar) in escapeChars {
321             escaped = escaped.replacingOccurrences(of: char, with: echar, options: .literal)
322         }
323         
324         return escaped
325     }
326     
327 }