I’ll be helping yyjhuj in a different context.
I wanted to post a summary here, just for folks reading along at home. I won’t go into too much detail, but I think the approach described below has a number of benefits:
-
It’s super easy (-:
-
The resulting parser is relatively straightforward.
-
The final code is reasonably efficient, especially in Release builds.
-
It uses no ‘unsafe’ constructs.
-
It can be easily extended for other types.
To start, I added an extension to Data
to parse integers:
extension Data {
mutating func popFirstBigEndian<T>(_ x: T.Type) -> T? where T: FixedWidthInteger {
guard self.count >= MemoryLayout<T>.size else { return nil }
defer {
self = self.dropFirst(MemoryLayout<T>.size)
}
return self.prefix(MemoryLayout<T>.size).reduce(0, { T($0) << 8 | T($1) })
}
}
With that, parsing a file in the format described by the above-mentioned Adobe Community Forums post is pretty straightforward:
var remaining = data
guard
let version = remaining.popFirstBigEndian(UInt16.self),
let subVersion = remaining.popFirstBigEndian(UInt16.self)
else {
throw ParseError.expectedVersionAndSubVersion
}
var sections: [Section] = []
while !remaining.isEmpty {
guard
let sig32 = remaining.popFirstBigEndian(UInt32.self),
let key32 = remaining.popFirstBigEndian(UInt32.self),
let length32 = remaining.popFirstBigEndian(UInt32.self),
let length = Int(exactly: length32)
else {
throw ParseError.expectedSectionHeader
}
guard OSType(rawValue: sig32) == .photoshop else {
throw ParseError.expectedPhotoshopSignature
}
guard let contents = remaining.popFirst(length) else {
throw ParseError.expectedSectionContents
}
guard length.isMultiple(of: 2) || remaining.popFirst() != nil else {
throw ParseError.expectedPad
}
let section = Section(key: OSType(rawValue: key32), contents: contents)
sections.append(section)
}
The final sticking point encountered by yyjhuj was that some of the sections of this file include binary floating point numbers. Parsing those is relatively straightforward — see the snippet below — but you have to recognise them for what they are.
extension Data {
mutating func popFirstBigEndianFloat64() -> Float64? {
guard let u64 = self.popFirstBigEndian(UInt64.self) else { return nil }
return Double(bitPattern: u64)
}
}
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"