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>
LVL 35
mrichmonAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

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 BormansInformation ArchitectCommented:
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

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Cloud Class® Course: Microsoft Office 2010

This course will introduce you to the interfaces and features of Microsoft Office 2010 Word, Excel, PowerPoint, Outlook, and Access. You will learn about the features that are shared between all products in the Office suite, as well as the new features that are product specific.

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 BormansInformation ArchitectCommented:
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
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Web Languages and Standards

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.