Search code examples
xsltreferenceintegerkeygenerated

XSLT: Generating integer IDs and reference them having string keys on the input


I need to generate integer IDs for products and than reference related products by those integer IDs in the output. On the input I have string keys representing this relationship. Thank you for your help.

Input:

<root>
  <products>
    <product>
      <!-- a unique string key of this node between the other product nodes -->
      <stringKey>AppleRef</stringKey>
      <Name>Apple</Name>
      <relatedProducts>
        <!-- a reference to product/StringKey of Orange -->
        <relatedProductStringKey>OrangeRef</relatedProductStringKey>
        <!-- other related products may follow -->
      </relatedProducts>
    </product>
    <product>
      <stringKey>OrangeRef</stringKey>
      <Name>Orange</Name>
      <relatedProducts>
        <relatedProductStringKey>AppleRef</relatedProductStringKey>
      </relatedProducts>
    </product>
  </products>
</root>

Expected output:

<root>
  <products>
    <P>
      <ProductInfo>
        <!-- a unique integer ID of this node between the other ProductsInfo nodes -->
        <ProductID>0</ProductID>
        <ProductRef>AppleRef</ProductRef>
        <ProductName>Apple</ProductName>
      </ProductInfo>
      <R>
        <ProductRelatedInfo>

          <!-- a unique integer ID of this node between the other ProductRelatedInfo nodes -->
          <RelatedID>0</RelatedID>

          <!-- a reference to ProductInfo/ProductID of Orange -->
          <RelatedProductID>1</RelatedProductID>

        </ProductRelatedInfo>
        <!-- other related products may follow -->

      </R>
    </P>
    <P>
      <ProductInfo>
        <ProductID>1</ProductID>
        <ProductRef>OrangeRef</ProductRef>
        <ProductName>Orange</ProductName>
      </ProductInfo>
      <R>
        <ProductRelatedInfo>
          <RelatedID>1</RelatedID>
          <RelatedProductID>0</RelatedProductID>
        </ProductRelatedInfo>
      </R>
    </P>
  </products>
</root>

Solution

  • This is easy to do using a key. For example, the following stylesheet:

    XSLT 1.0

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:key name="product" match="product" use="stringKey" />
    
    <!-- identity transform -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="stringKey">
        <ProductID><xsl:value-of select="count(../preceding-sibling::product)"/></ProductID>
        <ProductRef><xsl:value-of select="."/></ProductRef>
    </xsl:template>
    
    <xsl:template match="relatedProductStringKey">
        <RelatedProductID><xsl:value-of select="count(key('product', .)/preceding-sibling::product)"/></RelatedProductID>
    </xsl:template>
    
    </xsl:stylesheet>
    

    when applied to your input, will return:

    <?xml version="1.0" encoding="UTF-8"?>
    <root>
       <products>
          <product>
             <ProductID>0</ProductID>
             <ProductRef>AppleRef</ProductRef>
             <Name>Apple</Name>
             <relatedProducts>
                <RelatedProductID>1</RelatedProductID>
             </relatedProducts>
          </product>
          <product>
             <ProductID>1</ProductID>
             <ProductRef>OrangeRef</ProductRef>
             <Name>Orange</Name>
             <relatedProducts>
                <RelatedProductID>0</RelatedProductID>
             </relatedProducts>
          </product>
       </products>
    </root>
    

    If you prefer a meaningless, though not necessarily numeric ID, you might prefer the simpler:

    XSLT 1.0

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:key name="product" match="product" use="stringKey" />
    
    <!-- identity transform -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="stringKey">
        <ProductID><xsl:value-of select="generate-id(..)"/></ProductID>
        <ProductRef><xsl:value-of select="."/></ProductRef>
    </xsl:template>
    
    <xsl:template match="relatedProductStringKey">
        <RelatedProductID><xsl:value-of select="generate-id(key('product', .))"/></RelatedProductID>
    </xsl:template>
    
    </xsl:stylesheet>
    

    The exact result depends on the processor, for example Saxon might return:

    <?xml version="1.0" encoding="UTF-8"?>
    <root>
       <products>
          <product>
             <ProductID>d0e3</ProductID>
             <ProductRef>AppleRef</ProductRef>
             <Name>Apple</Name>
             <relatedProducts>
                <RelatedProductID>d0e11</RelatedProductID>
             </relatedProducts>
          </product>
          <product>
             <ProductID>d0e11</ProductID>
             <ProductRef>OrangeRef</ProductRef>
             <Name>Orange</Name>
             <relatedProducts>
                <RelatedProductID>d0e3</RelatedProductID>
             </relatedProducts>
          </product>
       </products>
    </root>
    

    while libxslt will produce something like:

    <?xml version="1.0" encoding="UTF-8"?>
    <root>
      <products>
        <product>
          <ProductID>idp116928</ProductID>
          <ProductRef>AppleRef</ProductRef>
          <Name>Apple</Name>
          <relatedProducts>
            <RelatedProductID>idp1506944</RelatedProductID>
          </relatedProducts>
        </product>
        <product>
          <ProductID>idp1506944</ProductID>
          <ProductRef>OrangeRef</ProductRef>
          <Name>Orange</Name>
          <relatedProducts>
            <RelatedProductID>idp116928</RelatedProductID>
          </relatedProducts>
        </product>
      </products>