package flare.util {
import flash.utils.ByteArray;
/**
* Utility methods for working with String instances. The
* format
method provides a powerful mechanism for formatting
* and templating strings.
*/
public class Strings
{
/**
* Constructor, throws an error if called, as this is an abstract class.
*/
public function Strings() {
throw new Error("This is an abstract class.");
}
/**
* Creates a new string by repeating an input string.
* @param s the string to repeat
* @param reps the number of times to repeat the string
* @return a new String containing the repeated input
*/
public static function repeat(s:String, reps:int):String
{
if (reps == 1) return s;
var b:ByteArray = new ByteArray();
for (var i:uint=0; i len) {
return left ? s.substr(0,len) : s.substr(slen-len, len);
} else {
var pad:String = repeat(' ',len-slen);
return left ? s + pad : pad + s;
}
}
/**
* Pads a number with a specified number of "0" digits on
* the left-hand-side of the number.
* @param x an input number
* @param digits the number of "0" digits to pad by
* @return a padded string representation of the input number
*/
public static function pad(x:Number, digits:int):String
{
var neg:Boolean = (x < 0);
x = Math.abs(x);
var e:int = 1 + int(Math.log(x) / Math.LN10);
var s:String = String(x);
for (; e
* this example page or
*
* Microsoft's documentation.
* @param fmt a formatting string. Format strings include markup
* indicating where input arguments should be placed in the string,
* along with optional formatting directives. For example,
* {1}, {0}
writes out the second value argument, a
* comma, and then the first value argument.
* @param args value arguments to be placed within the formatting
* string.
* @return the formatted string.
*/
public static function format(fmt:String, ...args):String
{
var b:ByteArray = new ByteArray(), a:Array;
var esc:Boolean = false, val:*;
var c:Number, idx:int, ialign:int;
var idx0:int, idx1:int, idx2:int;
var s:String, si:String, sa:String, sf:String;
for (var i:uint=0; i idx2 ? null : s.substring(idx1+1, idx2<0?s.length:idx2);
sf = idx2<0 ? null : s.substring(idx2+1);
try {
if (sa != null) { ialign = int(sa); }
if ((idx0=uint(si))==0 && si!="0") {
val = Property.$(si).getValue(args[0]);
} else {
val = args[idx0];
}
pattern(b, sf, val);
} catch (x:*) {
throw new ArgumentError("Invalid format string.");
}
i = idx;
}
} else {
// by default, copy value to buffer
b.writeUTFBytes(fmt.charAt(i));
}
}
b.position = 0;
s = b.readUTFBytes(b.length);
// finally adjust string alignment as needed
return (sa != null ? align(s, ialign) : s);
}
private static function pattern(b:ByteArray, pat:String, value:*):void
{
if (pat == null) {
b.writeUTFBytes(String(value));
} else if (value is Date) {
datePattern(b, pat, value as Date);
} else if (value is Number) {
numberPattern(b, pat, Number(value));
} else {
b.writeUTFBytes(String(value));
}
}
private static function count(s:String, c:Number, i:int):int
{
var n:int = 0;
for (n=0; i= 4) {
b.writeUTFBytes(DAY_NAMES[d.day]);
} else if (n == 3) {
b.writeUTFBytes(DAY_ABBREVS[d.day]);
} else if (n == 2) {
b.writeUTFBytes(pad(d.date, 2));
} else {
b.writeUTFBytes(String(d.date));
}
}
else if (c == _ERA) {
b.writeUTFBytes(d.fullYear<0 ? BC : AD);
}
else if (c == _FRAC) {
a = int(Math.round(Math.pow(10,n) * (d.time/1000 % 1)));
b.writeUTFBytes(String(a));
}
else if (c == _FRAZ) {
a = int(Math.round(Math.pow(10,n) * (d.time/1000 % 1)));
s = String(a);
for (a=s.length; s.charCodeAt(a-1)==_ZERO; --a);
b.writeUTFBytes(s.substring(0,a));
}
else if (c == _HOUR) {
a = (a=(int(d.hours)%12)) == 0 ? 12 : a;
b.writeUTFBytes(n==2 ? pad(a,2) : String(a));
}
else if (c == _HR24) {
a = int(d.hours);
//b.writeUTFBytes(n==2 ? pad(a,2) : String(a));
b.writeUTFBytes( String(a));
}
else if (c == _MINS) {
a = int(d.minutes);
b.writeUTFBytes(n==2 ? pad(a,2) : String(a));
}
else if (c == _MOS) {
if (n >= 4) {
b.writeUTFBytes(MONTH_NAMES[d.month]);
} else if (n == 3) {
b.writeUTFBytes(MONTH_ABBREVS[d.month]);
} else {
a = int(d.month+1);
b.writeUTFBytes(n==2 ? pad(a,2) : String(a));
}
}
else if (c == _SECS) {
a = int(d.seconds);
b.writeUTFBytes(n==2 ? pad(a,2) : String(a));
}
else if (c == _AMPM) {
s = d.hours > 11 ? (n==2 ? PM2 : PM1) : (n==2 ? AM2 : AM1);
b.writeUTFBytes(s);
}
else if (c == _YEAR) {
if (n == 1) {
b.writeUTFBytes(String(int(d.fullYear) % 100));
} else {
a = int(d.fullYear) % int(Math.pow(10,n));
b.writeUTFBytes(pad(a, n));
}
}
else if (c == _ZONE) {
c = int(d.timezoneOffset / 60);
if (c<0) { s='+'; c = -c; } else { s='-'; }
b.writeUTFBytes(s + (n>1 ? pad(c, 2) : String(c)));
if (n >= 3) {
b.position = b.length;
c = int(Math.abs(d.timezoneOffset) % 60);
b.writeUTFBytes(':'+pad(c,2));
}
}
else if (c == _BACKSLASH) {
b.writeUTFBytes(p.charAt(i+1));
n = 2;
}
else if (c == _APOSTROPHE) {
a = p.indexOf('\'',i+1);
b.writeUTFBytes(p.substring(i+1,a));
n = 1 + a - i;
}
else if (c == _QUOTE) {
a = p.indexOf('\"',i+1);
b.writeUTFBytes(p.substring(i+1,a));
n = 1 + a - i;
}
else if (c == _PERC) {
if (n>1) throw new ArgumentError("Invalid date format: "+p);
}
else {
b.writeUTFBytes(p.substr(i,n));
}
i += n;
}
}
// -- Number Formatting -----------------------------------------------
private static const GROUPING:String = ';';
private static const _ZERO:Number = '0'.charCodeAt(0);
private static const _HASH:Number = '#'.charCodeAt(0);
private static const _PERC:Number = '%'.charCodeAt(0);
private static const _DECP:Number = '.'.charCodeAt(0);
private static const _SEPR:Number = ','.charCodeAt(0);
private static const _PLUS:Number = '+'.charCodeAt(0);
private static const _MINUS:Number = '-'.charCodeAt(0);
private static const _e:Number = 'e'.charCodeAt(0);
private static const _E:Number = 'E'.charCodeAt(0);
/** Separator for decimal (fractional) values. */
public static var DECIMAL_SEPARATOR:String = '.';
/** Separator for thousands values. */
public static var THOUSAND_SEPARATOR:String = ',';
/** String representing Not-a-Number (NaN). */
public static var NaN:String = 'NaN';
/** String representing positive infinity. */
public static var POSITIVE_INFINITY:String = "+Inf";
/** String representing negative infinity. */
public static var NEGATIVE_INFINITY:String = "-Inf";
private static const _STD_NUM:Object = {
c: "$#,0.", // currency
d: "0", // integers
e: "0.00e+0", // scientific
f: 0, // fixed-point
g: 0, // general
n: "#,##0.", // number
p: "%", // percent
//r: 0, // round-trip
x: 0 // hexadecimal
};
private static function numberPattern(b:ByteArray, p:String, x:Number):void
{
var idx0:int, idx1:int, s:String = p.charAt(0).toLowerCase();
var upper:Boolean = s.charCodeAt(0) != p.charCodeAt(0);
if (isNaN(x)) {
// handle NaN
b.writeUTFBytes(Strings.NaN);
}
else if (!isFinite(x)) {
// handle infinite values
b.writeUTFBytes(x<0 ? NEGATIVE_INFINITY : POSITIVE_INFINITY);
}
else if (p.length <= 3 && _STD_NUM[s] != null) {
// handle standard formatting string
var digits:Number = p.length==1 ? 2 : int(p.substring(1));
if (s == 'c') {
digits = p.length==1 ? 2 : digits;
if (digits == 0) {
numberPattern(b, _STD_NUM[s], x);
} else {
numberPattern(b, _STD_NUM[s]+"."+repeat("0",digits), x);
}
}
else if (s == 'd') {
b.writeUTFBytes(pad(Math.round(x), digits));
}
else if (s == 'e') {
s = x.toExponential(digits);
s = upper ? s.toUpperCase() : s.toLowerCase();
b.writeUTFBytes(s);
}
else if (s == 'f') {
b.writeUTFBytes(x.toFixed(digits));
}
else if (s == 'g') {
var exp:Number = Math.log(Math.abs(x)) / Math.LN10;
exp = (exp >= 0 ? int(exp) : int(exp-1));
digits = (p.length==1 ? 15 : digits);
if (exp < -4 || exp > digits) {
s = upper ? 'E' : 'e';
numberPattern(b, "0."+repeat("#",digits)+s+"+0", x);
} else {
numberPattern(b, "0."+repeat("#",digits), x);
}
}
else if (s == 'n') {
numberPattern(b, _STD_NUM[s]+repeat("0",digits), x);
}
else if (s == 'p') {
numberPattern(b, _STD_NUM[s], x);
}
else if (s == 'x') {
s = padString(x.toString(16), digits);
s = upper ? s.toUpperCase() : s.toLowerCase();
b.writeUTFBytes(s);
}
else {
throw new ArgumentError("Illegal standard format: "+p);
}
}
else {
// handle custom formatting string
// TODO: GROUPING designator is escaped or in string literal
// TODO: Error handling?
if ((idx0=p.indexOf(GROUPING)) >= 0) {
if (x > 0) {
p = p.substring(0, idx0);
} else if (x < 0) {
idx1 = p.indexOf(GROUPING, ++idx0);
if (idx1 < 0) idx1 = p.length;
p = p.substring(idx0, idx1);
x = -x;
} else {
idx1 = 1 + p.indexOf(GROUPING, ++idx0);
p = p.substring(idx1);
}
}
formatNumber(b, p, x);
}
}
/**
* 0: Zero placeholder
* #: Digit placeholder
* .: Decimal point (any duplicates are ignored)
* ,: Thosand separator + scaling
* %: Percentage placeholder
* e/E: Scientific notation
*
* if has comma before dec point, use grouping
* if grouping right before dp, divide by 1000
* if percent and no e, multiply by 100
*/
private static function formatNumber(b:ByteArray, p:String, x:Number):void
{
var i:int, j:int, c:Number, n:int=1, digit:int=0;
var pp:int=-1, dp:int=-1, ep:int=-1, ep2:int=-1, sp:int=-1;
var nd:int=0, nf:int=0, ne:int=0, max:int=p.length-1;
var xd:Number, xf:Number, xe:int=0, zd:int=0, zf:int=0;
var sd:String, sf:String, se:String;
var hash:Boolean = false;
// ----------------------------------------------------------------
// first pass: extract info from the formatting pattern
for (i=0; i= 0) continue;
ep = i;
if (i= 0) {
if (dp >= 0) {
i = dp;
} else {
i = p.length;
while (i>sp) {
c = p.charCodeAt(i-1);
if (c==_ZERO || c==_HASH || c==_SEPR) break;
--i;
}
}
for (; --i >= sp;) {
if (p.charCodeAt(i) == _SEPR) {
if (adj) { x /= 1000; } else { group = true; break; }
} else {
adj = false;
}
}
}
// process percentage
if (pp >= 0) {
x *= 100;
}
// process negative number
if (x < 0) {
b.writeUTFBytes('-');
x = Math.abs(x);
}
// process power of ten for scientific format
if (ep >= 0) {
c = Math.log(x) / Math.LN10;
xe = (c>=0 ? int(c) : int(c-1)) - (nd-1);
x = x / Math.pow(10, xe);
}
// round the number as needed
c = Math.pow(10, nf);
x = Math.round(c*x) / c;
// separate number into component parts
xd = nf > 0 ? Math.floor(x) : Math.round(x);
xf = x - xd;
// create strings for integral and fractional parts
sd = pad(xd, nd);
sf = (xf+1).toFixed(nf).substring(2); // add 1 to avoid AS3 bug
if (hash) for (; zd=0 && sf.charCodeAt(zf)==_ZERO;);
// ----------------------------------------------------------------
// second pass: format the number
var inFraction:Boolean = false;
for (i=0, n=0; i= 0 || p.charCodeAt(i+1) != _HASH)
b.writeUTFBytes(DECIMAL_SEPARATOR);
inFraction = true;
n = 0;
}
else if (i == ep) {
b.writeUTFBytes(p.charAt(i));
if ((c=p.charCodeAt(i+1)) == _PLUS && xe > 0)
b.writeUTFBytes('+');
b.writeUTFBytes(pad(xe, ne));
i = ep2;
}
else if (!inFraction && n==0 && (c==_HASH || c==_ZERO)
&& sd.length-zd > nd) {
if (group) {
for (n=zd; n<=sd.length-nd; ++n) {
b.writeUTFBytes(sd.charAt(n));
if ((j=(sd.length-n-1)) > 0 && j%3 == 0)
b.writeUTFBytes(THOUSAND_SEPARATOR);
}
} else {
n = sd.length - nd + 1;
b.writeUTFBytes(sd.substr(zd, n-zd));
}
}
else if (c == _HASH) {
if (inFraction) {
if (n <= zf) b.writeUTFBytes(sf.charAt(n));
} else if (n >= zd) {
b.writeUTFBytes(sd.charAt(n));
if (group && (j=(sd.length-n-1)) > 0 && j%3 == 0)
b.writeUTFBytes(THOUSAND_SEPARATOR);
}
++n;
}
else if (c == _ZERO) {
if (inFraction) {
b.writeUTFBytes(n>=sf.length ? '0' : sf.charAt(n));
} else {
b.writeUTFBytes(sd.charAt(n));
if (group && (j=(sd.length-n-1)) > 0 && j%3 == 0)
b.writeUTFBytes(THOUSAND_SEPARATOR);
}
++n;
}
else if (c == _BACKSLASH) {
b.writeUTFBytes(p.charAt(++i));
}
else if (c == _APOSTROPHE) {
for(j=i+1; j 1) b.writeUTFBytes(p.substring(i+1,j));
i=j;
}
else if (c == _QUOTE) {
for(j=i+1; j 1) b.writeUTFBytes(p.substring(i+1,j));
i=j;
}
else {
if (c != _DECP && c != _SEPR) b.writeUTFBytes(p.charAt(i));
}
}
}
} // end of class Strings
}