Skip to content

Commit c438dad

Browse files
authored
Implement support for encoding DocTypes (#253)
* Implement support for encoding DocTypes
1 parent fe84f4f commit c438dad

File tree

6 files changed

+160
-4
lines changed

6 files changed

+160
-4
lines changed

Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,15 +121,23 @@ struct XMLCoderElement: Equatable {
121121
}
122122

123123
func toXMLString(
124-
with header: XMLHeader? = nil,
124+
with header: XMLHeader?,
125+
doctype: XMLDocumentType?,
125126
escapedCharacters: (elements: [(String, String)], attributes: [(String, String)]),
126127
formatting: XMLEncoder.OutputFormatting,
127128
indentation: XMLEncoder.PrettyPrintIndentation
128129
) -> String {
130+
var base = ""
131+
129132
if let header = header, let headerXML = header.toXML() {
130-
return headerXML + _toXMLString(escapedCharacters, formatting, indentation)
133+
base += headerXML
131134
}
132-
return _toXMLString(escapedCharacters, formatting, indentation)
135+
136+
if let doctype = doctype {
137+
base += doctype.toXML()
138+
}
139+
140+
return base + _toXMLString(escapedCharacters, formatting, indentation)
133141
}
134142

135143
private func formatUnsortedXMLElements(
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) 2018-2020 XMLCoder contributors
2+
//
3+
// This software is released under the MIT License.
4+
// https://opensource.org/licenses/MIT
5+
//
6+
// Created by Joannis Orlandos on 8/11/22.
7+
//
8+
9+
import Foundation
10+
11+
public struct XMLDocumentType {
12+
public enum External: String {
13+
case `public` = "PUBLIC"
14+
case system = "SYSTEM"
15+
}
16+
17+
public let rootElement: String
18+
public let external: External
19+
public let dtdName: String?
20+
public let dtdLocation: String
21+
22+
internal init(
23+
rootElement: String,
24+
external: External,
25+
dtdName: String?,
26+
dtdLocation: String
27+
) {
28+
self.rootElement = rootElement
29+
self.external = external
30+
self.dtdName = dtdName
31+
self.dtdLocation = dtdLocation
32+
}
33+
34+
public static func `public`(rootElement: String, dtdName: String, dtdLocation: String) -> XMLDocumentType {
35+
XMLDocumentType(
36+
rootElement: rootElement,
37+
external: .public,
38+
dtdName: dtdName,
39+
dtdLocation: dtdLocation
40+
)
41+
}
42+
43+
public static func system(rootElement: String, dtdLocation: String) -> XMLDocumentType {
44+
XMLDocumentType(
45+
rootElement: rootElement,
46+
external: .system,
47+
dtdName: nil,
48+
dtdLocation: dtdLocation
49+
)
50+
}
51+
52+
func toXML() -> String {
53+
var string = "<!DOCTYPE \(rootElement) \(external.rawValue)"
54+
55+
if let dtdName = dtdName {
56+
string += " \"\(dtdName)\""
57+
}
58+
59+
string += " \"\(dtdLocation)\""
60+
61+
string += ">\n"
62+
63+
return string
64+
}
65+
}

Sources/XMLCoder/Encoder/XMLEncoder.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,8 @@ open class XMLEncoder {
352352
open func encode<T: Encodable>(_ value: T,
353353
withRootKey rootKey: String? = nil,
354354
rootAttributes: [String: String]? = nil,
355-
header: XMLHeader? = nil) throws -> Data
355+
header: XMLHeader? = nil,
356+
doctype: XMLDocumentType? = nil) throws -> Data
356357
{
357358
let encoder = XMLEncoderImplementation(options: options, nodeEncodings: [])
358359
encoder.nodeEncodings.append(options.nodeEncodingStrategy.nodeEncodings(forType: T.self, with: encoder))
@@ -400,6 +401,7 @@ open class XMLEncoder {
400401

401402
return element.toXMLString(
402403
with: header,
404+
doctype: doctype,
403405
escapedCharacters: (
404406
elements: charactersEscapedInElements,
405407
attributes: charactersEscapedInAttributes

Tests/XMLCoderTests/Auxiliary/XMLElementTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ class XMLElementTests: XCTestCase {
8585
attributes: [inputNamespace])
8686

8787
let result = input.toXMLString(
88+
with: nil,
89+
doctype: nil,
8890
escapedCharacters: (elements: XMLEncoder().charactersEscapedInElements,
8991
attributes: XMLEncoder().charactersEscapedInAttributes),
9092
formatting: [],
@@ -120,6 +122,8 @@ class XMLElementTests: XCTestCase {
120122
attributes: [inputNamespace])
121123

122124
let result = input.toXMLString(
125+
with: nil,
126+
doctype: nil,
123127
escapedCharacters: (elements: XMLEncoder().charactersEscapedInElements,
124128
attributes: XMLEncoder().charactersEscapedInAttributes),
125129
formatting: [],
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) 2019-2020 XMLCoder contributors
2+
//
3+
// This software is released under the MIT License.
4+
// https://opensource.org/licenses/MIT
5+
//
6+
// Created by Max Desiatov on 02/05/2019.
7+
//
8+
9+
import XCTest
10+
@testable import XMLCoder
11+
12+
private let systemXML = """
13+
<!DOCTYPE si SYSTEM "http://example.com/myService_v1.dtd">
14+
<si><t>blah</t></si>
15+
""".data(using: .utf8)!
16+
17+
private let publicXML = """
18+
<!DOCTYPE si PUBLIC "-//Domain//DTD MyService v1//EN" "http://example.com/myService_v1.dtd">
19+
<si><t>blah</t></si>
20+
""".data(using: .utf8)!
21+
22+
private struct CapitalizedItem: Codable, Equatable {
23+
public let text: String
24+
25+
enum CodingKeys: String, CodingKey {
26+
case text = "t"
27+
}
28+
}
29+
30+
final class DocTypeTests: XCTestCase {
31+
func testPublicDocType() throws {
32+
let decoder = XMLDecoder()
33+
let decodedResult = try decoder.decode(CapitalizedItem.self, from: publicXML)
34+
XCTAssertEqual(decodedResult.text, "blah")
35+
36+
let encoder = XMLEncoder()
37+
let encoded = try encoder.encode(
38+
decodedResult,
39+
withRootKey: "si",
40+
doctype: .public(
41+
rootElement: "si",
42+
dtdName: "-//Domain//DTD MyService v1//EN",
43+
dtdLocation: "http://example.com/myService_v1.dtd"
44+
)
45+
)
46+
47+
XCTAssertEqual(encoded, publicXML)
48+
49+
let decodedResult2 = try decoder.decode(CapitalizedItem.self, from: encoded)
50+
XCTAssertEqual(decodedResult, decodedResult2)
51+
}
52+
53+
func testSystemDocType() throws {
54+
let decoder = XMLDecoder()
55+
let decodedResult = try decoder.decode(CapitalizedItem.self, from: systemXML)
56+
XCTAssertEqual(decodedResult.text, "blah")
57+
58+
let encoder = XMLEncoder()
59+
let encoded = try encoder.encode(
60+
decodedResult,
61+
withRootKey: "si",
62+
doctype: .system(
63+
rootElement: "si",
64+
dtdLocation: "http://example.com/myService_v1.dtd"
65+
)
66+
)
67+
68+
XCTAssertEqual(encoded, systemXML)
69+
70+
let decodedResult2 = try decoder.decode(CapitalizedItem.self, from: encoded)
71+
XCTAssertEqual(decodedResult, decodedResult2)
72+
}
73+
}

XMLCoder.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
/* Begin PBXBuildFile section */
2424
07E441BA2340F14B00890F46 /* EmptyElementEmptyStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E441B92340F14B00890F46 /* EmptyElementEmptyStringTests.swift */; };
25+
246B0DF8291BB1310076D5B9 /* XMLDocumentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 246B0DF7291BB1310076D5B9 /* XMLDocumentType.swift */; };
2526
4A062D4F2341924E009BCAC1 /* CombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A062D4E2341924E009BCAC1 /* CombineTests.swift */; };
2627
970FA9DC2422EFAE0023C1EC /* RootLevetExtraAttributesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970FA9DB2422EFAE0023C1EC /* RootLevetExtraAttributesTests.swift */; };
2728
B512614E248E07B500FC64FB /* ElementAndAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = B512614D248E07B500FC64FB /* ElementAndAttribute.swift */; };
@@ -172,6 +173,7 @@
172173

173174
/* Begin PBXFileReference section */
174175
07E441B92340F14B00890F46 /* EmptyElementEmptyStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyElementEmptyStringTests.swift; sourceTree = "<group>"; };
176+
246B0DF7291BB1310076D5B9 /* XMLDocumentType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLDocumentType.swift; sourceTree = "<group>"; };
175177
4A062D4E2341924E009BCAC1 /* CombineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombineTests.swift; sourceTree = "<group>"; };
176178
970FA9DB2422EFAE0023C1EC /* RootLevetExtraAttributesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootLevetExtraAttributesTests.swift; sourceTree = "<group>"; };
177179
B512614D248E07B500FC64FB /* ElementAndAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementAndAttribute.swift; sourceTree = "<group>"; };
@@ -386,6 +388,7 @@
386388
OBJ_34 /* XMLHeader.swift */,
387389
OBJ_35 /* XMLKey.swift */,
388390
OBJ_36 /* XMLStackParser.swift */,
391+
246B0DF7291BB1310076D5B9 /* XMLDocumentType.swift */,
389392
B5647C43248876FF001F6507 /* Attribute.swift */,
390393
B512614D248E07B500FC64FB /* ElementAndAttribute.swift */,
391394
B5647C4724897589001F6507 /* Element.swift */,
@@ -750,6 +753,7 @@
750753
OBJ_188 /* XMLEncodingStorage.swift in Sources */,
751754
OBJ_189 /* XMLKeyedEncodingContainer.swift in Sources */,
752755
OBJ_190 /* XMLReferencingEncoder.swift in Sources */,
756+
246B0DF8291BB1310076D5B9 /* XMLDocumentType.swift in Sources */,
753757
OBJ_191 /* XMLUnkeyedEncodingContainer.swift in Sources */,
754758
);
755759
runOnlyForDeploymentPostprocessing = 0;

0 commit comments

Comments
 (0)