using System;
using Microsoft.SPOT;
namespace ElzeKool.Utilities
{
///
/// Formats a number with the format defined in Format.
///
/// The function follows the C# Custom Formatting defined at the MSDN document below:
/// http://msdn.microsoft.com/en-us/library/0c899ak8.aspx
///
/// The Per mille symbol isn't supported as this is not included in the UTF8 character set.
///
/// Besides the custom formatting strings the function also supports the X,x,D,d numeric formatting specifiers
///
/// Formatting for number
/// Number to format
/// Formatted number
static class NumberFormatter
{
#region Basic culture settings
private const int CULTURE_GROUPSIZE = 3;
private const char CULTURE_GROUPSEPERATOR = ',';
private const char CULTURE_DECIMAL_POINT = '.';
#endregion
#region Internal Helper functions
// Works like IndexOf but checks if the character is escaped
private static int _IndexOfEscaped(String S, Char C) { return _IndexOfEscaped(S, C, 0); }
private static int _IndexOfEscaped(String S, Char C, int Offset)
{
int pos = -1;
bool escape = false;
for (int x = 0; x < S.Length; x++ )
{
if (escape)
{
escape = false;
}
else if (S[x] == '\\')
{
escape = true;
}
else
{
if ((S[x] == C) & (x >= Offset))
return x;
}
}
return pos;
}
// Works like LastIndexOf but checks if the character is escaped
private static int _LastIndexOfEscaped(String S, Char C) { return _LastIndexOfEscaped(S, C, 0); }
private static int _LastIndexOfEscaped(String S, Char C, int Offset)
{
int pos = -1;
bool escape = false;
for (int x = 0; x < S.Length; x++)
{
if (escape)
{
escape = false;
}
else if (S[x] == '\\')
{
escape = true;
}
else
{
if ((S[x] == C) & (x >= Offset))
pos = x;
}
}
return pos;
}
// Works like _IndexOfEscaped but works on reversed strings where the esacpe
// character is after the character to escape
private static int _ReversedIndexOfEscaped(String S, Char C, int Offset)
{
int pos = -1;
bool escape = false;
Char C_Next = (char) 0;
for (int x = 0; x < S.Length; x++)
{
if (x < S.Length - 1) { C_Next = S[x + 1]; }
if (escape)
{
escape = false;
}
else if ((x < S.Length - 1) & (C_Next == '\\'))
{
escape = true;
}
else
{
if ((S[x] == C) & (x >= Offset))
return x;
}
}
return pos;
}
// Return last digit for integer number
private static char _SingleDigit(int N)
{
N = N % 10;
return (char)(((char)N) + '0');
}
// Reverse String
private static String _ReverseString(String S)
{
String reversedS = "";
foreach (char c in S)
reversedS = c + reversedS;
return reversedS;
}
// Convert a numeric string to an integer
private static int _intval(String S)
{
if (S.Length == 0) return 0;
int r = 0;
foreach (Char c in S)
{
if ("0123456789".IndexOf(c) != -1)
{
r *= 10;
r += (int)(c - '0');
}
}
return r;
}
#endregion
#region The formatting functions
// This routine formats the Fractional part of a number to it's formatting rules
private static String _formatFractionalPart(String Format, int Number)
{
// This will hold the formatted string
String S = "";
// Make number positive
if (Number < 0) Number = -Number;
// By converting the number to a string we can easily find the MSD (Most Significant Digit)
String NumberAsString = Number.ToString();
// Strip it from zero's
for (int x = NumberAsString.Length-1; x >= 0; x--)
{
if (NumberAsString[x] != '0')
{
NumberAsString = NumberAsString.Substring(0, x+1);
break;
}
}
int NumberAsStringPos = 0;
// Check if escaped
bool escape = false;
// Go trough format string
for (int x = 0; x < Format.Length; x++)
{
// If last character was an escape character just output it
if (escape)
{
S += Format[x];
escape = false;
}
// Check for escape character
else if (Format[x] == '\\')
{
escape = true;
}
else
{
// Check what to do based on current character
switch (Format[x])
{
case '0':
if (NumberAsStringPos == NumberAsString.Length)
{
S += "0";
}
else
{
S += NumberAsString[NumberAsStringPos];
NumberAsStringPos++;
}
break;
case '#':
if (NumberAsStringPos != NumberAsString.Length)
{
S += NumberAsString[NumberAsStringPos];
NumberAsStringPos++;
}
break;
default:
S += Format[x];
break;
}
}
}
return S;
}
// This routine formats the Integer part of a number to it's formatting rules
private static String _formatIntegerPart(String Format, int Number)
{
int NumberScalingComma = -1;
// Search for , (Number Scaling Comma)
NumberScalingComma = _LastIndexOfEscaped(Format, ',');
if (NumberScalingComma == (Format.Length - 1))
{
// Divide by thousand and continue formatting there
return _formatIntegerPart(Format.Substring(0, Format.Length - 1), (int)System.Math.Round(Number / 1000F));
}
// Reverse format string as we work right to left
String reversedFormatString = _ReverseString(Format);
// Check if number is negative
bool NumberNegative = (Number < 0);
// Internal operator indicating if we should add a +/- for the string start
bool DisplayNegative = NumberNegative;
bool DisplayPositive = false;
// Make number positive
if (Number < 0) Number = -Number;
// Storage for formatted string
String S = "";
// Check if escaped
bool escape = false;
Char C_Next = (char)0;
// Go trough format string
for (int x = 0; x < reversedFormatString.Length; x++)
{
// Used for escape checking
if (x < reversedFormatString.Length - 1) { C_Next = reversedFormatString[x + 1]; }
// Check if previous character was an escape character
if (escape)
{
// Process next char
escape = false;
}
else if ((x < reversedFormatString.Length - 1) & (C_Next == '\\'))
{
// Add character to output string and say that next character is the escape character
S = reversedFormatString[x] + S;
escape = true;
}
else
{
// Select what to do based on the current character
switch (reversedFormatString[x])
{
case '0': // Display digit else display 0
S = _SingleDigit(Number) + S;
Number /= 10;
break;
case '#': // Display digit if digit not null
if (Number == 0) break;
S = _SingleDigit(Number) + S;
Number /= 10;
break;
case ',': // Comma indicating grouping
break;
case '-':
if (x != reversedFormatString.Length-1) S = reversedFormatString[x] + S;
break;
case '+': // Display + on positive numbers
if (x != reversedFormatString.Length-1)
S = reversedFormatString[x] + S;
else if (DisplayNegative == false) DisplayPositive = true;
break;
default: // Just add the character to the string
S = reversedFormatString[x] + S;
break;
}
// If we are on the integer side of the number check if this is the last hash or null, if so process the number until
// the processing number gets nul
if (((reversedFormatString[x] == '#') | (reversedFormatString[x] == '0')) & ((_ReversedIndexOfEscaped(reversedFormatString, '#', x + 1) == -1)) & (_ReversedIndexOfEscaped(reversedFormatString, '0', x + 1) == -1))
{
while (Number != 0)
{
S = _SingleDigit(Number) + S;
Number /= 10;
}
}
}
}
// Add number group specificier if requested
if (NumberScalingComma != -1)
{
String S_ = "";
int GroupSize = 0;
for (int x = S.Length-1; x >= 0 ; x--)
{
// Add grouping symbol when we got a full block
if (GroupSize == CULTURE_GROUPSIZE)
{
if ((x != 0) & (S[x+1] != '0'))
{
S_ = CULTURE_GROUPSEPERATOR + S_;
GroupSize = 0;
}
else
{
// Check if there are more Significant digits
bool MoreSignificantDigits = false;
foreach (char c in S.Substring(0, x+1))
{
if ("123456789".IndexOf(c) != -1)
{
MoreSignificantDigits = true;
break;
}
}
// Only add comma when there are more significant digits
if (MoreSignificantDigits)
{
S_ = CULTURE_GROUPSEPERATOR + S_;
GroupSize = 0;
}
}
}
// If number increase current group size
if ("0123456789".IndexOf(S[x]) != -1)
GroupSize++;
// Add character/digit to String
S_ = S[x] + S_;
}
// Copy temporary S to real S
S = S_;
}
// return formatted string
if (DisplayNegative)
{
return "-" + S;
}
else if (DisplayPositive)
{
return "+" + S;
}
else
{
return S;
}
}
// This function formats a section based on its format
private static String _formatSection(String Format, Double Number)
{
// Check for Infinity
if (Number == Double.NegativeInfinity)
return "-Infinity";
else if (Number == Double.PositiveInfinity)
return "Infinity";
// String to build formated number
String S = "";
// Check for percent Symbol
int PercentSymbol = _IndexOfEscaped(Format, '%');
if (PercentSymbol != -1)
{
return _formatSection(Format.Substring(0, PercentSymbol), Number * 100) + "%";
}
// Check for Exponent Symbol
int ExponentSymbol = _IndexOfEscaped(Format.ToLower(), 'e');
if (ExponentSymbol != -1)
{
// To be a valid exponent format it must be proceeded by 1 or more zeros
if (_IndexOfEscaped(Format, '0', ExponentSymbol + 1) != -1)
{
int Exponent = 0;
// Number = 1
if (Number == 1F)
{
// 1 has an exponent of 1
Exponent = 1;
}
// Number > 1
else if (Number > 1F)
{
// Numbers above 1 are divided by 10 until it's below 10
while ((Number <= -10F) | (Number >= 10F))
{
Number /= 10F;
Exponent++;
}
}
// Number < 1
else
{
// Numbers below 1 are multiplied by 10 until there above 10
while ((Number > -1F) & (Number < 1F))
{
Number *= 10F;
Exponent--;
}
}
// Return Exponent
return _formatSection(Format.Substring(0, ExponentSymbol), Number) + Format[ExponentSymbol] + _formatIntegerPart(Format.Substring(ExponentSymbol + 1), Exponent);
}
}
if (_IndexOfEscaped(Format, '.') != -1)
{
// Get Integer and Fractional part of number
int IntegerPart = (int)System.Math.Floor(Number);
int FractionalPart = (int)((Number * 1000000F - (Double)IntegerPart * 1000000F));
// Get formatted result for Integer Part
S += _formatIntegerPart(Format.Substring(0, _IndexOfEscaped(Format, '.')), IntegerPart);
// Get formatted result for Fractional
S += CULTURE_DECIMAL_POINT + _formatFractionalPart(Format.Substring(_IndexOfEscaped(Format, '.') + 1), FractionalPart);
}
else
{
// Round and process as integer
return _formatIntegerPart(Format, (int)System.Math.Round(Number));
}
// Return formatted result
return S;
}
// Used to convert 0-15 to 0-F
private static readonly char[] ByteToHex = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
// Convert integer to string
private static String _FormatAsHex(int Number, int Size)
{
String S = "";
bool Negative = (Number < 0);
if (Negative) Number = -Number;
// Build string
while (Number != 0)
{
S = ByteToHex[Number % 16] + S;
Number /= 16;
}
// Apply padding
while (S.Length < Size)
{
S = "0" + S;
}
if (Negative)
return "-" + S;
else
return S;
}
// Convert integer to string
private static String _FormatAsDecimal(int Number, int Size)
{
String S = "";
bool Negative = (Number < 0);
if (Negative) Number = -Number;
// Build string
while (Number != 0)
{
S = ((char)(Number % 10) + '0').ToString();
Number /= 10;
}
// Apply padding
while (S.Length < Size)
{
S = "0" + S;
}
if (Negative)
return "-" + S;
else
return S;
}
#endregion
///
/// Formats a number with the format defined in Format.
///
/// The function follows the C# Custom Formatting defined at the MSDN document below:
/// http://msdn.microsoft.com/en-us/library/0c899ak8.aspx
///
/// The Per mille symbol isn't supported as this is not included in the UTF8 character set.
///
/// Besides the custom formatting strings the function also supports the X,x,D,d numeric formatting specifiers
///
/// Formatting for number
/// Number to format
/// Formatted number
public static String Format(String Format, Double Number)
{
if (Format == null)
throw new NullReferenceException("Format can't be null.");
if (Format == "")
throw new ArgumentException("Specify a format.");
// Check for default formats -> Hex uppercase
if (Format[0] == 'X')
{
if (Format.Length > 1)
return _FormatAsHex((int)System.Math.Round(Number), _intval(Format.Substring(1)));
else
return _FormatAsHex((int)System.Math.Round(Number), 0);
}
// Check for default formats -> Hex lowercase
if (Format[0] == 'x')
{
if (Format.Length > 1)
return _FormatAsHex((int)System.Math.Round(Number), _intval(Format.Substring(1))).ToLower();
else
return _FormatAsHex((int)System.Math.Round(Number), 0).ToLower();
}
// Check for default formats -> Decimal
if ((Format[0] == 'D') | Format[0] == 'd')
{
if (Format.Length > 1)
return _FormatAsDecimal((int)System.Math.Round(Number), _intval(Format.Substring(1)));
else
return _FormatAsDecimal((int)System.Math.Round(Number), 0);
}
// This will hold the found sections
String[] Sections = new String[] { "", "", "" };
// Used to find sections
int SectionCount = 1;
int SectionSearchPos1 = 0;
int SectionSearchPos2 = 0;
SectionSearchPos1 = _IndexOfEscaped(Format, ';');
// Search for first section splitter
if (SectionSearchPos1 != -1)
{
// Section 1
Sections[0] = Format.Substring(0, SectionSearchPos1);
// Search for second section splitter
SectionCount += 1;
SectionSearchPos2 = _IndexOfEscaped(Format, ';', SectionSearchPos1 + 1);
if (SectionSearchPos2 != -1)
{
Sections[1] = Format.Substring(SectionSearchPos1 + 1, SectionSearchPos2 - SectionSearchPos1 - 1);
Sections[2] = Format.Substring(SectionSearchPos2 + 1);
SectionCount = 3;
}
else
{
// Section 2
Sections[1] = Format.Substring(SectionSearchPos1+1);
SectionCount = 2;
}
}
else
{
// Just one section
Sections[0] = Format;
SectionCount = 1;
}
// One section, The format string applies to all values.
if (SectionCount == 1)
{
return _formatSection(Sections[0], Number);
}
// Two Sections, The first section applies to positive values and zeros, and the second section applies to negative values.
else if (SectionCount == 2)
{
// Check for 0/Positive
if (Number >= 0F)
{
// Return formatted string formatted as section 1
return _formatSection(Sections[0], Number);
}
else
{
// If the number to be formatted is negative, but becomes zero after rounding according to the format in the second section, then the resulting zero is formatted according to the first section.
if (((_IndexOfEscaped(Sections[1], '.') == -1) & (_IndexOfEscaped(Sections[1].ToLower(), 'e') == -1)) & (System.Math.Round(Number) == 0F))
{
return _formatSection(Sections[0], 0F);
}
// Section 2 is for negative numbers, but don't display - sign (Make number positive)
return _formatSection(Sections[1], -Number);
}
}
// Three Sections, The first section applies to positive values, the second section applies to negative values, and the third section applies to zeros.
else
{
// Check for Positive
if (Number > 0F)
{
// If the number to be formatted is nonzero, but becomes zero after rounding according to the format in the first or second section, then the resulting zero is formatted according to the third section.
if (((_IndexOfEscaped(Sections[0], '.') == -1) & (_IndexOfEscaped(Sections[0].ToLower(), 'e') == -1)) & (System.Math.Round(Number) == 0F))
{
return _formatSection(Sections[2], 0F);
}
// Return formatted string formatted as section 1
return _formatSection(Sections[0], Number);
}
// Check for Negative
else if (Number < 0F)
{
// The second section can be left empty (by having nothing between the semicolons), in which case the first section applies to all nonzero values.
if (Sections[1] == "")
{
// If the number to be formatted is nonzero, but becomes zero after rounding according to the format in the first or second section, then the resulting zero is formatted according to the third section.
if (((_IndexOfEscaped(Sections[0], '.') == -1) & (_IndexOfEscaped(Sections[0].ToLower(), 'e') == -1)) & (System.Math.Round(Number) == 0F))
{
return _formatSection(Sections[2], 0F);
}
// Return formatted string formatted as section 1
return _formatSection(Sections[0], -Number);
}
else
{
// If the number to be formatted is nonzero, but becomes zero after rounding according to the format in the first or second section, then the resulting zero is formatted according to the third section.
if (((_IndexOfEscaped(Sections[1], '.') == -1) & (_IndexOfEscaped(Sections[1].ToLower(), 'e') == -1)) & (System.Math.Round(Number) == 0F))
{
return _formatSection(Sections[2], 0F);
}
// Return formatted string formatted as section 2
return _formatSection(Sections[1], -Number);
}
}
// Else Zero
{
// Return formatted string formatted as section 3
return _formatSection(Sections[2], Number);
}
}
}
}
}