Skip to content

Commit

Permalink
Clean up coding logic, improve box naming (#27)
Browse files Browse the repository at this point in the history
* Made `ArrayBox` and `DictionaryBox` conform to `Sequence`
* Renamed `ArrayBox` to `UnkeyedBox`.
What’s interesting about `ArrayBox` is its unkeyed semantics, not the fact it’s currently using an `Array` as storage, which is an implementation detail after all.
* Renamed `DictionaryBox` to `KeyedBox`.
What’s interesting about `DictionaryBox` is its keyed semantics, not the fact it’s currently using an `Dictionary` as storage, which is an implementation detail after all, and expected to be changed soon, even (to preserve element order).
* Turned `var isFragment` into `protocol SimpleBox: Box`
* Renamed unit test files related to `KeyedBox`/`UnkeyedBox`
* Greatly reduced redundancy of coding logic, simplifying maintenance
* Removed trailing whitespace
  • Loading branch information
regexident authored and MaxDesiatov committed Dec 21, 2018
1 parent 27bf224 commit 57b7b20
Show file tree
Hide file tree
Showing 28 changed files with 570 additions and 676 deletions.
26 changes: 13 additions & 13 deletions Sources/XMLCoder/Auxiliaries/XMLElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,24 @@ internal class _XMLElement {
self.children = children
}

static func createRootElement(rootKey: String, object: ArrayBox) -> _XMLElement? {
static func createRootElement(rootKey: String, object: UnkeyedBox) -> _XMLElement? {
let element = _XMLElement(key: rootKey)

_XMLElement.createElement(parentElement: element, key: rootKey, object: object)

return element
}

static func createRootElement(rootKey: String, object: DictionaryBox) -> _XMLElement? {
static func createRootElement(rootKey: String, object: KeyedBox) -> _XMLElement? {
let element = _XMLElement(key: rootKey)

_XMLElement.modifyElement(element: element, parentElement: nil, key: nil, object: object)

return element
}

fileprivate static func modifyElement(element: _XMLElement, parentElement: _XMLElement?, key: String?, object: DictionaryBox) {
let attributesBox = object[_XMLElement.attributesKey] as? DictionaryBox
fileprivate static func modifyElement(element: _XMLElement, parentElement: _XMLElement?, key: String?, object: KeyedBox) {
let attributesBox = object[_XMLElement.attributesKey] as? KeyedBox
let uniqueAttributes: [(String, String)]? = attributesBox?.unbox().compactMap { key, box in
return box.xmlString().map { (key, $0) }
}
Expand All @@ -59,11 +59,11 @@ internal class _XMLElement {

fileprivate static func createElement(parentElement: _XMLElement, key: String, object: Box) {
switch object {
case let box as ArrayBox:
case let box as UnkeyedBox:
for box in box.unbox() {
_XMLElement.createElement(parentElement: parentElement, key: key, object: box)
}
case let box as DictionaryBox:
case let box as KeyedBox:
modifyElement(element: _XMLElement(key: key), parentElement: parentElement, key: key, object: box)
case _:
let element = _XMLElement(key: key, value: object.xmlString())
Expand All @@ -84,28 +84,28 @@ internal class _XMLElement {
for childElement in children {
for child in childElement.value {
if let content = child.value {
if let oldContent = node[childElement.key] as? ArrayBox {
if let oldContent = node[childElement.key] as? UnkeyedBox {
oldContent.append(StringBox(content))
// FIXME: Box is a reference type, so this shouldn't be necessary:
node[childElement.key] = oldContent
} else if let oldContent = node[childElement.key] {
node[childElement.key] = ArrayBox([oldContent, StringBox(content)])
node[childElement.key] = UnkeyedBox([oldContent, StringBox(content)])
} else {
node[childElement.key] = StringBox(content)
}
} else if !child.children.isEmpty || !child.attributes.isEmpty {
let newValue = child.flatten()

if let existingValue = node[childElement.key] {
if let array = existingValue as? ArrayBox {
array.append(DictionaryBox(newValue))
if let unkeyed = existingValue as? UnkeyedBox {
unkeyed.append(KeyedBox(newValue))
// FIXME: Box is a reference type, so this shouldn't be necessary:
node[childElement.key] = array
node[childElement.key] = unkeyed
} else {
node[childElement.key] = ArrayBox([existingValue, DictionaryBox(newValue)])
node[childElement.key] = UnkeyedBox([existingValue, KeyedBox(newValue)])
}
} else {
node[childElement.key] = DictionaryBox(newValue)
node[childElement.key] = KeyedBox(newValue)
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/XMLCoder/Box/BoolBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ extension BoolBox: Box {
return false
}

var isFragment: Bool {
return true
}

/// # Lexical representation
/// Boolean has a lexical representation consisting of the following
/// legal literals {`true`, `false`, `1`, `0`}.
Expand All @@ -53,6 +49,10 @@ extension BoolBox: Box {
}
}

extension BoolBox: SimpleBox {

}

extension BoolBox: CustomStringConvertible {
var description: String {
return self.unboxed.description
Expand Down
7 changes: 5 additions & 2 deletions Sources/XMLCoder/Box/Box.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import Foundation

protocol Box {
var isNull: Bool { get }
var isFragment: Bool { get }

func xmlString() -> String?
}

/// A box that only describes a single atomic value.
protocol SimpleBox: Box {

}
8 changes: 4 additions & 4 deletions Sources/XMLCoder/Box/DataBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ extension DataBox: Box {
return false
}

var isFragment: Bool {
return true
}

func xmlString() -> String? {
return self.xmlString(format: self.format)
}
}

extension DataBox: SimpleBox {

}

extension DataBox: CustomStringConvertible {
var description: String {
return self.unboxed.description
Expand Down
8 changes: 4 additions & 4 deletions Sources/XMLCoder/Box/DateBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,15 @@ extension DateBox: Box {
return false
}

var isFragment: Bool {
return true
}

func xmlString() -> String? {
return self.xmlString(format: self.format)
}
}

extension DateBox: SimpleBox {

}

extension DateBox: CustomStringConvertible {
var description: String {
return self.unboxed.description
Expand Down
8 changes: 4 additions & 4 deletions Sources/XMLCoder/Box/DecimalBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ extension DecimalBox: Box {
return false
}

var isFragment: Bool {
return true
}

/// # Lexical representation
/// Decimal has a lexical representation consisting of a finite-length sequence of
/// decimal digits separated by a period as a decimal indicator.
Expand All @@ -60,6 +56,10 @@ extension DecimalBox: Box {
}
}

extension DecimalBox: SimpleBox {

}

extension DecimalBox: CustomStringConvertible {
var description: String {
return self.unboxed.description
Expand Down
8 changes: 4 additions & 4 deletions Sources/XMLCoder/Box/FloatBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ extension FloatBox: Box {
return false
}

var isFragment: Bool {
return true
}

/// # Lexical representation
/// float values have a lexical representation consisting of a mantissa followed, optionally,
/// by the character `"E"` or `"e"`, followed by an exponent. The exponent **must** be an integer.
Expand Down Expand Up @@ -78,6 +74,10 @@ extension FloatBox: Box {
}
}

extension FloatBox: SimpleBox {

}

extension FloatBox: CustomStringConvertible {
var description: String {
return self.unboxed.description
Expand Down
8 changes: 4 additions & 4 deletions Sources/XMLCoder/Box/IntBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ extension IntBox: Box {
return false
}

var isFragment: Bool {
return true
}

/// # Lexical representation
/// Integer has a lexical representation consisting of a finite-length sequence of
/// decimal digits with an optional leading sign. If the sign is omitted, `"+"` is assumed.
Expand All @@ -55,6 +51,10 @@ extension IntBox: Box {
}
}

extension IntBox: SimpleBox {

}

extension IntBox: CustomStringConvertible {
var description: String {
return self.unboxed.description
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// DictionaryBox.swift
// KeyedBox.swift
// XMLCoderPackageDescription
//
// Created by Vincent Esche on 11/19/18.
Expand All @@ -8,7 +8,7 @@
import Foundation

// Minimalist implementation of an order-preserving keyed box:
class DictionaryBox {
class KeyedBox {
typealias Key = String
typealias Value = Box

Expand Down Expand Up @@ -58,27 +58,30 @@ class DictionaryBox {
return try self.unboxed.compactMap(transform)
}

func mapValues(_ transform: (Value) throws -> Value) rethrows -> DictionaryBox {
return DictionaryBox(try self.unboxed.mapValues(transform))
func mapValues(_ transform: (Value) throws -> Value) rethrows -> KeyedBox {
return KeyedBox(try self.unboxed.mapValues(transform))
}
}

extension DictionaryBox: Box {
extension KeyedBox: Box {
var isNull: Bool {
return false
}

var isFragment: Bool {
return false
}

func xmlString() -> String? {
return nil
}
}

extension KeyedBox: Sequence {
typealias Iterator = Unboxed.Iterator

func makeIterator() -> Iterator {
return self.unboxed.makeIterator()
}
}

extension DictionaryBox: CustomStringConvertible {
extension KeyedBox: CustomStringConvertible {
var description: String {
return self.unboxed.description
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/XMLCoder/Box/NullBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ extension NullBox: Box {
return true
}

var isFragment: Bool {
return true
}

func xmlString() -> String? {
return nil
}
}

extension NullBox: SimpleBox {

}

extension NullBox: Equatable {
static func == (lhs: NullBox, rhs: NullBox) -> Bool {
return true
Expand Down
8 changes: 4 additions & 4 deletions Sources/XMLCoder/Box/StringBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ extension StringBox: Box {
return false
}

var isFragment: Bool {
return true
}

func xmlString() -> String? {
return self.unboxed.description
}
}

extension StringBox: SimpleBox {

}

extension StringBox: CustomStringConvertible {
var description: String {
return self.unboxed.description
Expand Down
8 changes: 4 additions & 4 deletions Sources/XMLCoder/Box/UIntBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ extension UIntBox: Box {
return false
}

var isFragment: Bool {
return true
}

/// # Lexical representation
/// Unsigned integer has a lexical representation consisting of an optional
/// sign followed by a finite-length sequence of decimal digits.
Expand All @@ -58,6 +54,10 @@ extension UIntBox: Box {
}
}

extension UIntBox: SimpleBox {

}

extension UIntBox: CustomStringConvertible {
var description: String {
return self.unboxed.description
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// ArrayBox.swift
// UnkeyedBox.swift
// XMLCoderPackageDescription
//
// Created by Vincent Esche on 11/20/18.
Expand All @@ -8,7 +8,7 @@
import Foundation

// Minimalist implementation of an order-preserving unkeyed box:
class ArrayBox {
class UnkeyedBox {
typealias Element = Box
typealias Unboxed = [Element]

Expand Down Expand Up @@ -56,21 +56,25 @@ class ArrayBox {
}
}

extension ArrayBox: Box {
extension UnkeyedBox: Box {
var isNull: Bool {
return false
}

var isFragment: Bool {
return false
}

func xmlString() -> String? {
return nil
}
}

extension ArrayBox: CustomStringConvertible {
extension UnkeyedBox: Sequence {
typealias Iterator = Unboxed.Iterator

func makeIterator() -> Iterator {
return self.unboxed.makeIterator()
}
}

extension UnkeyedBox: CustomStringConvertible {
var description: String {
return self.unboxed.description
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/XMLCoder/Decoder/DecodingErrorExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ internal extension DecodingError {
return "an unsigned integer value"
case is FloatBox:
return "a floating-point value"
case is ArrayBox:
case is UnkeyedBox:
return "a array value"
case is DictionaryBox:
case is KeyedBox:
return "a dictionary value"
case _:
return "\(type(of: box))"
Expand Down
Loading

0 comments on commit 57b7b20

Please sign in to comment.