1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 25 // NOTE: The API for the classes in this file are inspired by the Java text 26 // formatting classes but the implementation was NOT copied or ported 27 // from the Java sources. 28 29 // 30 // Format class 31 // 32 33 /** 34 * Base class for all formats. To format an object, instantiate the 35 * format of your choice and call the <code>format</code> method which 36 * returns the formatted string. 37 * 38 * @private 39 */ 40 AjxFormat = function(pattern) { 41 if (arguments.length == 0) { return; } 42 this._pattern = pattern; 43 this._segments = []; 44 } 45 46 /** 47 * Returns a string representation of this object. 48 * 49 * @return {string} a string representation of this object 50 */ 51 AjxFormat.prototype.toString = function() { 52 var s = []; 53 s.push("pattern=\"",this._pattern,'"'); 54 if (this._segments.length > 0) { 55 s.push(", segments={ "); 56 for (var i = 0; i < this._segments.length; i++) { 57 if (i > 0) { s.push(", "); } 58 s.push(String(this._segments[i])); 59 } 60 s.push(" }"); 61 } 62 return s.join(""); 63 }; 64 65 // Static functions 66 67 AjxFormat.initialize = function() { 68 AjxDateFormat.initialize(); 69 AjxNumberFormat.initialize(); 70 }; 71 72 // Public methods 73 74 /** 75 * This method does <em>not</em> need to be overridden unless 76 * the subclass doesn't use format segments and takes complete 77 * responsibility for formatting. 78 */ 79 AjxFormat.prototype.format = function(object) { 80 var s = []; 81 for (var i = 0; i < this._segments.length; i++) { 82 s.push(this._segments[i].format(object)); 83 } 84 return s.join(""); 85 }; 86 87 /** 88 * Parses the given string according to this format's pattern and returns 89 * an object. 90 * <p> 91 * <strong>Note:</strong> 92 * The default implementation of this method assumes that the sub-class 93 * has implemented the <code>_createParseObject</code> method. 94 */ 95 AjxFormat.prototype.parse = function(s) { 96 var object = this._createParseObject(); 97 var index = 0; 98 for (var i = 0; i < this._segments.length; i++) { 99 var segment = this._segments[i]; 100 index = segment.parse(object, s, index); 101 } 102 // REVISIT: Should this return null instead? 103 if (index < s.length) { 104 throw new AjxFormat.ParsingException(this, null, "input too long"); // I18n 105 } 106 return object; 107 }; 108 109 /** 110 * Returns an array of segments that comprise this format. 111 * <p> 112 * <strong>Note:</strong> 113 * This method is specific to this implementation and does not follow 114 * the format classes found in the <code>java.text</code> package. 115 */ 116 AjxFormat.prototype.getSegments = function() { 117 return this._segments; 118 }; 119 120 /** Returns a string pattern for this format. */ 121 AjxFormat.prototype.toPattern = function() { 122 return this._pattern; 123 }; 124 125 /** Returns a copy of this format. */ 126 AjxFormat.prototype.clone = function() { 127 return new this.constructor(this._pattern); 128 }; 129 130 // Protected methods 131 132 /** 133 * Creates the object that is initialized by parsing 134 * <p> 135 * <strong>Note:</strong> 136 * This must be implemented by sub-classes. 137 */ 138 AjxFormat.prototype._createParseObject = function(s) { 139 throw new AjxFormat.ParsingException(this, null, "not implemented"); // I18n 140 }; 141 142 // Protected static methods 143 144 AjxFormat._zeroPad = function(s, length, zeroChar, rightSide) { 145 s = typeof s == "string" ? s : String(s); 146 147 if (s.length >= length) return s; 148 149 zeroChar = zeroChar || '0'; 150 151 var a = []; 152 for (var i = s.length; i < length; i++) { 153 a.push(zeroChar); 154 } 155 a[rightSide ? "unshift" : "push"](s); 156 157 return a.join(""); 158 }; 159 160 // 161 // Format exception base class 162 // 163 164 AjxFormat.FormatException = function(format, message) { 165 this._format = format; 166 this._message = message; 167 }; 168 AjxFormat.FormatException.prototype.toString = function() { 169 return this._message; 170 }; 171 172 173 // 174 // Formatting exception class 175 // 176 177 AjxFormat.FormattingException = function(format, segment, message) { 178 AjxFormat.FormatException.call(this, format, message); 179 this._segment = segment; 180 }; 181 AjxFormat.FormattingException.prototype = new AjxFormat.FormatException; 182 AjxFormat.FormattingException.prototype.constructor = AjxFormat.FormattingException; 183 184 185 // 186 // Parsing exception class 187 // 188 189 AjxFormat.ParsingException = function(format, segment, message) { 190 AjxFormat.FormatException.call(this, format, message); 191 this._segment = segment; 192 }; 193 AjxFormat.ParsingException.prototype = new AjxFormat.FormatException; 194 AjxFormat.ParsingException.prototype.constructor = AjxFormat.ParsingException; 195 196 // 197 // Segment class 198 // 199 200 AjxFormat.Segment = function(format, s) { 201 if (arguments.length == 0) return; 202 this._parent = format; 203 this._s = s; 204 }; 205 206 AjxFormat.Segment.prototype.toString = function() { 207 return "segment: \""+this._s+'"'; 208 }; 209 210 // Public methods 211 212 AjxFormat.Segment.prototype.format = function(o) { 213 return this._s; 214 }; 215 216 /** 217 * Parses the string at the given index, initializes the parse object 218 * (as appropriate), and returns the new index within the string for 219 * the next parsing step. 220 * <p> 221 * <strong>Note:</strong> 222 * This method must be implemented by sub-classes. 223 * 224 * @param o [object] The parse object to be initialized. 225 * @param s [string] The input string to be parsed. 226 * @param index [number] The index within the string to start parsing. 227 */ 228 AjxFormat.Segment.prototype.parse = function(o, s, i) { 229 throw new AjxFormat.ParsingException(this._parent, this, "not implemented"); // I18n 230 }; 231 232 AjxFormat.Segment.prototype.getFormat = function() { 233 return this._parent; 234 }; 235 AjxFormat.Segment.prototype.toSubPattern = function() { 236 return this._s; 237 }; 238 239 // Protected methods 240 241 AjxFormat.Segment.prototype._getFixedLength = function() { 242 var fixedlen; 243 if (this._index + 1 < this._parent._segments.length) { 244 var nextSegment = this._parent._segments[this._index + 1]; 245 if (!(nextSegment instanceof AjxFormat.TextSegment)) { 246 fixedlen = this._s.length; 247 } 248 } 249 return fixedlen; 250 }; 251 252 // Protected static methods 253 254 AjxFormat.Segment._parseLiteral = function(literal, s, index) { 255 if (s.length - index < literal.length) { 256 throw new AjxFormat.ParsingException(this._parent, this, "input too short"); // I18n 257 } 258 for (var i = 0; i < literal.length; i++) { 259 if (literal.charAt(i) != s.charAt(index + i)) { 260 throw new AjxFormat.ParsingException(this._parent, this, "input doesn't match"); // I18n 261 } 262 } 263 return index + literal.length; 264 }; 265 266 AjxFormat.Segment._parseLiterals = function(o, f, adjust, literals, s, index) { 267 for (var i = 0; i < literals.length; i++) { 268 try { 269 var literal = literals[i]; 270 var nindex = AjxFormat.Segment._parseLiteral(literal, s, index); 271 if (f) { 272 var target = o || window; 273 if (typeof f == "function") { 274 f.call(target, i + adjust); 275 } 276 else { 277 target[f] = i + adjust; 278 } 279 } 280 return nindex; 281 } 282 catch (e) { 283 // ignore. keep trying to find a match 284 } 285 } 286 return -1; 287 }; 288 289 /** 290 * Parses an integer at the offset of the given string and calls a 291 * method on the specified object. 292 * 293 * @param o [object] The target object. 294 * @param f [function|string] The method to call on the target object. 295 * If this parameter is a string, then it is used 296 * as the name of the property to set on the 297 * target object. 298 * @param adjust [number] The numeric adjustment to make on the 299 * value before calling the object method. 300 * @param s [string] The string to parse. 301 * @param index [number] The index within the string to start parsing. 302 * @param fixedlen [number] If specified, specifies the required number 303 * of digits to be parsed. 304 * @param radix [number] Optional. Specifies the radix of the parse 305 * string. Defaults to 10 if not specified. 306 */ 307 AjxFormat.Segment._parseInt = function(o, f, adjust, s, index, fixedlen, radix) { 308 var len = fixedlen || s.length - index; 309 var head = index; 310 for (var i = 0; i < len; i++) { 311 if (!s.charAt(index++).match(/\d/)) { 312 index--; 313 break; 314 } 315 } 316 var tail = index; 317 if (head == tail) { 318 throw new AjxFormat.ParsingException(this._parent, this, "number not present"); // I18n 319 } 320 if (fixedlen && tail - head != fixedlen) { 321 throw new AjxFormat.ParsingException(this._parent, this, "number too short"); // I18n 322 } 323 var value = parseInt(s.substring(head, tail), radix || 10); 324 if (f) { 325 var target = o || window; 326 if (typeof f == "function") { 327 f.call(target, value + adjust); 328 } 329 else { 330 target[f] = value + adjust; 331 } 332 } 333 return tail; 334 }; 335 336 // 337 // Date format class 338 // 339 340 /** 341 * The AjxDateFormat class formats Date objects according to a specified 342 * pattern. The patterns are defined the same as the SimpleDateFormat 343 * class in the Java libraries. <strong>Note:</strong> <em>Only the 344 * Gregorian Calendar is supported at this time.</em> Supporting other 345 * calendars would require a lot more information downloaded to the 346 * client. Limiting dates to the Gregorian calendar is a trade-off. 347 * <p> 348 * <strong>Note:</strong> 349 * The date format differs from the Java patterns a few ways: the pattern 350 * "EEEEE" (5 'E's) denotes a <em>short</em> weekday and the pattern "MMMMM" 351 * (5 'M's) denotes a <em>short</em> month name. This matches the extended 352 * pattern found in the Common Locale Data Repository (CLDR) found at: 353 * http://www.unicode.org/cldr/. 354 * 355 * @param {string} pattern The date format pattern. 356 * 357 * @class 358 * @constructor 359 */ 360 AjxDateFormat = function(pattern) { 361 if (arguments.length == 0) { return; } 362 AjxFormat.call(this, pattern); 363 if (pattern == null) { return; } 364 if (typeof pattern == "number") { 365 switch (pattern) { 366 case AjxDateFormat.SHORT: pattern = I18nMsg.formatDateShort; break; 367 case AjxDateFormat.MEDIUM: pattern = I18nMsg.formatDateMedium; break; 368 case AjxDateFormat.LONG: pattern = I18nMsg.formatDateLong; break; 369 case AjxDateFormat.FULL: pattern = I18nMsg.formatDateFull; break; 370 case AjxDateFormat.NUMBER: pattern = I18nMsg.formatDateNumber; break; 371 } 372 } 373 for (var i = 0; i < pattern.length; i++) { 374 // literal 375 var c = pattern.charAt(i); 376 if (c == "'") { 377 var head = i + 1; 378 for (i++ ; i < pattern.length; i++) { 379 var c = pattern.charAt(i); 380 if (c == "'") { 381 if (i + 1 < pattern.length && pattern.charAt(i + 1) == "'") { 382 pattern = pattern.substr(0, i) + pattern.substr(i + 1); 383 } 384 else { 385 break; 386 } 387 } 388 } 389 // if (i == pattern.length) { 390 // NOTE: try to avoid silent errors 391 // throw new FormatException(this, "unterminated string literal"); // I18n 392 // } 393 var tail = i; 394 var segment = new AjxFormat.TextSegment(this, pattern.substring(head, tail)); 395 this._segments.push(segment); 396 continue; 397 } 398 399 // non-meta chars 400 var head = i; 401 while(i < pattern.length) { 402 c = pattern.charAt(i); 403 if (AjxDateFormat._META_CHARS.indexOf(c) != -1 || c == "'") { 404 break; 405 } 406 i++; 407 } 408 var tail = i; 409 if (head != tail) { 410 var segment = new AjxFormat.TextSegment(this, pattern.substring(head, tail)); 411 this._segments.push(segment); 412 i--; 413 continue; 414 } 415 416 // meta char 417 var head = i; 418 while(++i < pattern.length) { 419 if (pattern.charAt(i) != c) { 420 break; 421 } 422 } 423 var tail = i--; 424 var count = tail - head; 425 var field = pattern.substr(head, count); 426 var segment = null; 427 switch (c) { 428 case 'G': segment = new AjxDateFormat.EraSegment(this, field); break; 429 case 'y': segment = new AjxDateFormat.YearSegment(this, field); break; 430 case 'M': segment = new AjxDateFormat.MonthSegment(this, field); break; 431 case 'w': segment = new AjxDateFormat.WeekSegment(this, field); break; 432 case 'W': segment = new AjxDateFormat.WeekSegment(this, field); break; 433 case 'D': segment = new AjxDateFormat.DaySegment(this, field); break; 434 case 'd': segment = new AjxDateFormat.DaySegment(this, field); break; 435 case 'F': segment = new AjxDateFormat.WeekdaySegment(this, field); break; 436 case 'E': segment = new AjxDateFormat.WeekdaySegment(this, field); break; 437 case 'a': segment = new AjxDateFormat.AmPmSegment(this, field); break; 438 case 'H': segment = new AjxDateFormat.HourSegment(this, field); break; 439 case 'k': segment = new AjxDateFormat.HourSegment(this, field); break; 440 case 'K': segment = new AjxDateFormat.HourSegment(this, field); break; 441 case 'h': segment = new AjxDateFormat.HourSegment(this, field); break; 442 case 'm': segment = new AjxDateFormat.MinuteSegment(this, field); break; 443 case 's': segment = new AjxDateFormat.SecondSegment(this, field); break; 444 case 'S': segment = new AjxDateFormat.SecondSegment(this, field); break; 445 case 'z': segment = new AjxDateFormat.TimezoneSegment(this, field); break; 446 case 'Z': segment = new AjxDateFormat.TimezoneSegment(this, field); break; 447 } 448 if (segment != null) { 449 segment._index = this._segments.length; 450 this._segments.push(segment); 451 } 452 } 453 } 454 AjxDateFormat.prototype = new AjxFormat; 455 AjxDateFormat.prototype.constructor = AjxDateFormat; 456 457 AjxDateFormat.prototype.toString = function() { 458 return "[AjxDateFormat: "+AjxFormat.prototype.toString.call(this)+"]"; 459 }; 460 461 // Constants 462 463 /** Short date/time format style. */ 464 AjxDateFormat.SHORT = 0; 465 /** Medium date/time format style. */ 466 AjxDateFormat.MEDIUM = 1; 467 /** Long date/time format style. */ 468 AjxDateFormat.LONG = 2; 469 /** Full date/time format style. */ 470 AjxDateFormat.FULL = 3; 471 /** Default date/time format style. */ 472 AjxDateFormat.DEFAULT = AjxDateFormat.MEDIUM; 473 474 AjxDateFormat._META_CHARS = "GyMwWDdFEaHkKhmsSzZ"; 475 476 /** Number date . */ 477 AjxDateFormat.NUMBER = 4; 478 // Static methods 479 480 /** 481 * Get a date formatter. 482 * 483 * @param style The format style. 484 * @return {AjxDateFormat} The date formatter. 485 * 486 * @see {AjxDateFormat.SHORT} 487 * @see {AjxDateFormat.MEDIUM} 488 * @see {AjxDateFormat.LONG} 489 * @see {AjxDateFormat.FULL} 490 */ 491 AjxDateFormat.getDateInstance = function(style) { 492 // lazily create formatters 493 style = style != null ? style : AjxDateFormat.DEFAULT; 494 if (!AjxDateFormat._DATE_FORMATTERS[style]) { 495 AjxDateFormat._DATE_FORMATTERS[style] = new AjxDateFormat(AjxDateFormat._dateFormats[style]); 496 } 497 return AjxDateFormat._DATE_FORMATTERS[style]; 498 }; 499 500 /** 501 * Get a time formatter. 502 * 503 * @param style The format style. 504 * @return {AjxDateFormat} The time formatter. 505 * 506 * @see {AjxDateFormat.SHORT} 507 * @see {AjxDateFormat.MEDIUM} 508 * @see {AjxDateFormat.LONG} 509 * @see {AjxDateFormat.FULL} 510 */ 511 AjxDateFormat.getTimeInstance = function(style) { 512 // lazily create formatters 513 style = style != null ? style : AjxDateFormat.DEFAULT; 514 if (!AjxDateFormat._TIME_FORMATTERS[style]) { 515 AjxDateFormat._TIME_FORMATTERS[style] = new AjxDateFormat(AjxDateFormat._timeFormats[style]); 516 } 517 return AjxDateFormat._TIME_FORMATTERS[style]; 518 }; 519 520 /** 521 * Get a date and time formatter. 522 * 523 * @param dateStyle The format style for the date. 524 * @param timeStyle The format style for the time. 525 * @return {AjxDateFormat} The date and time formatter. 526 * 527 * @see {AjxDateFormat.SHORT} 528 * @see {AjxDateFormat.MEDIUM} 529 * @see {AjxDateFormat.LONG} 530 * @see {AjxDateFormat.FULL} 531 */ 532 AjxDateFormat.getDateTimeInstance = function(dateStyle, timeStyle) { 533 // lazily create formatters 534 dateStyle = dateStyle != null ? dateStyle : AjxDateFormat.DEFAULT; 535 timeStyle = timeStyle != null ? timeStyle : AjxDateFormat.DEFAULT; 536 var style = dateStyle * 10 + timeStyle; 537 if (!AjxDateFormat._DATETIME_FORMATTERS[style]) { 538 var pattern = I18nMsg.formatDateTime; 539 var params = [ AjxDateFormat._dateFormats[dateStyle], AjxDateFormat._timeFormats[timeStyle] ]; 540 541 var dateTimePattern = AjxMessageFormat.format(pattern, params); 542 AjxDateFormat._DATETIME_FORMATTERS[style] = new AjxDateFormat(dateTimePattern); 543 } 544 return AjxDateFormat._DATETIME_FORMATTERS[style]; 545 }; 546 547 /** 548 * Format a date. Equivalent to <code>new AjxDateFormat(pattern).format(date)</code>. 549 * 550 * @param {string} pattern The format. 551 * @param {Date} date The date to format. 552 * @return {string} The formatted string. 553 */ 554 AjxDateFormat.format = function(pattern, date) { 555 return new AjxDateFormat(pattern).format(date); 556 }; 557 558 /** 559 * Parse a date. Equivalent to <code>new AjxDateFormat(pattern).parse(dateStr)</code>. 560 * 561 * @param {string} pattern The format. 562 * @param {string} dateStr The input string to parse. 563 * @return {Date} The parsed date object. 564 */ 565 AjxDateFormat.parse = function(pattern, dateStr) { 566 return new AjxDateFormat(pattern).parse(dateStr); 567 }; 568 569 AjxDateFormat.initialize = function() { 570 // format 571 AjxDateFormat._dateFormats = [ 572 I18nMsg.formatDateShort, I18nMsg.formatDateMedium, 573 I18nMsg.formatDateLong, I18nMsg.formatDateFull,I18nMsg.formatDateNumber 574 ]; 575 AjxDateFormat._timeFormats = [ 576 I18nMsg.formatTimeShort, I18nMsg.formatTimeMedium, 577 I18nMsg.formatTimeLong, I18nMsg.formatTimeFull 578 ]; 579 AjxDateFormat._timeParsers = []; 580 for (var i=1, fmt; fmt = I18nMsg["parseTime"+i]; i++) { 581 AjxDateFormat._timeParsers.push(new RegExp(fmt,"i")); 582 } 583 584 585 AjxDateFormat._DATE_FORMATTERS = {}; 586 AjxDateFormat._TIME_FORMATTERS = {}; 587 AjxDateFormat._DATETIME_FORMATTERS = {}; 588 589 // segments 590 AjxDateFormat.MonthSegment.initialize(); 591 AjxDateFormat.WeekdaySegment.initialize(); 592 }; 593 594 // Public methods 595 596 /** 597 * Parses the given time using one of the AjxDateFormat._timeParsers, which are regexes defined in the properties 598 */ 599 AjxDateFormat.parseTime = function(timeStr) { 600 for (var i=0; i<AjxDateFormat._timeParsers.length; i++) { 601 var result = AjxDateFormat._timeParsers[i].exec(timeStr); 602 if (result) { 603 var hours = parseInt(result[1],10) || 0; 604 var minutes = parseInt(result[2],10) || 0; 605 var ampm = result[3]; 606 if (ampm) { 607 var pmChars = I18nMsg.parseTimePMChars; 608 if (hours == 12) { 609 hours = 0; 610 } 611 if (pmChars.indexOf(ampm)!=-1) { 612 hours += 12; 613 } 614 } 615 if (hours < 24 && minutes < 60) { 616 date = new Date(); 617 date.setHours(hours); 618 date.setMinutes(minutes); 619 date.setSeconds(0); 620 return date; 621 } 622 } 623 } 624 }; 625 626 /** 627 * Parses the given string and returns a date. If the string cannot be 628 * parsed as a date, <code>null</code> is returned. 629 * 630 * @param {string} s The string to parse. 631 * @return {Date} The parsed date object. 632 */ 633 AjxDateFormat.prototype.parse = function(s) { 634 var object = null; 635 try { 636 object = AjxFormat.prototype.parse.call(this, s); 637 638 // set the date components in proper order 639 640 // Use day 2 for the initial date - even if the setFullYear call screws up (see Bugzilla@Mozilla, 641 // Bug 1079720), it won't get the wrong year. 642 var date = new Date(0, 0, 2, 0, 0, 0, 0); 643 if (object.year != null) { date.setFullYear(object.year); } 644 if (object.month != null) { date.setMonth(object.month); } 645 // To insure the same behavior (if object does not set the day), init the day to 1. 646 date.setDate(1); 647 if (object.dayofmonth != null) { date.setDate(object.dayofmonth); } 648 else if (object.dayofyear != null) { date.setMonth(0, object.dayofyear); } 649 if (object.hours != null) { date.setHours(object.hours); } 650 if (object.minutes != null) { date.setMinutes(object.minutes); } 651 if (object.seconds != null) { date.setSeconds(object.seconds); } 652 if (object.milliseconds != null) { date.setMilliseconds(object.milliseconds); } 653 if (object.ampm != null) { 654 var hours = date.getHours(); 655 if (hours == 12 && object.ampm == 0) { 656 hours = 0; 657 } 658 else if (hours != 12 && object.ampm == 1) { 659 hours += 12; 660 } 661 date.setHours(hours); 662 } 663 if (object.timezone != null) { date.setMinutes(date.getMinutes() - object.timezone); } 664 // TODO: era 665 666 if (isNaN(date.getTime())) { 667 return null; //in some cases (see bug 51266) the date is invalid without throwing an exception. return null in this case 668 } 669 670 object = date; 671 } 672 catch (e) { 673 DBG.println(AjxDebug.DBG3, e); 674 // do nothing 675 } 676 return object; 677 }; 678 679 // Protected methods 680 681 AjxDateFormat.prototype._createParseObject = function() { 682 // NOTE: An object to hold the parsed parts is returned instead of 683 // a Date object because setting the day is dependent on which 684 // year is set. For example, if the user parsed "2/29/2008", 685 // which is a leap year, with the pattern "M/d/yyyy", then the 686 // year must be set before the month and date or else the Date 687 // object will roll it over to Mar 1. 688 return { 689 year: null, month: null, dayofmonth: null, dayofyear: null, 690 hours: null, minutes: null, seconds: null, milliseconds: null, 691 ampm: null, era: null, timezone: null 692 }; 693 }; 694 695 // 696 // Text segment class 697 // 698 699 AjxFormat.TextSegment = function(format, s) { 700 if (arguments.length == 0) return; 701 AjxFormat.Segment.call(this, format, s); 702 }; 703 AjxFormat.TextSegment.prototype = new AjxFormat.Segment; 704 AjxFormat.TextSegment.prototype.constructor = AjxFormat.TextSegment; 705 706 AjxFormat.TextSegment.prototype.toString = function() { 707 return "text: \""+this._s+'"'; 708 }; 709 710 // Public methods 711 712 AjxFormat.TextSegment.prototype.parse = function(o, s, index) { 713 return AjxFormat.Segment._parseLiteral(this._s, s, index); 714 }; 715 716 // 717 // Date segment class 718 // 719 720 AjxDateFormat.DateSegment = function(format, s) { 721 if (arguments.length == 0) return; 722 AjxFormat.Segment.call(this, format, s); 723 } 724 AjxDateFormat.DateSegment.prototype = new AjxFormat.Segment; 725 AjxDateFormat.DateSegment.prototype.constructor = AjxDateFormat.DateSegment; 726 727 // 728 // Date era segment class 729 // 730 731 AjxDateFormat.EraSegment = function(format, s) { 732 if (arguments.length == 0) return; 733 AjxDateFormat.DateSegment.call(this, format, s); 734 }; 735 AjxDateFormat.EraSegment.prototype = new AjxDateFormat.DateSegment; 736 AjxDateFormat.EraSegment.prototype.constructor = AjxDateFormat.EraSegment; 737 738 AjxDateFormat.EraSegment.prototype.toString = function() { 739 return "dateEra: \""+this._s+'"'; 740 }; 741 742 // Public methods 743 744 AjxDateFormat.EraSegment.prototype.format = function(date) { 745 // NOTE: Only support current era at the moment... 746 return I18nMsg.eraAD; 747 }; 748 749 // 750 // Date year segment class 751 // 752 753 AjxDateFormat.YearSegment = function(format, s) { 754 if (arguments.length == 0) return; 755 AjxDateFormat.DateSegment.call(this, format, s); 756 }; 757 AjxDateFormat.YearSegment.prototype = new AjxDateFormat.DateSegment; 758 AjxDateFormat.YearSegment.prototype.constructor = AjxDateFormat.YearSegment; 759 760 AjxDateFormat.YearSegment.prototype.toString = function() { 761 return "dateYear: \""+this._s+'"'; 762 }; 763 764 // Public methods 765 766 AjxDateFormat.YearSegment.prototype.format = function(date) { 767 var year = String(date.getFullYear()); 768 return this._s.length < 4 ? year.substr(year.length - 2) : AjxFormat._zeroPad(year, this._s.length); 769 }; 770 771 AjxDateFormat.YearSegment.prototype.parse = function(object, s, index) { 772 var fixedlen = this._getFixedLength(); 773 var nindex = AjxFormat.Segment._parseInt(object, "year", 0, s, index, fixedlen); 774 // adjust 2-digit years 775 if (nindex - index == 2) { 776 if (!AjxDateFormat._2digitStartYear) { 777 AjxDateFormat._2digitStartYear = parseInt(AjxMsg.dateParsing2DigitStartYear); 778 } 779 var syear = AjxDateFormat._2digitStartYear; 780 var pyear = parseInt(s.substr(index,2), 10); 781 var century = (Math.floor(syear / 100) + (pyear < (syear % 100) ? 1 : 0)) * 100; 782 var year = century + pyear; 783 object.year = year; 784 } 785 return nindex; 786 }; 787 788 // 789 // Date month segment class 790 // 791 792 AjxDateFormat.MonthSegment = function(format, s) { 793 if (arguments.length == 0) return; 794 AjxDateFormat.DateSegment.call(this, format, s); 795 }; 796 AjxDateFormat.MonthSegment.prototype = new AjxDateFormat.DateSegment; 797 AjxDateFormat.MonthSegment.prototype.constructor = AjxDateFormat.MonthSegment; 798 799 AjxDateFormat.MonthSegment.prototype.toString = function() { 800 return "dateMonth: \""+this._s+'"'; 801 }; 802 803 // Static functions 804 805 AjxDateFormat.MonthSegment.initialize = function() { 806 AjxDateFormat.MonthSegment.MONTHS = {}; 807 AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.SHORT] = [ 808 AjxMsg.monthJanShort, AjxMsg.monthFebShort, AjxMsg.monthMarShort, 809 AjxMsg.monthAprShort, AjxMsg.monthMayShort, AjxMsg.monthJunShort, 810 AjxMsg.monthJulShort, AjxMsg.monthAugShort, AjxMsg.monthSepShort, 811 AjxMsg.monthOctShort, AjxMsg.monthNovShort, AjxMsg.monthDecShort 812 ]; 813 AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.MEDIUM] = [ 814 I18nMsg.monthJanMedium, I18nMsg.monthFebMedium, I18nMsg.monthMarMedium, 815 I18nMsg.monthAprMedium, I18nMsg.monthMayMedium, I18nMsg.monthJunMedium, 816 I18nMsg.monthJulMedium, I18nMsg.monthAugMedium, I18nMsg.monthSepMedium, 817 I18nMsg.monthOctMedium, I18nMsg.monthNovMedium, I18nMsg.monthDecMedium 818 ]; 819 AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.LONG] = [ 820 I18nMsg.monthJanLong, I18nMsg.monthFebLong, I18nMsg.monthMarLong, 821 I18nMsg.monthAprLong, I18nMsg.monthMayLong, I18nMsg.monthJunLong, 822 I18nMsg.monthJulLong, I18nMsg.monthAugLong, I18nMsg.monthSepLong, 823 I18nMsg.monthOctLong, I18nMsg.monthNovLong, I18nMsg.monthDecLong 824 ]; 825 }; 826 827 // Public methods 828 829 AjxDateFormat.MonthSegment.prototype.format = function(date) { 830 var month = date.getMonth(); 831 switch (this._s.length) { 832 case 1: return String(month + 1); 833 case 2: return AjxFormat._zeroPad(month + 1, 2); 834 case 3: return AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.MEDIUM][month]; 835 case 5: return AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.SHORT][month]; 836 } 837 return AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.LONG][month]; 838 }; 839 840 AjxDateFormat.MonthSegment.prototype.parse = function(object, s, index) { 841 var months; 842 switch (this._s.length) { 843 case 3: 844 months = AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.MEDIUM]; 845 case 4: 846 months = months || AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.LONG]; 847 case 5: { 848 months = months || AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.SHORT]; 849 var nindex = AjxFormat.Segment._parseLiterals(object, "month", 0, months, s, index); 850 if (nindex == -1) { 851 throw new AjxFormat.ParsingException(this._parent, this, "no match"); // I18n 852 } 853 return nindex; 854 } 855 } 856 var fixedlen = this._getFixedLength(); 857 return AjxFormat.Segment._parseInt(object, "month", -1, s, index, fixedlen); 858 }; 859 860 // 861 // Date week segment class 862 // 863 864 AjxDateFormat.WeekSegment = function(format, s) { 865 if (arguments.length == 0) return; 866 AjxDateFormat.DateSegment.call(this, format, s); 867 }; 868 AjxDateFormat.WeekSegment.prototype = new AjxDateFormat.DateSegment; 869 AjxDateFormat.WeekSegment.prototype.constructor = AjxDateFormat.WeekSegment; 870 871 AjxDateFormat.WeekSegment.prototype.toString = function() { 872 return "weekMonth: \""+this._s+'"'; 873 }; 874 875 // Public methods 876 877 AjxDateFormat.WeekSegment.prototype.format = function(date) { 878 var year = date.getYear(); 879 var month = date.getMonth(); 880 var day = date.getDate(); 881 882 var ofYear = /w/.test(this._s); 883 var date2 = new Date(year, ofYear ? 0 : month, 1); 884 885 var week = 0; 886 while (true) { 887 week++; 888 if (date2.getMonth() > month || (date2.getMonth() == month && date2.getDate() >= day)) { 889 break; 890 } 891 date2.setDate(date2.getDate() + 7); 892 } 893 894 return AjxFormat._zeroPad(week, this._s.length); 895 }; 896 897 AjxDateFormat.WeekSegment.prototype.parse = function(object, s, index) { 898 var fixedlen = this._getFixedLength(); 899 return AjxFormat.Segment._parseInt(null, null, 0, s, index, fixedlen) 900 }; 901 902 // 903 // Date day segment class 904 // 905 906 AjxDateFormat.DaySegment = function(format, s) { 907 if (arguments.length == 0) return; 908 AjxDateFormat.DateSegment.call(this, format, s); 909 }; 910 AjxDateFormat.DaySegment.prototype = new AjxDateFormat.DateSegment; 911 AjxDateFormat.DaySegment.prototype.constructor = AjxDateFormat.DaySegment; 912 913 AjxDateFormat.DaySegment.prototype.toString = function() { 914 return "dateDay: \""+this._s+'"'; 915 }; 916 917 // Public methods 918 919 AjxDateFormat.DaySegment.prototype.format = function(date) { 920 var month = date.getMonth(); 921 var day = date.getDate(); 922 if (/D/.test(this._s) && month > 0) { 923 var year = date.getYear(); 924 do { 925 // set date to first day of month and then go back one day 926 var date2 = new Date(year, month, 1); 927 date2.setDate(0); 928 929 day += date2.getDate(); 930 month--; 931 } while (month > 0); 932 } 933 return AjxFormat._zeroPad(day, this._s.length); 934 }; 935 936 AjxDateFormat.DaySegment.prototype.parse = function(object, s, index) { 937 var fixedlen = this._getFixedLength(); 938 var dayofmonth = function(day) { 939 if(day > AjxDateUtil.MAX_DAYS_PER_MONTH) { throw new AjxFormat.ParsingException(this._parent, this, "number too long"); } 940 else { object["dayofmonth"] = day; } 941 }; 942 var pname = /D/.test(this._s) ? "dayofyear" : dayofmonth; 943 return AjxFormat.Segment._parseInt(object, pname, 0, s, index, fixedlen); 944 }; 945 946 // 947 // Date weekday segment class 948 // 949 950 AjxDateFormat.WeekdaySegment = function(format, s) { 951 if (arguments.length == 0) return; 952 AjxDateFormat.DateSegment.call(this, format, s); 953 }; 954 AjxDateFormat.WeekdaySegment.prototype = new AjxDateFormat.DateSegment; 955 AjxDateFormat.WeekdaySegment.prototype.constructor = AjxDateFormat.WeekdaySegment; 956 957 AjxDateFormat.DaySegment.prototype.toString = function() { 958 return "dateDay: \""+this._s+'"'; 959 }; 960 961 // Static functions 962 963 AjxDateFormat.WeekdaySegment.initialize = function() { 964 AjxDateFormat.WeekdaySegment.WEEKDAYS = {}; 965 // NOTE: The short names aren't available in Java so we have to define them. 966 AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.SHORT] = [ 967 AjxMsg.weekdaySunShort, AjxMsg.weekdayMonShort, AjxMsg.weekdayTueShort, 968 AjxMsg.weekdayWedShort, AjxMsg.weekdayThuShort, AjxMsg.weekdayFriShort, 969 AjxMsg.weekdaySatShort 970 ]; 971 AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.MEDIUM] = [ 972 I18nMsg.weekdaySunMedium, I18nMsg.weekdayMonMedium, I18nMsg.weekdayTueMedium, 973 I18nMsg.weekdayWedMedium, I18nMsg.weekdayThuMedium, I18nMsg.weekdayFriMedium, 974 I18nMsg.weekdaySatMedium 975 ]; 976 AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.LONG] = [ 977 I18nMsg.weekdaySunLong, I18nMsg.weekdayMonLong, I18nMsg.weekdayTueLong, 978 I18nMsg.weekdayWedLong, I18nMsg.weekdayThuLong, I18nMsg.weekdayFriLong, 979 I18nMsg.weekdaySatLong 980 ]; 981 }; 982 983 // Public methods 984 985 AjxDateFormat.WeekdaySegment.prototype.format = function(date) { 986 var weekday = date.getDay(); 987 if (/E/.test(this._s)) { 988 var style; 989 switch (this._s.length) { 990 case 4: style = AjxDateFormat.LONG; break; 991 case 5: style = AjxDateFormat.SHORT; break; 992 default: style = AjxDateFormat.MEDIUM; 993 } 994 return AjxDateFormat.WeekdaySegment.WEEKDAYS[style][weekday]; 995 } 996 return AjxFormat._zeroPad(weekday, this._s.length); 997 }; 998 999 AjxDateFormat.WeekdaySegment.prototype.parse = function(object, s, index) { 1000 var weekdays; 1001 switch (this._s.length) { 1002 case 3: 1003 weekdays = AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.MEDIUM]; 1004 case 4: 1005 weekdays = weekdays || AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.LONG]; 1006 case 5: { 1007 weekdays = weekdays || AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.SHORT]; 1008 var nindex = AjxFormat.Segment._parseLiterals(null, null, 0, weekdays, s, index); 1009 if (nindex == -1) { 1010 throw new AjxFormat.ParsingException(this._parent, this, "no match"); // I18n 1011 } 1012 return nindex; 1013 } 1014 } 1015 var fixedlen = this._getFixedLength(); 1016 return AjxFormat.Segment._parseInt(null, null, 0, s, index, fixedlen); 1017 }; 1018 1019 // 1020 // Time segment class 1021 // 1022 1023 AjxDateFormat.TimeSegment = function(format, s) { 1024 if (arguments.length == 0) return; 1025 AjxFormat.Segment.call(this, format, s); 1026 }; 1027 AjxDateFormat.TimeSegment.prototype = new AjxFormat.Segment; 1028 AjxDateFormat.TimeSegment.prototype.constructor = AjxDateFormat.TimeSegment; 1029 1030 // 1031 // Time hour segment class 1032 // 1033 1034 AjxDateFormat.HourSegment = function(format, s) { 1035 if (arguments.length == 0) return; 1036 AjxFormat.Segment.call(this, format, s); 1037 }; 1038 AjxDateFormat.HourSegment.prototype = new AjxDateFormat.TimeSegment; 1039 AjxDateFormat.HourSegment.prototype.constructor = AjxDateFormat.HourSegment; 1040 1041 AjxDateFormat.HourSegment.prototype.toString = function() { 1042 return "timeHour: \""+this._s+'"'; 1043 }; 1044 1045 // Public methods 1046 1047 AjxDateFormat.HourSegment.prototype.format = function(date) { 1048 //getHours returns 0-23 1049 var hours = date.getHours(); 1050 if (hours > 12 && /[hK]/.test(this._s)) { 1051 //changing 13-23 to 1-11 (pm) 1052 hours -= 12; 1053 } 1054 else if (hours == 0 && /[h]/.test(this._s)) { 1055 //h: 1-12 am/pm 1056 hours = 12; 1057 } 1058 else if (hours === 12 && /K/.test(this._s)) { 1059 //K: 0-11 am/pm 1060 hours = 0; 1061 } 1062 else if (hours === 0 && /k/.test(this._s)) { 1063 //k: 1-24 1064 hours = 24; 1065 } 1066 /*** 1067 // NOTE: This is commented out to match the Java formatter output 1068 // but from the comments for these meta-chars, it doesn't 1069 // seem right. 1070 if (/[Hk]/.test(this._s)) { 1071 hours--; 1072 } 1073 /***/ 1074 return AjxFormat._zeroPad(hours, this._s.length); 1075 }; 1076 1077 AjxDateFormat.HourSegment.prototype.parse = function(object, s, index) { 1078 var fixedlen = this._getFixedLength(); 1079 return AjxFormat.Segment._parseInt(object, "hours", 0, s, index, fixedlen); 1080 }; 1081 1082 // 1083 // Time minute segment class 1084 // 1085 1086 AjxDateFormat.MinuteSegment = function(format, s) { 1087 if (arguments.length == 0) return; 1088 AjxFormat.Segment.call(this, format, s); 1089 }; 1090 AjxDateFormat.MinuteSegment.prototype = new AjxDateFormat.TimeSegment; 1091 AjxDateFormat.MinuteSegment.prototype.constructor = AjxDateFormat.MinuteSegment; 1092 1093 AjxDateFormat.MinuteSegment.prototype.toString = function() { 1094 return "timeMinute: \""+this._s+'"'; 1095 }; 1096 1097 // Public methods 1098 1099 AjxDateFormat.MinuteSegment.prototype.format = function(date) { 1100 var minutes = date.getMinutes(); 1101 return AjxFormat._zeroPad(minutes, this._s.length); 1102 }; 1103 1104 AjxDateFormat.MinuteSegment.prototype.parse = function(object, s, index) { 1105 var fixedlen = this._getFixedLength(); 1106 return AjxFormat.Segment._parseInt(object, "minutes", 0, s, index, fixedlen); 1107 }; 1108 1109 // 1110 // Time second segment class 1111 // 1112 1113 AjxDateFormat.SecondSegment = function(format, s) { 1114 if (arguments.length == 0) return; 1115 AjxFormat.Segment.call(this, format, s); 1116 }; 1117 AjxDateFormat.SecondSegment.prototype = new AjxDateFormat.TimeSegment; 1118 AjxDateFormat.SecondSegment.prototype.constructor = AjxDateFormat.SecondSegment; 1119 1120 AjxDateFormat.SecondSegment.prototype.toString = function() { 1121 return "timeSecond: \""+this._s+'"'; 1122 }; 1123 1124 // Public methods 1125 1126 AjxDateFormat.SecondSegment.prototype.format = function(date) { 1127 var minutes = /s/.test(this._s) ? date.getSeconds() : date.getMilliseconds(); 1128 return AjxFormat._zeroPad(minutes, this._s.length); 1129 }; 1130 1131 AjxDateFormat.SecondSegment.prototype.parse = function(object, s, index) { 1132 var isSeconds = /s/.test(this._s); 1133 var pname = isSeconds ? "seconds" : "milliseconds"; 1134 var fixedlen = isSeconds ? this._getFixedLength() : 0; 1135 return AjxFormat.Segment._parseInt(object, pname, 0, s, index, fixedlen); 1136 }; 1137 1138 // 1139 // Time am/pm segment class 1140 // 1141 1142 AjxDateFormat.AmPmSegment = function(format, s) { 1143 if (arguments.length == 0) return; 1144 AjxFormat.Segment.call(this, format, s); 1145 }; 1146 AjxDateFormat.AmPmSegment.prototype = new AjxDateFormat.TimeSegment; 1147 AjxDateFormat.AmPmSegment.prototype.constructor = AjxDateFormat.AmPmSegment; 1148 1149 AjxDateFormat.AmPmSegment.prototype.toString = function() { 1150 return "timeAmPm: \""+this._s+'"'; 1151 }; 1152 1153 // Public methods 1154 1155 AjxDateFormat.AmPmSegment.prototype.format = function(date) { 1156 var hours = date.getHours(); 1157 return hours < 12 ? I18nMsg.periodAm : I18nMsg.periodPm; 1158 }; 1159 1160 AjxDateFormat.AmPmSegment.prototype.parse = function(object, s, index) { 1161 var periods = [ 1162 I18nMsg.periodAm.toLowerCase(), I18nMsg.periodPm.toLowerCase(), 1163 I18nMsg.periodAm.toUpperCase(), I18nMsg.periodPm.toUpperCase() 1164 ]; 1165 var nindex = AjxFormat.Segment._parseLiterals(object, "ampm", 0, periods, s, index); 1166 if (nindex == -1) { 1167 throw new AjxFormat.ParsingException(this._parent, this, "no match"); // I18n 1168 } 1169 object.ampm = object.ampm % 2; 1170 return nindex; 1171 }; 1172 1173 // 1174 // Time timezone segment class 1175 // 1176 1177 AjxDateFormat.TimezoneSegment = function(format, s) { 1178 if (arguments.length == 0) return; 1179 AjxFormat.Segment.call(this, format, s); 1180 }; 1181 AjxDateFormat.TimezoneSegment.prototype = new AjxDateFormat.TimeSegment; 1182 AjxDateFormat.TimezoneSegment.prototype.constructor = AjxDateFormat.TimezoneSegment; 1183 1184 AjxDateFormat.TimezoneSegment.prototype.toString = function() { 1185 return "timeTimezone: \""+this._s+'"'; 1186 }; 1187 1188 // Public methods 1189 1190 AjxDateFormat.TimezoneSegment.prototype.format = function(date) { 1191 var clientId = date.timezone || AjxTimezone.DEFAULT; 1192 if (/Z/.test(this._s)) { 1193 return AjxTimezone.getShortName(clientId); 1194 } 1195 return this._s.length < 4 ? AjxTimezone.getMediumName(clientId) : AjxTimezone.getLongName(clientId); 1196 }; 1197 1198 // 1199 // Message format class 1200 // 1201 1202 /** 1203 * Message formatter based on the Java <code>MessageFormat</code> class. 1204 * <p> 1205 * <strong>Note:</strong> 1206 * This implementation augments Java's <code>MessageFormat</code> patterns 1207 * to support list formatting. The following forms differ from the originals 1208 * defined in the JavaDoc for <code>MessageFormat</code>: 1209 * <pre> 1210 * <i>FormatElement</i>: 1211 * { <i>ArgumentIndex</i> } 1212 * { <i>ArgumentIndex</i> , <i>FormatType</i> } 1213 * { <i>ArgumentIndex</i> , <i>ListFormatType</i> , <i>FormatType</i> } 1214 * { <i>ArgumentIndex</i> , <i>FormatType</i> , <i>FormatStyle</i> } 1215 * { <i>ArgumentIndex</i> , <i>ListFormatType , <i>FormatType</i> , <i>FormatStyle</i> } 1216 * <i>ListFormatType</i>: 1217 * list 1218 * </pre> 1219 * 1220 * @param {string} pattern The message format pattern. 1221 * 1222 * @class 1223 * @constructor 1224 */ 1225 AjxMessageFormat = function(pattern) { 1226 if (arguments.length == 0) { return; } 1227 AjxFormat.call(this, pattern); 1228 if (pattern == null) { return; } 1229 for (var i = 0; i < pattern.length; i++) { 1230 // literal 1231 var c = pattern.charAt(i); 1232 if (c == "'") { 1233 if (i + 1 < pattern.length && pattern.charAt(i + 1) == "'") { 1234 var segment = new AjxFormat.TextSegment(this, "'"); 1235 this._segments.push(segment); 1236 i++; 1237 continue; 1238 } 1239 var head = i + 1; 1240 for (i++ ; i < pattern.length; i++) { 1241 var c = pattern.charAt(i); 1242 if (c == "'") { 1243 if (i + 1 < pattern.length && pattern.charAt(i + 1) == "'") { 1244 pattern = pattern.substr(0, i) + pattern.substr(i + 1); 1245 } 1246 else { 1247 break; 1248 } 1249 } 1250 } 1251 // if (i == pattern.length) { 1252 // NOTE: try to avoid silent errors 1253 // throw new AjxFormat.FormatException(this, "unterminated string literal"); // I18n 1254 //) } 1255 var tail = i; 1256 var segment = new AjxFormat.TextSegment(this, pattern.substring(head, tail)); 1257 this._segments.push(segment); 1258 continue; 1259 } 1260 1261 // non-meta chars 1262 var head = i; 1263 while(i < pattern.length) { 1264 c = pattern.charAt(i); 1265 if (c == '{' || c == "'") { 1266 break; 1267 } 1268 i++; 1269 } 1270 var tail = i; 1271 if (head != tail) { 1272 var segment = new AjxFormat.TextSegment(this, pattern.substring(head, tail)); 1273 this._segments.push(segment); 1274 i--; 1275 continue; 1276 } 1277 1278 // meta char 1279 var head = i + 1; 1280 var depth = 0; 1281 while(++i < pattern.length) { 1282 var c = pattern.charAt(i); 1283 if (c == '{') { 1284 depth++; 1285 } 1286 else if (c == '}') { 1287 if (depth == 0) { 1288 break; 1289 } 1290 depth--; 1291 } 1292 } 1293 var tail = i; 1294 var count = tail - head; 1295 var field = pattern.substr(head, count); 1296 var segment = new AjxMessageFormat.MessageSegment(this, field); 1297 if (segment != null) { 1298 this._segments.push(segment); 1299 } 1300 } 1301 } 1302 AjxMessageFormat.prototype = new AjxFormat; 1303 AjxMessageFormat.prototype.constructor = AjxMessageFormat; 1304 1305 AjxMessageFormat.prototype.toString = function() { 1306 return "[AjxMessageFormat: "+AjxFormat.prototype.toString.call(this)+"]"; 1307 }; 1308 1309 // Static methods 1310 1311 /** 1312 * Format a message pattern with replacement parameters. Equivalent to 1313 * <code>new AjxMessageFormat(pattern).format(params)</code>. 1314 * 1315 * @param {string} pattern Message pattern. 1316 * @param {Array} params Replacement parameters. 1317 * @return {string} Formatted message. 1318 */ 1319 AjxMessageFormat.format = function(pattern, params) { 1320 return new AjxMessageFormat(pattern).format(params); 1321 }; 1322 1323 // Public methods 1324 1325 /** 1326 * Format with replacement parameters. 1327 * 1328 * @param {Array} params Replacement parameters. 1329 * @return {string} Formatted message. 1330 */ 1331 AjxMessageFormat.prototype.format = function(params) { 1332 if (!(params instanceof Array)) { 1333 params = [ params ]; 1334 } 1335 return AjxFormat.prototype.format.call(this, params); 1336 }; 1337 1338 AjxMessageFormat.prototype.getFormats = function() { 1339 var formats = []; 1340 for (var i = 0; i < this._segments.length; i++) { 1341 var segment = this._segments[i]; 1342 if (segment instanceof AjxMessageFormat.MessageSegment) { 1343 formats.push(segment.getSegmentFormat()); 1344 } 1345 } 1346 return formats; 1347 }; 1348 AjxMessageFormat.prototype.getFormatsByArgumentIndex = function() { 1349 var formats = []; 1350 for (var i = 0; i < this._segments.length; i++) { 1351 var segment = this._segments[i]; 1352 if (segment instanceof AjxMessageFormat.MessageSegment) { 1353 formats[segment.getIndex()] = segment.getSegmentFormat(); 1354 } 1355 } 1356 return formats; 1357 }; 1358 1359 // 1360 // AjxMessageFormat.MessageSegment class 1361 // 1362 1363 AjxMessageFormat.MessageSegment = function(format, s) { 1364 if (arguments.length == 0) return; 1365 AjxFormat.Segment.call(this, format, s); 1366 var parts = AjxMessageFormat.MessageSegment._split(s, ','); 1367 this._index = Number(parts[0]); 1368 this._type = parts[1] || "string"; 1369 this._style = parts[2]; 1370 if (this._type == "list") { 1371 this._isList = true; 1372 this._type = parts[2] || "string"; 1373 this._style = parts[3]; 1374 } 1375 switch (this._type) { 1376 case "number": { 1377 switch (this._style) { 1378 case "integer": this._formatter = AjxNumberFormat.getIntegerInstance(); break; 1379 case "currency": this._formatter = AjxNumberFormat.getCurrencyInstance(); break; 1380 case "percent": this._formatter = AjxNumberFormat.getPercentInstance(); break; 1381 default: this._formatter = this._style == null ? AjxNumberFormat.getInstance() : new AjxNumberFormat(this._style); 1382 } 1383 break; 1384 } 1385 case "date": case "time": { 1386 var func = this._type == "date" ? AjxDateFormat.getDateInstance : AjxDateFormat.getTimeInstance; 1387 switch (this._style) { 1388 case "short": this._formatter = func(AjxDateFormat.SHORT); break; 1389 case "medium": this._formatter = func(AjxDateFormat.MEDIUM); break; 1390 case "long": this._formatter = func(AjxDateFormat.LONG); break; 1391 case "full": this._formatter = func(AjxDateFormat.FULL); break; 1392 default: this._formatter = this._style == null ? func(AjxDateFormat.DEFAULT) : new AjxDateFormat(this._style); 1393 } 1394 break; 1395 } 1396 case "choice": { 1397 this._formatter = new AjxChoiceFormat(this._style); 1398 break; 1399 } 1400 } 1401 if (this._isList) { 1402 this._formatter = new AjxListFormat(this._formatter); 1403 } 1404 }; 1405 AjxMessageFormat.MessageSegment.prototype = new AjxFormat.Segment; 1406 AjxMessageFormat.MessageSegment.prototype.constructor = AjxMessageFormat.MessageSegment; 1407 1408 AjxMessageFormat.MessageSegment.prototype.toString = function() { 1409 var a = [ "message: \"", this._s, "\", index: ", this._index ]; 1410 if (this._isList) a.push(", list: ", this._isList); 1411 if (this._type) a.push(", type: ", this._type); 1412 if (this._style) a.push(", style: ", this._style); 1413 if (this._formatter) a.push(", formatter: ", this._formatter.toString()); 1414 return a.join(""); 1415 }; 1416 1417 // Data 1418 1419 AjxMessageFormat.MessageSegment.prototype._isList = false; 1420 1421 // Public methods 1422 1423 AjxMessageFormat.MessageSegment.prototype.format = function(args) { 1424 var object = args[this._index]; 1425 if (this._formatter instanceof AjxChoiceFormat) { 1426 return this._formatter.format(args, this._index); 1427 } 1428 return this._formatter ? this._formatter.format(object) : String(object); 1429 }; 1430 1431 AjxMessageFormat.MessageSegment.prototype.getIndex = function() { 1432 return this._index; 1433 }; 1434 AjxMessageFormat.MessageSegment.prototype.getType = function() { 1435 return this._type; 1436 }; 1437 AjxMessageFormat.MessageSegment.prototype.getStyle = function() { 1438 return this._style; 1439 }; 1440 AjxMessageFormat.MessageSegment.prototype.getSegmentFormat = function() { 1441 return this._formatter; 1442 }; 1443 1444 // Protected static functions 1445 1446 AjxMessageFormat.MessageSegment._split = function(s, delimiter) { 1447 var parts = []; 1448 var head = 0; 1449 var tail; 1450 var depth = 0; 1451 for (tail = 0; tail < s.length; tail++) { 1452 var c = s.charAt(tail); 1453 if (c == '{') { 1454 depth++; 1455 } 1456 else if (c == '}') { 1457 depth--; 1458 } 1459 else if (c == delimiter && depth == 0) { 1460 parts.push(s.substring(head, tail)); 1461 head = tail + 1; 1462 } 1463 } 1464 if (tail > head) { 1465 parts.push(s.substring(head, tail)); 1466 } 1467 return parts; 1468 }; 1469 1470 1471 // 1472 // AjxNumberFormat class 1473 // 1474 1475 /** 1476 * Number formatter based on Java's <code>DecimalFormat</code>. 1477 * 1478 * @param {string} pattern The number pattern. 1479 * @param {boolean} skipNegFormat Specifies whether to skip the generation of this 1480 * format's negative value formatter. 1481 * <p> 1482 * <strong>Note:</strong> 1483 * This parameter is only used by the implementation 1484 * and should not be passed by application code 1485 * instantiating a custom number format. 1486 * @class 1487 * @constructor 1488 */ 1489 AjxNumberFormat = function(pattern, skipNegFormat) { 1490 if (arguments.length == 0) { return; } 1491 AjxFormat.call(this, pattern); 1492 if (!pattern) { return; } 1493 1494 var patterns = pattern.split(/;/); 1495 var pattern = patterns[0]; 1496 1497 // parse prefix 1498 var i = 0; 1499 var results = this.__parseStatic(pattern, i); 1500 i = results.offset; 1501 var hasPrefix = results.text != ""; 1502 if (hasPrefix) { 1503 this._segments.push(new AjxFormat.TextSegment(this, results.text)); 1504 } 1505 1506 // parse number descriptor 1507 var start = i; 1508 while (i < pattern.length && 1509 AjxNumberFormat._META_CHARS.indexOf(pattern.charAt(i)) != -1) { 1510 i++; 1511 } 1512 var end = i; 1513 1514 var numPattern = pattern.substring(start, end); 1515 var e = numPattern.indexOf('E'); 1516 var expon = e != -1 ? numPattern.substring(e + 1) : null; 1517 if (expon) { 1518 numPattern = numPattern.substring(0, e); 1519 this._showExponent = true; 1520 } 1521 1522 var dot = numPattern.indexOf(I18nMsg.numberSeparatorDecimal !='' ? I18nMsg.numberSeparatorDecimal : '.'); 1523 var whole = dot != -1 ? numPattern.substring(0, dot) : numPattern; 1524 if (whole) { 1525 var comma = whole.lastIndexOf(I18nMsg.numberSeparatorGrouping !='' ? I18nMsg.numberSeparatorGrouping : ','); 1526 if (comma != -1) { 1527 this._groupingOffset = whole.length - comma - 1; 1528 } 1529 whole = whole.replace(/[^#0]/g,""); 1530 var zero = whole.indexOf('0'); 1531 if (zero != -1) { 1532 this._minIntDigits = whole.length - zero; 1533 } 1534 this._maxIntDigits = whole.length; 1535 } 1536 1537 var fract = dot != -1 ? numPattern.substring(dot + 1) : null; 1538 if (fract) { 1539 var zero = fract.lastIndexOf('0'); 1540 if (zero != -1) { 1541 this._minFracDigits = zero + 1; 1542 } 1543 this._maxFracDigits = fract.replace(/[^#0]/g,"").length; 1544 } 1545 1546 this._segments.push(new AjxNumberFormat.NumberSegment(this, numPattern)); 1547 1548 // parse suffix 1549 var results = this.__parseStatic(pattern, i); 1550 i = results.offset; 1551 if (results.text != "") { 1552 this._segments.push(new AjxFormat.TextSegment(this, results.text)); 1553 } 1554 1555 // add negative formatter 1556 if (skipNegFormat) return; 1557 1558 if (patterns.length > 1) { 1559 var pattern = patterns[1]; 1560 this._negativeFormatter = new AjxNumberFormat(pattern, true); 1561 } 1562 else { 1563 // no negative pattern; insert minus sign before number segment 1564 var formatter = new AjxNumberFormat(""); 1565 formatter._segments = formatter._segments.concat(this._segments); 1566 1567 var index = hasPrefix ? 1 : 0; 1568 var minus = new AjxFormat.TextSegment(formatter, I18nMsg.numberSignMinus); 1569 formatter._segments.splice(index, 0, minus); 1570 1571 this._negativeFormatter = formatter; 1572 } 1573 } 1574 AjxNumberFormat.prototype = new AjxFormat; 1575 AjxNumberFormat.prototype.constructor = AjxNumberFormat; 1576 1577 AjxNumberFormat.prototype.toString = function() { 1578 var array = [ 1579 "[AjxNumberFormat: ", 1580 "formatter=", AjxFormat.prototype.toString.call(this) 1581 ]; 1582 if (this._negativeFormatter) { 1583 array.push(", negativeFormatter=", this._negativeFormatter.toString()); 1584 } 1585 array.push(']'); 1586 return array.join(""); 1587 }; 1588 1589 // Constants 1590 1591 AjxNumberFormat._NUMBER = "number"; 1592 AjxNumberFormat._INTEGER = "integer"; 1593 AjxNumberFormat._CURRENCY = "currency"; 1594 AjxNumberFormat._PERCENT = "percent"; 1595 1596 AjxNumberFormat._META_CHARS = "0#.,E"; 1597 1598 // Data 1599 1600 AjxNumberFormat.prototype._groupingOffset = Number.MAX_VALUE; 1601 AjxNumberFormat.prototype._minIntDigits = 1; 1602 AjxNumberFormat.prototype._isCurrency = false; 1603 AjxNumberFormat.prototype._isPercent = false; 1604 AjxNumberFormat.prototype._isPerMille = false; 1605 AjxNumberFormat.prototype._showExponent = false; 1606 1607 // Static functions 1608 1609 /** 1610 * Get general number formatter. 1611 * 1612 * @return {AjxNumberFormat} Number formatter. 1613 */ 1614 AjxNumberFormat.getInstance = function() { 1615 if (!AjxNumberFormat._FORMATTERS[AjxNumberFormat._NUMBER]) { 1616 AjxNumberFormat._FORMATTERS[AjxNumberFormat._NUMBER] = new AjxNumberFormat(I18nMsg.formatNumber); 1617 } 1618 return AjxNumberFormat._FORMATTERS[AjxNumberFormat._NUMBER]; 1619 }; 1620 /** 1621 * Get general number formatter. 1622 * <strong>Note:</strong> 1623 * Same as <code>AjxNumberFormat.getInstance()</code>. 1624 * 1625 * @return {AjxNumberFormat} Number formatter. 1626 */ 1627 AjxNumberFormat.getNumberInstance = AjxNumberFormat.getInstance; 1628 /** 1629 * Get currency number formatter. 1630 * 1631 * @return {AjxNumberFormat} Number formatter. 1632 */ 1633 AjxNumberFormat.getCurrencyInstance = function() { 1634 if (!AjxNumberFormat._FORMATTERS[AjxNumberFormat._CURRENCY]) { 1635 AjxNumberFormat._FORMATTERS[AjxNumberFormat._CURRENCY] = new AjxNumberFormat(I18nMsg.formatNumberCurrency); 1636 } 1637 return AjxNumberFormat._FORMATTERS[AjxNumberFormat._CURRENCY]; 1638 }; 1639 /** 1640 * Get integer number formatter. 1641 * 1642 * @return {AjxNumberFormat} Number formatter. 1643 */ 1644 AjxNumberFormat.getIntegerInstance = function() { 1645 if (!AjxNumberFormat._FORMATTERS[AjxNumberFormat._INTEGER]) { 1646 AjxNumberFormat._FORMATTERS[AjxNumberFormat._INTEGER] = new AjxNumberFormat(I18nMsg.formatNumberInteger); 1647 } 1648 return AjxNumberFormat._FORMATTERS[AjxNumberFormat._INTEGER]; 1649 }; 1650 /** 1651 * Get percent number formatter. 1652 * 1653 * @return {AjxNumberFormat} Number formatter. 1654 */ 1655 AjxNumberFormat.getPercentInstance = function() { 1656 if (!AjxNumberFormat._FORMATTERS[AjxNumberFormat._PERCENT]) { 1657 AjxNumberFormat._FORMATTERS[AjxNumberFormat._PERCENT] = new AjxNumberFormat(I18nMsg.formatNumberPercent); 1658 } 1659 return AjxNumberFormat._FORMATTERS[AjxNumberFormat._PERCENT]; 1660 }; 1661 1662 /** 1663 * Formats a number based on a given pattern. Equivalent to 1664 * <code>new AjxNumberFormat(pattern).format(number)</code>. 1665 * 1666 * @param {string} pattern The format pattern. 1667 * @param {number} number The number to format. 1668 * @return {string} The formatted number string. 1669 */ 1670 AjxNumberFormat.format = function(pattern, number) { 1671 return new AjxNumberFormat(pattern).format(number); 1672 }; 1673 1674 AjxNumberFormat.initialize = function() { 1675 AjxNumberFormat._FORMATTERS = {}; 1676 }; 1677 1678 // Public methods 1679 1680 /** 1681 * Formats a number. 1682 * 1683 * @param {number} number The number to format. 1684 * @return {string} The formatted number string. 1685 */ 1686 AjxNumberFormat.prototype.format = function(number) { 1687 if (number < 0 && this._negativeFormatter) { 1688 return this._negativeFormatter.format(number); 1689 } 1690 return AjxFormat.prototype.format.call(this, number); 1691 }; 1692 1693 // Private methods 1694 1695 AjxNumberFormat.prototype.__parseStatic = function(s, i) { 1696 var data = []; 1697 while (i < s.length) { 1698 var c = s.charAt(i++); 1699 if (AjxNumberFormat._META_CHARS.indexOf(c) != -1) { 1700 i--; 1701 break; 1702 } 1703 switch (c) { 1704 case "'": { 1705 var start = i; 1706 while (i < s.length && s.charAt(i++) != "'") { 1707 // do nothing 1708 } 1709 var end = i; 1710 c = end - start == 0 ? "'" : s.substring(start, end); 1711 break; 1712 } 1713 case '%': { 1714 c = I18nMsg.numberSignPercent; 1715 this._isPercent = true; 1716 break; 1717 } 1718 case '\u2030': { 1719 c = I18nMsg.numberSignPerMill; 1720 this._isPerMille = true; 1721 break; 1722 } 1723 case '\u00a4': { 1724 c = s.charAt(i) == '\u00a4' 1725 ? I18nMsg.currencyCode : I18nMsg.currencySymbol; 1726 this._isCurrency = true; 1727 break; 1728 } 1729 } 1730 data.push(c); 1731 } 1732 return { text: data.join(""), offset: i }; 1733 }; 1734 1735 // 1736 // AjxNumberFormat.NumberSegment class 1737 // 1738 1739 AjxNumberFormat.NumberSegment = function(format, s) { 1740 if (arguments.length == 0) return; 1741 AjxFormat.Segment.call(this, format, s); 1742 }; 1743 AjxNumberFormat.NumberSegment.prototype = new AjxFormat.Segment; 1744 AjxNumberFormat.NumberSegment.prototype.constructor = AjxNumberFormat.NumberSegment; 1745 1746 AjxNumberFormat.NumberSegment.prototype.toString = function() { 1747 return "number: \""+this._s+"\""; 1748 }; 1749 1750 // Public methods 1751 1752 AjxNumberFormat.NumberSegment.prototype.format = function(number) { 1753 // special values 1754 if (isNaN(number)) return I18nMsg.numberNaN; 1755 if (number === Number.NEGATIVE_INFINITY || number === Number.POSITIVE_INFINITY) { 1756 return I18nMsg.numberInfinity; 1757 } 1758 1759 // adjust value 1760 if (typeof number != "number") number = Number(number); 1761 number = Math.abs(number); // NOTE: minus sign is part of pattern 1762 if (this._parent._isPercent) number *= 100; 1763 else if (this._parent._isPerMille) number *= 1000; 1764 1765 // format 1766 var s = this._parent._showExponent 1767 ? number.toExponential(this._parent._maxFracDigits).toUpperCase().replace(/E\+/,"E") 1768 : number.toFixed(this._parent._maxFracDigits || 0); 1769 s = this._normalize(s); 1770 return s; 1771 }; 1772 1773 // Protected methods 1774 1775 AjxNumberFormat.NumberSegment.prototype._normalize = function(s) { 1776 var match = s.split(/[\.Ee]/); 1777 1778 // normalize whole part 1779 var whole = match.shift(); 1780 if (whole.length < this._parent._minIntDigits) { 1781 whole = AjxFormat._zeroPad(whole, this._parent._minIntDigits, I18nMsg.numberZero); 1782 } 1783 if (whole.length > this._parent._groupingOffset) { 1784 var a = []; 1785 1786 var i = whole.length - this._parent._groupingOffset; 1787 while (i > 0) { 1788 a.unshift(whole.substr(i, this._parent._groupingOffset)); 1789 a.unshift(I18nMsg.numberSeparatorGrouping); 1790 i -= this._parent._groupingOffset; 1791 } 1792 a.unshift(whole.substring(0, i + this._parent._groupingOffset)); 1793 1794 whole = a.join(""); 1795 } 1796 1797 // normalize rest 1798 var fract = '0'; 1799 var expon; 1800 1801 if(s.match(/\./)) 1802 fract = match.shift(); 1803 else if(s.match(/\e/) || s.match(/\E/)) 1804 expon = match.shift(); 1805 1806 fract = fract.replace(/0+$/,""); 1807 if (fract.length < this._parent._minFracDigits) { 1808 fract = AjxFormat._zeroPad(fract, this._parent._minFracDigits, I18nMsg.numberZero, true); 1809 } 1810 1811 var a = [ whole ]; 1812 if (fract.length > 0) { 1813 var decimal = this._parent._isCurrency 1814 ? I18nMsg.numberSeparatorMoneyDecimal 1815 : I18nMsg.numberSeparatorDecimal; 1816 a.push(decimal, fract); 1817 } 1818 if (expon) { 1819 a.push('E', expon.replace(/^\+/,"")); 1820 } 1821 1822 // return normalize result 1823 return a.join(""); 1824 }; 1825 1826 // 1827 // AjxChoiceFormat class 1828 // 1829 1830 /** 1831 * Choice formatter typically used to format plurals. This class is 1832 * modeled after Java's <code>ChoiceFormat</code>. 1833 * <p> 1834 * The arguments passed to this constructor can be either: 1835 * <ul> 1836 * <li>A single argument that represents a string pattern that specifies the 1837 * limits and formats separated by pipe characters (|). 1838 * <li>Two arguments, an array of limits and and array of format patterns. 1839 * </ul> 1840 * <p> 1841 * For complete details, see the JavaDoc for java.text.ChoiceFormat. 1842 * 1843 * @param {string} pattern Format pattern. 1844 * 1845 * @class 1846 * @constructor 1847 */ 1848 AjxChoiceFormat = function(pattern) { 1849 if (arguments.length == 0) { return; } 1850 AjxFormat.call(this, pattern); 1851 if (pattern == null) { return; } 1852 var choices = pattern.split("|"); 1853 if (arguments.length == 1) { 1854 this._limits = new Array(choices.length); 1855 this._lessThan = new Array(choices.length); 1856 this._formats = new Array(choices.length); 1857 var regex = new RegExp("^([^#<\u2264]+)([#<\u2264])(.*)$"); 1858 for (var i = 0; i < choices.length; i++) { 1859 var choice = choices[i]; 1860 var results = regex.exec(choice); 1861 var limit = results[1]; 1862 var separator = results[2]; 1863 var message = results[3]; 1864 // set limit 1865 if (limit == '\u221E') { 1866 this._limits[i] = Number.POSITIVE_INFINITY; 1867 } 1868 else if (limit == '-\u221E') { 1869 this._limits[i] = Number.NEGATIVE_INFINITY; 1870 } 1871 else { 1872 this._limits[i] = parseFloat(limit); 1873 } 1874 // set less-than 1875 this._lessThan[i] = separator == '#' || separator == '\u2264'; 1876 // set format 1877 this._formats[i] = new AjxMessageFormat(message); 1878 } 1879 } 1880 else { 1881 this._limits = arguments[0]; 1882 this._lessThan = new Array(arguments[0].length); 1883 this._formats = arguments[1]; 1884 this._pattern = []; 1885 for (var i = 0; i < this._formats.length; i++) { 1886 if (i > 0) { 1887 this._pattern.push("|"); 1888 } 1889 this._pattern.push(this._limits[i], '#', this._formats[i]); 1890 this._lessThan[i] = false; 1891 this._formats[i] = new AjxMessageFormat(this._formats[i]); 1892 } 1893 this._pattern = this._pattern.join(""); 1894 } 1895 } 1896 AjxChoiceFormat.prototype = new AjxFormat; 1897 AjxChoiceFormat.prototype.constructor = AjxChoiceFormat; 1898 1899 AjxChoiceFormat.prototype.toString = function() { 1900 return [ 1901 "[AjxChoiceFormat: ", 1902 "limits={ ", this._limits.join(", "), " }, ", 1903 "formats={ ", this._formats.join(", "), " }, ", 1904 "lessThan={ ", this._lessThan.join(", "), " }]" 1905 ].join(""); 1906 }; 1907 1908 // Public methods 1909 1910 AjxChoiceFormat.prototype.getLimits = function() { 1911 return this._limits; 1912 }; 1913 AjxChoiceFormat.prototype.getFormats = function() { 1914 return this._formats; 1915 }; 1916 1917 /** 1918 * Format a number based on the choice pattern. 1919 * 1920 * @param {number|Array} number Specifies the number to format. If called 1921 * from a message segment, this argument is 1922 * the array of arguments passed to the message 1923 * formatter. 1924 * @param {number} index Optional. If called from a message format, 1925 * this argument is the index into the array that 1926 * is passed as the first argument. The value at 1927 * the index in the array is the number to format. 1928 * @return {string} The formatted choice. 1929 */ 1930 AjxChoiceFormat.prototype.format = function(number, index) { 1931 var num = number instanceof Array ? number[index] : number; 1932 var formatter; 1933 if (isNaN(num) || num < this._limits[0]) { 1934 formatter = this._formats[0]; 1935 } 1936 else { 1937 for (var i = 0; i < this._limits.length - 1; i++) { 1938 var a = this._limits[i]; 1939 var b = this._limits[i+1]; 1940 var aGEb = num >= a; 1941 var aLTb = this._lessThan[i+1] ? num < b : num <= b; 1942 if (aGEb && aLTb) { 1943 formatter = this._formats[i]; 1944 break; 1945 } 1946 } 1947 if (!formatter) { 1948 formatter = this._formats[this._formats.length-1]; 1949 } 1950 } 1951 return formatter.format(number); 1952 }; 1953 1954 // 1955 // AjxListFormat class 1956 // 1957 1958 /** 1959 * This class allows the user to format a list by applying a specific 1960 * format to each object in an array and joining the results. Each formatted 1961 * item in the list is separated by a list separator string. 1962 * <p> 1963 * <strong>Note:</strong> 1964 * This format is <em>not</em> one of the standard formatter classes 1965 * available in Java. 1966 * 1967 * @param {AjxFormat} formatter The formatter. 1968 * @param {string} separator Optional. The list separator string. If 1969 * not specified, <code>AjxMsg.separatorList</code> 1970 * is used. 1971 * @param {string} lastSeparator Optional. The list separator string for 1972 * the last item in the list (e.g. " and "). 1973 * If not specified, <code>AjxMsg.separatorListLast</code> 1974 * is used. 1975 * @param {string} twoSeparator Optional. Separator used if the list consists of 1976 * exactly two items. If not specified, <code>AjxMsg.listSeparatorTwo</code> 1977 * is used. 1978 */ 1979 AjxListFormat = function(formatter, separator, lastSeparator, twoSeparator) { 1980 AjxFormat.call(this, formatter ? formatter.toPattern() : ""); 1981 this._formatter = formatter; 1982 this._separator = separator || AjxMsg.listSeparator; 1983 this._lastSeparator = lastSeparator || AjxMsg.listSeparatorLast; 1984 this._twoSeparator = twoSeparator || AjxMsg.listSeparatorTwo; 1985 } 1986 AjxListFormat.prototype = new AjxFormat; 1987 AjxListFormat.prototype.constructor = AjxListFormat; 1988 1989 // Public methods 1990 1991 /** 1992 * Formats a list of items. 1993 * 1994 * @param {Array} array Array of objects to be formatted in a list. 1995 * @return {string} The formatted list string. 1996 */ 1997 AjxListFormat.prototype.format = function(array) { 1998 array = array instanceof Array ? array : [ array ]; 1999 var list = []; 2000 var num = array.length; 2001 for (var i = 0; i < num; i++) { 2002 if (i > 0) { 2003 list.push((i < num - 1) ? this._separator : (num == 2) ? 2004 this._twoSeparator : this._lastSeparator); 2005 } 2006 var item = array[i]; 2007 list.push(this._formatter ? this._formatter.format(item) : String(item)); 2008 } 2009 return list.join(""); 2010 }; 2011 2012 // 2013 // INITIALIZE 2014 // 2015 2016 AjxFormat.initialize(); 2017