简体   繁体   中英

node-set() does not select some elements in my XSL Stylesheet

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)?

If you cut down the problem to something simpler, it would be easier to find the bug.

You speak of the expression exsl:node-set($enumBaseType)[1]//@typeRef on line 197, but I can only find that expression on line 154.

Unfortunately, although exsl:node-set is very widely used, the specification at http://exslt.org/exsl/functions/node-set/index.html is very vague. The original purpose of the function was to convert a result tree fragment to an equivalent document node; the spec also suggests that it can be used to convert a string to a text node, and it then gives an example in which the argument value is a set containing 5 element nodes and the output is -- well we don't really know, except that count() applied to the output returns 5. Perhaps it's returning the 5 supplied elements unchanged, perhaps it's returning copies of them, perhaps it's returning 5 document nodes; we can only guess. It doesn't help that the example has a comment saying that it's applying exsl:node-set to a result tree fragment, it then declares a result tree fragment variable $tree , and it then proceeds to apply exsl:node-set() to something completely different. The spec is a mess, and it's known that implementations vary widely as a result. I would recommend using the function ONLY to convert a result tree fragment to a node-set. It's very likely that you're relying on an MSXML interpretation of the spec where Saxon and Xalan have interpreted it different.

Result tree fragments don't exist in XSLT 2.0+, so the function has lost its purpose. Saxon implements it only so that it can run 1.0 stylesheets that use it, and the Saxon implementation simply returns its argument unchanged. So the instruction <xsl:value-of select="exsl:node-set($enumBaseType)[1]//@typeRef"/> reduces to <xsl:value-of select="$enumBaseType[1]//@typeRef"/> .

I'm afraid there's too much code here for me to get into debugging it in detail.

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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM