|
53 | 53 | import net.sf.saxon.s9api.*; |
54 | 54 | import net.sf.saxon.serialize.SerializationProperties; |
55 | 55 | import net.sf.saxon.trans.UncheckedXPathException; |
| 56 | + |
| 57 | +import java.io.IOException; |
| 58 | +import java.io.StringWriter; |
56 | 59 | import org.apache.logging.log4j.LogManager; |
57 | 60 | import org.apache.logging.log4j.Logger; |
58 | 61 | import org.exist.dom.QName; |
|
67 | 70 | import org.w3c.dom.Node; |
68 | 71 |
|
69 | 72 | import javax.annotation.Nonnull; |
| 73 | +import javax.annotation.Nullable; |
70 | 74 | import javax.xml.transform.ErrorListener; |
71 | 75 | import javax.xml.transform.Source; |
| 76 | +import javax.xml.transform.SourceLocator; |
72 | 77 | import javax.xml.transform.TransformerException; |
73 | 78 | import javax.xml.transform.dom.DOMSource; |
74 | 79 | import java.net.URI; |
|
79 | 84 |
|
80 | 85 | import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple; |
81 | 86 | import static org.exist.util.StringUtil.isNullOrEmpty; |
| 87 | +import static org.exist.util.StringUtil.notNullOrEmpty; |
82 | 88 | import static org.exist.xquery.functions.fn.transform.Options.Option.*; |
83 | 89 |
|
84 | 90 | /** |
|
104 | 110 | */ |
105 | 111 | public class Transform { |
106 | 112 |
|
107 | | - private static final Logger LOGGER = LogManager.getLogger(org.exist.xquery.functions.fn.transform.Transform.class); |
| 113 | + private static Logger LOGGER = LogManager.getLogger(org.exist.xquery.functions.fn.transform.Transform.class); |
108 | 114 | private static final org.exist.xquery.functions.fn.transform.Transform.ErrorListenerLog4jAdapter ERROR_LISTENER = new Transform.ErrorListenerLog4jAdapter(Transform.LOGGER); |
109 | 115 |
|
110 | 116 | final Convert.ToSaxon toSaxon = new Convert.ToSaxon() { |
@@ -161,6 +167,7 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro |
161 | 167 | } |
162 | 168 |
|
163 | 169 | final Xslt30Transformer xslt30Transformer = xsltExecutable.load30(); |
| 170 | + xslt30Transformer.setMessageListener(new XsltMessageListener(context.getBroker().getBrokerPool().getSaxonProcessor(), getLogger())); |
164 | 171 |
|
165 | 172 | options.initialMode.ifPresent(qNameValue -> xslt30Transformer.setInitialMode(Convert.ToSaxon.of(qNameValue.getQName()))); |
166 | 173 | xslt30Transformer.setInitialTemplateParameters(options.templateParams, false); |
@@ -464,6 +471,19 @@ private static Optional<Source> getSourceNode(final Optional<NodeValue> sourceNo |
464 | 471 | return sourceNode.map(NodeValue::getNode).map(node -> new DOMSource(node, baseURI.getStringValue())); |
465 | 472 | } |
466 | 473 |
|
| 474 | + /** |
| 475 | + * Designed to be package-protected accessible so that we can observe logging in tests. |
| 476 | + * |
| 477 | + * @param logger the logger to use in testing. |
| 478 | + */ |
| 479 | + static void setLogger(final Logger logger) { |
| 480 | + LOGGER = logger; |
| 481 | + } |
| 482 | + |
| 483 | + private Logger getLogger() { |
| 484 | + return LOGGER; |
| 485 | + } |
| 486 | + |
467 | 487 | private static class ErrorListenerLog4jAdapter implements ErrorListener { |
468 | 488 | private final Logger logger; |
469 | 489 |
|
@@ -532,4 +552,55 @@ public PendingException(String message, Throwable cause) { |
532 | 552 | super(message, cause); |
533 | 553 | } |
534 | 554 | } |
| 555 | + |
| 556 | + private static class XsltMessageListener implements MessageListener { |
| 557 | + |
| 558 | + private final Processor processor; |
| 559 | + private final Logger logger; |
| 560 | + |
| 561 | + public XsltMessageListener(final Processor processor, final Logger logger) { |
| 562 | + this.processor = processor; |
| 563 | + this.logger = logger; |
| 564 | + } |
| 565 | + |
| 566 | + @Override |
| 567 | + public void message(final XdmNode content, final boolean terminate, final SourceLocator locator) { |
| 568 | + |
| 569 | + try (final StringWriter writer = new StringWriter()) { |
| 570 | + final Serializer serializer = processor.newSerializer(); |
| 571 | + serializer.setOutputProperty(Serializer.Property.OMIT_XML_DECLARATION, "yes"); |
| 572 | + serializer.setOutputWriter(writer); |
| 573 | + serializer.serializeNode(content); |
| 574 | + |
| 575 | + @Nullable final String source; |
| 576 | + final int sourceLine; |
| 577 | + final int sourceColumn; |
| 578 | + if (locator != null) { |
| 579 | + source = locator.getSystemId(); |
| 580 | + sourceLine = locator.getLineNumber(); |
| 581 | + sourceColumn = locator.getColumnNumber(); |
| 582 | + } else { |
| 583 | + source = null; |
| 584 | + sourceLine = -1; |
| 585 | + sourceColumn = -1; |
| 586 | + } |
| 587 | + |
| 588 | + final StringBuilder tag = new StringBuilder("<xsl:message terminate=\"" + terminate + "\""); |
| 589 | + if (notNullOrEmpty(source)) { |
| 590 | + tag.append(" source=\"").append(source).append("\""); |
| 591 | + } |
| 592 | + if (sourceLine != -1) { |
| 593 | + tag.append(" sourceLine=\"").append(sourceLine).append("\""); |
| 594 | + tag.append(" sourceColumn=\"").append(sourceColumn).append("\""); |
| 595 | + } |
| 596 | + tag.append(">"); |
| 597 | + |
| 598 | + logger.info("{}{}</xsl:message>", tag.toString(), writer.toString()); |
| 599 | + } catch (final SaxonApiException e) { |
| 600 | + logger.error("Unable to serialize xsl:message content", e); |
| 601 | + } catch (final IOException e) { |
| 602 | + logger.error("Unable to close xsl:message writer", e); |
| 603 | + } |
| 604 | + } |
| 605 | + } |
535 | 606 | } |
0 commit comments