Skip to content

Commit 1478adf

Browse files
authored
Merge pull request #152 from evolvedbinary/7.x.x/hotfix/fn-format-number
[7.x.x] Improvements to `fn:format-number`
2 parents 3ddb8a6 + 0daf9df commit 1478adf

File tree

9 files changed

+440
-34
lines changed

9 files changed

+440
-34
lines changed

exist-core/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,7 @@
791791
<include>src/test/resources/standalone-webapp/WEB-INF/web.xml</include>
792792
<include>src/test/xquery/tail-recursion.xml</include>
793793
<include>src/test/xquery/maps/maps.xqm</include>
794+
<include>src/test/xquery/numbers/format-numbers.xql</include>
794795
<include>src/test/xquery/util/util.xml</include>
795796
<include>src/test/xquery/xquery3/parse-xml.xqm</include>
796797
<include>src/test/xquery/xquery3/serialize.xql</include>
@@ -1102,6 +1103,7 @@
11021103
<include>src/test/java/org/exist/xquery/CleanupTest.java</include>
11031104
<include>src/test/java/org/exist/xquery/ConstructedNodesRecoveryTest.java</include>
11041105
<include>src/main/java/org/exist/xquery/Context.java</include>
1106+
<include>src/main/java/org/exist/xquery/DecimalFormat.java</include>
11051107
<include>src/main/java/org/exist/xquery/DeferredFunctionCall.java</include>
11061108
<include>src/main/java/org/exist/xquery/DynamicCardinalityCheck.java</include>
11071109
<include>src/main/java/org/exist/xquery/DynamicTypeCheck.java</include>
@@ -1284,6 +1286,7 @@
12841286
<include>src/main/java/org/exist/xquery/functions/xmldb/XMLDBStore.java</include>
12851287
<include>src/main/java/org/exist/xquery/functions/xmldb/XMLDBXUpdate.java</include>
12861288
<include>src/test/java/org/exist/xquery/functions/xquery3/TryCatchTest.java</include>
1289+
<include>src/main/antlr/org/exist/xquery/parser/XQuery.g</include>
12871290
<include>src/main/antlr/org/exist/xquery/parser/XQueryTree.g</include>
12881291
<include>src/test/java/org/exist/xquery/update/AbstractUpdateTest.java</include>
12891292
<include>src/test/java/org/exist/xquery/update/IndexIntegrationTest.java</include>
@@ -1374,6 +1377,7 @@
13741377
<exclude>src/test/xquery/tail-recursion.xml</exclude>
13751378
<exclude>src/test/xquery/type-promotion.xqm</exclude>
13761379
<exclude>src/test/xquery/maps/maps.xqm</exclude>
1380+
<exclude>src/test/xquery/numbers/format-numbers.xql</exclude>
13771381
<exclude>src/test/xquery/securitymanager/acl.xqm</exclude>
13781382
<exclude>src/test/xquery/util/util.xml</exclude>
13791383
<exclude>src/test/xquery/xqsuite/xqsuite-assertions-dynamic.xqm</exclude>
@@ -1781,6 +1785,7 @@
17811785
<exclude>src/test/java/org/exist/xquery/CleanupTest.java</exclude>
17821786
<exclude>src/test/java/org/exist/xquery/ConstructedNodesRecoveryTest.java</exclude>
17831787
<exclude>src/main/java/org/exist/xquery/Context.java</exclude>
1788+
<exclude>src/main/java/org/exist/xquery/DecimalFormat.java</exclude>
17841789
<exclude>src/main/java/org/exist/xquery/DeferredFunctionCall.java</exclude>
17851790
<exclude>src/main/java/org/exist/xquery/DynamicCardinalityCheck.java</exclude>
17861791
<exclude>src/main/java/org/exist/xquery/DynamicTypeCheck.java</exclude>
@@ -1989,6 +1994,7 @@
19891994
<exclude>src/main/java/org/exist/xquery/functions/xmldb/XMLDBXUpdate.java</exclude>
19901995
<exclude>src/test/java/org/exist/xquery/functions/xquery3/SerializeTest.java</exclude>
19911996
<exclude>src/test/java/org/exist/xquery/functions/xquery3/TryCatchTest.java</exclude>
1997+
<exclude>src/main/antlr/org/exist/xquery/parser/XQuery.g</exclude>
19921998
<exclude>src/main/antlr/org/exist/xquery/parser/XQueryTree.g</exclude>
19931999
<exclude>src/main/java/org/exist/xquery/pragmas/TimePragma.java</exclude>
19942000
<exclude>src/test/java/org/exist/xquery/update/AbstractUpdateTest.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
*
@@ -150,6 +174,8 @@ imaginaryTokenDefinitions
150174
NAMESPACE_DECL
151175
DEF_NAMESPACE_DECL
152176
DEF_COLLATION_DECL
177+
DECIMAL_FORMAT_DECL
178+
DEFAULT_DECIMAL_FORMAT
153179
DEF_FUNCTION_NS_DECL
154180
CONTEXT_ITEM_DECL
155181
ANNOT_DECL
@@ -256,7 +282,7 @@ prolog throws XPathException
256282
(
257283
importDecl
258284
|
259-
( "declare" ( "default" | "boundary-space" | "ordering" | "construction" | "base-uri" | "copy-namespaces" | "namespace" ) ) =>
285+
( "declare" ( "default" | "boundary-space" | "ordering" | "construction" | "base-uri" | "copy-namespaces" | "namespace" | "decimal-format" ) ) =>
260286
s:setter
261287
{
262288
if(!inSetters)
@@ -295,10 +321,44 @@ versionDecl throws XPathException
295321
{ #versionDecl = #(#[VERSION_DECL, v.getText()], enc); }
296322
;
297323
324+
dfPropertyName
325+
:
326+
"decimal-separator"
327+
| "grouping-separator"
328+
| "infinity"
329+
| "minus-sign"
330+
| "NaN"
331+
| "percent"
332+
| "per-mille"
333+
| "zero-digit"
334+
| "digit"
335+
| "pattern-separator"
336+
| "exponent-separator"
337+
;
338+
339+
decimalFormatDecl
340+
{ String dfName = null; }
341+
:
342+
"declare"!
343+
(
344+
"default" "decimal-format"
345+
( dfPropertyName EQ! STRING_LITERAL )*
346+
{
347+
## = #( #[DECIMAL_FORMAT_DECL, "DECIMAL_FORMAT_DECL"], #[DEFAULT_DECIMAL_FORMAT, "DEFAULT_DECIMAL_FORMAT"], ## );
348+
}
349+
|
350+
"decimal-format" eqName
351+
( dfPropertyName EQ! STRING_LITERAL )*
352+
{
353+
## = #( #[DECIMAL_FORMAT_DECL, "DECIMAL_FORMAT_DECL"], ## );
354+
}
355+
)
356+
;
357+
298358
setter
299359
:
300360
(
301-
( "declare" "default" ) =>
361+
( "declare" "default" ( "collation" | "element" | "function" | "order" ) ) =>
302362
"declare"! "default"!
303363
(
304364
"collation"! defc:STRING_LITERAL
@@ -330,6 +390,8 @@ setter
330390
|
331391
( "declare" "namespace" ) =>
332392
namespaceDecl
393+
| ( "declare" ( "default" )? "decimal-format" ) =>
394+
decimalFormatDecl
333395
)
334396
;
335397

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);
@@ -511,6 +512,76 @@ throws PermissionDeniedException, EXistException, XPathException
511512
}
512513
)
513514
|
515+
#(
516+
DECIMAL_FORMAT_DECL
517+
{
518+
final XQueryAST root = (XQueryAST) _t; // points to DECIMAL_FORMAT_DECL
519+
// first sibling is either DEFAULT_DECIMAL_FORMAT (default) or EQNAME (named)
520+
final XQueryAST dfName = (XQueryAST) root.getNextSibling();
521+
522+
final QName qnDfName;
523+
if ("default".equals(dfName.getText())) {
524+
qnDfName = XQueryContext.UNNAMED_DECIMAL_FORMAT;
525+
if (hasDefaultDecimalFormat) {
526+
throw new XPathException(dfName.getLine(), dfName.getColumn(), ErrorCodes.W3CErrorCode.XQST0111.getErrorCode(), "Query prolog cannot contain two default decimal format declarations.");
527+
} else {
528+
hasDefaultDecimalFormat = true;
529+
}
530+
} else {
531+
try {
532+
qnDfName = QName.parse(staticContext, dfName.getText(), null);
533+
} catch (final IllegalQNameException iqe) {
534+
throw new XPathException(dfName.getLine(), dfName.getColumn(), ErrorCodes.XPST0081, "No namespace defined for prefix " + dfName.getText());
535+
}
536+
537+
if (staticContext.getStaticDecimalFormat(qnDfName) != null) {
538+
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());
539+
}
540+
}
541+
542+
// position current at the first property name for the decimal format
543+
XQueryAST current = (XQueryAST) dfName.getNextSibling();
544+
if ("default".equals(dfName.getText())) {
545+
current = (XQueryAST) current.getNextSibling();
546+
}
547+
548+
final Map<String, String> dfProperties = new HashMap<>();
549+
550+
while (current != null) {
551+
final XQueryAST pname = current;
552+
final XQueryAST pval = (XQueryAST) current.getNextSibling();
553+
554+
if (pval == null) {
555+
break;
556+
}
557+
558+
final String pn = pname.getText();
559+
String pv = pval.getText();
560+
if (pv.length() >= 2 && (pv.startsWith("\"") || pv.startsWith("'"))) {
561+
pv = pv.substring(1, pv.length() - 1);
562+
}
563+
if (dfProperties.put(pn, pv) != null) {
564+
throw new XPathException(dfName.getLine(), dfName.getColumn(), ErrorCodes.W3CErrorCode.XQST0114.getErrorCode(), "Decimal format: " + dfName.getText() + " defines the property: " + pn + " more than once.");
565+
}
566+
567+
current = (XQueryAST) pval.getNextSibling();
568+
}
569+
570+
final DecimalFormat df;
571+
try {
572+
df = DecimalFormat.fromProperties(dfProperties);
573+
} catch (final IllegalArgumentException ex) {
574+
throw new XPathException(dfName.getLine(), dfName.getColumn(), ErrorCodes.W3CErrorCode.XQST0097.getErrorCode(), ex.getMessage() + " within the picture string of the decimal format: " + dfName.getText() + ".");
575+
}
576+
if (!df.checkDistinctCharacters()) {
577+
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.");
578+
}
579+
580+
staticContext.setStaticDecimalFormat(qnDfName, df);
581+
context.setStaticDecimalFormat(qnDfName, df);
582+
}
583+
)
584+
|
514585
#(
515586
qname:GLOBAL_VAR
516587
{

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)