More fun scripting with Swift and Xcode: Encoding conversion without tears

To decide the best way ahead in scripting the iconv tool for text encoding conversion, I thought carefully about how I had structured handling of the data – both that brought in from the original text file, and that saved as the converted text. I concluded that I was confounding that handling with its conversion to attributed text for display.

I decided to factor the code involved with reading and writing the data into the Document, to try to keep the conversion to attributed text to the ViewController. In the end I didn’t quite achieve that, but managed additionally to use a less encoding-specific conversion function which works more reliably on a wide range of different formats.

In the NSDocument code, I now have two variables to store the input file path and data, the latter stored here as Data rather than any form of string:
var thePath = ""
var theData = Data.init()

Reading the input file now simply checks that the data being handed to it isn’t empty, then loads that data into theData and sets the path:
override func read(from data: Data, ofType typeName: String) throws {
if !data.isEmpty {
theData = data
thePath = (fileURL?.path)!
}
else {
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
}

When the ViewController is made, the data are converted into an attributed string. This conversion lets macOS examine the data and perform the best conversion that it can according to the content. The catch is required because that function throws, although as this function doesn’t throw, there needs to be a catch here; I will shortly replace it with a call to display an error alert.

To get the conversion to an attributed string to work properly, I pass it an empty options array, and a NULL for the document attributes:
if let theVC = self.windowControllers[0].contentViewController as? ViewController {
theVC.theInPath = thePath
if !theData.isEmpty {
var theDict: NSDictionary? = nil
do {
let attr = try NSAttributedString(data: theData, options: [:], documentAttributes: &theDict)
if (attr.length != 0) {
theVC.textScrollContent1.textStorage?.setAttributedString(attr)
}
} catch {
_ = NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
}
}

The final modifications to NSDocument take the raw data from the iconv conversion and pass them for saving:
override func data(ofType typeName: String) throws -> Data {
if let theVC = self.windowControllers[0].contentViewController as? ViewController {
return theVC.theOutData
}
else {
return Data()
}
}

These are formatted neatly below.

rosetta19

In the ViewController, there are now variables to hold the input file path, and the data generated by iconv:
var theInPath = ""
var theOutData = Data.init()

rosetta20

When the action function has run iconv and got its return status and the converted data, it now uses the same function call as in NSDocument to convert that data into an attributed string for display. The data itself is stored in theOutData, ready to be saved when the user wishes:
if status == 0 {
if !data.isEmpty {
theOutData = data
var theDict: NSDictionary? = nil
do {
let attr = try NSAttributedString(data: data, options: [:], documentAttributes: &theDict)
if (attr.length != 0) {
textScrollContent2.textStorage?.setAttributedString(attr)
}
} catch {
_ = NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
}
} else if status == 1 {
doErrorAlertModal(message: ("iconv command cannot perform the conversion."))
} else {
doErrorAlertModal(message: ("iconv command returned error number \(status)"))
}

rosetta21

Because iconv consistently returns an error code of 1 when it cannot perform a conversion, I handle that with its own error message, rather than just report the error generically.

I next have to see if I can provide a better-formatted version of the input text, according to the setting of its popup menu. This would provide the user with feedback as to whether that setting is correct, but would of course need iconv to convert the input text to UTF-8. That might be easier if I were to use the read input data as standard input to the iconv command, rather than read it from the original file each time. I have tried feeding standard input, but have not yet got it working.

The other important task is to create a Help Book which documents all the synonyms for the supported encoding types.