Skip to content

Commit c063344

Browse files
committed
Merge branch 'dev'
2 parents 8cd1c2d + d0124d7 commit c063344

44 files changed

Lines changed: 1859 additions & 141 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Add Nodal as a dependency in your `Package.swift`:
1212

1313
```swift
1414
dependencies: [
15-
.package(url: "https://github.com/tomasf/Nodal.git", .upToNextMinor(from: "0.3.0"))
15+
.package(url: "https://github.com/tomasf/Nodal.git", from: "1.0.0")
1616
]
1717
```
1818

@@ -78,13 +78,71 @@ let document = try Document(string: """
7878
</catalog>
7979
""")
8080

81-
if let name = query.firstNodeResult(with: document)?.node?.textContent {
82-
print("Book name:", name) // Outputs "XML Developer's Guide"
81+
let query = try XPathQuery("//book[@id='bk101']/title")
82+
if let title = query.firstNodeResult(with: document)?.node?.textContent {
83+
print("Book title:", title) // Outputs "XML Developer's Guide"
8384
}
8485
```
8586

8687
While Nodal supports XML namespaces when working with the DOM, its XPath implementation does not support namespaces. This limitation arises from pugixml. To work with elements and attributes in documents that use namespaces, you must use their qualified names in your XPath expressions.
8788

89+
## Value Serialization
90+
91+
Nodal provides protocols for serializing Swift values to and from XML. The `XMLValueCodable` protocol handles encoding and decoding values as strings (for attributes and text content), while `XMLElementCodable` handles encoding and decoding entire elements.
92+
93+
### Built-in Type Support
94+
95+
Common types like `Int`, `Double`, `Bool`, `String`, `UUID`, `URL`, `Date`, and `Data` conform to `XMLValueCodable` out of the box:
96+
97+
```swift
98+
let root = document.makeDocumentElement(name: "item")
99+
root.setValue(42, forAttribute: "count")
100+
root.setValue(UUID(), forAttribute: "id")
101+
root.setValue(Date.now, forAttribute: "created")
102+
103+
let count: Int = try root.value(forAttribute: "count")
104+
```
105+
106+
### Configurable Formats
107+
108+
You can configure how `Date` and `Data` values are serialized by setting properties on the document:
109+
110+
```swift
111+
document.dateFormat = .iso8601 // Default, e.g. "2024-01-15T10:30:00Z"
112+
document.dateFormat = .secondsSince1970 // Unix timestamp
113+
document.dateFormat = .millisecondsSince1970
114+
115+
document.dataFormat = .base64 // Default
116+
document.dataFormat = .hex // Hexadecimal encoding
117+
```
118+
119+
### Custom Types
120+
121+
Conform your own types to `XMLValueCodable` for string-based serialization, or `XMLElementCodable` for element-based serialization:
122+
123+
```swift
124+
struct Point: XMLValueCodable {
125+
var x: Double
126+
var y: Double
127+
128+
func xmlStringValue(for node: Node) -> String {
129+
"\(x),\(y)"
130+
}
131+
132+
init(xmlStringValue: String, for node: Node) throws {
133+
let parts = xmlStringValue.split(separator: ",")
134+
guard parts.count == 2,
135+
let x = Double(parts[0]),
136+
let y = Double(parts[1])
137+
else {
138+
throw XMLValueError.invalidFormat(expected: "x,y", found: xmlStringValue)
139+
}
140+
self.x = x
141+
self.y = y
142+
}
143+
}
144+
```
145+
88146
## Contributions
89147

90148
Contributions are welcome! If you have ideas, suggestions, or improvements, feel free to open an issue or submit a pull request.

Sources/Nodal/Codable/Value/Foundation+XMLValueCodable.swift

Lines changed: 65 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import Foundation
22

33
extension String: XMLValueCodable {
4-
public var xmlStringValue: String { self }
5-
public init(xmlStringValue: String) throws { self = xmlStringValue }
4+
public func xmlStringValue(for node: Node) -> String { self }
5+
public init(xmlStringValue: String, for node: Node) throws { self = xmlStringValue }
66
}
77

88
extension Bool: XMLValueCodable {
9-
public var xmlStringValue: String { self ? "true" : "false" }
9+
public func xmlStringValue(for node: Node) -> String { self ? "true" : "false" }
1010

11-
public init(xmlStringValue: String) throws {
11+
public init(xmlStringValue: String, for node: Node) throws {
1212
switch xmlStringValue {
1313
case "true", "1": self = true
1414
case "false", "0": self = false
@@ -18,9 +18,9 @@ extension Bool: XMLValueCodable {
1818
}
1919

2020
extension Double: XMLValueCodable {
21-
public var xmlStringValue: String { String(self) }
21+
public func xmlStringValue(for node: Node) -> String { String(self) }
2222

23-
public init(xmlStringValue: String) throws {
23+
public init(xmlStringValue: String, for node: Node) throws {
2424
guard let double = Double(xmlStringValue) else {
2525
throw XMLValueError.invalidFormat(expected: "Double", found: xmlStringValue)
2626
}
@@ -29,9 +29,9 @@ extension Double: XMLValueCodable {
2929
}
3030

3131
extension Float: XMLValueCodable {
32-
public var xmlStringValue: String { String(self) }
32+
public func xmlStringValue(for node: Node) -> String { String(self) }
3333

34-
public init(xmlStringValue: String) throws {
34+
public init(xmlStringValue: String, for node: Node) throws {
3535
guard let float = Float(xmlStringValue) else {
3636
throw XMLValueError.invalidFormat(expected: "Float", found: xmlStringValue)
3737
}
@@ -40,9 +40,9 @@ extension Float: XMLValueCodable {
4040
}
4141

4242
extension Int: XMLValueCodable {
43-
public var xmlStringValue: String { String(self) }
43+
public func xmlStringValue(for node: Node) -> String { String(self) }
4444

45-
public init(xmlStringValue: String) throws {
45+
public init(xmlStringValue: String, for node: Node) throws {
4646
guard let int = Int(xmlStringValue) else {
4747
throw XMLValueError.invalidFormat(expected: "Int", found: xmlStringValue)
4848
}
@@ -51,9 +51,9 @@ extension Int: XMLValueCodable {
5151
}
5252

5353
extension Int8: XMLValueCodable {
54-
public var xmlStringValue: String { String(self) }
54+
public func xmlStringValue(for node: Node) -> String { String(self) }
5555

56-
public init(xmlStringValue: String) throws {
56+
public init(xmlStringValue: String, for node: Node) throws {
5757
guard let value = Int8(xmlStringValue) else {
5858
throw XMLValueError.invalidFormat(expected: "Int8", found: xmlStringValue)
5959
}
@@ -62,9 +62,9 @@ extension Int8: XMLValueCodable {
6262
}
6363

6464
extension Int16: XMLValueCodable {
65-
public var xmlStringValue: String { String(self) }
65+
public func xmlStringValue(for node: Node) -> String { String(self) }
6666

67-
public init(xmlStringValue: String) throws {
67+
public init(xmlStringValue: String, for node: Node) throws {
6868
guard let value = Int16(xmlStringValue) else {
6969
throw XMLValueError.invalidFormat(expected: "Int16", found: xmlStringValue)
7070
}
@@ -73,9 +73,9 @@ extension Int16: XMLValueCodable {
7373
}
7474

7575
extension Int32: XMLValueCodable {
76-
public var xmlStringValue: String { String(self) }
76+
public func xmlStringValue(for node: Node) -> String { String(self) }
7777

78-
public init(xmlStringValue: String) throws {
78+
public init(xmlStringValue: String, for node: Node) throws {
7979
guard let value = Int32(xmlStringValue) else {
8080
throw XMLValueError.invalidFormat(expected: "Int32", found: xmlStringValue)
8181
}
@@ -84,9 +84,9 @@ extension Int32: XMLValueCodable {
8484
}
8585

8686
extension Int64: XMLValueCodable {
87-
public var xmlStringValue: String { String(self) }
87+
public func xmlStringValue(for node: Node) -> String { String(self) }
8888

89-
public init(xmlStringValue: String) throws {
89+
public init(xmlStringValue: String, for node: Node) throws {
9090
guard let value = Int64(xmlStringValue) else {
9191
throw XMLValueError.invalidFormat(expected: "Int64", found: xmlStringValue)
9292
}
@@ -95,9 +95,9 @@ extension Int64: XMLValueCodable {
9595
}
9696

9797
extension UInt: XMLValueCodable {
98-
public var xmlStringValue: String { String(self) }
98+
public func xmlStringValue(for node: Node) -> String { String(self) }
9999

100-
public init(xmlStringValue: String) throws {
100+
public init(xmlStringValue: String, for node: Node) throws {
101101
guard let value = UInt(xmlStringValue) else {
102102
throw XMLValueError.invalidFormat(expected: "UInt", found: xmlStringValue)
103103
}
@@ -106,9 +106,9 @@ extension UInt: XMLValueCodable {
106106
}
107107

108108
extension UInt8: XMLValueCodable {
109-
public var xmlStringValue: String { String(self) }
109+
public func xmlStringValue(for node: Node) -> String { String(self) }
110110

111-
public init(xmlStringValue: String) throws {
111+
public init(xmlStringValue: String, for node: Node) throws {
112112
guard let value = UInt8(xmlStringValue) else {
113113
throw XMLValueError.invalidFormat(expected: "UInt8", found: xmlStringValue)
114114
}
@@ -117,9 +117,9 @@ extension UInt8: XMLValueCodable {
117117
}
118118

119119
extension UInt16: XMLValueCodable {
120-
public var xmlStringValue: String { String(self) }
120+
public func xmlStringValue(for node: Node) -> String { String(self) }
121121

122-
public init(xmlStringValue: String) throws {
122+
public init(xmlStringValue: String, for node: Node) throws {
123123
guard let value = UInt16(xmlStringValue) else {
124124
throw XMLValueError.invalidFormat(expected: "UInt16", found: xmlStringValue)
125125
}
@@ -128,9 +128,9 @@ extension UInt16: XMLValueCodable {
128128
}
129129

130130
extension UInt32: XMLValueCodable {
131-
public var xmlStringValue: String { String(self) }
131+
public func xmlStringValue(for node: Node) -> String { String(self) }
132132

133-
public init(xmlStringValue: String) throws {
133+
public init(xmlStringValue: String, for node: Node) throws {
134134
guard let value = UInt32(xmlStringValue) else {
135135
throw XMLValueError.invalidFormat(expected: "UInt32", found: xmlStringValue)
136136
}
@@ -139,9 +139,9 @@ extension UInt32: XMLValueCodable {
139139
}
140140

141141
extension UInt64: XMLValueCodable {
142-
public var xmlStringValue: String { String(self) }
142+
public func xmlStringValue(for node: Node) -> String { String(self) }
143143

144-
public init(xmlStringValue: String) throws {
144+
public init(xmlStringValue: String, for node: Node) throws {
145145
guard let value = UInt64(xmlStringValue) else {
146146
throw XMLValueError.invalidFormat(expected: "UInt64", found: xmlStringValue)
147147
}
@@ -150,25 +150,56 @@ extension UInt64: XMLValueCodable {
150150
}
151151

152152
extension UUID: XMLValueCodable {
153-
public var xmlStringValue: String { uuidString }
153+
public func xmlStringValue(for node: Node) -> String { uuidString }
154154

155-
public init(xmlStringValue: String) throws {
155+
public init(xmlStringValue: String, for node: Node) throws {
156156
guard let value = UUID(uuidString: xmlStringValue) else {
157157
throw XMLValueError.invalidFormat(expected: "UUID", found: xmlStringValue)
158158
}
159159
self = value
160160
}
161161
}
162162

163+
extension URL: XMLValueCodable {
164+
public func xmlStringValue(for node: Node) -> String { absoluteString }
165+
166+
public init(xmlStringValue: String, for node: Node) throws {
167+
guard let url = URL(string: xmlStringValue) else {
168+
throw XMLValueError.invalidFormat(expected: "URL", found: xmlStringValue)
169+
}
170+
self = url
171+
}
172+
}
173+
174+
extension Date: XMLValueCodable {
175+
public func xmlStringValue(for node: Node) -> String {
176+
node.document.dateFormat.encode(self)
177+
}
178+
179+
public init(xmlStringValue: String, for node: Node) throws {
180+
self = try node.document.dateFormat.decode(xmlStringValue)
181+
}
182+
}
183+
184+
extension Data: XMLValueCodable {
185+
public func xmlStringValue(for node: Node) -> String {
186+
node.document.dataFormat.encode(self)
187+
}
188+
189+
public init(xmlStringValue: String, for node: Node) throws {
190+
self = try node.document.dataFormat.decode(xmlStringValue)
191+
}
192+
}
193+
163194
extension RawRepresentable where RawValue: XMLValueEncodable {
164-
public var xmlStringValue: String {
165-
rawValue.xmlStringValue
195+
public func xmlStringValue(for node: Node) -> String {
196+
rawValue.xmlStringValue(for: node)
166197
}
167198
}
168199

169200
extension RawRepresentable where RawValue: XMLValueDecodable {
170-
public init(xmlStringValue string: String) throws {
171-
let raw = try RawValue(xmlStringValue: string)
201+
public init(xmlStringValue string: String, for node: Node) throws {
202+
let raw = try RawValue(xmlStringValue: string, for: node)
172203
guard let value = Self(rawValue: raw) else {
173204
throw XMLValueError.invalidFormat(expected: "\(String(describing: Self.self))", found: string)
174205
}

0 commit comments

Comments
 (0)