Skip to content

Resolve #5 Allow for CharData to be keyed #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion Sources/XMLParsing/Decoder/XMLDecoder.swift
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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:
/// ```
/// <SomeElement SomeAttribute="value">
/// some string value
/// <SomeOtherElement>valuevalue</SomeOtherElement>
/// </SomeElement>
/// ```
/// 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
Expand Down Expand Up @@ -145,7 +157,7 @@ open class XMLDecoder {
open func decode<T : Decodable>(_ 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))
}
Expand Down
36 changes: 29 additions & 7 deletions Sources/XMLParsing/XMLStackParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Any> {
Expand All @@ -177,6 +179,8 @@ internal class _XMLElement {
} else {
node[childElement.key] = newValue
}
} else if let content = child.value {
node[childElement.key] = content
}
}
}
Expand Down Expand Up @@ -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:
/// ```
/// <SomeElement SomeAttribute="value">
/// some string value
/// <SomeOtherElement>valuevalue</SomeOtherElement>
/// </SomeElement>
/// ```
/// 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."))
}
Expand Down