Skip to content

Commit eec9ee5

Browse files
joewizclaude
andcommitted
[feature] Replace eXist-db native file module with EXPath File Module 4.0
Replace the custom file module (http://exist-db.org/xquery/file) with a spec-compliant EXPath File Module 4.0 (http://expath.org/ns/file) implementation, improving interoperability with other XQuery processors. The new module implements all 35+ functions from the EXPath File Module 4.0 specification including file properties, I/O, manipulation, path utilities, and system properties. The old module's eXist-specific file:sync function is relocated to util:file-sync. Includes 64 XQuery integration tests covering all major functions and error conditions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d5264a1 commit eec9ee5

File tree

56 files changed

+2646
-5450
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+2646
-5450
lines changed

extensions/modules/file/src/main/java/org/exist/xquery/modules/file/Sync.java renamed to exist-core/src/main/java/org/exist/xquery/functions/util/FileSync.java

Lines changed: 51 additions & 61 deletions
Large diffs are not rendered by default.

exist-core/src/main/java/org/exist/xquery/functions/util/UtilModule.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ public class UtilModule extends AbstractInternalModule {
152152
new FunctionDef(Base64Functions.signatures[3], Base64Functions.class),
153153
new FunctionDef(BaseConversionFunctions.FNS_INT_TO_OCTAL, BaseConversionFunctions.class),
154154
new FunctionDef(BaseConversionFunctions.FNS_OCTAL_TO_INT, BaseConversionFunctions.class),
155-
new FunctionDef(LineNumber.signature, LineNumber.class)
155+
new FunctionDef(LineNumber.signature, LineNumber.class),
156+
new FunctionDef(FileSync.signature, FileSync.class)
156157
};
157158

158159
static {

extensions/modules/file/src/main/resources/org/exist/xquery/modules/file/repo.xsl renamed to exist-core/src/main/resources/org/exist/xquery/functions/util/repo.xsl

File renamed without changes.

exist-core/src/test/resources/org/exist/xmldb/allowAnyUri.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1022,7 +1022,7 @@
10221022
<module uri="http://exist-db.org/xquery/cqlparser" class="org.exist.xquery.modules.cqlparser.CQLParserModule"/>
10231023
<!-- module uri="http://exist-db.org/xquery/exi" class="org.exist.xquery.modules.exi.ExiModule"/ -->
10241024
<module uri="http://exist-db.org/xquery/repo" class="org.exist.xquery.modules.expathrepo.ExpathPackageModule"/>
1025-
<module uri="http://exist-db.org/xquery/file" class="org.exist.xquery.modules.file.FileModule"/>
1025+
<module uri="http://expath.org/ns/file" class="org.expath.exist.file.ExpathFileModule"/>
10261026
<module uri="http://exist-db.org/xquery/image" class="org.exist.xquery.modules.image.ImageModule"/>
10271027
<module uri="http://exist-db.org/xquery/jndi" class="org.exist.xquery.modules.jndi.JNDIModule"/>
10281028
<module uri="http://exist-db.org/xquery/mail" class="org.exist.xquery.modules.mail.MailModule"/>

exist-distribution/pom.xml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -197,12 +197,6 @@
197197
<version>${project.version}</version>
198198
<scope>runtime</scope>
199199
</dependency>
200-
<dependency>
201-
<groupId>${project.groupId}</groupId>
202-
<artifactId>exist-file</artifactId>
203-
<version>${project.version}</version>
204-
<scope>runtime</scope>
205-
</dependency>
206200
<dependency>
207201
<groupId>${project.groupId}</groupId>
208202
<artifactId>exist-image</artifactId>

exist-distribution/src/main/config/conf.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1048,7 +1048,7 @@
10481048
<module uri="http://exist-db.org/xquery/cqlparser" class="org.exist.xquery.modules.cqlparser.CQLParserModule"/>
10491049
<!-- module uri="http://exist-db.org/xquery/exi" class="org.exist.xquery.modules.exi.ExiModule"/ -->
10501050
<module uri="http://exist-db.org/xquery/repo" class="org.exist.xquery.modules.expathrepo.ExpathPackageModule"/>
1051-
<module uri="http://exist-db.org/xquery/file" class="org.exist.xquery.modules.file.FileModule"/>
1051+
<module uri="http://expath.org/ns/file" class="org.expath.exist.file.ExpathFileModule"/>
10521052
<module uri="http://exist-db.org/xquery/image" class="org.exist.xquery.modules.image.ImageModule"/>
10531053
<module uri="http://exist-db.org/xquery/jndi" class="org.exist.xquery.modules.jndi.JNDIModule"/>
10541054
<module uri="http://exist-db.org/xquery/mail" class="org.exist.xquery.modules.mail.MailModule"/>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* eXist-db Open Source Native XML Database
3+
* Copyright (C) 2001 The eXist-db Authors
4+
*
5+
* info@exist-db.org
6+
* http://www.exist-db.org
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; either
11+
* version 2.1 of the License, or (at your option) any later version.
12+
*
13+
* This library is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16+
* Lesser General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Lesser General Public
19+
* License along with this library; if not, write to the Free Software
20+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21+
*/
22+
package org.expath.exist.file;
23+
24+
import org.exist.dom.QName;
25+
import org.exist.xquery.ErrorCodes.ErrorCode;
26+
27+
/**
28+
* Error codes defined by the EXPath File Module 4.0.
29+
*
30+
* @see <a href="https://qt4cg.org/specifications/expath-file-40/Overview.html">EXPath File Module 4.0</a>
31+
*/
32+
public class ExpathFileErrorCode {
33+
34+
public static final ErrorCode NOT_FOUND = new ErrorCode(
35+
new QName("not-found", ExpathFileModule.NAMESPACE_URI, ExpathFileModule.PREFIX),
36+
"The specified path does not exist.");
37+
38+
public static final ErrorCode INVALID_PATH = new ErrorCode(
39+
new QName("invalid-path", ExpathFileModule.NAMESPACE_URI, ExpathFileModule.PREFIX),
40+
"The specified path is invalid.");
41+
42+
public static final ErrorCode EXISTS = new ErrorCode(
43+
new QName("exists", ExpathFileModule.NAMESPACE_URI, ExpathFileModule.PREFIX),
44+
"The specified path already exists.");
45+
46+
public static final ErrorCode NO_DIR = new ErrorCode(
47+
new QName("no-dir", ExpathFileModule.NAMESPACE_URI, ExpathFileModule.PREFIX),
48+
"The specified path does not point to a directory.");
49+
50+
public static final ErrorCode IS_DIR = new ErrorCode(
51+
new QName("is-dir", ExpathFileModule.NAMESPACE_URI, ExpathFileModule.PREFIX),
52+
"The specified path points to a directory.");
53+
54+
public static final ErrorCode IS_RELATIVE = new ErrorCode(
55+
new QName("is-relative", ExpathFileModule.NAMESPACE_URI, ExpathFileModule.PREFIX),
56+
"The specified path is relative.");
57+
58+
public static final ErrorCode UNKNOWN_ENCODING = new ErrorCode(
59+
new QName("unknown-encoding", ExpathFileModule.NAMESPACE_URI, ExpathFileModule.PREFIX),
60+
"The specified encoding is not supported.");
61+
62+
public static final ErrorCode OUT_OF_RANGE = new ErrorCode(
63+
new QName("out-of-range", ExpathFileModule.NAMESPACE_URI, ExpathFileModule.PREFIX),
64+
"The specified offset or length is out of range.");
65+
66+
public static final ErrorCode IO_ERROR = new ErrorCode(
67+
new QName("io-error", ExpathFileModule.NAMESPACE_URI, ExpathFileModule.PREFIX),
68+
"A generic file system error occurred.");
69+
70+
private ExpathFileErrorCode() {
71+
// no instances
72+
}
73+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* eXist-db Open Source Native XML Database
3+
* Copyright (C) 2001 The eXist-db Authors
4+
*
5+
* info@exist-db.org
6+
* http://www.exist-db.org
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; either
11+
* version 2.1 of the License, or (at your option) any later version.
12+
*
13+
* This library is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16+
* Lesser General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Lesser General Public
19+
* License along with this library; if not, write to the Free Software
20+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21+
*/
22+
package org.expath.exist.file;
23+
24+
import java.util.List;
25+
import java.util.Map;
26+
27+
import org.exist.xquery.AbstractInternalModule;
28+
import org.exist.xquery.FunctionDef;
29+
30+
/**
31+
* EXPath File Module 4.0 implementation for eXist-db.
32+
*
33+
* @see <a href="https://qt4cg.org/specifications/expath-file-40/Overview.html">EXPath File Module 4.0</a>
34+
*/
35+
public class ExpathFileModule extends AbstractInternalModule {
36+
37+
public static final String NAMESPACE_URI = "http://expath.org/ns/file";
38+
public static final String PREFIX = "file";
39+
public static final String INCLUSION_DATE = "2025-05-01";
40+
public static final String RELEASED_IN_VERSION = "7.0.0";
41+
42+
private static final FunctionDef[] functions = {
43+
// FileProperties: exists, is-dir, is-file, is-absolute, last-modified, size(1), size(2)
44+
new FunctionDef(FileProperties.signatures[0], FileProperties.class),
45+
new FunctionDef(FileProperties.signatures[1], FileProperties.class),
46+
new FunctionDef(FileProperties.signatures[2], FileProperties.class),
47+
new FunctionDef(FileProperties.signatures[3], FileProperties.class),
48+
new FunctionDef(FileProperties.signatures[4], FileProperties.class),
49+
new FunctionDef(FileProperties.signatures[5], FileProperties.class),
50+
new FunctionDef(FileProperties.signatures[6], FileProperties.class),
51+
52+
// FileIO: read-text(1), read-text(2), read-text-lines(1), read-text-lines(2),
53+
// read-binary(1), read-binary(2), read-binary(3)
54+
new FunctionDef(FileIO.signatures[0], FileIO.class),
55+
new FunctionDef(FileIO.signatures[1], FileIO.class),
56+
new FunctionDef(FileIO.signatures[2], FileIO.class),
57+
new FunctionDef(FileIO.signatures[3], FileIO.class),
58+
new FunctionDef(FileIO.signatures[4], FileIO.class),
59+
new FunctionDef(FileIO.signatures[5], FileIO.class),
60+
new FunctionDef(FileIO.signatures[6], FileIO.class),
61+
62+
// FileWrite: write(2), write(3), write-text(2), write-text(3),
63+
// write-text-lines(2), write-text-lines(3), write-binary(2), write-binary(3)
64+
new FunctionDef(FileWrite.signatures[0], FileWrite.class),
65+
new FunctionDef(FileWrite.signatures[1], FileWrite.class),
66+
new FunctionDef(FileWrite.signatures[2], FileWrite.class),
67+
new FunctionDef(FileWrite.signatures[3], FileWrite.class),
68+
new FunctionDef(FileWrite.signatures[4], FileWrite.class),
69+
new FunctionDef(FileWrite.signatures[5], FileWrite.class),
70+
new FunctionDef(FileWrite.signatures[6], FileWrite.class),
71+
new FunctionDef(FileWrite.signatures[7], FileWrite.class),
72+
73+
// FileAppend: append(2), append(3), append-binary, append-text(2), append-text(3),
74+
// append-text-lines(2), append-text-lines(3)
75+
new FunctionDef(FileAppend.signatures[0], FileAppend.class),
76+
new FunctionDef(FileAppend.signatures[1], FileAppend.class),
77+
new FunctionDef(FileAppend.signatures[2], FileAppend.class),
78+
new FunctionDef(FileAppend.signatures[3], FileAppend.class),
79+
new FunctionDef(FileAppend.signatures[4], FileAppend.class),
80+
new FunctionDef(FileAppend.signatures[5], FileAppend.class),
81+
new FunctionDef(FileAppend.signatures[6], FileAppend.class),
82+
83+
// FileManipulation: copy, move, delete(1), delete(2), create-dir,
84+
// create-temp-dir, create-temp-file, list(1), list(2), list(3),
85+
// children, descendants, list-roots
86+
new FunctionDef(FileManipulation.signatures[0], FileManipulation.class),
87+
new FunctionDef(FileManipulation.signatures[1], FileManipulation.class),
88+
new FunctionDef(FileManipulation.signatures[2], FileManipulation.class),
89+
new FunctionDef(FileManipulation.signatures[3], FileManipulation.class),
90+
new FunctionDef(FileManipulation.signatures[4], FileManipulation.class),
91+
new FunctionDef(FileManipulation.signatures[5], FileManipulation.class),
92+
new FunctionDef(FileManipulation.signatures[6], FileManipulation.class),
93+
new FunctionDef(FileManipulation.signatures[7], FileManipulation.class),
94+
new FunctionDef(FileManipulation.signatures[8], FileManipulation.class),
95+
new FunctionDef(FileManipulation.signatures[9], FileManipulation.class),
96+
new FunctionDef(FileManipulation.signatures[10], FileManipulation.class),
97+
new FunctionDef(FileManipulation.signatures[11], FileManipulation.class),
98+
new FunctionDef(FileManipulation.signatures[12], FileManipulation.class),
99+
100+
// FilePaths: name, parent, path-to-native, path-to-uri, resolve-path(1), resolve-path(2)
101+
new FunctionDef(FilePaths.signatures[0], FilePaths.class),
102+
new FunctionDef(FilePaths.signatures[1], FilePaths.class),
103+
new FunctionDef(FilePaths.signatures[2], FilePaths.class),
104+
new FunctionDef(FilePaths.signatures[3], FilePaths.class),
105+
new FunctionDef(FilePaths.signatures[4], FilePaths.class),
106+
new FunctionDef(FilePaths.signatures[5], FilePaths.class),
107+
108+
// FileSystemProperties: dir-separator, line-separator, path-separator,
109+
// temp-dir, base-dir, current-dir
110+
new FunctionDef(FileSystemProperties.signatures[0], FileSystemProperties.class),
111+
new FunctionDef(FileSystemProperties.signatures[1], FileSystemProperties.class),
112+
new FunctionDef(FileSystemProperties.signatures[2], FileSystemProperties.class),
113+
new FunctionDef(FileSystemProperties.signatures[3], FileSystemProperties.class),
114+
new FunctionDef(FileSystemProperties.signatures[4], FileSystemProperties.class),
115+
new FunctionDef(FileSystemProperties.signatures[5], FileSystemProperties.class)
116+
};
117+
118+
public ExpathFileModule(final Map<String, List<? extends Object>> parameters) {
119+
super(functions, parameters);
120+
}
121+
122+
@Override
123+
public String getNamespaceURI() {
124+
return NAMESPACE_URI;
125+
}
126+
127+
@Override
128+
public String getDefaultPrefix() {
129+
return PREFIX;
130+
}
131+
132+
@Override
133+
public String getDescription() {
134+
return "EXPath File Module 4.0 - http://expath.org/ns/file";
135+
}
136+
137+
@Override
138+
public String getReleaseVersion() {
139+
return RELEASED_IN_VERSION;
140+
}
141+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* eXist-db Open Source Native XML Database
3+
* Copyright (C) 2001 The eXist-db Authors
4+
*
5+
* info@exist-db.org
6+
* http://www.exist-db.org
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; either
11+
* version 2.1 of the License, or (at your option) any later version.
12+
*
13+
* This library is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16+
* Lesser General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Lesser General Public
19+
* License along with this library; if not, write to the Free Software
20+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21+
*/
22+
package org.expath.exist.file;
23+
24+
import java.net.URI;
25+
import java.nio.charset.Charset;
26+
import java.nio.charset.UnsupportedCharsetException;
27+
import java.nio.file.InvalidPathException;
28+
import java.nio.file.Path;
29+
import java.nio.file.Paths;
30+
31+
import org.exist.xquery.Expression;
32+
import org.exist.xquery.XPathException;
33+
import org.exist.xquery.XQueryContext;
34+
35+
/**
36+
* Helper utilities for the EXPath File Module.
37+
*/
38+
public class ExpathFileModuleHelper {
39+
40+
private ExpathFileModuleHelper() {
41+
// no instances
42+
}
43+
44+
/**
45+
* Check that the calling user has DBA role.
46+
*
47+
* @param context the XQuery context
48+
* @param expression the calling expression (for error reporting)
49+
* @throws XPathException if the user is not a DBA
50+
*/
51+
public static void checkDbaRole(final XQueryContext context, final Expression expression) throws XPathException {
52+
if (!context.getSubject().hasDbaRole()) {
53+
throw new XPathException(expression,
54+
"Permission denied, calling user '" + context.getSubject().getName() +
55+
"' must be a DBA to call this function.");
56+
}
57+
}
58+
59+
/**
60+
* Resolve a path string (file: URI or native path) to a {@link Path}.
61+
*
62+
* @param path the path string or file: URI
63+
* @param expression the calling expression (for error reporting)
64+
* @return the resolved Path
65+
* @throws XPathException if the path is invalid
66+
*/
67+
public static Path getPath(final String path, final Expression expression) throws XPathException {
68+
try {
69+
if (path.startsWith("file:")) {
70+
return Paths.get(new URI(path));
71+
} else {
72+
return Paths.get(path);
73+
}
74+
} catch (final InvalidPathException e) {
75+
throw new XPathException(expression, ExpathFileErrorCode.INVALID_PATH,
76+
"Invalid path: " + path + " - " + e.getMessage());
77+
} catch (final Exception e) {
78+
throw new XPathException(expression, ExpathFileErrorCode.INVALID_PATH,
79+
path + " is not a valid path or URI: " + e.getMessage());
80+
}
81+
}
82+
83+
/**
84+
* Validate and return a {@link Charset} for the given encoding name.
85+
*
86+
* @param encoding the encoding name
87+
* @param expression the calling expression (for error reporting)
88+
* @return the Charset
89+
* @throws XPathException if the encoding is not supported
90+
*/
91+
public static Charset getCharset(final String encoding, final Expression expression) throws XPathException {
92+
try {
93+
return Charset.forName(encoding);
94+
} catch (final UnsupportedCharsetException e) {
95+
throw new XPathException(expression, ExpathFileErrorCode.UNKNOWN_ENCODING,
96+
"Unknown encoding: " + encoding);
97+
}
98+
}
99+
}

0 commit comments

Comments
 (0)