Skip to content

Commit

Permalink
Refactor XMLCoderElement.flatten, add tests (#88)
Browse files Browse the repository at this point in the history
* Refactor XMLCoderElement.flatten, add tests
* Add missing assert to FlattenTests
* Fix FlattenTests error due to missing assert
  • Loading branch information
MaxDesiatov committed Apr 8, 2019
1 parent 3944866 commit f241d42
Show file tree
Hide file tree
Showing 9 changed files with 408 additions and 275 deletions.
2 changes: 1 addition & 1 deletion LICENSE
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2018 Shawn Moore
Copyright (c) 2018-2019 Shawn Moore and XMLCoder contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
81 changes: 2 additions & 79 deletions Sources/XMLCoder/Auxiliaries/Box/KeyedBox.swift
Expand Up @@ -5,84 +5,6 @@
// Created by Vincent Esche on 11/19/18.
//

struct KeyedStorage<Key: Hashable & Comparable, Value> {
struct Iterator: IteratorProtocol {
fileprivate var orderIterator: Order.Iterator
fileprivate var buffer: Buffer
mutating func next() -> (Key, Value)? {
guard
let key = orderIterator.next(),
let value = buffer[key]
else { return nil }

return (key, value)
}
}

typealias Buffer = [Key: Value]
typealias Order = [Key]

fileprivate var order = Order()
fileprivate var buffer = Buffer()

var isEmpty: Bool {
return buffer.isEmpty
}

var count: Int {
return buffer.count
}

var keys: Buffer.Keys {
return buffer.keys
}

init<S>(_ sequence: S) where S: Sequence, S.Element == (Key, Value) {
order = sequence.map { $0.0 }
buffer = Dictionary(uniqueKeysWithValues: sequence)
}

subscript(key: Key) -> Value? {
get {
return buffer[key]
}
set {
if buffer[key] == nil {
order.append(key)
}
buffer[key] = newValue
}
}

func map<T>(_ transform: (Key, Value) throws -> T) rethrows -> [T] {
return try buffer.map(transform)
}

func compactMap<T>(_ transform: ((Key, Value)) throws -> T?) rethrows -> [T] {
return try buffer.compactMap(transform)
}

init() {}
}

extension KeyedStorage: Sequence {
func makeIterator() -> Iterator {
return Iterator(orderIterator: order.makeIterator(), buffer: buffer)
}
}

extension KeyedStorage: CustomStringConvertible {
var description: String {
let result = order.compactMap { (key: Key) -> String? in
guard let value = buffer[key] else { return nil }

return "\"\(key)\": \(value)"
}.joined(separator: ", ")

return "[\(result)]"
}
}

struct KeyedBox {
typealias Key = String
typealias Attribute = SimpleBox
Expand All @@ -104,7 +26,8 @@ struct KeyedBox {

extension KeyedBox {
init<E, A>(elements: E, attributes: A)
where E: Sequence, E.Element == (Key, Element), A: Sequence, A.Element == (Key, Attribute) {
where E: Sequence, E.Element == (Key, Element),
A: Sequence, A.Element == (Key, Attribute) {
let elements = Elements(elements)
let attributes = Attributes(attributes)
self.init(elements: elements, attributes: attributes)
Expand Down
149 changes: 149 additions & 0 deletions Sources/XMLCoder/Auxiliaries/KeyedStorage.swift
@@ -0,0 +1,149 @@
//
// KeyedStorage.swift
// XMLCoder
//
// Created by Max Desiatov on 07/04/2019.
//

struct KeyedStorage<Key: Hashable & Comparable, Value> {
struct Iterator: IteratorProtocol {
fileprivate var orderIterator: Order.Iterator
fileprivate var buffer: Buffer
mutating func next() -> (Key, Value)? {
guard
let key = orderIterator.next(),
let value = buffer[key]
else { return nil }

return (key, value)
}
}

typealias Buffer = [Key: Value]
typealias Order = [Key]

fileprivate var order = Order()
fileprivate var buffer = Buffer()

var isEmpty: Bool {
return buffer.isEmpty
}

var count: Int {
return buffer.count
}

var keys: Buffer.Keys {
return buffer.keys
}

init<S>(_ sequence: S) where S: Sequence, S.Element == (Key, Value) {
order = sequence.map { $0.0 }
buffer = Dictionary(uniqueKeysWithValues: sequence)
}

subscript(key: Key) -> Value? {
get {
return buffer[key]
}
set {
if buffer[key] == nil {
order.append(key)
}
buffer[key] = newValue
}
}

func map<T>(_ transform: (Key, Value) throws -> T) rethrows -> [T] {
return try buffer.map(transform)
}

func compactMap<T>(
_ transform: ((Key, Value)) throws -> T?
) rethrows -> [T] {
return try buffer.compactMap(transform)
}

init() {}
}

extension KeyedStorage: Sequence {
func makeIterator() -> Iterator {
return Iterator(orderIterator: order.makeIterator(), buffer: buffer)
}
}

extension KeyedStorage: CustomStringConvertible {
var description: String {
let result = order.compactMap { (key: Key) -> String? in
guard let value = buffer[key] else { return nil }

return "\"\(key)\": \(value)"
}.joined(separator: ", ")

return "[\(result)]"
}
}

private extension KeyedStorage where Key == String, Value == Box {
mutating func merge(value: String, at key: String) {
switch self[key] {
case var unkeyedBox as UnkeyedBox:
unkeyedBox.append(StringBox(value))
self[key] = unkeyedBox
case let stringBox as StringBox:
self[key] = UnkeyedBox([stringBox, StringBox(value)])
default:
self[key] = StringBox(value)
}
}

mutating func mergeElementsAttributes(from element: XMLCoderElement) {
let hasValue = element.value != nil

let key = element.key
let content = element.flatten()
switch self[key] {
case var unkeyedBox as UnkeyedBox:
unkeyedBox.append(content)
self[key] = unkeyedBox
case let keyedBox as KeyedBox:
self[key] = UnkeyedBox([keyedBox, content])
case let box? where !hasValue:
self[key] = UnkeyedBox([box, content])
default:
self[key] = content
}
}

mutating func mergeNull(at key: String) {
switch self[key] {
case var unkeyedBox as UnkeyedBox:
unkeyedBox.append(NullBox())
self[key] = unkeyedBox
case let box?:
self[key] = UnkeyedBox([box, NullBox()])
default:
self[key] = NullBox()
}
}
}

extension KeyedStorage where Key == String, Value == Box {
func merge(element: XMLCoderElement) -> KeyedStorage<String, Box> {
var result = self

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

if hasElements || hasAttributes {
result.mergeElementsAttributes(from: element)
} else if let value = element.value {
result.merge(value: value, at: element.key)
} else {
result.mergeNull(at: element.key)
}

return result
}
}
56 changes: 4 additions & 52 deletions Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift
Expand Up @@ -44,59 +44,13 @@ struct XMLCoderElement: Equatable {
elements.append(element)
}

// FIXME: this should be split into separate functions and
// thoroughtly tested
func flatten() -> KeyedBox {
let attributes = KeyedStorage(self.attributes.mapValues {
StringBox($0) as SimpleBox
}.shuffled())
let storage = KeyedStorage<String, Box>()

var elements = self.elements.reduce(storage) { result, element in
var result = result
let key = element.key

let hasValue = element.value != nil
let hasElements = !element.elements.isEmpty
let hasAttributes = !element.attributes.isEmpty

if hasValue || hasElements || hasAttributes {
if let content = element.value {
switch result[key] {
case var unkeyedBox as UnkeyedBox:
unkeyedBox.append(StringBox(content))
result[key] = unkeyedBox
case let stringBox as StringBox:
result[key] = UnkeyedBox([stringBox, StringBox(content)])
default:
result[key] = StringBox(content)
}
}
if hasElements || hasAttributes {
let content = element.flatten()
switch result[key] {
case var unkeyedBox as UnkeyedBox:
unkeyedBox.append(content)
result[key] = unkeyedBox
case let box? where !hasValue:
result[key] = UnkeyedBox([box, content])
default:
result[key] = content
}
}
} else {
switch result[key] {
case var unkeyedBox as UnkeyedBox:
unkeyedBox.append(NullBox())
result[key] = unkeyedBox
case let box?:
result[key] = UnkeyedBox([box, NullBox()])
default:
result[key] = NullBox()
}
}
return result
}
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
Expand Down Expand Up @@ -287,11 +241,9 @@ struct XMLCoderElement: Equatable {

extension XMLCoderElement {
init(key: String, box: UnkeyedBox) {
let elements = box.map { box in
XMLCoderElement(key: key, box: box)
}

self.init(key: key, elements: elements)
self.init(key: key, elements: box.map {
XMLCoderElement(key: key, box: $0)
})
}

init(key: String, box: KeyedBox) {
Expand Down
@@ -1,5 +1,5 @@
//
// XMLDecoder.swift
// SingleValueDecodingContainer.swift
// XMLCoder
//
// Created by Shawn Moore on 11/20/17.
Expand Down

0 comments on commit f241d42

Please sign in to comment.