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 }