I want to be able to change "VarName1" to select a different month and not have to add each as a separate block of code.
Here is an example of the xml data I have:
<Year1998>
<MonthJan>
<Day1>1st</Day1>
<Day2>2nd</Day2>
...
</MonthJan>
<MonthFeb>
<Day1>1st</Day1>
<Day2>2nd</Day2>
...
</MonthFeb>
...
</Year1988>
I have tried the following with "VarName1" as a global variable but it doesn't show the values stored:
<xsl:variable name="VarName1" select="MonthJan" />
...
<xsl:for-each select="Year1988">
<tr>
<td><xsl:value-of select="$VarName1/Day1" /></td>
...
</tr>
</xsl:for-each>
This works, but I can't use a global variable:
<xsl:for-each select="Year1988">
<xsl:variable name="VarName1" select="MonthJan" />
<tr>
<td><xsl:value-of select="$VarName1/Day1" /></td>
...
</tr>
</xsl:for-each>
The XML you show is a perfect example of how one can shoot themselves in the foot with bad design decisions.
You can make somewhat dynamic XPath by using the name() function:
<xsl:variable name="MonthName" value="'MonthJan'" />
<xsl:value-of select="//*[name() = $MonthName]/Day1" />
but this is klunky, unwieldy, and - at the bottom line - unnecessary.
I strongly recommend that you change your XML structure into something sane instead.
<Year num="1998">
<Month num="1" name="Feb">
<Day num="1">1st</Day>
<Day num="2>2nd</Day>
...
</Month>
<Month num="2" name="Feb">
<Day num="1">1st</Day>
<Day num="2">2nd</Day>
...
</Month>
...
</Year>
and things will become much easier to handle immediately.
You can try This
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" omit-xml-declaration="no"/>
<xsl:variable name="VarName1" select="Year1988/MonthJan"/>
<xsl:template match="/">
<xsl:for-each select="Year1988">
<tr>
<td><xsl:value-of select="$VarName1/Day1" /></td>
</tr>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Related
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>
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>
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>
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>
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.