4 This is base class for holding XML structure.
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.
9 open class AEXMLElement {
13 /// Every `AEXMLElement` should have its parent element instead of `AEXMLDocument` which parent is `nil`.
14 open internal(set) weak var parent: AEXMLElement?
16 /// Child XML elements.
17 open internal(set) var children = [AEXMLElement]()
22 /// XML Element value.
23 open var value: String?
25 /// XML Element attributes.
26 open var attributes: [String : String]
28 /// Error value (`nil` if there is no error).
29 open var error: AEXMLError?
31 /// String representation of `value` property (if `value` is `nil` this is empty String).
32 open var string: String { return value ?? String() }
34 /// Boolean representation of `value` property (`nil` if `value` can't be represented as Bool).
35 open var bool: Bool? { return Bool(string) }
37 /// Integer representation of `value` property (`nil` if `value` can't be represented as Integer).
38 open var int: Int? { return Int(string) }
40 /// Double representation of `value` property (`nil` if `value` can't be represented as Double).
41 open var double: Double? { return Double(string) }
46 Designated initializer - all parameters are optional.
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).
52 - returns: An initialized `AEXMLElement` object.
54 public init(name: String, value: String? = nil, attributes: [String : String] = [String : String]()) {
57 self.attributes = attributes
62 /// The first element with given name **(Empty element with error if not exists)**.
63 open subscript(key: String) -> AEXMLElement {
65 first = children.first(where: { $0.name == key })
67 let errorElement = AEXMLElement(name: key)
68 errorElement.error = AEXMLError.elementNotFound
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 } }
77 /// Returns the first element with equal name as `self` **(nil if not exists)**.
78 open var first: AEXMLElement? { return all?.first }
80 /// Returns the last element with equal name as `self` **(nil if not exists)**.
81 open var last: AEXMLElement? { return all?.last }
83 /// Returns number of all elements with equal name as `self`.
84 open var count: Int { return all?.count ?? 0 }
87 Returns all elements with given value.
89 - parameter value: XML element value.
91 - returns: Optional Array of found XML elements.
93 open func all(withValue value: String) -> [AEXMLElement]? {
94 let found = all?.flatMap {
95 $0.value == value ? $0 : nil
101 Returns all elements containing given attributes.
103 - parameter attributes: Array of attribute names.
105 - returns: Optional Array of found XML elements.
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)
117 Returns all elements with given attributes.
119 - parameter attributes: Dictionary of Keys and Values of attributes.
121 - returns: Optional Array of found XML elements.
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
134 Returns all descendant elements which satisfy the given predicate.
136 Searching is done vertically; children are tested before siblings. Elements appear in the list
137 in the order in which they are found.
139 - parameter predicate: Function which returns `true` for a desired element and `false` otherwise.
141 - returns: Array of found XML elements.
143 open func allDescendants(where predicate: (AEXMLElement) -> Bool) -> [AEXMLElement] {
144 var result: [AEXMLElement] = []
146 for child in children {
147 if predicate(child) {
150 result.append(contentsOf: child.allDescendants(where: predicate))
157 Returns the first descendant element which satisfies the given predicate, or nil if no such element is found.
159 Searching is done vertically; children are tested before siblings.
161 - parameter predicate: Function which returns `true` for the desired element and `false` otherwise.
163 - returns: Optional AEXMLElement.
165 open func firstDescendant(where predicate: (AEXMLElement) -> Bool) -> AEXMLElement? {
166 for child in children {
167 if predicate(child) {
169 } else if let descendant = child.firstDescendant(where: predicate) {
177 Indicates whether the element has a descendant satisfying the given predicate.
179 - parameter predicate: Function which returns `true` for the desired element and `false` otherwise.
183 open func hasDescendant(where predicate: (AEXMLElement) -> Bool) -> Bool {
184 return firstDescendant(where: predicate) != nil
190 Adds child XML element to `self`.
192 - parameter child: Child XML element to add.
194 - returns: Child XML element with `self` as `parent`.
196 @discardableResult open func addChild(_ child: AEXMLElement) -> AEXMLElement {
198 children.append(child)
203 Adds child XML element to `self`.
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).
209 - returns: Child XML element with `self` as `parent`.
211 @discardableResult open func addChild(name: String,
212 value: String? = nil,
213 attributes: [String : String] = [String : String]()) -> AEXMLElement
215 let child = AEXMLElement(name: name, value: value, attributes: attributes)
216 return addChild(child)
220 Adds an array of XML elements to `self`.
222 - parameter children: Child XML element array to add.
224 - returns: Child XML elements with `self` as `parent`.
226 @discardableResult open func addChildren(_ children: [AEXMLElement]) -> [AEXMLElement] {
227 children.forEach{ addChild($0) }
231 /// Removes `self` from `parent` XML element.
232 open func removeFromParent() {
233 parent?.removeChild(self)
236 fileprivate func removeChild(_ child: AEXMLElement) {
237 if let childIndex = children.index(where: { $0 === child }) {
238 children.remove(at: childIndex)
242 fileprivate var parentsCount: Int {
246 while let parent = element.parent {
254 fileprivate func indent(withDepth depth: Int) -> String {
256 var indent = String()
266 /// Complete hierarchy of `self` and `children` in **XML** escaped and formatted String
267 open var xml: String {
271 xml += indent(withDepth: parentsCount - 1)
274 if attributes.count > 0 {
276 for (key, value) in attributes {
277 xml += " \(key)=\"\(value.xmlEscaped)\""
281 if value == nil && children.count == 0 {
285 if children.count > 0 {
288 for child in children {
289 xml += "\(child.xml)\n"
292 xml += indent(withDepth: parentsCount - 1)
295 // insert string value and close element
296 xml += ">\(string.xmlEscaped)</\(name)>"
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: "")
311 public extension String {
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: "&", options: .literal)
318 // replace the other four special characters
319 let escapeChars = ["<" : "<", ">" : ">", "'" : "'", "\"" : """]
320 for (char, echar) in escapeChars {
321 escaped = escaped.replacingOccurrences(of: char, with: echar, options: .literal)