XML element, select elements

Element Selection

1. How to select nodes based on the value of attributes
2. How to choose element A if it is a direct descendant of element B
3. How to make an XSL stylesheet traverse the tree and get all the element names
4. How to to select a group of child elements
5. How to select an element with given content
6. Comparing content
7. How to Locate specific occurrence of element using XPath
8. How to put images into my web page
9. How to return a selected node in the context of its ancestors
10. Following axis explained.
11. How to select an element dependant on its childs content
12. How to Display multiple items
13. How to access first and second child
14. Find the missing items
15. Handling elements whose name is not known

1.

How to select nodes based on the value of attributes

Francis Norton

Question: expansion.
I am struggling with the following problem:
 
 <el att="aa, bb, cc">I want to select this</el>
 <el att="bb, cc">Or I want to select this</el>
 <el att="aa, cc">Or maybe I want to select this</el>
 

I want to select nodes based on the value of att: Either those containing 'aa', or 'bb', or 'cc', regardless of the other surrounding values, i.e. if I check for 'aa', I'd like to get the first and last, in the case of 'bb' the first and second, etc.

<xsl:stylesheet  default-space="strip" 
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"     version="1.0">
    <xsl:output method="xml" indent="yes">
 <xsl:template match="/">
 <result>
 <aa>
 <xsl:apply-templates select="//el[contains(@att, 'aa')]" />
 </aa>
 <bb>
 <xsl:apply-templates select="//el[contains(@att, 'bb')]" />
 </bb>
 </result>
 </xsl:template>
 
 <xsl:template match="el">
 att:'<xsl:value-of select="@att" />'
 value:'<xsl:value-of select="." />'
 </xsl:template>
 
 </xsl:stylesheet>

2.

How to choose element A if it is a direct descendant of element B

Mike Kay

<xsl:template match="B//A">
(Assuming I read "direct descendant" correctly. What would an indirect
descendant be?)
 
 An answer that lets me also choose only those element
 A's that are the direct descendent of B's, which are in turn direct
 descendent of element C's, would be even better.

<xsl:template match="C//B//A">

            

3.

How to make an XSL stylesheet traverse the tree and get all the element names

Mike Brown

There is a built-in template for elements that looks like this:

<xsl:template match="*">
   <xsl:apply-templates/>
</xsl:template>

You are overriding this template when you match "*". So all you need to do is put in an apply-templates:

<xsl:template match="*">
   <xsl:value-of select="name(.)"/>
   <xsl:apply-templates/>
</xsl:template>

You may also want to override the built-in template for text nodes: <xsl:template match="text()"/>

And put line breaks and other formatting where you need them in the other template.

4.

How to to select a group of child elements

Olivier Corby

 
Q expansion:
like this:
 <xsl:for-each select="child::a or child::b or
 child::c">

You can try this:

<xsl:for-each select="a|b|c">
            

5.

How to select an element with given content

Ken Holman


To select between
%lt;sex>M%lt;/sex>
and
%lt;sex>F%lt;/sex>

%lt;xsl:template match="/test">
   %lt;xsl:for-each select="person">
     %lt;xsl:choose>
       %lt;xsl:when test="sex='M'">&#xa;Male: %lt;/xsl:when>
       %lt;xsl:when test="sex='F'">&#xa;Female: %lt;/xsl:when>
       %lt;xsl:otherwise>&#xa;Who knows?: %lt;/xsl:otherwise>
     %lt;/xsl:choose>
     %lt;xsl:value-of select="name"/>
   %lt;/xsl:for-each>
%lt;/xsl:template>
            

6.

Comparing content

Ben Robb


Q: Expansion
 Is it AT ALL possible to compare the content of two elements 
 in XSL? Can I say
 
 if content of element1 = content of element2 ?



************************************************
XML Page
************************************************

<?xml version="1.0" ?>

<DOCUMENT>
<TAG1>something</TAG1>
<TAG2>something</TAG2>
<TAG3>something else</TAG3>
<TAG4>whatever<TAG5>something else</TAG5>next</TAG4>
</DOCUMENT>

*************************************************
XSL page
*************************************************
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">

<xsl:template match="/">  

<xsl:if test="//DOCUMENT[TAG1 = TAG2]">
   <xsl:value-of select="//DOCUMENT/TAG1"/> 
</xsl:if>

<BR/>

<xsl:if test="//DOCUMENT[TAG3 = TAG4/TAG5]">
<xsl:value-of select="//TAG5"/>
</xsl:if>

</xsl:template>  
</xsl:stylesheet>  

***********************************************
Output
***********************************************

something<BR/>something else

***********************************************

You could also use the "for-each" syntax if there were more than one:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">

<xsl:template match="/">  

<xsl:for-each select="//DOCUMENT[TAG1 = TAG2]">
   <xsl:value-of select="TAG1"/> 
</xsl:for-each>

<BR/>

<xsl:for-each select="//DOCUMENT[TAG3 = TAG4/TAG5]">
<xsl:value-of select="TAG3"/>
</xsl:for-each>

</xsl:template>  
</xsl:stylesheet>

            

7.

How to Locate specific occurrence of element using XPath

Sean


How to locate 4th <E> element which comes right beneath
<A>/<C> in the example below 

<A>
  <B>
    <E></E>
    <E></E>
    ...
  <C>
    <E></E>
    <E></E>
  </C>
  <C>
    <E></E>
    <E></E>  <- I am trying to locate this element
    <E></E>
    <E></E>
  </C>
  <D>
    <E></E>
    <E></E>
    ...
  </D>
</A>



You want (A/C/E)[4], which is the collection of all E nodes under C,
and then the fourth one.

            

8.

How to put images into my web page

Tangi Vass


The trick is to enclosed the IMG's SRC value with accolades : 
<img src="{@MAINMENU}"/>
where MAINMENU is the attribute containing the location of
the image.

            

9.

How to return a selected node in the context of its ancestors

David Carlisle


Q expansion, 

given 

<vendor name="james">
 <product id="1234">
  <material>SiO2</material>
 </product>
 <product id="5678">
  <material>CO2</material>
 </product>
</vendor>

How to return the document order context of a given node.



<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Tranform"     version="1.0"
                 version="1.0">

<xsl:output method="xml"/>

<xsl:variable name="x" 
       select="generate-id(/vendor/product/material[.='SiO2'])"/>

<xsl:template match="*">
<xsl:if test="descendant-or-self::*[generate-id(.)=$x]">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:if>
</xsl:template>

</xsl:stylesheet>


            

10.

Following axis explained.

Ken Holman


Q expansion.

How to find specific siblings, e.g. from
a template match on bullet.
<?xml version="1.0"?>
<textItems>
   <bullet>first list element</bullet>
   <bullet>second list element</bullet>
   <normal>some text</normal>
   <bullet>another list</bullet>
   <bullet>second anon list</bullet>
</textItems>


following-sibling::bullet 
	- all following siblings named "bullet"
following-sibling::*[self::bullet] 
	- all following siblings named "bullet"
following-sibling::bullet[1] 
	- the first following sibling named "bullet"
following-sibling::*[1] 
	- the immediately following sibling regardless
      of that sibling's name
following-sibling::*[1][self::bullet] 
	- the immediate following sibling
      only if it is named "bullet"


A working example is below.




<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Tranform"     
     version="1.0"
                 version="1.0">

<xsl:output method="xml" indent="yes"/>

<xsl:template match="textItems">
   <result>
     <xsl:apply-templates/>
   </result>
</xsl:template>

<xsl:template match="bullet">
   <xsl:choose>
         <!--when there is an immediate sibling of the same name,
             then assume this node has already been addressed by
             the first of the contiguous siblings-->
     <xsl:when test="preceding-sibling::*[1][self::bullet]"/>
     <xsl:otherwise><!--at the first of a group of siblings-->
       <ul><!--"walk" along all sibling bullets for list items-->
         <xsl:apply-templates select="." mode="sibling-bullets"/>
       </ul>
     </xsl:otherwise>
   </xsl:choose>
</xsl:template>

         <!--place each of a group of adjacent siblings-->
<xsl:template match="bullet" mode="sibling-bullets">
   <li><xsl:apply-templates/></li> 
     <!--put out this one-->
     <!--go to next one-->
   <xsl:apply-templates
             select="following-sibling::*[1][self::bullet]"
             mode="sibling-bullets"/>
</xsl:template>

<xsl:template match="normal">   <!--other stuff-->
   <text><xsl:apply-templates/></text>
</xsl:template>

</xsl:stylesheet>



            

11.

How to select an element dependant on its childs content

James Clark and David Carlisle


Q expansion
Given
<record>
.... lots of these data-field elements ....

<data-field type="773" indicator="0">
<subfield code="a">Soemthing Here</subfield>
<subfield code="g">Number 2, 14, (something) blah
</subfield>
<subfield code="x">0039-310X</subfield>
</data-field>
</record>

I'm want to select all the records with subfield 0039-310X.

 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Tranform"     
    version="1.0"
version="1.0">
  <xsl:output type="xml" indent="yes" /> 

 <xsl:template match="root">
 <xsl:copy>
  <xsl:apply-templates mode="copy"
select="record[data-field/subfield='0039-0310X']" /> 
  </xsl:copy>
  </xsl:template>

 <xsl:template mode="copy" match="*">
 <xsl:copy>
  <xsl:copy-of select="@*" /> 
  <xsl:apply-templates mode="copy" /> 
  </xsl:copy>
  </xsl:template>
  </xsl:stylesheet>

            

12.

How to Display multiple items

Phil Lanch



 <subject>
 <topic>Communication within the team - importance of team meetings</topic>
 <method>Micro-planning</method>
 <method>Creating a structure, defining processes and roles</method>
 <method>Briefing new members</method>
 <method>Sharing news</method>
 </subject>

<xsl:template match="subject">
 <p><b><xsl:value-of select="topic" /></b></p>
 <p><xsl:value-of select="method" /></p>
 </xsl:template>
    

Only shows the first method element.

You could try iterating over the <method> elements: That's OK, but when your stylesheets get more complicated it's clearer to split them up into separate templates:

<xsl:template match="subject">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="topic">
  <p><b><xsl:apply-templates/></b></p>
</xsl:template>

<xsl:template match="method">
  <p><xsl:apply-templates/></p>
</xsl:template>

13.

How to access first and second child

Michel Goossens

I don't really know how to browse the children of a template. I need to access the first and second child. For instance, the <msup> tag has 2 childs, the first one is the argument, the second one is the exponent.

For an input file of

<msup>
   <mrow>
     <mi>x</mi>
   </mrow>
   <mrow>
     <mn>2</mn>
     <mi>y</mi>
   </mrow>
</msup>

it gives an output file of

x power ( 2y )

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

<xsl:template match="mi|mn|mo|mtext|ms">
        <xsl:apply-templates/>
</xsl:template>
<xsl:template match="mrow|mspace">
        <xsl:apply-templates/>
</xsl:template>

<xsl:template match="msup">
<xsl:value-of select="*[1]"/>
<xsl:text> power ( </xsl:text>
<xsl:value-of select="*[2]"/>
<xsl:text> )</xsl:text>
</xsl:template>
</xsl:stylesheet>

14.

Find the missing items

Jeni Tennison


> I'm wondering if anyone has created a template (or has ideas on how
> to efficiently create such) that generates a list of items that is
> missing from a given source document. Assume that a node-set is
> passed that contains an assorted cluster of indices, the template
> should start at one, and continue to the highest provided element,
> outputting all missing elements.

Hrm... It'd be a lot easier if your nodes were sorted to start with -- are you happy to use a node-set() extension function? If so, sort them:

  <xsl:variable name="sorted-childnode-rtf">
    <xsl:for-each select="obj/childnode">
      <xsl:sort select="." data-type="number" />
      <xsl:copy-of select="." />
    </xsl:for-each>
  </xsl:variable>
  <xsl:variable name="sorted-childnodes"
                select="exsl:node-set($sorted-childnodes)" />

Then have a recursive template that takes a running count and a bunch of nodes as parameters:

<xsl:template name="fill-in-gaps">
  <xsl:param name="count" select="1" />
  <xsl:param name="nodes" select="/.." />
  ...
</xsl:template>

If there aren't any nodes left, you're done. If there are, compare the first node with the count. If it's the same, then add one to the count and move on to the next node:

<xsl:template name="fill-in-gaps">
  <xsl:param name="count" select="1" />
  <xsl:param name="format" select="'1'" />
  <xsl:param name="nodes" select="/.." />
  <xsl:if test="$nodes">
    <xsl:variable name="number">
      <xsl:number value="$count" format="{$format}" />
    </xsl:variable>
    <xsl:choose>
      <xsl:when test="$number = $nodes[1]">
        ...
      </xsl:when>
      <xsl:otherwise>
        ...
      </xsl:otherwise>
    </xsl:choose>
  </xsl:if>
</xsl:template>

Otherwise there's a gap, so output the thing to fill it, add one to the count but keep the set of nodes the same for the next call:

<xsl:template name="fill-in-gaps">
  <xsl:param name="count" select="1" />
  <xsl:param name="nodes" select="/.." />
  <xsl:if test="$nodes">
    <xsl:choose>
      <xsl:when test="$nodes[1] = $count">
        <xsl:call-template name="fill-in-gaps">
          <xsl:with-param name="count" select="count + 1" />
          <xsl:with-param name="nodes"
                          select="$nodes[position() > 1]" />
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <obj>
          <childnode><xsl:value-of select="$count" /></childnode>
        </obj>
        <xsl:call-template name="fill-in-gaps">
          <xsl:with-param name="count" select="count + 1" />
          <xsl:with-param name="nodes" select="$nodes" />
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:if>
</xsl:template>

If you don't want to use an extension function, then you should apply templates to the nodes in ascending order:

  <xsl:apply-templates select="obj/childnode">
    <xsl:sort select="." data-type="number" />
  </xsl:apply-templates>

Then have a recursive template that matches the node and extracts from it a value (one less than its value) and a bunch of nodes (those childnode elements that have a value less than it does):

<xsl:template match="childnode" name="fill-in-gaps">
  <xsl:param name="value" select=". - 1" />
  <xsl:param name="nodes"
             select="../../obj/childnode[. &lt; current()]" />
  ...
</xsl:template>

In the recursive template work down through the values until there's a node in $nodes that has the same value as $value:

<xsl:template match="childnode" name="fill-in-gaps">
  <xsl:param name="value" select=". - 1" />
  <xsl:param name="nodes"
             select="../../obj/childnode[. &lt; current()]" />
  <xsl:if test="not($value = $nodes)">
    <obj><childnode><xsl:value-of select="$value" />
</childnode></obj>
    <xsl:call-template name="fill-in-gaps">
      <xsl:with-param name="value" select="$value - 1" />
      <xsl:with-param name="nodes" select="$nodes" />
    </xsl:call-template>
  </xsl:if>
</xsl:template>

Easiest XSLT 2.0 way, I think, would be to create a sequence of numbers from 1 to the maximum value of the childnodes, then filter it to contain only those values that aren't the same as the values of one of the nodes:

  <xsl:variable name="nodes" select="obj/childnode" />
  <xsl:for-each select="(1 to max($nodes))[not($nodes = .)]">
    <obj><childnode><xsl:value-of select="." /></childnode></obj>
  </xsl:for-each>

It's not very efficient, though, especially for lots of nodes.

15.

Handling elements whose name is not known

Dimitre Novatchev


> Suppose I start with an empty specialized stylesheet,
> acme-corp-2html.xsl, that xsl:imports the general stylesheet,
> us-gaap-ci-2html.xsl.  Even without defining any templates of its own,
> acme-corp-2html.xsl will handle all the elements that the general
> stylesheet can handle. That's good.
>
> However, the acme-corp instance documents uses many elements (let's say
> it could be hundreds) that are not present in generic us-gaap-ci
> instance documents. Assuming that the imported us-gaap-ci-2html.xsl just
> simplistically defines templates that match based on element names in
> the us-gaap-ci.xsd, these specialized Acme elements won't be matched by
> the imported templates.
>
> I don't see how xsl:import helps me to get the generic
> us-gaap-ci-2html.xsl templates to apply to the new elements used by Acme
> corp. (Or am I missing something?)

Yes, you are missing the fact that you could have templates that are so general that they will work in every case.

For example, your first imported stylesheet module could contain the identity template:

   <xsl:template match="node() | @*">
      <xsl:copy>
          <xsl:apply-templates select="node() | @*"/>
      </xsl:copy>
   </xsl:template>

this template matches every possible node with the exception of namespace nodes, which are simply copied. It works on any xml.source without having to know in advance its structure.


>
> I could manually determine the names of elements that are unique to
> acme-corp, and add template rules for each into acme-corp-2html.xsl.
> The bodies of these templates would be exactly the same as the templates
> of the corresponding elements in the included stylesheet. And there are
> thousands of companies like Acme Corp.

You don't need to add new templates. For example you could define a global xsl:variable in the importing stylesheet module, that contains the names of all elements that are to be handled by a template.

The template itself could match any element, but within its code would process only elements, whose name is the value of one of the nodes contained in the global variable. Something like this:


<xsl:template match="*">
  <xsl:if test="name() = $vgNames/*">
     <!-- Process this element here -->
  </xsl:if>
</xsl:template>

And the "vgNames" xsl:variable could be defined like this:

<xsl:variable name="vgNames" select="document('')/*/myNames:myNames[1]">

and somewhere globally defined there would be:

<myNames:myNames>
  <name>name1</name>
  <name>name2</name>
  <name>name3</name>
. . . . . .  . . . . . . . . . . .. . . .
  <name>nameN</name>
</myNames:myNames>


>
> I want to be able to write versions of generic stylesheets (like
> us-gaap-ci-2html.xsl) that will work no only for us-gaap-ci.xsd instance
> documents, but also for any documents derived from us-gaap-ci that
> define their own more specialized elements.

The examples above show that this can be done. Probably if you provide (the simplest possible and minimal) complete example people will produce the solution for you in no time.