Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ imaginaryTokenDefinitions
PREVIOUS_ITEM
NEXT_ITEM
WINDOW_VARS
DECIMAL_FORMAT_DECL
DEF_DECIMAL_FORMAT_DECL
;

// === XPointer ===
Expand Down Expand Up @@ -256,7 +258,7 @@ prolog throws XPathException
(
importDecl
|
( "declare" ( "default" | "boundary-space" | "ordering" | "construction" | "base-uri" | "copy-namespaces" | "namespace" ) ) =>
( "declare" ( "default" | "boundary-space" | "ordering" | "construction" | "base-uri" | "copy-namespaces" | "namespace" | "decimal-format" ) ) =>
s:setter
{
if(!inSetters)
Expand Down Expand Up @@ -311,6 +313,9 @@ setter
{ #setter= #(#[DEF_FUNCTION_NS_DECL, "defaultFunctionNSDecl"], deff); }
|
"order"^ "empty"! ( "greatest" | "least" )
|
"decimal-format"! ( dfDefProperty )*
{ #setter = #(#[DEF_DECIMAL_FORMAT_DECL, "defaultDecimalFormatDecl"], #setter); }
)
|
( "declare" "boundary-space" ) =>
Expand All @@ -330,9 +335,30 @@ setter
|
( "declare" "namespace" ) =>
namespaceDecl
|
( "declare" "decimal-format" ) =>
decimalFormatDecl
)
;

decimalFormatDecl
{ String eq = null; }
:
decl:"declare"! "decimal-format"! eq=eqName! ( dfDefProperty )*
{
#decimalFormatDecl = #(#[DECIMAL_FORMAT_DECL, eq], #decimalFormatDecl);
#decimalFormatDecl.copyLexInfo(#decl);
}
;

dfDefProperty
:
( "decimal-separator"^ | "grouping-separator"^ | "infinity"^ | "minus-sign"^
| "NaN"^ | "percent"^ | "per-mille"^ | "zero-digit"^ | "digit"^
| "pattern-separator"^ | "exponent-separator"^ )
EQ! STRING_LITERAL
;

preserveMode
:
( "preserve" | "no-preserve" )
Expand Down Expand Up @@ -2304,6 +2330,30 @@ reservedKeywords returns [String name]
"next" { name = "next"; }
|
"when" { name = "when"; }
|
"decimal-format" { name = "decimal-format"; }
|
"decimal-separator" { name = "decimal-separator"; }
|
"grouping-separator" { name = "grouping-separator"; }
|
"infinity" { name = "infinity"; }
|
"minus-sign" { name = "minus-sign"; }
|
"NaN" { name = "NaN"; }
|
"percent" { name = "percent"; }
|
"per-mille" { name = "per-mille"; }
|
"zero-digit" { name = "zero-digit"; }
|
"digit" { name = "digit"; }
|
"pattern-separator" { name = "pattern-separator"; }
|
"exponent-separator" { name = "exponent-separator"; }
;


Expand Down
147 changes: 147 additions & 0 deletions exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,122 @@ options {
return variableName;
}
}

private static String dfRequireSingleChar(final AST node, final String propName, final String value) throws XPathException {
if (value.codePointCount(0, value.length()) != 1) {
throw new XPathException(node.getLine(), node.getColumn(), ErrorCodes.XQST0098,
"The value of decimal-format property '" + propName + "' must be a single character, but got: \"" + value + "\"");
}
return value;
}

private static void dfValidateZeroDigit(final AST node, final String value) throws XPathException {
final int cp = value.codePointAt(0);
if (Character.getType(cp) != Character.DECIMAL_DIGIT_NUMBER || Character.getNumericValue(cp) != 0) {
throw new XPathException(node.getLine(), node.getColumn(), ErrorCodes.XQST0098,
"The value of decimal-format property 'zero-digit' must be a Unicode digit with numeric value zero, but got: \"" + value + "\"");
}
}

private static void dfValidateDistinctPictureChars(final AST node, final DecimalFormat df) throws XPathException {
// The 8 single-character picture-string properties must all have distinct values
final int[] chars = { df.decimalSeparator, df.groupingSeparator, df.percent, df.perMille,
df.zeroDigit, df.digit, df.patternSeparator, df.exponentSeparator };
final String[] names = { "decimal-separator", "grouping-separator", "percent", "per-mille",
"zero-digit", "digit", "pattern-separator", "exponent-separator" };
for (int i = 0; i < chars.length; i++) {
for (int j = i + 1; j < chars.length; j++) {
if (chars[i] == chars[j]) {
throw new XPathException(node.getLine(), node.getColumn(), ErrorCodes.XQST0098,
"Decimal-format properties '" + names[i] + "' and '" + names[j] +
"' must have distinct values, but both are: '" + new String(Character.toChars(chars[i])) + "'");
}
}
}
}

private DecimalFormat processDecimalFormatProperties(final AST parentNode) throws XPathException {
// Start with UNNAMED defaults
int decimalSeparator = DecimalFormat.UNNAMED.decimalSeparator;
int exponentSeparator = DecimalFormat.UNNAMED.exponentSeparator;
int groupingSeparator = DecimalFormat.UNNAMED.groupingSeparator;
int percent = DecimalFormat.UNNAMED.percent;
int perMille = DecimalFormat.UNNAMED.perMille;
int zeroDigit = DecimalFormat.UNNAMED.zeroDigit;
int digit = DecimalFormat.UNNAMED.digit;
int patternSeparator = DecimalFormat.UNNAMED.patternSeparator;
String infinity = DecimalFormat.UNNAMED.infinity;
String nan = DecimalFormat.UNNAMED.NaN;
int minusSign = DecimalFormat.UNNAMED.minusSign;

AST child = parentNode.getFirstChild();
while (child != null) {
final String propName = child.getText();
final AST valueNode = child.getFirstChild();
if (valueNode == null) {
child = child.getNextSibling();
continue;
}
final String value = valueNode.getText();

switch (propName) {
case "decimal-separator":
dfRequireSingleChar(child, propName, value);
decimalSeparator = value.codePointAt(0);
break;
case "grouping-separator":
dfRequireSingleChar(child, propName, value);
groupingSeparator = value.codePointAt(0);
break;
case "infinity":
infinity = value;
break;
case "minus-sign":
dfRequireSingleChar(child, propName, value);
minusSign = value.codePointAt(0);
break;
case "NaN":
nan = value;
break;
case "percent":
dfRequireSingleChar(child, propName, value);
percent = value.codePointAt(0);
break;
case "per-mille":
dfRequireSingleChar(child, propName, value);
perMille = value.codePointAt(0);
break;
case "zero-digit":
dfRequireSingleChar(child, propName, value);
dfValidateZeroDigit(child, value);
zeroDigit = value.codePointAt(0);
break;
case "digit":
dfRequireSingleChar(child, propName, value);
digit = value.codePointAt(0);
break;
case "pattern-separator":
dfRequireSingleChar(child, propName, value);
patternSeparator = value.codePointAt(0);
break;
case "exponent-separator":
dfRequireSingleChar(child, propName, value);
exponentSeparator = value.codePointAt(0);
break;
default:
break;
}
child = child.getNextSibling();
}

final DecimalFormat df = new DecimalFormat(
decimalSeparator, exponentSeparator, groupingSeparator,
percent, perMille, zeroDigit, digit,
patternSeparator, infinity, nan, minusSign
);
dfValidateDistinctPictureChars(parentNode, df);
return df;
}
}

xpointer [PathExpr path]
Expand Down Expand Up @@ -337,6 +453,8 @@ throws PermissionDeniedException, EXistException, XPathException
boolean baseuri = false;
boolean ordering = false;
boolean construction = false;
Set declaredDecimalFormats = new HashSet();
boolean defaultDecimalFormatDeclared = false;

}:
(
Expand Down Expand Up @@ -632,6 +750,35 @@ throws PermissionDeniedException, EXistException, XPathException
)
)
|
#(
dfDecl:DECIMAL_FORMAT_DECL (.)*
{
final QName dfQName;
try {
dfQName = QName.parse(staticContext, dfDecl.getText(), null);
} catch (final IllegalQNameException iqe) {
throw new XPathException(dfDecl.getLine(), dfDecl.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix in decimal format name: " + dfDecl.getText());
}
final String dfKey = dfQName.getNamespaceURI() + ":" + dfQName.getLocalPart();
if (declaredDecimalFormats.contains(dfKey))
throw new XPathException(dfDecl, ErrorCodes.XQST0097, "Duplicate decimal format declaration: " + dfDecl.getText());
declaredDecimalFormats.add(dfKey);
final DecimalFormat df = processDecimalFormatProperties(dfDecl);
context.setStaticDecimalFormat(dfQName, df);
}
)
|
#(
defDfDecl:DEF_DECIMAL_FORMAT_DECL (.)*
{
if (defaultDecimalFormatDeclared)
throw new XPathException(defDfDecl, ErrorCodes.XQST0097, "Duplicate default decimal format declaration.");
defaultDecimalFormatDeclared = true;
final DecimalFormat df = processDecimalFormatProperties(defDfDecl);
context.setDefaultStaticDecimalFormat(df);
}
)
|
functionDecl [path]
|
importDecl [path]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@
/**
* Data class for a Decimal Format.
*
* See https://www.w3.org/TR/xpath-31/#dt-static-decimal-formats
* See https://www.w3.org/TR/xquery-31/#id-decimal-format-decl
*
* NOTE: UTF-16 characters are stored as code-points!
*
* @author <a href="mailto:adam@evolvedbinary.com">Adam Retter</a>
*/
public class DecimalFormat {

/**
* The default (unnamed) decimal format as defined by the XQuery 3.1 specification.
*
* @see <a href="https://www.w3.org/TR/xquery-31/#id-decimal-format-decl">XQuery 3.1 §4.10: Decimal Format Declaration</a>
*/
public static final DecimalFormat UNNAMED = new DecimalFormat(
'.',
'e',
Expand Down
7 changes: 7 additions & 0 deletions exist-core/src/main/java/org/exist/xquery/ErrorCodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ public class ErrorCodes {

public static final ErrorCode XQST0094 = new W3CErrorCode("XQST0094", "The name of each grouping variable must be equal (by the eq operator on expanded QNames) to the name of a variable in the input tuple stream.");

public static final ErrorCode XQST0097 = new W3CErrorCode("XQST0097",
"It is a static error to have more than one decimal-format declaration with the same name, " +
"or more than one default decimal-format declaration, in the same module.");
public static final ErrorCode XQST0098 = new W3CErrorCode("XQST0098",
"It is a static error if the properties representing characters used in a picture string " +
"do not each have distinct values, or if a property value is not valid for its property.");

public static final ErrorCode XQDY0101 = new W3CErrorCode("XQDY0101", "An error is raised if a computed namespace constructor attempts to do any of the following:\n" +
"Bind the prefix xml to some namespace URI other than http://www.w3.org/XML/1998/namespace.\n" +
"Bind a prefix other than xml to the namespace URI http://www.w3.org/XML/1998/namespace.\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,9 @@ public class XQueryContext implements BinaryValueManager, Context {
* HTTP context.
*/
private @Nullable HttpContext httpContext = null;
/**
* Sentinel QName for the default (unnamed) decimal format per XQuery 3.1 §4.10.
*/
private static final QName UNNAMED_DECIMAL_FORMAT = new QName("__UNNAMED__", Function.BUILTIN_FUNCTION_NS);

private final Map<QName, DecimalFormat> staticDecimalFormats = hashMap(Tuple(UNNAMED_DECIMAL_FORMAT, DecimalFormat.UNNAMED));
Expand Down Expand Up @@ -2902,6 +2905,10 @@ public void setStaticDecimalFormat(final QName qnDecimalFormat, final DecimalFor
staticDecimalFormats.put(qnDecimalFormat, decimalFormat);
}

public void setDefaultStaticDecimalFormat(final DecimalFormat decimalFormat) {
staticDecimalFormats.put(UNNAMED_DECIMAL_FORMAT, decimalFormat);
}

public Map<String, Sequence> getCachedUriCollectionResults() {
return cachedUriCollectionResults;
}
Expand Down
Loading
Loading