diff --git a/exist-core/pom.xml b/exist-core/pom.xml
index 94ed898b780..372b1ecb01a 100644
--- a/exist-core/pom.xml
+++ b/exist-core/pom.xml
@@ -390,19 +390,6 @@
Saxon-HE
-
- org.exist-db
- exist-saxon-regex
- 9.4.0-9.e1
-
-
-
- net.sf.saxon
- Saxon-HE
-
-
-
-
com.evolvedbinary.thirdparty.org.apache.xmlrpc
xmlrpc-common
diff --git a/exist-core/src/main/java/org/exist/dom/persistent/ElementImpl.java b/exist-core/src/main/java/org/exist/dom/persistent/ElementImpl.java
index c490a4dfaff..ec98af10fd1 100644
--- a/exist-core/src/main/java/org/exist/dom/persistent/ElementImpl.java
+++ b/exist-core/src/main/java/org/exist/dom/persistent/ElementImpl.java
@@ -822,6 +822,7 @@ public Attr getAttributeNodeNS(final String namespaceURI, final String localName
@Override
public NamedNodeMap getAttributes() {
final org.exist.dom.NamedNodeMapImpl map = new NamedNodeMapImpl(ownerDocument, true);
+
if(hasAttributes()) {
try(final DBBroker broker = ownerDocument.getBrokerPool().getBroker();
final INodeIterator iterator = broker.getNodeIterator(this)) {
@@ -837,6 +838,14 @@ public NamedNodeMap getAttributes() {
if(next.getNodeType() != Node.ATTRIBUTE_NODE) {
break;
}
+ // Skip namespace declarations for the XML namespace — the xml prefix
+ // is always implicitly bound and Saxon 12 rejects any explicit
+ // declaration involving http://www.w3.org/XML/1998/namespace
+ if (next.getNodeType() == Node.ATTRIBUTE_NODE
+ && Namespaces.XMLNS_NS.equals(next.getNamespaceURI())
+ && XMLConstants.XML_NS_URI.equals(next.getNodeValue())) {
+ continue;
+ }
map.setNamedItem(next);
}
} catch(final EXistException | IOException e) {
@@ -847,6 +856,13 @@ public NamedNodeMap getAttributes() {
for (final Map.Entry entry : namespaceMappings.entrySet()) {
final String prefix = entry.getKey();
final String ns = entry.getValue();
+ // Skip namespace declarations involving the XML namespace URI —
+ // Saxon 12 rejects any explicit declaration of the xml prefix
+ // or binding of the XML namespace to a non-xml prefix
+ if (XMLConstants.XML_NS_PREFIX.equals(prefix)
+ || XMLConstants.XML_NS_URI.equals(ns)) {
+ continue;
+ }
final QName attrName = new QName(prefix, Namespaces.XMLNS_NS, XMLConstants.XMLNS_ATTRIBUTE);
final AttrImpl attr = new AttrImpl(getExpression(), attrName, ns, null);
attr.setOwnerDocument(ownerDocument);
diff --git a/exist-core/src/main/java/org/exist/http/urlrewrite/RewriteConfig.java b/exist-core/src/main/java/org/exist/http/urlrewrite/RewriteConfig.java
index 6303fd67940..ace13810fd3 100644
--- a/exist-core/src/main/java/org/exist/http/urlrewrite/RewriteConfig.java
+++ b/exist-core/src/main/java/org/exist/http/urlrewrite/RewriteConfig.java
@@ -31,9 +31,8 @@
import org.exist.security.PermissionDeniedException;
import org.exist.storage.DBBroker;
import org.exist.storage.lock.Lock.LockMode;
-import org.exist.thirdparty.net.sf.saxon.functions.regex.JDK15RegexTranslator;
-import org.exist.thirdparty.net.sf.saxon.functions.regex.RegexSyntaxException;
-import org.exist.thirdparty.net.sf.saxon.functions.regex.RegularExpression;
+import net.sf.saxon.regex.JavaRegularExpression;
+import net.sf.saxon.str.StringView;
import org.exist.util.XMLReaderPool;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.Constants;
@@ -272,16 +271,13 @@ private static final class Mapping {
private Mapping(String regex, final URLRewrite action) throws ServletException {
try {
- final int options = RegularExpression.XML11 | RegularExpression.XPATH30;
- int flagbits = 0;
-
- final List warnings = new ArrayList<>();
- regex = JDK15RegexTranslator.translate(regex, options, flagbits, warnings);
+ final JavaRegularExpression javaRegex = new JavaRegularExpression(StringView.of(regex), "");
+ regex = javaRegex.getJavaRegularExpression();
this.pattern = Pattern.compile(regex, 0);
this.action = action;
this.matcher = pattern.matcher("");
- } catch (final RegexSyntaxException e) {
+ } catch (final net.sf.saxon.trans.XPathException e) {
throw new ServletException("Syntax error in regular expression specified for path. " +
e.getMessage(), e);
}
diff --git a/exist-core/src/main/java/org/exist/storage/serializers/Serializer.java b/exist-core/src/main/java/org/exist/storage/serializers/Serializer.java
index e2f2166443b..27b5aaec44e 100644
--- a/exist-core/src/main/java/org/exist/storage/serializers/Serializer.java
+++ b/exist-core/src/main/java/org/exist/storage/serializers/Serializer.java
@@ -825,7 +825,8 @@ public void setStylesheet(final Document doc, final @Nullable String stylesheet)
// restore handlers
receiver = oldReceiver;
- factory.get().setURIResolver(null);
+ // Saxon 12 rejects null URIResolver; reset to default identity resolver
+ factory.get().setURIResolver((href, base) -> null);
}
LOG.debug("compiling stylesheet took {}", System.currentTimeMillis() - start);
if (templates != null) {
diff --git a/exist-core/src/main/java/org/exist/util/XMLBackwardsCompatHandler.java b/exist-core/src/main/java/org/exist/util/XMLBackwardsCompatHandler.java
new file mode 100644
index 00000000000..47e364d09cb
--- /dev/null
+++ b/exist-core/src/main/java/org/exist/util/XMLBackwardsCompatHandler.java
@@ -0,0 +1,106 @@
+/*
+ * eXist-db Open Source Native XML Database
+ * Copyright (C) 2001 The eXist-db Authors
+ *
+ * info@exist-db.org
+ * http://www.exist-db.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.exist.util;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * A SAX ContentHandler wrapper that suppresses duplicate startDocument/endDocument calls.
+ * Saxon 12's LinkedTreeBuilder does not tolerate receiving startDocument more than once,
+ * which can happen when eXist's Serializer sends document events that overlap with
+ * explicitly-called startDocument/endDocument in the XSLT compilation pipeline.
+ */
+public class XMLBackwardsCompatHandler implements ContentHandler {
+
+ private final ContentHandler delegate;
+ private boolean documentStarted = false;
+
+ public XMLBackwardsCompatHandler(final ContentHandler delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void startDocument() throws SAXException {
+ if (!documentStarted) {
+ documentStarted = true;
+ delegate.startDocument();
+ }
+ }
+
+ @Override
+ public void endDocument() throws SAXException {
+ // Suppress — the caller will call endDocument on the delegate directly
+ }
+
+ @Override
+ public void setDocumentLocator(final Locator locator) {
+ delegate.setDocumentLocator(locator);
+ }
+
+ @Override
+ public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
+ // Saxon 12 rejects any namespace declaration involving the XML namespace URI
+ // (http://www.w3.org/XML/1998/namespace) — the xml prefix is always implicitly bound
+ if ("xml".equals(prefix) || javax.xml.XMLConstants.XML_NS_URI.equals(uri)) {
+ return;
+ }
+ delegate.startPrefixMapping(prefix, uri);
+ }
+
+ @Override
+ public void endPrefixMapping(final String prefix) throws SAXException {
+ delegate.endPrefixMapping(prefix);
+ }
+
+ @Override
+ public void startElement(final String uri, final String localName, final String qName, final Attributes atts) throws SAXException {
+ delegate.startElement(uri, localName, qName, atts);
+ }
+
+ @Override
+ public void endElement(final String uri, final String localName, final String qName) throws SAXException {
+ delegate.endElement(uri, localName, qName);
+ }
+
+ @Override
+ public void characters(final char[] ch, final int start, final int length) throws SAXException {
+ delegate.characters(ch, start, length);
+ }
+
+ @Override
+ public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
+ delegate.ignorableWhitespace(ch, start, length);
+ }
+
+ @Override
+ public void processingInstruction(final String target, final String data) throws SAXException {
+ delegate.processingInstruction(target, data);
+ }
+
+ @Override
+ public void skippedEntity(final String name) throws SAXException {
+ delegate.skippedEntity(name);
+ }
+}
diff --git a/exist-core/src/main/java/org/exist/validation/XmlLibraryChecker.java b/exist-core/src/main/java/org/exist/validation/XmlLibraryChecker.java
index 5b9a570f3f3..ad240345548 100644
--- a/exist-core/src/main/java/org/exist/validation/XmlLibraryChecker.java
+++ b/exist-core/src/main/java/org/exist/validation/XmlLibraryChecker.java
@@ -54,7 +54,7 @@ public class XmlLibraryChecker {
* Possible XML Transformers, at least one must be valid
*/
private final static ClassVersion[] validTransformers = {
- new ClassVersion("Saxon", "8.9.0", "net.sf.saxon.Version.getProductVersion()"),
+ new ClassVersion("Saxon", "12.0", "net.sf.saxon.Version.getProductVersion()"),
new ClassVersion("Xalan", "Xalan Java 2.7.1", "org.apache.xalan.Version.getVersion()"),
};
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunAnalyzeString.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunAnalyzeString.java
index 8fe035492a7..c6cce6bf13d 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunAnalyzeString.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunAnalyzeString.java
@@ -25,9 +25,11 @@
import java.util.List;
import net.sf.saxon.Configuration;
-import net.sf.saxon.om.Item;
import net.sf.saxon.regex.RegexIterator;
+import net.sf.saxon.regex.RegexMatchHandler;
import net.sf.saxon.regex.RegularExpression;
+import net.sf.saxon.str.StringView;
+import net.sf.saxon.str.UnicodeString;
import org.exist.dom.QName;
import org.exist.dom.memtree.MemTreeBuilder;
import org.exist.xquery.*;
@@ -126,15 +128,15 @@ private void analyzeString(final MemTreeBuilder builder, final String input, Str
final List warnings = new ArrayList<>(1);
try {
- final RegularExpression regularExpression = config.compileRegularExpression(pattern, flags, "XP30", warnings);
- if (regularExpression.matches("")) {
+ final RegularExpression regularExpression = config.compileRegularExpression(StringView.of(pattern), flags, "XP30", warnings);
+ if (regularExpression.matches(StringView.of(""))) {
throw new XPathException(this, ErrorCodes.FORX0003, "regular expression could match empty string");
}
//TODO(AR) cache the regular expression... might be possible through Saxon config
- final RegexIterator regexIterator = regularExpression.analyze(input);
- Item item;
+ final RegexIterator regexIterator = regularExpression.analyze(StringView.of(input));
+ net.sf.saxon.value.StringValue item;
while ((item = regexIterator.next()) != null) {
if (regexIterator.isMatching()) {
match(builder, regexIterator);
@@ -147,7 +149,7 @@ private void analyzeString(final MemTreeBuilder builder, final String input, Str
LOG.warn(warning);
}
} catch (final net.sf.saxon.trans.XPathException e) {
- switch (e.getErrorCodeLocalPart()) {
+ switch (e.getErrorCodeQName().getLocalPart()) {
case "FORX0001" -> throw new XPathException(this, ErrorCodes.FORX0001, e.getMessage());
case "FORX0002" -> throw new XPathException(this, ErrorCodes.FORX0002, e.getMessage());
case "FORX0003" -> throw new XPathException(this, ErrorCodes.FORX0003, e.getMessage());
@@ -158,10 +160,10 @@ private void analyzeString(final MemTreeBuilder builder, final String input, Str
private void match(final MemTreeBuilder builder, final RegexIterator regexIterator) throws net.sf.saxon.trans.XPathException {
builder.startElement(QN_MATCH, null);
- regexIterator.processMatchingSubstring(new RegexIterator.MatchHandler() {
+ regexIterator.processMatchingSubstring(new RegexMatchHandler() {
@Override
- public void characters(final CharSequence s) {
- builder.characters(s);
+ public void characters(final UnicodeString s) {
+ builder.characters(s.toString());
}
@Override
@@ -180,9 +182,9 @@ public void onGroupEnd(final int groupNumber) throws net.sf.saxon.trans.XPathExc
builder.endElement();
}
- private void nonMatch(final MemTreeBuilder builder, final Item item) {
+ private void nonMatch(final MemTreeBuilder builder, final net.sf.saxon.value.StringValue item) {
builder.startElement(QN_NON_MATCH, null);
- builder.characters(item.getStringValueCS());
+ builder.characters(item.getStringValue());
builder.endElement();
}
}
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunMatches.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunMatches.java
index 6f06bd772ce..962fd628458 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunMatches.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunMatches.java
@@ -47,6 +47,7 @@
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import net.sf.saxon.regex.RegularExpression;
+import net.sf.saxon.str.StringView;
import static org.exist.xquery.FunctionDSL.*;
import static org.exist.xquery.functions.fn.FnModule.functionSignatures;
@@ -517,16 +518,16 @@ private boolean matchXmlRegex(final String string, final String pattern, final S
List warnings = new ArrayList<>(1);
RegularExpression regex = context.getBroker().getBrokerPool()
.getSaxonConfiguration()
- .compileRegularExpression(pattern, flags, "XP30", warnings);
+ .compileRegularExpression(StringView.of(pattern), flags, "XP30", warnings);
for (final String warning : warnings) {
LOG.warn(warning);
}
- return regex.containsMatch(string);
+ return regex.containsMatch(StringView.of(string));
} catch (final net.sf.saxon.trans.XPathException e) {
- switch (e.getErrorCodeLocalPart()) {
+ switch (e.getErrorCodeQName().getLocalPart()) {
case "FORX0001" -> throw new XPathException(this, ErrorCodes.FORX0001, "Invalid regular expression: " + e.getMessage());
case "FORX0002" -> throw new XPathException(this, ErrorCodes.FORX0002, "Invalid regular expression: " + e.getMessage());
// no FORX0003 here since fn:matches is allowed to match an empty string
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunReplace.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunReplace.java
index 6dea523469a..6bd745b3e65 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunReplace.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunReplace.java
@@ -27,6 +27,7 @@
import net.sf.saxon.Configuration;
import net.sf.saxon.functions.Replace;
import net.sf.saxon.regex.RegularExpression;
+import net.sf.saxon.str.StringView;
import org.exist.dom.QName;
import org.exist.xquery.*;
import org.exist.xquery.value.FunctionParameterSequenceType;
@@ -119,24 +120,24 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro
final List warnings = new ArrayList<>(1);
try {
- final RegularExpression regularExpression = config.compileRegularExpression(pattern, flags, "XP30", warnings);
- if (regularExpression.matches("")) {
+ final RegularExpression regularExpression = config.compileRegularExpression(StringView.of(pattern), flags, "XP30", warnings);
+ if (regularExpression.matches(StringView.of(""))) {
throw new XPathException(this, ErrorCodes.FORX0003, "regular expression could match empty string");
}
//TODO(AR) cache the regular expression... might be possible through Saxon config
if (!hasLiteral(flags)) {
- final String msg = Replace.checkReplacement(replace);
+ final String msg = Replace.checkReplacement(StringView.of(replace));
if (msg != null) {
throw new XPathException(this, ErrorCodes.FORX0004, msg);
}
}
- final CharSequence res = regularExpression.replace(string, replace);
+ final net.sf.saxon.str.UnicodeString res = regularExpression.replace(StringView.of(string), StringView.of(replace));
result = new StringValue(this, res.toString());
} catch (final net.sf.saxon.trans.XPathException e) {
- switch (e.getErrorCodeLocalPart()) {
+ switch (e.getErrorCodeQName().getLocalPart()) {
case "FORX0001" -> throw new XPathException(this, ErrorCodes.FORX0001, e.getMessage());
case "FORX0002" -> throw new XPathException(this, ErrorCodes.FORX0002, e.getMessage());
case "FORX0003" -> throw new XPathException(this, ErrorCodes.FORX0003, e.getMessage());
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Transform.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Transform.java
index fe9d58239f8..accad4f74d2 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Transform.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Transform.java
@@ -138,7 +138,13 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro
final Xslt30Transformer xslt30Transformer = xsltExecutable.load30();
- options.initialMode.ifPresent(qNameValue -> xslt30Transformer.setInitialMode(Convert.ToSaxon.of(qNameValue.getQName())));
+ if (options.initialMode.isPresent()) {
+ try {
+ xslt30Transformer.setInitialMode(Convert.ToSaxon.of(options.initialMode.get().getQName()));
+ } catch (final SaxonApiException e) {
+ throw new XPathException(fnTransform, ErrorCodes.FOXT0003, "Unable to set initial mode: " + e.getMessage(), e);
+ }
+ }
xslt30Transformer.setInitialTemplateParameters(options.templateParams, false);
xslt30Transformer.setInitialTemplateParameters(options.tunnelParams, true);
if (options.baseOutputURI.isPresent()) {
@@ -365,7 +371,7 @@ private MapType invokeApplyTemplates() throws XPathException, SaxonApiException
final Sequence initialMatchSelection = options.initialMatchSelection.get();
final Item item = initialMatchSelection.itemAt(0);
if (item instanceof Document) {
- final Source sourceIMS = new DOMSource((Document)item, context.getBaseURI().getStringValue());
+ final Source sourceIMS = new DOMSource((Document)item);
xslt30Transformer.applyTemplates(sourceIMS, destination);
} else {
final XdmValue selection = toSaxon.of(initialMatchSelection);
@@ -425,7 +431,10 @@ private Sequence postProcess(final AtomicValue key, final Sequence before, final
}
private static Optional getSourceNode(final Optional sourceNode, final AnyURIValue baseURI) {
- return sourceNode.map(NodeValue::getNode).map(node -> new DOMSource(node, baseURI.getStringValue()));
+ // Saxon 12 rejects duplicate document-URIs in the document pool.
+ // Don't set a system ID on the source DOMSource to avoid collisions
+ // with the stylesheet or other documents sharing the same base URI.
+ return sourceNode.map(NodeValue::getNode).map(node -> new DOMSource(node));
}
private static class ErrorListenerLog4jAdapter implements ErrorListener {
diff --git a/exist-core/src/main/java/org/exist/xquery/regex/RegexUtil.java b/exist-core/src/main/java/org/exist/xquery/regex/RegexUtil.java
index d54ca496c01..576fc60801a 100644
--- a/exist-core/src/main/java/org/exist/xquery/regex/RegexUtil.java
+++ b/exist-core/src/main/java/org/exist/xquery/regex/RegexUtil.java
@@ -21,17 +21,14 @@
*/
package org.exist.xquery.regex;
-import org.exist.thirdparty.net.sf.saxon.functions.regex.JDK15RegexTranslator;
-import org.exist.thirdparty.net.sf.saxon.functions.regex.RegexSyntaxException;
-import org.exist.thirdparty.net.sf.saxon.functions.regex.RegularExpression;
+import net.sf.saxon.regex.JavaRegularExpression;
+import net.sf.saxon.str.StringView;
import org.exist.xquery.ErrorCodes;
import org.exist.xquery.Expression;
import org.exist.xquery.XPathException;
import org.exist.xquery.value.StringValue;
import javax.annotation.Nullable;
-import java.util.ArrayList;
-import java.util.List;
import java.util.regex.Pattern;
/**
@@ -140,21 +137,19 @@ public static boolean hasIgnoreWhitespace(final int flags) {
* @throws XPathException if the XQuery Regular Expression is invalid.
*/
public static String translateRegexp(final Expression context, final String pattern, final boolean ignoreWhitespace, final boolean caseBlind) throws XPathException {
- // convert pattern to Java regex syntax
+ // convert pattern to Java regex syntax using Saxon's regex translator
try {
- final int options = RegularExpression.XML11 | RegularExpression.XPATH30;
-
- int flagbits = 0;
+ final StringBuilder flags = new StringBuilder();
if (ignoreWhitespace) {
- flagbits |= Pattern.COMMENTS;
+ flags.append('x');
}
if (caseBlind) {
- flagbits |= Pattern.CASE_INSENSITIVE;
+ flags.append('i');
}
- final List warnings = new ArrayList<>();
- return JDK15RegexTranslator.translate(pattern, options, flagbits, warnings);
- } catch (final RegexSyntaxException e) {
+ final JavaRegularExpression regex = new JavaRegularExpression(StringView.of(pattern), flags.toString());
+ return regex.getJavaRegularExpression();
+ } catch (final net.sf.saxon.trans.XPathException e) {
throw new XPathException(context, ErrorCodes.FORX0002, "Conversion from XPath F&O 3.0 regular expression syntax to Java regular expression syntax failed: " + e.getMessage(), new StringValue(pattern), e);
}
}
diff --git a/exist-core/src/main/java/org/exist/xquery/value/DayTimeDurationValue.java b/exist-core/src/main/java/org/exist/xquery/value/DayTimeDurationValue.java
index cf960f108f2..3a38935556e 100644
--- a/exist-core/src/main/java/org/exist/xquery/value/DayTimeDurationValue.java
+++ b/exist-core/src/main/java/org/exist/xquery/value/DayTimeDurationValue.java
@@ -21,8 +21,6 @@
*/
package org.exist.xquery.value;
-import net.sf.saxon.tree.util.FastStringBuffer;
-import net.sf.saxon.value.FloatingPointConverter;
import org.exist.xquery.ErrorCodes;
import org.exist.xquery.Expression;
import org.exist.xquery.XPathException;
@@ -118,7 +116,7 @@ public String getStringValue() {
}
//Copied from Saxon 8.6.1
- final FastStringBuffer sb = new FastStringBuffer(32);
+ final StringBuilder sb = new StringBuilder(32);
if (canonicalDuration.getSign() < 0) {
sb.append('-');
}
@@ -136,38 +134,10 @@ public String getStringValue() {
sb.append(m + "M");
}
if ((s.intValue() != 0) || (d == 0 && m == 0 && h == 0)) {
- //TODO : ugly -> factorize
- //sb.append(Integer.toString(s.intValue()));
- //double ms = s.doubleValue() - s.intValue();
- //if (ms != 0.0) {
- // sb.append(".");
- // sb.append(Double.toString(ms).substring(2));
- //}
- //0 is a dummy parameter
- FloatingPointConverter.appendFloat(sb, s.floatValue(), false);
+ sb.append(net.sf.saxon.value.FloatValue.floatToString(s.floatValue()));
sb.append("S");
- /*
- if (micros == 0) {
- sb.append(s + "S");
- } else {
- long ms = (s * 1000000) + micros;
- String mss = ms + "";
- if (s == 0) {
- mss = "0000000" + mss;
- mss = mss.substring(mss.length()-7);
- }
- sb.append(mss.substring(0, mss.length()-6));
- sb.append('.');
- int lastSigDigit = mss.length()-1;
- while (mss.charAt(lastSigDigit) == '0') {
- lastSigDigit--;
- }
- sb.append(mss.substring(mss.length()-6, lastSigDigit+1));
- sb.append('S');
- }
- */
}
- //End of copy
+ //End of copy
return sb.toString();
}
diff --git a/exist-core/src/main/java/org/exist/xquery/value/DoubleValue.java b/exist-core/src/main/java/org/exist/xquery/value/DoubleValue.java
index 3cd6cd24094..e526c48ec0b 100644
--- a/exist-core/src/main/java/org/exist/xquery/value/DoubleValue.java
+++ b/exist-core/src/main/java/org/exist/xquery/value/DoubleValue.java
@@ -22,8 +22,7 @@
package org.exist.xquery.value;
import com.ibm.icu.text.Collator;
-import net.sf.saxon.tree.util.FastStringBuffer;
-import net.sf.saxon.value.FloatingPointConverter;
+
import org.exist.util.ByteConversion;
import org.exist.xquery.Constants;
import org.exist.xquery.ErrorCodes;
@@ -93,10 +92,7 @@ public int getType() {
@Override
public String getStringValue() {
- final FastStringBuffer sb = new FastStringBuffer(20);
- //0 is a dummy parameter
- FloatingPointConverter.appendDouble(sb, value, false);
- return sb.toString();
+ return net.sf.saxon.value.DoubleValue.doubleToString(value).toString();
}
public double getValue() {
diff --git a/exist-core/src/main/java/org/exist/xquery/value/FloatValue.java b/exist-core/src/main/java/org/exist/xquery/value/FloatValue.java
index 6c67124e711..631287e43b3 100644
--- a/exist-core/src/main/java/org/exist/xquery/value/FloatValue.java
+++ b/exist-core/src/main/java/org/exist/xquery/value/FloatValue.java
@@ -22,8 +22,6 @@
package org.exist.xquery.value;
import com.ibm.icu.text.Collator;
-import net.sf.saxon.tree.util.FastStringBuffer;
-import net.sf.saxon.value.FloatingPointConverter;
import org.exist.util.ByteConversion;
import org.exist.xquery.Constants;
import org.exist.xquery.ErrorCodes;
@@ -115,10 +113,7 @@ public String getStringValue() throws XPathException {
return s;
*/
- final FastStringBuffer sb = new FastStringBuffer(20);
- //0 is a dummy parameter
- FloatingPointConverter.appendFloat(sb, value, false);
- return sb.toString();
+ return net.sf.saxon.value.FloatValue.floatToString(value).toString();
}
/* (non-Javadoc)
diff --git a/exist-core/src/main/java/org/exist/xslt/EXistDbXMLReader.java b/exist-core/src/main/java/org/exist/xslt/EXistDbXMLReader.java
index 70d6a880f91..9d1d98bb358 100644
--- a/exist-core/src/main/java/org/exist/xslt/EXistDbXMLReader.java
+++ b/exist-core/src/main/java/org/exist/xslt/EXistDbXMLReader.java
@@ -90,9 +90,13 @@ public void parse(final InputSource input) {
final Serializer serializer = source.getBroker().borrowSerializer();
try {
- this.source = input;
+ this.source = input;
this.contentHandler.setDocumentLocator(this);
- serializer.setSAXHandlers(this.contentHandler, null);
+ // Filter out the implicit xml namespace that eXist's persistent DOM
+ // stores in its namespace mappings. Saxon 12 rejects SAX events
+ // declaring the XML namespace URI (http://www.w3.org/XML/1998/namespace).
+ final ContentHandler filtered = new org.exist.util.XMLBackwardsCompatHandler(this.contentHandler);
+ serializer.setSAXHandlers(filtered, null);
serializer.toSAX(source.getDocument());
this.contentHandler.endDocument();
diff --git a/exist-core/src/main/java/org/exist/xslt/StylesheetResolverAndCompiler.java b/exist-core/src/main/java/org/exist/xslt/StylesheetResolverAndCompiler.java
index 4b90b81eac4..7bd5f0e82f3 100644
--- a/exist-core/src/main/java/org/exist/xslt/StylesheetResolverAndCompiler.java
+++ b/exist-core/src/main/java/org/exist/xslt/StylesheetResolverAndCompiler.java
@@ -39,6 +39,7 @@
import org.apache.logging.log4j.Logger;
import org.exist.dom.persistent.DocumentImpl;
import org.exist.dom.persistent.LockedDocument;
+import org.xml.sax.ContentHandler;
import org.exist.security.PermissionDeniedException;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
@@ -145,12 +146,24 @@ private Templates compileTemplates(
//factory.setURIResolver(new EXistURIResolver(broker, stylesheet.getCollection().getURI().toString()));
final TemplatesHandler handler = factory(broker.getBrokerPool(), errorListener).newTemplatesHandler();
- handler.setSystemId(stylesheet.getBaseURI());
+ // Use the full xmldb URI as the system ID so Saxon 12 can resolve
+ // relative xsl:import/xsl:include hrefs via the EXistURIResolver
+ final String baseURI = stylesheet.getBaseURI();
+ if (baseURI != null && !baseURI.contains(":")) {
+ handler.setSystemId(XmldbURI.EMBEDDED_SERVER_URI_PREFIX + baseURI);
+ } else {
+ handler.setSystemId(baseURI);
+ }
handler.startDocument();
+ // Wrap the handler to suppress duplicate startDocument/endDocument events.
+ // Serializer.toSAX() may send its own doc events (depending on GENERATE_DOC_EVENTS),
+ // and Saxon 12 does not tolerate duplicate calls.
+ final ContentHandler guard = new org.exist.util.XMLBackwardsCompatHandler(handler);
+
final Serializer serializer = broker.borrowSerializer();
try {
- serializer.setSAXHandlers(handler, null);
+ serializer.setSAXHandlers(guard, null);
serializer.toSAX(stylesheet);
} finally {
broker.returnSerializer(serializer);
diff --git a/exist-core/src/test/java/org/exist/dom/memtree/DocumentImplTest.java b/exist-core/src/test/java/org/exist/dom/memtree/DocumentImplTest.java
index c485b63a05c..f720e0ae1bd 100644
--- a/exist-core/src/test/java/org/exist/dom/memtree/DocumentImplTest.java
+++ b/exist-core/src/test/java/org/exist/dom/memtree/DocumentImplTest.java
@@ -94,33 +94,26 @@ public void checkNamespaces_saxon() throws IOException, ParserConfigurationExcep
final Element elem = doc.getDocumentElement();
final NamedNodeMap attrs = elem.getAttributes();
- assertEquals(3, attrs.getLength());
+ // Saxon 12 no longer includes the implicit xml namespace declaration
+ assertEquals(2, attrs.getLength());
int index = 0;
final Attr attr1 = (Attr)attrs.item(index++);
assertEquals(Node.ATTRIBUTE_NODE, attr1.getNodeType());
assertEquals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, attr1.getNamespaceURI());
- assertEquals(XMLConstants.XMLNS_ATTRIBUTE, attr1.getPrefix());
- assertEquals(XMLConstants.XML_NS_PREFIX, attr1.getLocalName());
- assertEquals(XMLConstants.XMLNS_ATTRIBUTE + ":" + XMLConstants.XML_NS_PREFIX, attr1.getNodeName());
- assertEquals(XMLConstants.XML_NS_URI, attr1.getValue());
+ assertEquals(null, attr1.getPrefix());
+ assertEquals(XMLConstants.XMLNS_ATTRIBUTE, attr1.getLocalName());
+ assertEquals(XMLConstants.XMLNS_ATTRIBUTE, attr1.getNodeName());
+ assertEquals("http://exist-db.org/xquery/repo", attr1.getValue());
final Attr attr2 = (Attr)attrs.item(index++);
assertEquals(Node.ATTRIBUTE_NODE, attr2.getNodeType());
assertEquals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, attr2.getNamespaceURI());
- assertEquals(null, attr2.getPrefix());
- assertEquals(XMLConstants.XMLNS_ATTRIBUTE, attr2.getLocalName());
- assertEquals(XMLConstants.XMLNS_ATTRIBUTE, attr2.getNodeName());
+ assertEquals(XMLConstants.XMLNS_ATTRIBUTE, attr2.getPrefix());
+ assertEquals("repo", attr2.getLocalName());
+ assertEquals(XMLConstants.XMLNS_ATTRIBUTE + ":repo", attr2.getNodeName());
assertEquals("http://exist-db.org/xquery/repo", attr2.getValue());
-
- final Attr attr3 = (Attr)attrs.item(index++);
- assertEquals(Node.ATTRIBUTE_NODE, attr3.getNodeType());
- assertEquals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, attr3.getNamespaceURI());
- assertEquals(XMLConstants.XMLNS_ATTRIBUTE, attr3.getPrefix());
- assertEquals("repo", attr3.getLocalName());
- assertEquals(XMLConstants.XMLNS_ATTRIBUTE + ":repo", attr3.getNodeName());
- assertEquals("http://exist-db.org/xquery/repo", attr3.getValue());
}
@Test
diff --git a/exist-core/src/test/xquery/xquery3/transform/fnTransform68.xqm b/exist-core/src/test/xquery/xquery3/transform/fnTransform68.xqm
index 7686ee2bc4f..a72cfb57707 100644
--- a/exist-core/src/test/xquery/xquery3/transform/fnTransform68.xqm
+++ b/exist-core/src/test/xquery/xquery3/transform/fnTransform68.xqm
@@ -36,7 +36,7 @@ declare variable $testTransform:transform-68-xsl-text := document { };
declare
- %test:assertError("FOXT0001")
+ %test:assertTrue
function testTransform:transform-68-supports-dynamic-evaluation() {
let $xsl := $testTransform:transform-68-xsl-text
let $result := fn:transform(map{
diff --git a/exist-parent/pom.xml b/exist-parent/pom.xml
index 4c9650a180c..8535c1ff1d4 100644
--- a/exist-parent/pom.xml
+++ b/exist-parent/pom.xml
@@ -121,7 +121,7 @@
1.8.1.3
1.8.1.3-jakarta5
2.1.3
- 9.9.1-8
+ 12.5
6.0.21
2.11.0
4.13.2
diff --git a/extensions/indexes/spatial/src/main/java/org/exist/indexing/spatial/AbstractGMLJDBCIndexWorker.java b/extensions/indexes/spatial/src/main/java/org/exist/indexing/spatial/AbstractGMLJDBCIndexWorker.java
index 7228a15aca4..6cccd9d7ad0 100644
--- a/extensions/indexes/spatial/src/main/java/org/exist/indexing/spatial/AbstractGMLJDBCIndexWorker.java
+++ b/extensions/indexes/spatial/src/main/java/org/exist/indexing/spatial/AbstractGMLJDBCIndexWorker.java
@@ -632,7 +632,21 @@ public Element streamGeometryToElement(final Geometry geometry, final String srs
//2) gmlPrefix
//3) other stuff...
//This will possibly require some changes in GeometryTransformer
- gmlString = gmlTransformer.transform(geometry);
+ // Force the JDK's built-in TransformerFactory for GeoTools.
+ // Saxon 12's IdentityTransformer rejects SAXSources whose XMLReader
+ // does not support the lexical-handler property, which GeoTools' reader doesn't.
+ final String savedFactory = System.getProperty("javax.xml.transform.TransformerFactory");
+ try {
+ System.setProperty("javax.xml.transform.TransformerFactory",
+ "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
+ gmlString = gmlTransformer.transform(geometry);
+ } finally {
+ if (savedFactory != null) {
+ System.setProperty("javax.xml.transform.TransformerFactory", savedFactory);
+ } else {
+ System.clearProperty("javax.xml.transform.TransformerFactory");
+ }
+ }
} catch (final TransformerException e) {
throw new SpatialIndexException(e);
}