[Runtime tests] Added failing TransformTests.prg() tests with ints

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//
USING System
USING System.Collections.Generic
USING System.Linq
USING System.Text
using XUnit
using System.Globalization

BEGIN NAMESPACE XSharp.VO.Tests

	class TransFormTests

		[Fact, Trait("Category", "TransForm")];
		method LogicalTest() as void
			// with @R other chars are appended, but only a single YN or TF flag is written. Other positions become a space
			Assert.Equal("Y", TransForm(true, "Y"))
			Assert.Equal("N", TransForm(false, "Y"))
			Assert.Equal("T", TransForm(true, "L"))
			Assert.Equal("F", TransForm(false, "L"))
			Assert.Equal("T x", TransForm(true, "@R L x"))
			Assert.Equal("F x", TransForm(false, "@R L x"))
			Assert.Equal("Y x", TransForm(true, "@R YYx"))
			Assert.Equal("N x", TransForm(FALSE, "@R YYx"))
			SetNatDLL("French.DLL")
			Assert.Equal("O", TransForm(true, "Y"))
			Assert.Equal("N", TransForm(false, "Y"))
			Assert.Equal("V", TransForm(true, "L"))
			Assert.Equal("F", TransForm(false, "L"))
			Assert.Equal("V x", TransForm(true, "@R L x"))
			Assert.Equal("F x", TransForm(false, "@R L x"))
			Assert.Equal("O x", TransForm(true, "@R YYx"))
			Assert.Equal("N x", TransForm(false, "@R YYx"))

		[Fact, Trait("Category", "TransForm")];
		method DateTest() as void
			// with @R other chars are appended, but only a single YN or TF flag is written. Other positions become a space
			Assert.Equal("10/12/2010", TransForm(2010.12.10,"@E"))
			Assert.Equal("20/11/2010", TransForm(2010.11.20,"@E"))
			SetDateFormat("dd-mm-yyyy")
			Assert.Equal("20-11-2010", TransForm(2010.11.20,"@D"))
			SetDateFormat("dd-mm-yy")
			Assert.Equal("20-11-10", TransForm(2010.11.20,"@D"))

		[Fact, Trait("Category", "TransForm")];
		method StringTest() as void
			Assert.Equal("ABC", Transform("abc", "@!"))
			Assert.Equal("AbC", Transform("abc", "!a!"))
			Assert.Equal("1234 AB", Transform("1234ab", "@R! 9999 AA"))
			Assert.Equal("1234 AB", Transform("1234ab", "@R 9999 !!"))

		[Fact, Trait("Category", "TransForm")];
		METHOD LongTest() AS VOID
			SetDecimalSep('.')
			SetThousandSep(',')
			Assert.Equal("  1", Transform(1, "999"))
			Assert.Equal("123", Transform(123, "###"))
			Assert.Equal("**", Transform(123, "##"))
			Assert.Equal("**", Transform(123, "99"))
			Assert.Equal("(1)", Transform(-1, "@( 99"))
			Assert.Equal("(-1)", Transform(-1, "@( 999"))
			Assert.Equal("( -1)", Transform(-1, "@( 9999"))
			Assert.Equal("(-1)", Transform(-1, "@) 9999"))
			Assert.Equal(" (-1)", Transform(-1, "@) 99999"))
			Assert.Equal("   ", Transform(0, "@Z 999"))
			Assert.Equal(" -1 DB", Transform(-1, "@X 999"))
			Assert.Equal(" -1 DB", Transform(-1, "@XC 999"))
			Assert.Equal("  1 CR", Transform(1, "@XC 999"))
			Assert.Equal("  1 CR", Transform(1, "@C 999"))
			Assert.Equal("  0", Transform(0, "@C 999"))
			Assert.Equal("1,234", Transform(1234, "9,999"))
			SetDecimalSep(',')
			SetThousandSep('.')
			Assert.Equal("1.234", Transform(1234, "9,999"))

		[Fact, Trait("Category", "TransForm")];
		method FLoatTest() as void
			SetDecimalSep('.')
			SetThousandSep(',')
			Assert.Equal("1.23", Transform(1.23, "9.99"))
			Assert.Equal("1,23", Transform(1.23, "@E 9.99"))
			Assert.Equal("*.**", Transform(12.34, "9.99"))

// some new failing tests:
	[Fact, Trait("Category", "TransForm")];
	METHOD IntLiteralTest() AS VOID
		LOCAL dec AS dword
		LOCAL thou AS DWORD

		dec := SetDecimalSep(Asc(","))
		thou := SetThousandSep(Asc("."))
		Assert.Equal(" 12.345,00" , Transform(12345.0, "999,999.99"))
		// this fails:
		Assert.Equal(" 12.345,00" , Transform(12345, "999,999.99"))

		SetDecimalSep(Asc("."))
		SetThousandSep(Asc(","))
		Assert.Equal(" 12,345.00" , Transform(12345.0, "999,999.99"))
		Assert.Equal(" 12,345.00" , Transform(12345, "999,999.99"))

		SetDecimalSep(dec)
		SetThousandSep(thou)
	RETURN

// this should go in a string.prg in VO.Tests
	[Fact, Trait("Category", "StringEq")];
	METHOD StringEquality() AS VOID
		LOCAL c1,c2 AS STRING
		LOCAL u1,u2 AS USUAL
		local exact as LOGIC
		u1 := c1 := "TESTLONG"
		u2 := c2 := "TEST"
		exact := SetExact( FALSE )

		Assert.Equal(true, c1 = c2)
		Assert.Equal(true, c1 != c2)

		Assert.Equal(true, u1 = u2)
		Assert.Equal(false, u1 != u2)

		SetExact( exact )

	RETURN

	end class
end NAMESPACE

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//
USING System
USING System.Collections.Generic
USING System.Linq
USING System.Text
using XUnit
using System.Globalization

BEGIN NAMESPACE XSharp.VO.Tests

	class TransFormTests

		[Fact, Trait("Category", "TransForm")];
		method LogicalTest() as void
			// with @R other chars are appended, but only a single YN or TF flag is written. Other positions become a space
			Assert.Equal("Y", TransForm(true, "Y"))
			Assert.Equal("N", TransForm(false, "Y"))
			Assert.Equal("T", TransForm(true, "L"))
			Assert.Equal("F", TransForm(false, "L"))
			Assert.Equal("T x", TransForm(true, "@R L x"))
			Assert.Equal("F x", TransForm(false, "@R L x"))
			Assert.Equal("Y x", TransForm(true, "@R YYx"))
			Assert.Equal("N x", TransForm(FALSE, "@R YYx"))
			SetNatDLL("French.DLL")
			Assert.Equal("O", TransForm(true, "Y"))
			Assert.Equal("N", TransForm(false, "Y"))
			Assert.Equal("V", TransForm(true, "L"))
			Assert.Equal("F", TransForm(false, "L"))
			Assert.Equal("V x", TransForm(true, "@R L x"))
			Assert.Equal("F x", TransForm(false, "@R L x"))
			Assert.Equal("O x", TransForm(true, "@R YYx"))
			Assert.Equal("N x", TransForm(false, "@R YYx"))

		[Fact, Trait("Category", "TransForm")];
		method DateTest() as void
			// with @R other chars are appended, but only a single YN or TF flag is written. Other positions become a space
			Assert.Equal("10/12/2010", TransForm(2010.12.10,"@E"))
			Assert.Equal("20/11/2010", TransForm(2010.11.20,"@E"))
			SetDateFormat("dd-mm-yyyy")
			Assert.Equal("20-11-2010", TransForm(2010.11.20,"@D"))
			SetDateFormat("dd-mm-yy")
			Assert.Equal("20-11-10", TransForm(2010.11.20,"@D"))

		[Fact, Trait("Category", "TransForm")];
		method StringTest() as void
			Assert.Equal("ABC", Transform("abc", "@!"))
			Assert.Equal("AbC", Transform("abc", "!a!"))
			Assert.Equal("1234 AB", Transform("1234ab", "@R! 9999 AA"))
			Assert.Equal("1234 AB", Transform("1234ab", "@R 9999 !!"))

		[Fact, Trait("Category", "TransForm")];
		METHOD LongTest() AS VOID
			SetDecimalSep('.')
			SetThousandSep(',')
			Assert.Equal("  1", Transform(1, "999"))
			Assert.Equal("123", Transform(123, "###"))
			Assert.Equal("**", Transform(123, "##"))
			Assert.Equal("**", Transform(123, "99"))
			Assert.Equal("(1)", Transform(-1, "@( 99"))
			Assert.Equal("(-1)", Transform(-1, "@( 999"))
			Assert.Equal("( -1)", Transform(-1, "@( 9999"))
			Assert.Equal("(-1)", Transform(-1, "@) 9999"))
			Assert.Equal(" (-1)", Transform(-1, "@) 99999"))
			Assert.Equal("   ", Transform(0, "@Z 999"))
			Assert.Equal(" -1 DB", Transform(-1, "@X 999"))
			Assert.Equal(" -1 DB", Transform(-1, "@XC 999"))
			Assert.Equal("  1 CR", Transform(1, "@XC 999"))
			Assert.Equal("  1 CR", Transform(1, "@C 999"))
			Assert.Equal("  0", Transform(0, "@C 999"))
			Assert.Equal("1,234", Transform(1234, "9,999"))
			SetDecimalSep(',')
			SetThousandSep('.')
			Assert.Equal("1.234", Transform(1234, "9,999"))

		[Fact, Trait("Category", "TransForm")];
		method FLoatTest() as void
			SetDecimalSep('.')
			SetThousandSep(',')
			Assert.Equal("1.23", Transform(1.23, "9.99"))
			Assert.Equal("1,23", Transform(1.23, "@E 9.99"))
			Assert.Equal("*.**", Transform(12.34, "9.99"))

	end class
end NAMESPACE


Replace for loop with Array.Copy which is faster

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//
USING XSharp
USING System.Text
// Array of chars used for the various trim functions
INTERNAL GLOBAL trimChars := <CHAR>{ ' ' } AS CHAR[]

FUNCTION AllTrim(c AS STRING) AS STRING
	IF ( c == NULL )
		RETURN c
	ENDIF
	RETURN c:Trim(trimChars)

///
/// Convert a character to its ASCII value using default Windows codepage
///
/// <param name="c"></param>
/// <returns>
/// </returns>

FUNCTION Asc(c AS STRING) AS DWORD
	LOCAL ascValue := 0 AS DWORD
	LOCAL chValue AS CHAR
	IF ( !String.IsNullOrEmpty(c) )
		chValue := c[0]
		ascValue := (DWORD) chValue
		IF ascValue > 127
			LOCAL encoding AS Encoding
			encoding := StringHelpers.WinEncoding
			LOCAL buffer AS BYTE[]
			VAR chars := <CHAR> {chValue}
			IF encoding:IsSingleByte
				buffer := BYTE[]{1}
				encoding:GetBytes(chars,0,1,buffer,0)
				ascValue := buffer[0]
			ELSE
				buffer := BYTE[]{2}
				IF encoding:GetBytes(chars,0,1,buffer,0) == 1
					ascValue := buffer[0]
				ELSE
					IF BitConverter.IsLittleEndian
						LOCAL tmp := buffer[0] AS BYTE
						buffer[0] := buffer[1]
						buffer[1] := tmp
					ENDIF
					ascValue := BitConverter.ToUInt16( buffer, 0 )
				ENDIF
			ENDIF
		ENDIF
	ENDIF
	RETURN ascValue

///
/// Convert a character to its Unicode ASCII value.
///
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION AscW(c AS STRING) AS DWORD
	LOCAL ascValue := 0 AS DWORD
	LOCAL chValue AS CHAR
	IF ( !String.IsNullOrEmpty(c) )
		chValue := c[0]
		ascValue := (DWORD) chValue
	ENDIF
	RETURN ascValue

///
/// Return the position of the first occurrence of a substring within a string.
///
/// <param name="cSearch">The string to search for.</param>
/// <param name="c">The string to search in.</param>
/// <returns>
/// The position of the first occurrence of cSearch within cTarget.  If cSearch is not found, At() returns 0.
/// If cSearch is empty or c is empty, At() returns 0.
/// </returns>
FUNCTION At(cSearch AS STRING,c AS STRING) AS DWORD
	LOCAL position := 0 AS DWORD
	IF ( c != NULL .AND. cSearch != NULL )
		IF c:Length != 0 .AND. cSearch:Length != 0
			position := (DWORD) c:IndexOf(cSearch, StringComparison.Ordinal) +1
		END IF
	ENDIF
	RETURN position

///
/// Return the position of the first occurrence of a substring within a string.
///
/// <param name="cSearch">The string to search for.</param>
/// <param name="c">The string to search in.</param>
/// <returns>
/// The position of the first occurrence of cSearch within cTarget.  If cSearch is not found, At() returns 0.
/// If cSearch is empty or c is empty, At() returns 0.
/// </returns>
FUNCTION At2(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN At(cSearch,c)

///
/// Return the position of the first occurrence of a substring within a string, starting at a specified position.
///
/// <param name="cSearch">The string to search for.</param>
/// <param name="c">The string to search in.</param>
/// <param name="dwOff">The position to begin the search with. This offset is ZERO based</param>
/// <returns>
/// The position of the first occurrence of cSearch within cTarget behind the give position.  If cSearch is not found, At() returns 0.
/// If cSearch is empty or c is empty, At3() returns 0.
/// </returns>
FUNCTION At3(cSearch AS STRING,c AS STRING,dwOff AS DWORD) AS DWORD
	LOCAL position := 0 AS DWORD
	// note dwOffset is ZERO based in VO
	IF ( c != NULL .AND. cSearch != NULL .AND. dwOff <= c:Length )
		IF c:Length != 0 .AND. cSearch:Length != 0
			position := (DWORD) c:IndexOf(cSearch,(INT)dwOff) +1
		ENDIF
	ENDIF
	RETURN position

///
/// Return the position of the first occurrence of a substring within a string, without regard for case.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION AtC(cSearch AS STRING,c AS STRING) AS DWORD
	LOCAL position := 0 AS DWORD
	IF ( c != NULL .AND. cSearch != NULL )
		IF c:Length != 0 .AND. cSearch:Length != 0
			position := (DWORD) c:IndexOf(cSearch, StringComparison.OrdinalIgnoreCase) +1
		ENDIF
	ENDIF
	RETURN position

///
/// Return the position of the first occurrence of a substring within a string, without regard for case.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION AtC2(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN AtC(cSearch,c)

///
/// Return the line number of the first occurrence of a substring within a multiple line string, without regard for case.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION ATCLine(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN AtLine( cSearch:ToUpper(), c:ToUpper() )

///
/// Return the line number of the first occurrence of a substring within a multiple line string, without regard for case.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION ATCLine2(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN AtLine2( cSearch:ToUpper(), c:ToUpper() )

///
/// Return the line number of the first occurrence of a substring within a multiple line string.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION ATLine(cSearch AS STRING,c AS STRING) AS DWORD
	LOCAL nPos AS DWORD
	IF String.IsNullOrEmpty(c) .OR. String.IsNullOrEmpty(cSearch)
		RETURN 0
	ENDIF
	IF c:StartsWith(cSearch)
		RETURN 1
	ENDIF
	nPos    := At( cSearch, c )
	IF (nPos > 0)
		c := Left( c, nPos - 1 )
		nPos := MemLines( c)
		IF c:EndsWith(e"\r\n")
			nPos++
		ENDIF
	ENDIF
	RETURN nPos

///
/// Return the line number of the first occurrence of a substring within a multiple line string.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION ATLine2(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN AtLine(cSearch, c)

/// This function is not implemented yet
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION B64EncFile(c AS STRING) AS STRING
	THROW NotImplementedException{}
	RETURN String.Empty

/// This function is not implemented yet
/// <param name="cIn"></param>
/// <returns>
/// </returns>
FUNCTION B64EncString(cIn AS STRING) AS STRING
	THROW NotImplementedException{}
	RETURN String.Empty

///
/// Return an uninitialized string of a specified size.
///
/// <param name="dwSize"></param>
/// <returns>
/// </returns>
FUNCTION Buffer(dwSize AS DWORD) AS STRING
	RETURN STRING{'\0', (INT) dwSize}

///
/// Return the even-numbered characters in a string.
///
/// <param name="c">The string from which the even characters shall be extracted.</param>
/// <returns>
/// A string which is assembled from the even characters in c.
/// </returns>
FUNCTION CharEven(c AS STRING) AS STRING
	LOCAL evenChars:=NULL AS STRING
	IF ( !string.IsNullOrEmpty(c) )
		//local chars  := c:ToCharArray() as char[]
		LOCAL isEven := FALSE AS  LOGIC
		LOCAL sb     := System.Text.StringBuilder{} AS System.Text.StringBuilder

		FOREACH ch AS CHAR IN c//hars
			IF isEven
				sb:Append(ch)
			ENDIF
			isEven := !isEven
		NEXT
		evenChars := sb:ToString()
	ENDIF
	RETURN evenChars

///
/// Return a string whose odd-numbered characters and even-numbered characters are from 2 different strings.
///
/// <param name="c1"></param>
/// <param name="c2"></param>
/// <returns>
/// </returns>
FUNCTION CharMix(cOdd AS STRING,cEven AS STRING) AS STRING
	LOCAL n1 := 0 AS INT
	LOCAL n2 := 0 AS INT
	LOCAL i1 := 0 AS INT
	LOCAL i2 := 0  AS INT
	LOCAL sb AS StringBuilder

	IF cEven:Length == 0
		RETURN ""
	ELSE
		sb := StringBuilder{ cOdd:Length * 2 }
		n1 := cOdd:Length - 1
		n2 := cEven:Length - 1

		FOR i1 := 0 UPTO n1
			sb:Append( cOdd[i1] )
			IF i2 > n2
				i2 := 0
			ENDIF
			sb:Append( cEven[i2++] )
		NEXT

		RETURN sb:ToString()
	ENDIF

///
/// Return the odd-numbered characters in a string.
///
/// <param name="c">The string from which the odd characters shall be extracted.</param>
/// <returns>
/// A string which is assembled from the odd characters in c.
/// </returns>
FUNCTION CharOdd(c AS STRING) AS STRING
	LOCAL oddChars:=NULL AS STRING
	IF ( !string.IsNullOrEmpty(c) )
		//local chars  := c:ToCharArray() as char[]
		LOCAL isOdd  := TRUE AS  LOGIC
		LOCAL sb     := System.Text.StringBuilder{} AS System.Text.StringBuilder

		FOREACH ch AS CHAR IN c//chars
			IF isOdd
				sb:Append(ch)
			ENDIF
			isOdd := !isOdd
		NEXT
		oddChars := sb:ToString()
	ENDIF
	RETURN oddChars

///
/// Return a character based on its position in a string.
///
/// <param name="c">The strign to be searched</param>
/// <param name="nStart">The position of the reuested charachter</param>
/// <returns>
/// The character at the given position as a string, if position is beyond the length
/// of the length of the string String.Empty is returned.
/// </returns>
FUNCTION CharPos(c AS STRING, nStart AS DWORD) AS STRING
	LOCAL result := string.Empty AS STRING
	IF ( nStart >= 1 && nStart <= (DWORD) c:Length )
		result := c:SubString((INT)nStart-1,1)
	ENDIF
	RETURN result

///
/// Convert an ASCII code to a character value.
///
/// <param name="dwChar"></param>
/// <returns>
/// </returns>
FUNCTION Chr(c AS DWORD) AS STRING
  LOCAL b   AS BYTE
  LOCAL ret AS STRING
   b := (BYTE)( c & 0xFF )  // VO ignores the high 24 bits

   IF b <= 0x7F
      ret := Convert.ToChar( b ):ToString()
   ELSE
      LOCAL encoding AS Encoding

      encoding := StringHelpers.WinEncoding

      LOCAL chars := CHAR[]{ 1 } AS CHAR[]
      LOCAL bytes := BYTE[]{ 1 } AS BYTE[]
      LOCAL decoder := encoding:GetDecoder() AS Decoder
      bytes[__ARRAYBASE__] := b
      decoder:GetChars( bytes, 0, 1, chars, 0 )
      ret := chars[__ARRAYBASE__]:ToString()

   ENDIF

   RETURN ret

///
/// Convert an ASCII code to a character value.
///
/// <param name="dwChar"></param>
/// <returns>
/// </returns>
FUNCTION ChrW(c AS DWORD) AS STRING
   IF c > 0xFFFF
      THROW Error.ArgumentError( __ENTITY__, "dwChar", "Number too High")
   ENDIF
   RETURN Convert.ToChar( (INT) ( c & 0xFFFF ) ):ToString()

/// This function is not implemented yet
//
// Encrypt or decrypt a string.
//
/// <param name="cSource"></param>
/// <param name="cKey"></param>
/// <returns>
/// </returns>
FUNCTION Crypt(cSource AS STRING,cKey AS STRING) AS STRING
	THROW NotImplementedException{}
	RETURN String.Empty

/// This function is not implemented yet
//
// Encrypt or decrypt a string, changing the contents of the original string as well as returning the encrypted string.
//
/// <param name="cSource"></param>
/// <param name="cKey"></param>
/// <returns>
/// </returns>
FUNCTION CryptA(cSource AS STRING,cKey AS STRING) AS STRING
	THROW NotImplementedException{}
	RETURN String.Empty

///
/// Decode a file from an e-mail transfer.
///
/// <param name="cMailPart"></param>
/// <param name="hfOut"></param>
/// <returns>
/// </returns>
FUNCTION DecodeBase64(cMailPart AS STRING,hFile AS IntPtr) AS INT
	IF cMailPart == NULL .OR. cMailPart:Length== 0
		RETURN 0
	ENDIF
	VAR aBytes := Convert.FromBase64String( cMailPart )
	RETURN (INT) FWrite3(hFile, aBytes, (DWORD) aBytes:Length)

///
/// Encode a file for e-mail transfer.
///
/// <param name="hfIn"></param>
/// <param name="hfOut"></param>
/// <returns>
/// </returns>
FUNCTION EncodeBase64(hFileIn AS IntPtr,hFileOut AS IntPtr) AS INT
	LOCAL nSize AS DWORD
	LOCAL nRead AS DWORD
	// determine file size
	FSeek3(hFileIn, 0, FS_END)
	nSize := FTell(hFileIn)
	FSeek3(hFileIn, 0, FS_SET)
	VAR aBuffer := BYTE[]{ (INT) nSize}
	nRead := FRead3(hFileIn, aBuffer, nSize)
	IF nRead != nSize
		RETURN 0
	ENDIF
	VAR cContents := Convert.ToBase64String(aBuffer, Base64FormattingOptions.InsertLineBreaks)
	nSize := FWrite(hFileOut, cContents)
	RETURN (INT) nSize

///
/// Replace all soft carriage returns (Chr(141)) in a string with hard carriage returns (Chr(13)).
///
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION HardCR(c AS STRING) AS STRING
	RETURN c:Replace( (CHAR) 141, (CHAR) 13 )

INTERNAL FUNCTION _nibble (c AS CHAR) AS BYTE
	LOCAL b AS BYTE
	SWITCH c
	CASE '0'
	CASE '1'
	CASE '2'
	CASE '3'
	CASE '4'
	CASE '5'
	CASE '6'
	CASE '7'
	CASE '8'
	CASE '9'
		b := (BYTE) c - '0'
	CASE 'A'
	CASE 'B'
	CASE 'C'
	CASE 'D'
	CASE 'E'
	CASE 'F'
		b := (BYTE) c - 'A' + 10
	OTHERWISE
		b := 0
	END SWITCH
	RETURN b
///
///
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION Hex2C(c AS STRING) AS STRING
	LOCAL i AS INT
	LOCAL sb AS StringBuilder
	sb := StringBuilder{c:Length}
	i := 0
	DO WHILE i <= c:Length - 2
		VAR b1 := _nibble(c[i])
		VAR b2 := _nibble(c[i+1])
		VAR b  := (b1 << 4) + b2
		sb:Append( Chr( b))
		i += 2
	ENDDO
	RETURN sb:ToString()

///
/// Indicate whether a substring is contained in a string.
///
/// <param name="cSearch">The string to search for.</param>
/// <param name="c">The string to search in.</param>
/// <returns>
/// True if the searched string is in the string.
/// </returns>
FUNCTION Instr(cSearch AS STRING,c AS STRING) AS LOGIC
	LOCAL result := FALSE AS LOGIC
	IF cSearch != NULL .AND. c != NULL
		result := c:IndexOf( cSearch, StringComparison.Ordinal ) > -1
	ENDIF
	RETURN result

///
/// Extract a substring beginning with the first character in a string.
///
/// <param name="c">A string from which the left part should be extracted.</param>
/// <param name="dwLen">The length of the substring which should be extracted.</param>
/// <returns>
/// A string of the left first characters in the given length.
/// </returns>function Len(c as string) as dword
FUNCTION Left(c AS STRING, dwLen AS DWORD) AS STRING
	IF ( c!=NULL )
		IF dwLen < c:Length
			c := c:Substring( 0, (INT) dwLen )
		ELSE
			NOP
		ENDIF
	ENDIF
	RETURN c

///
/// Convert the uppercase and mixed case characters in a string to lowercase.
///
/// <param name="cSource">THe string to be converted.</param>
/// <returns>
/// Returns the input string with all characters converted to lowercase.
/// </returns>
FUNCTION Lower(cSource AS STRING) AS STRING
	IF cSource != NULL
		cSource := cSource:ToLower()
	ENDIF
	RETURN cSource

///
/// Convert the uppercase and mixed case characters in a string to lowercase,
/// changing the contents of the argument as well as the return value.
///
///
/// <param name="cSorce"></param>
/// <returns>
/// Returns the input string with all characters converted to lowercase.
/// </returns>
FUNCTION LowerA(cSource REF STRING) AS STRING
	IF cSource != NULL
		cSource := cSource:ToLower()
	ENDIF
	RETURN cSource

///
/// Remove leading spaces from a string.
///
/// <param name="c">The string from which leading spaces should be cut off.</param>
/// <returns>
/// The input strings without eading spaces.
/// </returns>
FUNCTION LTrim(c AS STRING) AS STRING
	IF (c == NULL)
		RETURN c
	ENDIF
	RETURN c:TrimStart(trimChars)

///
/// Return the number of times a substring occurs in a string.
///
/// <param name="c">The string to be search in.</param>
/// <param name="cSearch">THe string of which its occurance should be counted</param>
/// <returns>
/// THe number how often the string to be searched for occurs in the original string.
/// </returns>
FUNCTION Occurs(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN Occurs3(cSearch,c, 0)

///
/// Return the number of times a substring occurs in a string.
///
/// <param name="c">The string to be search in.</param>
/// <param name="cSearch">THe string of which its occurance should be counted</param>
/// <returns>
/// THe number how often the string to be searched for occurs in the original string.
/// </returns>
FUNCTION Occurs2(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN Occurs3(cSearch,c, 0)

///
/// Return the number of times a substring occurs in a string, starting at a specified position.
///
/// <param name="cSrc"></param>
/// <param name="c"></param>
/// <param name="nOffs"></param>
/// <returns>
/// </returns>
FUNCTION Occurs3(cSrc AS STRING,c AS STRING,nOffset AS DWORD) AS DWORD
	LOCAL pos AS INT
	LOCAL count AS DWORD

	IF String.IsNullOrEmpty(cSrc) .OR. String.IsNullOrEmpty(c)
		RETURN 0
	ENDIF

	IF nOffset > 0
		nOffSet -= 1
	ENDIF

	count := 0
	IF nOffSet < (DWORD) c:Length
		DO WHILE ( pos := c:IndexOf(cSrc, (INT)nOffSet, StringComparison.Ordinal) ) >= 0
			count++
			nOffSet := (DWORD)pos + cSrc:Length
		ENDDO
	ENDIF

	RETURN count

///
/// Convert a string of ANSI characters to OEM characters.
///
/// <param name="cSource">String in Ansi format</param>
/// <returns>String converted to Unicode
/// </returns>
/// <remarks>This is a compatibility function. Do not use this unless you really have to.
/// X# is a Unicode app and conversions from Unicode - Ansi - Oem - Unicode will take place
/// if you use this function.

/// You should also realize that Ansi2Oem(Oem2Ansi(cSource)) will not always return cSource. Some characters may
/// not be available in the OEM codepage and could be translated to other characters.
/// For example: Windows codepage 1252 has Capital E Umlaut on position 203.
/// When translated to OEM codepage 437 this will become captial E without umlaut (69). Converting back to Ansi
/// this will remain a E without umlaut.
/// </remarks>
FUNCTION Ansi2Oem(cSource AS STRING) AS STRING
	LOCAL aBytes AS BYTE[]
	LOCAL iLen	 AS INT
	iLen := cSource:Length
	aBytes := String2Bytes(cSource)
	aBytes := Ansi2Oem(aBytes, iLen)
	RETURN Bytes2String(aBytes, iLen)

///
/// Convert an array of bytes from ANSI to OEM.
///
FUNCTION Ansi2Oem(bSource AS BYTE[]) AS BYTE[]
	RETURN Ansi2Oem(bSource, bSource:Length)

///
/// Convert an array of bytes from ANSI to OEM.
///
FUNCTION Ansi2Oem(bSource AS BYTE[], iLen AS INT) AS BYTE[]
	LOCAL bDest AS BYTE[]
	bDest := BYTE[]{iLen}
	CharToOemBuffA(bSource, bDest, (DWORD) iLen)
	RETURN bDest

///
/// Convert a string of ANSI characters to OEM characters, changing the contents of the original string as well as the returned string.
///
/// <param name="cSource">String in Ansi format</param>
/// <returns>String converted to Unicode
/// </returns>
/// <remarks>This is a compatibility function. Do not use this unless you really have to.
/// X# is a Unicode app and conversions from Unicode - Oem - Ansi - Unicode will take place
/// if you use this function.

/// You should also realize that Ansi2Oem(Oem2Ansi(cSource)) will not always return cSource. Some characters may
/// not be available in the OEM codepage and could be translated to other characters.
/// For example: Windows codepage 1252 has Capital E Umlaut on position 203.
/// When translated to OEM codepage 437 this will become captial E without umlaut (69). Converting back to Ansi
/// this will remain a E without umlaut.
/// </remarks>

FUNCTION Ansi2OemA(cSource REF STRING) AS STRING
	LOCAL aBytes AS BYTE[]
	LOCAL iLen	 AS INT
	iLen := cSource:Length
	aBytes := String2Bytes(cSource)
	aBytes := Ansi2Oem(aBytes, iLen)
	cSource := Bytes2String(aBytes, iLen)
	RETURN cSource

///
/// Convert a string of OEM characters to ANSI characters.
///
/// <param name="cSource">String in OEM format</param>
/// <returns>String converted to Ansi</returns>
/// <remarks>This is a compatibility function. Do not use this unless you really have to.
/// X# is a Unicode app and conversions from Unicode - Oem - Ansi - Unicode will take place
/// if you use this function.

/// You should also realize that Ansi2Oem(Oem2Ansi(cSource)) will not always return cSource. Some characters may
/// not be available in the OEM codepage and could be translated to other characters.
/// For example: Windows codepage 1252 has Capital E Umlaut on position 203.
/// When translated to OEM codepage 437 this will become captial E without umlaut (69). Converting back to Ansi
/// this will remain a E without umlaut.
/// </remarks>
FUNCTION Oem2Ansi(cSource AS STRING) AS STRING
	LOCAL aBytes AS BYTE[]
	LOCAL iLen	 AS INT
	iLen := cSource:Length
	aBytes := String2Bytes(cSource)
	aBytes := Oem2Ansi(aBytes, iLen)
	RETURN Bytes2String(aBytes, iLen)

///
/// Convert an array of bytes from OEM to ANSI .
///
FUNCTION Oem2Ansi(bSource AS BYTE[]) AS BYTE[]
	RETURN Oem2Ansi(bSource, bSource:Length)

///
/// Convert an array of bytes from OEM to ANSI .
///
FUNCTION Oem2AnsiA(bSource AS BYTE[]) AS VOID
	LOCAL bDest AS BYTE[]
	bDest := Oem2Ansi(bSource, bSource:Length)
	System.Array.Copy(bDest, bSource, bSource:Length)
    RETURN

///
/// Convert an array of bytes from OEM to ANSI .
///
FUNCTION Oem2Ansi(bSource AS BYTE[], iLen AS INT) AS BYTE[]
	LOCAL bDest AS BYTE[]
	bDest := BYTE[]{iLen}
	OemToCharBuffA(bSource, bDest, (DWORD) iLen)
	RETURN bDest

///
/// Convert a specified number of OEM characters in a source buffer to a buffer of corresponding, if any, ANSI characters.
///
/// <param name="pszDest"></param>
/// <param name="pszSource"></param>
/// <param name="dwCount"></param>
/// <returns>
/// </returns>
FUNCTION Oem2AnsiBuff(bDest AS BYTE[],bSource AS BYTE[],dwCount AS DWORD) AS BYTE[]
	OemToCharBuffA(bSource, bDest, dwCount)
	RETURN bDest

///
/// Convert a specified number of ANSI characters in a source buffer to a buffer of corresponding OEM characters.
///
/// <param name="pszDest"></param>
/// <param name="pszSource"></param>
/// <param name="dwCount"></param>
/// <returns>
/// </returns>
FUNCTION Ansi2OemBuff(bDest AS BYTE[],bSource AS BYTE[],dwCount AS DWORD) AS BYTE[]
	CharToOemBuffA(bSource, bDest, dwCount)
	RETURN bDest

INTERNAL _DLL FUNCTION CharToOemBuffA( lpszSrc AS BYTE[], lpszDst AS BYTE[], cchDstLength AS DWORD ) AS LOGIC PASCAL:USER32.CharToOemBuffA
INTERNAL _DLL FUNCTION OemToCharBuffA( lpszSrc AS BYTE[], lpszDst AS BYTE[], cchDstLength AS DWORD ) AS LOGIC PASCAL:USER32.OemToCharBuffA

///
/// Convert a string of OEM characters to ANSI characters, changing the contents of the argument as well as the return value.
///
/// <param name="cSource">String in OEM format</param>
/// <returns>String converted to Ansi
/// </returns>
/// <remarks>This is a compatibility function. Do not use this unless you really have to.
/// X# is a Unicode app and conversions from Unicode - Oem - Ansi - Unicode will take place
/// if you use this function.

/// You should also realize that Ansi2Oem(Oem2Ansi(cSource)) will not always return cSource. Some characters may
/// not be available in the OEM codepage and could be translated to other characters.
/// For example: Windows codepage 1252 has Capital E Umlaut on position 203.
/// When translated to OEM codepage 437 this will become captial E without umlaut (69). Converting back to Ansi
/// this will remain a E without umlaut.
/// </remarks>
FUNCTION Oem2AnsiA(cSource REF STRING) AS STRING
	LOCAL aBytes AS BYTE[]
	LOCAL iLen	 AS INT
	iLen := cSource:Length
	aBytes := String2Bytes(cSource)
	aBytes := Oem2Ansi(aBytes, iLen)
	cSource := Bytes2String(aBytes, iLen)
	RETURN cSource

///
/// Change the first character of each word to uppercase
///
/// <param name="c">The string to be converted.</param>
/// <returns>
/// The converted string according to the CurrentCulture
/// </returns>
FUNCTION Proper(cString AS STRING) AS STRING
	LOCAL sb AS StringBuilder
	LOCAL inside AS LOGIC
	IF cString != NULL
		sb := StringBuilder{cString:Length}
		inside := FALSE
		FOREACH ch AS CHAR IN cString
			VAR cToAdd := ch
			IF inside
				IF Char.IsLetterOrDigit(ch)
					cToAdd := char.ToLower(ch)
				ELSE
					inside := FALSE
				ENDIF
			ELSE
				IF Char.IsLetterOrDigit(ch)
					cToAdd := char.ToUpper(ch)
					inside := TRUE
				ENDIF
			ENDIF
			sb:append(cToAdd)
		NEXT
		cString := sb:ToString()
	ENDIF
	RETURN cString

///
/// Capitalize a proper name correctly, changing the contents of the argument as well as the return value.
///
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION ProperA(c REF STRING) AS STRING
	c := Proper(c)
	RETURN c

///
///
/// <param name="cIn"></param>
/// <returns>
/// </returns>
FUNCTION QPEncString(cIn AS STRING) AS STRING
	THROW NotImplementedException{}
	RETURN String.Empty

///
/// Return the position of the last occurrence of a substring within a string.
///
/// <param name="cSearch">THe string to be searched.</param>
/// <param name="c">The string to be searched in.</param>
/// <returns>
/// The right most position of the string to be searched inside the searched string.
/// </returns>
FUNCTION RAt(cSearch AS STRING,c AS STRING) AS DWORD
	LOCAL rightMost := 0 AS DWORD
	IF cSearch != NULL .AND. c != NULL
		IF c:Length != 0 .AND. cSearch:Length != 0
			rightMost:= (DWORD) c:LastIndexOf(cSearch, StringComparison.Ordinal) + 1
		ENDIF
	ENDIF
	RETURN rightMost

///
/// Return the position of the last occurrence of a substring within a string.
///
/// <param name="cSearch">THe string to be searched.</param>
/// <param name="c">The string to be searched in.</param>
/// <returns>
/// The right most position of the string to be searched inside the searched string.
/// </returns>
FUNCTION RAt2(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN RAt(cSearch,c)

///
/// Return the position of the last occurrence of a substring within a string.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <param name="dwOff"></param>
/// <returns>
/// </returns>

FUNCTION RAt3(cSearch AS STRING,c AS STRING,dwOffSet AS DWORD) AS DWORD
	LOCAL nResult := 0 AS DWORD
	IF cSearch != NULL .AND. c != NULL
		IF c:Length != 0 .AND. cSearch:Length != 0
			IF dwOffSet > (DWORD) c:Length
				dwOffSet := 0U
			ENDIF
			VAR cTemp := c:Substring((INT) dwOffSet)
			nResult := RAt(cSearch, cTemp)
			IF nResult > 0U
				nResult := nResult+dwOffSet
			ENDIF
		ENDIF
	ENDIF
	RETURN nResult

///
/// Return the line number of the last occurrence of a substring within a multiline string.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION RAtLine(cSearch AS STRING, c AS STRING) AS DWORD
	LOCAL nPos AS DWORD
	IF cSearch == NULL .OR. c == NULL .OR. cSearch:Length == 0 .OR. c:Length == 0
		RETURN 0
	ENDIF
	nPos := RAt(cSearch,c)
	c := Left(c,nPos-1)

	RETURN MemLines(c)

///
/// Return the line number of the last occurrence of a substring within a multiline string.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION RATLine2(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN RAtLine(cSearch, c)

///
/// Repeat a string a specified number of times.
///
/// <param name="c">The string to be repeated.</param>
/// <param name="dwCount">The number of replications.</param>
/// <returns>
/// A string which consist of dwCount replications of c.
/// </returns>
FUNCTION Repl(c AS STRING,dwCount AS DWORD) AS STRING
	RETURN Replicate(c, dwCount)
///
/// Repeat a string a specified number of times.
///
/// <param name="c">The string to be repeated.</param>
/// <param name="dwCount">The number of replications.</param>
/// <returns>
/// A string which consist of dwCount replications of c.
/// </returns>
FUNCTION Replicate(c AS STRING,dwCount AS DWORD) AS STRING
	LOCAL cReturn := "" AS STRING
	IF dwCount > 0 .AND. c != NULL
		cReturn := System.Text.StringBuilder{}:Insert( 0, c, (INT) dwCount ):ToString()
	ENDIF
	RETURN cReturn

///
/// Return a substring beginning with the rightmost character.
///
/// <param name="c">The string to extract the rightmost characters from.</param>
/// <param name="dwLen">The length of the string to extract.</param>
/// <returns>
/// Returns the right most part in the given length.
/// </returns>
FUNCTION Right(c AS STRING,dwLen AS DWORD) AS STRING
	IF c != NULL
		IF dwLen > c:Length
			NOP
		ELSE
			c := c:Substring( c:Length - (INT) dwLen, (INT) dwLen )
		ENDIF
	ENDIF
	RETURN c

///
/// Remove trailing spaces from a string.
///
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION RTrim(c AS STRING) AS STRING
	IF ( c == NULL )
		RETURN c
	ENDIF
	RETURN c:TrimEnd(trimChars)

///
/// Create new character variable with the same characters as the original string.
///
/// <param name="c">The string be cloned.</param>
/// <returns>
/// A opy of the input string.
/// </returns>
FUNCTION SClone(c AS STRING) AS STRING
	LOCAL clonedString := NULL AS STRING
	IF (c != NULL)
		clonedString := String.Copy( c )
	ENDIF
	RETURN clonedString

///
/// Create a string of spaces.
///
/// <param name="dwSize"></param>
/// <returns>
/// </returns>
FUNCTION Space(dwSize AS DWORD) AS STRING
	RETURN STRING{' ',(INT)dwSize}

///
/// Create a string of spaces.
///
/// <param name="dwSize"></param>
/// <returns>
/// </returns>
FUNCTION Space(iSize AS INT) AS STRING
	RETURN STRING{' ',iSize}

///
/// Return the length of a strongly typed string.
///
/// <param name="c">String which length should be calculated.</param>
/// <returns>
/// The length of the string.
/// </returns>
FUNCTION SLen(c AS STRING) AS DWORD
	LOCAL len := 0 AS DWORD
	IF c != NULL
		len := (DWORD) c:Length
	ENDIF
	RETURN len

///
/// Convert a string to Soundex form.
///
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION SoundEx(c AS STRING) AS STRING
	LOCAL sb		AS StringBuilder
	LOCAL iLen		AS INT
	LOCAL ret		AS STRING
	LOCAL i			AS INT
	LOCAL cLastChar AS CHAR
	LOCAL cSoundExChar AS CHAR

	IF String.IsNullOrEmpty(c)
		RETURN "0000"
	END IF
	cLastChar := (CHAR) 0
	c	 := c:ToUpper()
	iLen := c:Length - 1
	sb := StringBuilder{}
	sb:Append( c[0] )
	FOR i := 1 TO iLen
		cSoundExChar := _SoundExChar( c[i] )
		IF cSoundExChar != '0' .AND. cSoundExChar != cLastChar
			sb:Append( cSoundExChar )
		ENDIF
		cLastChar := cSoundExChar
		IF sb:Length == 4
			EXIT
		ENDIF
	NEXT
	ret := sb:ToString()

	RETURN ret:PadRight( 4, '0' )

INTERNAL FUNCTION _SoundExChar( c AS CHAR ) AS CHAR
	LOCAL ret AS CHAR
	ret := (CHAR) 0
	SWITCH c
		CASE c'A' ;	CASE c'E'; CASE c'I'; CASE c'O'; CASE c'U'; CASE c'Y'
			ret := c'0'
		CASE c'H' ;	CASE c'W'
			ret := c'0'
		CASE c'B' ;	CASE c'F';CASE c'P' ;CASE c'V'
			ret := c'1'
		CASE c'C' ;	CASE c'G';CASE c'J' ;CASE c'K';CASE c'Q';CASE c'S';CASE c'X';CASE c'Z'
			ret := c'2'
		CASE c'D' ;	CASE c'T'
			ret := c'3'
		CASE c'L'
			ret := c'4'
		CASE c'M' ;	CASE c'N'
			ret := c'5'
		CASE c'R'
			ret := '6'
		OTHERWISE
			ret := '0'
	END SWITCH

	RETURN ret

/// This function is not implemented yet
//
// Allows text substitution in strings entered at runtime.
//
/// <param name="s"></param>
/// <returns>
/// </returns>
FUNCTION StrEvaluate(s AS STRING) AS STRING
	THROW NotImplementedException{}
	RETURN String.Empty

///
/// Delete and insert characters in a string.
///
/// <param name="c">The string in which the substitution should take place</param>
/// <param name="n">Starting position of the substring to be deleted.</param>
/// <param name="nDel">The number of characters to be deleted.</param>
/// <param name="cIns">The string which should be inserted instead of the deleted characters.</param>
/// <returns>
/// A new string with the substring from the starting position in the given length being subsituted with the insert string.
/// </returns>

FUNCTION Stuff(c AS STRING,nStart AS DWORD,nToDelete AS DWORD,cIns AS STRING) AS STRING
	LOCAL result := cIns AS STRING
	IF c != NULL
		IF string.IsNullOrEmpty(cIns)
			cIns := ""
		ENDIF
		IF nStart > 0
			nStart -= 1
		ENDIF
		LOCAL part1 := c AS STRING
		IF  (INT) nStart < c:Length
			part1 := c:Substring(0,(INT)nStart)
		ENDIF
		LOCAL part2 := "" AS STRING
		VAR iOffSet := (INT) (nStart + nToDelete)
		IF  iOffSet  < c:length
			part2 := c:Substring( iOffSet )
		ENDIF
		result := part1 + cIns + part2
	ENDIF
	RETURN result

/// <exclude />
FUNCTION __SubStr( c AS STRING, nStart AS INT, nLength AS INT ) AS STRING
	IF c != NULL
		IF nStart == 0
			nStart := 1
		ENDIF

		IF nStart < 0
			nStart := c:Length+nStart+1
		ENDIF

		IF nLength < 0
			nLength := c:Length
		ENDIF

		IF nStart <= c:Length .AND. nStart > 0
			nLength := Math.Min( c:Length - nStart + 1, nLength )
			c := c:Substring( nStart - 1, nLength )
		ELSE
			c := ""
		ENDIF
	ENDIF
	RETURN c

///
/// Extract a substring from a string, using strong typing and only two arguments.
///
/// <param name="c">The string to be extracted from.</param>
/// <param name="dwStart">The starting position from which the substring should be extracted.</param>
/// <returns>
/// The extracted substring.
/// </returns>
FUNCTION SubStr2(c AS STRING,dwStart AS DWORD) AS STRING
	IF c != NULL
		c := __SubStr( c, (INT) dwStart, ( c:Length - (INT) dwStart  + 1 ) )
	ENDIF
	RETURN c

///
/// Extract a substring from a string, using strong typing and three required arguments.
///
/// <param name="c">The string to be extracted from.</param>
/// <param name="dwStart">The starting position from which the substring should be extracted.</param>
/// <param name="dwLen">The length of the substring to beextracted</param>
/// <returns>
/// The extracted substring in the given length.
/// </returns>
FUNCTION SubStr3(c AS STRING,dwStart AS DWORD,dwLen AS DWORD) AS STRING
	IF c != NULL
		c := __SubStr( c, (INT) dwStart, (INT) dwLen )
	ENDIF
	RETURN c

///
/// Remove trailing spaces from a string.
///
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION Trim(c AS STRING) AS STRING
	IF ( c == NULL )
		RETURN c
	ENDIF
	RETURN c:TrimEnd(trimChars)

///
/// Convert the lowercase and mixed case characters in a string to uppercase.
///
/// <param name="cSorce"></param>
/// <returns>
/// </returns>
FUNCTION Upper(cSource AS STRING) AS STRING
	IF cSource != NULL
		cSource := cSource:ToUpper()
	ENDIF
	RETURN cSource

///
/// Convert the lowercase and mixed case characters in a string to uppercase, changing the contents of the argument as well as the return value.
///
/// <param name="cSorce"></param>
/// <returns>
/// </returns>
FUNCTION UpperA(cSource REF STRING) AS STRING
	IF cSource != NULL
		cSource := cSource:ToUpper()
	ENDIF
	RETURN cSource

/// This function is not implemented yet
/// <param name="cLine"></param>
/// <param name="hfOut"></param>
/// <returns>
/// </returns>
UNSAFE FUNCTION UUDecodeLine(cLine AS STRING,hfOut AS PTR) AS DWORD
	THROW NotImplementedException{}
RETURN 0

/// This function is not implemented yet
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION UUEncFile(c AS STRING) AS STRING
	THROW NotImplementedException{}
	RETURN String.Empty

/// This function is not implemented yet
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION UUEncLine(c AS STRING) AS STRING
	THROW NotImplementedException{}
	RETURN String.Empty

/// Determine if the leftmost character in a string is alphabetic.
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character is alphabetic.</returns>
FUNCTION IsAlpha(cSource AS STRING) AS LOGIC
	LOCAL ret := FALSE AS LOGIC
	IF ! String.IsNullOrEmpty( cSource )
		ret := Char.IsLetter( cSource, 0 )
	ENDIF
	RETURN ret

/// Determine if the leftmost character in a string is alphanumeric.
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character is either alphabetic or numeric otherwise FALSE.</returns>
FUNCTION IsAlNum(cSource AS STRING) AS LOGIC
	LOCAL ret := FALSE AS LOGIC
	IF ! String.IsNullOrEmpty( cSource )
		ret := Char.IsLetterOrDigit( cSource, 0 )
	ENDIF
	RETURN ret

/// Determine if the leftmost character in a string is alphanumeric..
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character is either alphabetic or numeric otherwise FALSE.</returns>
FUNCTION IsAlphaNum(cSource AS STRING) AS LOGIC
	RETURN IsAlNum(cSource)

/// Determine if the leftmost character in a string is a digit (that is, a numeric digit between 0 and 9).
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character of the string is a number from 0 to 9; otherwise FALSE.</returns>
FUNCTION IsDigit(cSource AS STRING) AS LOGIC
	LOCAL ret := FALSE AS LOGIC
	IF ! String.IsNullOrEmpty( cSource )
		ret := Char.IsDigit(cSource, 0 )
	ENDIF
	RETURN ret

/// Determine if the leftmost character in a string is a binary digit  (0 or 1)).
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character of the string is 0 or 1 otherwise FALSE.</returns>
FUNCTION IsBDigit(cSource AS STRING) AS LOGIC
	LOCAL ret := FALSE AS LOGIC
	IF ! String.IsNullOrEmpty( cSource )
		SWITCH cSource[0]
			CASE '0'
			CASE '1'
				ret := TRUE
		END SWITCH
	ENDIF
	RETURN ret

/// Determine if the leftmost character in a string is a hex character (that is, digits from 1 through 9 and letters from A through F).
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character is hex otherwise FALSE.</returns>
FUNCTION IsXDigit(cSource AS STRING) AS LOGIC
	LOCAL ret := FALSE AS LOGIC
	IF ! String.IsNullOrEmpty( cSource )
		LOCAL c AS CHAR
		c := cSource[0]
		IF char.IsDigit(c) .OR. ;
			(c >= 'A' .AND. c <= 'F') .OR. ;
			(c >= 'a' .AND. c <= 'f')
			ret := TRUE
		ENDIF
	ENDIF
	RETURN ret

/// Determine if the leftmost character in a string is a blank (that is, Chr(9) through Chr(13) or Chr(32)).
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character of the string blank otherwise FALSE.</returns>
FUNCTION IsSpace(cSource AS STRING) AS LOGIC
	LOCAL ret := FALSE AS LOGIC
	IF ! String.IsNullOrEmpty( cSource )
		ret := char.IsWhiteSpace(cSource,0)
	ENDIF
	RETURN ret

/// Determine if the leftmost character in a string is uppercase.
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character is an uppercase letter otherwise, FALSE.</returns>
FUNCTION IsUpper(cSource AS STRING) AS LOGIC
	LOCAL ret := FALSE AS LOGIC
	IF ! String.IsNullOrEmpty( cSource )
		ret := Char.IsUpper(cSource, 0 )
	ENDIF
	RETURN ret

/// Determine if the leftmost character in a string is lower.
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character is a lowercase letter otherwise, FALSE.</returns>
FUNCTION IsLower(cSource AS STRING) AS LOGIC
	LOCAL ret := FALSE AS LOGIC
	IF ! String.IsNullOrEmpty( cSource )
		ret := Char.IsLower(cSource, 0 )
	ENDIF
	RETURN ret

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//
USING XSharp
USING System.Text
// Array of chars used for the various trim functions
INTERNAL GLOBAL trimChars := <CHAR>{ ' ' } AS CHAR[]

FUNCTION AllTrim(c AS STRING) AS STRING
	IF ( c == NULL )
		RETURN c
	ENDIF
	RETURN c:Trim(trimChars)

///
/// Convert a character to its ASCII value using default Windows codepage
///
/// <param name="c"></param>
/// <returns>
/// </returns>

FUNCTION Asc(c AS STRING) AS DWORD
	LOCAL ascValue := 0 AS DWORD
	LOCAL chValue AS CHAR
	IF ( !String.IsNullOrEmpty(c) )
		chValue := c[0]
		ascValue := (DWORD) chValue
		IF ascValue > 127
			LOCAL encoding AS Encoding
			encoding := StringHelpers.WinEncoding
			LOCAL buffer AS BYTE[]
			VAR chars := <CHAR> {chValue}
			IF encoding:IsSingleByte
				buffer := BYTE[]{1}
				encoding:GetBytes(chars,0,1,buffer,0)
				ascValue := buffer[0]
			ELSE
				buffer := BYTE[]{2}
				IF encoding:GetBytes(chars,0,1,buffer,0) == 1
					ascValue := buffer[0]
				ELSE
					IF BitConverter.IsLittleEndian
						LOCAL tmp := buffer[0] AS BYTE
						buffer[0] := buffer[1]
						buffer[1] := tmp
					ENDIF
					ascValue := BitConverter.ToUInt16( buffer, 0 )
				ENDIF
			ENDIF
		ENDIF
	ENDIF
	RETURN ascValue

///
/// Convert a character to its Unicode ASCII value.
///
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION AscW(c AS STRING) AS DWORD
	LOCAL ascValue := 0 AS DWORD
	LOCAL chValue AS CHAR
	IF ( !String.IsNullOrEmpty(c) )
		chValue := c[0]
		ascValue := (DWORD) chValue
	ENDIF
	RETURN ascValue

///
/// Return the position of the first occurrence of a substring within a string.
///
/// <param name="cSearch">The string to search for.</param>
/// <param name="c">The string to search in.</param>
/// <returns>
/// The position of the first occurrence of cSearch within cTarget.  If cSearch is not found, At() returns 0.
/// If cSearch is empty or c is empty, At() returns 0.
/// </returns>
FUNCTION At(cSearch AS STRING,c AS STRING) AS DWORD
	LOCAL position := 0 AS DWORD
	IF ( c != NULL .AND. cSearch != NULL )
		IF c:Length != 0 .and. cSearch:Length != 0
			position := (DWORD) c:IndexOf(cSearch, StringComparison.Ordinal) +1
		END IF
	ENDIF
	RETURN position

///
/// Return the position of the first occurrence of a substring within a string.
///
/// <param name="cSearch">The string to search for.</param>
/// <param name="c">The string to search in.</param>
/// <returns>
/// The position of the first occurrence of cSearch within cTarget.  If cSearch is not found, At() returns 0.
/// If cSearch is empty or c is empty, At() returns 0.
/// </returns>
FUNCTION At2(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN At(cSearch,c)

///
/// Return the position of the first occurrence of a substring within a string, starting at a specified position.
///
/// <param name="cSearch">The string to search for.</param>
/// <param name="c">The string to search in.</param>
/// <param name="dwOff">The position to begin the search with. This offset is ZERO based</param>
/// <returns>
/// The position of the first occurrence of cSearch within cTarget behind the give position.  If cSearch is not found, At() returns 0.
/// If cSearch is empty or c is empty, At3() returns 0.
/// </returns>
FUNCTION At3(cSearch AS STRING,c AS STRING,dwOff AS DWORD) AS DWORD
	LOCAL position := 0 AS DWORD
	// note dwOffset is ZERO based in VO
	IF ( c != NULL .AND. cSearch != NULL .AND. dwOff <= c:Length )
		IF c:Length != 0 .and. cSearch:Length != 0
			position := (DWORD) c:IndexOf(cSearch,(INT)dwOff) +1
		ENDIF
	ENDIF
	RETURN position

///
/// Return the position of the first occurrence of a substring within a string, without regard for case.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION AtC(cSearch AS STRING,c AS STRING) AS DWORD
	LOCAL position := 0 AS DWORD
	IF ( c != NULL .AND. cSearch != NULL )
		IF c:Length != 0 .and. cSearch:Length != 0
			position := (DWORD) c:IndexOf(cSearch, StringComparison.OrdinalIgnoreCase) +1
		ENDIF
	ENDIF
	RETURN position

///
/// Return the position of the first occurrence of a substring within a string, without regard for case.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION AtC2(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN AtC(cSearch,c)

///
/// Return the line number of the first occurrence of a substring within a multiple line string, without regard for case.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION ATCLine(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN AtLine( cSearch:ToUpper(), c:ToUpper() )

///
/// Return the line number of the first occurrence of a substring within a multiple line string, without regard for case.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION ATCLine2(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN AtLine2( cSearch:ToUpper(), c:ToUpper() )

///
/// Return the line number of the first occurrence of a substring within a multiple line string.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION ATLine(cSearch AS STRING,c AS STRING) AS DWORD
	LOCAL nPos AS DWORD
	IF String.IsNullOrEmpty(c) .OR. String.IsNullOrEmpty(cSearch)
		RETURN 0
	ENDIF
	IF c:StartsWith(cSearch)
		RETURN 1
	ENDIF
	nPos    := At( cSearch, c )
	IF (nPos > 0)
		c := Left( c, nPos - 1 )
		nPos := MemLines( c)
		IF c:EndsWith(e"\r\n")
			nPos++
		ENDIF
	ENDIF
	RETURN nPos

///
/// Return the line number of the first occurrence of a substring within a multiple line string.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION ATLine2(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN AtLine(cSearch, c)

/// This function is not implemented yet
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION B64EncFile(c AS STRING) AS STRING
	THROW NotImplementedException{}
	RETURN String.Empty

/// This function is not implemented yet
/// <param name="cIn"></param>
/// <returns>
/// </returns>
FUNCTION B64EncString(cIn AS STRING) AS STRING
	THROW NotImplementedException{}
	RETURN String.Empty

///
/// Return an uninitialized string of a specified size.
///
/// <param name="dwSize"></param>
/// <returns>
/// </returns>
FUNCTION Buffer(dwSize AS DWORD) AS STRING
	RETURN STRING{'\0', (INT) dwSize}

///
/// Return the even-numbered characters in a string.
///
/// <param name="c">The string from which the even characters shall be extracted.</param>
/// <returns>
/// A string which is assembled from the even characters in c.
/// </returns>
FUNCTION CharEven(c AS STRING) AS STRING
	LOCAL evenChars:=NULL AS STRING
	IF ( !string.IsNullOrEmpty(c) )
		//local chars  := c:ToCharArray() as char[]
		LOCAL isEven := FALSE AS  LOGIC
		LOCAL sb     := System.Text.StringBuilder{} AS System.Text.StringBuilder

		FOREACH ch AS CHAR IN c//hars
			IF isEven
				sb:Append(ch)
			ENDIF
			isEven := !isEven
		NEXT
		evenChars := sb:ToString()
	ENDIF
	RETURN evenChars

///
/// Return a string whose odd-numbered characters and even-numbered characters are from 2 different strings.
///
/// <param name="c1"></param>
/// <param name="c2"></param>
/// <returns>
/// </returns>
FUNCTION CharMix(cOdd AS STRING,cEven AS STRING) AS STRING
	LOCAL n1 := 0 AS INT
	LOCAL n2 := 0 AS INT
	LOCAL i1 := 0 AS INT
	LOCAL i2 := 0  AS INT
	LOCAL sb AS StringBuilder

	IF cEven:Length == 0
		RETURN ""
	ELSE
		sb := StringBuilder{ cOdd:Length * 2 }
		n1 := cOdd:Length - 1
		n2 := cEven:Length - 1

		FOR i1 := 0 UPTO n1
			sb:Append( cOdd[i1] )
			IF i2 > n2
				i2 := 0
			ENDIF
			sb:Append( cEven[i2++] )
		NEXT

		RETURN sb:ToString()
	ENDIF

///
/// Return the odd-numbered characters in a string.
///
/// <param name="c">The string from which the odd characters shall be extracted.</param>
/// <returns>
/// A string which is assembled from the odd characters in c.
/// </returns>
FUNCTION CharOdd(c AS STRING) AS STRING
	LOCAL oddChars:=NULL AS STRING
	IF ( !string.IsNullOrEmpty(c) )
		//local chars  := c:ToCharArray() as char[]
		LOCAL isOdd  := TRUE AS  LOGIC
		LOCAL sb     := System.Text.StringBuilder{} AS System.Text.StringBuilder

		FOREACH ch AS CHAR IN c//chars
			IF isOdd
				sb:Append(ch)
			ENDIF
			isOdd := !isOdd
		NEXT
		oddChars := sb:ToString()
	ENDIF
	RETURN oddChars

///
/// Return a character based on its position in a string.
///
/// <param name="c">The strign to be searched</param>
/// <param name="nStart">The position of the reuested charachter</param>
/// <returns>
/// The character at the given position as a string, if position is beyond the length
/// of the length of the string String.Empty is returned.
/// </returns>
FUNCTION CharPos(c AS STRING, nStart AS DWORD) AS STRING
	LOCAL result := string.Empty AS STRING
	IF ( nStart >= 1 && nStart <= (DWORD) c:Length )
		result := c:SubString((INT)nStart-1,1)
	ENDIF
	RETURN result

///
/// Convert an ASCII code to a character value.
///
/// <param name="dwChar"></param>
/// <returns>
/// </returns>
FUNCTION Chr(c AS DWORD) AS STRING
  LOCAL b   AS BYTE
  LOCAL ret AS STRING
   b := (BYTE)( c & 0xFF )  // VO ignores the high 24 bits

   IF b <= 0x7F
      ret := Convert.ToChar( b ):ToString()
   ELSE
      LOCAL encoding AS Encoding

      encoding := StringHelpers.WinEncoding

      LOCAL chars := CHAR[]{ 1 } AS CHAR[]
      LOCAL bytes := BYTE[]{ 1 } AS BYTE[]
      LOCAL decoder := encoding:GetDecoder() AS Decoder
      bytes[__ARRAYBASE__] := b
      decoder:GetChars( bytes, 0, 1, chars, 0 )
      ret := chars[__ARRAYBASE__]:ToString()

   ENDIF

   RETURN ret

///
/// Convert an ASCII code to a character value.
///
/// <param name="dwChar"></param>
/// <returns>
/// </returns>
FUNCTION ChrW(c AS DWORD) AS STRING
   IF c > 0xFFFF
      THROW Error.ArgumentError( __ENTITY__, "dwChar", "Number too High")
   ENDIF
   RETURN Convert.ToChar( (INT) ( c & 0xFFFF ) ):ToString()

/// This function is not implemented yet
//
// Encrypt or decrypt a string.
//
/// <param name="cSource"></param>
/// <param name="cKey"></param>
/// <returns>
/// </returns>
FUNCTION Crypt(cSource AS STRING,cKey AS STRING) AS STRING
	THROW NotImplementedException{}
	RETURN String.Empty

/// This function is not implemented yet
//
// Encrypt or decrypt a string, changing the contents of the original string as well as returning the encrypted string.
//
/// <param name="cSource"></param>
/// <param name="cKey"></param>
/// <returns>
/// </returns>
FUNCTION CryptA(cSource AS STRING,cKey AS STRING) AS STRING
	THROW NotImplementedException{}
	RETURN String.Empty

///
/// Decode a file from an e-mail transfer.
///
/// <param name="cMailPart"></param>
/// <param name="hfOut"></param>
/// <returns>
/// </returns>
FUNCTION DecodeBase64(cMailPart AS STRING,hFile AS IntPtr) AS INT
	IF cMailPart == NULL .OR. cMailPart:Length== 0
		RETURN 0
	ENDIF
	VAR aBytes := Convert.FromBase64String( cMailPart )
	RETURN (INT) FWrite3(hFile, aBytes, (DWORD) aBytes:Length)

///
/// Encode a file for e-mail transfer.
///
/// <param name="hfIn"></param>
/// <param name="hfOut"></param>
/// <returns>
/// </returns>
FUNCTION EncodeBase64(hFileIn AS IntPtr,hFileOut AS IntPtr) AS INT
	LOCAL nSize AS DWORD
	LOCAL nRead AS DWORD
	// determine file size
	FSeek3(hFileIn, 0, FS_END)
	nSize := FTell(hFileIn)
	FSeek3(hFileIn, 0, FS_SET)
	VAR aBuffer := BYTE[]{ (INT) nSize}
	nRead := FRead3(hFileIn, aBuffer, nSize)
	IF nRead != nSize
		RETURN 0
	ENDIF
	VAR cContents := Convert.ToBase64String(aBuffer, Base64FormattingOptions.InsertLineBreaks)
	nSize := FWrite(hFileOut, cContents)
	RETURN (INT) nSize

///
/// Replace all soft carriage returns (Chr(141)) in a string with hard carriage returns (Chr(13)).
///
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION HardCR(c AS STRING) AS STRING
	RETURN c:Replace( (CHAR) 141, (CHAR) 13 )

INTERNAL FUNCTION _nibble (c AS CHAR) AS BYTE
	LOCAL b AS BYTE
	SWITCH c
	CASE '0'
	CASE '1'
	CASE '2'
	CASE '3'
	CASE '4'
	CASE '5'
	CASE '6'
	CASE '7'
	CASE '8'
	CASE '9'
		b := (BYTE) c - '0'
	CASE 'A'
	CASE 'B'
	CASE 'C'
	CASE 'D'
	CASE 'E'
	CASE 'F'
		b := (BYTE) c - 'A' + 10
	OTHERWISE
		b := 0
	END SWITCH
	RETURN b
///
///
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION Hex2C(c AS STRING) AS STRING
	LOCAL i AS INT
	LOCAL sb AS StringBuilder
	sb := StringBuilder{c:Length}
	i := 0
	DO WHILE i <= c:Length - 2
		VAR b1 := _nibble(c[i])
		VAR b2 := _nibble(c[i+1])
		VAR b  := (b1 << 4) + b2
		sb:Append( Chr( b))
		i += 2
	ENDDO
	RETURN sb:ToString()

///
/// Indicate whether a substring is contained in a string.
///
/// <param name="cSearch">The string to search for.</param>
/// <param name="c">The string to search in.</param>
/// <returns>
/// True if the searched string is in the string.
/// </returns>
FUNCTION Instr(cSearch AS STRING,c AS STRING) AS LOGIC
	LOCAL result := FALSE AS LOGIC
	IF cSearch != NULL .AND. c != NULL
		result := c:IndexOf( cSearch, StringComparison.Ordinal ) > -1
	ENDIF
	RETURN result

///
/// Extract a substring beginning with the first character in a string.
///
/// <param name="c">A string from which the left part should be extracted.</param>
/// <param name="dwLen">The length of the substring which should be extracted.</param>
/// <returns>
/// A string of the left first characters in the given length.
/// </returns>function Len(c as string) as dword
FUNCTION Left(c AS STRING, dwLen AS DWORD) AS STRING
	IF ( c!=NULL )
		IF dwLen < c:Length
			c := c:Substring( 0, (INT) dwLen )
		ELSE
			NOP
		ENDIF
	ENDIF
	RETURN c

///
/// Convert the uppercase and mixed case characters in a string to lowercase.
///
/// <param name="cSource">THe string to be converted.</param>
/// <returns>
/// Returns the input string with all characters converted to lowercase.
/// </returns>
FUNCTION Lower(cSource AS STRING) AS STRING
	IF cSource != NULL
		cSource := cSource:ToLower()
	ENDIF
	RETURN cSource

///
/// Convert the uppercase and mixed case characters in a string to lowercase,
/// changing the contents of the argument as well as the return value.
///
///
/// <param name="cSorce"></param>
/// <returns>
/// Returns the input string with all characters converted to lowercase.
/// </returns>
FUNCTION LowerA(cSource REF STRING) AS STRING
	IF cSource != NULL
		cSource := cSource:ToLower()
	ENDIF
	RETURN cSource

///
/// Remove leading spaces from a string.
///
/// <param name="c">The string from which leading spaces should be cut off.</param>
/// <returns>
/// The input strings without eading spaces.
/// </returns>
FUNCTION LTrim(c AS STRING) AS STRING
	IF (c == NULL)
		RETURN c
	ENDIF
	RETURN c:TrimStart(trimChars)

///
/// Return the number of times a substring occurs in a string.
///
/// <param name="c">The string to be search in.</param>
/// <param name="cSearch">THe string of which its occurance should be counted</param>
/// <returns>
/// THe number how often the string to be searched for occurs in the original string.
/// </returns>
FUNCTION Occurs(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN Occurs3(cSearch,c, 0)

///
/// Return the number of times a substring occurs in a string.
///
/// <param name="c">The string to be search in.</param>
/// <param name="cSearch">THe string of which its occurance should be counted</param>
/// <returns>
/// THe number how often the string to be searched for occurs in the original string.
/// </returns>
FUNCTION Occurs2(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN Occurs3(cSearch,c, 0)

///
/// Return the number of times a substring occurs in a string, starting at a specified position.
///
/// <param name="cSrc"></param>
/// <param name="c"></param>
/// <param name="nOffs"></param>
/// <returns>
/// </returns>
FUNCTION Occurs3(cSrc AS STRING,c AS STRING,nOffset AS DWORD) AS DWORD
	LOCAL pos AS INT
	LOCAL count AS DWORD

	IF String.IsNullOrEmpty(cSrc) .OR. String.IsNullOrEmpty(c)
		RETURN 0
	ENDIF

	IF nOffset > 0
		nOffSet -= 1
	ENDIF

	count := 0
	IF nOffSet < (DWORD) c:Length
		DO WHILE ( pos := c:IndexOf(cSrc, (INT)nOffSet, StringComparison.Ordinal) ) >= 0
			count++
			nOffSet := (DWORD)pos + cSrc:Length
		ENDDO
	ENDIF

	RETURN count

///
/// Convert a string of ANSI characters to OEM characters.
///
/// <param name="cSource">String in Ansi format</param>
/// <returns>String converted to Unicode
/// </returns>
/// <remarks>This is a compatibility function. Do not use this unless you really have to.
/// X# is a Unicode app and conversions from Unicode - Ansi - Oem - Unicode will take place
/// if you use this function.

/// You should also realize that Ansi2Oem(Oem2Ansi(cSource)) will not always return cSource. Some characters may
/// not be available in the OEM codepage and could be translated to other characters.
/// For example: Windows codepage 1252 has Capital E Umlaut on position 203.
/// When translated to OEM codepage 437 this will become captial E without umlaut (69). Converting back to Ansi
/// this will remain a E without umlaut.
/// </remarks>
FUNCTION Ansi2Oem(cSource AS STRING) AS STRING
	LOCAL aBytes AS BYTE[]
	LOCAL iLen	 AS INT
	iLen := cSource:Length
	aBytes := String2Bytes(cSource)
	aBytes := Ansi2Oem(aBytes, iLen)
	RETURN Bytes2String(aBytes, iLen)

///
/// Convert an array of bytes from ANSI to OEM.
///
FUNCTION Ansi2Oem(bSource AS BYTE[]) AS BYTE[]
	RETURN Ansi2Oem(bSource, bSource:Length)

///
/// Convert an array of bytes from ANSI to OEM.
///
FUNCTION Ansi2Oem(bSource AS BYTE[], iLen AS INT) AS BYTE[]
	LOCAL bDest AS BYTE[]
	bDest := BYTE[]{iLen}
	CharToOemBuffA(bSource, bDest, (DWORD) iLen)
	RETURN bDest

///
/// Convert a string of ANSI characters to OEM characters, changing the contents of the original string as well as the returned string.
///
/// <param name="cSource">String in Ansi format</param>
/// <returns>String converted to Unicode
/// </returns>
/// <remarks>This is a compatibility function. Do not use this unless you really have to.
/// X# is a Unicode app and conversions from Unicode - Oem - Ansi - Unicode will take place
/// if you use this function.

/// You should also realize that Ansi2Oem(Oem2Ansi(cSource)) will not always return cSource. Some characters may
/// not be available in the OEM codepage and could be translated to other characters.
/// For example: Windows codepage 1252 has Capital E Umlaut on position 203.
/// When translated to OEM codepage 437 this will become captial E without umlaut (69). Converting back to Ansi
/// this will remain a E without umlaut.
/// </remarks>

FUNCTION Ansi2OemA(cSource REF STRING) AS STRING
	LOCAL aBytes AS BYTE[]
	LOCAL iLen	 AS INT
	iLen := cSource:Length
	aBytes := String2Bytes(cSource)
	aBytes := Ansi2Oem(aBytes, iLen)
	cSource := Bytes2String(aBytes, iLen)
	RETURN cSource

///
/// Convert a string of OEM characters to ANSI characters.
///
/// <param name="cSource">String in OEM format</param>
/// <returns>String converted to Ansi</returns>
/// <remarks>This is a compatibility function. Do not use this unless you really have to.
/// X# is a Unicode app and conversions from Unicode - Oem - Ansi - Unicode will take place
/// if you use this function.

/// You should also realize that Ansi2Oem(Oem2Ansi(cSource)) will not always return cSource. Some characters may
/// not be available in the OEM codepage and could be translated to other characters.
/// For example: Windows codepage 1252 has Capital E Umlaut on position 203.
/// When translated to OEM codepage 437 this will become captial E without umlaut (69). Converting back to Ansi
/// this will remain a E without umlaut.
/// </remarks>
FUNCTION Oem2Ansi(cSource AS STRING) AS STRING
	LOCAL aBytes AS BYTE[]
	LOCAL iLen	 AS INT
	iLen := cSource:Length
	aBytes := String2Bytes(cSource)
	aBytes := Oem2Ansi(aBytes, iLen)
	RETURN Bytes2String(aBytes, iLen)

///
/// Convert an array of bytes from OEM to ANSI .
///
FUNCTION Oem2Ansi(bSource AS BYTE[]) AS BYTE[]
	RETURN Oem2Ansi(bSource, bSource:Length)

///
/// Convert an array of bytes from OEM to ANSI .
///
FUNCTION Oem2AnsiA(bSource AS BYTE[]) AS VOID
	LOCAL bDest AS BYTE[]
	bDest := Oem2Ansi(bSource, bSource:Length)
	FOR LOCAL n := 1 AS INT UPTO bSource:Length
		bSource[n] := bDest[n]
	NEXT

///
/// Convert an array of bytes from OEM to ANSI .
///
FUNCTION Oem2Ansi(bSource AS BYTE[], iLen AS INT) AS BYTE[]
	LOCAL bDest AS BYTE[]
	bDest := BYTE[]{iLen}
	OemToCharBuffA(bSource, bDest, (DWORD) iLen)
	RETURN bDest

///
/// Convert a specified number of OEM characters in a source buffer to a buffer of corresponding, if any, ANSI characters.
///
/// <param name="pszDest"></param>
/// <param name="pszSource"></param>
/// <param name="dwCount"></param>
/// <returns>
/// </returns>
FUNCTION Oem2AnsiBuff(bDest AS BYTE[],bSource AS BYTE[],dwCount AS DWORD) AS BYTE[]
	OemToCharBuffA(bSource, bDest, dwCount)
	RETURN bDest

///
/// Convert a specified number of ANSI characters in a source buffer to a buffer of corresponding OEM characters.
///
/// <param name="pszDest"></param>
/// <param name="pszSource"></param>
/// <param name="dwCount"></param>
/// <returns>
/// </returns>
FUNCTION Ansi2OemBuff(bDest AS BYTE[],bSource AS BYTE[],dwCount AS DWORD) AS BYTE[]
	CharToOemBuffA(bSource, bDest, dwCount)
	RETURN bDest

INTERNAL _DLL FUNCTION CharToOemBuffA( lpszSrc AS BYTE[], lpszDst AS BYTE[], cchDstLength AS DWORD ) AS LOGIC PASCAL:USER32.CharToOemBuffA
INTERNAL _DLL FUNCTION OemToCharBuffA( lpszSrc AS BYTE[], lpszDst AS BYTE[], cchDstLength AS DWORD ) AS LOGIC PASCAL:USER32.OemToCharBuffA

///
/// Convert a string of OEM characters to ANSI characters, changing the contents of the argument as well as the return value.
///
/// <param name="cSource">String in OEM format</param>
/// <returns>String converted to Ansi
/// </returns>
/// <remarks>This is a compatibility function. Do not use this unless you really have to.
/// X# is a Unicode app and conversions from Unicode - Oem - Ansi - Unicode will take place
/// if you use this function.

/// You should also realize that Ansi2Oem(Oem2Ansi(cSource)) will not always return cSource. Some characters may
/// not be available in the OEM codepage and could be translated to other characters.
/// For example: Windows codepage 1252 has Capital E Umlaut on position 203.
/// When translated to OEM codepage 437 this will become captial E without umlaut (69). Converting back to Ansi
/// this will remain a E without umlaut.
/// </remarks>
FUNCTION Oem2AnsiA(cSource REF STRING) AS STRING
	LOCAL aBytes AS BYTE[]
	LOCAL iLen	 AS INT
	iLen := cSource:Length
	aBytes := String2Bytes(cSource)
	aBytes := Oem2Ansi(aBytes, iLen)
	cSource := Bytes2String(aBytes, iLen)
	RETURN cSource

///
/// Change the first character of each word to uppercase
///
/// <param name="c">The string to be converted.</param>
/// <returns>
/// The converted string according to the CurrentCulture
/// </returns>
FUNCTION Proper(cString AS STRING) AS STRING
	LOCAL sb AS StringBuilder
	LOCAL inside AS LOGIC
	IF cString != NULL
		sb := StringBuilder{cString:Length}
		inside := FALSE
		FOREACH ch AS CHAR IN cString
			VAR cToAdd := ch
			IF inside
				IF Char.IsLetterOrDigit(ch)
					cToAdd := char.ToLower(ch)
				ELSE
					inside := FALSE
				ENDIF
			ELSE
				IF Char.IsLetterOrDigit(ch)
					cToAdd := char.ToUpper(ch)
					inside := TRUE
				ENDIF
			ENDIF
			sb:append(cToAdd)
		NEXT
		cString := sb:ToString()
	ENDIF
	RETURN cString

///
/// Capitalize a proper name correctly, changing the contents of the argument as well as the return value.
///
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION ProperA(c REF STRING) AS STRING
	c := Proper(c)
	RETURN c

///
///
/// <param name="cIn"></param>
/// <returns>
/// </returns>
FUNCTION QPEncString(cIn AS STRING) AS STRING
	THROW NotImplementedException{}
	RETURN String.Empty

///
/// Return the position of the last occurrence of a substring within a string.
///
/// <param name="cSearch">THe string to be searched.</param>
/// <param name="c">The string to be searched in.</param>
/// <returns>
/// The right most position of the string to be searched inside the searched string.
/// </returns>
FUNCTION RAt(cSearch AS STRING,c AS STRING) AS DWORD
	LOCAL rightMost := 0 AS DWORD
	IF cSearch != NULL .AND. c != NULL
		IF c:Length != 0 .and. cSearch:Length != 0
			rightMost:= (DWORD) c:LastIndexOf(cSearch, StringComparison.Ordinal) + 1
		ENDIF
	ENDIF
	RETURN rightMost

///
/// Return the position of the last occurrence of a substring within a string.
///
/// <param name="cSearch">THe string to be searched.</param>
/// <param name="c">The string to be searched in.</param>
/// <returns>
/// The right most position of the string to be searched inside the searched string.
/// </returns>
FUNCTION RAt2(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN RAt(cSearch,c)

///
/// Return the position of the last occurrence of a substring within a string.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <param name="dwOff"></param>
/// <returns>
/// </returns>

FUNCTION RAt3(cSearch AS STRING,c AS STRING,dwOffSet AS DWORD) AS DWORD
	LOCAL nResult := 0 AS DWORD
	IF cSearch != NULL .AND. c != NULL
		IF c:Length != 0 .and. cSearch:Length != 0
			IF dwOffSet > (DWORD) c:Length
				dwOffSet := 0U
			ENDIF
			VAR cTemp := c:Substring((INT) dwOffSet)
			nResult := RAt(cSearch, cTemp)
			IF nResult > 0U
				nResult := nResult+dwOffSet
			ENDIF
		ENDIF
	ENDIF
	RETURN nResult

///
/// Return the line number of the last occurrence of a substring within a multiline string.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION RAtLine(cSearch AS STRING, c AS STRING) AS DWORD
	LOCAL nPos AS DWORD
	IF cSearch == NULL .OR. c == NULL .or. cSearch:Length == 0 .or. c:Length == 0
		RETURN 0
	ENDIF
	nPos := RAt(cSearch,c)
	c := Left(c,nPos-1)

	RETURN MemLines(c)

///
/// Return the line number of the last occurrence of a substring within a multiline string.
///
/// <param name="cSearch"></param>
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION RATLine2(cSearch AS STRING,c AS STRING) AS DWORD
	RETURN RAtLine(cSearch, c)

///
/// Repeat a string a specified number of times.
///
/// <param name="c">The string to be repeated.</param>
/// <param name="dwCount">The number of replications.</param>
/// <returns>
/// A string which consist of dwCount replications of c.
/// </returns>
FUNCTION Repl(c AS STRING,dwCount AS DWORD) AS STRING
	RETURN Replicate(c, dwCount)
///
/// Repeat a string a specified number of times.
///
/// <param name="c">The string to be repeated.</param>
/// <param name="dwCount">The number of replications.</param>
/// <returns>
/// A string which consist of dwCount replications of c.
/// </returns>
FUNCTION Replicate(c AS STRING,dwCount AS DWORD) AS STRING
	LOCAL cReturn := "" AS STRING
	IF dwCount > 0 .AND. c != NULL
		cReturn := System.Text.StringBuilder{}:Insert( 0, c, (INT) dwCount ):ToString()
	ENDIF
	RETURN cReturn

///
/// Return a substring beginning with the rightmost character.
///
/// <param name="c">The string to extract the rightmost characters from.</param>
/// <param name="dwLen">The length of the string to extract.</param>
/// <returns>
/// Returns the right most part in the given length.
/// </returns>
FUNCTION Right(c AS STRING,dwLen AS DWORD) AS STRING
	IF c != NULL
		IF dwLen > c:Length
			NOP
		ELSE
			c := c:Substring( c:Length - (INT) dwLen, (INT) dwLen )
		ENDIF
	ENDIF
	RETURN c

///
/// Remove trailing spaces from a string.
///
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION RTrim(c AS STRING) AS STRING
	IF ( c == NULL )
		RETURN c
	ENDIF
	RETURN c:TrimEnd(trimChars)

///
/// Create new character variable with the same characters as the original string.
///
/// <param name="c">The string be cloned.</param>
/// <returns>
/// A opy of the input string.
/// </returns>
FUNCTION SClone(c AS STRING) AS STRING
	LOCAL clonedString := NULL AS STRING
	IF (c != NULL)
		clonedString := String.Copy( c )
	ENDIF
	RETURN clonedString

///
/// Create a string of spaces.
///
/// <param name="dwSize"></param>
/// <returns>
/// </returns>
FUNCTION Space(dwSize AS DWORD) AS STRING
	RETURN STRING{' ',(INT)dwSize}

///
/// Create a string of spaces.
///
/// <param name="dwSize"></param>
/// <returns>
/// </returns>
FUNCTION Space(iSize AS INT) AS STRING
	RETURN STRING{' ',iSize}

///
/// Return the length of a strongly typed string.
///
/// <param name="c">String which length should be calculated.</param>
/// <returns>
/// The length of the string.
/// </returns>
FUNCTION SLen(c AS STRING) AS DWORD
	LOCAL len := 0 AS DWORD
	IF c != NULL
		len := (DWORD) c:Length
	ENDIF
	RETURN len

///
/// Convert a string to Soundex form.
///
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION SoundEx(c AS STRING) AS STRING
	LOCAL sb		AS StringBuilder
	LOCAL iLen		AS INT
	LOCAL ret		AS STRING
	LOCAL i			AS INT
	LOCAL cLastChar AS CHAR
	LOCAL cSoundExChar AS CHAR

	IF String.IsNullOrEmpty(c)
		RETURN "0000"
	END IF
	cLastChar := (CHAR) 0
	c	 := c:ToUpper()
	iLen := c:Length - 1
	sb := StringBuilder{}
	sb:Append( c[0] )
	FOR i := 1 TO iLen
		cSoundExChar := _SoundExChar( c[i] )
		IF cSoundExChar != '0' .AND. cSoundExChar != cLastChar
			sb:Append( cSoundExChar )
		ENDIF
		cLastChar := cSoundExChar
		IF sb:Length == 4
			EXIT
		ENDIF
	NEXT
	ret := sb:ToString()

	RETURN ret:PadRight( 4, '0' )

INTERNAL FUNCTION _SoundExChar( c AS CHAR ) AS CHAR
	LOCAL ret AS CHAR
	ret := (CHAR) 0
	SWITCH c
		CASE c'A' ;	CASE c'E'; CASE c'I'; CASE c'O'; CASE c'U'; CASE c'Y'
			ret := c'0'
		CASE c'H' ;	CASE c'W'
			ret := c'0'
		CASE c'B' ;	CASE c'F';CASE c'P' ;CASE c'V'
			ret := c'1'
		CASE c'C' ;	CASE c'G';CASE c'J' ;CASE c'K';CASE c'Q';CASE c'S';CASE c'X';CASE c'Z'
			ret := c'2'
		CASE c'D' ;	CASE c'T'
			ret := c'3'
		CASE c'L'
			ret := c'4'
		CASE c'M' ;	CASE c'N'
			ret := c'5'
		CASE c'R'
			ret := '6'
		OTHERWISE
			ret := '0'
	END SWITCH

	RETURN ret

/// This function is not implemented yet
//
// Allows text substitution in strings entered at runtime.
//
/// <param name="s"></param>
/// <returns>
/// </returns>
FUNCTION StrEvaluate(s AS STRING) AS STRING
	THROW NotImplementedException{}
	RETURN String.Empty

///
/// Delete and insert characters in a string.
///
/// <param name="c">The string in which the substitution should take place</param>
/// <param name="n">Starting position of the substring to be deleted.</param>
/// <param name="nDel">The number of characters to be deleted.</param>
/// <param name="cIns">The string which should be inserted instead of the deleted characters.</param>
/// <returns>
/// A new string with the substring from the starting position in the given length being subsituted with the insert string.
/// </returns>

FUNCTION Stuff(c AS STRING,nStart AS DWORD,nToDelete AS DWORD,cIns AS STRING) AS STRING
	LOCAL result := cIns AS STRING
	IF c != NULL
		IF string.IsNullOrEmpty(cIns)
			cIns := ""
		ENDIF
		IF nStart > 0
			nStart -= 1
		ENDIF
		LOCAL part1 := c AS STRING
		IF  (INT) nStart < c:Length
			part1 := c:Substring(0,(INT)nStart)
		ENDIF
		LOCAL part2 := "" AS STRING
		VAR iOffSet := (INT) (nStart + nToDelete)
		IF  iOffSet  < c:length
			part2 := c:Substring( iOffSet )
		ENDIF
		result := part1 + cIns + part2
	ENDIF
	RETURN result

/// <exclude />
FUNCTION __SubStr( c AS STRING, nStart AS INT, nLength AS INT ) AS STRING
	IF c != NULL
		IF nStart == 0
			nStart := 1
		ENDIF

		IF nStart < 0
			nStart := c:Length+nStart+1
		ENDIF

		IF nLength < 0
			nLength := c:Length
		ENDIF

		IF nStart <= c:Length .AND. nStart > 0
			nLength := Math.Min( c:Length - nStart + 1, nLength )
			c := c:Substring( nStart - 1, nLength )
		ELSE
			c := ""
		ENDIF
	ENDIF
	RETURN c

///
/// Extract a substring from a string, using strong typing and only two arguments.
///
/// <param name="c">The string to be extracted from.</param>
/// <param name="dwStart">The starting position from which the substring should be extracted.</param>
/// <returns>
/// The extracted substring.
/// </returns>
FUNCTION SubStr2(c AS STRING,dwStart AS DWORD) AS STRING
	IF c != NULL
		c := __SubStr( c, (INT) dwStart, ( c:Length - (INT) dwStart  + 1 ) )
	ENDIF
	RETURN c

///
/// Extract a substring from a string, using strong typing and three required arguments.
///
/// <param name="c">The string to be extracted from.</param>
/// <param name="dwStart">The starting position from which the substring should be extracted.</param>
/// <param name="dwLen">The length of the substring to beextracted</param>
/// <returns>
/// The extracted substring in the given length.
/// </returns>
FUNCTION SubStr3(c AS STRING,dwStart AS DWORD,dwLen AS DWORD) AS STRING
	IF c != NULL
		c := __SubStr( c, (INT) dwStart, (INT) dwLen )
	ENDIF
	RETURN c

///
/// Remove trailing spaces from a string.
///
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION Trim(c AS STRING) AS STRING
	IF ( c == NULL )
		RETURN c
	ENDIF
	RETURN c:TrimEnd(trimChars)

///
/// Convert the lowercase and mixed case characters in a string to uppercase.
///
/// <param name="cSorce"></param>
/// <returns>
/// </returns>
FUNCTION Upper(cSource AS STRING) AS STRING
	IF cSource != NULL
		cSource := cSource:ToUpper()
	ENDIF
	RETURN cSource

///
/// Convert the lowercase and mixed case characters in a string to uppercase, changing the contents of the argument as well as the return value.
///
/// <param name="cSorce"></param>
/// <returns>
/// </returns>
FUNCTION UpperA(cSource REF STRING) AS STRING
	IF cSource != NULL
		cSource := cSource:ToUpper()
	ENDIF
	RETURN cSource

/// This function is not implemented yet
/// <param name="cLine"></param>
/// <param name="hfOut"></param>
/// <returns>
/// </returns>
UNSAFE FUNCTION UUDecodeLine(cLine AS STRING,hfOut AS PTR) AS DWORD
	THROW NotImplementedException{}
RETURN 0

/// This function is not implemented yet
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION UUEncFile(c AS STRING) AS STRING
	THROW NotImplementedException{}
	RETURN String.Empty

/// This function is not implemented yet
/// <param name="c"></param>
/// <returns>
/// </returns>
FUNCTION UUEncLine(c AS STRING) AS STRING
	THROW NotImplementedException{}
	RETURN String.Empty

/// Determine if the leftmost character in a string is alphabetic.
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character is alphabetic.</returns>
FUNCTION IsAlpha(cSource AS STRING) AS LOGIC
	LOCAL ret := FALSE AS LOGIC
	IF ! String.IsNullOrEmpty( cSource )
		ret := Char.IsLetter( cSource, 0 )
	ENDIF
	RETURN ret

/// Determine if the leftmost character in a string is alphanumeric.
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character is either alphabetic or numeric otherwise FALSE.</returns>
FUNCTION IsAlNum(cSource AS STRING) AS LOGIC
	LOCAL ret := FALSE AS LOGIC
	IF ! String.IsNullOrEmpty( cSource )
		ret := Char.IsLetterOrDigit( cSource, 0 )
	ENDIF
	RETURN ret

/// Determine if the leftmost character in a string is alphanumeric..
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character is either alphabetic or numeric otherwise FALSE.</returns>
FUNCTION IsAlphaNum(cSource AS STRING) AS LOGIC
	RETURN IsAlNum(cSource)

/// Determine if the leftmost character in a string is a digit (that is, a numeric digit between 0 and 9).
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character of the string is a number from 0 to 9; otherwise FALSE.</returns>
FUNCTION IsDigit(cSource AS STRING) AS LOGIC
	LOCAL ret := FALSE AS LOGIC
	IF ! String.IsNullOrEmpty( cSource )
		ret := Char.IsDigit(cSource, 0 )
	ENDIF
	RETURN ret

/// Determine if the leftmost character in a string is a binary digit  (0 or 1)).
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character of the string is 0 or 1 otherwise FALSE.</returns>
FUNCTION IsBDigit(cSource AS STRING) AS LOGIC
	LOCAL ret := FALSE AS LOGIC
	IF ! String.IsNullOrEmpty( cSource )
		SWITCH cSource[0]
			CASE '0'
			CASE '1'
				ret := TRUE
		END SWITCH
	ENDIF
	RETURN ret

/// Determine if the leftmost character in a string is a hex character (that is, digits from 1 through 9 and letters from A through F).
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character is hex otherwise FALSE.</returns>
FUNCTION IsXDigit(cSource AS STRING) AS LOGIC
	LOCAL ret := FALSE AS LOGIC
	IF ! String.IsNullOrEmpty( cSource )
		LOCAL c AS CHAR
		c := cSource[0]
		IF char.IsDigit(c) .OR. ;
			(c >= 'A' .AND. c <= 'F') .OR. ;
			(c >= 'a' .AND. c <= 'f')
			ret := TRUE
		ENDIF
	ENDIF
	RETURN ret

/// Determine if the leftmost character in a string is a blank (that is, Chr(9) through Chr(13) or Chr(32)).
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character of the string blank otherwise FALSE.</returns>
FUNCTION IsSpace(cSource AS STRING) AS LOGIC
	LOCAL ret := FALSE AS LOGIC
	IF ! String.IsNullOrEmpty( cSource )
		ret := char.IsWhiteSpace(cSource,0)
	ENDIF
	RETURN ret

/// Determine if the leftmost character in a string is uppercase.
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character is an uppercase letter otherwise, FALSE.</returns>
FUNCTION IsUpper(cSource AS STRING) AS LOGIC
	LOCAL ret := FALSE AS LOGIC
	IF ! String.IsNullOrEmpty( cSource )
		ret := Char.IsUpper(cSource, 0 )
	ENDIF
	RETURN ret

/// Determine if the leftmost character in a string is lower.
/// <param name="cSource">The string to examine.</param>
/// <returns>TRUE if the first character is a lowercase letter otherwise, FALSE.</returns>
FUNCTION IsLower(cSource AS STRING) AS LOGIC
	LOCAL ret := FALSE AS LOGIC
	IF ! String.IsNullOrEmpty( cSource )
		ret := Char.IsLower(cSource, 0 )
	ENDIF
	RETURN ret


codegen/QtQuickWidgets: updated

codegen/QtQuickWidgets: updated

Modified:
codegen/QtQuickWidgets/QQuickWidget.prg

%%
%% Qt5xHb - Bindings libraries for Harbour/xHarbour and Qt Framework 5
%%
%% Copyright (C) 2018 Marcos Antonio Gambeta <marcosgambeta AT outlook DOT com>
%%

$header

#include "hbclass.ch"

$addRequests

$beginClassFrom=QWidget

   METHOD new
   METHOD delete
   METHOD source
   METHOD setSource
   METHOD engine
   METHOD rootContext
   METHOD rootObject
   METHOD resizeMode
   METHOD setResizeMode
   METHOD status
   METHOD errors
   METHOD sizeHint
   METHOD initialSize
   METHOD setContent
   METHOD format
   METHOD setFormat

$addSignals

$endClass

$destructor

#pragma BEGINDUMP

$includes=5,3,0

#include <QQmlError>
#include <QQmlEngine>
#include <QQmlContext>
#include <QQuickItem>

$prototype=explicit QQuickWidget(QWidget *parent = 0)
$internalConstructor=5,3,0|new1|QWidget *=0

$prototype=QQuickWidget(QQmlEngine* engine, QWidget *parent)
$internalConstructor=5,3,0|new2|QQmlEngine *,QWidget *

$prototype=QQuickWidget(const QUrl &source, QWidget *parent = 0)
$internalConstructor=5,3,0|new3|const QUrl &,QWidget *=0

//[1]explicit QQuickWidget(QWidget *parent = 0)
//[2]QQuickWidget(QQmlEngine* engine, QWidget *parent)
//[3]QQuickWidget(const QUrl &source, QWidget *parent = 0)

HB_FUNC_STATIC( QQUICKWIDGET_NEW )
{
#if (QT_VERSION >= QT_VERSION_CHECK(5,3,0))
  if( ISBETWEEN(0,1) && ISOPTQWIDGET(1) )
  {
    QQuickWidget_new1();
  }
  else if( ISNUMPAR(2) && ISQQMLENGINE(1) && ISQWIDGET(2) )
  {
    QQuickWidget_new2();
  }
  else if( ISBETWEEN(1,2) && ISQURL(1) && ISOPTQWIDGET(2) )
  {
    QQuickWidget_new3();
  }
  else
  {
    hb_errRT_BASE( EG_ARG, 3012, NULL, HB_ERR_FUNCNAME, HB_ERR_ARGS_BASEPARAMS );
  }
#endif
}

$deleteMethod=5,3,0

$prototype=QUrl source() const
$method=5,3,0|QUrl|source|

$prototype=void setSource(const QUrl&)
$method=5,3,0|void|setSource|const QUrl &

$prototype=QQmlEngine* engine() const
$method=5,3,0|QQmlEngine *|engine|

$prototype=QQmlContext* rootContext() const
$method=5,3,0|QQmlContext *|rootContext|

$prototype=QQuickItem *rootObject() const
$method=5,3,0|QQuickItem *|rootObject|

$prototype=ResizeMode resizeMode() const
$method=5,3,0|QQuickWidget::ResizeMode|resizeMode|

$prototype=void setResizeMode(ResizeMode)
$method=5,3,0|void|setResizeMode|QQuickWidget::ResizeMode

$prototype=Status status() const
$method=5,3,0|QQuickWidget::Status|status|

$prototype=QList<QQmlError> errors() const
$method=5,3,0|QList<QQmlError>|errors|

$prototype=QSize sizeHint() const
$method=5,3,0|QSize|sizeHint|

$prototype=QSize initialSize() const
$method=5,3,0|QSize|initialSize|

$prototype=void setContent(const QUrl& url, QQmlComponent *component, QObject *item)
$method=5,3,0|void|setContent|const QUrl &,QQmlComponent *,QObject *

$prototype=QSurfaceFormat format() const
$method=5,3,0|QSurfaceFormat|format|

$prototype=void setFormat(const QSurfaceFormat &format)
$method=5,3,0|void|setFormat|const QSurfaceFormat &

$beginSignals
$signal=5,3,0|statusChanged(QQuickWidget::Status)
$signal=5,3,0|sceneGraphError(QQuickWindow::SceneGraphError,QString)
$endSignals

#pragma ENDDUMP

%%
%% Qt5xHb - Bindings libraries for Harbour/xHarbour and Qt Framework 5
%%
%% Copyright (C) 2018 Marcos Antonio Gambeta <marcosgambeta AT outlook DOT com>
%%

$header

#include "hbclass.ch"

$addRequests

$beginClassFrom=QWidget

   METHOD new
   METHOD delete
   METHOD source
   METHOD setSource
   METHOD engine
   METHOD rootContext
   METHOD rootObject
   METHOD resizeMode
   METHOD setResizeMode
   METHOD status
   METHOD errors
   METHOD sizeHint
   METHOD initialSize
   METHOD setContent
   METHOD format
   METHOD setFormat

   METHOD onStatusChanged
   METHOD onSceneGraphError

$endClass

$destructor

#pragma BEGINDUMP

$includes=5,3,0

#include <QQmlError>
#include <QQmlEngine>
#include <QQmlContext>
#include <QQuickItem>

$prototype=explicit QQuickWidget(QWidget *parent = 0)
$internalConstructor=5,3,0|new1|QWidget *=0

$prototype=QQuickWidget(QQmlEngine* engine, QWidget *parent)
$internalConstructor=5,3,0|new2|QQmlEngine *,QWidget *

$prototype=QQuickWidget(const QUrl &source, QWidget *parent = 0)
$internalConstructor=5,3,0|new3|const QUrl &,QWidget *=0

//[1]explicit QQuickWidget(QWidget *parent = 0)
//[2]QQuickWidget(QQmlEngine* engine, QWidget *parent)
//[3]QQuickWidget(const QUrl &source, QWidget *parent = 0)

HB_FUNC_STATIC( QQUICKWIDGET_NEW )
{
#if (QT_VERSION >= QT_VERSION_CHECK(5,3,0))
  if( ISBETWEEN(0,1) && ISOPTQWIDGET(1) )
  {
    QQuickWidget_new1();
  }
  else if( ISNUMPAR(2) && ISQQMLENGINE(1) && ISQWIDGET(2) )
  {
    QQuickWidget_new2();
  }
  else if( ISBETWEEN(1,2) && ISQURL(1) && ISOPTQWIDGET(2) )
  {
    QQuickWidget_new3();
  }
  else
  {
    hb_errRT_BASE( EG_ARG, 3012, NULL, HB_ERR_FUNCNAME, HB_ERR_ARGS_BASEPARAMS );
  }
#endif
}

$deleteMethod=5,3,0

$prototype=QUrl source() const
$method=5,3,0|QUrl|source|

$prototype=void setSource(const QUrl&)
$method=5,3,0|void|setSource|const QUrl &

$prototype=QQmlEngine* engine() const
$method=5,3,0|QQmlEngine *|engine|

$prototype=QQmlContext* rootContext() const
$method=5,3,0|QQmlContext *|rootContext|

$prototype=QQuickItem *rootObject() const
$method=5,3,0|QQuickItem *|rootObject|

$prototype=ResizeMode resizeMode() const
$method=5,3,0|QQuickWidget::ResizeMode|resizeMode|

$prototype=void setResizeMode(ResizeMode)
$method=5,3,0|void|setResizeMode|QQuickWidget::ResizeMode

$prototype=Status status() const
$method=5,3,0|QQuickWidget::Status|status|

$prototype=QList<QQmlError> errors() const
$method=5,3,0|QList<QQmlError>|errors|

$prototype=QSize sizeHint() const
$method=5,3,0|QSize|sizeHint|

$prototype=QSize initialSize() const
$method=5,3,0|QSize|initialSize|

$prototype=void setContent(const QUrl& url, QQmlComponent *component, QObject *item)
$method=5,3,0|void|setContent|const QUrl &,QQmlComponent *,QObject *

$prototype=QSurfaceFormat format() const
$method=5,3,0|QSurfaceFormat|format|

$prototype=void setFormat(const QSurfaceFormat &format)
$method=5,3,0|void|setFormat|const QSurfaceFormat &

$beginSignals
$signal=5,3,0|statusChanged(QQuickWidget::Status)
$signal=5,3,0|sceneGraphError(QQuickWindow::SceneGraphError,QString)
$endSignals

#pragma ENDDUMP


theta is now implemented, but I still not really understand the type of AppW :-/

-- item notation

-- basic: http://www.cedar-forest.org/forest/talks/talks2001/talkPPDP2001.ps
-- extended: http://www.cedar-forest.org/forest/talks/talks2001/microsoft.ps

import "../src/LangPrelude.prg" (maybeM)
import "LabeledDeBruijn.prg"

data Itm :: Inventory Tag ~> * where
  -- applicator wagon
  IApp :: Itm inv -> Itm inv -> Itm inv
  -- abstractor wagon
  IAbs :: Label n -> Itm [inv; n]i -> Itm inv
  -- argument access
  IArg :: Itm [ups; t]i
  IUp :: Itm [ups; t]i -> Itm [ups; t, t']i
 deriving syntax (it) Nat(IArg, IUp)

iota :: Tm inv -> Itm inv
iota Arg = IArg
iota (Up m) = IUp (iota m)
iota (Lm n b) = IAbs n (iota b)
iota (App f a) = IApp (iota a) (iota f)

slide1 = iota (Lm `x (Lm `y (1tm,0tm)tm), 0tm)tm  -- assuming `z = 0tm

-- ok now, let's drift to thrists

data Wagon :: Inventory Tag ~> Inventory Tag ~> * where
  AppW :: Thrist Wagon inv inv'' -> Wagon inv' inv
  AbsW :: Label n -> Wagon inv [inv; n]i
  ArgW :: Wagon [ups; t]i [ups; t]i
  UpW :: Wagon [ups; t]i [ups; t]i -> Wagon [ups; t, t']i [ups; t, t']i
 deriving syntax (w) Nat(ArgW, UpW) Item(AppW)

data Thrist :: forall (l :: *1) . (l ~> l ~> *)  ~> l ~> l ~> * where
  Nil :: Thrist k a a
  Cons :: k a b -> Thrist k b c -> Thrist k a c
 deriving List(t)

theta :: Tm inv -> exists inv' . Thrist Wagon inv inv'
theta Arg = Ex [ArgW]t
theta (Up m) = case ensureVar m' of -- Note: NOT TOTAL!
               Just Eq -> Ex [UpW  m']t
    where (Ex thr) = theta m
    	  [m']t = thr
          ensureVar :: Wagon i j -> Maybe (Equal i j)
          ensureVar ArgW = Just Eq
          ensureVar (UpW v) = do { Eq <- ensureVar v; return Eq }
          monad maybeM

theta (Lm n b) = Ex [AbsW n; thr]t where (Ex thr) = theta b
theta (App f a) = Ex [(a')w; f']t
    where (Ex a') = theta a
          (Ex f') = theta f

-- item notation

-- basic: http://www.cedar-forest.org/forest/talks/talks2001/talkPPDP2001.ps
-- extended: http://www.cedar-forest.org/forest/talks/talks2001/microsoft.ps

import "../src/LangPrelude.prg" (maybeM)
import "LabeledDeBruijn.prg"

data Itm :: Inventory Tag ~> * where
  -- applicator wagon
  IApp :: Itm inv -> Itm inv -> Itm inv
  -- abstractor wagon
  IAbs :: Label n -> Itm [inv; n]i -> Itm inv
  -- argument access
  IArg :: Itm [ups; t]i
  IUp :: Itm [ups; t]i -> Itm [ups; t, t']i
 deriving syntax (it) Nat(IArg, IUp)

iota :: Tm inv -> Itm inv
iota Arg = IArg
iota (Up m) = IUp (iota m)
iota (Lm n b) = IAbs n (iota b)
iota (App f a) = IApp (iota a) (iota f)

slide1 = iota (Lm `x (Lm `y (1tm,0tm)tm), 0tm)tm  -- assuming `z = 0tm

-- ok now, let's drift to thrists

data Wagon :: Inventory Tag ~> Inventory Tag ~> * where
  AppW :: Wagon inv inv -> Wagon inv inv
  AbsW :: Label n -> Wagon [inv; n]i inv
  ArgW :: Wagon [ups; t]i [ups; t]i
  UpW :: Wagon [ups; t]i [ups; t]i -> Wagon [ups; t, t']i [ups; t, t']i
 deriving syntax (w) Nat(ArgW, UpW) Item(AppW)

data Thrist :: forall (l :: *1) . (l ~> l ~> *)  ~> l ~> l ~> * where
  Nil :: Thrist k a a
  Cons :: k a b -> Thrist k b c -> Thrist k a c
 deriving List(t)

theta :: Tm inv -> exists inv' . Thrist Wagon inv inv'
theta Arg = Ex [ArgW]t
theta (Up m) = case ensureVar m' of
               Just Eq -> Ex [UpW  m']t
    where (Ex thr) = theta m
    	  [m']t = thr
          ensureVar :: Wagon i j -> Maybe (Equal i j)
          ensureVar ArgW = Just Eq
          ensureVar (UpW v) = do { Eq <- ensureVar v; return Eq }
          monad maybeM

{-
theta (Lm n b) = AbsW n (theta b)
theta (App f a) = AppW (theta a) (theta f)
-}


start fleshing out theta more renaming action too

-- item notation

-- basic: http://www.cedar-forest.org/forest/talks/talks2001/talkPPDP2001.ps
-- extended: http://www.cedar-forest.org/forest/talks/talks2001/microsoft.ps

import "LabeledDeBruijn.prg"

data Itm :: Inventory Tag ~> * where
  -- applicator wagon
  IApp :: Itm inv -> Itm inv -> Itm inv
  -- abstractor wagon
  IAbs :: Label n -> Itm [inv; n]i -> Itm inv
  -- argument access
  IArg :: Itm [ups; t]i
  IUp :: Itm [ups; t]i -> Itm [ups; t, t']i
 deriving syntax (it) Nat(IArg, IUp)

iota :: Tm inv -> Itm inv
iota Arg = IArg
iota (Up m) = IUp (iota m)
iota (Lm n b) = IAbs n (iota b)
iota (App f a) = IApp (iota a) (iota f)

slide1 = iota (Lm `x (Lm `y (1tm,0tm)tm), 0tm)tm  -- assuming `z = 0tm

-- ok now, lets drift to thrists

data Wagon :: Inventory Tag ~> Inventory Tag ~> * where
  AppW :: Wagon inv inv -> Wagon inv inv
  AbsW :: Label n -> Wagon [inv; n]i inv
  ArgW :: Wagon [ups; t]i [ups; t]i
  UpW :: Wagon [ups; t]i [ups; t]i -> Wagon [ups; t, t']i [ups; t, t']i
 deriving syntax (w) Nat(ArgW, UpW) Item(AppW)

data Thrist :: forall (l :: *1) . (l ~> l ~> *)  ~> l ~> l ~> * where
  Nil :: Thrist k a a
  Cons :: k a b -> Thrist k b c -> Thrist k a c
 deriving List(t)

theta :: Tm inv -> exists inv' . Thrist Wagon inv inv'
theta Arg = Ex [ArgW]t
{-
theta (Up m) = Ex [UpW  m']t
    where (Ex thr) = theta m
    	  [m']t = thr
theta (Lm n b) = AbsW n (theta b)
theta (App f a) = AppW (theta a) (theta f)
-}

-- item notation

-- basic: http://www.cedar-forest.org/forest/talks/talks2001/talkPPDP2001.ps
-- extended: http://www.cedar-forest.org/forest/talks/talks2001/microsoft.ps

import "LabeledDeBruijn.prg"

data Itm :: Inventory Tag ~> * where
  -- applicator wagon
  AppW :: Itm inv -> Itm inv -> Itm inv
  -- abstractor wagon
  AbsW :: Label n -> Itm [inv; n]i -> Itm inv
  -- argument access
  IArg :: Itm [ups; t]i
  IUp :: Itm [ups; t]i -> Itm [ups; t, t']i
 deriving syntax (it) Nat(IArg, IUp)

iota :: Tm inv -> Itm inv
iota Arg = IArg
iota (Up m) = IUp (iota m)
iota (Up m) = IUp (iota m)
iota (Lm n b) = AbsW n (iota b)
iota (App f a) = AppW (iota a) (iota f)

slide1 = iota (Lm `x (Lm `y (1tm,0tm)tm), 0tm)tm  -- assuming `z = 0tm

-- ok now, lets drift to thrists

data Wagon :: Inventory Tag ~> Inventory Tag ~> * where
  AppI :: Wagon inv inv -> Wagon inv inv
  AbsI :: Label n -> Wagon [inv; n]i inv
  ArgI :: Wagon [ups; t]i [ups; t]i
  UpI :: Wagon [ups; t]i [ups; t]i -> Wagon [ups; t, t']i [ups; t, t']i
 deriving syntax (w) Nat(ArgI, UpI) Item(AppI)

data Thrist :: forall (l :: *1) . (l ~> l ~> *)  ~> l ~> l ~> * where
  Nil :: Thrist k a a
  Cons :: k a b -> Thrist k b c -> Thrist k a c
 deriving List(l)