Search code examples
swiftcastinglibxl

How can I cast this char* into a String optional? Using xlSheetReadStrA from LibXL, checking for empty cells


Here is the signature declared in the framework header:

const char* XLAPIENTRY xlSheetReadStrA(SheetHandle handle, int row, int col, FormatHandle* format);

here is the documentation for ReadStr: http://www.libxl.com/spreadsheet.html

here is how we currently read a cell:

func readNillableString(row:Int32, col:Int32) -> String? {
    let value:String? = String(format: "%s", xlSheetReadStrA(sheet, row, col, nil) ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
    let set = NSMutableCharacterSet.alphanumeric()
    set.addCharacters(in: "., +%%()/[]:-_ ®÷")
    if self.rangeOfCharacter(from: set.inverted) != nil || self.isEmpty {
        return nil
    }
    if value?.isEmpty == true {
        return nil
    }
    return value
}

But in reality, all we're trying to do is convert a value in an excel cell into a Swift String

The main issue is empty cells, when we call xlSheetReadStrA on an empty cell, and take that char* return and cast it to a String, this is the result:

(lldb) po value
▿ Optional<String>
  - some : "hÿh\u{03}\u{01}"

It seems like we're doing a lot of unnecessary error handling, when we basically just want a way to return a swift String if there is a value, and nil if there isn't

That rangeOfCharacter call is just a way to determine if the cell is empty...


Does anybody have any LibXL experience, or can look at this parsing code and figure a better way to do this? I'm thinking potentially there is something more efficient with the original casting of the char* xlSheetReadStrA return value, that would remove the need for the following checks


Solution

  • I have never used LibXL, but your code cannot handle nil cases properly.

    Try something like this:

    func readNillableString(row:Int32, col:Int32) -> String? {
        guard let cStr = xlSheetReadStrA(sheet, row, col, nil) else {
            return nil
        }
        let value = String(cString: cStr).trimmingCharacters(in: .whitespacesAndNewlines)
        if value.isEmpty {
            return nil
        }
        return value
    }
    

    Just try and see what happens.