diff --git a/XMLParsing/XML/Decoder/XMLDecoder.swift b/XMLParsing/XML/Decoder/XMLDecoder.swift old mode 100644 new mode 100755 index 78ee8e9..ea38518 --- a/XMLParsing/XML/Decoder/XMLDecoder.swift +++ b/XMLParsing/XML/Decoder/XMLDecoder.swift @@ -114,6 +114,18 @@ open class XMLDecoder { /// Contextual user-provided information for use during decoding. open var userInfo: [CodingUserInfoKey : Any] = [:] + + /// When an XML Element has both attributes and child elements, the CharData within the element will be keyed with the `characterDataToken` + /// - Note The following XML has both attribute data, child elements, and character data: + /// ``` + /// + /// some string value + /// valuevalue + /// + /// ``` + /// The "some string value" will be keyed on "CharData" + open var characterDataToken: String? + /// Options set on the top-level encoder to pass down the decoding hierarchy. internal struct _Options { let dateDecodingStrategy: DateDecodingStrategy @@ -145,7 +157,7 @@ open class XMLDecoder { open func decode(_ type: T.Type, from data: Data) throws -> T { let topLevel: [String: Any] do { - topLevel = try _XMLStackParser.parse(with: data) + topLevel = try _XMLStackParser.parse(with: data, charDataToken: self.characterDataToken) } catch { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid XML.", underlyingError: error)) } diff --git a/XMLParsing/XML/XMLStackParser.swift b/XMLParsing/XML/XMLStackParser.swift index 6aea4f6..d9e1625 100644 --- a/XMLParsing/XML/XMLStackParser.swift +++ b/XMLParsing/XML/XMLStackParser.swift @@ -157,15 +157,17 @@ internal class _XMLElement { parentElement.children[key] = (parentElement.children[key] ?? []) + [element] } - func flatten() -> [String: Any] { + func flatten(with charDataKey: String? = nil) -> [String: Any] { var node: [String: Any] = attributes for childElement in children { for child in childElement.value { - if let content = child.value { - node[childElement.key] = content - } else if !child.children.isEmpty || !child.attributes.isEmpty { - let newValue = child.flatten() + if !child.children.isEmpty || !child.attributes.isEmpty { + var newValue = child.flatten(with: charDataKey) + if let content = child.value, + let charDataKey = charDataKey { + newValue[charDataKey] = content + } if let existingValue = node[childElement.key] { if var array = existingValue as? Array { @@ -177,6 +179,8 @@ internal class _XMLElement { } else { node[childElement.key] = newValue } + } else if let content = child.value { + node[childElement.key] = content } } } @@ -248,12 +252,30 @@ internal class _XMLStackParser: NSObject, XMLParserDelegate { var currentElementName: String? var currentElementData = "" - static func parse(with data: Data) throws -> [String: Any] { + + /// Parses the XML data and returns a dictionary of the parsed output + /// + /// - Parameters: + /// - data: The Data to be parsed + /// - charDataToken: The token to key the charData in mixed content elements off of + /// - Returns: Dictionary of the parsed XML + /// - Throws: DecodingError if there's an issue parsing the XML + /// - Note: + /// When an XML Element has both attributes and child elements, the CharData within the element will be keyed with the `characterDataToken` + /// The following XML has both attribute data, child elements, and character data: + /// ``` + /// + /// some string value + /// valuevalue + /// + /// ``` + /// The "some string value" will be keyed on "CharData" + static func parse(with data: Data, charDataToken: String? = nil) throws -> [String: Any] { let parser = _XMLStackParser() do { if let node = try parser.parse(with: data) { - return node.flatten() + return node.flatten(with: charDataToken) } else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data could not be parsed into XML.")) }