Skip to content

Commit

Permalink
Support for mixed-content node contents
Browse files Browse the repository at this point in the history
  • Loading branch information
ultramiraculous committed Dec 17, 2019
1 parent c47aa6a commit df68c01
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 65 deletions.
7 changes: 4 additions & 3 deletions Sources/XMLCoder/Auxiliaries/KeyedStorage.swift
Expand Up @@ -77,11 +77,12 @@ extension KeyedStorage where Key == String, Value == Box {

let hasElements = !element.elements.isEmpty
let hasAttributes = !element.attributes.isEmpty

let hasText = element.stringValue != nil

if hasElements || hasAttributes {
result.append(element.transformToBoxTree(), at: element.key)
} else if let value = element.value {
result.append(StringBox(value), at: element.key)
} else if hasText {
result.append(element.transformToBoxTree(), at: element.key)
} else {
result.append(SingleKeyedBox(key: element.key, element: NullBox()), at: element.key)
}
Expand Down
131 changes: 90 additions & 41 deletions Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift
Expand Up @@ -23,53 +23,107 @@ struct XMLCoderElement: Equatable {
]

let key: String
private(set) var value: String?
private(set) var stringValue: String?
private(set) var elements: [XMLCoderElement] = []
private(set) var attributes: [Attribute] = []
private(set) var containsTextNodes: Bool = false

var isStringNode: Bool {
return key == ""
}

var isCDATANode: Bool {
return key == "#CDATA"
}

var isTextNode: Bool {
return isStringNode || isCDATANode
}

init(
key: String,
value: String? = nil,
elements: [XMLCoderElement] = [],
attributes: [Attribute] = []
) {
self.key = key
self.value = value
self.stringValue = nil
self.elements = elements
self.attributes = attributes
}

mutating func append(value string: String) {
guard value != nil else {
value = string
return
}
value?.append(string)
init(
key: String,
stringValue string: String,
attributes: [Attribute] = []) {
self.key = key
self.elements = [XMLCoderElement(stringValue: string)]
self.attributes = attributes
containsTextNodes = true
}

init(
key: String,
cdataValue string: String,
attributes: [Attribute] = []) {
self.key = key
self.elements = [XMLCoderElement(cdataValue: string)]
self.attributes = attributes
containsTextNodes = true
}

init(stringValue string: String) {
self.key = ""
self.stringValue = string
}

init(cdataValue string: String) {
self.key = "#CDATA"
self.stringValue = string
}

mutating func append(element: XMLCoderElement, forKey key: String) {
elements.append(element)
containsTextNodes = containsTextNodes || element.isTextNode
}

func transformToBoxTree() -> KeyedBox {
mutating func append(string: String) {
if elements.last?.isTextNode == true {
let oldValue = elements[elements.count - 1].stringValue ?? ""
elements[elements.count - 1].stringValue = oldValue + string
} else {
elements.append(XMLCoderElement(stringValue: string))
}
containsTextNodes = true
}

mutating func append(cdata string: String) {
if elements.last?.isCDATANode == true {
let oldValue = elements[elements.count - 1].stringValue ?? ""
elements[elements.count - 1].stringValue = oldValue + string
} else {
elements.append(XMLCoderElement(cdataValue: string))
}
containsTextNodes = true
}

func transformToBoxTree() -> Box {
if isStringNode {
return StringBox(stringValue!)
} else if isCDATANode {
return StringBox("![CDATA[\(stringValue!)")
}

let attributes = KeyedStorage(self.attributes.map { attribute in
(key: attribute.key, value: StringBox(attribute.value) as SimpleBox)
})
let storage = KeyedStorage<String, Box>()
var elements = self.elements.reduce(storage) { $0.merge(element: $1) }

// Handle attributed unkeyed value <foo attr="bar">zap</foo>
// Value should be zap. Detect only when no other elements exist
if elements.isEmpty, let value = value {
elements.append(StringBox(value), at: "")
}
let elements = self.elements.reduce(storage) { $0.merge(element: $1) }
return KeyedBox(elements: elements, attributes: attributes)
}

func toXMLString(with header: XMLHeader? = nil,
withCDATA cdata: Bool,
formatting: XMLEncoder.OutputFormatting,
ignoreEscaping _: Bool = false) -> String {
formatting: XMLEncoder.OutputFormatting) -> String {
if let header = header, let headerXML = header.toXML() {
return headerXML + _toXMLString(withCDATA: cdata, formatting: formatting)
}
Expand Down Expand Up @@ -100,6 +154,10 @@ struct XMLCoderElement: Equatable {
formatting: XMLEncoder.OutputFormatting,
prettyPrinted: Bool
) -> String {
if let stringValue = element.stringValue {
return stringValue.escape(XMLCoderElement.escapedCharacterSet)
}

var string = ""
string += element._toXMLString(
indented: level + 1, withCDATA: cdata, formatting: formatting
Expand Down Expand Up @@ -149,7 +207,7 @@ struct XMLCoderElement: Equatable {
at: level,
cdata: cdata,
formatting: formatting,
prettyPrinted: prettyPrinted)
prettyPrinted: (prettyPrinted && !containsTextNodes))
}
}

Expand Down Expand Up @@ -195,39 +253,27 @@ struct XMLCoderElement: Equatable {
private func _toXMLString(
indented level: Int = 0,
withCDATA cdata: Bool,
formatting: XMLEncoder.OutputFormatting,
ignoreEscaping: Bool = false
) -> String {
formatting: XMLEncoder.OutputFormatting) -> String {
let prettyPrinted = formatting.contains(.prettyPrinted)
let indentation = String(
repeating: " ", count: (prettyPrinted ? level : 0) * 4
)
var string = indentation

if !key.isEmpty {
string += "<\(key)"
}

formatXMLAttributes(formatting, &string)

if let value = value {
if !key.isEmpty {
string += ">"
}
if !ignoreEscaping {
string += (cdata == true ? "<![CDATA[\(value)]]>" :
"\(value.escape(XMLCoderElement.escapedCharacterSet))")
} else {
string += "\(value)"
}

if !elements.isEmpty {
let prettyPrintElements = prettyPrinted && !containsTextNodes
if !key.isEmpty {
string += "</\(key)>"
string += prettyPrintElements ? ">\n" : ">"
}
} else if !elements.isEmpty {
string += prettyPrinted ? ">\n" : ">"
formatXMLElements(formatting, &string, level, cdata, prettyPrinted)
formatXMLElements(formatting, &string, level, cdata, prettyPrintElements)

string += indentation
if prettyPrintElements { string += indentation }
if !key.isEmpty {
string += "</\(key)>"
}
Expand Down Expand Up @@ -298,8 +344,11 @@ extension XMLCoderElement {
}

init(key: String, box: SimpleBox) {
self.init(key: key)
value = box.xmlString
if let value = box.xmlString {
self.init(key: key, stringValue: value)
} else {
self.init(key: key)
}
}

init(key: String, box: Box) {
Expand Down
11 changes: 8 additions & 3 deletions Sources/XMLCoder/Auxiliaries/XMLStackParser.swift
Expand Up @@ -26,7 +26,7 @@ class XMLStackParser: NSObject {
errorContextLength length: UInt,
shouldProcessNamespaces: Bool,
trimValueWhitespaces: Bool
) throws -> KeyedBox {
) throws -> Box {
let parser = XMLStackParser(trimValueWhitespaces: trimValueWhitespaces)

let node = try parser.parse(
Expand Down Expand Up @@ -159,8 +159,13 @@ extension XMLStackParser: XMLParserDelegate {
}

func parser(_: XMLParser, foundCharacters string: String) {
let processedString = process(string: string)
guard processedString.count > 0, string.count != 0 else {
return
}

withCurrentElement { currentElement in
currentElement.append(value: process(string: string))
currentElement.append(string: processedString)
}
}

Expand All @@ -170,7 +175,7 @@ extension XMLStackParser: XMLParserDelegate {
}

withCurrentElement { currentElement in
currentElement.append(value: process(string: string))
currentElement.append(cdata: string)
}
}
}
10 changes: 7 additions & 3 deletions Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift
Expand Up @@ -212,9 +212,13 @@ extension XMLDecoderImplementation {
else { throw error }
return value
case let singleKeyedBox as SingleKeyedBox:
guard let value = singleKeyedBox.element as? B
else { throw error }
return value
if let value = singleKeyedBox.element as? B {
return value
} else if let box = singleKeyedBox.element as? KeyedBox, let value = box.elements[""].first as? B {
return value
} else {
throw error
}
case is NullBox:
throw error
case let keyedBox as KeyedBox:
Expand Down
2 changes: 1 addition & 1 deletion Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift
Expand Up @@ -162,7 +162,7 @@ struct XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol {

let elements = container.unboxed.elements[key.stringValue]

if let containsKeyed = elements as? [KeyedBox], let keyed = containsKeyed.first {
if let containsKeyed = elements as? [KeyedBox], containsKeyed.count == 1, let keyed = containsKeyed.first {
return XMLUnkeyedDecodingContainer(
referencing: decoder,
wrapping: SharedBox(keyed.elements.map(SingleKeyedBox.init))
Expand Down
11 changes: 6 additions & 5 deletions Tests/XMLCoderTests/Auxiliary/XMLElementTests.swift
Expand Up @@ -13,7 +13,7 @@ class XMLElementTests: XCTestCase {
let null = XMLCoderElement(key: "foo")

XCTAssertEqual(null.key, "foo")
XCTAssertNil(null.value)
XCTAssertNil(null.stringValue)
XCTAssertEqual(null.elements, [])
XCTAssertEqual(null.attributes, [])
}
Expand All @@ -22,7 +22,7 @@ class XMLElementTests: XCTestCase {
let keyed = XMLCoderElement(key: "foo", box: UnkeyedBox())

XCTAssertEqual(keyed.key, "foo")
XCTAssertNil(keyed.value)
XCTAssertNil(keyed.stringValue)
XCTAssertEqual(keyed.elements, [])
XCTAssertEqual(keyed.attributes, [])
}
Expand All @@ -34,17 +34,18 @@ class XMLElementTests: XCTestCase {
))

XCTAssertEqual(keyed.key, "foo")
XCTAssertNil(keyed.value)
XCTAssertNil(keyed.stringValue)
XCTAssertEqual(keyed.elements, [])
XCTAssertEqual(keyed.attributes, [Attribute(key: "blee", value: "42")])
}

func testInitSimple() {
let keyed = XMLCoderElement(key: "foo", box: StringBox("bar"))
let element = XMLCoderElement(stringValue: "bar")

XCTAssertEqual(keyed.key, "foo")
XCTAssertEqual(keyed.value, "bar")
XCTAssertEqual(keyed.elements, [])
XCTAssertNil(keyed.stringValue)
XCTAssertEqual(keyed.elements, [element])
XCTAssertEqual(keyed.attributes, [])
}
}
5 changes: 2 additions & 3 deletions Tests/XMLCoderTests/Auxiliary/XMLStackParserTests.swift
Expand Up @@ -29,15 +29,14 @@ class XMLStackParserTests: XCTestCase {

let expected = XMLCoderElement(
key: "container",
value: "",
elements: [
XMLCoderElement(
key: "value",
value: "42"
stringValue: "42"
),
XMLCoderElement(
key: "data",
value: "lorem ipsum"
cdataValue: "lorem ipsum"
),
]
)
Expand Down
9 changes: 3 additions & 6 deletions Tests/XMLCoderTests/Minimal/BoxTreeTests.swift
Expand Up @@ -12,24 +12,21 @@ class BoxTreeTests: XCTestCase {
func testNestedValues() throws {
let e1 = XMLCoderElement(
key: "foo",
value: "456",
elements: [],
stringValue: "456",
attributes: [Attribute(key: "id", value: "123")]
)
let e2 = XMLCoderElement(
key: "foo",
value: "123",
elements: [],
stringValue: "123",
attributes: [Attribute(key: "id", value: "789")]
)
let root = XMLCoderElement(
key: "container",
value: nil,
elements: [e1, e2],
attributes: []
)

let boxTree = root.transformToBoxTree()
let boxTree = root.transformToBoxTree() as! KeyedBox
let foo = boxTree.elements["foo"]
XCTAssertEqual(foo.count, 2)
}
Expand Down

0 comments on commit df68c01

Please sign in to comment.