1 /* Copyright 2004 The Apache Software Foundation 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package org.apache.xmlbeans.impl.util; 17 18 import org.apache.xmlbeans.GDate; 19 import org.apache.xmlbeans.GDateBuilder; 20 import org.apache.xmlbeans.GDateSpecification; 21 import org.apache.xmlbeans.SchemaType; 22 import org.apache.xmlbeans.XmlCalendar; 23 import org.apache.xmlbeans.XmlError; 24 import org.apache.xmlbeans.impl.common.InvalidLexicalValueException; 25 26 import javax.xml.namespace.NamespaceContext; 27 import javax.xml.namespace.QName; 28 import java.math.BigDecimal; 29 import java.math.BigInteger; 30 import java.util.Calendar; 31 import java.util.Collection; 32 import java.util.Date; 33 import java.net.URI; 34 35 public final class XsTypeConverter 36 { 37 private static final String POS_INF_LEX = "INF"; 38 private static final String NEG_INF_LEX = "-INF"; 39 private static final String NAN_LEX = "NaN"; 40 41 private static final char NAMESPACE_SEP = ':'; 42 private static final String EMPTY_PREFIX = ""; 43 private static final BigDecimal DECIMAL__ZERO = new BigDecimal(0.0); 44 45 // See Section 2.4.3 of FRC2396 http://www.ietf.org/rfc/rfc2396.txt 46 private static final String[] URI_CHARS_TO_BE_REPLACED = {" " , "{" , "}" , "|" , "\\" , "^" , "[" , "]" , "`" }; 47 private static final String[] URI_CHARS_REPLACED_WITH = {"%20", "%7b", "%7d", "%7c", "%5c", "%5e", "%5b", "%5d", "%60"}; 48 49 // ======================== float ======================== 50 public static float lexFloat(CharSequence cs) 51 throws NumberFormatException 52 { 53 final String v = cs.toString(); 54 try { 55 //current jdk impl of parseFloat calls trim() on the string. 56 //Any other space is illegal anyway, whether there are one or more spaces. 57 //so no need to do a collapse pass through the string. 58 if (cs.length() > 0) { 59 char ch = cs.charAt(cs.length() - 1); 60 if (ch == 'f' || ch == 'F') { 61 if (cs.charAt(cs.length() - 2) != 'N') 62 throw new NumberFormatException("Invalid char '" + ch + "' in float."); 63 } 64 } 65 return Float.parseFloat(v); 66 } 67 catch (NumberFormatException e) { 68 if (v.equals(POS_INF_LEX)) return Float.POSITIVE_INFINITY; 69 if (v.equals(NEG_INF_LEX)) return Float.NEGATIVE_INFINITY; 70 if (v.equals(NAN_LEX)) return Float.NaN; 71 72 throw e; 73 } 74 } 75 76 public static float lexFloat(CharSequence cs, Collection errors) 77 { 78 try { 79 return lexFloat(cs); 80 } 81 catch (NumberFormatException e) { 82 String msg = "invalid float: " + cs; 83 errors.add(XmlError.forMessage(msg)); 84 85 return Float.NaN; 86 } 87 } 88 89 public static String printFloat(float value) 90 { 91 if (value == Float.POSITIVE_INFINITY) 92 return POS_INF_LEX; 93 else if (value == Float.NEGATIVE_INFINITY) 94 return NEG_INF_LEX; 95 else if (Float.isNaN(value)) 96 return NAN_LEX; 97 else 98 return Float.toString(value); 99 } 100 101 102 // ======================== double ======================== 103 public static double lexDouble(CharSequence cs) 104 throws NumberFormatException 105 { 106 final String v = cs.toString(); 107 108 try { 109 //current jdk impl of parseDouble calls trim() on the string. 110 //Any other space is illegal anyway, whether there are one or more spaces. 111 //so no need to do a collapse pass through the string. 112 if (cs.length() > 0) { 113 char ch = cs.charAt(cs.length() - 1); 114 if (ch == 'd' || ch == 'D') 115 throw new NumberFormatException("Invalid char '" + ch + "' in double."); 116 } 117 return Double.parseDouble(v); 118 } 119 catch (NumberFormatException e) { 120 if (v.equals(POS_INF_LEX)) return Double.POSITIVE_INFINITY; 121 if (v.equals(NEG_INF_LEX)) return Double.NEGATIVE_INFINITY; 122 if (v.equals(NAN_LEX)) return Double.NaN; 123 124 throw e; 125 } 126 } 127 128 public static double lexDouble(CharSequence cs, Collection errors) 129 { 130 try { 131 return lexDouble(cs); 132 } 133 catch (NumberFormatException e) { 134 String msg = "invalid double: " + cs; 135 errors.add(XmlError.forMessage(msg)); 136 137 return Double.NaN; 138 } 139 } 140 141 public static String printDouble(double value) 142 { 143 if (value == Double.POSITIVE_INFINITY) 144 return POS_INF_LEX; 145 else if (value == Double.NEGATIVE_INFINITY) 146 return NEG_INF_LEX; 147 else if (Double.isNaN(value)) 148 return NAN_LEX; 149 else 150 return Double.toString(value); 151 } 152 153 154 // ======================== decimal ======================== 155 public static BigDecimal lexDecimal(CharSequence cs) 156 throws NumberFormatException 157 { 158 final String v = cs.toString(); 159 160 //TODO: review this 161 //NOTE: we trim unneeded zeros from the string because 162 //java.math.BigDecimal considers them significant for its 163 //equals() method, but the xml value 164 //space does not consider them significant. 165 //See http://www.w3.org/2001/05/xmlschema-errata#e2-44 166 return new BigDecimal(trimTrailingZeros(v)); 167 } 168 169 public static BigDecimal lexDecimal(CharSequence cs, Collection errors) 170 { 171 try { 172 return lexDecimal(cs); 173 } 174 catch (NumberFormatException e) { 175 String msg = "invalid long: " + cs; 176 errors.add(XmlError.forMessage(msg)); 177 return DECIMAL__ZERO; 178 } 179 } 180 181 private static final char[] CH_ZEROS = new char[] {'0', '0', '0', '0', '0', '0', '0', '0', 182 '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'}; 183 184 public static String printDecimal(BigDecimal value) 185 { 186 // We can't simply use value.toString() here, because in JDK1.5 that returns an 187 // exponent String and exponents are not allowed in XMLSchema decimal values 188 // The following code comes from Apache Harmony 189 String intStr = value.unscaledValue().toString(); 190 int scale = value.scale(); 191 if ((scale == 0) || ((value.longValue() == 0) && (scale < 0))) 192 return intStr; 193 194 int begin = (value.signum() < 0) ? 1 : 0; 195 int delta = scale; 196 // We take space for all digits, plus a possible decimal point, plus 'scale' 197 StringBuffer result = new StringBuffer(intStr.length() + 1 + Math.abs(scale)); 198 199 if (begin == 1) 200 { 201 // If the number is negative, we insert a '-' character at front 202 result.append('-'); 203 } 204 if (scale > 0) 205 { 206 delta -= (intStr.length() - begin); 207 if (delta >= 0) 208 { 209 result.append("0."); //$NON-NLS-1$ 210 // To append zeros after the decimal point 211 for (; delta > CH_ZEROS.length; delta -= CH_ZEROS.length) 212 result.append(CH_ZEROS); 213 result.append(CH_ZEROS, 0, delta); 214 result.append(intStr.substring(begin)); 215 } 216 else 217 { 218 delta = begin - delta; 219 result.append(intStr.substring(begin, delta)); 220 result.append('.'); 221 result.append(intStr.substring(delta)); 222 } 223 } 224 else 225 {// (scale <= 0) 226 result.append(intStr.substring(begin)); 227 // To append trailing zeros 228 for (; delta < -CH_ZEROS.length; delta += CH_ZEROS.length) 229 result.append(CH_ZEROS); 230 result.append(CH_ZEROS, 0, -delta); 231 } 232 return result.toString(); 233 } 234 235 // ======================== integer ======================== 236 public static BigInteger lexInteger(CharSequence cs) 237 throws NumberFormatException 238 { 239 if (cs.length() > 1) { 240 if (cs.charAt(0) == '+' && cs.charAt(1) == '-') 241 throw new NumberFormatException("Illegal char sequence '+-'"); 242 } 243 final String v = cs.toString(); 244 245 //TODO: consider special casing zero and one to return static values 246 //from BigInteger to avoid object creation. 247 return new BigInteger(trimInitialPlus(v)); 248 } 249 250 public static BigInteger lexInteger(CharSequence cs, Collection errors) 251 { 252 try { 253 return lexInteger(cs); 254 } 255 catch (NumberFormatException e) { 256 String msg = "invalid long: " + cs; 257 errors.add(XmlError.forMessage(msg)); 258 return BigInteger.ZERO; 259 } 260 } 261 262 public static String printInteger(BigInteger value) 263 { 264 return value.toString(); 265 } 266 267 // ======================== long ======================== 268 public static long lexLong(CharSequence cs) 269 throws NumberFormatException 270 { 271 final String v = cs.toString(); 272 return Long.parseLong(trimInitialPlus(v)); 273 } 274 275 public static long lexLong(CharSequence cs, Collection errors) 276 { 277 try { 278 return lexLong(cs); 279 } 280 catch (NumberFormatException e) { 281 String msg = "invalid long: " + cs; 282 errors.add(XmlError.forMessage(msg)); 283 return 0L; 284 } 285 } 286 287 public static String printLong(long value) 288 { 289 return Long.toString(value); 290 } 291 292 293 // ======================== short ======================== 294 public static short lexShort(CharSequence cs) 295 throws NumberFormatException 296 { 297 return parseShort(cs); 298 } 299 300 public static short lexShort(CharSequence cs, Collection errors) 301 { 302 try { 303 return lexShort(cs); 304 } 305 catch (NumberFormatException e) { 306 String msg = "invalid short: " + cs; 307 errors.add(XmlError.forMessage(msg)); 308 return 0; 309 } 310 } 311 312 public static String printShort(short value) 313 { 314 return Short.toString(value); 315 } 316 317 318 // ======================== int ======================== 319 public static int lexInt(CharSequence cs) 320 throws NumberFormatException 321 { 322 return parseInt(cs); 323 } 324 325 public static int lexInt(CharSequence cs, Collection errors) 326 { 327 try { 328 return lexInt(cs); 329 } 330 catch (NumberFormatException e) { 331 String msg = "invalid int:" + cs; 332 errors.add(XmlError.forMessage(msg)); 333 return 0; 334 } 335 } 336 337 public static String printInt(int value) 338 { 339 return Integer.toString(value); 340 } 341 342 343 // ======================== byte ======================== 344 public static byte lexByte(CharSequence cs) 345 throws NumberFormatException 346 { 347 return parseByte(cs); 348 } 349 350 public static byte lexByte(CharSequence cs, Collection errors) 351 { 352 try { 353 return lexByte(cs); 354 } 355 catch (NumberFormatException e) { 356 String msg = "invalid byte: " + cs; 357 errors.add(XmlError.forMessage(msg)); 358 return 0; 359 } 360 } 361 362 public static String printByte(byte value) 363 { 364 return Byte.toString(value); 365 } 366 367 368 // ======================== boolean ======================== 369 public static boolean lexBoolean(CharSequence v) 370 { 371 switch (v.length()) { 372 case 1: // "0" or "1" 373 final char c = v.charAt(0); 374 if ('0' == c) return false; 375 if ('1' == c) return true; 376 break; 377 case 4: //"true" 378 if ('t' == v.charAt(0) && 379 'r' == v.charAt(1) && 380 'u' == v.charAt(2) && 381 'e' == v.charAt(3)) { 382 return true; 383 } 384 break; 385 case 5: //"false" 386 if ('f' == v.charAt(0) && 387 'a' == v.charAt(1) && 388 'l' == v.charAt(2) && 389 's' == v.charAt(3) && 390 'e' == v.charAt(4)) { 391 return false; 392 } 393 break; 394 } 395 396 //reaching here means an invalid boolean lexical 397 String msg = "invalid boolean: " + v; 398 throw new InvalidLexicalValueException(msg); 399 } 400 401 public static boolean lexBoolean(CharSequence value, Collection errors) 402 { 403 try { 404 return lexBoolean(value); 405 } 406 catch (InvalidLexicalValueException e) { 407 errors.add(XmlError.forMessage(e.getMessage())); 408 return false; 409 } 410 } 411 412 public static String printBoolean(boolean value) 413 { 414 return (value ? "true" : "false"); 415 } 416 417 418 // ======================== string ======================== 419 public static String lexString(CharSequence cs, Collection errors) 420 { 421 final String v = cs.toString(); 422 423 return v; 424 } 425 426 427 public static String lexString(CharSequence lexical_value) 428 { 429 return lexical_value.toString(); 430 } 431 432 public static String printString(String value) 433 { 434 return value; 435 } 436 437 438 // ======================== QName ======================== 439 public static QName lexQName(CharSequence charSeq, NamespaceContext nscontext) 440 { 441 String prefix, localname; 442 443 int firstcolon; 444 boolean hasFirstCollon = false; 445 for (firstcolon = 0; firstcolon < charSeq.length(); firstcolon++) 446 if (charSeq.charAt(firstcolon) == NAMESPACE_SEP) { 447 hasFirstCollon = true; 448 break; 449 } 450 451 if (hasFirstCollon) { 452 prefix = charSeq.subSequence(0, firstcolon).toString(); 453 localname = charSeq.subSequence(firstcolon + 1, charSeq.length()).toString(); 454 if (firstcolon == 0) { 455 throw new InvalidLexicalValueException("invalid xsd:QName '" + charSeq.toString() + "'"); 456 } 457 } else { 458 prefix = EMPTY_PREFIX; 459 localname = charSeq.toString(); 460 } 461 462 String uri = nscontext.getNamespaceURI(prefix); 463 464 if (uri == null) { 465 if (prefix != null && prefix.length() > 0) 466 throw new InvalidLexicalValueException("Can't resolve prefix: " + prefix); 467 468 uri = ""; 469 } 470 471 return new QName(uri, localname); 472 } 473 474 public static QName lexQName(String xsd_qname, Collection errors, 475 NamespaceContext nscontext) 476 { 477 try { 478 return lexQName(xsd_qname, nscontext); 479 } 480 catch (InvalidLexicalValueException e) { 481 errors.add(XmlError.forMessage(e.getMessage())); 482 final int idx = xsd_qname.indexOf(NAMESPACE_SEP); 483 return new QName(null, xsd_qname.substring(idx)); 484 } 485 } 486 487 public static String printQName(QName qname, NamespaceContext nsContext, 488 Collection errors) 489 { 490 final String uri = qname.getNamespaceURI(); 491 assert uri != null; //qname is not allowed to have null uri values 492 final String prefix; 493 if (uri.length() > 0) { 494 prefix = nsContext.getPrefix(uri); 495 if (prefix == null) { 496 String msg = "NamespaceContext does not provide" + 497 " prefix for namespaceURI " + uri; 498 errors.add(XmlError.forMessage(msg)); 499 } 500 } else { 501 prefix = null; 502 } 503 return getQNameString(uri, qname.getLocalPart(), prefix); 504 505 } 506 507 public static String getQNameString(String uri, 508 String localpart, 509 String prefix) 510 { 511 if (prefix != null && 512 uri != null && 513 uri.length() > 0 && 514 prefix.length() > 0) { 515 return (prefix + NAMESPACE_SEP + localpart); 516 } else { 517 return localpart; 518 } 519 } 520 521 // ======================== GDate ======================== 522 public static GDate lexGDate(CharSequence charSeq) 523 { 524 return new GDate(charSeq); 525 } 526 527 public static GDate lexGDate(String xsd_gdate, Collection errors) 528 { 529 try { 530 return lexGDate(xsd_gdate); 531 } 532 catch (IllegalArgumentException e) { 533 errors.add(XmlError.forMessage(e.getMessage())); 534 return new GDateBuilder().toGDate(); 535 } 536 } 537 538 public static String printGDate(GDate gdate, Collection errors) 539 { 540 return gdate.toString(); 541 } 542 543 544 // ======================== dateTime ======================== 545 public static XmlCalendar lexDateTime(CharSequence v) 546 { 547 GDateSpecification value = getGDateValue(v, SchemaType.BTC_DATE_TIME); 548 return value.getCalendar(); 549 } 550 551 552 public static String printDateTime(Calendar c) 553 { 554 return printDateTime(c, SchemaType.BTC_DATE_TIME); 555 } 556 557 public static String printTime(Calendar c) 558 { 559 return printDateTime(c, SchemaType.BTC_TIME); 560 } 561 562 public static String printDate(Calendar c) 563 { 564 return printDateTime(c, SchemaType.BTC_DATE); 565 } 566 567 public static String printDate(Date d) 568 { 569 GDateSpecification value = getGDateValue(d, SchemaType.BTC_DATE); 570 return value.toString(); 571 } 572 573 public static String printDateTime(Calendar c, int type_code) 574 { 575 GDateSpecification value = getGDateValue(c, type_code); 576 return value.toString(); 577 } 578 579 public static String printDateTime(Date c) 580 { 581 GDateSpecification value = getGDateValue(c, SchemaType.BTC_DATE_TIME); 582 return value.toString(); 583 } 584 585 586 // ======================== hexBinary ======================== 587 public static CharSequence printHexBinary(byte[] val) 588 { 589 return HexBin.bytesToString(val); 590 } 591 592 public static byte[] lexHexBinary(CharSequence lexical_value) 593 { 594 byte[] buf = HexBin.decode(lexical_value.toString().getBytes()); 595 if (buf != null) 596 return buf; 597 else 598 throw new InvalidLexicalValueException("invalid hexBinary value"); 599 } 600 601 602 // ======================== base64binary ======================== 603 public static CharSequence printBase64Binary(byte[] val) 604 { 605 final byte[] bytes = Base64.encode(val); 606 return new String(bytes); 607 } 608 609 public static byte[] lexBase64Binary(CharSequence lexical_value) 610 { 611 byte[] buf = Base64.decode(lexical_value.toString().getBytes()); 612 if (buf != null) 613 return buf; 614 else 615 throw new InvalidLexicalValueException("invalid base64Binary value"); 616 } 617 618 619 // date utils 620 public static GDateSpecification getGDateValue(Date d, 621 int builtin_type_code) 622 { 623 GDateBuilder gDateBuilder = new GDateBuilder(d); 624 gDateBuilder.setBuiltinTypeCode(builtin_type_code); 625 GDate value = gDateBuilder.toGDate(); 626 return value; 627 } 628 629 630 public static GDateSpecification getGDateValue(Calendar c, 631 int builtin_type_code) 632 { 633 GDateBuilder gDateBuilder = new GDateBuilder(c); 634 gDateBuilder.setBuiltinTypeCode(builtin_type_code); 635 GDate value = gDateBuilder.toGDate(); 636 return value; 637 } 638 639 public static GDateSpecification getGDateValue(CharSequence v, 640 int builtin_type_code) 641 { 642 GDateBuilder gDateBuilder = new GDateBuilder(v); 643 gDateBuilder.setBuiltinTypeCode(builtin_type_code); 644 GDate value = gDateBuilder.toGDate(); 645 return value; 646 } 647 648 private static String trimInitialPlus(String xml) 649 { 650 if (xml.length() > 0 && xml.charAt(0) == '+') { 651 return xml.substring(1); 652 } else { 653 return xml; 654 } 655 } 656 657 private static String trimTrailingZeros(String xsd_decimal) 658 { 659 final int last_char_idx = xsd_decimal.length() - 1; 660 if (xsd_decimal.charAt(last_char_idx) == '0') 661 { 662 final int last_point = xsd_decimal.lastIndexOf('.'); 663 if (last_point >= 0) { 664 //find last trailing zero 665 for (int idx = last_char_idx; idx > last_point; idx--) { 666 if (xsd_decimal.charAt(idx) != '0') { 667 return xsd_decimal.substring(0, idx + 1); 668 } 669 } 670 //reaching here means the string matched xxx.0* 671 return xsd_decimal.substring(0, last_point); 672 } 673 } 674 return xsd_decimal; 675 } 676 677 private static int parseInt(CharSequence cs) 678 { 679 return parseIntXsdNumber(cs, Integer.MIN_VALUE, Integer.MAX_VALUE); 680 } 681 682 private static short parseShort(CharSequence cs) 683 { 684 return (short)parseIntXsdNumber(cs, Short.MIN_VALUE, Short.MAX_VALUE); 685 } 686 687 private static byte parseByte(CharSequence cs) 688 { 689 return (byte)parseIntXsdNumber(cs, Byte.MIN_VALUE, Byte.MAX_VALUE); 690 } 691 692 private static int parseIntXsdNumber(CharSequence ch, int min_value, int max_value) 693 { 694 // int parser on a CharSequence 695 int length = ch.length(); 696 if (length < 1) 697 throw new NumberFormatException("For input string: \"" + ch.toString() + "\""); 698 699 int sign = 1; 700 int result = 0; 701 int start = 0; 702 int limit; 703 int limit2; 704 705 char c = ch.charAt(0); 706 if (c == '-') { 707 start++; 708 limit = (min_value / 10); 709 limit2 = -(min_value % 10); 710 } else if (c == '+') { 711 start++; 712 sign = -1; 713 limit = -(max_value / 10); 714 limit2 = (max_value % 10); 715 } else { 716 sign = -1; 717 limit = -(max_value / 10); 718 limit2 = (max_value % 10); 719 } 720 721 for (int i = 0; i < length - start; i++) { 722 c = ch.charAt(i + start); 723 int v = Character.digit(c, 10); 724 725 if (v < 0) 726 throw new NumberFormatException("For input string: \"" + ch.toString() + "\""); 727 728 if (result < limit || (result==limit && v > limit2)) 729 throw new NumberFormatException("For input string: \"" + ch.toString() + "\""); 730 731 result = result * 10 - v; 732 } 733 734 return sign * result; 735 } 736 737 // ======================== anyURI ======================== 738 public static CharSequence printAnyURI(CharSequence val) 739 { 740 return val; 741 } 742 743 /** 744 * Checkes the regular expression of URI, defined by RFC2369 http://www.ietf.org/rfc/rfc2396.txt Appendix B. 745 * Note: The whitespace normalization rule collapse must be applied priot to calling this method. 746 * @param lexical_value the lexical value 747 * @return same input value if input value is in the lexical space 748 * @throws InvalidLexicalValueException 749 */ 750 public static CharSequence lexAnyURI(CharSequence lexical_value) 751 { 752 /* // Reg exp from RFC2396, but it's too forgiving for XQTS 753 Pattern p = Pattern.compile("^([^:/?#]+:)?(//[^/?#]*)?([^?#]*)(\\?[^#]*)?(#.*)?"); 754 Matcher m = p.matcher(lexical_value); 755 if ( !m.matches() ) 756 throw new InvalidLexicalValueException("invalid anyURI value"); 757 else 758 { 759 for ( int i = 0; i<= m.groupCount(); i++ ) 760 { 761 System.out.print(" " + i + ": " + m.group(i)); 762 } 763 System.out.println(""); 764 return lexical_value; 765 } */ 766 767 // Per XMLSchema spec allow spaces inside URIs 768 StringBuffer s = new StringBuffer(lexical_value.toString()); 769 for (int ic = 0; ic<URI_CHARS_TO_BE_REPLACED.length; ic++) 770 { 771 int i = 0; 772 while ((i = s.indexOf(URI_CHARS_TO_BE_REPLACED[ic], i)) >= 0) 773 { 774 s.replace(i, i + 1, URI_CHARS_REPLACED_WITH[ic]); 775 i += 3; 776 } 777 } 778 779 try 780 { 781 URI.create(s.toString()); 782 } 783 catch (IllegalArgumentException e) 784 { 785 throw new InvalidLexicalValueException("invalid anyURI value: " + lexical_value, e); 786 } 787 788 return lexical_value; 789 } 790 }