From e9189b95c6f43ccc6532c5f3a001d2fe25d7040e Mon Sep 17 00:00:00 2001 From: Laurettta Date: Mon, 3 Aug 2026 10:10:36 +0100 Subject: [PATCH 1/3] [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/3] [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 6285211abb3f80e6d73ef526dacd22fd5583b83b Mon Sep 17 00:00:00 2001 From: Laurettta Date: Wed, 5 Aug 2026 10:09:09 +0100 Subject: [PATCH 3/3] [test] test for fn:transform() logs --- .../fn/transform/FunTransformITTest.java | 69 +++++++++++++++++++ 1 file changed, 69 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..4b6006264f 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,11 @@ 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.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 +54,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 +254,46 @@ 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 org.apache.logging.log4j.core.Logger transformLogger = + (org.apache.logging.log4j.core.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; + } + } }