From e9189b95c6f43ccc6532c5f3a001d2fe25d7040e Mon Sep 17 00:00:00 2001 From: Laurettta Date: Mon, 3 Aug 2026 10:10:36 +0100 Subject: [PATCH 1/4] [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/4] [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/4] [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/4] [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);