XSLT 1.0 For Each if output - xslt

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>

Related

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: find first element above selected element

I want to get the first heading (h1) before a table in a docx.
I can get all headings with:
<xsl:template match="w:p[w:pPr/w:pStyle[#w:val='berschrift1']]">
<p>
<context>
<xsl:value-of select="." />
</context>
</p>
</xsl:template>
and I can also get all tables
<xsl:template match="w:tbl">
<p>
<table>
<xsl:value-of select="." />
</table>
</p>
</xsl:template>
But unfortunetly the processor does not accept
<xsl:template match="w:tbl/preceding-sibling::w:p[w:pPr/w:pStyle[#w:val='berschrift1']]">
<p>
<table>
<xsl:value-of select="." />
</table>
</p>
</xsl:template>
Here is a reduced XML file extracted from a docx: http://pastebin.com/KbUyzRVv
I want something like that as a result:
<context>Let’s get it on</context> <- my heading
<table>data</table>
<context>Let’s get it on</context> <- my heading
<table>data</table>
<context>We’re in the middle of something</context> <- my heading
<table>data</table>
Thanks to Daniel Haley I was able to find a solution for that problem. I'll post it here, so it is independend of the pastebin I postet below.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:v="urn:schemas-microsoft-com:vml" exclude-result-prefixes="xsl w v">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="w:tbl">
<context>
<xsl:value-of select="(preceding-sibling::w:p[w:pPr/w:pStyle[#w:val = 'berschrift1']])[last()]"/>
</context>
<table>
<xsl:value-of select="."/>
</table>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
Hard to answer without a Minimal, Complete, and Verifiable example, but try this:
<xsl:template match="w:tbl">
<p>
<table>
<xsl:value-of select="(preceding::w:p[w:pPr/w:pStyle[#w:val='berschrift1']])[last()]"/>
</table>
</p>
</xsl:template>
Assuming you can use XSLT 2.0 (and most people can, nowadays), I find a useful technique here is to have a global variable that selects all the relevant nodes:
<xsl:variable name="special"
select="//w:tbl/preceding-sibling::w:p[w:pPr/w:pStyle[#w:val='berschrift1']][1]"/>
and then use this variable in a template rule:
<xsl:template match="w:p[. intersect $special]"/>
In XSLT 3.0 you can reduce this to
<xsl:template match="$special"/>

xlst transformation to display departments of each company node

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>

Parse the XML Elements inside the CData Using XSLT and Print to HTML

Input xml is
<getArtifactContentResponse>
<return>
<![CDATA[
<metadata>
<overview>
<name>scannapp</name>
<developerId>developer702</developerId>
<stateId>2</stateId>
<serverURL>abc.com</serverURL>
<id>cspapp1103</id>
<description>scann doc</description>
<hostingTypeId>1</hostingTypeId>
</overview>
</metadata>
]]>
</return>
</getArtifactContentResponse>
Below is the stylesheet which I have developed. I am able to retrieve the XML inside Cdata but not able to fetch the elements value.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" >
<xsl:output method="html" encoding="utf-8" omit-xml-declaration="no" indent="no"/>
<xsl:template match="/">
<html>
<body>
<h1>Company Details</h1>
<table border="1">
<tr>
<th>name</th>
<th>developerId</th>
<th>Id</th>
</tr>table
<xsl:variable name ="data" select="//getArtifactContentResponse/return/node()" />
<tr>
<td>
<xsl:value-of select="$data/metadata/overview/name" disable-output-escaping="yes"/>
</td>
<td>
<xsl:value-of select="$data/metadata/overview/developerId" />
</td>
<td>
<xsl:value-of select="$data/metadata/overview/Id" />
</td>
</tr>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Output coming as
<html><body><h1>Company Details</h1><table border="1"><tr><th>name</th><th>developerId</th><th>serverURL</th></tr>table
<tr><td></td><td></td><td></td></tr></table></body></html>
Expected output
<html><body><h1>Company Details</h1><table border="1"><tr><th>name</th><th>developerId</th><th>serverURL</th></tr>table
<tr><td>scannapp</td><td>developer702</td><td>cspapp1103</td></tr></table></body></html>
I want to take the value name,developerId,Id and print to HtML. How to do that Please help me. Using XSLT version1.0.
AFAIK, there's no way to parse CDATA as XML.
Using an extension function to parse the section as text would be nice, but not really necessary. Here's an example that extracts the three items you're after:
...
<xsl:variable name ="cdata" select="/getArtifactContentResponse/return" />
<name>
<xsl:value-of select="substring-before(substring-after($cdata, '<name>'), '</name>')"/>
</name>
<developerId>
<xsl:value-of select="substring-before(substring-after($cdata, '<developerId>'), '</developerId>')"/>
</developerId>
<id>
<xsl:value-of select="substring-before(substring-after($cdata, '<id>'), '</id>')"/>
</id>
...

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.