Some checks failed
rules-freshness / rules (push) Failing after 12m24s
The JSON writer escapes the characters a YAML reader mangles. PureYAML.JSON.parse and parseLocated read JSON with those characters held back and given back, so no position moves, and Convert.convert takes the format of its source.
106 lines
4.7 KiB
Swift
106 lines
4.7 KiB
Swift
import PureYAML
|
|
|
|
extension PureYAML.JSON {
|
|
/// JSON text with the characters the YAML reader cannot be given held back.
|
|
///
|
|
/// Each U+007F to U+009F, U+2028, U+2029, U+FFFE and U+FFFF that is inside a string is replaced, one scalar for one scalar, by a private-use character that is not
|
|
/// in the text, so that the reader sees a plain character and every position of the text is the position it had. The replacement is undone in the value that
|
|
/// comes out of the reader. A private-use character that the text itself holds is never used as a replacement, so what comes back is never mistaken for it.
|
|
struct Source {
|
|
let masked: String
|
|
/// What each replacement stands for.
|
|
private let originals: [Unicode.Scalar: Unicode.Scalar]
|
|
|
|
init(_ text: String) throws {
|
|
let present = Set(text.unicodeScalars.filter { Self.privateUse.contains($0.value) })
|
|
var free = Self.privateUse.lazy.compactMap(Unicode.Scalar.init).filter { !present.contains($0) }.makeIterator()
|
|
var replacement: [Unicode.Scalar: Unicode.Scalar] = [:]
|
|
var originals: [Unicode.Scalar: Unicode.Scalar] = [:]
|
|
var output = String.UnicodeScalarView()
|
|
var inString = false
|
|
var escaped = false
|
|
for scalar in text.unicodeScalars {
|
|
guard inString else {
|
|
inString = scalar == "\""
|
|
output.append(scalar)
|
|
continue
|
|
}
|
|
if escaped {
|
|
escaped = false
|
|
} else if scalar == "\\" {
|
|
escaped = true
|
|
} else if scalar == "\"" {
|
|
inString = false
|
|
} else if Self.isHeldBack(scalar) {
|
|
if replacement[scalar] == nil {
|
|
guard let next = free.next() else {
|
|
throw PureYAML.JSON.Convert.Failure.sourceInvalid("the text holds too many private-use characters to read it as JSON")
|
|
}
|
|
replacement[scalar] = next
|
|
originals[next] = scalar
|
|
}
|
|
output.append(replacement[scalar] ?? scalar)
|
|
continue
|
|
}
|
|
output.append(scalar)
|
|
}
|
|
masked = String(output)
|
|
self.originals = originals
|
|
}
|
|
|
|
private static let privateUse: ClosedRange<UInt32> = 0xE000 ... 0xF8FF
|
|
|
|
/// The characters RFC 8259 lets a string hold as they are and the YAML reader folds or refuses.
|
|
static func isHeldBack(_ scalar: Unicode.Scalar) -> Bool {
|
|
(0x7F ... 0x9F).contains(scalar.value) || [0x2028, 0x2029, 0xFFFE, 0xFFFF].contains(scalar.value)
|
|
}
|
|
|
|
func restoring(_ text: String) -> String {
|
|
guard !originals.isEmpty else { return text }
|
|
var output = String.UnicodeScalarView()
|
|
for scalar in text.unicodeScalars {
|
|
output.append(originals[scalar] ?? scalar)
|
|
}
|
|
return String(output)
|
|
}
|
|
|
|
func restoring(_ value: PureYAML.Model.Value) -> PureYAML.Model.Value {
|
|
guard !originals.isEmpty else { return value }
|
|
switch value {
|
|
case .null, .bool, .int, .double:
|
|
return value
|
|
case let .string(text):
|
|
return .string(restoring(text))
|
|
case let .sequence(items):
|
|
return .sequence(items.map(restoring))
|
|
case let .mapping(mapping):
|
|
return .mapping(restoring(mapping))
|
|
}
|
|
}
|
|
|
|
func restoring(_ located: PureYAML.Model.LocatedValue) -> PureYAML.Model.LocatedValue {
|
|
guard !originals.isEmpty else { return located }
|
|
switch located {
|
|
case let .scalar(value, range):
|
|
return .scalar(restoring(value), range)
|
|
case let .sequence(items, range):
|
|
return .sequence(items.map(restoring), range)
|
|
case let .mapping(pairs, range):
|
|
return .mapping(pairs.map { .init(keyNode: restoring($0.keyNode), keyRange: $0.keyRange, value: restoring($0.value)) }, range)
|
|
}
|
|
}
|
|
|
|
private func restoring(_ mapping: PureYAML.Model.Mapping) -> PureYAML.Model.Mapping {
|
|
.init(mapping.pairs.map { .init(keyNode: restoring($0.keyNode), value: restoring($0.value)) })
|
|
}
|
|
|
|
private func restoring(_ key: PureYAML.Model.Key) -> PureYAML.Model.Key {
|
|
switch key {
|
|
case let .string(text): .string(restoring(text))
|
|
case let .sequence(items): .sequence(items.map(restoring))
|
|
case let .mapping(mapping): .mapping(restoring(mapping))
|
|
}
|
|
}
|
|
}
|
|
}
|