We have an XML file which we use to produce another XML file through XSL 1.0 (using the exslt.org/common extension).
The input XML file uses namespaces, and the result is not what we expected. The input XML file is:
<dataTypeSet xsi:schemaLocation="http://www.aviation-ia.com/aeec/SupportFiles/schema-6/widgetDefn.xsd widgetDefn.xsd" xmlns="http://www.aviation-ia.com/aeec/SupportFiles/schema-6/widgetDefn.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataTypeList xsi:schemaLocation="http://www.aviation-ia.com/aeec/SupportFiles/schema-6/widgetDefn.xsd widgetDefn.xsd">
<baseType name="uchar" typeRef="uchar" />
<baseType name="ushort" typeRef="ushort" />
<baseEnumType name="ubyteEnum" typeRef="uchar" />
<baseEnumType name="ushortEnum" typeRef="ushort" />
<ubyteEnum name="A661_Bool" xsi:schemaLocation="http://www.aviation-ia.com/aeec/SupportFiles/schema-6/widgetDefn.xsd widgetDefn.xsd" />
<ushortEnum name="A661_Picture" xsi:schemaLocation="http://www.aviation-ia.com/aeec/SupportFiles/schema-6/widgetDefn.xsd widgetDefn.xsd" />
</dataTypeList>
<dataTypeList>
<struct xsi:schemaLocation="http://www.aviation-ia.com/aeec/SupportFiles/schema-6/widgetDefn.xsd widgetDefn.xsd" name="A661_EntryPopUpStruct">
<field name="Enable" typeRef="A661_Bool" />
<field name="Picture" typeRef="A661_Picture" />
</struct>
</dataTypeList>
</dataTypeSet>
The schema this XML file uses is:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:a661wd="http://www.aviation-ia.com/aeec/SupportFiles/schema-6/widgetDefn.xsd"
targetNamespace="http://www.aviation-ia.com/aeec/SupportFiles/schema-6/widgetDefn.xsd"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="http://www.w3.org/2001/03/xml.xsd"/>
<xs:simpleType name="baseTypeDefinitionsSimple">
<xs:restriction base="xs:token">
<xs:enumeration value="uchar"/>
<xs:enumeration value="ushort"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="ubyteEnumType">
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
<xs:complexType name="ushortEnumType">
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
<xs:element name="dataTypeList" type="a661wd:dataTypesType"/>
<xs:complexType name="struct">
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="field" type="a661wd:fieldType"/>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
<xs:complexType name="fieldType">
<xs:attribute name="name" type="xs:string" use="required"/>
<xs:attribute name="typeRef" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="baseBaseType">
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="typeRef" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="dataTypesType">
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:choice>
<xs:element name="struct" type="a661wd:struct"/>
<xs:element name="baseType" type="a661wd:baseBaseType"/>
<xs:element name="baseEnumType" type="a661wd:baseBaseType"/>
<xs:element name="ubyteEnum" type="a661wd:ubyteEnumType"/>
<xs:element name="ushortEnum" type="a661wd:ushortEnumType"/>
</xs:choice>
</xs:sequence>
<xs:attribute ref="xml:base"/>
</xs:complexType>
<xs:element name="dataTypeSet">
<xs:complexType>
<xs:sequence>
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element ref="a661wd:dataTypeList"/>
</xs:sequence>
</xs:sequence>
<xs:attribute ref="xml:base"/>
</xs:complexType>
</xs:element>
</xs:schema>
The XSL transform file is:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:wd="http://www.aviation-ia.com/aeec/SupportFiles/schema-6/widgetDefn.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<!-- VARIABLE: g_baseEnumTypes nodeset - the base enum type types e.g. ubyteEnum, ushortEnum -->
<xsl:variable name="g_baseEnumTypes"
select="wd:dataTypeSet/wd:dataTypeList/wd:baseEnumType" />
<xsl:variable name="g_baseEnumTypeNodeNames">
<xsl:for-each select="$g_baseEnumTypes">
<xsl:text>local-name()='</xsl:text>
<xsl:value-of select="@name" disable-output-escaping="yes"/>
<xsl:text>'</xsl:text>
<xsl:if test="position() != last()">
<xsl:text> or </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="g_baseEnumTypeNodeNamesString" select="substring(exsl:node-set($g_baseEnumTypeNodeNames)[1],1,string-length(exsl:node-set($g_baseEnumTypeNodeNames)[1]))"/>
<xsl:template match="/">
<xsl:element name="docDetails">
<xsl:element name="docGenDataTypes">
<xsl:element name="enumTypes">
<xsl:for-each select="$g_baseEnumTypes">
<xsl:variable name="baseEnumNode" select="."/>
<xsl:for-each select="//wd:dataTypeSet/wd:dataTypeList/wd:*[local-name()=current()/@name]">
<xsl:call-template name="writeEnumDefinition">
<xsl:with-param name="baseEnumType" select="$baseEnumNode"/>
</xsl:call-template>
</xsl:for-each>
</xsl:for-each>
</xsl:element>
<xsl:element name="structTypes">
<xsl:for-each select="//wd:dataTypeSet/wd:dataTypeList/wd:struct">
<xsl:call-template name="writeStructDefinition"/>
</xsl:for-each>
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
<!-- Override default xslt template match rule - prevent copying over of text nodes-->
<xsl:template match="text( )"/>
<!-- Template: locate alias type with matching name - if found recurse, else found actual root type -->
<xsl:template name="recurseDataType">
<xsl:param name="searchForDataType"/>
<xsl:call-template name="underlyingTypeDetails">
<xsl:with-param name="searchForDataType" select="$searchForDataType"/>
</xsl:call-template>
</xsl:template>
<!-- Template: locate underlying data type details with matching name. -->
<!-- e.g. aliasType chain will eventually resolve to a concrete type with a defined size in bits. -->
<xsl:template name="underlyingTypeDetails">
<xsl:param name="searchForDataType"/>
<!-- TODO performance enhancement - nest tests in order of proability of match-->
<xsl:variable name="foundBaseTypeNode"
select="//wd:dataTypeSet/wd:dataTypeList/wd:baseType[@name=$searchForDataType]" />
<xsl:message>template underlyingTypeDetails with <xsl:value-of select="$searchForDataType"/></xsl:message>
<xsl:choose>
<!-- When got base type def -->
<xsl:when test="$foundBaseTypeNode">
<!-- Copy found node, attributes and children -->
<xsl:message>template underlyingTypeDetails with <xsl:value-of select="$searchForDataType"/></xsl:message>
<xsl:apply-templates select="$foundBaseTypeNode" mode="copyAndChangeNS"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="foundEnumTypeNode"
select="//wd:dataTypeSet/wd:dataTypeList/wd:*[$g_baseEnumTypeNodeNamesString][@name=$searchForDataType]"/>
<xsl:choose>
<!-- When got base type def -->
<xsl:when test="$foundEnumTypeNode">
<xsl:message>found foundEnumTypeNode</xsl:message>
<!-- context is the BASE enum definition -->
<xsl:call-template name="writeEnumBaseTypeDetails">
<xsl:with-param name="enumTypeDefnNode" select="$foundEnumTypeNode"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Template: For a BASE Enum Type Definition scope - Create enumerated type details node. -->
<xsl:template name="writeEnumBaseTypeDetails">
<xsl:param name ="enumTypeDefnNode"/>
<xsl:variable name ="enumTypeName" select ="local-name(exsl:node-set($enumTypeDefnNode)[1])"/>
<xsl:variable name="enumBaseType" select="exsl:node-set($g_baseEnumTypes)[@name=$enumTypeName]"/>
<xsl:element name="enumType">
<xsl:attribute name="name">
<xsl:value-of select="exsl:node-set($enumTypeDefnNode)[1]//@name"/>
</xsl:attribute>
<xsl:attribute name="typeRef">
<xsl:value-of select="exsl:node-set($enumBaseType)[1]//@typeRef"/>
<xsl:message>template writeEnumBaseTypeDetails typeRef with <xsl:value-of select="exsl:node-set($enumBaseType)[1]//@typeRef"/></xsl:message>
</xsl:attribute>
</xsl:element>
</xsl:template>
<!-- Template: Write out enumerate type definition details - required to record possible (range-of) values for some enumerated widget parameters -->
<!-- Called for all enumerates - whether needed by widget parameter documentation or not - as this is simplest to implement. -->
<xsl:template name="writeEnumDefinition">
<xsl:param name="baseEnumType"/>
<xsl:element name="enumTypeDefn">
<xsl:attribute name="name">
<xsl:value-of select="@name"/>
</xsl:attribute>
<xsl:attribute name="enumBase">
<xsl:value-of select="local-name()"/>
</xsl:attribute>
</xsl:element>
</xsl:template>
<!-- Template: Write out structure type definition details - required for complex parameter type details. -->
<!-- recurse structure field types - currently only supports simple types (not structures of structures) -->
<xsl:template name="writeStructDefinition">
<xsl:element name="structTypeDefn">
<xsl:attribute name="name">
<xsl:value-of select="@name"/>
</xsl:attribute>
<xsl:for-each select="wd:*">
<xsl:choose>
<xsl:when test="local-name()='field'">
<xsl:element name="field">
<!-- Copy found node, attributes and children -->
<xsl:attribute name="name">
<xsl:value-of select="@name"/>
<xsl:message>Look for <xsl:value-of select="@name"/></xsl:message>
</xsl:attribute>
<xsl:attribute name="typeRef">
<xsl:value-of select="@typeRef"/>
</xsl:attribute>
<xsl:call-template name="recurseDataType">
<xsl:with-param name="searchForDataType" select="@typeRef"/>
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="." mode="copyAndChangeNS"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:element>
</xsl:template>
<!-- Template: Copy node (and atributes and recurse through children) stripping out namespace details. -->
<xsl:template match="*" mode="copyAndChangeNS">
<xsl:element name="{local-name()}">
<!-- copy all the attributes from the matched element, stripping schema xsi:schemaLocation and supporting namespace nodes -->
<xsl:copy-of select="@*[not(namespace-uri() = 'http://www.w3.org/2001/XMLSchema-instance')]"/>
<xsl:apply-templates select="node()" mode="copyAndChangeNS"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
I am using SAXON (the last HE version), but I have the same result with Xalan. The Java code which I use to trigger the transform is:
public class XSLTTest3 {
private File samples3 = null;
private final File inputFile;
private final File outputFile;
private final File xslFile;
public XSLTTest3() {
File dir = new File(System.getProperty("user.dir"));
samples3 = new File(dir, "samples3");
inputFile = new File(samples3, "inputFile.xml");
outputFile = new File(samples3, "outputFile.xml");
xslFile = new File(samples3, "xslTest.xsl");
}
public void applyXSLT() {
try {
TransformerFactory factory = TransformerFactory.newInstance("net.sf.saxon.jaxp.SaxonTransformerFactory", Thread.currentThread().getContextClassLoader());
Transformer transformer = factory.newTransformer(new StreamSource(xslFile));
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dFactory.newDocumentBuilder();
InputSource xslInputSource = new InputSource(new FileInputStream(inputFile));
xslInputSource.setSystemId(inputFile.toURI().toString());
Document xslDoc = dBuilder.parse(xslInputSource);
DOMSource xslDomSource = new DOMSource(xslDoc);
transformer.transform(xslDomSource, new StreamResult(new FileOutputStream(outputFile)));
} catch (Exception ex) {
ex.printStackTrace();
}
}
public static void main(String[] args) {
XSLTTest3 xlstTest = new XSLTTest3();
xlstTest.applyXSLT();
}
}
I expected to have this result:
<docDetails>
<docGenDataTypes>
<enumTypes>
<enumTypeDefn name="A661_Bool" enumBase="ubyteEnum"/>
<enumTypeDefn name="A661_Picture" enumBase="ushortEnum"/>
</enumTypes>
<structTypes>
<structTypeDefn name="A661_EntryPopUpStruct">
<field name="Enable" typeRef="A661_Bool">
<enumType name="A661_Bool" typeRef="uchar"/>
</field>
<field name="Picture" typeRef="A661_Picture">
<enumType name="A661_Picture" typeRef="ushort"/>
</field>
</structTypeDefn>
</structTypes>
</docGenDataTypes>
</docDetails>
But I have:
<docDetails>
<docGenDataTypes>
<enumTypes>
<enumTypeDefn name="A661_Bool" enumBase="ubyteEnum"/>
<enumTypeDefn name="A661_Picture" enumBase="ushortEnum"/>
</enumTypes>
<structTypes>
<structTypeDefn name="A661_EntryPopUpStruct">
<field name="Enable" typeRef="A661_Bool">
<enumType name="A661_Bool" typeRef="uchar"/>
</field>
<field name="Picture" typeRef="A661_Picture">
<enumType name="A661_Picture" typeRef=""/>
</field>
</structTypeDefn>
</structTypes>
</docGenDataTypes>
</docDetails>
The way this XSL works is looking for a type in the XML file which has the same name as the type referenced in the Field
, and emit its base type and size.
The problem is with the exsl:node-set($enumBaseType)[1]//@typeRef
expression in line 197 of the XSL script. Strangely it works without any problem for the first Enable
Field
(type A661_Bool
), but the script using this same function is not able to find the next Picture
Field
(type A661_Picture
), even if the type above is defined exactly in the same way.
If I remove all the namespace stuff, everything works fine (but I can't do it, because the real use-case uses more than one Schema and relies on these schemas to validate the input content).
We used MSXML before, and it worked correctly with the same XSL script, so I suspect: either the way I use the script in Java is wrong or there is a difference in the way MSXML and SAXON / Xalan works.
What is wrong in the exsl:node-set($enumBaseType)[1]//@typeRef
expression (it looks like the problem here)?
I don't see the need to use exsl:node-set
in some of those expressions (as no result tree fragments are constructed) and both XslCompiledTransform as well as Saxon 10 and 11 with the simplification of
<!-- Template: For a BASE Enum Type Definition scope - Create enumerated type details node. -->
<xsl:template name="writeEnumBaseTypeDetails">
<xsl:param name ="enumTypeDefnNode"/>
<xsl:variable name ="enumTypeName" select ="local-name(exsl:node-set($enumTypeDefnNode)[1])"/>
<xsl:variable name="enumBaseType" select="$g_baseEnumTypes[@name=$enumTypeName]"/>
<xsl:element name="enumType">
<xsl:attribute name="name">
<xsl:value-of select="exsl:node-set($enumTypeDefnNode)[1]//@name"/>
</xsl:attribute>
<xsl:attribute name="typeRef">
<xsl:value-of select="$enumBaseType/@typeRef"/>
</xsl:attribute>
</xsl:element>
</xsl:template>
give the result
<docDetails>
<docGenDataTypes>
<enumTypes>
<enumTypeDefn name="A661_Bool" enumBase="ubyteEnum"/>
<enumTypeDefn name="A661_Picture" enumBase="ushortEnum"/>
</enumTypes>
<structTypes>
<structTypeDefn name="A661_EntryPopUpStruct">
<field name="Enable" typeRef="A661_Bool">
<enumType name="A661_Bool" typeRef="uchar"/>
</field>
<field name="Picture" typeRef="A661_Picture">
<enumType name="A661_Picture" typeRef="ushort"/>
</field>
</structTypeDefn>
</structTypes>
</docGenDataTypes>
</docDetails>