xlst transformation to display departments of each company node - xslt

I am beginner for XSLT, currently I am trying to display list of <department> in each <company> node.
Below is my XML
<employee_data>
<employeedetails id="1">
<company id="1">
<companyname>AOL</companyname>
<department>IT</department>
</company>
<employeename>Patrick</employeename>
<employeedesg>Software Engineer</employeedesg>
<employeesalary>18000</employeesalary>
<employeedoj>10/03/2015</employeedoj>
</employeedetails>
..... similar sets......
..... similar sets......
<employeedetails id="10">
<company id="1">
<companyname>AOL</companyname>
<department>HR</department>
</company>
<employeename>Patricia</employeename>
<employeedesg>HR Assistant</employeedesg>
<employeesalary>18000</employeesalary>
<employeedoj>10/03/2015</employeedoj>
</employeedetails>
</employee_data>
I have written the below XSLT to fetch only company names, but I want to display all the departments from the various companies.
XSLT:
<xsl:key name="companyname" match="/employee_data/employeedetails/company/companyname" use="."/>
<xsl:for-each select="/employee_data/employeedetails/company/companyname[generate-id() = generate-id(key('companyname',.)[1])]">
<tr>
<td>
<xsl:value-of select="../#id"/>
</td>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:for-each>

If you want to get the department nodes for a company simply use the same key that you have used in the grouping of the companies
<xsl:for-each select="key('companyname',.)">
<xsl:value-of select="../department" />
<br />
</xsl:for-each>
As a side note, you can actually simplify your key. You don't actually need to specify the full path to the company element, just the name will do
<xsl:key name="companyname" match="company" use="companyname"/>
You would only need to consider using a path if there were other company elements at a different level in the XML hierarchy that you didn't want included.
Also note I am matching company and not companyname simply to avoid using the .. parent axis.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" />
<xsl:key name="companyname" match="company" use="companyname"/>
<xsl:template match="/">
<xsl:for-each select="/employee_data/employeedetails/company[generate-id() = generate-id(key('companyname', companyname)[1])]">
<tr>
<td>
<xsl:value-of select="#id"/>
</td>
<td>
<xsl:apply-templates select="key('companyname', companyname)" />
</td>
</tr>
</xsl:for-each>
</xsl:template>
<xsl:template match="company">
<xsl:value-of select="department" />
<br />
</xsl:template>
</xsl:stylesheet>

Related

XSL conditions: how to choose a value from list only when the condition is met?

I am trying to implement a logic which would do choosing a value if it matches the condition. The problem is, if my list contains more elements than one, it wouldn't display the value which matches the condition, but only the first one. Could you provide some advice? thanks. I need this to implement multiple filtering by conditions. Meaning that in this for-each I would display only books with year 2008, but in another table I could also use 2010 from the list of years. The below example is a part of the template for one table which uses 2008, but I will have multiple tables where I would filter books by 2010 as well.
XSL:
<xsl:for-each select="years">
<xsl:choose>
<xsl:when test="year=2008">
<td class="year"><xsl:value-of select="year"/></td>
</xsl:when>
</xsl:choose>
</xsl:for-each>
XML:
<book>
<title>Professional ASP.NET 4 in C# and VB</title>
<author>Bill Evjen, Scott Hanselman, Devin Rader</author>
<country>USA</country>
<price>580</price>
<years>
<year>2010</year>
<year>2008</year>
</years>
</book>
This one would populate 2010 into my HTML, not 2008 as I would expect. Is this doable at all?
Update:
I've tried this approach for example to filter against multiple conditions separately:
<xsl:for-each select="library/book">
<tr>
<td class="filterTd title"><xsl:value-of select="title"/></td>
<td class="filterTd author"><xsl:value-of select="author"/></td>
<td class="filterTd price"><xsl:value-of select="price"/></td>
<xsl:for-each select="years/year[.2008]">
<td class="year">
<xsl:value-of select="."/>
</td>
</xsl:for-each>
<xsl:for-each select="years/year[.2010]">
<td class="year">
<xsl:value-of select="."/>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
This would not populate any td at all in my case..
If you only want to display books for a specific year, you would put a condition in the xsl:for-each that selects the book
<xsl:for-each select="library/book[years/year=$year]">
Where $year is a variable (or parameter) containing the year you want
Do note, if you know you are dealing with a specific year, then you don't actually need to do <xsl:value-of select="year" />, you can just output the year value you are working with.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="yes" />
<xsl:template match="/">
<xsl:call-template name="table">
<xsl:with-param name="year" select="2008" />
</xsl:call-template>
</xsl:template>
<xsl:template name="table">
<xsl:param name="year" />
<table>
<xsl:for-each select="library/book[years/year=$year]">
<tr>
<td class="filterTd title"><xsl:value-of select="title"/></td>
<td class="filterTd author"><xsl:value-of select="author"/></td>
<td class="filterTd price"><xsl:value-of select="price"/></td>
<td class="year">
<xsl:value-of select="$year"/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Note, you could also use a key here to look up books by year
<xsl:key name="booksByYear" match="book" use="years/year" />
Then, the xsl:for-each to select books, looks like this:
<xsl:for-each select="key('booksByYear', $year)">

How to declare a variable which tie an attribute to another one, in another node?

I need to declare a variable. I would like to link/tie 2 attributes in this variable.
<xsl:variable name="politiciens" select="Trans/Speakers/Speaker/#id='Trans/Episode/Section/Turn/#speaker'"/>`
XSL =
<xsl:for-each select="Trans/Speakers/Speaker[#check='yes']">
<!-- declare a variable which contain #id, and whom will link/tie #speaker from Turn-->
<xsl:variable name="politiciens" select="#id=//Turn/#Speaker" />
<tr>
<td><xsl:value-of select="#name"/></td>
td><xsl:value-of select="count(Trans/Episode/Section/Turn[#Speaker=$politiciens]/tour/motBDL[#lexeme='JE'])" /></td>
</tr>
</xsl:for-each>
XML =
<Trans scribe="ComputerName" audio_filename="Debat Hollande Sarkozy 1998" video_filename="" version="8" version_date="181221">
<Speakers>
<Speaker id="spk1" name="Nicolas Sarkozy" check="yes"/>
</Speakers>
<Episode>
<Section type="report" startTime="0" endTime="1408.652">
<Turn startTime="0" endTime="0.152">
<Sync time="0"/>
</Turn>
<Turn speaker="spk1" startTime="0.152" endTime="3.038">
<Sync time="0.152"/>
<tour nbmots="14" id="000000">
<motBDL lexeme="POUR">pour</motBDL>
</tour>
</Turn>
</Section>
</Episode>
</Trans>
Expected = The two attributes to be linked together <xsl:variable name="politiciens" select="#id='#speaker'"/>
I think it sounds like you need to read up on keys. If you want to look-up the Turn elements for the current Speaker you can define a key like so
<xsl:key name="turns" match="Turn" use="#speaker" />
Then, within your xsl:for-each that gets the speakers, you can use the key to count the number of turn elements like so:
<xsl:value-of select="count(key('turns', #id)/tour/motBDL)" />
(I've omitted the check on #lexeme='JE' because there isn't any matching value in your XML)
Try this XSLT as a starting point:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="yes" />
<xsl:key name="turns" match="Turn" use="#speaker" />
<xsl:template match="/">
<table>
<xsl:for-each select="Trans/Speakers/Speaker[#check='yes']">
<tr>
<td><xsl:value-of select="#name"/></td>
<td><xsl:value-of select="count(key('turns', #id)/tour/motBDL)" /></td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
And, just as an example, you could also define a key to look up the Speaker elements themselves, if you want to list out the individual turns.
For example....
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="yes" />
<xsl:key name="speakers" match="Speaker" use="#id" />
<xsl:template match="/">
<table>
<xsl:for-each select="Trans/Episode/Section/Turn[#speaker != '']">
<tr>
<td><xsl:value-of select="key('speakers', #speaker)/#name"/></td>
<td><xsl:value-of select="#startTime" /></td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>

XSLT 1.0 For Each if output

I know xslt 1.0 has been done to death however, i have spent considerable time researching an answer to my problem. I have seen the following questions:
XSLT for-each loop table output
XSLT 1.0 Group By
I am trying to teach myself as best I can but time is getting away from me and I need to get this up and working.
Anyway I implemented what I thought was the solution and realized that some of my XML's have duplicate names in the for each and therefore the output is moving into the next column. I need Visio to be in the Visio column (or table header) and Project to be in the Project column etc.
Here is my XML Sample
<?xml version="1.0" standalone="yes"?>
<Root>
<v_Add_Remove_Programs>
<ResourceID>12345678</ResourceID>
<GroupID>5</GroupID>
<ProdID0>Office14.PRJSTD</ProdID0>
<DisplayName0>Microsoft Project Standard 2010</DisplayName0>
<InstallDate0></InstallDate0>
<Publisher0>Microsoft Corporation</Publisher0>
<Version0>14.0.7015.1000</Version0>
</v_Add_Remove_Programs>
<v_Add_Remove_Programs>
<ResourceID>12345678</ResourceID>
<GroupID>152</GroupID>
<ProdID0>{90140000-003A-0000-0000-0000000FF1CE}</ProdID0>
<DisplayName0>Microsoft Office Project Standard 2010</DisplayName0>
<InstallDate0>20180219</InstallDate0>
<Publisher0>Microsoft Corporation</Publisher0>
<Version0>14.0.7015.1000</Version0>
</v_Add_Remove_Programs>
<v_Add_Remove_Programs>
<ResourceID>12345678</ResourceID>
<GroupID>330</GroupID>
<ProdID0>Office15.VISSTD</ProdID0>
<DisplayName0>Microsoft Visio Standard 2013</DisplayName0>
<InstallDate0></InstallDate0>
<Publisher0>Microsoft Corporation</Publisher0>
<Version0>15.0.4569.1506</Version0>
</v_Add_Remove_Programs>
<v_Add_Remove_Programs>
<ResourceID>12345678</ResourceID>
<GroupID>331</GroupID>
<ProdID0>{90150000-0053-0000-0000-0000000FF1CE}</ProdID0>
<DisplayName0>Microsoft Visio Standard 2013</DisplayName0>
<InstallDate0>20171225</InstallDate0>
<Publisher0>Microsoft Corporation</Publisher0>
<Version0>15.0.4569.1506</Version0>
</v_Add_Remove_Programs>
<v_Add_Remove_Programs>
<ResourceID>12345678</ResourceID>
<GroupID>4</GroupID>
<ProdID0>Office14.PRJSTD</ProdID0>
<DisplayName0>Microsoft Project Standard 2010</DisplayName0>
<InstallDate0></InstallDate0>
<Publisher0>Microsoft Corporation</Publisher0>
<Version0>14.0.7015.1000</Version0>
</v_Add_Remove_Programs>
</Root>
Here is my XSLT
<?xml version = "1.0" encoding = "UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>Add Remove Software</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Visio</th>
<th>Project</th>
</tr>
<xsl:for-each select="//v_Add_Remove_Programs">
<xsl:variable name="DispName" select="DisplayName0" />
<xsl:if test="contains($DispName, 'Visio Standard') or
contains($DispName, 'Visio Professional')">
<td>
<xsl:value-of select="$DispName"/>
</td>
</xsl:if>
<xsl:if test="contains($DispName, 'Project Standard')">
<td>
<xsl:value-of select="$DispName"/>
</td>
</xsl:if>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
This is the output I desire.
Resource ID | Visio |Project
12345678 | Microsoft Visio Standard 2013 |Microsoft Project Standard 2013
Thanks very much in advance really appreciate it.
I am guessing you want to do something like:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/Root">
<html>
<body>
<h2>Add Remove Software</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Visio</th>
<th>Project</th>
</tr>
<xsl:for-each select="v_Add_Remove_Programs">
<tr>
<td>
<xsl:value-of select="DisplayName0[contains(., 'Visio Standard') or
contains(., 'Visio Professional')]"/>
</td>
<td>
<xsl:value-of select="DisplayName0[contains(., 'Project Standard')]"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Given your example input, the (rendered) output will be:
It looks like you want to group by ResourceID. In this case, read up on Muenchian Grouping. In this case you would define your key like so:
<xsl:key name="resources" match="v_Add_Remove_Programs" use="ResourceID" />
And to get the distinct ResourceID elements, which represent your rows, do this...
<xsl:for-each select="v_Add_Remove_Programs[generate-id() = generate-id(key('resources', ResourceID)[1])]">
But you have an added complication, as it looks like you also need to get the distinct programs within each resource. So, you need to do a second lot of grouping with this key...
<xsl:key name="resourceNames" match="v_Add_Remove_Programs" use="concat(ResourceID, '|', DisplayName0)" />
And to get the distinct programs, for a certain type, do this..
<xsl:for-each select="key('resources', ResourceID)
[generate-id() = generate-id(key('resourceNames', concat(ResourceID, '|', DisplayName0))[1])]
[starts-with(DisplayName0, $type)]">
Try this XSLT, which you can see in action at http://xsltfiddle.liberty-development.net/6qVRKxa
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:key name="resources" match="v_Add_Remove_Programs" use="ResourceID" />
<xsl:key name="resourceNames" match="v_Add_Remove_Programs" use="concat(ResourceID, '|', DisplayName0)" />
<xsl:template match="Root">
<table>
<tr>
<th>Resource ID</th>
<th>Visio</th>
<th>Project</th>
</tr>
<xsl:for-each select="v_Add_Remove_Programs[generate-id() = generate-id(key('resources', ResourceID)[1])]">
<tr>
<td>
<xsl:value-of select="ResourceID" />
</td>
<td>
<xsl:call-template name="resourceList">
<xsl:with-param name="type" select="'Microsoft Visio'" />
</xsl:call-template>
</td>
<td>
<xsl:call-template name="resourceList">
<xsl:with-param name="type" select="'Microsoft Project'" />
</xsl:call-template>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template name="resourceList">
<xsl:param name="type" />
<xsl:for-each select="key('resources', ResourceID)
[generate-id() = generate-id(key('resourceNames', concat(ResourceID, '|', DisplayName0))[1])]
[starts-with(DisplayName0, $type)]">
<xsl:value-of select="DisplayName0" />
<br />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Get xsl 1.0 to only show first item in a group within a list

This is for a category index of blog posts-I only want to show the category once using xsl v1.0. There will be multiple posts in each category. The desired result is:
Cat Name 1
cat Name 2
Cat Name 3
I assume grouping the items and only showing the first one in a group (using the cat name as a key) would work but the Muenchian method is a bit beyond my abilities. So a simpler method or a simple explanation of the Muenchian method would be most appreciated.
The xml
<Root>
<Schema>
<Field Type="Lookup" DisplayName="Category name" Required="FALSE" ShowField="Category_x0020_name" Name="Category_x0020_name" Group="" />
<Field ReadOnly="TRUE" Type="Computed" Name="LinkTitle" DisplayName="Post number" />
</Schema>
<Data ItemCount="1">
<Row Category_x0020_name="" LinkTitle="" />
</Data>
</Root>
The xsl:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" />
<xsl:template match="/">
<table border="0" cellpadding="0" cellspacing="0">
<h3>Categories</h3>
<xsl:for-each select="//Data/Row">
<xsl:if test="./#Category_x0020_name !=''">
<tr valign="top"> <td>
<a href="/cat{./#LinkTitle}.aspx">
<xsl:value-of select="./#Category_x0020_name" /></a></td> </tr>
</xsl:if>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Do not be afraid of Meunchian method. Use it once, and you will be able to apply it whenever you need it.
collect the wanted data to group in a key
apply the templates to only one node with same key using a predicate like
generate-id()=generate-id(key(...)[1])
That's what you need know to use Meunchian grouping. Here to get you started:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="Cat" match="Data/Row" use="#Category_x0020_name"/>
<xsl:template match="/*/Data">
<xsl:apply-templates select="Row
[generate-id()
= generate-id(key('Cat',#Category_x0020_name)[1])]"/>
</xsl:template>
<xsl:template match="Row">
<xsl:value-of select="concat(#Category_x0020_name,'
')"/>
</xsl:template>
</xsl:stylesheet>
Since you don't need to list the members of each category, and assuming your data sets are not very large so performance is not a big factor, you can forego Muenchian grouping for something a little less elegant. Just change your <xsl:if test> to:
<xsl:if test="./#Category_x0020_name !='' and
not(./#Category_x0020_name = preceding::Row/#Category_x0020_name)">
In other words, only output a category name the first time it occurs.
Incidentally, you can remove the ./ wherever it occurs at the beginning of an XPath expression. It's redundant. It means "starting from the context node," but you're already starting from the context node. If you want to leave it in for readability or something, that's OK.
Then with input like
<Root>...
<Data ItemCount="1">
<Row Category_x0020_name="foo" LinkTitle="Foo" />
<Row Category_x0020_name="bar" LinkTitle="Bar" />
<Row Category_x0020_name="foo" LinkTitle="Foo" />
</Data>
</Root>
You get this output:
<table border="0" cellpadding="0" cellspacing="0">
<h3>Categories</h3>
<tr valign="top">
<td>foo</td>
</tr>
<tr valign="top">
<td>bar</td>
</tr>
</table>

XSLT to find all nodes in one list that aren't in another

I have xml that essentialy looks like the following (my actual .xml is a build log containing a union of VStudio and DotCover build output):
<buildlog>
<Projects>
<project name="project1.csproj" />
<project name="project2.csproj" />
<project name="project3.csproj" />
<project name="project4.csproj" />
</Projects>
<Root>
<Assembly Name="project1" />
<Assembly Name="project2" />
</Root>
</buildlog>
I need a list of all project nodes that don't have a corresponding #name match on #Name in the set of Root/Assembly nodes. My goal here is to generate a report showing all projects that don't have code coverage.
Here's the beginnings of my xsl stylesheet:
<xsl:template match="/">
<xsl:apply-templates select="buildlog/Projects" />
</xsl:template>
<xsl:template match="Projects">
<table>
<tr>
<td>Assemblies Not Covered:</td>
<td><xsl:value-of select="count(./project)"/></td>
</tr>
</table>
<xsl:apply-templates select="project" />
</xsl:template>
<xsl:template match="project">
<table>
<tr>
<td style="padding-left:30px">
<xsl:value-of select="substring-before(./#name,'.csproj')"/>
</td>
</tr>
</table>
</xsl:template>
Thanks in advance.
INTERIM SOLUTION
For the time being I solved my issue using ms:script extensions. Below is the code.
Not the most elegant give it is a Windows only solution, but it solved the problem. I'll
the things other folks recommended and answer back in the comment sections.
Here's my solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/TR/xhtml1/strict"
xmlns:ms="urn:schemas-microsoft-com:xslt"
xmlns:dsi="urn:dsi-scripts">
<xsl:output method="html"/>
<xsl:template match="/">
<xsl:apply-templates select="buildlog/Projects" />
</xsl:template>
<xsl:template match="Projects">
<table>
<tr>
<td>Assemblies Not Covered:</td>
</tr>
</table>
<xsl:apply-templates select="project" />
</xsl:template>
<xsl:template match="project">
<xsl:variable name="assemblies" select="//Assembly" />
<xsl:if test="not(dsi:HasCoverage($assemblies/#Name, ./#name))">
<table>
<tr>
<td style="padding-left:30px">
<xsl:value-of select="substring-before(./#name,'.csproj')"/>
</td>
</tr>
</table>
</xsl:if>
</xsl:template>
<xsl:template match="Assembly">
<table>
<tr>
<td style="padding-left:30px">
<xsl:value-of select="#Name"/>
</td>
</tr>
</table>
</xsl:template>
<ms:script language="C#" implements-prefix="dsi">
<![CDATA[
public bool HasCoverage(XPathNodeIterator assemblies, string project)
{
bool result = false;
while(assemblies.MoveNext())
{
if(assemblies.Current.Value == project.Replace(".csproj",string.Empty))
{
result = true;
break;
}
}
return result;
}
]]>
</ms:script>
</xsl:stylesheet>
I need a list of all project nodes
that don't have a corresponding #name
match on #Name in the set of
Root/Assembly nodes
Use:
/*/Projects/project
[not(substring-before(#name, '.csproj') = /*/Root/Assembly/#Name)]
This XPath expression selects exactly the wanted project elements.
Complete solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match=
"Projects/project
[not(substring-before(#name, '.csproj')
=
/*/Root/Assembly/#Name)] ">
<xsl:value-of select="concat(#name,'
')"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<buildlog>
<Projects>
<project name="project1.csproj" />
<project name="project2.csproj" />
<project name="project3.csproj" />
<project name="project4.csproj" />
</Projects>
<Root>
<Assembly Name="project1" />
<Assembly Name="project2" />
</Root>
</buildlog>
the wanted result is produced:
project3.csproj
project4.csproj
In case more efficiency becomes really necessary, one should use keys.