• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 372
  • Last Modified:

Understanding xslt and xpath in a specific example

I have the following xslt file that will sort my xml file based on table name.  The xslt was coded with significant help from daniel_balla.

It works, but I don't understand it very well as I have not used xslt nor xpath before - other than glancing at it briefly.  I would like to understand how this xslt file works well enough to make certain changes to it or future xslt files that will do similar things.

For example, I may want to be able to sort the nodes on schema, then table, then column.  How would this affect the xslt file?

So what I am looking for here is an explanation of how and why the xslt file works, as well as how to make changes like that listed above.

============= xslt file ==============
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/02/xpath-functions" xmlns:xdt="http://www.w3.org/2005/02/xpath-datatypes">
      <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
      <xsl:template match="database/schema">
            <xsl:copy>
                  <xsl:apply-templates select="@*|node()">
                        <xsl:sort select="name" order="ascending"/>
                  </xsl:apply-templates>
            </xsl:copy>
      </xsl:template>
      <xsl:template match="@*|node()">
            <xsl:copy>
                  <xsl:apply-templates select="@*|node()"/>
            </xsl:copy>
      </xsl:template>
</xsl:stylesheet>

===== sample xml file ==========
<?xml version="1.0" encoding="iso-8859-1"?>
<database>
      <schema name="MySchema">
            <table>
                  <name>Positions</name>
                  <description>Positions available within the organization</description>
                  <columns>
                        <column>
                              <name>PostionID</name>
                              <notnull />
                              <datatype>number(8)</datatype>
                              <description>Position unique identifier</description>
                              <sampledata>
                                    <value>615779</value>
                              </sampledata>
                        </column>
                        <column>
                              <name>PositionTitle</name>
                              <notnull />
                              <datatype>varchar2(50)</datatype>
                              <description>Title of position</description>
                              <sampledata>
                                    <value>Administrative Specialist</value>
                              </sampledata>
                        </column>
                        <column>
                              <name>PayRate</name>
                              <datatype>number(9,2)</datatype>
                              <description>Salary level of this position</description>
                              <sampledata>
                                    <value>3452.67</value>
                              </sampledata>
                        </column>
                        <column>
                              <name>LastModified</name>
                              <notnull />
                              <datatype>date</datatype>
                              <description>Date record was last created/modified</description>
                              <sampledata>
                                    <value>2003-10-27 16:59:45</value>
                              </sampledata>
                        </column>
                  </columns>
            </table>
            <table>
                  <name>Employees</name>
                  <description>Information about all current employees</description>
                  <columns>
                        <column>
                              <name>EmployeeID</name>
                              <notnull />
                              <datatype>number(9)</datatype>
                              <description>unique ID for this employee</description>
                              <sampledata>
                                    <value>61579945</value>
                              </sampledata>
                        </column>
                        <column>
                              <name>FirstName</name>
                              <notnull />
                              <datatype>varchar2(30)</datatype>
                              <description>Employee First Name</description>
                              <sampledata>
                                    <value>John</value>
                              </sampledata>
                        </column>
                        <column>
                              <name>LastName</name>
                              <notnull />
                              <datatype>varchar2(30)</datatype>
                              <description>Employee Last Name</description>
                              <sampledata>
                                    <value>Smith</value>
                              </sampledata>
                        </column>
                        <column>
                              <name>HireDate</name>
                              <notnull />
                              <datatype>date</datatype>
                              <description>Date employee was hired</description>
                              <sampledata>
                                    <value>2003-10-27 16:59:45</value>
                              </sampledata>
                        </column>
                  </columns>
            </table>
            <table>
                  <name>Departments</name>
                  <description>Information about each department</description>
                  <columns>
                        <column>
                              <name>DepartmentID</name>
                              <notnull />
                              <datatype>number(8)</datatype>
                              <description>Unique identifier for department</description>
                              <sampledata>
                                    <value>123456</value>
                              </sampledata>
                        </column>
                        <column>
                              <name>DepartmentName</name>
                              <datatype>varchar2(30)</datatype>
                              <description>Name of department</description>
                              <sampledata>
                                    <value>Sales</value>
                              </sampledata>
                        </column>
                  </columns>
            </table>
      </schema>
      <schema name="AnotherSchema">
            <table>
                  <name>Positions</name>
                  <description>Positions available within the organization</description>
                  <columns>
                        <column>
                              <name>PostionID</name>
                              <notnull />
                              <datatype>number(8)</datatype>
                              <description>Position unique identifier</description>
                              <sampledata>
                                    <value>615779</value>
                              </sampledata>
                        </column>
                        <column>
                              <name>PositionTitle</name>
                              <notnull />
                              <datatype>varchar2(50)</datatype>
                              <description>Title of position</description>
                              <sampledata>
                                    <value>Administrative Specialist</value>
                              </sampledata>
                        </column>
                        <column>
                              <name>PayRate</name>
                              <datatype>number(9,2)</datatype>
                              <description>Salary level of this position</description>
                              <sampledata>
                                    <value>3452.67</value>
                              </sampledata>
                        </column>
                        <column>
                              <name>LastModified</name>
                              <notnull />
                              <datatype>date</datatype>
                              <description>Date record was last created/modified</description>
                              <sampledata>
                                    <value>2003-10-27 16:59:45</value>
                              </sampledata>
                        </column>
                  </columns>
            </table>
            <table>
                  <name>Employees</name>
                  <description>Information about all current employees</description>
                  <columns>
                        <column>
                              <name>EmployeeID</name>
                              <notnull />
                              <datatype>number(9)</datatype>
                              <description>unique ID for this employee</description>
                              <sampledata>
                                    <value>61579945</value>
                              </sampledata>
                        </column>
                        <column>
                              <name>FirstName</name>
                              <notnull />
                              <datatype>varchar2(30)</datatype>
                              <description>Employee First Name</description>
                              <sampledata>
                                    <value>John</value>
                              </sampledata>
                        </column>
                        <column>
                              <name>LastName</name>
                              <notnull />
                              <datatype>varchar2(30)</datatype>
                              <description>Employee Last Name</description>
                              <sampledata>
                                    <value>Smith</value>
                              </sampledata>
                        </column>
                        <column>
                              <name>HireDate</name>
                              <notnull />
                              <datatype>date</datatype>
                              <description>Date employee was hired</description>
                              <sampledata>
                                    <value>2003-10-27 16:59:45</value>
                              </sampledata>
                        </column>
                  </columns>
            </table>
            <table>
                  <name>Departments</name>
                  <description>Information about each department</description>
                  <columns>
                        <column>
                              <name>DepartmentID</name>
                              <notnull />
                              <datatype>number(8)</datatype>
                              <description>Unique identifier for department</description>
                              <sampledata>
                                    <value>123456</value>
                              </sampledata>
                        </column>
                        <column>
                              <name>DepartmentName</name>
                              <datatype>varchar2(30)</datatype>
                              <description>Name of department</description>
                              <sampledata>
                                    <value>Sales</value>
                              </sampledata>
                        </column>
                  </columns>
            </table>
      </schema>
</database>
0
mrichmon
Asked:
mrichmon
  • 3
  • 2
2 Solutions
 
mrichmonAuthor Commented:
Is this right:

 <xsl:template match="database/schema">
says select all nodes of schema that are children of a database node

<xsl:copy>
says we will copy what is inside

<xsl:apply-templates select="@*|node()">
says to apply the template inside to all child nodes and attirbutes of the node being looked at.

Here I am a bit confused since looking at http://www.w3schools.com/xpath it shows
Wildcard        Description
*       Matches any element node
@*       Matches any attribute node
node()       Matches any node of any kind

So wouldn't just node() work as well?  If not can you explain why?

<xsl:sort select="name" order="ascending"/>
says to sort on a name element.  (If it were an attribute it would be <xsl:sort select="@name" order="ascending"/> correct?)  Also I am not clear here on how it knows to use the name element of the table.  If the schema had a name element then this does not work (I tried adding a name element to the sample file).  Or how it knows not to sort the columns right now.  Does this only work on children and not all descendants?

After that there is another template section:
<xsl:template match="@*|node()">
     <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
</xsl:template>

which seems to just copy every node in the document.  If that is correct I do not understand how it knows that the nodes that are copied are sorted or not - why isn't the original document returned.

I guess I don't understand how the two template sections interact with each other.

Any comments would be great as well as any recommendations as to good tutorials that are not simple.  I have done basic google searches, and I think I understand the simple tutorials like those at W3Schools, but this is a more complex xslt, and therefore I would like tutorials that do similar things (like the output is a clone of the original document, but sorted).  So if you recommend a tutorial, please don't just post a link, but say why you think that tutorial will help with what I am looking for.

Thanks
0
 
daniel_ballaCommented:
Hi mrichmon,
>  <xsl:template match="database/schema">
> says select all nodes of schema that are children of a database node
Yes

><xsl:copy>
>says we will copy what is inside
yes

><xsl:apply-templates select="@*|node()">
>says to apply the template inside to all child nodes and attirbutes of the node being looked at.
*      Matches any element node
@*      Matches any attribute node
node()      Matches any node of any kind
That is correct, but, there are four different types of nodes: elements, comments, processing instructions, and text nodes.
node() checks for all of these. if you would use * instead it would only search for elements. However, attributes are not nodes, and they need to be added.

>If it were an attribute it would be <xsl:sort select="@name" order="ascending"/> correct?
yes

>Does this only work on children and not all descendants?
yes, that is why we reference the children of table, such as name. you can go to deeper children, but you have to use the path. In order to go to all levels, we build a recursive template such as the second one in this XSL file.

> I do not understand how it knows that the nodes that are copied are sorted or not - why isn't the original document returned.
Because this template is called by the other one, so it first applies the database/schema template which uses apply-templates to call the appropiate templates for the children nodes, and we use xsl:sort in the apply-templates so it will call them sorted.

>I guess I don't understand how the two template sections interact with each other.
Think of templates like a sort of functions - they define a certain "implementation" for a node that they match. you can call these "functions" by apply-templates and so you can create recursive constructs, such as the one above.

Again, this isn't the easiest XSLT, so don't give it up. Let me come back with another post about tutorials and thoughts about XSLT training.

Cheers!
0
 
Geert BormansCommented:
some minor adjustments

>> says select all nodes of schema that are children of a database node
>Yes

on the condition that the database node is a child of the current context node

>><xsl:copy>
>>says we will copy what is inside
>yes
mmh, <xsl:copy> copies the current node, without the children,
most often, it just copies the start tag and the end tag (that is when the current node is an element)
well, and when it is an element, it also copies the namespaces

Your XSLT is a straightforward example of a slightly changed identity transform
This is what you would do when you want to copy the input to the output, be it with a small modification

This part is the Identity Transform
     <xsl:template match="@*|node()">
          <xsl:copy>
               <xsl:apply-templates select="@*|node()"/>
          </xsl:copy>
     </xsl:template>

For every node that matches this match expression, be it attributes or element or text nodes "@* | node()"
This template gets activated.
This template takes the folowing action
- copy the current node without its children or attributes (<xsl:copy>)
- push the selected nodeset to the templates, selected are all attributes and child-nodes (<xsl:apply-templates select="@*|node()"/>)

This means that children are pushed to the templates,
the same template gets activated
the children get copied and the children of the children are pushed to the templates

XSLT is rulebased... you have a bunch of templates (with match rules)
and if a nodeset matches a match expression in a template... the actions in that template get executed

and as you see from the example... this works in a hierarchical way

The Identity Transform (the five lines I copied)
creates exactly the same output tree as the input tree was (hence the name)

In XSLT there is a rule that says in simple terms that the most specific match statement wins
In your example <xsl:template match="database/schema"> is more specific then <xsl:template match="@*|node()">
because a named element node is ofcourse more specific than an unnamed unspecified node
This means that every nodeset that contains of schema elements that are children of a database element
will activate a different template
This other template does exactly the same as the the other more generic template except that it pushes the child nodeset to the templates in an ordered way

               <xsl:apply-templates select="@*|node()">
                    <xsl:sort select="name" order="ascending"/>
               </xsl:apply-templates>
 is like <xsl:apply-templates select="@*|node()"/>
but the child nodes (set of table elements in this case) are pushed to the templates, ordered ascending by name child element

That is it.
Don't hesitate to ask more questions if need be

Try to understand the slightly altered Identity Transform concept,
because very often you might want to copy an XML file, with only a small change
like moving a attribute node or renaming an element

cheers


0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
mrichmonAuthor Commented:
Thanks both of you.

Gertone, that sentence about how precedence works (i.e. the more specific one applies) really helped clarify for me.

I have to go for the day, but may be back with more questions.  I think I am getting it.  Thanks!
0
 
Geert BormansCommented:
cheers
0
 
mrichmonAuthor Commented:
>>><xsl:template match="database/schema">
>>> says select all nodes of schema that are children of a database node

>>Yes

>on the condition that the database node is a child of the current context node

So because this runs on the whole document it would work on all the schemas. And this:
<xsl:template match="/database/schema">
would force it only to consider ones from the root of the document correct?

I think I am getting this.

with your comments so far I was able to modify to sort on schema (using name attribute), then table, then column name.  I used:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/02/xpath-functions" xmlns:xdt="http://www.w3.org/2005/02/xpath-datatypes">
      <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
      <xsl:template match="database">
            <xsl:copy>
                  <xsl:apply-templates select="@*|node()">
                        <xsl:sort select="@name" order="ascending"/>
                  </xsl:apply-templates>
            </xsl:copy>
      </xsl:template>
      <xsl:template match="database/schema">
            <xsl:copy>
                  <xsl:apply-templates select="@*|node()">
                        <xsl:sort select="name" order="ascending"/>
                  </xsl:apply-templates>
            </xsl:copy>
      </xsl:template>
      <xsl:template match="columns">
            <xsl:copy>
                  <xsl:apply-templates select="@*|node()">
                        <xsl:sort select="name" order="ascending"/>
                  </xsl:apply-templates>
            </xsl:copy>
      </xsl:template>
      <xsl:template match="@*|node()">
            <xsl:copy>
                  <xsl:apply-templates select="@*|node()"/>
            </xsl:copy>
      </xsl:template>
</xsl:stylesheet>


Thanks a bunch for the help!
0

Featured Post

Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

  • 3
  • 2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now