0
votes

I'm still wet behind the ears with XSLT, and I'm having trouble with XSLT templates. I'm trying to make a template to match each indentation level of my XML data, but for some reason, no matter what variation of my apply-templates calls that I try, I'm only able to print the first indentation level... or rather, it's only applying one indentation level, as if it's matching the first pattern that comes up and ignoring the rest. Can someone tell if anything is off about my XSLT syntax? This same method was used for some other data, and worked fine.

<?xml version="1.0" encoding="ISO-8859-1"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" omit-xml-declaration="no" indent="yes" />
    <xsl:strip-space elements="*" />

    <xsl:template match="/">
        <config>
            <xsl:apply-templates match="/Objects/*/*" /> <!-- Matches template #1 -->
            <xsl:apply-templates match="/Objects/*/*/*" /> <!-- Matches template #2 -->
            <xsl:apply-templates match="/Objects/*/*/*/*" /> <!-- Matches template #3 -->
            <xsl:apply-templates match="/Objects/*/*/*/*/*" /> <!-- Matches template #4 -->
            <xsl:apply-templates match="/Objects/*/*/*/*/*/*" /> <!-- Matches template #5 -->
            <xsl:apply-templates match="/Objects/*/*/*/*/*/*/*" /> <!-- Matches template #6 -->
        </config>
    </xsl:template>

    <!-- <xsl:template match="Object">
        <element>
            <typefield>Object</typefield>
            <xsl:call-template name="elementPrinter" />
        </element>
    </xsl:template> -->

    <!-- Begin Template #1 -->
    <xsl:template match="Object/*">
        <element>
            <typefield>
                <xsl:value-of select="@Name" />
            </typefield>
            <xsl:call-template name="elementPrinter" />
        </element>
    </xsl:template>
    <!-- End Template #1 -->

    <!-- Begin Template #2 -->
    <xsl:template match="Object/*/*">
        <element2>
            <typefield>
                <xsl:value-of select="@Name" />
            </typefield>
            <xsl:call-template name="parentIDPrinter" />
            <xsl:call-template name="elementPrinter" />
        </element2>
    </xsl:template>
    <!-- End Template #2 -->

    <!-- Begin Template #3 -->
    <xsl:template match="Object/*/*/*">
        <element>
            <typefield>
                <xsl:value-of select="@Name" />
            </typefield>
            <xsl:call-template name="gparentIDPrinter" />
            <xsl:call-template name="parentIDPrinter" />
            <xsl:call-template name="elementPrinter" />
        </element>
    </xsl:template>
    <!-- End Template #3 -->

    <!-- Begin Template #4 -->
    <xsl:template match="Object/*/*/*/*">
        <element>
            <typefield>
                <xsl:value-of select="@Name" />
            </typefield>
            <xsl:call-template name="gparentIDPrinter" />
            <xsl:call-template name="parentIDPrinter" />
            <xsl:call-template name="elementPrinter" />
        </element>
    </xsl:template>
    <!-- End Template #4 -->

    <!-- Begin Template #5 -->
    <xsl:template match="Object/*/*/*/*/*">
        <element>
            <typefield>
                <xsl:value-of select="@Name" />
            </typefield>
            <xsl:call-template name="gparentIDPrinter" />
            <xsl:call-template name="parentIDPrinter" />
            <xsl:call-template name="elementPrinter" />
        </element>
    </xsl:template>
    <!-- End Template #5 -->

    <!-- Begin Template #6 -->
    <xsl:template match="Object/*/*/*/*/*/*">
        <element>
            <typefield>
                <xsl:value-of select="@Name" />
            </typefield>
            <xsl:call-template name="gparentIDPrinter" />
            <xsl:call-template name="parentIDPrinter" />
            <xsl:call-template name="elementPrinter" />
        </element>
    </xsl:template>
    <!-- End Template #6 -->

    <!-- Prints all elements within the matching node. -->
    <xsl:template name="elementPrinter">
        <xsl:for-each select="*">
            <xsl:if test="text() != ''">
                <xsl:choose>
                    <xsl:when test="@Name">
                        <xsl:variable name="eName">
                            <xsl:value-of select="@Name" />
                        </xsl:variable>

                        <xsl:element name="{$eName}">
                            <xsl:value-of select="text()" />
                        </xsl:element>
                    </xsl:when>
                    <xsl:when test="not(@Name)">
                        <xsl:variable name="eName">
                            <xsl:value-of select="@Type" />
                        </xsl:variable>

                        <xsl:element name="{$eName}">
                            <xsl:value-of select="text()" />
                        </xsl:element>
                    </xsl:when>
                </xsl:choose>
            </xsl:if>
        </xsl:for-each>
    </xsl:template>

    <!-- Prints a tag containing the name of the node's parent. -->
    <xsl:template name="parentIDPrinter">
        <xsl:for-each select="../../*[1]">
            <xsl:choose>
                <xsl:when test="./@Name">
                    <xsl:variable name="pName">
                        <xsl:value-of select="@Name" />
                    </xsl:variable>

                    <xsl:element name="parent">
                        <xsl:value-of select="$pName" />
                    </xsl:element>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:variable name="pName">
                        <xsl:value-of select="@Type" />
                    </xsl:variable>

                    <xsl:element name="parent">
                        <xsl:value-of select="$pName" />
                    </xsl:element>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>
    </xsl:template>

    <!-- Prints a tag containing the name of the node's grandparent. -->
    <xsl:template name="gparentIDPrinter">
        <xsl:for-each select="../../../../*[1]">
            <xsl:choose>
                <xsl:when test="./@Name">
                    <xsl:variable name="pName">
                        <xsl:value-of select="@Name" />
                    </xsl:variable>

                    <xsl:element name="grandparent">
                        <xsl:value-of select="$pName" />
                    </xsl:element>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:variable name="pName">
                        <xsl:value-of select="@Type" />
                    </xsl:variable>

                    <xsl:element name="grandparent">
                        <xsl:value-of select="$pName" />
                    </xsl:element>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

Unfortunately, I can't post the sample data, but the format is pretty much just this.

<Objects>
    <Object>
        <Property Name="whatever">
            <Property Name="whatever1.1">whatever</Property>
            <Property Name="whatever1.2">whatever2</Property>
        </Property>
        <Property Name="whatever">
            <Property Name="whatever1.1">whatever</Property>
            <Property Name="whatever1.2">whatever2</Property>
        </Property>
    </Object>
    <Object>
        <Property Name="whatever">
            <Property Name="whatever1.1">whatever</Property>
            <Property Name="whatever1.2">whatever2</Property>
        </Property>
        <Property Name="whatever">
            <Property Name="whatever1.1">whatever</Property>
            <Property Name="whatever1.2">whatever2</Property>
        </Property>
    </Object>
</Objects>

When I leave just the first apply-templates call in, it gives me the first layer, which I should expect, which is all the first-layer Property tags... but if I put in the next line, which should match template #2, all it does is print the exact same data from template 1's pattern right after it, instead of matching template 2's pattern data. Why is it ignoring my other templates?

1

1 Answers

2
votes

Your XSLT is invalid - apply-templates has a select attribute, not a match attribute. Once that is fixed, it produces the following result:

<config>
  <element>
    <typefield>whatever</typefield>
    <whatever1.1>whatever</whatever1.1>
    <whatever1.2>whatever2</whatever1.2>
  </element>
  <element>
    <typefield>whatever</typefield>
    <whatever1.1>whatever</whatever1.1>
    <whatever1.2>whatever2</whatever1.2>
  </element>
  <element>
    <typefield>whatever</typefield>
    <whatever1.1>whatever</whatever1.1>
    <whatever1.2>whatever2</whatever1.2>
  </element>
  <element>
    <typefield>whatever</typefield>
    <whatever1.1>whatever</whatever1.1>
    <whatever1.2>whatever2</whatever1.2>
  </element>
  <element2>
    <typefield>whatever1.1</typefield>
    <parent>whatever</parent>
  </element2>
  <element2>
    <typefield>whatever1.2</typefield>
    <parent>whatever</parent>
  </element2>
  <element2>
    <typefield>whatever1.1</typefield>
    <parent>whatever</parent>
  </element2>
  <element2>
    <typefield>whatever1.2</typefield>
    <parent>whatever</parent>
  </element2>
  <element2>
    <typefield>whatever1.1</typefield>
    <parent>whatever</parent>
  </element2>
  <element2>
    <typefield>whatever1.2</typefield>
    <parent>whatever</parent>
  </element2>
  <element2>
    <typefield>whatever1.1</typefield>
    <parent>whatever</parent>
  </element2>
  <element2>
    <typefield>whatever1.2</typefield>
    <parent>whatever</parent>
  </element2>
</config>

As you can see here, your second template is being used. There are 8 instances of <element2>. Perhaps you didn't notice them because they occur after all of the <element>s. The reason all of the <elements> appear first is that you apply templates to all of the first-level <Property> nodes first, and then apply templates to the next level. If you want each node's children to be listed after their parents, you should write your XSLT like this:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" omit-xml-declaration="no" indent="yes" />
  <xsl:strip-space elements="*" />

  <xsl:template match="/">
    <config>
      <xsl:apply-templates select="/Objects/*/*" />
      <!-- Matches template #1 -->
    </config>
  </xsl:template>

  <!-- <xsl:template match="Object">
        <element>
            <typefield>Object</typefield>
            <xsl:call-template name="elementPrinter" />
        </element>
    </xsl:template> -->

  <!-- Begin Template #1 -->
  <xsl:template match="Object/*">
    <element>
      <typefield>
        <xsl:value-of select="@Name" />
      </typefield>
      <xsl:call-template name="elementPrinter" />
    </element>
    <xsl:apply-templates select="*" />
  </xsl:template>
  <!-- End Template #1 -->

  <!-- Begin Template #2 -->
  <xsl:template match="Object/*/*">
    <element2>
      <typefield>
        <xsl:value-of select="@Name" />
      </typefield>
      <xsl:call-template name="parentIDPrinter" />
      <xsl:call-template name="elementPrinter" />
    </element2>
    <xsl:apply-templates select="*" />
  </xsl:template>
  <!-- End Template #2 -->

  <!-- Begin Template #3 -->
  <xsl:template match="Object/*/*/*">
    <element>
      <typefield>
        <xsl:value-of select="@Name" />
      </typefield>
      <xsl:call-template name="gparentIDPrinter" />
      <xsl:call-template name="parentIDPrinter" />
      <xsl:call-template name="elementPrinter" />
    </element>
    <xsl:apply-templates select="*" />
  </xsl:template>
  <!-- End Template #3 -->

  <!-- Begin Template #4 -->
  <xsl:template match="Object/*/*/*/*">
    <element>
      <typefield>
        <xsl:value-of select="@Name" />
      </typefield>
      <xsl:call-template name="gparentIDPrinter" />
      <xsl:call-template name="parentIDPrinter" />
      <xsl:call-template name="elementPrinter" />
    </element>
    <xsl:apply-templates select="*" />
  </xsl:template>
  <!-- End Template #4 -->

  <!-- Begin Template #5 -->
  <xsl:template match="Object/*/*/*/*/*">
    <element>
      <typefield>
        <xsl:value-of select="@Name" />
      </typefield>
      <xsl:call-template name="gparentIDPrinter" />
      <xsl:call-template name="parentIDPrinter" />
      <xsl:call-template name="elementPrinter" />
    </element>
    <xsl:apply-templates select="*" />
  </xsl:template>
  <!-- End Template #5 -->

  <!-- Begin Template #6 -->
  <xsl:template match="Object/*/*/*/*/*/*">
    <element>
      <typefield>
        <xsl:value-of select="@Name" />
      </typefield>
      <xsl:call-template name="gparentIDPrinter" />
      <xsl:call-template name="parentIDPrinter" />
      <xsl:call-template name="elementPrinter" />
    </element>
    <xsl:apply-templates select="*" />
  </xsl:template>
  <!-- End Template #6 -->

  <!-- Prints all elements within the matching node. -->
  <xsl:template name="elementPrinter">
    <xsl:for-each select="*">
      <xsl:if test="text() != ''">
        <xsl:choose>
          <xsl:when test="@Name">
            <xsl:variable name="eName">
              <xsl:value-of select="@Name" />
            </xsl:variable>

            <xsl:element name="{$eName}">
              <xsl:value-of select="text()" />
            </xsl:element>
          </xsl:when>
          <xsl:when test="not(@Name)">
            <xsl:variable name="eName">
              <xsl:value-of select="@Type" />
            </xsl:variable>

            <xsl:element name="{$eName}">
              <xsl:value-of select="text()" />
            </xsl:element>
          </xsl:when>
        </xsl:choose>
      </xsl:if>
    </xsl:for-each>
  </xsl:template>

  <!-- Prints a tag containing the name of the node's parent. -->
  <xsl:template name="parentIDPrinter">
    <xsl:for-each select="../../*[1]">
      <xsl:choose>
        <xsl:when test="./@Name">
          <xsl:variable name="pName">
            <xsl:value-of select="@Name" />
          </xsl:variable>

          <xsl:element name="parent">
            <xsl:value-of select="$pName" />
          </xsl:element>
        </xsl:when>
        <xsl:otherwise>
          <xsl:variable name="pName">
            <xsl:value-of select="@Type" />
          </xsl:variable>

          <xsl:element name="parent">
            <xsl:value-of select="$pName" />
          </xsl:element>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>

  <!-- Prints a tag containing the name of the node's grandparent. -->
  <xsl:template name="gparentIDPrinter">
    <xsl:for-each select="../../../../*[1]">
      <xsl:choose>
        <xsl:when test="./@Name">
          <xsl:variable name="pName">
            <xsl:value-of select="@Name" />
          </xsl:variable>

          <xsl:element name="grandparent">
            <xsl:value-of select="$pName" />
          </xsl:element>
        </xsl:when>
        <xsl:otherwise>
          <xsl:variable name="pName">
            <xsl:value-of select="@Type" />
          </xsl:variable>

          <xsl:element name="grandparent">
            <xsl:value-of select="$pName" />
          </xsl:element>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Notice how I added an <xsl:apply-templates select="*" /> to the end of your 6 templates. I think there are other ways to improve this XSLT, but since it seems to be just a sample, I'll leave that for another question.

I think your logic is also broken here:

<xsl:for-each select="../../*[1]">

and here:

<xsl:for-each select="../../../../*[1]">

If you want to switch to the context of the parent and grandparent, you should use:

<xsl:for-each select="..">

and

<xsl:for-each select="../..">