Swift for Xojo Developers : Part 3 - TextInputFile

Here is a class that provides all the same syntax and error checking for reading a TextFile. This of course uses the Folderitem class previous posted.

/*
 +------------+----------------------------------------------------------------+
 |       Name | textInputFile [to be consistent with textOutputFile]           |
 +------------+----------------------------------------------------------------+
 |       Type | class                                                          |
 +------------+----------------------------------------------------------------+
 |  Properties| EOF                                                            |
 +------------+----------------------------------------------------------------+
 |     Methods| open               readALL               readLine              |
 |            | close              read                                        |
 +------------+----------------------------------------------------------------+
 */

class textInputFile {
    private var fileHandle : FileHandle?
    private var zIsOpen    : Bool = false
    private var zEOF       : Bool = false
    private let delimiter  : String = "\n"
    private let encoding   : UInt =  String.Encoding.utf8.rawValue
    private let chunkSize  : Int  = 96//4096
    private let buffer     : NSMutableData?
    private let delimData  : NSData?

    required init() {
        zIsOpen=false
        ThrowErrorMessage( .OK )
        buffer  = NSMutableData(capacity: chunkSize)
        delimData  = delimiter.data(using: String.Encoding.utf8)! as NSData
    }

    public var lastErrorCode     : Int     { get { return zLastErrorCode } }
    public var readError         : Bool    { get {return zLastErrorCode != 0 } }
    public var lastErrorMessage  : String  { get { return zLastErrorMsg } }

    public func open(_ f : folderitem ) {
        zIsOpen=false
        zEOF   = false
        if f.exists==true {
            fileHandle = FileHandle(forReadingAtPath: f.path)!
            if fileHandle != nil { zIsOpen=true }
            if zIsOpen==false {
                ThrowErrorMessage( .BADFILE)
            }
        }
    }

    public var EOF : Bool { get { return zEOF } }

    public func close() {
        if zIsOpen {
            fileHandle?.closeFile()
            zIsOpen=false
        }
    }

    public var readALL : String { get { return  NSString(data:(fileHandle?.readDataToEndOfFile())!, encoding: encoding)! as String } }

    // NOTE : this routine returns ONE extra line, as it detects EOF BEFORE the read not after :(
    public var readLine : String? { get {
        precondition(fileHandle != nil, "Attempt to read from closed file")

        if zEOF { return nil }

        // Read data chunks from file until a line delimiter is found:
        var range = buffer?.range(of: delimData! as Data, options: [], in: NSMakeRange(0, (buffer?.length)!))
        while range?.location == NSNotFound {
            let tmpData = fileHandle?.readData(ofLength: chunkSize)
            if tmpData?.count == 0 {
                // EOF or read error.
                zEOF = true
                if (buffer?.length)! > 0 {
                    // Buffer contains last line in file (not terminated by delimiter).
                    let line = NSString(data: buffer! as Data, encoding: encoding)
                    buffer?.length = 0
                    return line as String?
                }
                // No more lines.
                return ""//nil
            }
            buffer?.append(tmpData!)
            range = buffer?.range(of: delimData! as Data, options: [], in: NSMakeRange(0, (buffer?.length)!))
        }

        // Convert complete line (excluding the delimiter) to a string:
        let line = NSString(data: (buffer?.subdata(with: NSMakeRange(0, (range?.location)!)))!, encoding: encoding)
        // Remove line (and the delimiter) from the buffer:
        buffer?.replaceBytes(in: NSMakeRange(0, (range?.location)! + (range?.length)!), withBytes: nil, length: 0)
        return line as String?
        }
    }
}
1 Like

Here is an update to the previously posted enum and one shared method

enum fileERRORS : Int {
    case OK = 0
    case RENAME    = 1001
    case INDEX     = 1002
    case COPY      = 1003
    case MOVE      = 1004
    case DIRECTORY = 1005
    case DELETE    = 1006
    case TEXTOUT   = 1007
    case BADFILE   = 1008
    var description : String {
        switch self {
        case .OK        : return "OK"
        case .RENAME    : return "Unable to RENAME file."
        case .INDEX     : return "Index out of range"
        case .COPY      : return "Unable to Copy file."
        case .MOVE      : return "Unable to Move file."
        case .DIRECTORY : return "Unable to create directory."
        case .DELETE    : return "Unable to delete file."
        case .TEXTOUT   : return "No open file for write."
        case .BADFILE   : return "Unable to access file."
        }
    }
}


private func ThrowErrorMessage(_ errCode : fileERRORS,_ errorMsg:String="") {
    zLastErrorCode = errCode.rawValue
    zLastError     = (zLastErrorCode != 0)
    zLastErrorMsg  = errCode.description
    if zLastError { print("ERROR : \(zLastErrorMsg) [\(errorMsg)]") }
}

If you would like a copy of the entire module with the File Managment code, just send me a Private Message

Note : I do not yet have a BinaryStream set up yet, as so far I haven’t had a need, but if I do, I will be writing one :slight_smile:

1 Like