blob: e2075be498e67ae5c706f730b050989ea967f67c [file] [log] [blame]
/* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.xmlbeans.impl.util;
import org.apache.xmlbeans.GDate;
import org.apache.xmlbeans.GDateBuilder;
import org.apache.xmlbeans.GDateSpecification;
import org.apache.xmlbeans.SchemaType;
import org.apache.xmlbeans.XmlCalendar;
import org.apache.xmlbeans.XmlError;
import org.apache.xmlbeans.impl.common.InvalidLexicalValueException;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.net.URI;
public final class XsTypeConverter
{
private static final String POS_INF_LEX = "INF";
private static final String NEG_INF_LEX = "-INF";
private static final String NAN_LEX = "NaN";
private static final char NAMESPACE_SEP = ':';
private static final String EMPTY_PREFIX = "";
private static final BigDecimal DECIMAL__ZERO = new BigDecimal(0.0);
// See Section 2.4.3 of FRC2396 http://www.ietf.org/rfc/rfc2396.txt
private static final String[] URI_CHARS_TO_BE_REPLACED = {" " , "{" , "}" , "|" , "\\" , "^" , "[" , "]" , "`" };
private static final String[] URI_CHARS_REPLACED_WITH = {"%20", "%7b", "%7d", "%7c", "%5c", "%5e", "%5b", "%5d", "%60"};
// ======================== float ========================
public static float lexFloat(CharSequence cs)
throws NumberFormatException
{
final String v = cs.toString();
try {
//current jdk impl of parseFloat calls trim() on the string.
//Any other space is illegal anyway, whether there are one or more spaces.
//so no need to do a collapse pass through the string.
if (cs.length() > 0) {
char ch = cs.charAt(cs.length() - 1);
if (ch == 'f' || ch == 'F') {
if (cs.charAt(cs.length() - 2) != 'N')
throw new NumberFormatException("Invalid char '" + ch + "' in float.");
}
}
return Float.parseFloat(v);
}
catch (NumberFormatException e) {
if (v.equals(POS_INF_LEX)) return Float.POSITIVE_INFINITY;
if (v.equals(NEG_INF_LEX)) return Float.NEGATIVE_INFINITY;
if (v.equals(NAN_LEX)) return Float.NaN;
throw e;
}
}
public static float lexFloat(CharSequence cs, Collection errors)
{
try {
return lexFloat(cs);
}
catch (NumberFormatException e) {
String msg = "invalid float: " + cs;
errors.add(XmlError.forMessage(msg));
return Float.NaN;
}
}
public static String printFloat(float value)
{
if (value == Float.POSITIVE_INFINITY)
return POS_INF_LEX;
else if (value == Float.NEGATIVE_INFINITY)
return NEG_INF_LEX;
else if (Float.isNaN(value))
return NAN_LEX;
else
return Float.toString(value);
}
// ======================== double ========================
public static double lexDouble(CharSequence cs)
throws NumberFormatException
{
final String v = cs.toString();
try {
//current jdk impl of parseDouble calls trim() on the string.
//Any other space is illegal anyway, whether there are one or more spaces.
//so no need to do a collapse pass through the string.
if (cs.length() > 0) {
char ch = cs.charAt(cs.length() - 1);
if (ch == 'd' || ch == 'D')
throw new NumberFormatException("Invalid char '" + ch + "' in double.");
}
return Double.parseDouble(v);
}
catch (NumberFormatException e) {
if (v.equals(POS_INF_LEX)) return Double.POSITIVE_INFINITY;
if (v.equals(NEG_INF_LEX)) return Double.NEGATIVE_INFINITY;
if (v.equals(NAN_LEX)) return Double.NaN;
throw e;
}
}
public static double lexDouble(CharSequence cs, Collection errors)
{
try {
return lexDouble(cs);
}
catch (NumberFormatException e) {
String msg = "invalid double: " + cs;
errors.add(XmlError.forMessage(msg));
return Double.NaN;
}
}
public static String printDouble(double value)
{
if (value == Double.POSITIVE_INFINITY)
return POS_INF_LEX;
else if (value == Double.NEGATIVE_INFINITY)
return NEG_INF_LEX;
else if (Double.isNaN(value))
return NAN_LEX;
else
return Double.toString(value);
}
// ======================== decimal ========================
public static BigDecimal lexDecimal(CharSequence cs)
throws NumberFormatException
{
final String v = cs.toString();
//TODO: review this
//NOTE: we trim unneeded zeros from the string because
//java.math.BigDecimal considers them significant for its
//equals() method, but the xml value
//space does not consider them significant.
//See http://www.w3.org/2001/05/xmlschema-errata#e2-44
return new BigDecimal(trimTrailingZeros(v));
}
public static BigDecimal lexDecimal(CharSequence cs, Collection errors)
{
try {
return lexDecimal(cs);
}
catch (NumberFormatException e) {
String msg = "invalid long: " + cs;
errors.add(XmlError.forMessage(msg));
return DECIMAL__ZERO;
}
}
private static final char[] CH_ZEROS = new char[] {'0', '0', '0', '0', '0', '0', '0', '0',
'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'};
public static String printDecimal(BigDecimal value)
{
// We can't simply use value.toString() here, because in JDK1.5 that returns an
// exponent String and exponents are not allowed in XMLSchema decimal values
// The following code comes from Apache Harmony
String intStr = value.unscaledValue().toString();
int scale = value.scale();
if ((scale == 0) || ((value.longValue() == 0) && (scale < 0)))
return intStr;
int begin = (value.signum() < 0) ? 1 : 0;
int delta = scale;
// We take space for all digits, plus a possible decimal point, plus 'scale'
StringBuilder result = new StringBuilder(intStr.length() + 1 + Math.abs(scale));
if (begin == 1)
{
// If the number is negative, we insert a '-' character at front
result.append('-');
}
if (scale > 0)
{
delta -= (intStr.length() - begin);
if (delta >= 0)
{
result.append("0."); //$NON-NLS-1$
// To append zeros after the decimal point
for (; delta > CH_ZEROS.length; delta -= CH_ZEROS.length)
result.append(CH_ZEROS);
result.append(CH_ZEROS, 0, delta);
result.append(intStr.substring(begin));
}
else
{
delta = begin - delta;
result.append(intStr.substring(begin, delta));
result.append('.');
result.append(intStr.substring(delta));
}
}
else
{// (scale <= 0)
result.append(intStr.substring(begin));
// To append trailing zeros
for (; delta < -CH_ZEROS.length; delta += CH_ZEROS.length)
result.append(CH_ZEROS);
result.append(CH_ZEROS, 0, -delta);
}
return result.toString();
}
// ======================== integer ========================
public static BigInteger lexInteger(CharSequence cs)
throws NumberFormatException
{
if (cs.length() > 1) {
if (cs.charAt(0) == '+' && cs.charAt(1) == '-')
throw new NumberFormatException("Illegal char sequence '+-'");
}
final String v = cs.toString();
//TODO: consider special casing zero and one to return static values
//from BigInteger to avoid object creation.
return new BigInteger(trimInitialPlus(v));
}
public static BigInteger lexInteger(CharSequence cs, Collection errors)
{
try {
return lexInteger(cs);
}
catch (NumberFormatException e) {
String msg = "invalid long: " + cs;
errors.add(XmlError.forMessage(msg));
return BigInteger.ZERO;
}
}
public static String printInteger(BigInteger value)
{
return value.toString();
}
// ======================== long ========================
public static long lexLong(CharSequence cs)
throws NumberFormatException
{
final String v = cs.toString();
return Long.parseLong(trimInitialPlus(v));
}
public static long lexLong(CharSequence cs, Collection errors)
{
try {
return lexLong(cs);
}
catch (NumberFormatException e) {
String msg = "invalid long: " + cs;
errors.add(XmlError.forMessage(msg));
return 0L;
}
}
public static String printLong(long value)
{
return Long.toString(value);
}
// ======================== short ========================
public static short lexShort(CharSequence cs)
throws NumberFormatException
{
return parseShort(cs);
}
public static short lexShort(CharSequence cs, Collection errors)
{
try {
return lexShort(cs);
}
catch (NumberFormatException e) {
String msg = "invalid short: " + cs;
errors.add(XmlError.forMessage(msg));
return 0;
}
}
public static String printShort(short value)
{
return Short.toString(value);
}
// ======================== int ========================
public static int lexInt(CharSequence cs)
throws NumberFormatException
{
return parseInt(cs);
}
public static int lexInt(CharSequence cs, Collection errors)
{
try {
return lexInt(cs);
}
catch (NumberFormatException e) {
String msg = "invalid int:" + cs;
errors.add(XmlError.forMessage(msg));
return 0;
}
}
public static String printInt(int value)
{
return Integer.toString(value);
}
// ======================== byte ========================
public static byte lexByte(CharSequence cs)
throws NumberFormatException
{
return parseByte(cs);
}
public static byte lexByte(CharSequence cs, Collection errors)
{
try {
return lexByte(cs);
}
catch (NumberFormatException e) {
String msg = "invalid byte: " + cs;
errors.add(XmlError.forMessage(msg));
return 0;
}
}
public static String printByte(byte value)
{
return Byte.toString(value);
}
// ======================== boolean ========================
public static boolean lexBoolean(CharSequence v)
{
switch (v.length()) {
case 1: // "0" or "1"
final char c = v.charAt(0);
if ('0' == c) return false;
if ('1' == c) return true;
break;
case 4: //"true"
if ('t' == v.charAt(0) &&
'r' == v.charAt(1) &&
'u' == v.charAt(2) &&
'e' == v.charAt(3)) {
return true;
}
break;
case 5: //"false"
if ('f' == v.charAt(0) &&
'a' == v.charAt(1) &&
'l' == v.charAt(2) &&
's' == v.charAt(3) &&
'e' == v.charAt(4)) {
return false;
}
break;
}
//reaching here means an invalid boolean lexical
String msg = "invalid boolean: " + v;
throw new InvalidLexicalValueException(msg);
}
public static boolean lexBoolean(CharSequence value, Collection errors)
{
try {
return lexBoolean(value);
}
catch (InvalidLexicalValueException e) {
errors.add(XmlError.forMessage(e.getMessage()));
return false;
}
}
public static String printBoolean(boolean value)
{
return (value ? "true" : "false");
}
// ======================== string ========================
public static String lexString(CharSequence cs, Collection errors)
{
final String v = cs.toString();
return v;
}
public static String lexString(CharSequence lexical_value)
{
return lexical_value.toString();
}
public static String printString(String value)
{
return value;
}
// ======================== QName ========================
public static QName lexQName(CharSequence charSeq, NamespaceContext nscontext)
{
String prefix, localname;
int firstcolon;
boolean hasFirstCollon = false;
for (firstcolon = 0; firstcolon < charSeq.length(); firstcolon++)
if (charSeq.charAt(firstcolon) == NAMESPACE_SEP) {
hasFirstCollon = true;
break;
}
if (hasFirstCollon) {
prefix = charSeq.subSequence(0, firstcolon).toString();
localname = charSeq.subSequence(firstcolon + 1, charSeq.length()).toString();
if (firstcolon == 0) {
throw new InvalidLexicalValueException("invalid xsd:QName '" + charSeq.toString() + "'");
}
} else {
prefix = EMPTY_PREFIX;
localname = charSeq.toString();
}
String uri = nscontext.getNamespaceURI(prefix);
if (uri == null) {
if (prefix != null && prefix.length() > 0)
throw new InvalidLexicalValueException("Can't resolve prefix: " + prefix);
uri = "";
}
return new QName(uri, localname);
}
public static QName lexQName(String xsd_qname, Collection errors,
NamespaceContext nscontext)
{
try {
return lexQName(xsd_qname, nscontext);
}
catch (InvalidLexicalValueException e) {
errors.add(XmlError.forMessage(e.getMessage()));
final int idx = xsd_qname.indexOf(NAMESPACE_SEP);
return new QName(null, xsd_qname.substring(idx));
}
}
public static String printQName(QName qname, NamespaceContext nsContext,
Collection errors)
{
final String uri = qname.getNamespaceURI();
assert uri != null; //qname is not allowed to have null uri values
final String prefix;
if (uri.length() > 0) {
prefix = nsContext.getPrefix(uri);
if (prefix == null) {
String msg = "NamespaceContext does not provide" +
" prefix for namespaceURI " + uri;
errors.add(XmlError.forMessage(msg));
}
} else {
prefix = null;
}
return getQNameString(uri, qname.getLocalPart(), prefix);
}
public static String getQNameString(String uri,
String localpart,
String prefix)
{
if (prefix != null &&
uri != null &&
uri.length() > 0 &&
prefix.length() > 0) {
return (prefix + NAMESPACE_SEP + localpart);
} else {
return localpart;
}
}
// ======================== GDate ========================
public static GDate lexGDate(CharSequence charSeq)
{
return new GDate(charSeq);
}
public static GDate lexGDate(String xsd_gdate, Collection errors)
{
try {
return lexGDate(xsd_gdate);
}
catch (IllegalArgumentException e) {
errors.add(XmlError.forMessage(e.getMessage()));
return new GDateBuilder().toGDate();
}
}
public static String printGDate(GDate gdate, Collection errors)
{
return gdate.toString();
}
// ======================== dateTime ========================
public static XmlCalendar lexDateTime(CharSequence v)
{
GDateSpecification value = getGDateValue(v, SchemaType.BTC_DATE_TIME);
return value.getCalendar();
}
public static String printDateTime(Calendar c)
{
return printDateTime(c, SchemaType.BTC_DATE_TIME);
}
public static String printTime(Calendar c)
{
return printDateTime(c, SchemaType.BTC_TIME);
}
public static String printDate(Calendar c)
{
return printDateTime(c, SchemaType.BTC_DATE);
}
public static String printDate(Date d)
{
GDateSpecification value = getGDateValue(d, SchemaType.BTC_DATE);
return value.toString();
}
public static String printDateTime(Calendar c, int type_code)
{
GDateSpecification value = getGDateValue(c, type_code);
return value.toString();
}
public static String printDateTime(Date c)
{
GDateSpecification value = getGDateValue(c, SchemaType.BTC_DATE_TIME);
return value.toString();
}
// ======================== hexBinary ========================
public static CharSequence printHexBinary(byte[] val)
{
return HexBin.bytesToString(val);
}
public static byte[] lexHexBinary(CharSequence lexical_value)
{
byte[] buf = HexBin.decode(lexical_value.toString().getBytes());
if (buf != null)
return buf;
else
throw new InvalidLexicalValueException("invalid hexBinary value");
}
// ======================== base64binary ========================
public static CharSequence printBase64Binary(byte[] val)
{
final byte[] bytes = Base64.encode(val);
return new String(bytes);
}
public static byte[] lexBase64Binary(CharSequence lexical_value)
{
byte[] buf = Base64.decode(lexical_value.toString().getBytes());
if (buf != null)
return buf;
else
throw new InvalidLexicalValueException("invalid base64Binary value");
}
// date utils
public static GDateSpecification getGDateValue(Date d,
int builtin_type_code)
{
GDateBuilder gDateBuilder = new GDateBuilder(d);
gDateBuilder.setBuiltinTypeCode(builtin_type_code);
GDate value = gDateBuilder.toGDate();
return value;
}
public static GDateSpecification getGDateValue(Calendar c,
int builtin_type_code)
{
GDateBuilder gDateBuilder = new GDateBuilder(c);
gDateBuilder.setBuiltinTypeCode(builtin_type_code);
GDate value = gDateBuilder.toGDate();
return value;
}
public static GDateSpecification getGDateValue(CharSequence v,
int builtin_type_code)
{
GDateBuilder gDateBuilder = new GDateBuilder(v);
gDateBuilder.setBuiltinTypeCode(builtin_type_code);
GDate value = gDateBuilder.toGDate();
return value;
}
private static String trimInitialPlus(String xml)
{
if (xml.length() > 0 && xml.charAt(0) == '+') {
return xml.substring(1);
} else {
return xml;
}
}
private static String trimTrailingZeros(String xsd_decimal)
{
final int last_char_idx = xsd_decimal.length() - 1;
if (xsd_decimal.charAt(last_char_idx) == '0')
{
final int last_point = xsd_decimal.lastIndexOf('.');
if (last_point >= 0) {
//find last trailing zero
for (int idx = last_char_idx; idx > last_point; idx--) {
if (xsd_decimal.charAt(idx) != '0') {
return xsd_decimal.substring(0, idx + 1);
}
}
//reaching here means the string matched xxx.0*
return xsd_decimal.substring(0, last_point);
}
}
return xsd_decimal;
}
private static int parseInt(CharSequence cs)
{
return parseIntXsdNumber(cs, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
private static short parseShort(CharSequence cs)
{
return (short)parseIntXsdNumber(cs, Short.MIN_VALUE, Short.MAX_VALUE);
}
private static byte parseByte(CharSequence cs)
{
return (byte)parseIntXsdNumber(cs, Byte.MIN_VALUE, Byte.MAX_VALUE);
}
private static int parseIntXsdNumber(CharSequence ch, int min_value, int max_value)
{
// int parser on a CharSequence
int length = ch.length();
if (length < 1)
throw new NumberFormatException("For input string: \"" + ch.toString() + "\"");
int sign = 1;
int result = 0;
int start = 0;
int limit;
int limit2;
char c = ch.charAt(0);
if (c == '-') {
start++;
limit = (min_value / 10);
limit2 = -(min_value % 10);
} else if (c == '+') {
start++;
sign = -1;
limit = -(max_value / 10);
limit2 = (max_value % 10);
} else {
sign = -1;
limit = -(max_value / 10);
limit2 = (max_value % 10);
}
for (int i = 0; i < length - start; i++) {
c = ch.charAt(i + start);
int v = Character.digit(c, 10);
if (v < 0)
throw new NumberFormatException("For input string: \"" + ch.toString() + "\"");
if (result < limit || (result==limit && v > limit2))
throw new NumberFormatException("For input string: \"" + ch.toString() + "\"");
result = result * 10 - v;
}
return sign * result;
}
// ======================== anyURI ========================
public static CharSequence printAnyURI(CharSequence val)
{
return val;
}
/**
* Checkes the regular expression of URI, defined by RFC2369 http://www.ietf.org/rfc/rfc2396.txt Appendix B.
* Note: The whitespace normalization rule collapse must be applied priot to calling this method.
* @param lexical_value the lexical value
* @return same input value if input value is in the lexical space
* @throws InvalidLexicalValueException
*/
public static CharSequence lexAnyURI(CharSequence lexical_value)
{
/* // Reg exp from RFC2396, but it's too forgiving for XQTS
Pattern p = Pattern.compile("^([^:/?#]+:)?(//[^/?#]*)?([^?#]*)(\\?[^#]*)?(#.*)?");
Matcher m = p.matcher(lexical_value);
if ( !m.matches() )
throw new InvalidLexicalValueException("invalid anyURI value");
else
{
for ( int i = 0; i<= m.groupCount(); i++ )
{
System.out.print(" " + i + ": " + m.group(i));
}
System.out.println("");
return lexical_value;
} */
// Per XMLSchema spec allow spaces inside URIs
StringBuilder s = new StringBuilder(lexical_value.toString());
for (int ic = 0; ic<URI_CHARS_TO_BE_REPLACED.length; ic++)
{
int i = 0;
while ((i = s.indexOf(URI_CHARS_TO_BE_REPLACED[ic], i)) >= 0)
{
s.replace(i, i + 1, URI_CHARS_REPLACED_WITH[ic]);
i += 3;
}
}
try
{
URI.create(s.toString());
}
catch (IllegalArgumentException e)
{
throw new InvalidLexicalValueException("invalid anyURI value: " + lexical_value, e);
}
return lexical_value;
}
}