1 package net.sf.saxon.functions; 2 import net.sf.saxon.Controller; 3 import net.sf.saxon.tinytree.CharSlice; 4 import net.sf.saxon.expr.Expression; 5 import net.sf.saxon.expr.StaticContext; 6 import net.sf.saxon.expr.Token; 7 import net.sf.saxon.expr.XPathContext; 8 import net.sf.saxon.om.*; 9 import net.sf.saxon.style.ExpressionContext; 10 import net.sf.saxon.trans.*; 11 import net.sf.saxon.value.*; 12 13 import java.io.Serializable ; 14 import java.math.BigDecimal ; 15 import java.math.BigInteger ; 16 import java.util.ArrayList ; 17 import java.util.List ; 18 19 22 23 public class FormatNumber2 extends SystemFunction implements XSLTFunction { 24 25 private NamespaceResolver nsContext = null; 26 28 private DecimalSymbols decimalFormatSymbols = null; 29 31 private transient String picture = null; 32 34 private SubPicture[] subPictures = null; 35 37 private boolean requireFixup = false; 38 40 private transient boolean checked = false; 41 43 44 public void checkArguments(StaticContext env) throws XPathException { 45 if (checked) return; 46 checked = true; 47 super.checkArguments(env); 48 if (argument[1] instanceof StringValue) { 49 picture = ((StringValue)argument[1]).getStringValue(); 51 } 52 if (argument.length==3) { 53 if (argument[2] instanceof StringValue) { 54 56 String qname = ((StringValue)argument[2]).getStringValue(); 57 String dfLocalName; 58 String dfURI; 59 try { 60 String [] parts = Name.getQNameParts(qname); 61 dfLocalName = parts[1]; 62 dfURI = env.getURIForPrefix(parts[0]); 63 } catch (QNameException e) { 64 throw new StaticError("Invalid decimal format name. " + e.getMessage()); 65 } 66 67 DecimalFormatManager dfm = ((ExpressionContext)env).getXSLStylesheet().getDecimalFormatManager(); 68 requireFixup = true; 69 dfm.registerUsage(dfURI, dfLocalName, this); 70 } else { 72 nsContext = env.getNamespaceResolver(); 74 } 75 } else { 76 if (env instanceof ExpressionContext) { 78 DecimalFormatManager dfm = ((ExpressionContext)env).getXSLStylesheet().getDecimalFormatManager(); 80 dfm.registerUsage("", "", this); 81 } else { 83 } 85 } 86 } 87 88 92 93 public void fixup(DecimalSymbols dfs) { 94 requireFixup = false; 96 decimalFormatSymbols = dfs; 97 if (picture != null) { 98 try { 99 subPictures = getSubPictures(picture, dfs); 100 } catch (XPathException err) { 101 subPictures = null; 102 } 104 } 105 } 106 107 112 113 private SubPicture[] getSubPictures(String picture, DecimalSymbols dfs) throws XPathException { 114 int[] picture4 = StringValue.expand(picture); 115 SubPicture[] pics = new SubPicture[2]; 116 if (picture4.length==0) { 117 DynamicError err = new DynamicError("format-number() picture is zero-length"); 118 err.setErrorCode("XTDE1310"); 119 throw err; 120 } 121 int sep = -1; 122 for (int c=0; c<picture4.length; c++) { 123 if (picture4[c] == dfs.patternSeparator) { 124 if (c==0) { 125 grumble("first subpicture is zero-length"); 126 } else if (sep >= 0) { 127 grumble("more than one pattern separator"); 128 } else if (sep == picture4.length-1) { 129 grumble("second subpicture is zero-length"); 130 } 131 sep = c; 132 } 133 } 134 135 if (sep<0) { 136 pics[0] = new SubPicture(picture4, dfs); 137 pics[1] = null; 138 } else { 139 int[] pic0 = new int[sep]; 140 System.arraycopy(picture4, 0, pic0, 0, sep); 141 int[] pic1 = new int[picture4.length - sep - 1]; 142 System.arraycopy(picture4, sep+1, pic1, 0, picture4.length - sep - 1); 143 pics[0] = new SubPicture(pic0, dfs); 144 pics[1] = new SubPicture(pic1, dfs); 145 } 146 return pics; 147 } 148 149 153 154 public Expression preEvaluate(StaticContext env) throws XPathException { 155 return this; 156 } 157 158 161 162 public String evaluateAsString(XPathContext context) throws XPathException { 163 164 int numArgs = argument.length; 165 Controller ctrl = context.getController(); 166 167 DecimalSymbols dfs = decimalFormatSymbols; 168 169 AtomicValue av0 = (AtomicValue)argument[0].evaluateItem(context); 170 if (av0 == null) { 171 av0 = DoubleValue.NaN; 172 }; 173 NumericValue number = (NumericValue)av0.getPrimitiveValue(); 174 175 if (dfs == null) { 176 if (requireFixup) { 178 dynamicError("Unknown decimal format name", "XTDE1280", context); 180 return null; 181 } 182 DecimalFormatManager dfm = ctrl.getExecutable().getDecimalFormatManager(); 183 if (numArgs==2) { 184 dfs = dfm.getDefaultDecimalFormat(); 185 } else { 186 String qname = argument[2].evaluateItem(context).getStringValue(); 188 try { 189 String [] parts = Name.getQNameParts(qname); 190 String localName = parts[1]; 191 String uri = nsContext.getURIForPrefix(parts[0], false); 192 if (uri==null) { 193 dynamicError("Namespace prefix '" + parts[0] + "' has not been defined", "XTDE1280", context); 194 return null; 195 } 196 dfs = dfm.getNamedDecimalFormat(uri, localName); 197 if (dfs==null) { 198 dynamicError( 199 "format-number function: decimal-format '" + localName + "' is not defined", "XTDE1280", context); 200 return null; 201 } 202 } catch (QNameException e) { 203 dynamicError("Invalid decimal format name. " + e.getMessage(), "XTDE1280", context); 204 } 205 } 206 } 207 SubPicture[] pics = subPictures; 208 if (pics == null) { 209 String format = argument[1].evaluateItem(context).getStringValue(); 210 pics = getSubPictures(format, dfs); 211 } 212 return formatNumber(number, pics, dfs).toString(); 213 } 214 215 218 219 public Item evaluateItem(XPathContext c) throws XPathException { 220 return new StringValue(evaluateAsString(c)); 221 } 222 223 226 227 private CharSequence formatNumber(NumericValue number, 228 SubPicture[] subPictures, 229 DecimalSymbols dfs) { 230 231 NumericValue absN = number; 232 SubPicture pic; 233 String minusSign = ""; 234 if (number.signum() < 0) { 235 absN = number.negate(); 236 if (subPictures[1]==null) { 237 pic = subPictures[0]; 238 minusSign = "" + unicodeChar(dfs.minusSign); 239 } else { 240 pic = subPictures[1]; 241 } 242 } else { 243 pic = subPictures[0]; 244 } 245 246 return pic.format(absN, dfs, minusSign); 247 } 248 249 private void grumble(String s) throws XPathException { 250 dynamicError("format-number picture: " + s, "XTDE1310", null); 251 } 252 253 256 257 private class SubPicture implements Serializable { 258 259 int minWholePartSize = 0; 260 int maxWholePartSize = 0; 261 int minFractionPartSize = 0; 262 int maxFractionPartSize = 0; 263 boolean isPercent = false; 264 boolean isPerMille = false; 265 String prefix = ""; 266 String suffix = ""; 267 int[] wholePartGroupingPositions = null; 268 int[] fractionalPartGroupingPositions = null; 269 270 public SubPicture(int[] pic, DecimalSymbols dfs) throws XPathException { 271 272 final int percentSign = dfs.percent; 273 final int perMilleSign = dfs.permill; 274 final int decimalSeparator = dfs.decimalSeparator; 275 final int groupingSeparator = dfs.groupingSeparator; 276 final int digitSign = dfs.digit; 277 final int zeroDigit = dfs.zeroDigit; 278 279 List wholePartPositions = null; 280 List fractionalPartPositions = null; 281 282 boolean foundDigit = false; 283 for (int i=0; i<pic.length; i++) { 284 if (pic[i] == digitSign || pic[i] == zeroDigit) { 285 foundDigit = true; 286 break; 287 } 288 } 289 if (!foundDigit) { 290 grumble("subpicture contains no digit or zero-digit sign"); 291 } 292 293 int phase = 0; 294 301 for (int i=0; i<pic.length; i++) { 302 int c = pic[i]; 303 304 if (c == percentSign || c == perMilleSign) { 305 if (isPercent || isPerMille) { 306 grumble("Cannot have more than one percent or per-mille character in a sub-picture"); 307 } 308 isPercent = (c==percentSign); 309 isPerMille = (c==perMilleSign); 310 switch (phase) { 311 case 0: 312 prefix += unicodeChar(c); 313 break; 314 case 1: 315 case 2: 316 case 3: 317 case 4: 318 case 5: 319 phase = 5; 320 suffix += unicodeChar(c); 321 break; 322 } 323 } else if (c == digitSign) { 324 switch (phase) { 325 case 0: 326 case 1: 327 phase = 1; 328 maxWholePartSize++; 329 break; 330 case 2: 331 grumble("Digit sign must not appear after a zero-digit sign in the integer part of a sub-picture"); 332 break; 333 case 3: 334 case 4: 335 phase = 4; 336 maxFractionPartSize++; 337 break; 338 case 5: 339 grumble("Passive character must not appear between active characters in a sub-picture"); 340 break; 341 } 342 } else if (c == zeroDigit) { 343 switch (phase) { 344 case 0: 345 case 1: 346 case 2: 347 phase = 2; 348 minWholePartSize++; 349 maxWholePartSize++; 350 break; 351 case 3: 352 minFractionPartSize++; 353 maxFractionPartSize++; 354 break; 355 case 4: 356 grumble("Zero digit sign must not appear after a digit sign in the fractional part of a sub-picture"); 357 break; 358 case 5: 359 grumble("Passive character must not appear between active characters in a sub-picture"); 360 break; 361 } 362 } else if (c == decimalSeparator) { 363 switch (phase) { 364 case 0: 365 case 1: 366 case 2: 367 phase = 3; 368 break; 369 case 3: 370 case 4: 371 case 5: 372 grumble("There must only be one decimal separator in a sub-picture"); 373 break; 374 } 375 } else if (c == groupingSeparator) { 376 switch (phase) { 377 case 0: 378 case 1: 379 case 2: 380 if (wholePartPositions == null) { 381 wholePartPositions = new ArrayList (3); 382 } 383 wholePartPositions.add(new Integer (maxWholePartSize)); 384 break; 386 case 3: 387 case 4: 388 if (maxFractionPartSize == 0) { 389 grumble("Grouping separator cannot be adjacent to decimal separator"); 390 } 391 if (fractionalPartPositions == null) { 392 fractionalPartPositions = new ArrayList (3); 393 } 394 fractionalPartPositions.add(new Integer (maxFractionPartSize)); 395 break; 396 case 5: 397 grumble("Grouping separator found in suffix of sub-picture"); 398 break; 399 } 400 } else { switch (phase) { 402 case 0: 403 prefix += unicodeChar(c); 404 break; 405 case 1: 406 case 2: 407 case 3: 408 case 4: 409 case 5: 410 phase = 5; 411 suffix += unicodeChar(c); 412 break; 413 } 414 } 415 } 416 417 422 424 if (wholePartPositions != null) { 425 int n = wholePartPositions.size(); 427 wholePartGroupingPositions = new int[n]; 428 for (int i=0; i<n; i++) { 429 wholePartGroupingPositions[i] = 430 maxWholePartSize - ((Integer )wholePartPositions.get(n - i - 1)).intValue(); 431 } 432 if (n > 1) { 433 boolean regular = true; 434 int first = wholePartGroupingPositions[0]; 435 for (int i=1; i<n; i++) { 436 if (wholePartGroupingPositions[i] != i * first) { 437 regular = false; 438 break; 439 } 440 } 441 if (regular) { 442 wholePartGroupingPositions = new int[1]; 443 wholePartGroupingPositions[0] = first; 444 } 445 } 446 if (wholePartGroupingPositions[0] == 0) { 447 grumble("Cannot have a grouping separator adjacent to the decimal separator"); 448 } 449 } 450 451 if (fractionalPartPositions != null) { 452 int n = fractionalPartPositions.size(); 453 fractionalPartGroupingPositions = new int[n]; 454 for (int i=0; i<n; i++) { 455 fractionalPartGroupingPositions[i] = 456 ((Integer )fractionalPartPositions.get(i)).intValue(); 457 } 458 } 459 } 460 461 465 466 public CharSequence format(NumericValue value, DecimalSymbols dfs, String minusSign) { 467 468 470 if (value.isNaN()) { 471 return prefix + dfs.NaN + suffix; 472 } 473 474 if (value instanceof DoubleValue && Double.isInfinite(value.getDoubleValue())) { 475 return minusSign + prefix + dfs.infinity + suffix; 476 } 477 478 if (value instanceof FloatValue && Double.isInfinite(value.getDoubleValue())) { 479 return minusSign + prefix + dfs.infinity + suffix; 480 } 481 482 int multiplier = 1; 483 if (isPercent) { 484 multiplier = 100; 485 } else if (isPerMille) { 486 multiplier = 1000; 487 } 488 489 if (multiplier != 1) { 490 try { 491 value = value.arithmetic(Token.MULT, new IntegerValue(multiplier), null); 492 } catch (XPathException e) { 493 value = new DoubleValue(value.getDoubleValue() * multiplier); 494 } 495 } 496 497 StringBuffer sb = new StringBuffer (20); 498 if (value instanceof DoubleValue || value instanceof FloatValue) { 499 formatDouble(value.getDoubleValue(), sb); 500 501 } else if (value instanceof IntegerValue || value instanceof BigIntegerValue) { 502 formatInteger(value, sb); 503 504 } else if (value instanceof DecimalValue) { 505 formatDecimal((DecimalValue)value, sb); 506 } 507 508 510 512 int[] ib = StringValue.expand(sb); 513 int ibused = ib.length; 514 int point = sb.indexOf("."); 515 if (point == -1) { 516 point = sb.length(); 517 } else { 518 ib[point] = dfs.decimalSeparator; 519 520 if (maxFractionPartSize == 0) { 522 ibused--; 523 } 524 } 525 526 528 if (dfs.zeroDigit != '0') { 529 int newZero = dfs.zeroDigit; 530 for (int i=0; i<ibused; i++) { 531 int c = ib[i]; 532 if (c>='0' && c<='9') { 533 ib[i] = (c-'0'+newZero); 534 } 535 } 536 } 537 538 540 if (wholePartGroupingPositions != null) { 541 if (wholePartGroupingPositions.length == 1) { 542 int g = wholePartGroupingPositions[0]; 544 int p = point - g; 545 while (p > 0) { 546 ib = insert(ib, ibused++, dfs.groupingSeparator, p); 547 p -= g; 549 } 550 } else { 551 for (int i=0; i<wholePartGroupingPositions.length; i++) { 553 int p = point - wholePartGroupingPositions[i]; 554 if (p > 0) { 555 ib = insert(ib, ibused++, dfs.groupingSeparator, p); 556 } 558 } 559 } 560 } 561 562 564 if (fractionalPartGroupingPositions != null) { 565 for (int i=0; i<fractionalPartGroupingPositions.length; i++) { 567 int p = point + 1 + fractionalPartGroupingPositions[i] + i; 568 if (p < ibused-1) { 569 ib = insert(ib, ibused++, dfs.groupingSeparator, p); 570 } else { 572 break; 573 } 574 } 575 } 576 577 579 FastStringBuffer res = new FastStringBuffer(prefix.length() + minusSign.length() + suffix.length() + ibused); 583 res.append(minusSign); 584 res.append(prefix); 585 res.append(StringValue.contract(ib, ibused)); 586 res.append(suffix); 587 return res; 588 } 589 590 private void formatDecimal(DecimalValue value, StringBuffer sb) { 591 BigDecimal dval = value.getValue(); 592 dval = dval.setScale(maxFractionPartSize, BigDecimal.ROUND_HALF_EVEN); 593 sb.append(dval.toString()); 594 595 int point = sb.indexOf("."); 596 int intDigits; 597 if (point >= 0) { 598 int zz = maxFractionPartSize - minFractionPartSize; 599 while (zz>0) { 600 if (sb.charAt(sb.length()-1) == '0') { 601 sb.setLength(sb.length()-1); 602 zz--; 603 } else { 604 break; 605 } 606 } 607 intDigits = point; 608 if (sb.charAt(sb.length()-1) == '.') { 609 sb.setLength(sb.length()-1); 610 } 611 } else { 612 intDigits = sb.length(); 613 } 614 for (int i=0; i<(minWholePartSize - intDigits); i++) { 615 sb.insert(0, '0'); 616 } 617 } 618 619 private void formatInteger(NumericValue value, StringBuffer sb) { 620 sb.append(value.toString()); 621 int leadingZeroes = minWholePartSize - sb.length(); 622 for (int i=0; i < leadingZeroes; i++) { 623 sb.insert(0, '0'); 624 } 625 sb.append('.'); 626 for (int i=0; i < minFractionPartSize; i++) { 627 sb.append('0'); 628 } 629 } 630 631 636 private void formatDouble(double value, StringBuffer sb) { 637 639 double d = value; 640 if (maxFractionPartSize != 0) { 641 d *= Math.pow(10, maxFractionPartSize); 642 } 643 int point; 644 if (Math.abs(d) > Long.MAX_VALUE) { 645 long bits = Double.doubleToLongBits(value); 647 boolean negative = (bits & 0x8000000000000000L) != 0; 648 int exponent = (int)((bits & 0x7ff0000000000000L)>>52) - 1023 - 52; 649 long mantissa = bits & 0x000fffffffffffffL | 0x0010000000000000L; 650 BigInteger big = BigInteger.valueOf(mantissa); 651 big = big.multiply(BigInteger.valueOf(2).pow(exponent)); 652 653 if (negative) { 654 sb.append('-'); 655 } 656 sb.append(big.toString()); 657 sb.append('.'); 659 660 662 666 } else { 667 long ld = (long)d; 668 669 671 double rem = d - ld; 672 if (rem > 0.5) { 673 ld++; 674 } else if (rem == 0.5) { 675 if ((ld & 1) == 1) { 677 ld++; 678 } 679 } 680 681 String sd = "" + ld; int wholeSize = sd.length() - maxFractionPartSize; 683 if (wholeSize > 0) { 684 sb.append(sd.substring(0, wholeSize)); 685 } 686 687 point = sb.length(); 688 sb.append('.'); 689 690 while (wholeSize < 0) { 691 sb.append('0'); 692 wholeSize++; 693 } 694 sb.append(sd.substring(wholeSize)); 695 696 698 while (point < minWholePartSize) { 699 sb.insert(0, '0'); 700 point++; 701 } 702 703 while (point > maxWholePartSize) { 704 if (sb.charAt(0)=='0') { 705 sb.deleteCharAt(0); 706 point--; 707 } else { 708 break; 709 } 710 } 711 712 int actualFractionSize = sb.length()-point-1; 713 while (actualFractionSize > minFractionPartSize) { 714 if (sb.charAt(sb.length()-1) == '0') { 715 sb.deleteCharAt(sb.length()-1); 716 actualFractionSize--; 717 } else { 718 break; 719 } 720 } 721 722 if (sb.charAt(sb.length()-1) == '.') { 723 sb.deleteCharAt(sb.length()-1); 724 } 725 } 726 } 727 } 728 729 734 735 private static CharSequence unicodeChar(int ch) { 736 if (ch<65536) { 737 return "" + (char)ch; 738 } 739 else { ch -= 65536; 744 char[] sb = new char[2]; 745 sb[0] = ((char)((ch / 1024) + 55296)); 746 sb[1] = ((char)((ch % 1024) + 56320)); 747 return new CharSlice(sb, 0, 2); 748 } 749 } 750 751 754 755 private static int[] insert(int[] array, int used, int value, int position) { 756 if (used+1 > array.length) { 757 int[] a2 = new int[used+10]; 758 System.arraycopy(array, 0, a2, 0, used); 759 array = a2; 760 } 761 for (int i=used-1; i>=position; i--) { 762 array[i+1] = array[i]; 763 } 764 array[position] = value; 765 return array; 766 } 767 } 768 769 | Popular Tags |