Skip to content

Commit b2b5d72

Browse files
authored
fix: Pretty print elements with intrinsic values on a single line (#296)
Elements using the empty-string CodingKey ("") for intrinsic text content were being pretty-printed with the value on a separate line: <element ref="id"> 120000.0 </element> This happened because the pretty printer only checked containsTextNodes, which isn't set for intrinsic values encoded via keyed containers. Add a check for elements whose children are all inline (key is empty) and don't wrap named child elements, treating them the same as text nodes.
1 parent 5e1ada8 commit b2b5d72

2 files changed

Lines changed: 106 additions & 1 deletion

File tree

Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,10 @@ struct XMLCoderElement: Equatable, Sendable {
314314
}
315315

316316
if !elements.isEmpty || formatting.contains(.noEmptyElements) {
317-
let prettyPrintElements = prettyPrinted && !containsTextNodes
317+
let hasOnlyIntrinsicContent = elements.allSatisfy { element in
318+
element.key.isEmpty && !element.elements.contains { !$0.key.isEmpty }
319+
}
320+
let prettyPrintElements = prettyPrinted && !containsTextNodes && !hasOnlyIntrinsicContent
318321
if !key.isEmpty {
319322
string += prettyPrintElements ? ">\n" : ">"
320323
}

Tests/XMLCoderTests/PrettyPrintTest.swift

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,58 @@ private struct NestedContainer: Encodable {
1414
let values: [String]
1515
}
1616

17+
/// Element with an attribute and intrinsic text value (empty-string CodingKey).
18+
private struct Measurement: Codable, Equatable {
19+
@Attribute var ref: String?
20+
var value: Double
21+
22+
enum CodingKeys: String, CodingKey {
23+
case ref
24+
case value = ""
25+
}
26+
27+
init(value: Double, ref: String? = nil) {
28+
self._ref = Attribute(ref)
29+
self.value = value
30+
}
31+
32+
init(from decoder: Decoder) throws {
33+
let container = try decoder.container(keyedBy: CodingKeys.self)
34+
_ref = try container.decodeIfPresent(Attribute<String?>.self, forKey: .ref) ?? Attribute(nil)
35+
value = try container.decode(Double.self, forKey: .value)
36+
}
37+
}
38+
39+
private struct Sample: Codable, Equatable {
40+
var depth: Double
41+
var readings: [Measurement]
42+
43+
enum CodingKeys: String, CodingKey {
44+
case depth
45+
case readings = "measurement"
46+
}
47+
48+
init(depth: Double, readings: [Measurement]) {
49+
self.depth = depth
50+
self.readings = readings
51+
}
52+
53+
init(from decoder: Decoder) throws {
54+
let container = try decoder.container(keyedBy: CodingKeys.self)
55+
depth = try container.decode(Double.self, forKey: .depth)
56+
readings = try container.decodeIfPresent([Measurement].self, forKey: .readings) ?? []
57+
}
58+
59+
func encode(to encoder: Encoder) throws {
60+
var container = encoder.container(keyedBy: CodingKeys.self)
61+
try container.encode(depth, forKey: .depth)
62+
for reading in readings {
63+
let enc = container.superEncoder(forKey: .readings)
64+
try reading.encode(to: enc)
65+
}
66+
}
67+
}
68+
1769
final class PrettyPrintTest: XCTestCase {
1870
private let testContainer = TopContainer(nested: NestedContainer(values: ["foor", "bar"]))
1971

@@ -56,6 +108,56 @@ final class PrettyPrintTest: XCTestCase {
56108
)
57109
}
58110

111+
func testIntrinsicValueWithoutAttribute() throws {
112+
let encoder = XMLEncoder()
113+
encoder.outputFormatting = [.prettyPrinted]
114+
encoder.prettyPrintIndentation = .spaces(4)
115+
116+
let sample = Sample(depth: 6.0, readings: [
117+
Measurement(value: 118000.0),
118+
])
119+
let encoded = try encoder.encode(sample, withRootKey: "sample")
120+
121+
XCTAssertEqual(
122+
String(data: encoded, encoding: .utf8)!,
123+
"""
124+
<sample>
125+
<depth>6.0</depth>
126+
<measurement>118000.0</measurement>
127+
</sample>
128+
"""
129+
)
130+
131+
let decoded = try XMLDecoder().decode(Sample.self, from: encoded)
132+
XCTAssertEqual(decoded, sample)
133+
}
134+
135+
func testIntrinsicValueWithAttribute() throws {
136+
let encoder = XMLEncoder()
137+
encoder.outputFormatting = [.prettyPrinted]
138+
encoder.prettyPrintIndentation = .spaces(4)
139+
140+
let sample = Sample(depth: 30.0, readings: [
141+
Measurement(value: 120000.0, ref: "sensor-1"),
142+
Measurement(value: 122000.0, ref: "sensor-2"),
143+
])
144+
let encoded = try encoder.encode(sample, withRootKey: "sample")
145+
146+
XCTAssertEqual(
147+
String(data: encoded, encoding: .utf8)!,
148+
"""
149+
<sample>
150+
<depth>30.0</depth>
151+
<measurement ref="sensor-1">120000.0</measurement>
152+
<measurement ref="sensor-2">122000.0</measurement>
153+
</sample>
154+
"""
155+
)
156+
157+
let decoded = try XMLDecoder().decode(Sample.self, from: encoded)
158+
XCTAssertEqual(decoded, sample)
159+
}
160+
59161
func testTabs() throws {
60162
let encoder = XMLEncoder()
61163
encoder.outputFormatting = [.prettyPrinted]

0 commit comments

Comments
 (0)