tags:

views:

74

answers:

2

Hi!

So I have a really confusing problem on my hands..

It seems as though that the entire XML data hierarchy is not being searched through when using an XPath expression in XSL.

Some dummy XML data:

<pets name="myPets" NUM="2">
    <dog name="allMyDogs" NUM="5">
        <dog name="Frank"  NUM="3"/>
        <dog name="Spot"  NUM="4"/>
        <dog name="Rover"  NUM="1"/>
        <dog name="Rupert" NUM="6"/>
        <cat name="Lucy"  NUM="4"/>
    </dog>
    <cat name="allMyCats" NUM="4">
        <cat name="Simba" NUM="4"/>
        <cat name="Princess"  NUM="5"/>
        <cat name="Fluffy" NUM="1"/>
        <cat name="Lucy"  NUM="3"/>
        <cat name="Lucy"  NUM="35"/>
        <cat name="Lucy"  NUM="6"/>
        <cat name="Lucy" NUM="1"/>
    </cat>
    <cat name="Lucy" NUM="9"/>
</pets>

The following is the portion of XSLT code I believe is causing the issue:

 <xsl:key name="elem_key" match="elem" use="concat(@key, .)" />

  <xsl:variable name="all_data">
    <xsl:apply-templates select="*">
      <xsl:sort select="name()" />
    </xsl:apply-templates>
  </xsl:variable>

  <xsl:template match="//*[@NUM&lt;=4]">
    <elem key="{name()}">
      <xsl:copy-of select="@*" />
      <xsl:for-each select="@*">
        <xsl:sort select="name()" />
        <attribute>|<xsl:value-of select="name()" />|</attribute>
      </xsl:for-each>
    </elem>
  </xsl:template>

  <xsl:template match="/">
    <html>
      <body>
        <xsl:for-each select="msxsl:node-set($all_data)">
              <xsl:for-each select="*[generate-id()=generate-id(key('elem_key',concat(@key, .))[1])]">
                <table >
                  <tr>
                    <td>Element Name</td>
                    <xsl:for-each select="*">
                      <td>
                        <xsl:value-of select="translate(.,'|','')" />
                      </td>
                    </xsl:for-each>
                  </tr>
                  <xsl:for-each select="key('elem_key', concat(@key, .))">
                    <xsl:variable name="curr_elem" select="." />
                    <tr>
                      <td>
                        <xsl:value-of select="@key" />
                      </td>
                      <xsl:for-each select="*">
                        <td >
                          <xsl:value-of select="$curr_elem/@*[name()=translate(current(),'|','')]" />
                        </td>
                      </xsl:for-each>
                    </tr>
                  </xsl:for-each>
                </table>
                <p />
              </xsl:for-each>
        </xsl:for-each>
      </body>
    </html>
  </xsl:template>

The XPath expression used:

//*[@NUM&lt;=4]

(Above should generate many results)

The incorrect results I get:

Element Name    name     NUM
pets            myPets   2

As you can see it seems to stop at the root.

If I change the XPath to:

//*[@NUM=4]

I get these incorrect results:

Element Name  name    NUM
dog        Spot      4


Element Name  name      NUM
cat        Lucy      4


Element Name  name      NUM
cat        allMyCats   4

What appears to happen is that it will stop searching down into the hierarchy once it has found a match. The first two (Spot and Lucy) are correct, but then it stopped at allMyCats, when there is a child node of allMyCats (Simba) that has a NUM of 4.

Can anyone help me fix this code so that it returns the correct results? I'm quite frustrated! :(

Thanks!

A: 

*[generate-id()=generate-id(key('elem_key',concat(@key, .))[1])] You've a key doing a selection that matches a few cases, and then you ask for just the first with [1]

I'm not clear on what layout you are wanting as the correct results to advise further.

Jon Hanna
+4  A: 

Just change:

  <xsl:variable name="all_data">
    <xsl:apply-templates select="*">
      <xsl:sort select="name()" />
    </xsl:apply-templates>
  </xsl:variable>

To:

  <xsl:variable name="all_data">
    <xsl:apply-templates select="*/*">
      <xsl:sort select="name()" />
    </xsl:apply-templates>
  </xsl:variable>

The first (of many) problem is:

<xsl:apply-templates select="*">

selects all children elements of the current node. The current node is / and it has only one child -- the top element pets.

You actually want to collect the data for all children of pets.

There are other problems, but I don't have the space and time to address them here.

The complete corrected code is below:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:msxsl="urn:schemas-microsoft-com:xslt"
version="1.0">

 <xsl:key name="elem_key" match="elem" 
      use="concat(@key, .)" />

  <xsl:variable name="all_data">
    <xsl:apply-templates select="*//*">
      <xsl:sort select="name()" />
    </xsl:apply-templates>
  </xsl:variable>

  <xsl:template match="*[@NUM&lt;=4]">
    <elem key="{name()}">
      <xsl:copy-of select="@*" />
      <xsl:for-each select="@*">
        <xsl:sort select="name()" />
        <attribute>|<xsl:value-of 
             select="concat(name(),'=',.)" />|</attribute>
      </xsl:for-each>
    </elem>
  </xsl:template>

  <xsl:template match="/">
    <html>
      <body>
        <xsl:for-each select="msxsl:node-set($all_data)">
              <xsl:for-each select=
               "*[generate-id()
                 =
                  generate-id(key('elem_key',concat(@key, .))[1])
                 ]">
                <table >
                  <tr>
                    <td>Element Name</td>
                    <xsl:for-each select="*">
                      <td>
                        <xsl:value-of select=
                          "substring-before(translate(.,'|',''),'=')" />
                      </td>
                    </xsl:for-each>
                  </tr>
                    <tr>
                      <td>
                        <xsl:value-of select="@key" />
                      </td>
                      <xsl:for-each select="*">
                        <td>
                          <xsl:value-of select=
                          "substring-after
                              (translate(current(),'|',''),
                               '='
                               )"/>
                        </td>
                      </xsl:for-each>
                    </tr>
                </table>
                <p />
              </xsl:for-each>
        </xsl:for-each>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the provided XML document:

<pets name="myPets" NUM="2">
    <dog name="allMyDogs" NUM="5">
        <dog name="Frank"  NUM="3"/>
        <dog name="Spot"  NUM="4"/>
        <dog name="Rover"  NUM="1"/>
        <dog name="Rupert" NUM="6"/>
        <cat name="Lucy"  NUM="4"/>
    </dog>
    <cat name="allMyCats" NUM="4">
        <cat name="Simba" NUM="4"/>
        <cat name="Princess"  NUM="5"/>
        <cat name="Fluffy" NUM="1"/>
        <cat name="Lucy"  NUM="3"/>
        <cat name="Lucy"  NUM="35"/>
        <cat name="Lucy"  NUM="6"/>
        <cat name="Lucy" NUM="1"/>
    </cat>
    <cat name="Lucy" NUM="9"/>
</pets>

the wanted result (multiple animals) is produced:

<html xmlns:msxsl="urn:schemas-microsoft-com:xslt">
    <body>
        <table>
            <tr>
                <td>Element Name</td>
                <td>name</td>
                <td>NUM</td>
            </tr>
            <tr>
                <td>cat</td>
                <td>Lucy</td>
                <td>4</td>
            </tr>
        </table>
        <p></p>
        <table>
            <tr>
                <td>Element Name</td>
                <td>name</td>
                <td>NUM</td>
            </tr>
            <tr>
                <td>cat</td>
                <td>allMyCats</td>
                <td>4</td>
            </tr>
        </table>
        <p></p>
        <table>
            <tr>
                <td>Element Name</td>
                <td>name</td>
                <td>NUM</td>
            </tr>
            <tr>
                <td>cat</td>
                <td>Simba</td>
                <td>4</td>
            </tr>
        </table>
        <p></p>
        <table>
            <tr>
                <td>Element Name</td>
                <td>name</td>
                <td>NUM</td>
            </tr>
            <tr>
                <td>cat</td>
                <td>Fluffy</td>
                <td>1</td>
            </tr>
        </table>
        <p></p>
        <table>
            <tr>
                <td>Element Name</td>
                <td>name</td>
                <td>NUM</td>
            </tr>
            <tr>
                <td>cat</td>
                <td>Lucy</td>
                <td>3</td>
            </tr>
        </table>
        <p></p>
        <table>
            <tr>
                <td>Element Name</td>
                <td>name</td>
                <td>NUM</td>
            </tr>
            <tr>
                <td>cat</td>
                <td>Lucy</td>
                <td>1</td>
            </tr>
        </table>
        <p></p>
        <table>
            <tr>
                <td>Element Name</td>
                <td>name</td>
                <td>NUM</td>
            </tr>
            <tr>
                <td>dog</td>
                <td>Frank</td>
                <td>3</td>
            </tr>
        </table>
        <p></p>
        <table>
            <tr>
                <td>Element Name</td>
                <td>name</td>
                <td>NUM</td>
            </tr>
            <tr>
                <td>dog</td>
                <td>Spot</td>
                <td>4</td>
            </tr>
        </table>
        <p></p>
        <table>
            <tr>
                <td>Element Name</td>
                <td>name</td>
                <td>NUM</td>
            </tr>
            <tr>
                <td>dog</td>
                <td>Rover</td>
                <td>1</td>
            </tr>
        </table>
        <p></p>
    </body>
</html>
Dimitre Novatchev
@Dimitre: I tried this with //*[@NUM = 4] and //*[@NUM <= 4] but I still do not get any children under "allMyCats"
developer
@iHeartGreek: Then you are using different code or XML file. I edited my reply and put there the complete transformation, the XML file and the result. You should be able to copy them and to receive the same result from the transformation.
Dimitre Novatchev
@Dimitre: For //*[@NUM<=4] shown in your code.. Would I not expect to get the cats named Fluffy and Simba returned? Oh and Lucy NUM=3 and Lucy NUM=1 are missing too from your results. I'm sorry, but I didn't receive the correct results with this code (exact copy paste) :( I'm in a pickle.
developer
@iHeartGreek: I have solved the problem you were complaining of: receiving only one result. Now you have found that there are other problems, too. Just ask a new question. This one has been solved.
Dimitre Novatchev
@Dimitre: +1 for provide complete stylesheet. Also, from XSLT 1.0 spec its not clear the initial context (XSLT 2.0 specs provides a whole section for that), so I would recommend to use absolute path in top level declarations.
Alejandro
@Alejandro: The initial context item at global level is the document node.
Dimitre Novatchev
@Alejandro: The XSLT 1.0 http://www.w3.org/TR/xslt#top-level-variables specifies it quite well: "At the top-level, the expression or template specifying the variable value is evaluated with the same context as that used to process the root node of the source document: the current node is the root node of the source document and the current node list is a list containing just the root node of the source document."
Dimitre Novatchev
@Dimitre: Right you are! So, there is explict definition for variables and parameters.
Alejandro
@iHeartGreek: I spent some time and found all your major mistakes and corrected them, edited my answer and now you will find the complete corrected code there.
Dimitre Novatchev
@Dimitre: Thank you for all the editing you have gone back and done. I'm sorry if you misunderstood my question, but I feel my question did cover this. There was two examples, one where I only had one result and another where I had more than one, but they were incorrect. I had stated "What appears to happen is that it will stop searching down into the hierarchy once it has found a match." and that I wanted "correct results" for the Xpath expression. But thank you again for taking the time to correct the code.
developer
@iHeartGreek: So, now I have provided you with the complete solution, haven't I?
Dimitre Novatchev
@Dimitre: Unfortunately though, the solution you provided me does not include the same formatting (I need all similar nodes under same heading as I asked in this question http://stackoverflow.com/questions/3313539/dynamic-grouping-on-element-and-attributes-names) and I'm having difficulty getting the grouping to work again. I suppose I could ask a new question though. But I'm unsure if I should select this as an answer because it created a new problem for me. But that said, thank you again for the time spent helping me. I will +1 nonetheless. :)
developer
@iHeartGreek: Yes, asking a new question would be better, but this time try to make it as clear and simple as possible -- as you see even my guessing power isn't infinite :)
Dimitre Novatchev
@Dimitre: lol Ok thanks, I posted a new question and hopefully it is more clear.. :S http://stackoverflow.com/questions/3461732/need-help-fixing-xslt-document
developer