diff --git a/Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift b/Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift index 4cf61d91..1b1d8a0f 100644 --- a/Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift +++ b/Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift @@ -314,7 +314,10 @@ struct XMLCoderElement: Equatable, Sendable { } if !elements.isEmpty || formatting.contains(.noEmptyElements) { - let prettyPrintElements = prettyPrinted && !containsTextNodes + let hasOnlyIntrinsicContent = elements.allSatisfy { element in + element.key.isEmpty && !element.elements.contains { !$0.key.isEmpty } + } + let prettyPrintElements = prettyPrinted && !containsTextNodes && !hasOnlyIntrinsicContent if !key.isEmpty { string += prettyPrintElements ? ">\n" : ">" } diff --git a/Tests/XMLCoderTests/PrettyPrintTest.swift b/Tests/XMLCoderTests/PrettyPrintTest.swift index 89f05fa7..8ad412c9 100644 --- a/Tests/XMLCoderTests/PrettyPrintTest.swift +++ b/Tests/XMLCoderTests/PrettyPrintTest.swift @@ -14,6 +14,58 @@ private struct NestedContainer: Encodable { let values: [String] } +/// Element with an attribute and intrinsic text value (empty-string CodingKey). +private struct Measurement: Codable, Equatable { + @Attribute var ref: String? + var value: Double + + enum CodingKeys: String, CodingKey { + case ref + case value = "" + } + + init(value: Double, ref: String? = nil) { + self._ref = Attribute(ref) + self.value = value + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + _ref = try container.decodeIfPresent(Attribute.self, forKey: .ref) ?? Attribute(nil) + value = try container.decode(Double.self, forKey: .value) + } +} + +private struct Sample: Codable, Equatable { + var depth: Double + var readings: [Measurement] + + enum CodingKeys: String, CodingKey { + case depth + case readings = "measurement" + } + + init(depth: Double, readings: [Measurement]) { + self.depth = depth + self.readings = readings + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + depth = try container.decode(Double.self, forKey: .depth) + readings = try container.decodeIfPresent([Measurement].self, forKey: .readings) ?? [] + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(depth, forKey: .depth) + for reading in readings { + let enc = container.superEncoder(forKey: .readings) + try reading.encode(to: enc) + } + } +} + final class PrettyPrintTest: XCTestCase { private let testContainer = TopContainer(nested: NestedContainer(values: ["foor", "bar"])) @@ -56,6 +108,56 @@ final class PrettyPrintTest: XCTestCase { ) } + func testIntrinsicValueWithoutAttribute() throws { + let encoder = XMLEncoder() + encoder.outputFormatting = [.prettyPrinted] + encoder.prettyPrintIndentation = .spaces(4) + + let sample = Sample(depth: 6.0, readings: [ + Measurement(value: 118000.0), + ]) + let encoded = try encoder.encode(sample, withRootKey: "sample") + + XCTAssertEqual( + String(data: encoded, encoding: .utf8)!, + """ + + 6.0 + 118000.0 + + """ + ) + + let decoded = try XMLDecoder().decode(Sample.self, from: encoded) + XCTAssertEqual(decoded, sample) + } + + func testIntrinsicValueWithAttribute() throws { + let encoder = XMLEncoder() + encoder.outputFormatting = [.prettyPrinted] + encoder.prettyPrintIndentation = .spaces(4) + + let sample = Sample(depth: 30.0, readings: [ + Measurement(value: 120000.0, ref: "sensor-1"), + Measurement(value: 122000.0, ref: "sensor-2"), + ]) + let encoded = try encoder.encode(sample, withRootKey: "sample") + + XCTAssertEqual( + String(data: encoded, encoding: .utf8)!, + """ + + 30.0 + 120000.0 + 122000.0 + + """ + ) + + let decoded = try XMLDecoder().decode(Sample.self, from: encoded) + XCTAssertEqual(decoded, sample) + } + func testTabs() throws { let encoder = XMLEncoder() encoder.outputFormatting = [.prettyPrinted]