Skip to content

Commit 20dc1f1

Browse files
committed
[feature] fn:transform - requested-properties
Validate “requested-properties” against the system properties of the transformer implementation we are calling (Saxon). fn:transform will proceed if the requested properties match those provided by the transformer. fn:transform will raise an exception if any requested property does not match. No effort is made to deal with or to configure numeric values, so a request for product-version 2.1 will NOT be satisfied by product-version 2.3. Strictly, this behavious conforms to the spec https://www.w3.org/TR/xpath-functions-31/#func-transform, though in some cases it is not terribly helpful.
1 parent 74402d9 commit 20dc1f1

File tree

2 files changed

+113
-4
lines changed

2 files changed

+113
-4
lines changed

exist-core/src/main/java/org/exist/xquery/functions/fn/FnTransform.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import net.jpountz.xxhash.XXHash64;
3030
import net.jpountz.xxhash.XXHashFactory;
3131
import net.sf.saxon.Configuration;
32+
import net.sf.saxon.expr.parser.RetainedStaticContext;
3233
import net.sf.saxon.functions.SystemProperty;
3334
import net.sf.saxon.s9api.*;
3435
import net.sf.saxon.serialize.SerializationProperties;
@@ -112,6 +113,14 @@ public class FnTransform extends BasicFunction {
112113
private static final Configuration SAXON_CONFIGURATION = new Configuration();
113114
private static final Processor SAXON_PROCESSOR = new Processor(FnTransform.SAXON_CONFIGURATION);
114115

116+
static class SystemProperties {
117+
private static RetainedStaticContext retainedStaticContext = new RetainedStaticContext(SAXON_CONFIGURATION);
118+
119+
static String get(QName qName) {
120+
return SystemProperty.getProperty(qName.getNamespaceURI(), qName.getLocalPart(), retainedStaticContext);
121+
}
122+
}
123+
115124
private static final Convert.ToSaxon toSaxon = new Convert.ToSaxon() {
116125
@Override
117126
DocumentBuilder newDocumentBuilder() {
@@ -1065,6 +1074,8 @@ private Map<net.sf.saxon.s9api.QName, XdmValue> readParamsMap(final Optional<Map
10651074
baseOutputURI = FnTransform.BASE_OUTPUT_URI.get(xsltVersion, options);
10661075

10671076
serializationParams = FnTransform.SERIALIZATION_PARAMS.get(xsltVersion, options);
1077+
1078+
validateRequestedProperties(FnTransform.REQUESTED_PROPERTIES.get(xsltVersion, options).orElse(new MapType(context)));
10681079
}
10691080

10701081
private DeliveryFormat getDeliveryFormat(final float xsltVersion, final MapType options) throws XPathException {
@@ -1102,6 +1113,38 @@ private String getStylesheetNodeDocumentPath(final MapType options) throws XPath
11021113
}
11031114
return "";
11041115
}
1116+
1117+
private void validateRequestedProperties(final MapType requestedProperties) throws XPathException {
1118+
for (final IEntry<AtomicValue, Sequence> entry : requestedProperties) {
1119+
final AtomicValue key = entry.key();
1120+
if (!Type.subTypeOf(key.getType(), Type.QNAME)) {
1121+
throw new XPathException(ErrorCodes.XPTY0004, "Type error: requested-properties key: " + key.toString() + " is not a QName");
1122+
}
1123+
final Sequence value = entry.value();
1124+
if (!value.hasOne()) {
1125+
throw new XPathException(ErrorCodes.XPTY0004, "Type error: requested-properties " + key.toString() + " does not have a single item value.");
1126+
}
1127+
final Item item = value.itemAt(0);
1128+
final String requiredPropertyValue;
1129+
if (Type.subTypeOf(item.getType(), Type.STRING)) {
1130+
requiredPropertyValue = item.getStringValue();
1131+
} else if (Type.subTypeOf(item.getType(), Type.BOOLEAN)) {
1132+
requiredPropertyValue = ((BooleanValue) item).getValue() ? "yes" : "no";
1133+
} else {
1134+
throw new XPathException(ErrorCodes.XPTY0004,
1135+
"Type error: requested-properties " + key +
1136+
" is not a " + Type.getTypeName(Type.STRING) +
1137+
" or a " + Type.getTypeName(Type.BOOLEAN));
1138+
}
1139+
final String actualPropertyValue = SystemProperties.get(((QNameValue) key).getQName());
1140+
if (!actualPropertyValue.equalsIgnoreCase(requiredPropertyValue)) {
1141+
throw new XPathException(ErrorCodes.FOXT0001,
1142+
"The XSLT processor cannot provide the requested-property: " + key +
1143+
" requested: " + requiredPropertyValue +
1144+
", actual: " + actualPropertyValue);
1145+
}
1146+
}
1147+
}
11051148
}
11061149

11071150
}

exist-core/src/test/xquery/xquery3/fnTransform68.xqm

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,86 @@ declare variable $testTransform:transform-68-xsl-text := document { <xsl:stylesh
2929
xmlns:xs='http://www.w3.org/2001/XMLSchema'
3030
xmlns:saxon='http://saxon.sf.net/'
3131
xmlns:my='http://www.w3.org/fots/fn/transform/myfunctions' version='2.0'>
32+
<xsl:param name='v'/>
3233
<xsl:template name='main'>
33-
<out><xsl:value-of select="saxon:in-summer-time(xs:dateTime('2016-07-01T00:00:00'), 'Europe/London')"/></out>
34+
<out><xsl:value-of select='$v'/></out>
3435
</xsl:template>
3536
</xsl:stylesheet> };
3637

38+
declare
39+
%test:assertError("FOXT0001")
40+
function testTransform:transform-68-supports-dynamic-evaluation() {
41+
let $xsl := $testTransform:transform-68-xsl-text
42+
let $result := fn:transform(map{
43+
"stylesheet-node":$xsl,
44+
"initial-template": fn:QName('','main'),
45+
"delivery-format" : "serialized",
46+
"stylesheet-params": map { QName("","v"): "2" },
47+
"requested-properties" : map{fn:QName('http://www.w3.org/1999/XSL/Transform','supports-dynamic-evaluation'):true()}})
48+
return contains($result?output,">2</out>")
49+
};
50+
51+
declare
52+
%test:assertError("FOXT0001")
53+
function testTransform:transform-68-supports-xalan() {
54+
let $xsl := $testTransform:transform-68-xsl-text
55+
let $result := fn:transform(map{
56+
"stylesheet-node":$xsl,
57+
"initial-template": fn:QName('','main'),
58+
"delivery-format" : "serialized",
59+
"stylesheet-params": map { QName("","v"): "2" },
60+
"requested-properties" : map{fn:QName('http://www.w3.org/1999/XSL/Transform','product-name'):"Xalan"}})
61+
return contains($result?output,">2</out>")
62+
};
63+
3764
declare
3865
%test:assertTrue
39-
function testTransform:transform-68() {
66+
function testTransform:transform-68-supports-saxon() {
4067
let $xsl := $testTransform:transform-68-xsl-text
4168
let $result := fn:transform(map{
4269
"stylesheet-node":$xsl,
4370
"initial-template": fn:QName('','main'),
4471
"delivery-format" : "serialized",
45-
"requested-properties" : map{fn:QName('http://www.w3.org/1999/XSL/Transform','vendor'):'Saxonica'}})
46-
return contains($result?output,">true</out>")
72+
"stylesheet-params": map { QName("","v"): "2" },
73+
"requested-properties" : map{fn:QName('http://www.w3.org/1999/XSL/Transform','product-name'):"SAXON"}})
74+
return contains($result?output,">2</out>")
4775
};
4876

77+
declare
78+
%test:assertTrue
79+
function testTransform:transform-68-vendor-saxonica() {
80+
let $xsl := $testTransform:transform-68-xsl-text
81+
let $result := fn:transform(map{
82+
"stylesheet-node":$xsl,
83+
"initial-template": fn:QName('','main'),
84+
"delivery-format" : "serialized",
85+
"stylesheet-params": map { QName("","v"): "2" },
86+
"requested-properties" : map{fn:QName('http://www.w3.org/1999/XSL/Transform','vendor'):"Saxonica"}})
87+
return contains($result?output,">2</out>")
88+
};
89+
90+
declare
91+
%test:assertError("XPTY0004")
92+
function testTransform:transform-68-vendor-empty() {
93+
let $xsl := $testTransform:transform-68-xsl-text
94+
let $result := fn:transform(map{
95+
"stylesheet-node":$xsl,
96+
"initial-template": fn:QName('','main'),
97+
"delivery-format" : "serialized",
98+
"stylesheet-params": map { QName("","v"): "2" },
99+
"requested-properties" : map{fn:QName('http://www.w3.org/1999/XSL/Transform','vendor'):()}})
100+
return contains($result?output,">2</out>")
101+
};
102+
103+
declare
104+
%test:assertTrue
105+
function testTransform:transform-68-unknown-property() {
106+
let $xsl := $testTransform:transform-68-xsl-text
107+
let $result := fn:transform(map{
108+
"stylesheet-node":$xsl,
109+
"initial-template": fn:QName('','main'),
110+
"delivery-format" : "serialized",
111+
"stylesheet-params": map { QName("","v"): "2" },
112+
"requested-properties" : map{fn:QName('http://www.w3.org/1999/XSL/Transform','wookie'):"Chewbacca"}})
113+
return contains($result?output,">2</out>")
114+
};

0 commit comments

Comments
 (0)