Skip to content

Commit

Permalink
[fix] fn:transform global parameters missing
Browse files Browse the repository at this point in the history
Problem manifests when loading the stylesheet from eXist-db.

Stylesheet document fetching into sources is not recognising as documents the documents it received from the DB (as opposed to ones which are constructed in memory). All our testing has been done with temporary in memory documents and we missed the case of transform source in eXist-db.

To fix it, we just have to recognise org.exist.dom.persistent.DocumentImpl as well as org.exist.dom.memtree.DocumentImpl as a valid document source;
similarly, the case where a document can be a NodeProxy wasn’t recognised, we just need to fetch the node from the proxy when we recognise it as such, and proceed with using a source wrapping the resulting node as the stylesheet source.

This appears to make the
  • Loading branch information
alanpaxton committed Apr 25, 2023
1 parent 650609f commit 8acb3bf
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import net.sf.saxon.s9api.XdmValue;
import org.apache.commons.lang3.StringUtils;
import org.exist.dom.memtree.NamespaceNode;
import org.exist.dom.persistent.NodeProxy;
import org.exist.security.PermissionDeniedException;
import org.exist.xquery.ErrorCodes;
import org.exist.xquery.XPathException;
Expand Down Expand Up @@ -527,7 +528,12 @@ private Source resolvePossibleStylesheetLocation(final String location) throws X
"Can not access '" + location + "'" + e.getMessage());
}
if (document != null && document.hasOne() && Type.subTypeOf(document.getItemType(), Type.NODE)) {
return new DOMSource((Node) document.itemAt(0));
if (document instanceof NodeProxy proxy) {
return new DOMSource(proxy.getNode());
}
else if (document.itemAt(0) instanceof Node node) {
return new DOMSource(node);
}
}
throw new XPathException(fnTransform, ErrorCodes.FODC0002,
"Location '"+ location + "' returns an item which is not a document node");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.dom.QName;
import org.exist.dom.memtree.DocumentImpl;
import org.exist.util.Holder;
import org.exist.xquery.ErrorCodes;
import org.exist.xquery.XPathException;
Expand Down Expand Up @@ -177,7 +176,7 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro
final Document document;
Source source = sourceNode.get();
final Node node = ((DOMSource)sourceNode.get()).getNode();
if (!(node instanceof DocumentImpl)) {
if (!(node instanceof org.exist.dom.memtree.DocumentImpl) && !(node instanceof org.exist.dom.persistent.DocumentImpl)) {
//The source may not be a document
//If it isn't, it should be part of a document, so we build a DOMSource to use
document = node.getOwnerDocument();
Expand All @@ -193,7 +192,6 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro
final Transform.TemplateInvocation invocation = new Transform.TemplateInvocation(
options, sourceNode, delivery, xslt30Transformer, resultDocuments);
return invocation.invoke();

} catch (final SaxonApiException | UncheckedXPathException e) {
throw originalXPathException("Could not transform input: ", e, ErrorCodes.FOXT0003);
}
Expand All @@ -205,6 +203,7 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro


private XsltExecutable compileExecutable(final Options options) throws XPathException {

final XsltCompiler xsltCompiler = org.exist.xquery.functions.fn.transform.Transform.SAXON_PROCESSOR.newXsltCompiler();
final SingleRequestErrorListener errorListener = new SingleRequestErrorListener(Transform.ERROR_LISTENER);
xsltCompiler.setErrorListener(errorListener);
Expand All @@ -223,7 +222,7 @@ private XsltExecutable compileExecutable(final Options options) throws XPathExce
xsltCompiler.setURIResolver((href, base) -> {
try {
final URI hrefURI = URI.create(href);
if (!options.resolvedStylesheetBaseURI.isPresent() && !hrefURI.isAbsolute() && StringUtils.isEmpty(base)) {
if (options.resolvedStylesheetBaseURI.isEmpty() && !hrefURI.isAbsolute() && StringUtils.isEmpty(base)) {
final XPathException resolutionException = new XPathException(fnTransform,
ErrorCodes.XTSE0165,
"transform using a relative href, \n" +
Expand Down Expand Up @@ -266,8 +265,7 @@ private XPathException originalXPathException(final String prefix, @Nonnull fina

cause = e;
while (cause != null) {
if (cause instanceof net.sf.saxon.trans.XPathException) {
final net.sf.saxon.trans.XPathException xPathException = (net.sf.saxon.trans.XPathException)cause;
if (cause instanceof final net.sf.saxon.trans.XPathException xPathException) {
final StructuredQName from = xPathException.getErrorCodeQName();
if (from != null) {
final QName errorCodeQName = new QName(from.getLocalPart(), from.getURI(), from.getPrefix());
Expand Down
65 changes: 65 additions & 0 deletions exist-core/src/test/resources/org/exist/xquery/tei-toc.xsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:tei="http://www.tei-c.org/ns/1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:math="http://www.w3.org/2005/xpath-functions/math" xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl" exclude-result-prefixes="xs math xd tei" version="3.0">

<xsl:param name="heading" as="xs:boolean" select="true()"/>
<xsl:param name="documentID" as="xs:string" select="/tei:TEI/@xml:id"/>

<xsl:output indent="true"/>

<xsl:mode on-no-match="shallow-skip" use-accumulators="#all"/>
<xsl:mode name="html" on-no-match="text-only-copy"/>

<xsl:accumulator name="document-nos" initial-value="()" as="xs:string*">
<xsl:accumulator-rule match="tei:div[@type eq 'document']" select="($value, @n)" phase="end"/>
</xsl:accumulator>

<xsl:accumulator name="document-ids" initial-value="()" as="xs:string*">
<xsl:accumulator-rule match="tei:div[tei:div/@type = 'document']" select="()"/>
<xsl:accumulator-rule match="tei:div[@type eq 'document']" select="($value, @xml:id)" phase="end"/>
</xsl:accumulator>

<xsl:template match="tei:TEI">
<div class="toc">
<div class="toc__header">
<h4 class="title">Contents</h4>
</div>
<nav aria-label="Side navigation,,," class="toc__chapters">
<ul class="chapters js-smoothscroll">
<xsl:apply-templates select="tei:text"/>
</ul>
</nav>
</div>
</xsl:template>

<xsl:template match="tei:div[@xml:id][not(@type = ('document'))]">
<xsl:variable name="accDocs" as="xs:string*" select="accumulator-after('document-nos')"/>
<xsl:variable name="prevDocs" as="xs:string*" select="accumulator-before('document-nos')"/>
<xsl:variable name="docs" as="xs:string*" select="$accDocs[not(. = $prevDocs)]"/>
<xsl:variable name="prevDocIDs" as="xs:string*" select="accumulator-before('document-ids')"/>
<xsl:variable name="docIDs" as="xs:string*" select="accumulator-after('document-ids')[not(. = $prevDocIDs)]"/>
<li data-tei-id="{@xml:id}">
<xsl:if test="exists($docIDs) and tei:div[@type='document']">
<xsl:attribute name="data-tei-documents" select="string-join($docIDs, ' ')"/>
</xsl:if>

<a href="/{$documentID}/{@xml:id}">
<xsl:apply-templates mode="html" select="tei:head"/>
</a>
<xsl:value-of select="(' (Document' || 's'[count($docs) gt 1] || ' ' || $docs[1] || ' - '[count($docs) gt 1] || $docs[last()][count($docs) gt 1] || ')')[exists($docs)]"/>

<xsl:where-populated>
<ul class="chapters__nested">
<xsl:apply-templates/>
</ul>
</xsl:where-populated>
</li>
</xsl:template>

<xsl:template match="tei:div[@xml:id eq 'toc']" priority="2"/>

<xsl:template match="tei:head/tei:note" mode="html"/>

<xsl:template match="tei:lb" mode="html">
<br/>
</xsl:template>

</xsl:stylesheet>
111 changes: 111 additions & 0 deletions exist-core/src/test/xquery/xquery3/transform/fnTransformIssue4609.xqm
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
(:
: eXist-db Open Source Native XML Database
: Copyright (C) 2001 The eXist-db Authors
:
: [email protected]
: 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
:)
xquery version "3.1";

module namespace testTransform="http://exist-db.org/xquery/test/function_transform";
import module namespace xmldb="http://exist-db.org/xquery/xmldb";

declare namespace test="http://exist-db.org/xquery/xqsuite";

declare variable $testTransform:doc := document {
<TEI xmlns="http://www.tei-c.org/ns/1.0" xml:id="output">
<teiHeader>
<fileDesc>
<titleStmt>
<title/>
</titleStmt>
<publicationStmt>
<p>Test document</p>
</publicationStmt>
<sourceDesc>
<p>born digital</p>
</sourceDesc>
</fileDesc>
</teiHeader>
<text>
<front>
<div xml:id="pressrelease" type="section" subtype="press-release">
<head>Press Release</head>
</div>
</front>
<body>
<div type="compilation" xml:id="comp">
<head>Main Book</head>
<div type="chapter" xml:id="ch1">
<head>Chapter One</head>
<div type="document" n="1" xml:id="d1"/>
<div type="document" n="2" xml:id="d2"/>
<div type="document" n="3" xml:id="d3"/>
<div type="document" n="4" xml:id="d4"/>
<div type="document" n="5" xml:id="d5"/>
<div type="document" n="6" xml:id="d6"/>
<div type="document" n="7" xml:id="d7"/>
</div>
<div type="chapter" xml:id="ch2">
<head>Chapter Two</head>
<div type="document" n="8" xml:id="d8"/>
<div type="document" n="9" xml:id="d9"/>
<div type="document" n="10" xml:id="d10"/>
<div type="document" n="11" xml:id="d11"/>
<div type="document" n="12" xml:id="d12"/>
<div type="document" n="13" xml:id="d13"/>
<div type="document" n="14" xml:id="d14"/>
<div type="document" n="15" xml:id="d15"/>
</div>
<div type="chapter" xml:id="ch3">
<head>Chapter Three</head>
<div type="document" n="16" xml:id="d16"/>
<div type="document" n="17" xml:id="d17"/>
<div type="document" n="18" xml:id="d18"/>
<div type="document" n="19" xml:id="d19"/>
<div type="document" n="20" xml:id="d20"/>
<div type="document" n="21" xml:id="d21"/>
</div>
<div type="chapter" xml:id="ch4">
<head>Chapter Four</head>
<div type="document" n="22" xml:id="d22"/>
<div type="document" n="23" xml:id="d23"/>
<div type="document" n="24" xml:id="d24"/>
<div type="document" n="25" xml:id="d25"/>
<div type="document" n="26" xml:id="d26"/>
<div type="document" n="27" xml:id="d27"/>
<div type="document" n="28" xml:id="d28"/>
</div>
</div>
</body>
</text>
</TEI>
};

declare
%test:assertEquals("<div class=""toc""><div class=""toc__header""><h4 class=""title"">Contents</h4></div><nav aria-label=""Side navigation,,,"" class=""toc__chapters""><ul class=""chapters js-smoothscroll""><li data-tei-id=""pressrelease""><a href=""/output/pressrelease"">Press Release</a></li><li data-tei-id=""comp""><a href=""/output/comp"">Main Book</a> (Documents 1 - 28)<ul class=""chapters__nested""><li data-tei-id=""ch1"" data-tei-documents=""d1 d2 d3 d4 d5 d6 d7""><a href=""/output/ch1"">Chapter One</a> (Documents 1 - 7)</li><li data-tei-id=""ch2"" data-tei-documents=""d8 d9 d10 d11 d12 d13 d14 d15""><a href=""/output/ch2"">Chapter Two</a> (Documents 8 - 15)</li><li data-tei-id=""ch3"" data-tei-documents=""d16 d17 d18 d19 d20 d21""><a href=""/output/ch3"">Chapter Three</a> (Documents 16 - 21)</li><li data-tei-id=""ch4"" data-tei-documents=""d22 d23 d24 d25 d26 d27 d28""><a href=""/output/ch4"">Chapter Four</a> (Documents 22 - 28)</li></ul></li></ul></nav></div>")
function testTransform:issue-4609() {
let $create-collection := xmldb:create-collection("/db", "fn_transform_issue_4609")
let $doc-store := xmldb:store("/db/fn_transform_issue_4609", "input.xml", $testTransform:doc)

(: this works :)
let $result := ( fn:transform(map{
"stylesheet-location": 'resource:/org/exist/xquery/tei-toc.xsl',
"source-node": doc('/db/fn_transform_issue_4609/input.xml')
}))
return $result?output
};

0 comments on commit 8acb3bf

Please sign in to comment.