[7849] | 1 | package br.com.stimuli.string{
|
---|
| 2 |
|
---|
| 3 | /**
|
---|
| 4 | * Creates a string with variable substitutions. Very similiar to printf, specially python's printf
|
---|
| 5 | * @param raw The string to be substituted.
|
---|
| 6 | * @param rest The objects to be substitued, can be positional or by properties inside the object (in wich case only one object can be passed)
|
---|
| 7 | * @return The formated and substitued string.
|
---|
| 8 | * @example
|
---|
| 9 | * <pre>
|
---|
| 10 | * import br.com.stimuli.string.printf;
|
---|
| 11 | * // objects are substitued in the other they appear
|
---|
| 12 | *
|
---|
| 13 | * printf("This is an %s lybrary for creating %s", "Actioscript 3.0", "strings");
|
---|
| 14 | * // outputs: "This is an Actioscript 3.0 lybrary for creating strings";
|
---|
| 15 | * // you can also format numbers:
|
---|
| 16 | *
|
---|
| 17 | * printf("You can also display numbers like PI: %f, and format them to a fixed precision, such as PI with 3 decimal places %.3f", Math.PI, Math.PI);
|
---|
| 18 | * // outputs: " You can also display numbers like PI: 3.141592653589793, and format them to a fixed precision, such as PI with 3 decimal places 3.142"
|
---|
| 19 | * // Instead of positional (the order of arguments to print f, you can also use propertie of an object):
|
---|
| 20 | * var userInfo : Object = {
|
---|
| 21 | "name": "Arthur Debert",
|
---|
| 22 | "email": "arthur@stimuli.com.br",
|
---|
| 23 | "website":"http://www.stimuli.com.br/",
|
---|
| 24 | "ocupation": "developer"
|
---|
| 25 | }
|
---|
| 26 | *
|
---|
| 27 | * printf("My name is %(name)s and I am a %(ocupation)s. You can read more on my personal %(website)s, or reach me through my %(email)s", userInfo);
|
---|
| 28 | * // outputs: "My name is Arthur Debert and I am a developer. You can read more on my personal http://www.stimuli.com.br/, or reach me through my arthur@stimuli.com.br"
|
---|
| 29 | * // you can also use date parts:
|
---|
| 30 | * var date : Date = new Date();
|
---|
| 31 | * printf("Today is %d/%m/%Y", date, date, date)
|
---|
| 32 | *
|
---|
| 33 | * </pre>
|
---|
| 34 | * @see br.com.stimuli.string
|
---|
| 35 | */
|
---|
| 36 | public function printf(raw : String, ...rest) : String{
|
---|
| 37 | /**
|
---|
| 38 | * Pretty ugly!
|
---|
| 39 | * basicaly
|
---|
| 40 | * % -> the start of a substitution hole
|
---|
| 41 | * (some_var_name) -> [optional] used in named substitutions
|
---|
| 42 | * .xx -> [optional] the precision with witch numbers will be formated
|
---|
| 43 | * x -> the formatter (string, hexa, float, date part)
|
---|
| 44 | */
|
---|
| 45 | var SUBS_RE : RegExp = /%(\((?P<var_name>[\w_\d]+)\))?(\.(?P<precision>[0-9]))?(?P<formater>[sxofaAbBcdHIjmMpSUwWxXyYZ])/ig;
|
---|
| 46 |
|
---|
| 47 | var matches : Array = [];
|
---|
| 48 | var result : Object = SUBS_RE.exec(raw);
|
---|
| 49 | var match : Match;
|
---|
| 50 | var runs : int = 0;
|
---|
| 51 | var numMatches : int = 0;
|
---|
| 52 | var numberVariables : int = rest.length;
|
---|
| 53 | // quick check if we find string subs amongst the text to match (something like %(foo)s
|
---|
| 54 | var isPositionalSubts : Boolean = !Boolean(raw.match(/%\(\s*[\w\d_]+\s*\)/));
|
---|
| 55 | trace(raw, isPositionalSubts);
|
---|
| 56 | var replacementValue : *;
|
---|
| 57 | var formater : String;
|
---|
| 58 | var varName : String;
|
---|
| 59 | var precision : String;
|
---|
| 60 | // matched through the string, creating Match objects for easier later reuse
|
---|
| 61 | while (Boolean(result)){
|
---|
| 62 | match = new Match();
|
---|
| 63 | match.startIndex = result.index;
|
---|
| 64 | match.length = String(result[0]).length;
|
---|
| 65 | match.endIndex = match.startIndex + match.length;
|
---|
| 66 | match.content = String(result[0]);
|
---|
| 67 | trace(match.content);
|
---|
| 68 | // try to get substitution properties
|
---|
| 69 | formater = result.formater;
|
---|
| 70 | varName = result.var_name;
|
---|
| 71 | precision = result.precision;
|
---|
| 72 |
|
---|
| 73 | if (isPositionalSubts){
|
---|
| 74 | // by position, grab next subs:
|
---|
| 75 | try{
|
---|
| 76 | replacementValue = rest[matches.length];
|
---|
| 77 | }catch(e : Error){
|
---|
| 78 | throw new Error(BAD_VARIABLE_NUMBER)
|
---|
| 79 | }
|
---|
| 80 |
|
---|
| 81 | }else{
|
---|
| 82 | // be hash / properties
|
---|
| 83 | replacementValue = rest[0][varName];
|
---|
| 84 | if (replacementValue == undefined){
|
---|
| 85 | // check for bad variable names
|
---|
| 86 | var errorMsg : String = "Var name:'" + varName + "' not found on " + rest[0];
|
---|
| 87 | throw new Error(errorMsg);
|
---|
| 88 | }
|
---|
| 89 |
|
---|
| 90 |
|
---|
| 91 | }
|
---|
| 92 | // format the string accodingly to the formatter
|
---|
| 93 | if (formater == STRING_FORMATTER){
|
---|
| 94 | match.replacement = replacementValue.toString();
|
---|
| 95 | }else if (formater == FLOAT_FORMATER){
|
---|
| 96 | // floats, check if we need to truncate precision
|
---|
| 97 | if (precision){
|
---|
| 98 | match.replacement = truncateNumber(Number(replacementValue), int(precision)).toString()
|
---|
| 99 | }else{
|
---|
| 100 | match.replacement = replacementValue.toString();
|
---|
| 101 | }
|
---|
| 102 | }else if (formater == OCTAL_FORMATER){
|
---|
| 103 | match.replacement = int(replacementValue).toString(8);
|
---|
| 104 | }else if (formater == HEXA_FORMATER){
|
---|
| 105 | match.replacement = "0x" + int(replacementValue).toString(16);
|
---|
| 106 | }else if(DATES_FORMATERS.indexOf(formater) > -1){
|
---|
| 107 | switch (formater){
|
---|
| 108 | case DATE_DAY_FORMATTER:
|
---|
| 109 | match.replacement = replacementValue.date;
|
---|
| 110 | break
|
---|
| 111 | case DATE_FULLYEAR_FORMATTER:
|
---|
| 112 | match.replacement = replacementValue.fullYear;
|
---|
| 113 | break
|
---|
| 114 | case DATE_YEAR_FORMATTER:
|
---|
| 115 | match.replacement = replacementValue.fullYear.toString().substr(2,2);
|
---|
| 116 | break
|
---|
| 117 | case DATE_MONTH_FORMATTER:
|
---|
| 118 | match.replacement = replacementValue.month + 1;
|
---|
| 119 | break
|
---|
| 120 | case DATE_HOUR24_FORMATTER:
|
---|
| 121 | match.replacement = replacementValue.hours;
|
---|
| 122 | break
|
---|
| 123 | case DATE_HOUR_FORMATTER:
|
---|
| 124 | var hours24 : Number = replacementValue.hours;
|
---|
| 125 | match.replacement = (hours24 -12).toString();
|
---|
| 126 | break
|
---|
| 127 | case DATE_HOUR_AMPM_FORMATTER:
|
---|
| 128 | match.replacement = (replacementValue.hours >= 12 ? "p.m" : "a.m");
|
---|
| 129 | break
|
---|
| 130 | case DATE_TOLOCALE_FORMATTER:
|
---|
| 131 | match.replacement = replacementValue.toLocaleString();
|
---|
| 132 | break
|
---|
| 133 | case DATE_MINUTES_FORMATTER:
|
---|
| 134 | match.replacement = replacementValue.minutes;
|
---|
| 135 | break
|
---|
| 136 | case DATE_SECONDS_FORMATTER:
|
---|
| 137 | match.replacement = replacementValue.seconds;
|
---|
| 138 | break
|
---|
| 139 | }
|
---|
| 140 | }else{
|
---|
| 141 | trace("no good replacment " );
|
---|
| 142 | }
|
---|
| 143 | matches.push(match);
|
---|
| 144 | // just a small check in case we get stuck: kludge!
|
---|
| 145 | runs ++;
|
---|
| 146 | if (runs > 10000){
|
---|
| 147 | trace("something is wrong, breaking out")
|
---|
| 148 | break
|
---|
| 149 | }
|
---|
| 150 | numMatches ++;
|
---|
| 151 | // iterates next match
|
---|
| 152 | result = SUBS_RE.exec(raw);
|
---|
| 153 | }
|
---|
| 154 | // in case there's nothing to substitute, just return the initial string
|
---|
| 155 | if(matches.length == 0){
|
---|
| 156 | trace("no matches, returning" );
|
---|
| 157 | return raw;
|
---|
| 158 | }
|
---|
| 159 | // now actually do the substitution, keeping a buffer to be joined at
|
---|
| 160 | //the end for better performance
|
---|
| 161 | var buffer : Array = [];
|
---|
| 162 | var lastMatch : Match;
|
---|
| 163 | // beggininf os string, if it doesn't start with a substitition
|
---|
| 164 | var previous : String = raw.substr(0, matches[0].startIndex);
|
---|
| 165 | var subs : String;
|
---|
| 166 | for each(match in matches){
|
---|
| 167 | // finds out the previous string part and the next substitition
|
---|
| 168 | if (lastMatch){
|
---|
| 169 | previous = raw.substring(lastMatch.endIndex , match.startIndex);
|
---|
| 170 | }
|
---|
| 171 | buffer.push(previous);
|
---|
| 172 | buffer.push(match.replacement);
|
---|
| 173 | lastMatch = match;
|
---|
| 174 |
|
---|
| 175 | }
|
---|
| 176 | // buffer the tail of the string: text after the last substitution
|
---|
| 177 | buffer.push(raw.substr(match.endIndex, raw.length - match.endIndex));
|
---|
| 178 | return buffer.join("");
|
---|
| 179 | }
|
---|
| 180 | }
|
---|
| 181 |
|
---|
| 182 |
|
---|
| 183 | // internal usage
|
---|
| 184 | /** @private */
|
---|
| 185 | const BAD_VARIABLE_NUMBER : String = "The number of variables to be replaced and template holes don't match";
|
---|
| 186 | /** Converts to a string*/
|
---|
| 187 | const STRING_FORMATTER : String = "s";
|
---|
| 188 | /** Outputs as a Number, can use the precision specifier: %.2sf will output a float with 2 decimal digits.*/
|
---|
| 189 | const FLOAT_FORMATER : String = "f";
|
---|
| 190 | /** Converts to an OCTAL number */
|
---|
| 191 | const OCTAL_FORMATER : String = "o";
|
---|
| 192 | /** Converts to a Hexa number (includes 0x) */
|
---|
| 193 | const HEXA_FORMATER : String = "x";
|
---|
| 194 | /** @private */
|
---|
| 195 | const DATES_FORMATERS : String = "aAbBcdHIjmMpSUwWxXyYZ";
|
---|
| 196 | /** Day of month, from 0 to 30 on <code>Date</code> objects.*/
|
---|
| 197 | const DATE_DAY_FORMATTER : String = "d";
|
---|
| 198 | /** Full year, e.g. 2007 on <code>Date</code> objects.*/
|
---|
| 199 | const DATE_FULLYEAR_FORMATTER : String = "Y";
|
---|
| 200 | /** Year, e.g. 07 on <code>Date</code> objects.*/
|
---|
| 201 | const DATE_YEAR_FORMATTER : String = "y";
|
---|
| 202 | /** Month from 1 to 12 on <code>Date</code> objects.*/
|
---|
| 203 | const DATE_MONTH_FORMATTER : String = "m";
|
---|
| 204 | /** Hours (0-23) on <code>Date</code> objects.*/
|
---|
| 205 | const DATE_HOUR24_FORMATTER : String = "H";
|
---|
| 206 | /** Hours 0-12 on <code>Date</code> objects.*/
|
---|
| 207 | const DATE_HOUR_FORMATTER : String = "I";
|
---|
| 208 | /** a.m or p.m on <code>Date</code> objects.*/
|
---|
| 209 | const DATE_HOUR_AMPM_FORMATTER : String = "p";
|
---|
| 210 | /** Minutes on <code>Date</code> objects.*/
|
---|
| 211 | const DATE_MINUTES_FORMATTER : String = "M";
|
---|
| 212 | /** Seconds on <code>Date</code> objects.*/
|
---|
| 213 | const DATE_SECONDS_FORMATTER : String = "S";
|
---|
| 214 | /** A string rep of a <code>Date</code> object on the current locale.*/
|
---|
| 215 | const DATE_TOLOCALE_FORMATTER : String = "c";
|
---|
| 216 |
|
---|
| 217 | var version : String = "$Id: printf.as 5 2008-08-01 12:18:25Z debert $"
|
---|
| 218 |
|
---|
| 219 |
|
---|
| 220 |
|
---|
| 221 | /** @private
|
---|
| 222 | * Internal class that normalizes matching information.
|
---|
| 223 | */
|
---|
| 224 | class Match{
|
---|
| 225 | public var startIndex : int;
|
---|
| 226 | public var endIndex : int;
|
---|
| 227 | public var length : int;
|
---|
| 228 | public var content : String;
|
---|
| 229 | public var replacement : String;
|
---|
| 230 | public var before : String;
|
---|
| 231 | public function toString() : String{
|
---|
| 232 | return "Match [" + startIndex + " - " + endIndex + "] (" + length + ") " + content + ", replacement:" +replacement + ";"
|
---|
| 233 | }
|
---|
| 234 | }
|
---|
| 235 | /** @private */
|
---|
| 236 | function truncateNumber(raw : Number, decimals :int =2) : Number {
|
---|
| 237 | var power : int = Math.pow(10, decimals);
|
---|
| 238 | return Math.round(raw * ( power )) / power;
|
---|
| 239 | }
|
---|