From e9189b95c6f43ccc6532c5f3a001d2fe25d7040e Mon Sep 17 00:00:00 2001 From: Laurettta Date: Mon, 3 Aug 2026 10:10:36 +0100 Subject: [PATCH 1/6] [bugfix] fn:transform() now logs xsl:message output Closes https://github.com/evolvedbinary/elemental/issues/234 We have registered a Saxon MessageListener on the Xslt30Transformer so xsl:message output now goes to the Elemental log instead of just printing to the console. --- .../org/exist/xquery/functions/fn/transform/Transform.java | 4 ++++ 1 file changed, 4 insertions(+) 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 8ae6be6143..2c6b6c22ff 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 @@ -162,6 +162,10 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro final Xslt30Transformer xslt30Transformer = xsltExecutable.load30(); + xslt30Transformer.setMessageListener((content, terminate, locator) ->{ + LOGGER.info("xsl: message: {}", content.getStringValue()); + }); + options.initialMode.ifPresent(qNameValue -> xslt30Transformer.setInitialMode(Convert.ToSaxon.of(qNameValue.getQName()))); xslt30Transformer.setInitialTemplateParameters(options.templateParams, false); xslt30Transformer.setInitialTemplateParameters(options.tunnelParams, true); From 933b1bed681df2b97031969d2b1354ad59fb3b3f Mon Sep 17 00:00:00 2001 From: Laurettta Date: Tue, 4 Aug 2026 09:19:18 +0100 Subject: [PATCH 2/6] [bugfix] fn:transform() now logs xsl:message output Closes https://github.com/evolvedbinary/elemental/issues/234 --- .../functions/fn/transform/Transform.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) 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 2c6b6c22ff..c0fb097e49 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 @@ -53,6 +53,7 @@ import net.sf.saxon.s9api.*; import net.sf.saxon.serialize.SerializationProperties; import net.sf.saxon.trans.UncheckedXPathException; +import org.apache.commons.io.output.StringBuilderWriter; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.exist.dom.QName; @@ -163,7 +164,31 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro final Xslt30Transformer xslt30Transformer = xsltExecutable.load30(); xslt30Transformer.setMessageListener((content, terminate, locator) ->{ - LOGGER.info("xsl: message: {}", content.getStringValue()); + try { + final StringBuilderWriter writer = new StringBuilderWriter(); + final Serializer serializer = context.getBroker().getBrokerPool().getSaxonProcessor().newSerializer(); + serializer.setOutputProperty(Serializer.Property.OMIT_XML_DECLARATION, "yes"); + serializer.setOutputWriter(writer); + serializer.serializeNode(content); + + final String source; + final int sourceLine; + final int sourceColumn; + if (locator != null) { + source = locator.getSystemId(); + sourceLine = locator.getLineNumber(); + sourceColumn = locator.getColumnNumber(); + } else { + source = null; + sourceLine = -1; + sourceColumn = -1; + } + + LOGGER.info("{}", + terminate, source, sourceLine, sourceColumn, writer.toString()); + } catch (final SaxonApiException e) { + LOGGER.error("Unable to serialize xsl:message content", e); + } }); options.initialMode.ifPresent(qNameValue -> xslt30Transformer.setInitialMode(Convert.ToSaxon.of(qNameValue.getQName()))); From 54d93bc6238627979d5f1e410edb3df9607de748 Mon Sep 17 00:00:00 2001 From: Laurettta Date: Wed, 5 Aug 2026 10:09:09 +0100 Subject: [PATCH 3/6] [test] test for fn:transform() logs --- .../fn/transform/FunTransformITTest.java | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformITTest.java b/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformITTest.java index 43b9a63732..7afcab097f 100644 --- a/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformITTest.java +++ b/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformITTest.java @@ -21,6 +21,12 @@ package org.exist.xquery.functions.fn.transform; import com.evolvedbinary.j8fu.tuple.Tuple2; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.layout.PatternLayout; import org.exist.EXistException; import org.exist.collections.Collection; import org.exist.security.PermissionDeniedException; @@ -49,7 +55,9 @@ import javax.xml.transform.Source; import java.io.IOException; +import java.util.List; import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple; import static org.junit.Assert.*; @@ -247,6 +255,45 @@ public void identityMixedMemoryAndPersistentDom() throws XPathException, Permiss expectQuery(IDENTITY_MIXED_XSLT_QUERY_5, expected); } + @Test + public void xslMessageIsLogged() throws EXistException, PermissionDeniedException, IOException { + final CapturingAppender appender = new CapturingAppender(); + appender.start(); + + final Logger transformLogger = (Logger) LogManager.getLogger(Transform.class); + transformLogger.addAppender(appender); + + try { + final String query = + "fn:transform(map {\n" + + " \"stylesheet-text\": '\n" + + " \n" + + " Hello from XSLT\n" + + " \n" + + " \n" + + " ',\n" + + " \"source-node\": document { }\n" + + "})?output"; + + final BrokerPool pool = existEmbeddedServer.getBrokerPool(); + try (final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject())); + final XQueryUtil.QueryResult queryResult = XQueryUtil.query(broker, new StringSource(query), false, null, null, null, null, null)) { + assertNotNull(queryResult.result); + } catch (final XPathException e) { + fail("Transform should have succeeded: " + e.getMessage()); + } + + final Optional logged = appender.getMessages().stream() + .filter(message -> message.contains(" messages = new CopyOnWriteArrayList<>(); + + CapturingAppender() { + super("capturing-appender", null, PatternLayout.createDefaultLayout(), false, Property.EMPTY_ARRAY); + } + + @Override + public void append(final LogEvent event) { + messages.add(event.getMessage().getFormattedMessage()); + } + + List getMessages() { + return messages; + } + } } From efb9abe85826e6ebc7415da21c06be060205e8be Mon Sep 17 00:00:00 2001 From: Laurettta Date: Mon, 10 Aug 2026 11:41:50 +0100 Subject: [PATCH 4/6] [bugfix] Log xsl:message output from fn:transform() to Elemental's log --- .../functions/fn/transform/Transform.java | 83 ++++++++++++------- .../fn/transform/FunTransformITTest.java | 25 +++--- 2 files changed, 67 insertions(+), 41 deletions(-) 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 c0fb097e49..5bdbabff9d 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 @@ -53,7 +53,7 @@ import net.sf.saxon.s9api.*; import net.sf.saxon.serialize.SerializationProperties; import net.sf.saxon.trans.UncheckedXPathException; -import org.apache.commons.io.output.StringBuilderWriter; +import java.io.StringWriter; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.exist.dom.QName; @@ -68,8 +68,10 @@ import org.w3c.dom.Node; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.xml.transform.ErrorListener; import javax.xml.transform.Source; +import javax.xml.transform.SourceLocator; import javax.xml.transform.TransformerException; import javax.xml.transform.dom.DOMSource; import java.net.URI; @@ -162,34 +164,7 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro } final Xslt30Transformer xslt30Transformer = xsltExecutable.load30(); - - xslt30Transformer.setMessageListener((content, terminate, locator) ->{ - try { - final StringBuilderWriter writer = new StringBuilderWriter(); - final Serializer serializer = context.getBroker().getBrokerPool().getSaxonProcessor().newSerializer(); - serializer.setOutputProperty(Serializer.Property.OMIT_XML_DECLARATION, "yes"); - serializer.setOutputWriter(writer); - serializer.serializeNode(content); - - final String source; - final int sourceLine; - final int sourceColumn; - if (locator != null) { - source = locator.getSystemId(); - sourceLine = locator.getLineNumber(); - sourceColumn = locator.getColumnNumber(); - } else { - source = null; - sourceLine = -1; - sourceColumn = -1; - } - - LOGGER.info("{}", - terminate, source, sourceLine, sourceColumn, writer.toString()); - } catch (final SaxonApiException e) { - LOGGER.error("Unable to serialize xsl:message content", e); - } - }); + xslt30Transformer.setMessageListener(new XsltMessageListener(context)); options.initialMode.ifPresent(qNameValue -> xslt30Transformer.setInitialMode(Convert.ToSaxon.of(qNameValue.getQName()))); xslt30Transformer.setInitialTemplateParameters(options.templateParams, false); @@ -561,4 +536,54 @@ public PendingException(String message, Throwable cause) { super(message, cause); } } + + private static class XsltMessageListener implements MessageListener{ + + private final XQueryContext context; + + public XsltMessageListener(final XQueryContext context){ + this.context = context; + } + + public void message(XdmNode content, boolean terminate, SourceLocator locator){ + + try{ + final StringWriter writer = new StringWriter(); + final Serializer serializer = context.getBroker().getBrokerPool().getSaxonProcessor().newSerializer(); + serializer.setOutputProperty(Serializer.Property.OMIT_XML_DECLARATION, "yes"); + serializer.setOutputWriter(writer); + serializer.serializeNode(content); + + @Nullable + final String source; + final int sourceLine; + final int sourceColumn; + if (locator != null){ + source = locator.getSystemId(); + sourceLine = locator.getLineNumber(); + sourceColumn = locator.getColumnNumber(); + } else{ + source = null; + sourceLine = -1; + sourceColumn = -1; + } + + String tag = "", tag, writer.toString()); + } catch (final SaxonApiException e) { + LOGGER.error("Unable to serialize xsl:message content", e); + } + + + } + } } diff --git a/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformITTest.java b/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformITTest.java index 7afcab097f..408e40dd97 100644 --- a/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformITTest.java +++ b/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformITTest.java @@ -55,6 +55,7 @@ import javax.xml.transform.Source; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; @@ -256,7 +257,7 @@ public void identityMixedMemoryAndPersistentDom() throws XPathException, Permiss } @Test - public void xslMessageIsLogged() throws EXistException, PermissionDeniedException, IOException { + public void xslMessageIsLogged() throws EXistException, PermissionDeniedException, IOException, XPathException { final CapturingAppender appender = new CapturingAppender(); appender.start(); @@ -266,10 +267,9 @@ public void xslMessageIsLogged() throws EXistException, PermissionDeniedExceptio try { final String query = "fn:transform(map {\n" + - " \"stylesheet-text\": '\n" + + " \"stylesheet-text\": '\n" + " \n" + " Hello from XSLT\n" + - " \n" + " \n" + " ',\n" + " \"source-node\": document { }\n" + @@ -279,16 +279,17 @@ public void xslMessageIsLogged() throws EXistException, PermissionDeniedExceptio try (final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject())); final XQueryUtil.QueryResult queryResult = XQueryUtil.query(broker, new StringSource(query), false, null, null, null, null, null)) { assertNotNull(queryResult.result); - } catch (final XPathException e) { - fail("Transform should have succeeded: " + e.getMessage()); } - final Optional logged = appender.getMessages().stream() - .filter(message -> message.contains(" messages = new CopyOnWriteArrayList<>(); + private final List messages = new ArrayList<>(); CapturingAppender() { super("capturing-appender", null, PatternLayout.createDefaultLayout(), false, Property.EMPTY_ARRAY); From 55708673ca21084bd4ca38d38bdf54ae2f41efc2 Mon Sep 17 00:00:00 2001 From: Laurettta Date: Wed, 12 Aug 2026 00:36:36 +0100 Subject: [PATCH 5/6] [bugfix] Clean up xsl:message logging after review comments --- .../functions/fn/transform/Transform.java | 48 +++++++++---------- .../fn/transform/FunTransformITTest.java | 1 - 2 files changed, 24 insertions(+), 25 deletions(-) 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 5bdbabff9d..5724674424 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 @@ -53,6 +53,8 @@ import net.sf.saxon.s9api.*; import net.sf.saxon.serialize.SerializationProperties; import net.sf.saxon.trans.UncheckedXPathException; + +import java.io.IOException; import java.io.StringWriter; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -164,7 +166,7 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro } final Xslt30Transformer xslt30Transformer = xsltExecutable.load30(); - xslt30Transformer.setMessageListener(new XsltMessageListener(context)); + xslt30Transformer.setMessageListener(new XsltMessageListener(context.getBroker().getBrokerPool().getSaxonProcessor())); options.initialMode.ifPresent(qNameValue -> xslt30Transformer.setInitialMode(Convert.ToSaxon.of(qNameValue.getQName()))); xslt30Transformer.setInitialTemplateParameters(options.templateParams, false); @@ -537,53 +539,51 @@ public PendingException(String message, Throwable cause) { } } - private static class XsltMessageListener implements MessageListener{ + private static class XsltMessageListener implements MessageListener { - private final XQueryContext context; + private final Processor processor; - public XsltMessageListener(final XQueryContext context){ - this.context = context; + public XsltMessageListener(final Processor processor) { + this.processor = processor; } - public void message(XdmNode content, boolean terminate, SourceLocator locator){ + public void message(final XdmNode content, final boolean terminate, final SourceLocator locator) { - try{ - final StringWriter writer = new StringWriter(); - final Serializer serializer = context.getBroker().getBrokerPool().getSaxonProcessor().newSerializer(); + try (final StringWriter writer = new StringWriter()) { + final Serializer serializer = processor.newSerializer(); serializer.setOutputProperty(Serializer.Property.OMIT_XML_DECLARATION, "yes"); serializer.setOutputWriter(writer); serializer.serializeNode(content); - @Nullable - final String source; + @Nullable final String source; final int sourceLine; final int sourceColumn; - if (locator != null){ + if (locator != null) { source = locator.getSystemId(); sourceLine = locator.getLineNumber(); sourceColumn = locator.getColumnNumber(); - } else{ + } else { source = null; sourceLine = -1; sourceColumn = -1; } - String tag = "", tag, writer.toString()); - } catch (final SaxonApiException e) { - LOGGER.error("Unable to serialize xsl:message content", e); - } - + tag.append(">"); + LOGGER.info("{}{}", tag, writer.toString()); + } catch (final SaxonApiException e) { + LOGGER.error("Unable to serialize xsl:message content", e); + } catch (final IOException e) { + LOGGER.error("Unable to close xsl:message writer", e); + } } } } diff --git a/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformITTest.java b/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformITTest.java index 408e40dd97..9e8e4ea70d 100644 --- a/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformITTest.java +++ b/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformITTest.java @@ -58,7 +58,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.concurrent.CopyOnWriteArrayList; import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple; import static org.junit.Assert.*; From 10c646e3f979b1cc93654a581a99dcfe38ab87f8 Mon Sep 17 00:00:00 2001 From: Laurettta Date: Wed, 12 Aug 2026 09:18:05 +0100 Subject: [PATCH 6/6] [bugfix] Log xsl:message output from fn:transform() to Elemental's log Closes https://github.com/evolvedbinary/elemental/issues/234 --- .../java/org/exist/xquery/functions/fn/transform/Transform.java | 1 + 1 file changed, 1 insertion(+) 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 5724674424..8bb0a9fcb9 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 @@ -547,6 +547,7 @@ public XsltMessageListener(final Processor processor) { this.processor = processor; } + @Override public void message(final XdmNode content, final boolean terminate, final SourceLocator locator) { try (final StringWriter writer = new StringWriter()) {