Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make character escaping customizable in XMLEncoder #188

Merged
merged 7 commits into from Jun 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
88 changes: 50 additions & 38 deletions .github/workflows/main.yml
Expand Up @@ -6,9 +6,9 @@ name: CI
# events but only for the master branch
on:
push:
branches: [ master ]
branches: [master]
pull_request:
branches: [ master ]
branches: [master]

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
Expand All @@ -18,70 +18,82 @@ jobs:

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2

- name: Build with Xcode 11.0
run: ./test_xcodebuild.sh Xcode_11
env:
IOS_DEVICE: 'platform=iOS Simulator,OS=13.0,name=iPhone 8'
TVOS_DEVICE: 'platform=tvOS Simulator,OS=13.0,name=Apple TV 4K'
- name: Build with Xcode 11.0
run: ./test_xcodebuild.sh Xcode_11
env:
IOS_DEVICE: "platform=iOS Simulator,OS=13.0,name=iPhone 8"
TVOS_DEVICE: "platform=tvOS Simulator,OS=13.0,name=Apple TV 4K"

xcode-11_1:
runs-on: macOS-10.15

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v2

- name: Build with Xcode 11.1
run: ./test_xcodebuild.sh Xcode_11.1
env:
IOS_DEVICE: 'platform=iOS Simulator,OS=13.1,name=iPhone 8'
TVOS_DEVICE: 'platform=tvOS Simulator,OS=13.0,name=Apple TV 4K'
- name: Build with Xcode 11.1
run: ./test_xcodebuild.sh Xcode_11.1
env:
IOS_DEVICE: "platform=iOS Simulator,OS=13.1,name=iPhone 8"
TVOS_DEVICE: "platform=tvOS Simulator,OS=13.0,name=Apple TV 4K"

xcode-11_2:
runs-on: macOS-10.15

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v2

- name: Build with Xcode 11.2
run: ./test_xcodebuild.sh Xcode_11.2
env:
IOS_DEVICE: 'platform=iOS Simulator,OS=13.2.2,name=iPhone 8'
TVOS_DEVICE: 'platform=tvOS Simulator,OS=13.2,name=Apple TV 4K'
- name: Build with Xcode 11.2
run: ./test_xcodebuild.sh Xcode_11.2
env:
IOS_DEVICE: "platform=iOS Simulator,OS=13.2.2,name=iPhone 8"
TVOS_DEVICE: "platform=tvOS Simulator,OS=13.2,name=Apple TV 4K"

xcode-11_3:
runs-on: macOS-10.15

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v2

- name: Build with Xcode 11.3
run: ./test_xcodebuild.sh Xcode_11.3
env:
IOS_DEVICE: 'platform=iOS Simulator,OS=13.3,name=iPhone 8'
TVOS_DEVICE: 'platform=tvOS Simulator,OS=13.3,name=Apple TV 4K'
- name: Build with Xcode 11.3
run: ./test_xcodebuild.sh Xcode_11.3
env:
IOS_DEVICE: "platform=iOS Simulator,OS=13.3,name=iPhone 8"
TVOS_DEVICE: "platform=tvOS Simulator,OS=13.3,name=Apple TV 4K"

xcode-11_4:
runs-on: macOS-10.15

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v2

- name: Build with Xcode 11.4
run: ./test_xcodebuild.sh Xcode_11.4
env:
IOS_DEVICE: 'platform=iOS Simulator,OS=13.4,name=iPhone 8'
TVOS_DEVICE: 'platform=tvOS Simulator,OS=13.4,name=Apple TV 4K'
CODECOV_JOB: 'true'
CODECOV_TOKEN: ${{ secrets.codecovToken }}
- name: Build with Xcode 11.4
run: ./test_xcodebuild.sh Xcode_11.4
env:
IOS_DEVICE: "platform=iOS Simulator,OS=13.4,name=iPhone 8"
TVOS_DEVICE: "platform=tvOS Simulator,OS=13.4,name=Apple TV 4K"

xcode-11_5:
runs-on: macOS-10.15

steps:
- uses: actions/checkout@v2

- name: Build with Xcode 11.5
run: ./test_xcodebuild.sh Xcode_11.5
env:
IOS_DEVICE: "platform=iOS Simulator,OS=13.5,name=iPhone 8"
TVOS_DEVICE: "platform=tvOS Simulator,OS=13.4,name=Apple TV 4K"
CODECOV_JOB: "true"
CODECOV_TOKEN: ${{ secrets.codecovToken }}

pod-lib-lint:
runs-on: macOS-10.15

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v2

- name: Run CocoaPods linter
run: ./pod.sh
- name: Run CocoaPods linter
run: ./pod.sh
1 change: 1 addition & 0 deletions .vscode/settings.json
@@ -1,4 +1,5 @@
{
"licenser.license": "MIT",
"licenser.author": "XMLCoder contributors",
"editor.formatOnSave": true,
}
70 changes: 31 additions & 39 deletions Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift
Expand Up @@ -14,15 +14,6 @@ struct Attribute: Equatable {
}

struct XMLCoderElement: Equatable {
private static let attributesKey = "___ATTRIBUTES"
private static let escapedCharacterSet = [
("&", "&"),
("<", "&lt;"),
(">", "&gt;"),
("'", "&apos;"),
("\"", "&quot;"),
]

let key: String
private(set) var stringValue: String?
private(set) var elements: [XMLCoderElement] = []
Expand Down Expand Up @@ -124,18 +115,20 @@ struct XMLCoderElement: Equatable {

func toXMLString(
with header: XMLHeader? = nil,
escapedCharacters: (elements: [(String, String)], attributes: [(String, String)]),
formatting: XMLEncoder.OutputFormatting,
indentation: XMLEncoder.PrettyPrintIndentation
) -> String {
if let header = header, let headerXML = header.toXML() {
return headerXML + _toXMLString(formatting, indentation)
return headerXML + _toXMLString(escapedCharacters, formatting, indentation)
}
return _toXMLString(formatting, indentation)
return _toXMLString(escapedCharacters, formatting, indentation)
}

private func formatUnsortedXMLElements(
_ string: inout String,
_ level: Int,
_ escapedCharacters: (elements: [(String, String)], attributes: [(String, String)]),
_ formatting: XMLEncoder.OutputFormatting,
_ indentation: XMLEncoder.PrettyPrintIndentation,
_ prettyPrinted: Bool
Expand All @@ -144,6 +137,7 @@ struct XMLCoderElement: Equatable {
from: elements,
into: &string,
at: level,
escapedCharacters: escapedCharacters,
formatting: formatting,
indentation: indentation,
prettyPrinted: prettyPrinted
Expand All @@ -155,54 +149,55 @@ struct XMLCoderElement: Equatable {
at level: Int,
formatting: XMLEncoder.OutputFormatting,
indentation: XMLEncoder.PrettyPrintIndentation,
escapedCharacters: (elements: [(String, String)], attributes: [(String, String)]),
prettyPrinted: Bool
) -> String {
if let stringValue = element.stringValue {
if element.isCDATANode {
return "<![CDATA[\(stringValue)]]>"
} else {
return stringValue.escape(XMLCoderElement.escapedCharacterSet)
return stringValue.escape(escapedCharacters.elements)
}
}

var string = ""
string += element._toXMLString(indented: level + 1, formatting, indentation)
string += element._toXMLString(indented: level + 1, escapedCharacters, formatting, indentation)
string += prettyPrinted ? "\n" : ""
return string
}

fileprivate func formatSortedXMLElements(
_ string: inout String,
_ level: Int,
_ escapedCharacters: (elements: [(String, String)], attributes: [(String, String)]),
_ formatting: XMLEncoder.OutputFormatting,
_ indentation: XMLEncoder.PrettyPrintIndentation,
_ prettyPrinted: Bool
) {
formatXMLElements(from: elements.sorted { $0.key < $1.key },
into: &string,
at: level,
escapedCharacters: escapedCharacters,
formatting: formatting,
indentation: indentation,
prettyPrinted: prettyPrinted)
}

fileprivate func attributeString(key: String, value: String) -> String {
return " \(key)=\"\(value.escape(XMLCoderElement.escapedCharacterSet))\""
}

fileprivate func formatXMLAttributes(
from attributes: [Attribute],
into string: inout String
into string: inout String,
charactersEscapedInAttributes: [(String, String)]
) {
for attribute in attributes {
string += attributeString(key: attribute.key, value: attribute.value)
string += " \(attribute.key)=\"\(attribute.value.escape(charactersEscapedInAttributes))\""
}
}

fileprivate func formatXMLElements(
from elements: [XMLCoderElement],
into string: inout String,
at level: Int,
escapedCharacters: (elements: [(String, String)], attributes: [(String, String)]),
formatting: XMLEncoder.OutputFormatting,
indentation: XMLEncoder.PrettyPrintIndentation,
prettyPrinted: Bool
Expand All @@ -212,32 +207,28 @@ struct XMLCoderElement: Equatable {
at: level,
formatting: formatting,
indentation: indentation,
escapedCharacters: escapedCharacters,
prettyPrinted: prettyPrinted && !containsTextNodes)
}
}

fileprivate func formatSortedXMLAttributes(_ string: inout String) {
formatXMLAttributes(
from: attributes.sorted(by: { $0.key < $1.key }), into: &string
)
}

fileprivate func formatUnsortedXMLAttributes(_ string: inout String) {
formatXMLAttributes(from: attributes, into: &string)
}

private func formatXMLAttributes(
_ formatting: XMLEncoder.OutputFormatting,
_ string: inout String
_ string: inout String,
_ charactersEscapedInAttributes: [(String, String)]
) {
if formatting.contains(.sortedKeys) {
formatSortedXMLAttributes(&string)
return
}
formatUnsortedXMLAttributes(&string)
let attributes = formatting.contains(.sortedKeys) ?
self.attributes.sorted(by: { $0.key < $1.key }) :
self.attributes
formatXMLAttributes(
from: attributes,
into: &string,
charactersEscapedInAttributes: charactersEscapedInAttributes
)
}

private func formatXMLElements(
_ escapedCharacters: (elements: [(String, String)], attributes: [(String, String)]),
_ formatting: XMLEncoder.OutputFormatting,
_ indentation: XMLEncoder.PrettyPrintIndentation,
_ string: inout String,
Expand All @@ -246,17 +237,18 @@ struct XMLCoderElement: Equatable {
) {
if formatting.contains(.sortedKeys) {
formatSortedXMLElements(
&string, level, formatting, indentation, prettyPrinted
&string, level, escapedCharacters, formatting, indentation, prettyPrinted
)
return
}
formatUnsortedXMLElements(
&string, level, formatting, indentation, prettyPrinted
&string, level, escapedCharacters, formatting, indentation, prettyPrinted
)
}

private func _toXMLString(
indented level: Int = 0,
_ escapedCharacters: (elements: [(String, String)], attributes: [(String, String)]),
_ formatting: XMLEncoder.OutputFormatting,
_ indentation: XMLEncoder.PrettyPrintIndentation
) -> String {
Expand All @@ -276,14 +268,14 @@ struct XMLCoderElement: Equatable {
string += "<\(key)"
}

formatXMLAttributes(formatting, &string)
formatXMLAttributes(formatting, &string, escapedCharacters.attributes)

if !elements.isEmpty {
let prettyPrintElements = prettyPrinted && !containsTextNodes
if !key.isEmpty {
string += prettyPrintElements ? ">\n" : ">"
}
formatXMLElements(formatting, indentation, &string, level, prettyPrintElements)
formatXMLElements(escapedCharacters, formatting, indentation, &string, level, prettyPrintElements)

if prettyPrintElements { string += prefix }
if !key.isEmpty {
Expand Down
22 changes: 22 additions & 0 deletions Sources/XMLCoder/Encoder/XMLEncoder.swift
Expand Up @@ -267,6 +267,24 @@ open class XMLEncoder {
}
}

/// Characters and their escaped representations to be escaped in attributes
open var charactersEscapedInAttributes = [
("&", "&amp;"),
("<", "&lt;"),
(">", "&gt;"),
("'", "&apos;"),
("\"", "&quot;"),
]

/// Characters and their escaped representations to be escaped in elements
open var charactersEscapedInElements = [
("&", "&amp;"),
("<", "&lt;"),
(">", "&gt;"),
("'", "&apos;"),
("\"", "&quot;"),
]

/// The output format to produce. Defaults to `[]`.
open var outputFormatting: OutputFormatting = []

Expand Down Expand Up @@ -384,6 +402,10 @@ open class XMLEncoder {

return element.toXMLString(
with: header,
escapedCharacters: (
attributes: charactersEscapedInAttributes,
elements: charactersEscapedInElements
),
formatting: outputFormatting,
indentation: prettyPrintIndentation
).data(using: .utf8, allowLossyConversion: true)!
Expand Down