Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ public Sequence eval(final Sequence contextSequence, final Item contextItem) thr
throw new XPathException(this, ErrorCodes.XPTY0004, "Cannot cast " + Type.getTypeName(item.getType()) + " to xs:QName");
}
} else {
// Per XPath F&O 3.1, Section 19: if the source and target types
// have no valid casting relationship, raise XPTY0004 (not FORG0001).
if (!Type.isCastable(item.getType(), requiredType)) {
throw new XPathException(this, ErrorCodes.XPTY0004,
"Cannot cast " + Type.getTypeName(item.getType()) +
" to " + Type.getTypeName(requiredType));
}
result = item.convertTo(requiredType);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,19 @@ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathExc
}
else {
try {
// Per XPath F&O 3.1: if the cast is impossible per the casting table,
// castable as returns false without attempting the conversion.
if (!Type.isCastable(seq.itemAt(0).getType(), requiredType)) {
result = BooleanValue.FALSE;
} else {
seq.itemAt(0).convertTo(requiredType);
//If ? is specified after the target type, the result of the cast expression is an empty sequence.
if (requiredCardinality.isSuperCardinalityOrEqualOf(seq.getCardinality()))
{result = BooleanValue.TRUE;}
//If ? is not specified after the target type, a type error is raised [err:XPTY0004].
else
{result = BooleanValue.FALSE;}
}
//TODO : improve by *not* using a costly exception ?
} catch(final XPathException e) {
result = BooleanValue.FALSE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,9 @@ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathExc
{max = value;}

else {
if (Type.getCommonSuperType(max.getType(), value.getType()) == Type.ANY_ATOMIC_TYPE) {
if (Type.getCommonSuperType(max.getType(), value.getType()) == Type.ANY_ATOMIC_TYPE
&& !(Type.subTypeOfUnion(max.getType(), Type.NUMERIC)
&& Type.subTypeOfUnion(value.getType(), Type.NUMERIC))) {
throw new XPathException(this, ErrorCodes.FORG0006, "Cannot compare " + Type.getTypeName(max.getType()) +
" and " + Type.getTypeName(value.getType()), max);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,9 @@ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathExc
if (min == null)
{min = value;}
else {
if (Type.getCommonSuperType(min.getType(), value.getType()) == Type.ANY_ATOMIC_TYPE) {
if (Type.getCommonSuperType(min.getType(), value.getType()) == Type.ANY_ATOMIC_TYPE
&& !(Type.subTypeOfUnion(min.getType(), Type.NUMERIC)
&& Type.subTypeOfUnion(value.getType(), Type.NUMERIC))) {
throw new XPathException(this, ErrorCodes.FORG0006, "Cannot compare " + Type.getTypeName(min.getType()) +
" and " + Type.getTypeName(value.getType()), value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@ private class Parser {
private final char[] WS = {0x000A, 0x0009, 0x000D, 0x0020};
private final String WS_STR = new String(WS);

private final String[] dayNames = {
"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"
private final String[] lowerDayNames = {
"mon", "tue", "wed", "thu", "fri", "sat", "sun",
"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"
};

private final String[] monthNames = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
private final String[] lowerMonthNames = {
"jan", "feb", "mar", "apr", "may", "jun",
"jul", "aug", "sep", "oct", "nov", "dec"
};

private final String[] tzNames = {
Expand Down Expand Up @@ -163,30 +163,53 @@ public XMLGregorianCalendar parse() throws IllegalArgumentException {
if (vidx != vlen) {
throw new IllegalArgumentException(value);
}
// Handle 24:00:00 as midnight at the end of the day (= 00:00:00 next day)
if (hour == 24 && minute == 0 && second == 0
&& (fractionalSecond == null || fractionalSecond.signum() == 0)) {
hour = 0;
final XMLGregorianCalendar cal = TimeUtils
.getInstance()
.getFactory()
.newXMLGregorianCalendar(year, month, day, hour, minute, second, fractionalSecond, timezone);
cal.add(TimeUtils.getInstance().getFactory().newDuration(true, 0, 0, 1, 0, 0, 0));
return cal;
}
return TimeUtils
.getInstance()
.getFactory()
.newXMLGregorianCalendar(year, month, day, hour, minute, second, fractionalSecond, timezone);
}

private void dayName() {
if (StringUtils.startsWithAny(value, dayNames)) {
skipTo(WS_STR);
vidx++;
if (StringUtils.startsWithAny(value.substring(vidx).toLowerCase(), lowerDayNames)) {
skipTo(WS_STR + ",");
if (vidx < vlen && (isWS(peek()) || peek() == ',')) {
vidx++;
}
}
}

private void dateSpec() throws IllegalArgumentException {
if (isWS(peek())) {
skipWS();
}
if (StringUtils.startsWithAny(value.substring(vidx), monthNames)) {
if (startsWithMonthName(value.substring(vidx))) {
asctime();
} else {
rfcDate();
}
}

private boolean startsWithMonthName(final String s) {
final String lower = s.toLowerCase();
for (final String mn : lowerMonthNames) {
if (lower.startsWith(mn)) {
return true;
}
}
return false;
}

private void rfcDate() throws IllegalArgumentException {
day();
dsep();
Expand Down Expand Up @@ -232,8 +255,8 @@ private void month() throws IllegalArgumentException {
if (vidx >= vlen) {
throw new IllegalArgumentException(value);
}
final String monthName = value.substring(vstart, vidx);
final int idx = Arrays.asList(monthNames).indexOf(monthName);
final String monthName = value.substring(vstart, vidx).toLowerCase();
final int idx = Arrays.asList(lowerMonthNames).indexOf(monthName);
if (idx < 0) {
throw new IllegalArgumentException(value);
}
Expand All @@ -248,12 +271,18 @@ private void time() throws IllegalArgumentException {
hours();
minutes();
seconds();
skipWS();
timezone();
// Whitespace before timezone is optional per the IETF date grammar
if (isWS(peek())) {
skipWS();
}
// Timezone is optional in the grammar: (S? timezone)?
if (vidx < vlen) {
timezone();
}
}

private void hours() throws IllegalArgumentException {
hour = parseInt(2, 2);
hour = parseInt(1, 2);
}

private void minutes() throws IllegalArgumentException {
Expand All @@ -263,12 +292,13 @@ private void minutes() throws IllegalArgumentException {
}

private void seconds() throws IllegalArgumentException {
if (isWS(peek())) {
if (peek() != ':') {
// No colon means no seconds component
second = 0;
return;
}
skip(':');
second = parseInt(2, 2);
second = parseInt(1, 2);
fractionalSecond = parseBigDecimal();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
package org.exist.xquery.functions.fn;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import org.exist.Namespaces;
Expand All @@ -38,6 +39,8 @@

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;

import static org.exist.xquery.FunctionDSL.*;
import static org.exist.xquery.functions.fn.FnModule.functionSignatures;
Expand Down Expand Up @@ -212,6 +215,17 @@ private Sequence parseResource(Sequence href, String handleDuplicates, JsonFacto
}
try {
String url = href.getStringValue();
// Resolve relative URIs against the static base URI
if (url.indexOf(':') == Constants.STRING_NOT_FOUND) {
final String baseURI = context.getBaseURI().getStringValue();
if (baseURI != null && !baseURI.isEmpty()) {
try {
url = new URI(baseURI).resolve(url).toString();
} catch (final URISyntaxException e) {
// fall through to default handling
}
}
}
if (url.indexOf(':') == Constants.STRING_NOT_FOUND) {
url = XmldbURI.EMBEDDED_SERVER_URI_PREFIX + url;
}
Expand All @@ -225,6 +239,8 @@ private Sequence parseResource(Sequence href, String handleDuplicates, JsonFacto
final Item result = readValue(context, parser, handleDuplicates);
return result == null ? Sequence.EMPTY_SEQUENCE : result.toSequence();
}
} catch (final JsonParseException e) {
throw new XPathException(this, ErrorCodes.FOJS0001, e.getMessage());
} catch (IOException | PermissionDeniedException e) {
throw new XPathException(this, ErrorCodes.FOUT1170, e.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
package org.exist.xquery.value;

import org.exist.util.io.Base64OutputStream;
import org.exist.xquery.ErrorCodes;
import org.exist.xquery.Expression;
import org.exist.xquery.XPathException;

Expand Down Expand Up @@ -50,7 +51,7 @@ private Matcher getMatcher(final String toMatch) {
@Override
public void verifyString(String str) throws XPathException {
if (!getMatcher(str).matches()) {
throw new XPathException((Expression) null, "FORG0001: Invalid base64 data");
throw new XPathException((Expression) null, ErrorCodes.FORG0001, "Invalid base64 data");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ public AtomicValue convertTo(final int requiredType) throws XPathException {
case Type.UNTYPED_ATOMIC:
return new UntypedAtomicValue(getExpression(), getStringValue());
default:
throw new XPathException(getExpression(), ErrorCodes.XPTY0004,
// Handle integer subtypes (nonPositiveInteger, negativeInteger, etc.)
if (Type.subTypeOf(requiredType, Type.INTEGER)) {
return new IntegerValue(getExpression(), value ? 1 : 0).convertTo(requiredType);
}
throw new XPathException(getExpression(), ErrorCodes.FORG0001,
"cannot convert 'xs:boolean(" + value + ")' to " + Type.getTypeName(requiredType));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,21 +195,21 @@ public AtomicValue convertTo(final int requiredType) throws XPathException {

public DecimalValue toDecimalValue() throws XPathException {
if (isNaN() || isInfinite()) {
throw conversionError(Type.DECIMAL);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no longer using the existing conversionError()method?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was the method call conversionError() removed and code duplication introduced? we could pass in an additional error code for the other usages instead in order to reduce code duplication.

throw conversionError(ErrorCodes.FOCA0002, Type.DECIMAL);
}
return new DecimalValue(getExpression(), BigDecimal.valueOf(value));
}

public IntegerValue toIntegerValue() throws XPathException {
if (isNaN() || isInfinite()) {
throw conversionError(Type.INTEGER);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no longer using the existing conversionError()method?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as in toDecimalValue()

throw conversionError(ErrorCodes.FOCA0002, Type.INTEGER);
}
return new IntegerValue(getExpression(), (long) value);
}

public IntegerValue toIntegerSubType(final int subType) throws XPathException {
if (isNaN() || isInfinite()) {
throw conversionError(subType);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no longer using the existing conversionError()method?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as in toDecimalValue()

throw conversionError(ErrorCodes.FOCA0002, subType);
}
if (subType != Type.INTEGER && value > Integer.MAX_VALUE) {
throw new XPathException(getExpression(), ErrorCodes.FOCA0003, "Value is out of range for type "
Expand All @@ -219,7 +219,11 @@ public IntegerValue toIntegerSubType(final int subType) throws XPathException {
}

private XPathException conversionError(final int type) {
return new XPathException(getExpression(), ErrorCodes.FORG0001, "Cannot convert "
return conversionError(ErrorCodes.FORG0001, type);
}

private XPathException conversionError(final ErrorCodes.ErrorCode errorCode, final int type) {
return new XPathException(getExpression(), errorCode, "Cannot convert "
+ Type.getTypeName(getType()) + "('" + getStringValue() + "') to "
+ Type.getTypeName(type));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,28 +379,28 @@ private void checkType() throws XPathException {
case Type.LANGUAGE:
final Matcher matcher = langPattern.matcher(value);
if (!matcher.matches()) {
throw new XPathException(getExpression(),
throw new XPathException(getExpression(), ErrorCodes.FORG0001,
"Type error: string "
+ value
+ " is not valid for type xs:language");
}
return;
case Type.NAME:
if (QName.isQName(value) != VALID.val) {
throw new XPathException(getExpression(), "Type error: string " + value + " is not a valid xs:Name");
throw new XPathException(getExpression(), ErrorCodes.FORG0001, "Type error: string " + value + " is not a valid xs:Name");
}
return;
case Type.NCNAME:
case Type.ID:
case Type.IDREF:
case Type.ENTITY:
if (!XMLNames.isNCName(value)) {
throw new XPathException(getExpression(), "Type error: string " + value + " is not a valid " + Type.getTypeName(type));
throw new XPathException(getExpression(), ErrorCodes.FORG0001, "Type error: string " + value + " is not a valid " + Type.getTypeName(type));
}
return;
case Type.NMTOKEN:
if (!XMLNames.isNmToken(value)) {
throw new XPathException(getExpression(), "Type error: string " + value + " is not a valid xs:NMTOKEN");
throw new XPathException(getExpression(), ErrorCodes.FORG0001, "Type error: string " + value + " is not a valid xs:NMTOKEN");
}
}
}
Expand Down
Loading
Loading