Skip to content

Commit 41d3ea2

Browse files
authored
Merge pull request #155 from evolvedbinary/6.x.x/hotfix/fn-format-number
[6.x.x] Improvements to `fn:format-number`
2 parents d4514ca + 12b1135 commit 41d3ea2

9 files changed

Lines changed: 440 additions & 34 deletions

File tree

exist-core/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,7 @@
765765
<include>src/test/resources/standalone-webapp/WEB-INF/web.xml</include>
766766
<include>src/test/xquery/tail-recursion.xml</include>
767767
<include>src/test/xquery/maps/maps.xqm</include>
768+
<include>src/test/xquery/numbers/format-numbers.xql</include>
768769
<include>src/test/xquery/util/util.xml</include>
769770
<include>src/test/xquery/xquery3/parse-xml.xqm</include>
770771
<include>src/test/xquery/xquery3/serialize.xql</include>
@@ -1072,6 +1073,7 @@
10721073
<include>src/main/java/org/exist/xquery/CombiningExpression.java</include>
10731074
<include>src/test/java/org/exist/xquery/ConstructedNodesRecoveryTest.java</include>
10741075
<include>src/main/java/org/exist/xquery/Context.java</include>
1076+
<include>src/main/java/org/exist/xquery/DecimalFormat.java</include>
10751077
<include>src/main/java/org/exist/xquery/DeferredFunctionCall.java</include>
10761078
<include>src/main/java/org/exist/xquery/DynamicCardinalityCheck.java</include>
10771079
<include>src/main/java/org/exist/xquery/DynamicTypeCheck.java</include>
@@ -1255,6 +1257,7 @@
12551257
<include>src/main/java/org/exist/xquery/functions/xmldb/XMLDBStore.java</include>
12561258
<include>src/main/java/org/exist/xquery/functions/xmldb/XMLDBXUpdate.java</include>
12571259
<include>src/test/java/org/exist/xquery/functions/xquery3/TryCatchTest.java</include>
1260+
<include>src/main/antlr/org/exist/xquery/parser/XQuery.g</include>
12581261
<include>src/main/antlr/org/exist/xquery/parser/XQueryTree.g</include>
12591262
<include>src/main/java/org/exist/xquery/pragmas/Optimize.java</include>
12601263
<include>src/test/java/org/exist/xquery/update/AbstractUpdateTest.java</include>
@@ -1342,6 +1345,7 @@
13421345
<exclude>src/test/xquery/pi.xqm</exclude>
13431346
<exclude>src/test/xquery/tail-recursion.xml</exclude>
13441347
<exclude>src/test/xquery/maps/maps.xqm</exclude>
1348+
<exclude>src/test/xquery/numbers/format-numbers.xql</exclude>
13451349
<exclude>src/test/xquery/securitymanager/acl.xqm</exclude>
13461350
<exclude>src/test/xquery/util/util.xml</exclude>
13471351
<exclude>src/test/xquery/xquery3/parse-xml.xqm</exclude>
@@ -1736,6 +1740,7 @@
17361740
<exclude>src/main/java/org/exist/xquery/CombiningExpression.java</exclude>
17371741
<exclude>src/test/java/org/exist/xquery/ConstructedNodesRecoveryTest.java</exclude>
17381742
<exclude>src/main/java/org/exist/xquery/Context.java</exclude>
1743+
<exclude>src/main/java/org/exist/xquery/DecimalFormat.java</exclude>
17391744
<exclude>src/main/java/org/exist/xquery/DeferredFunctionCall.java</exclude>
17401745
<exclude>src/main/java/org/exist/xquery/DynamicCardinalityCheck.java</exclude>
17411746
<exclude>src/main/java/org/exist/xquery/DynamicTypeCheck.java</exclude>
@@ -1945,6 +1950,7 @@
19451950
<exclude>src/main/java/org/exist/xquery/functions/xmldb/XMLDBXUpdate.java</exclude>
19461951
<exclude>src/test/java/org/exist/xquery/functions/xquery3/SerializeTest.java</exclude>
19471952
<exclude>src/test/java/org/exist/xquery/functions/xquery3/TryCatchTest.java</exclude>
1953+
<exclude>src/main/antlr/org/exist/xquery/parser/XQuery.g</exclude>
19481954
<exclude>src/main/antlr/org/exist/xquery/parser/XQueryTree.g</exclude>
19491955
<exclude>src/main/java/org/exist/xquery/pragmas/Optimize.java</exclude>
19501956
<exclude>src/main/java/org/exist/xquery/pragmas/TimePragma.java</exclude>

exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,28 @@
11
/*
2+
* Elemental
3+
* Copyright (C) 2024, Evolved Binary Ltd
4+
*
5+
* admin@evolvedbinary.com
6+
* https://www.evolvedbinary.com | https://www.elemental.xyz
7+
*
8+
* This library is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU Lesser General Public
10+
* License as published by the Free Software Foundation; version 2.1.
11+
*
12+
* This library is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
* Lesser General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public
18+
* License along with this library; if not, write to the Free Software
19+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20+
*
21+
* NOTE: Parts of this file contain code from 'The eXist-db Authors'.
22+
* The original license header is included below.
23+
*
24+
* =====================================================================
25+
*
226
* eXist-db Open Source Native XML Database
327
* Copyright (C) 2001 The eXist-db Authors
428
*
@@ -143,6 +167,8 @@ imaginaryTokenDefinitions
143167
NAMESPACE_DECL
144168
DEF_NAMESPACE_DECL
145169
DEF_COLLATION_DECL
170+
DECIMAL_FORMAT_DECL
171+
DEFAULT_DECIMAL_FORMAT
146172
DEF_FUNCTION_NS_DECL
147173
CONTEXT_ITEM_DECL
148174
ANNOT_DECL
@@ -244,7 +270,7 @@ prolog throws XPathException
244270
(
245271
importDecl
246272
|
247-
( "declare" ( "default" | "boundary-space" | "ordering" | "construction" | "base-uri" | "copy-namespaces" | "namespace" ) ) =>
273+
( "declare" ( "default" | "boundary-space" | "ordering" | "construction" | "base-uri" | "copy-namespaces" | "namespace" | "decimal-format" ) ) =>
248274
s:setter
249275
{
250276
if(!inSetters)
@@ -283,10 +309,44 @@ versionDecl throws XPathException
283309
{ #versionDecl = #(#[VERSION_DECL, v.getText()], enc); }
284310
;
285311
312+
dfPropertyName
313+
:
314+
"decimal-separator"
315+
| "grouping-separator"
316+
| "infinity"
317+
| "minus-sign"
318+
| "NaN"
319+
| "percent"
320+
| "per-mille"
321+
| "zero-digit"
322+
| "digit"
323+
| "pattern-separator"
324+
| "exponent-separator"
325+
;
326+
327+
decimalFormatDecl
328+
{ String dfName = null; }
329+
:
330+
"declare"!
331+
(
332+
"default" "decimal-format"
333+
( dfPropertyName EQ! STRING_LITERAL )*
334+
{
335+
## = #( #[DECIMAL_FORMAT_DECL, "DECIMAL_FORMAT_DECL"], #[DEFAULT_DECIMAL_FORMAT, "DEFAULT_DECIMAL_FORMAT"], ## );
336+
}
337+
|
338+
"decimal-format" eqName
339+
( dfPropertyName EQ! STRING_LITERAL )*
340+
{
341+
## = #( #[DECIMAL_FORMAT_DECL, "DECIMAL_FORMAT_DECL"], ## );
342+
}
343+
)
344+
;
345+
286346
setter
287347
:
288348
(
289-
( "declare" "default" ) =>
349+
( "declare" "default" ( "collation" | "element" | "function" | "order" ) ) =>
290350
"declare"! "default"!
291351
(
292352
"collation"! defc:STRING_LITERAL
@@ -318,6 +378,8 @@ setter
318378
|
319379
( "declare" "namespace" ) =>
320380
namespaceDecl
381+
| ( "declare" ( "default" )? "decimal-format" ) =>
382+
decimalFormatDecl
321383
)
322384
;
323385

exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ options {
106106
protected Set<String> importedModules = new HashSet<>();
107107
protected Set<String> importedModuleFunctions = null;
108108
protected Set<QName> importedModuleVariables = null;
109+
private boolean hasDefaultDecimalFormat = false;
109110

110111
public XQueryTreeParser(XQueryContext context) {
111112
this(context, null);
@@ -497,6 +498,76 @@ throws PermissionDeniedException, EXistException, XPathException
497498
}
498499
)
499500
|
501+
#(
502+
DECIMAL_FORMAT_DECL
503+
{
504+
final XQueryAST root = (XQueryAST) _t; // points to DECIMAL_FORMAT_DECL
505+
// first sibling is either DEFAULT_DECIMAL_FORMAT (default) or EQNAME (named)
506+
final XQueryAST dfName = (XQueryAST) root.getNextSibling();
507+
508+
final QName qnDfName;
509+
if ("default".equals(dfName.getText())) {
510+
qnDfName = XQueryContext.UNNAMED_DECIMAL_FORMAT;
511+
if (hasDefaultDecimalFormat) {
512+
throw new XPathException(dfName.getLine(), dfName.getColumn(), ErrorCodes.W3CErrorCode.XQST0111.getErrorCode(), "Query prolog cannot contain two default decimal format declarations.");
513+
} else {
514+
hasDefaultDecimalFormat = true;
515+
}
516+
} else {
517+
try {
518+
qnDfName = QName.parse(staticContext, dfName.getText(), null);
519+
} catch (final IllegalQNameException iqe) {
520+
throw new XPathException(dfName.getLine(), dfName.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + dfName.getText());
521+
}
522+
523+
if (staticContext.getStaticDecimalFormat(qnDfName) != null) {
524+
throw new XPathException(dfName.getLine(), dfName.getColumn(), ErrorCodes.W3CErrorCode.XQST0111.getErrorCode(), "Query prolog cannot contain two decimal format declarations with the same name: " + dfName.getText());
525+
}
526+
}
527+
528+
// position current at the first property name for the decimal format
529+
XQueryAST current = (XQueryAST) dfName.getNextSibling();
530+
if ("default".equals(dfName.getText())) {
531+
current = (XQueryAST) current.getNextSibling();
532+
}
533+
534+
final Map<String, String> dfProperties = new HashMap<>();
535+
536+
while (current != null) {
537+
final XQueryAST pname = current;
538+
final XQueryAST pval = (XQueryAST) current.getNextSibling();
539+
540+
if (pval == null) {
541+
break;
542+
}
543+
544+
final String pn = pname.getText();
545+
String pv = pval.getText();
546+
if (pv.length() >= 2 && (pv.startsWith("\"") || pv.startsWith("'"))) {
547+
pv = pv.substring(1, pv.length() - 1);
548+
}
549+
if (dfProperties.put(pn, pv) != null) {
550+
throw new XPathException(dfName.getLine(), dfName.getColumn(), ErrorCodes.W3CErrorCode.XQST0114.getErrorCode(), "Decimal format: " + dfName.getText() + " defines the property: " + pn + " more than once.");
551+
}
552+
553+
current = (XQueryAST) pval.getNextSibling();
554+
}
555+
556+
final DecimalFormat df;
557+
try {
558+
df = DecimalFormat.fromProperties(dfProperties);
559+
} catch (final IllegalArgumentException ex) {
560+
throw new XPathException(dfName.getLine(), dfName.getColumn(), ErrorCodes.W3CErrorCode.XQST0097.getErrorCode(), ex.getMessage() + " within the picture string of the decimal format: " + dfName.getText() + ".");
561+
}
562+
if (!df.checkDistinctCharacters()) {
563+
throw new XPathException(dfName.getLine(), dfName.getColumn(), ErrorCodes.W3CErrorCode.XQST0098.getErrorCode(), "Characters within the picture string of the decimal format: " + dfName.getText() + " are not distinct.");
564+
}
565+
566+
staticContext.setStaticDecimalFormat(qnDfName, df);
567+
context.setStaticDecimalFormat(qnDfName, df);
568+
}
569+
)
570+
|
500571
#(
501572
qname:GLOBAL_VAR
502573
{

exist-core/src/main/java/org/exist/util/CodePointString.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -353,14 +353,14 @@ public CodePointString insert(final int[] indexes, final int codePoint) {
353353
* @return this
354354
*/
355355
public CodePointString removeFirst(final int codePoint) {
356-
int idx = -1;
357-
for (int i = 0; i < codePoints.length; i++) {
358-
if (codePoints[i] == codePoint) {
359-
idx = i;
360-
break;
361-
}
362-
}
356+
final int idx = indexOf(codePoint);
357+
return removeChar(idx);
358+
}
363359

360+
/**
361+
* Removes the codepoint at the specified index.
362+
*/
363+
public CodePointString removeChar(final int idx) {
364364
if (idx > -1) {
365365
final int[] newCodePoints = new int[codePoints.length - 1];
366366

0 commit comments

Comments
 (0)