Skip to content

Commit 17a7067

Browse files
joewizclaude
andcommitted
[bugfix] Require ! flag for empty-matching regex in XQ4 fn:replace and fn:tokenize
Without the ! flag, empty-matching patterns raise FORX0003 in both XQ 3.1 and XQ 4.0 mode. With the ! flag in XQ 4.0, fn:replace uses the Java regex fallback and fn:tokenize tokenizes between each character. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 779c405 commit 17a7067

2 files changed

Lines changed: 10 additions & 9 deletions

File tree

exist-core/src/main/java/org/exist/xquery/functions/fn/FunReplace.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,18 +151,19 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro
151151
try {
152152
final RegularExpression regularExpression = config.compileRegularExpression(pattern, flags, "XP30", warnings);
153153
final boolean canMatchEmpty = regularExpression.matches("");
154+
final boolean allowEmptyMatch = flags.contains("!");
154155

155156
// XQ 3.1: FORX0003 if regex can match empty string
156-
// XQ 4.0: empty-matching regex is allowed
157-
if (canMatchEmpty && context.getXQueryVersion() < 40 && !isFunctionReplacement) {
157+
// XQ 4.0: empty-matching regex allowed only with the ! flag
158+
if (canMatchEmpty && (context.getXQueryVersion() < 40 || !allowEmptyMatch) && !isFunctionReplacement) {
158159
throw new XPathException(this, ErrorCodes.FORX0003, "regular expression could match empty string");
159160
}
160161

161162
if (isFunctionReplacement) {
162163
result = evalFunctionReplacement(string, pattern, flags,
163164
(FunctionReference) replacementArg.itemAt(0));
164-
} else if (canMatchEmpty) {
165-
// XQ4: empty-matching regex allowed — use Java regex fallback
165+
} else if (canMatchEmpty && allowEmptyMatch) {
166+
// XQ4: empty-matching regex allowed with ! flag — use Java regex fallback
166167
// since Saxon's replace() doesn't handle empty matches well
167168
result = evalEmptyMatchReplace(string, pattern, replace, flags);
168169
} else {

exist-core/src/main/java/org/exist/xquery/functions/fn/FunTokenize.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,13 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro
128128
final Pattern pat = PatternFactory.getInstance().getPattern(pattern, flags);
129129

130130
if (pat.matcher("").matches()) {
131-
// XQ 3.1: FORX0003 if regex can match empty string
132-
// XQ 4.0: empty-matching regex is allowed
133-
if (context.getXQueryVersion() < 40) {
131+
// XQ4 with ! flag: empty-matching allowed — tokenize between each character
132+
// XQ 3.1 or XQ4 without ! flag: FORX0003
133+
if (context.getXQueryVersion() >= 40 && flagsStr.contains("!")) {
134+
result = tokenizeEmptyMatch(string, pat);
135+
} else {
134136
throw new XPathException(this, ErrorCodes.FORX0003, "regular expression could match empty string");
135137
}
136-
// XQ4: empty-matching regex allowed — tokenize between each character
137-
result = tokenizeEmptyMatch(string, pat);
138138
} else {
139139
final String[] tokens = pat.split(string, -1);
140140
result = new ValueSequence();

0 commit comments

Comments
 (0)