Skip to content

Commit

Permalink
Make character escaping customizable in XMLEncoder (CoreOffice#188)
Browse files Browse the repository at this point in the history
Resolve CoreOffice#185.
Resolve CoreOffice#187.

Add new `charactersEscapedInAttributes` and `charactersEscapedInElements` properties on `XMLEncoder` that allow customizing how certain characters are escaped.
  • Loading branch information
MaxDesiatov authored and Arjun Gupta committed Jun 26, 2020
1 parent b7a0bc3 commit 4524c24
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 145 deletions.
88 changes: 50 additions & 38 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit 4524c24

Please sign in to comment.