2024-09-15 00:40:27 +02:00
|
|
|
//
|
|
|
|
// HTMLEntityDecoder.swift
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// Created by Brent Simmons on 9/14/24.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
|
|
|
public final class HTMLEntityDecoder {
|
|
|
|
|
|
|
|
static func decodedString(withEncodedString encodedString: String) -> String {
|
|
|
|
|
|
|
|
let scanner = Scanner(string: encodedString)
|
|
|
|
scanner.charactersToBeSkipped = nil
|
|
|
|
var result = ""
|
|
|
|
var didDecodeAtLeastOneEntity = false
|
|
|
|
|
|
|
|
while true {
|
|
|
|
|
2024-09-15 23:26:01 +02:00
|
|
|
var scannedString: NSString? = nil
|
|
|
|
if scanner.scanUpTo("&", into: &scannedString) {
|
2024-09-15 00:40:27 +02:00
|
|
|
result.append(scannedString)
|
|
|
|
}
|
|
|
|
if scanner.isAtEnd {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
let savedScanLocation = scanner.scanLocation
|
|
|
|
|
|
|
|
var decodedEntity: String? = nil
|
|
|
|
if scanner.scanEntityValue(&decodedEntity) {
|
|
|
|
result.append(decodedEntity)
|
|
|
|
didDecodeAtLeastOneEntity = true
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
result.append("&")
|
|
|
|
scanner.scanLocation = savedScanLocation + 1
|
|
|
|
}
|
2024-09-15 23:26:01 +02:00
|
|
|
|
2024-09-15 00:40:27 +02:00
|
|
|
if scanner.isAtEnd {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !didDecodeAtLeastOneEntity { // No changes made?
|
|
|
|
return encodedString
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
}
|
2024-09-15 23:26:01 +02:00
|
|
|
|
|
|
|
/// Purpose-built version of NSScanner, which has deprecated the parts we want to use.
|
|
|
|
final class RSScanner {
|
|
|
|
|
|
|
|
let string: String
|
|
|
|
let count: Int
|
|
|
|
var scanLocation = 0
|
|
|
|
|
|
|
|
var isAtEnd {
|
|
|
|
scanLocation >= count - 1
|
|
|
|
}
|
|
|
|
|
|
|
|
init(string: String) {
|
|
|
|
self.string = string
|
|
|
|
self.count = string.count
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Scans up to `characterToFind` and returns the characters up to (and not including) `characterToFind`.
|
|
|
|
/// - Returns: nil when there were no characters accumulated (next character was `characterToFind` or already at end of string)
|
|
|
|
func scanUpTo(_ characterToFind: Character) -> String? {
|
|
|
|
|
|
|
|
if isAtEnd {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
while true {
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func currentCharacter() -> Character? {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private func
|
|
|
|
|
|
|
|
}
|