Swift for Xojo Developers - Part 10 : String Extension

This libary is not a class, but an extension to the Swift String Datatype. It creates string functions with the more familiar syntax.

The extensions add features using this syntax

returnValue = StringValue.function([arg])

in addition there are function versions of these with a syntax of

returnValue = function(StringValue[,arg])

These like all the ones I have already posted, and most of what I will post in the future follow a syntax similar to Xojo API 1.0

//
//  libSTRINGS.swift
//  rdsBASIC
//
//  Created by Dave Sisemore on 04-Aug-2016
//  Updated for Swift 4.1 on 22-Jul-2018
//  Updated for Swift 5.1 on 17-Feb-2020
//  Copyright © 2018 Dave Sisemore. All rights reserved.
//

import UIKit
import Foundation

extension String {
    /// returns a string containing ONLY numeric digits from the source string
    /// - example
    /// var s="ABC123def456xyz789"
    /// var t=s.onlyDigits()
    /// t="123456789"
    public func onlyDigits() -> String {
        let filtredUnicodeScalars = unicodeScalars.filter{CharacterSet.decimalDigits.contains($0)}
        return String(String.UnicodeScalarView(filtredUnicodeScalars))
    }

    /// Returns the integer code point for the first character in the string using the character's encoding.
    /// result = stringVariable.Asc()
    public func Asc() -> Int { return AscAt(self,Index:1) }

    /// Returns the number of characters in the specified string.
    /// result=stringVariable.Len()
    public func Len() -> Int { return self.count }

    /// Returns a portion of a string. The first character is numbered 1.
    /// result=stringVariable.Mid(start [,length])
    public func Mid( _ start:Int,_ length:Int=(-1)) -> String {
        let strSize : Int = self.count
        var mStart  : Int = start
        var mLen    : Int = length
        if mStart <= 0 { return "" }
        if (mLen < 1) || (mStart+mLen>strSize) { mLen = strSize-mStart+1 }
        mStart=mStart-1 // Swift strings are 0 based
        return (self as NSString).substring( with: NSRange(location: mStart, length: mLen ) )
    }

    /// Returns the first n characters in a source string.
    /// result=stringVariable.Left(count)
    public func Left(_ count:Int)  -> String {
        if count<=0 { return "" }
        return self.Mid(1,count)
    }

    /// Returns the last n characters from the string specified.
    /// result=stringVariable.Right(count)
    public func Right(_ count:Int) -> String {
        let strSize = self.count
        var indexFirst=strSize-count+1
        if indexFirst<1 { indexFirst=1 }
        return Mid(indexFirst,count)
    }
    /// Returns the position of the first occurrence of a string inside another string. The first character is numbered 1.
    /// result = stringVariable.InStr(find,[checkcase])
    /// return is not case sensitive unless checkcase=true
    public func InStr(_ lookfor:String,_ checkcase:Bool=false) -> Int {
        var self_test:String=self
        var othr_test:String=lookfor
        if !checkcase {
            self_test=self_test.uppercased()
            othr_test=othr_test.uppercased()
        }
        let myRange: NSRange = (self_test as NSString).range(of: othr_test)
        var loc:Int=myRange.location
        if (loc<0) || (loc>self_test.count) {
            loc=0
        } else {
            loc+=1
        }
        return loc
    }

    /// same as above, but a starting index can be specified
    /// result = stringVariable.InStr([start,] find)
    /// return is not case sensitive unless checkcase=true
    public func InStr(_ start:Int,_ lookfor:String,_ checkcase:Bool=false) -> Int {
        var loc  : Int=0
        var strt : Int=start
        if (strt<=0) { strt=1 }
        if strt<self.Len() {
            let temp:String=self.Mid(strt)
            loc=temp.InStr(lookfor,checkcase)
            if loc>0 { loc=loc+strt-1 }
        }
        return loc
    }

    /// compares to another string [-1 self<other 0=self=other +1=self>other]
    /// compare is not case sensitive unless checkcase=true
    public func Compare(_ other:String,_ checkcase:Bool=false) -> Int  {
        // -1 less than   0=equal   1=greater
        var flag : Int=0
        var self_test:String=self
        var othr_test:String=other
        if !checkcase {
            self_test=self_test.uppercased()
            othr_test=othr_test.uppercased()
        }
        if self_test==othr_test { flag=0 }
        if self_test<othr_test { flag=(-1) }
        if self_test>othr_test { flag=1 }
        return flag
    }

    /// returns true if string begins with specified value
    /// compare is not case sensitive unless checkcase=true
    public func BeginsWith(_ other:String,_ checkcase:Bool=false) -> Bool {
        var self_test:String=self
        var othr_test:String=other
        if !checkcase {
            self_test=self_test.uppercased()
            othr_test=othr_test.uppercased()
        }
        return self_test.hasPrefix(othr_test)

    }
    /// returns true if string ends with specified value
    /// compare is not case sensitive unless checkcase=true
    public func EndsWith(_ other:String,_ checkcase:Bool=false) -> Bool {
        var self_test:String=self
        var othr_test:String=other
        if !checkcase {
            self_test=self_test.uppercased()
            othr_test=othr_test.uppercased()
        }
        return self_test.hasSuffix(othr_test)
    }

    /// Returns the string passed with leading and trailing whitespaces removed.
    public func Trim() -> String {
        return self.trimmingCharacters(in: .whitespacesAndNewlines)
    }

    /// Returns the string passed with leading (left side) whitespaces removed.
    public func LTrim() -> String {
        var x:Int
        let cs:CharacterSet = .whitespacesAndNewlines
        x=self.Len()
        for i in(0...x-1) {
            if !cs.contains(UnicodeScalar(characterAtIndex(i))!) {
                return (self as NSString).substring(from: i)
            }
        }
        return self
    }

    /// Returns the string data type passed with trailing (right side) whitespaces removed.
    public func RTrim() -> String {
        var x:Int
        let cs:CharacterSet = .whitespacesAndNewlines
        x=self.Len()
        for i in(0...x-1).reversed() {
            if !cs.contains(UnicodeScalar(characterAtIndex(i))!) {
                return (self as NSString).substring(to: i+1)
            }
        }
        return self
    }

    /// Returns a field from a row of data. The first field is numbered 1.
    /// result=stringVariable.NthField(fieldNumber,[separator])
    /// NOTE : arguments are in REVERSE order from Xojo
    public func NthField(_ fieldNumber:Int,_ sep_by:String=",") -> String {
        let arr:[String]=self.components(separatedBy: sep_by)
        let x=fieldNumber-1
        if x<0 || fieldNumber>arr.count { return "" }
        return arr[x]
    }

    /// Returns the number of values (fields) in the string passed that are separated by the separator string passed.
    /// result = stringVariable.CountFields([separator])
    public func CountFields(_ sep_by:String=",") -> Int {
        let arr:[String]=self.components(separatedBy: sep_by)
        return arr.count
    }

    /// Creates a one-dimensional array from the String passed.
    /// array=source.Split([delimiter])
    public func Split(_ sep_by:String=",") -> [String] {
        let arr:[String]=self.components(separatedBy: sep_by)
        return arr
    }

    public func Lowercase() -> String { return self.lowercased()  }
    public func Uppercase() -> String { return self.uppercased() }
    public func Titlecase() -> String { return self.capitalized }

    /// Returns the Double equivalent of the passed string.
    /// result=stringVariable.CDbl()
    /// supports Hex, Oct and Binary values (&H, &O, &B)
    public func CDbl() -> Double {
        var retDbl:Double=0
        var retStr:String = ""
        var mTest :String = ""
        if self.Len()>2 {
            retStr = self.Mid(3)
            mTest  = self.Left(2)
        }
        switch mTest {
        case "&H" : retDbl=Double(Hex2Int(retStr))
        case "&O" : retDbl=Double(Oct2Int(retStr))
        case "&B" : retDbl=Double(Bin2Int(retStr))
        default   : retDbl=(self as NSString).doubleValue
        }
        return retDbl
    }

    /// Returns the numeric form of a string as an Int64.
    /// result=stringVariable.CLong
    public func CLong() -> Int64 { return Int64(CDbl()) }

    /// Alias for CDBL()
    /// result=stringVariable.Val()
    public func Val() -> Double { return CDbl() }

    /// Returns the Integer equivalent of the passed string.
    /// result=stringVariable.intVal()
    /// supports Hex, Oct and Binary values (&H, &O, &B)
    /// this is required due to Swift strict typecasting
    public func intVal() -> Int { return Int(Val()) }

    /// Returns true if string is empty ("")
    /// result=stringVariable.Empty()
    public func Empty() -> Bool { return self.isEmpty }

    /// Replaces the first occurrence of a string with another string.
    /// result=stringVariable.Replace(oldString, newString)
        /// compare is not case sensitive unless checkcase=true
    public func Replace(_ oldStr:String,_ newStr:String,_ checkcase:Bool=false) -> String {
        var self_test:String=self
        var othr_test:String=oldStr
        if !checkcase {
            self_test=self_test.uppercased()
            othr_test=othr_test.uppercased()
        }
        var temp:String=""
        let x:Int=self_test.InStr(othr_test)
        let y:Int=x+oldStr.Len()
        if x>0 {
            if x>1 { temp=self.Left(x-1) }
            temp=temp+newStr+self.Mid(y)
        } else {
            temp=self
        }
        return temp
    }

    /// Replaces all occurrences of a string with another string.
    /// at this time seems checkcase is only a placeholder
    public func ReplaceAll(_ oldStr:String,_ newStr:String,_ checkcase:Bool=false) -> String {
        return self.replacingOccurrences(of: oldStr, with: newStr)//, options: nil, range: nil)
    }

    /// Indicates whether the value passed is numeric.
/// result=stringVariable.isNumeric()
    public func isNumeric() -> Bool {
        let length = self.count
        let scanner = Scanner(string: self)
        scanner.charactersToBeSkipped = nil
        var value: Double = 0.0
        if scanner.scanDouble(&value) && (scanner.scanLocation == length) {
            return true
        }
        return false
    }

    /******  P R I V A T E   S U P P O R T   F U N C T I O N S ******/
    private func AscAt(_ s:String,Index:Int) -> Int {
        var x:UInt32=0
        var i:Int=0
        for c in s.unicodeScalars {
            x=UnicodeScalar(c).value
            i+=1
            if (i==Index) { break }
        }
        return Int(x)
    }

    private func Base2Int(_ s:String,_ base:Int) -> Int {
        var retInt:Int=0
        // var i:Int
        var x:Int
        var valid:String=""
        var c:String=""
        let mStr:String  = s.uppercased()
        let strSize :Int = mStr.count
        switch base {
        case 2  : valid="01"
        case 8  : valid="01234567"
        case 16 : valid="0123456789ABCDEF"
        default : return 0
        }

        for i in 1 ... strSize {
            c=mStr.Mid(i,1)
            x=valid.InStr(c)
            if (x==0) {
                retInt=0
                break
            }
            retInt=(retInt * base)+(x-1)
        }
        return retInt
    }

    private func Hex2Int(_ s:String) -> Int { return Base2Int(s,16) }
    private func Oct2Int(_ s:String) -> Int { return Base2Int(s,8) }
    private func Bin2Int(_ s:String) -> Int { return Base2Int(s,2) }
    private func characterAtIndex(_ index: Int) -> unichar { return (self as NSString).character(at: index)
    }
} /* END - String extension */

// --------- End of Framework ---------


/*** FUNCTION Version of all String Properties ***/
/*** these mirror the extension, but are result=function(sourceString,[parameters]) ***/

public func Asc(            _ s:String)                                                  -> Int    { return s.Asc() }
public func BeginsWith(     _ s:String, other:String,_ checkcase:Bool=false)             -> Bool   { return s.BeginsWith(other,checkcase) }
public func Compare(        _ s:String, other:String,  checkcase:Bool=false)             -> Int    { return s.Compare(other,checkcase) }
public func Empty(          _ s:String)                                                  -> Bool   { return s.Empty() }
public func EndsWith(       _ s:String, other:String,_ checkcase:Bool=false)             -> Bool   { return s.EndsWith(other,checkcase) }
public func CDbl(           _ s:String)                                                  -> Double { return s.CDbl() }
public func CLong(          _ s:String)                                                  -> Int64  { return s.CLong() }
public func Mid(            _ s:String,_ start:Int,_ length:Int=(-1))                    -> String { return s.Mid(start,length) }
public func Left(           _ s:String,_ indx:Int)                                       -> String { return s.Left(indx) }
public func Right(          _ s:String,_ indx:Int)                                       -> String { return s.Right(indx) }
public func InStr(          _ s:String,_ find:String,_ checkcase:Bool=false)             -> Int    { return s.InStr(find,checkcase) }
public func InStr(_ start:Int,s:String,_ find:String,_ checkcase:Bool=false)             -> Int    { return s.InStr(start,find,checkcase) }
public func isNumeric(      _ s:String)                                                  -> Bool   { return s.isNumeric() }
public func Len(            _ s:String)                                                  -> Int    { return s.Len() }
public func Trim(           _ s:String)                                                  -> String { return s.Trim() }
public func LTrim(          _ s:String)                                                  -> String { return s.LTrim() }
public func RTrim(          _ s:String)                                                  -> String { return s.RTrim() }
public func NthField(       _ s:String,_ fieldNumber:Int,_ sep_by:String=",")            -> String { return s.NthField(fieldNumber,sep_by) }
public func CountFields(    _ s:String,_ sep_by:String=",")                              -> Int    { return s.CountFields(sep_by) }
public func Uppercase(      _ s:String)                                                  -> String { return s.Uppercase() }
public func Lowercase(      _ s:String)                                                  -> String { return s.Lowercase() }
public func Titlecase(      _ s:String)                                                  -> String { return s.Titlecase() }
public func Val(            _ s:String)                                                  -> Double { return s.Val() }
public func intVal(         _ s:String)                                                  -> Int      { return s.intVal() }
public func Split(          _ s:String,_ sep_by:String=",")                              -> [String] { return s.Split(sep_by) }
public func Replace(        _ s:String,_ oldStr:String,_ newStr:String,_ checkcase:Bool=false) -> String   { return s.Replace(oldStr,newStr,checkcase) }
public func ReplaceAll(     _ s:String,_ oldStr:String,_ newStr:String,_ checkcase:Bool=false) -> String   { return s.ReplaceAll(oldStr,newStr,checkcase) }

/* ** Other String Related Functions ** */

public func Chr(_ v:Int)     -> String { return String(describing: UnicodeScalar(v)) }
public func Chr(_ v:CGFloat) -> String { return Chr(Int(v)) }

public func Join(_ s:[String],_ sep_by:String=",")                   -> String   { return s.joined(separator: sep_by) }

public func Str(_ x:Any,dp:Int=12) -> String {
    var retStr:String=""
    switch x {
    case let mBool as Bool:
        if mBool {
            retStr = "true"
        } else {
            retStr = "false"
        }
    case let mInt as Int    : retStr = NSString(format:"%d",mInt) as String
    case let mDbl as Double : retStr = NSString(format:"%.\(dp)f" as NSString,mDbl) as String
    case let mStr as String : retStr = mStr
    default                 : retStr=""
    }
    return retStr
}

public func numericLen(_ s:String) -> Int {
    let scanner = Scanner(string: s)
    scanner.charactersToBeSkipped = nil
    var value: Double = 0.0
    if scanner.scanDouble(&value) && (scanner.scanLocation > 0) {
        return scanner.scanLocation
    }
    return 0
}
1 Like