diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Convert.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Convert.java index 98a02f5aadb..93fd22fbb15 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Convert.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Convert.java @@ -158,6 +158,7 @@ private XdmValue ofNode(final Node node) throws XPathException { final DocumentBuilder sourceBuilder = newDocumentBuilder(); try { if (node instanceof Document) { + // a document node (in-memory or persistent) can be built directly return sourceBuilder.build(new DOMSource(node)); } else { //The source must be part of a document diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Delivery.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Delivery.java index 682e910e2d0..7d58d70008c 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Delivery.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Delivery.java @@ -57,15 +57,15 @@ enum Format { this.serializationProperties = serializationProperties; } - final Destination createDestination(final Xslt30Transformer xslt30Transformer, final boolean forceCreation) { + final Destination createDestination(final Xslt30Transformer xslt30Transformer) { switch (format) { case DOCUMENT: - if (!forceCreation) { - this.builder = context.getDocumentBuilder(); - } else { - this.builder = new MemTreeBuilder(context); - this.builder.startDocument(); - } + // NOTE: Always build the result into a fresh document builder. + // The shared builder of the XQueryContext may already be in use + // by an enclosing expression (e.g. an element constructor). + // convert() returns the builder's whole document - using the shared builder corrupts both. + this.builder = new MemTreeBuilder(context); + this.builder.startDocument(); return new SAXDestination(new DocumentBuilderReceiver(builder)); case SERIALIZED: final Serializer serializer = xslt30Transformer.newSerializer(); diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Options.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Options.java index e59e6567a1b..8543648c730 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Options.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Options.java @@ -32,9 +32,12 @@ 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; import org.exist.xquery.XQueryContext; +import org.exist.xquery.util.DocUtils; import org.exist.xquery.functions.array.ArrayType; import org.exist.xquery.functions.fn.FnTransform; import org.exist.xquery.functions.map.MapType; @@ -166,7 +169,12 @@ class Options { stylesheetBaseUri = xsltSource._1; } if (!StringUtils.isEmpty(stylesheetBaseUri)) { - resolvedStylesheetBaseURI = Optional.of(resolveURI(new AnyURIValue(stylesheetBaseUri), context.getBaseURI())); + // Only resolve if it's not already absolute (database URIs start with "/" or "xmldb:") + if (stylesheetBaseUri.startsWith("/") || stylesheetBaseUri.startsWith("xmldb:") || stylesheetBaseUri.startsWith("exist://")) { + resolvedStylesheetBaseURI = Optional.of(new AnyURIValue(stylesheetBaseUri)); + } else { + resolvedStylesheetBaseURI = Optional.of(resolveURI(new AnyURIValue(stylesheetBaseUri), context.getBaseURI())); + } } else { resolvedStylesheetBaseURI = Optional.empty(); } @@ -467,7 +475,7 @@ private Tuple2 getStylesheet(final MapType options) throws XPath final List> results = new ArrayList<>(1); final Optional stylesheetLocation = Options.STYLESHEET_LOCATION.get(options).map(StringValue::getStringValue); if (stylesheetLocation.isPresent()) { - results.add(Tuple(stylesheetLocation.get(), resolveStylesheetLocation(stylesheetLocation.get()))); + results.add(resolveStylesheetLocation(stylesheetLocation.get())); } final Optional stylesheetNode = Options.STYLESHEET_NODE.get(options).map(NodeValue::getNode); @@ -496,19 +504,67 @@ private Tuple2 getStylesheet(final MapType options) throws XPath * It may be a dynamically configured document. * Or a document within the database. *

+ *

+ * A relative location is first resolved the way {@code fn:doc} resolves + * relative paths: against the base URI of the query (where a collection + * path is treated as a "directory") and/or the location of the querying + * module within the database. If that does not find a document, the + * location is resolved strictly against the static base URI according + * to RFC 3986 (e.g. for file: or http: base URIs). + * See issue 5052. + *

* @param stylesheetLocation path or URI of stylesheet - * @return a source wrapping the contents of the stylesheet + * @return a Tuple whose first value is the actual location of the resolved + * stylesheet, and whose second value is a source wrapping its contents * @throws XPathException if there is a problem resolving the location. */ - private Source resolveStylesheetLocation(final String stylesheetLocation) throws XPathException { + private Tuple2 resolveStylesheetLocation(final String stylesheetLocation) throws XPathException { final URI uri = URI.create(stylesheetLocation); if (uri.isAbsolute()) { - return URIResolution.resolveDocument(stylesheetLocation, context, fnTransform); - } else { + return resolvePossibleStylesheetLocation(stylesheetLocation); + } + + try { + return resolvePossibleStylesheetLocation(stylesheetLocation); + } catch (final XPathException e) { final AnyURIValue resolved = resolveURI(new AnyURIValue(stylesheetLocation), context.getBaseURI()); - return URIResolution.resolveDocument(resolved.getStringValue(), context, fnTransform); + return resolvePossibleStylesheetLocation(resolved.getStringValue()); + } + } + + /** + * Resolve a stylesheet location + * + * @param location of the stylesheet + * @return a Tuple whose first value is the actual location of the resolved + * stylesheet (used as its base URI), and whose second value is the + * resolved stylesheet as a source + * @throws XPathException if the item does not exist, or is not a document + */ + private Tuple2 resolvePossibleStylesheetLocation(final String location) throws XPathException { + + Sequence document; + try { + document = DocUtils.getDocument(context, location); + } catch (final PermissionDeniedException e) { + throw new XPathException(fnTransform, ErrorCodes.FODC0002, + "Can not access '" + location + "'" + e.getMessage()); + } + if (document != null && document.hasOne() && Type.subTypeOf(document.getItemType(), Type.NODE)) { + if (document instanceof NodeProxy nodeProxy) { + final DOMSource source = new DOMSource(nodeProxy.getNode()); + source.setSystemId(location); + return Tuple(location, source); + } + else if (document.itemAt(0) instanceof Node node) { + final DOMSource source = new DOMSource(node); + source.setSystemId(location); + return Tuple(location, source); + } } + throw new XPathException(fnTransform, ErrorCodes.FODC0002, + "Location '"+ location + "' returns an item which is not a document node"); } /** 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 301ff0e7635..8aa87353268 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 @@ -167,7 +167,7 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro xslt30Transformer.setResultDocumentHandler(resultDocumentURI -> { final Delivery resultDelivery = new Delivery(context, options.deliveryFormat, serializationProperties); resultDocuments.put(resultDocumentURI, resultDelivery); - return resultDelivery.createDestination(xslt30Transformer, true); + return resultDelivery.createDestination(xslt30Transformer); }); if (options.globalContextItem.isPresent()) { @@ -330,7 +330,7 @@ private class TemplateInvocation { this.options = options; this.sourceNode = sourceNode; this.delivery = delivery; - this.destination = delivery.createDestination(xslt30Transformer, false); + this.destination = delivery.createDestination(xslt30Transformer); this.xslt30Transformer = xslt30Transformer; this.resultDocuments = resultDocuments; } diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/URIResolution.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/URIResolution.java index 5abbe73da14..b1aae22989a 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/URIResolution.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/URIResolution.java @@ -46,29 +46,84 @@ public class URIResolution { /** * URI resolution, the core should be the same as for fn:resolve-uri + *

+ * A location within the database is resolved against the base as if the base + * were a collection whenever {@link #assumeCollection(String)} holds, and as + * if it were a document otherwise. A base outside the database (for instance a + * {@code file:} or {@code http:} URI) is always resolved strictly according to + * RFC 3986, that is, as if it were a document. + *

* @param relative URI to resolve * @param base to resolve against * @return resolved URI * @throws URISyntaxException if resolution is not possible */ static AnyURIValue resolveURI(final AnyURIValue relative, final AnyURIValue base) throws URISyntaxException, XPathException { - var relativeURI = new URI(relative.getStringValue()); + final URI relativeURI = new URI(relative.getStringValue()); if (relativeURI.isAbsolute()) { return relative; } - var baseURI = new URI(base.getStringValue() ); - if (!baseURI.isAbsolute()) { + final String baseString = base.getStringValue(); + final URI baseURI = new URI(baseString); + // a database path such as /db/apps/app has no scheme, so URI#isAbsolute is false, + // yet it is an absolute location within the database and can be resolved against + final boolean isAbsoluteBase = baseURI.isAbsolute() || baseString.startsWith("/"); + if (!isAbsoluteBase) { return relative; } try { - var xBase = XmldbURI.xmldbUriFor(baseURI); - var resolved = xBase.getURI().resolve(relativeURI); + final XmldbURI xBase = XmldbURI.xmldbUriFor(baseURI); + // NOTE: for an xmldb: base, XmldbURI#getURI has already stripped the xmldb: prefix, + // but for the short form (xmldb:/db/...) it has not; only add the prefix if it is absent, + // otherwise the result doubles up as xmldb:xmldb:/db/... + final URI resolved = asResolutionBase(xBase.getURI()).resolve(relativeURI); + if (XmldbURI.XMLDB_SCHEME.equals(resolved.getScheme())) { + return new AnyURIValue(resolved.toString()); + } return new AnyURIValue(XmldbURI.XMLDB_URI_PREFIX + resolved); - } catch (URISyntaxException e) { + } catch (final URISyntaxException e) { return new AnyURIValue(baseURI.resolve(relativeURI)); } } + /** + * Prepare a location within the database to be resolved against. + *

+ * RFC 3986 discards the last segment of the base unless it is empty, which is + * correct for a document but not for a collection: resolving {@code style.xsl} + * against the collection {@code /db/apps/app} would yield {@code /db/apps/style.xsl}. + * A collection is therefore given the trailing slash that marks it as a + * "directory" before it is resolved against. + *

+ * + * @param base location within the database + * @return the location to resolve against + */ + private static URI asResolutionBase(final URI base) { + final String baseString = base.toString(); + if (!baseString.endsWith("/") && assumeCollection(baseString)) { + return URI.create(baseString + "/"); + } + return base; + } + + /** + * Whether a location within the database is assumed to be a collection. + *

+ * A collection and a document are not distinguishable by their path alone, so + * the absence of an extension in the last segment is taken to mean a collection. + * This is a heuristic: a document stored without an extension (which is legal, + * if unusual) is mistaken for a collection. + *

+ * + * @param location within the database + * @return true if the location is assumed to be a collection + */ + private static boolean assumeCollection(final String location) { + final String lastSegment = location.substring(location.lastIndexOf('/') + 1); + return lastSegment.indexOf('.') == -1; + } + public static class CompileTimeURIResolver implements URIResolver { private final XQueryContext xQueryContext; @@ -85,7 +140,7 @@ public Source resolve(final String href, final String base) throws TransformerEx try { final AnyURIValue baseURI = new AnyURIValue(base); final AnyURIValue hrefURI = new AnyURIValue(href); - var resolved = resolveURI(hrefURI, baseURI); + final AnyURIValue resolved = resolveURI(hrefURI, baseURI); return resolveDocument(resolved.getStringValue()); } catch (URISyntaxException e) { throw new TransformerException( @@ -123,10 +178,14 @@ static Source resolveDocument(final String location, final XQueryContext xQueryC } if (document.hasOne() && Type.subTypeOf(document.getItemType(), Type.NODE)) { if (document instanceof NodeProxy proxy) { - return new DOMSource(proxy.getNode()); + final DOMSource source = new DOMSource(proxy.getNode()); + source.setSystemId(location); + return source; } else if (document.itemAt(0) instanceof Node node) { - return new DOMSource(node); + final DOMSource source = new DOMSource(node); + source.setSystemId(location); + return source; } } throw new XPathException(containingExpression, ErrorCodes.FODC0002, diff --git a/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformTest.java b/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformTest.java index 0a6f6b84099..6a095aac65f 100644 --- a/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformTest.java +++ b/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformTest.java @@ -114,6 +114,59 @@ public void resolution() throws XPathException, URISyntaxException { URIResolution.resolveURI(relative5, base5)); } + @Test + public void resolutionAgainstDatabasePath() throws XPathException, URISyntaxException { + final AnyURIValue relative = new AnyURIValue("functions1.xsl"); + + // a database path has no scheme, yet it is an absolute location within the database + final AnyURIValue databaseBase = new AnyURIValue("/db/apps/fn_transform/tei-toc2.xsl"); + final AnyURIValue resolved = URIResolution.resolveURI(relative, databaseBase); + assertEquals(new AnyURIValue("xmldb:/db/apps/fn_transform/functions1.xsl"), resolved); + + // a resolved stylesheet becomes the base for the relative xsl:import it contains, + // so resolving against an xmldb: base must not prepend a second xmldb: prefix + assertEquals(new AnyURIValue("xmldb:/db/apps/fn_transform/functions2.xsl"), + URIResolution.resolveURI(new AnyURIValue("functions2.xsl"), resolved)); + + final AnyURIValue shortBase = new AnyURIValue("xmldb:/db/apps/fn_transform/tei-toc2.xsl"); + assertEquals(new AnyURIValue("xmldb:/db/apps/fn_transform/functions1.xsl"), + URIResolution.resolveURI(relative, shortBase)); + + // the instance name and authority of an xmldb: base are preserved + final AnyURIValue remoteBase = new AnyURIValue("xmldb:exist://localhost:8080/db/apps/fn_transform/tei-toc2.xsl"); + assertEquals(new AnyURIValue("xmldb:exist://localhost:8080/db/apps/fn_transform/functions1.xsl"), + URIResolution.resolveURI(relative, remoteBase)); + } + + @Test + public void resolutionAgainstCollection() throws XPathException, URISyntaxException { + final AnyURIValue relative = new AnyURIValue("functions1.xsl"); + + // the last segment of a collection has no extension, so it is kept when resolving against it + final AnyURIValue collectionBase = new AnyURIValue("/db/apps/fn_transform"); + assertEquals(new AnyURIValue("xmldb:/db/apps/fn_transform/functions1.xsl"), + URIResolution.resolveURI(relative, collectionBase)); + + final AnyURIValue xmldbCollectionBase = new AnyURIValue("xmldb:exist:///db/apps/fn_transform"); + assertEquals(new AnyURIValue("xmldb:exist:/db/apps/fn_transform/functions1.xsl"), + URIResolution.resolveURI(relative, xmldbCollectionBase)); + + // a collection that already ends in a slash is not given a second one + final AnyURIValue slashedBase = new AnyURIValue("/db/apps/fn_transform/"); + assertEquals(new AnyURIValue("xmldb:/db/apps/fn_transform/functions1.xsl"), + URIResolution.resolveURI(relative, slashedBase)); + + // the last segment of a document has an extension, so it is discarded when resolving against it + final AnyURIValue documentBase = new AnyURIValue("/db/apps/fn_transform/tei-toc2.xsl"); + assertEquals(new AnyURIValue("xmldb:/db/apps/fn_transform/functions1.xsl"), + URIResolution.resolveURI(relative, documentBase)); + + // outside of the database RFC 3986 applies, so the last segment is discarded regardless + final AnyURIValue httpBase = new AnyURIValue("https://127.0.0.1:8088/db/apps/fn_transform"); + assertEquals(new AnyURIValue("https://127.0.0.1:8088/db/apps/functions1.xsl"), + URIResolution.resolveURI(relative, httpBase)); + } + /** * Create some UT coverage of the CompileTimeURIResolver * This is more significantly exercised by XQTS tests diff --git a/exist-core/src/test/xquery/xquery3/transform/fnTransform5052.xqm b/exist-core/src/test/xquery/xquery3/transform/fnTransform5052.xqm new file mode 100644 index 00000000000..f13cd5f8fd6 --- /dev/null +++ b/exist-core/src/test/xquery/xquery3/transform/fnTransform5052.xqm @@ -0,0 +1,199 @@ +(: + : eXist-db Open Source Native XML Database + : Copyright (C) 2001 The eXist-db Authors + : + : info@exist-db.org + : 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"; + +(:~ + : Tests for issue 5052: fn:transform does not resolve relative URIs + : against the database. + : + : Covers: + : - relative xsl:include / xsl:import hrefs in stylesheets stored in the + : database (resolved from the containing collection of the stylesheet) + : - a relative "stylesheet-location" in a query stored in the database + : (resolved from the containing collection of the query, consistent + : with fn:doc and transform:transform) + : + : @see https://github.com/eXist-db/exist/issues/5052 + :) +module namespace t5052="http://exist-db.org/xquery/test/fn-transform-5052"; + +import module namespace xmldb="http://exist-db.org/xquery/xmldb"; +import module namespace util="http://exist-db.org/xquery/util"; + +declare namespace test="http://exist-db.org/xquery/xqsuite"; +declare namespace xsl="http://www.w3.org/1999/XSL/Transform"; + +declare variable $t5052:coll-name := "fn-transform-5052"; +declare variable $t5052:coll := "/db/" || $t5052:coll-name; + +declare variable $t5052:plain-xsl := + + + ; + +declare variable $t5052:included-xsl := + + included + ; + +declare variable $t5052:main-include-xsl := + + + + ; + +declare variable $t5052:imported-xsl := + + imported + ; + +declare variable $t5052:main-import-xsl := + + + + ; + +declare variable $t5052:nested-a-xsl := + + + ; + +declare variable $t5052:nested-b-xsl := + + nested + ; + +declare variable $t5052:main-nested-xsl := + + + + ; + +(: a query stored in the database, using a stylesheet-location relative to its collection :) +declare variable $t5052:relative-location-xq := + 'xquery version "3.1"; + fn:transform(map{ + "stylesheet-location": "plain.xsl", + "source-node": document { } + })?output'; + +(: as above, but with a base-uri declared in the prolog: the collection URI without a + : trailing slash, as reported in https://github.com/eXist-db/exist/issues/5052 :) +declare variable $t5052:relative-location-base-uri-xq := + 'xquery version "3.1"; + declare base-uri "/db/fn-transform-5052"; + fn:transform(map{ + "stylesheet-location": "plain.xsl", + "source-node": document { } + })?output'; + +declare + %test:setUp +function t5052:setup() { + xmldb:create-collection("/db", $t5052:coll-name), + xmldb:create-collection($t5052:coll, "sub"), + xmldb:store($t5052:coll, "plain.xsl", $t5052:plain-xsl), + xmldb:store($t5052:coll, "included.xsl", $t5052:included-xsl), + xmldb:store($t5052:coll, "main-include.xsl", $t5052:main-include-xsl), + xmldb:store($t5052:coll || "/sub", "imported.xsl", $t5052:imported-xsl), + xmldb:store($t5052:coll, "main-import.xsl", $t5052:main-import-xsl), + xmldb:store($t5052:coll || "/sub", "nested-a.xsl", $t5052:nested-a-xsl), + xmldb:store($t5052:coll || "/sub", "nested-b.xsl", $t5052:nested-b-xsl), + xmldb:store($t5052:coll, "main-nested.xsl", $t5052:main-nested-xsl), + xmldb:store($t5052:coll, "relative-location.xq", $t5052:relative-location-xq, "application/xquery"), + xmldb:store($t5052:coll, "relative-location-base-uri.xq", $t5052:relative-location-base-uri-xq, "application/xquery") +}; + +declare + %test:tearDown +function t5052:tearDown() { + xmldb:remove($t5052:coll) +}; + +(:~ Control: absolute stylesheet-location without includes works before and after the fix. :) +declare + %test:assertEquals("") +function t5052:absolute-location-no-include() { + fn:transform(map{ + "stylesheet-location": $t5052:coll || "/plain.xsl", + "source-node": document { } + })?output +}; + +(:~ Relative xsl:include, resolved from the collection containing the stylesheet. :) +declare + %test:assertEquals("included") +function t5052:include-relative-same-collection() { + fn:serialize(fn:transform(map{ + "stylesheet-location": $t5052:coll || "/main-include.xsl", + "source-node": document { } + })?output) +}; + +(:~ Relative xsl:import into a sub-collection. :) +declare + %test:assertEquals("imported") +function t5052:import-relative-sub-collection() { + fn:serialize(fn:transform(map{ + "stylesheet-location": $t5052:coll || "/main-import.xsl", + "source-node": document { } + })?output) +}; + +(:~ Base URI must be propagated per stylesheet module: sub/nested-a.xsl includes + : nested-b.xsl which lives next to it in sub/. :) +declare + %test:assertEquals("nested") +function t5052:include-relative-nested() { + fn:serialize(fn:transform(map{ + "stylesheet-location": $t5052:coll || "/main-nested.xsl", + "source-node": document { } + })?output) +}; + +(:~ Relative xsl:include where the stylesheet is passed as a persistent + : stylesheet-node stored in the database. :) +declare + %test:assertEquals("included") +function t5052:stylesheet-node-stored-include() { + fn:serialize(fn:transform(map{ + "stylesheet-node": doc($t5052:coll || "/main-include.xsl"), + "source-node": document { } + })?output) +}; + +(:~ A query stored in the database resolves a relative stylesheet-location + : from its own collection (as fn:doc and transform:transform do). :) +declare + %test:assertEquals("") +function t5052:relative-location-stored-query() { + fn:serialize(util:eval(xs:anyURI($t5052:coll || "/relative-location.xq"))) +}; + +(:~ As reported in issue 5052: a declared base-uri pointing to a collection + : (no trailing slash) must not lose its last path segment when a relative + : stylesheet-location is resolved against it. :) +declare + %test:assertEquals("") +function t5052:relative-location-collection-base-uri() { + fn:serialize(util:eval(xs:anyURI($t5052:coll || "/relative-location-base-uri.xq"))) +}; diff --git a/exist-core/src/test/xquery/xquery3/transform/fnTransform6065.xqm b/exist-core/src/test/xquery/xquery3/transform/fnTransform6065.xqm new file mode 100644 index 00000000000..15edac83427 --- /dev/null +++ b/exist-core/src/test/xquery/xquery3/transform/fnTransform6065.xqm @@ -0,0 +1,108 @@ +(: + : eXist-db Open Source Native XML Database + : Copyright (C) 2001 The eXist-db Authors + : + : info@exist-db.org + : 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 t6065="http://exist-db.org/xquery/test/t6065"; +import module namespace xmldb="http://exist-db.org/xquery/xmldb"; +declare namespace test="http://exist-db.org/xquery/xqsuite"; +declare namespace xsl="http://www.w3.org/1999/XSL/Transform"; + +declare variable $t6065:collection-name := "test-issue-6065"; +declare variable $t6065:collection := "/db/" || $t6065:collection-name; + +declare variable $t6065:source-doc-name := "source-document.xml"; + + +declare variable $t6065:stylesheet-unnamed-template := + + + + + + + +; + +declare variable $t6065:stylesheet-named-template := + + + + + + + +; + +declare variable $t6065:document := + + + + Gambardella, Matthew + + + Ralls, Kim + + + +; + +declare + %test:setUp +function t6065:setup() { + xmldb:create-collection("/db", $t6065:collection-name), + xmldb:store($t6065:collection, "unnamed-template.xsl", $t6065:stylesheet-unnamed-template, "application/xslt+xml"), + xmldb:store($t6065:collection, "with-named-template.xsl", $t6065:stylesheet-named-template, "application/xslt+xml"), + xmldb:store($t6065:collection, $t6065:source-doc-name, $t6065:document, "application/document") +}; + +declare + %test:tearDown +function t6065:cleanup() { + xmldb:remove($t6065:collection) +}; + +declare + %test:assertEquals("Gambardella, MatthewRalls, Kim") +function t6065:test-1() { + transform(map{ + "source-node": document { }, + "stylesheet-node": doc($t6065:collection || "/unnamed-template.xsl"), + "stylesheet-params": map { + xs:QName("v"): doc($t6065:collection || "/" || $t6065:source-doc-name) + } +(: ,"delivery-format": "serialized":) + })?output +(: ,():) +}; + +declare + %test:assertEquals("2") +function t6065:test-2() { + transform(map{ + "source-node": document { }, + "stylesheet-node": doc($t6065:collection || "/unnamed-template.xsl"), + "stylesheet-params": map { xs:QName("v"): "2" } +(: ,"delivery-format": "serialized":) + })?output +(: ,():) +}; +