I need to group the following XML doc to show:
Parent Item Qty
----------------------------------
TopLevelAsy 1
SubAsy Part15 4
Top Assembly Part19 2
Top Assembly Part15 2
Top Assembly SubAsy 2
But what I get using my XSL is:
Parent Item Qty
----------------------------------
TopLevelAsy 1
SubAsy Part15 2
SubAsy Part15 2
Top Assembly Part19 2
Top Assembly Part15 2
Top Assembly SubAsy 2
Her is my XML:
<DOCUMENT>
<ProductRevision id="id41" name="Top Assembly" accessRefs="#id30" subType="ItemRevision" masterRef="#id47" revision="A"></ProductRevision>
<ProductRevision id="id15" name="PartA-15" accessRefs="#id30" subType="ItemRevision" masterRef="#id36" revision="A"></ProductRevision>
<ProductRevision id="id19" name="PartB-19" accessRefs="#id30" subType="ItemRevision" masterRef="#id46" revision="A"></ProductRevision>
<ProductRevision id="id48" name="SubAsy" accessRefs="#id30" subType="ItemRevision" masterRef="#id76" revision="A"></ProductRevision>
<ProductView id="id4" ruleRefs="#id2" rootRefs="id7" primaryOccurrenceRef="id7">
<Occurrence id="id7" instancedRef="#id41" occurrenceRefs="id15 id11 id17 id16 id18 id21">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/"></ApplicationRef>
<data>
<title>TopLevelAsy</title>
<year>1985</year>
</data>
</Occurrence>
<Occurrence id="id11" instancedRef="#id19" parentRef="#id7">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/yBEAAAV4xLJc5D/"></ApplicationRef>
<data>
<title>Part19</title>
<year>1988</year>
</data>
</Occurrence>
<Occurrence id="id15" instancedRef="#id15" parentRef="#id7">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/sdljfjdkLJc5D/"></ApplicationRef>
<data>
<title>Part15</title>
<year>1988</year>
</data>
</Occurrence>
<Occurrence id="id17" instancedRef="#id19" parentRef="#id7">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/yBEAAAV4xLJc5D/"></ApplicationRef>
<data>
<title>Part19</title>
<year>1988</year>
</data>
</Occurrence>
<Occurrence id="id16" instancedRef="#id15" parentRef="#id7">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/sdljfjdkLJc5D/"></ApplicationRef>
<data>
<title>Part15</title>
<year>1988</year>
</data>
</Occurrence>
<!-- sub assembly Second occurrence -->
<Occurrence id="id21" instancedRef="#id48" parentRef="#id7" occurrenceRefs="id153 id135">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/wesdjdLJc5D/"></ApplicationRef>
<data>
<title>Sub Assembly</title>
<year>1985</year>
</data>
</Occurrence>
<Occurrence id="id153" instancedRef="#id15" parentRef="#id21">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/wesdjdLJc5D/jkdsdwV4xLJc5D/"></ApplicationRef>
<data>
<title>Part15</title>
<year>1988</year>
</data>
</Occurrence>
<Occurrence id="id135" instancedRef="#id15" parentRef="#id21">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/wesdjdLJc5D/jkdsdwV4xLJc5D/"></ApplicationRef>
<data>
<title>Part15</title>
<year>1988</year>
</data>
</Occurrence>
<!-- sub assembly first occurrence -->
<Occurrence id="id18" instancedRef="#id48" parentRef="#id7" occurrenceRefs="id53 id35">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/wesdjdLJc5D/"></ApplicationRef>
<data>
<title>Sub Assembly</title>
<year>1985</year>
</data>
</Occurrence>
<Occurrence id="id53" instancedRef="#id15" parentRef="#id18">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/wesdjdLJc5D/vdsfdwV4xLJc5D/"></ApplicationRef>
<data>
<title>Part15</title>
<year>1988</year>
</data>
</Occurrence>
<Occurrence id="id35" instancedRef="#id15" parentRef="#id18">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/wesdjdLJc5D/vdsfdwV4xLJc5D/"></ApplicationRef>
<data>
<title>Part15</title>
<year>1988</year>
</data>
</Occurrence>
</ProductView>
</DOCUMENT>
The XSLT i have written is
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="no" />
<!-- <xsl:key name="byref" match="Occurrence" use="#instancedRef"/> -->
<xsl:key name="byid" match="Occurrence" use="concat(#title,#instancedRef)" />
<xsl:key name="byRef" match="Occurrence" use="#instancedRef" />
<xsl:template match="/">
<table border="1">
<!-- generate the keys for instance occurance-->
<!-- generate the keys for parent id -->
<xsl:for-each select="DOCUMENT/ProductView/Occurrence[generate-id(.)=generate-id(key('byid', concat(#instancedRef,#title))[1])]">
<xsl:sort select="#parentRef" />
<xsl:variable name="pRef" select="#parentRef" />
<xsl:variable name="instRef" select="#instancedRef" />
<xsl:variable name="pdOccId" select="substring-after($pRef,'#')" />
<xsl:variable name="pdRevIdTag" select="//DOCUMENT/ProductView/Occurrence[#id=$pdOccId]/#instancedRef" />
<xsl:variable name="pdRevId" select="substring-after($pdRevIdTag,'#')" />
<xsl:variable name="parentlabeltag" select="ApplicationRef/#label" />
<tr>
<td>
<xsl:text>Parent: </xsl:text>
<xsl:value-of select="//DOCUMENT/ProductRevision[#id=$pdRevId]/#name" />
</td>
<td align="right">
<xsl:value-of select="data/title" />
<xsl:text> </xsl:text>
</td>
<td>
<xsl:value-of select="count(key('byid', concat(#instancedRef,#title)))" />
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Any help will be great. Please help me figure this out. Thank you.
George,
Here's the solution:
You need to use 2 xslts for the same. The first xslt will generate an easily parseable xml. The second xslt will use this xml as its input to achieve the desired result.
Xslt1:
Your xml will be the input to this xslt
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="bytitle" match="Occurrence" use="data/title"/>
<xsl:template match="/">
<root>
<xsl:for-each select="DOCUMENT/ProductView/Occurrence[not(#parentRef)]">
<xsl:element name="Data">
<xsl:attribute name="parentTitle" />
<xsl:attribute name="childTitle">
<xsl:value-of select="data/title"/>
</xsl:attribute>
</xsl:element>
</xsl:for-each>
<xsl:for-each select="DOCUMENT/ProductView/Occurrence[generate-id(.)=generate-id(key('bytitle', data/title)[1])]">
<xsl:sort select="#id"/>
<xsl:variable name="title" select="data/title" />
<xsl:variable name="driver" select="//DOCUMENT/ProductView/Occurrence[data/title = $title]/#id"/>
<xsl:for-each select="//DOCUMENT/ProductView/Occurrence[substring-after(#parentRef,'#') = $driver/.]">
<xsl:element name="Data">
<xsl:attribute name="parentTitle">
<xsl:value-of select="$title"/>
</xsl:attribute>
<xsl:attribute name="childTitle">
<xsl:value-of select="data/title"/>
</xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Output of this xslt:
<?xml version="1.0" encoding="utf-8"?>
<root>
<Data parentTitle="" childTitle="TopLevelAsy" />
<Data parentTitle="Sub Assembly" childTitle="Part15" />
<Data parentTitle="Sub Assembly" childTitle="Part15" />
<Data parentTitle="Sub Assembly" childTitle="Part15" />
<Data parentTitle="Sub Assembly" childTitle="Part15" />
<Data parentTitle="TopLevelAsy" childTitle="Part19" />
<Data parentTitle="TopLevelAsy" childTitle="Part15" />
<Data parentTitle="TopLevelAsy" childTitle="Part19" />
<Data parentTitle="TopLevelAsy" childTitle="Part15" />
<Data parentTitle="TopLevelAsy" childTitle="Sub Assembly" />
<Data parentTitle="TopLevelAsy" childTitle="Sub Assembly" />
</root>
Xslt2:
The output of xslt 1 should be the input to this xslt
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:key name="bytitle" match="Data" use="concat(#parentTitle, #childTitle)"/>
<xsl:template match="/">
<table border="1">
<xsl:for-each select="/root/Data[generate-id(.)=generate-id(key('bytitle', concat(#parentTitle, #childTitle))[1])]">
<xsl:sort select="#parentTitle"/>
<xsl:variable name="parentTitle" select="#parentTitle"/>
<xsl:variable name="childTitle" select="#childTitle"/>
<tr>
<td>
<xsl:value-of select="#parentTitle"/>
</td>
<td>
<xsl:value-of select="#childTitle"/>
</td>
<td>
<xsl:value-of select="count(//root/Data[#parentTitle=$parentTitle and #childTitle = $childTitle])"/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Output:
TopLevelAsy 1
Sub Assembly Part15 4
TopLevelAsy Part19 2
TopLevelAsy Part15 2
TopLevelAsy Sub Assembly 2
George, you are getting 2 rows for sub assembly as there are two elements for sub-assembly with different ids (id21 and id18) and out of the four Part15s, two belong to id21 and two belong to id18.
Since you are grouping according to the parent id, your output seems correct. If you want to have both the sub assembly elements grouped, then you need to specify the parent title rather than parent id in your concatenated key.
I just tweaked your xslt to see the grouping. This is how they are grouped. As you can see the Parent Ref for the 2nd and 3rd rows are different
Instance Ref:#id41 Parent Ref: Parent: TopLevelAsy 1
Instance Ref:#id15 Parent Ref:#id18 Parent: SubAsy Part15 2
Instance Ref:#id15 Parent Ref:#id21 Parent: SubAsy Part15 2
Instance Ref:#id19 Parent Ref:#id7 Parent: Top Assembly Part19 2
Instance Ref:#id15 Parent Ref:#id7 Parent: Top Assembly Part15 2
Instance Ref:#id48 Parent Ref:#id7 Parent: Top Assembly Sub Assembly 2
Edit
This is not a complete solution but you might have to do something on these lines:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="no"/>
<!-- <xsl:key name="byref" match="Occurrence" use="#instancedRef"/> -->
<xsl:key name="bytitle" match="Occurrence" use="data/title"/>
<xsl:template match="/">
<table border="1">
<xsl:for-each select="DOCUMENT/ProductView/Occurrence[generate-id(.)=generate-id(key('bytitle', data/title)[1])]">
<xsl:sort select="#id"/>
<xsl:variable name="title" select="data/title" />
<br />
Title:<xsl:value-of select="$title"/>
<xsl:variable name="driver" select="//DOCUMENT/ProductView/Occurrence[data/title = $title]/#id"/>
Driver:<xsl:value-of select="$driver/."/>
<!--<xsl:for-each select ="$driver">
<xsl:value-of select="."/>
</xsl:for-each>-->
<xsl:for-each select="//DOCUMENT/ProductView/Occurrence[substring-after(#parentRef,'#') = $driver/.]">
Child:<xsl:value-of select="data/title"/>
</xsl:for-each>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Related
Excuse my ignorance. I am just beginning XSL and XML transformations.
I receive xml data from a vendor.
I only need to include certain "ids" in my transformation.
I also need to add a "display name" based on the ID to the final output.
I would be able to manual add the ID and Display names necessary into the XSL.
XML ex.
<root>
<DATA>
<ID>rd_bl</ID>
<travel>15</travel<
<delay>7</delay>
</DATA>
<DATA>
<ID>yl_gr</ID>
<travel>18</travel<
<delay>9</delay>
</DATA>
<DATA>
<ID>pu_gr</ID>
<travel>17</travel<
<delay>6</delay>
</DATA>
</root>
I would like to write a list of IDs and "display names" in the xsl - only the records with the listed IDs would be included.
ID - Display Name
rd_bl - Red to Blue
pu_gr - Purple to Green
In this example the data from yl_gr would be ignored and not show up in the transformation.
Any help is greatly appreciated.
Thanks!
Here’s a simple stylesheet that checks whether an ID is within an approved list of IDs and uses a “display name” for it in the output.
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:variable name="desired-ids">
<id name="rd_bl">Red to Blue</id>
<id name="pu_gr">Purple to Green</id>
</xsl:variable>
<xsl:template match="root">
<root>
<xsl:apply-templates />
</root>
</xsl:template>
<xsl:template match="DATA">
<xsl:variable name="current-id" select="ID/text()" />
<xsl:if test="$desired-ids/id[#name=$current-id]">
<entry>
<name>
<xsl:value-of select="$desired-ids/id[#name=$current-id]" />
</name>
<travel>
<xsl:value-of select="travel" />
</travel>
<delay>
<xsl:value-of select="delay" />
</delay>
</entry>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Output using your example XML after correcting the closing tag errors:
<root>
<entry>
<name>Red to Blue</name>
<travel>15</travel>
<delay>7</delay>
</entry>
<entry>
<name>Purple to Green</name>
<travel>17</travel>
<delay>6</delay>
</entry>
</root>
EDIT: in case you’re stuck with XSL 1.0:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:variable name="desired-ids">rd_bl="Red to Blue" pu_gr="Purple to Green"</xsl:variable>
<xsl:template match="root">
<root>
<xsl:apply-templates />
</root>
</xsl:template>
<xsl:template match="DATA">
<xsl:variable name="current-id" select="ID/text()" />
<xsl:variable name="id-with-equals" select="concat($current-id, '=')" />
<xsl:if test="contains($desired-ids, $id-with-equals)">
<xsl:variable name="id-with-open-quote" select="concat($id-with-equals, '"')" />
<xsl:variable name="display-name" select="substring-before(substring-after($desired-ids, $id-with-open-quote), '"')" />
<entry>
<name>
<xsl:value-of select="$display-name" />
</name>
<travel>
<xsl:value-of select="travel" />
</travel>
<delay>
<xsl:value-of select="delay" />
</delay>
</entry>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
You can see this is much less elegant, it uses awkward string-matching to check for a valid ID and extract the display name.
Will this stylesheet help?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:template match="/">
<table>
<thead>
<th>ID</th>
<th>Display Name</th>
</thead>
<tbody>
<xsl:apply-templates select="root/DATA"/>
</tbody>
</table>
</xsl:template>
<xsl:template match="DATA">
<xsl:choose>
<xsl:when test="ID='rd_bl'">
<tr>
<td><xsl:value-of select="ID"/></td>
<td>Red to Blue</td>
</tr>
</xsl:when>
<xsl:when test="ID='pu_gr'">
<tr>
<td><xsl:value-of select="ID"/></td>
<td>Purple to Green</td>
</tr>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I can't figure out how create xsl to group some nodes between other nodes. Basically, everytime I see a 'SPLIT' I have to end the div and create a new one.
The xml looks like this:
<data name="a" />
<data name="b" />
<data name="c" />
<data name="SPLIT" />
<data name="d" />
<data name="e" />
<data name="SPLIT" />
<data name="f" />
<data name="g" />
<data name="h" />
The output needs to look like this
<div>
a
b
c
</div>
<div>
d
e
</div>
<div>
f
g
h
</div>
I know how to do this by 'cheating', but would like to know if there is a proper way to do it:
<div>
<xsl:for-each select="data">
<xsl:choose>
<xsl:when test="#name='SPLIT'">
<xsl:text disable-output-escaping="yes"> </div> <div></xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#name"/>
</xsl:otherwise>
</xsl:for-each>
</div>
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:apply-templates
select="node()[1]|following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="data">
<div>
<xsl:call-template name="open"/>
</div>
<xsl:apply-templates
select="following-sibling::data[#name='SPLIT'][1]
/following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="data" mode="open" name="open">
<xsl:value-of select="concat(#name,'
')"/>
<xsl:apply-templates select="following-sibling::node()[1]"
mode="open"/>
</xsl:template>
<xsl:template match="data[#name='SPLIT']" mode="open"/>
</xsl:stylesheet>
Output:
<div>
a
b
c
</div>
<div>
d
e
</div>
<div>
f
g
h
</div>
Note: Fine grained traversal.
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kFollowing" match="data[not(#name='SPLIT')]"
use="generate-id(preceding-sibling::data[#name='SPLIT'][1])"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="data[#name='SPLIT']" name="regularSplit">
<div>
<xsl:apply-templates mode="copy"
select="key('kFollowing', generate-id())"/>
</div>
</xsl:template>
<xsl:template match="data[#name='SPLIT'][
not(preceding-sibling::data[#name='SPLIT'])]">
<div>
<xsl:apply-templates mode="copy"
select="preceding-sibling::data"/>
</div>
<xsl:call-template name="regularSplit"/>
</xsl:template>
<xsl:template match="*" mode="copy">
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="data[not(#name='SPLIT')]"/>
</xsl:stylesheet>
when applied on the provided XML document (wrapped into a top element to become well-formed):
<t>
<data name="a" />
<data name="b" />
<data name="c" />
<data name="SPLIT" />
<data name="d" />
<data name="e" />
<data name="SPLIT" />
<data name="f" />
<data name="g" />
<data name="h" />
</t>
produces the wanted, correct result:
<t>
<div>
<data name="a"></data>
<data name="b"></data>
<data name="c"></data>
</div>
<div>
<data name="d"></data>
<data name="e"></data>
</div>
<div>
<data name="f"></data>
<data name="g"></data>
<data name="h"></data>
</div>
</t>
Do note: Keys are used to specify conveniently and verry efficiently all "non-SPLIT" elements that follow immediately a "SPLIT" element.
I've created a menu in umbraco using XSLT. The menu is using the usual ul and li elements and I'm displaying only the first level of the menu. The aim is to create a menu that expands to show the sub menu when I click a parent node (in the top level).
I am after the xslt I would need to expose the sub menu when clicked.
I think I would need to make use of ancestor-or-self to detect the current menu and parent menu and display them and also the $currentPage variable.
I have the following xslt:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp " "> ]>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxml="urn:schemas-microsoft-com:xslt"
xmlns:umbraco.library="urn:umbraco.library" xmlns:Exslt.ExsltCommon="urn:Exslt.ExsltCommon" xmlns:Exslt.ExsltDatesAndTimes="urn:Exslt.ExsltDatesAndTimes" xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath" xmlns:Exslt.ExsltRegularExpressions="urn:Exslt.ExsltRegularExpressions" xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings" xmlns:Exslt.ExsltSets="urn:Exslt.ExsltSets" xmlns:tagsLib="urn:tagsLib" xmlns:urlLib="urn:urlLib"
exclude-result-prefixes="msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets tagsLib urlLib ">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:param name="currentPage"/>
<xsl:template match="/">
<div id="kb-categories">
<h3>Categories</h3>
<xsl:call-template name="drawNodes">
<xsl:with-param name="parent" select="$currentPage/ancestor-or-self::node [#level=1]"/>
</xsl:call-template>
</div>
</xsl:template>
<xsl:template name="drawNodes">
<xsl:param name="parent"/>
<xsl:if test="(umbraco.library:IsProtected($parent/#id, $parent/#path) = 0 or (umbraco.library:IsProtected($parent/#id, $parent/#path) = 1)) and $parent/#level = 1">
<ul class="kb-menuLevel1" >
<xsl:for-each select="$parent/node [string(./data [#alias='showInMenu']) = 1]">
<li>
<a href="/kb{umbraco.library:NiceUrl(#id)}">
<xsl:value-of select="#nodeName"/>
</a>
<xsl:variable name="level" select="#level" />
<xsl:if test="(count(./node [string(./data [#alias='showInMenu']) = '1']) > 0)">
<xsl:call-template name="drawNodes">
<xsl:with-param name="parent" select="."/>
</xsl:call-template>
</xsl:if>
</li>
</xsl:for-each>
</ul>
</xsl:if>
<xsl:if test="(umbraco.library:IsProtected($parent/#id, $parent/#path) = 0 or (umbraco.library:IsProtected($parent/#id, $parent/#path) = 1)) and $parent/#level > 1">
<ul class="kb-menuLevel{#level}" style="display: none;">
<xsl:for-each select="$parent/node [string(./data [#alias='showInMenu']) = 1]">
<li>
<a href="/kb{umbraco.library:NiceUrl(#id)}">
<xsl:value-of select="#nodeName"/>
</a>
<xsl:variable name="level" select="#level" />
<xsl:if test="(count(./node [string(./data [#alias='showInMenu']) = '1']) > 0)">
<xsl:call-template name="drawNodes">
<xsl:with-param name="parent" select="."/>
</xsl:call-template>
</xsl:if>
</li>
</xsl:for-each>
</ul>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I suspect this could be improved using apply-templates, but I'm not yet up to speed with that (this being only the second day of my learning xslt).
My menu:
Menu Item 1
Menu Item 2
Menu Item 3
Menu Item 4
when I click on Menu Item 2 I will be taken to the page for menu Item 2 and the submenu will also be displayed:
Menu Item 1
Menu Item 2
-- Menu Item 2.1
-- Menu Item 2.2
Menu Item 3
Menu Item 4
and so on down the nested menu.
Here is some sample xml for the above.
<root>
<node id="1" nodeTypeAlias="kbHomepage" nodeName="Home" level="1">
<data alias="introduction">
<![CDATA[<p>Welcome</p>]]>
</data>
<node id="2" nodeTypeAlias="guide" nodeName="Menu Item 1" level="2">
<data alias="bodyText">
<![CDATA[<p>This is some text</p>]]>
</data>
<data alias="showInMenu">1</data>
<data alias="menuName">Menu Item 1</data>
</node>
<node id="3" nodeTypeAlias="guide" nodeName="Menu Item 2" level="2">
<data alias="bodyText">
<![CDATA[<p>This is some text</p>]]>
</data>
<data alias="showInMenu">1</data>
<data alias="menuName">Menu Item 2</data>
<node id="4" nodeTypeAlias="guide" nodeName="Menu Item 2.1" level="3">
<data alias="bodyText">
<![CDATA[<p>Some Text</p>]]>
</data>
<data alias="showInMenu">1</data>
<data alias="menuName">Menu Item 2.1</data>
</node>
<node id="5" nodeTypeAlias="guide" nodeName="Menu Item 2.2" level="3">
<data alias="bodyText">
<![CDATA[<p>Some Text</p>]]>
</data>
<data alias="showInMenu">1</data>
<data alias="menuName">Menu Item 2.2</data>
<node id="6" nodeTypeAlias="guide" nodeName="Item 2.2.1 Guide" level="4">
<data alias="bodyText">
<![CDATA[<p>Some Text</p>]]>
</data>
<data alias="showInMenu">0</data>
<data alias="menuName"></data>
</node>
</node>
</node>
<node id="8" nodeTypeAlias="guide" nodeName="Menu Item 3" level="2">
<data alias="bodyText">
<![CDATA[<p>This is some text</p>]]>
</data>
<data alias="showInMenu">1</data>
<data alias="menuName">Menu Item 3</data>
</node>
<node id="9" nodeTypeAlias="guide" nodeName="Menu Item 4" level="2">
<data alias="bodyText">
<![CDATA[<p>This is some text</p>]]>
</data>
<data alias="showInMenu">1</data>
<data alias="menuName">Menu Item 4</data>
</node>
</node>
<node id="7" nodeTypeAlias="someAlias" nodeName="Some Other Page" level="1">
<data alias="bodyText">
<![CDATA[<p>This is some text</p>]]>
</data>
</node>
</root>
edit: the following almost does what I need :
<xsl:variable name="visibleChidren" select="node[data[#alias='showInMenu'] = 1 and (#level = 2 or descendant-or-self::*[generate-id($currentPage) = generate-id(.)] or preceding-sibling::*[generate-id($currentPage) = generate-id(.)] or following-sibling::*[generate-id($currentPage) = generate-id(.)])]" />
I just need to also include the direct children from the current page.
I tried (with my very limited knowledge about Umbraco) to clean up your code a bit and remove the redundancy. It looks as though it would work with the XML sample you provided, but I cannot really test it against Umbraco.
<!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp " "> ]>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxml="urn:schemas-microsoft-com:xslt"
xmlns:umbraco.library="urn:umbraco.library" xmlns:Exslt.ExsltCommon="urn:Exslt.ExsltCommon" xmlns:Exslt.ExsltDatesAndTimes="urn:Exslt.ExsltDatesAndTimes" xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath" xmlns:Exslt.ExsltRegularExpressions="urn:Exslt.ExsltRegularExpressions" xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings" xmlns:Exslt.ExsltSets="urn:Exslt.ExsltSets" xmlns:tagsLib="urn:tagsLib" xmlns:urlLib="urn:urlLib"
exclude-result-prefixes="msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets tagsLib urlLib ">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="utf-8" />
<xsl:param name="currentPage" />
<xsl:template match="/">
<div id="kb-categories">
<h3>Categories</h3>
<xsl:apply-templates mode="list" select="/root/node[#nodeTypeAlias='kbHomepage']" />
</div>
</xsl:template>
<!-- matches anything with <node> children and creates an <ul> -->
<xsl:template match="*[node]" mode="list">
<!-- prepare a list of all visible children -->
<xsl:variable name="visibleChidren" select="node[
data[#alias='showInMenu'] = 1
and (
not(umbraco.library:IsProtected(#id, #path))
or umbraco.library:IsLoggedOn()
)
]" />
<!-- prepare a CSS class for the "selected path" -->
<xsl:variable name="display">
<xsl:if test=".//node[generate-id() = generate-id($currentPage)]">
<xsl:text>visible</xsl:text>
</xsl:if>
</xsl:variable>
<xsl:if test="$visibleChidren">
<ul class="menu kb-menuLevel{$visibleChidren[1]/#level} {$display}">
<xsl:apply-templates mode="item" select="$visibleChidren" />
</ul>
</xsl:if>
</xsl:template>
<!-- matches <node> elements and turns them into list items -->
<xsl:template match="node" mode="item">
<li>
<xsl:if test="generate-id() = generate-id($currentPage)">
<xsl:attribute name="class">selected</xsl:attribute>
</xsl:if>
<a href="/kb{{umbraco.library:NiceUrl(#id)}}">
<xsl:value-of select="#nodeName" />
</a>
<!-- if there are any child nodes, render them -->
<xsl:if test="node">
<xsl:apply-templates mode="list" select="." />
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>
Gives you the following. Note that I have escaped the attribute value template in <a href... - remove the double curlies above to enable them again:
<div id="kb-categories">
<h3>Categories</h3>
<ul class="menu kb-menuLevel2 visible">
<li>
Menu Item 1
</li>
<li>
Menu Item 2
<ul class="menu kb-menuLevel3 visible">
<li class="selected">
Menu Item 2.1
</li>
<li>
Menu Item 2.2
</li>
</ul>
</li>
<li>
Menu Item 3
</li>
<li>
Menu Item 4
</li>
</ul>
</div>
Now you could do in CSS:
ul.menu {
display: hidden;
}
ul.menu.visible {
display: block;
}
ul.menu li.selected {
font-weight: bold;
}
Does that help you?
I figured out what I need to do what I want. The key line being:
<xsl:variable name="visibleChidren" select="node[data[#alias='showInMenu'] = 1 and (#level = 2 or descendant-or-self::*[generate-id($currentPage) = generate-id(.)] or preceding-sibling::*[generate-id($currentPage) = generate-id(.)] or following-sibling::*[generate-id($currentPage) = generate-id(.)] or parent::*[generate-id($currentPage) = generate-id(.)])]" />
From the entire xslt:
<!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp " "> ]>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxml="urn:schemas-microsoft-com:xslt"
xmlns:umbraco.library="urn:umbraco.library" xmlns:Exslt.ExsltCommon="urn:Exslt.ExsltCommon" xmlns:Exslt.ExsltDatesAndTimes="urn:Exslt.ExsltDatesAndTimes" xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath" xmlns:Exslt.ExsltRegularExpressions="urn:Exslt.ExsltRegularExpressions" xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings" xmlns:Exslt.ExsltSets="urn:Exslt.ExsltSets" xmlns:tagsLib="urn:tagsLib" xmlns:urlLib="urn:urlLib"
exclude-result-prefixes="msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets tagsLib urlLib ">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:param name="currentPage"/>
<xsl:variable name="currentLevel" select="$currentPage/#level" />
<xsl:template match="/">
<div id="kb-categories">
<h3>Categories</h3>
<xsl:apply-templates mode="list" select="$currentPage/ancestor-or-self::node [#nodeTypeAlias = 'kbHomepage']" />
</div>
</xsl:template>
<!-- matches anything with <node> children and makes a list out of them -->
<xsl:template match="node" mode="list">
<!-- select only sub-nodes that have 'showInMenu' = 1 -->
<xsl:variable name="visibleChidren" select="node[data[#alias='showInMenu'] = 1 and (#level = 2 or descendant-or-self::*[generate-id($currentPage) = generate-id(.)] or preceding-sibling::*[generate-id($currentPage) = generate-id(.)] or following-sibling::*[generate-id($currentPage) = generate-id(.)] or parent::*[generate-id($currentPage) = generate-id(.)])]" />
<xsl:if test="$visibleChidren">
<ul>
<xsl:apply-templates mode="item" select="$visibleChidren" />
</ul>
</xsl:if>
</xsl:template>
<xsl:template match="node" mode="item">
<li>
<a href="/kb{umbraco.library:NiceUrl(#id)}">
<xsl:value-of select="#nodeName"/>
</a>
<xsl:apply-templates mode="list" select="." />
</li>
</xsl:template>
</xsl:stylesheet>
Or you could solve yourself a lot of hacking about in XSLT and use the following navigation package from our.umbraco.org
This I think does everything you need and no need to get your hands dirty in the murky world of XSLT.
http://our.umbraco.org/projects/cogworks---flexible-navigation
I have a problem. I get the data from xml then transform it with xslt.
Let us say I have a xml file:
<?xml version="1.0"?>
<root>
<row id="1" fname="Dan" lname="Wahlin">
<address type="home">
<street>1234 Anywhere St.</street>
<city>AnyTown</city>
<zip>85789</zip>
</address>
<address type="business">
<street>1234 LottaWork Ave.</street>
<city>AnyTown</city>
<zip>85786</zip>
</address>
</row>
<row id="2" fname="Elaine" lname="Wahlin">
<address type="home">
<street>1234 Anywhere St.</street>
<city>AnyTown</city>
<zip>85789</zip>
</address>
<address type="business">
<street>1233 Books Way</street>
<city>AnyTown</city>
<zip>85784</zip>
</address>
</row>
</root>
And this stylesheet:
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" encoding="utf-8" omit-xml-declaration="no"/>
<xsl:template match="/">
<root>
<xsl:apply-templates/>
</root>
</xsl:template>
<xsl:template match="row">
<row>
<xsl:attribute name="id">
<xsl:value-of select="id"/>
</xsl:attribute>
<xsl:attribute name="fname">
<xsl:value-of select="name/fname"/>
</xsl:attribute>
<xsl:attribute name="lname">
<xsl:value-of select="name/lname"/>
</xsl:attribute>
<xsl:for-each select="address">
<xsl:copy-of select="."/>
</xsl:for-each> </row>
</xsl:template>
</xsl:stylesheet
How can limit this to 3 records, then after 3 records it create a tr tag?
For example:
<table>
<tr>
<td>Address1</td>
<td>Address2</td>
<td>Address3</td>
</tr>
<tr>
<td>Address4</td>
<td>Address5</td>
<td>Address6</td>
</tr>
</table
try something like
<xsl:for-each select="PATH">
<xsl:variable name="pos" select="position() mod 3" />
</xsl:for-each>
then you can work with
<xsl:if test="$pos = 0">
</xsl:if>
and
<xsl:if test="$pos != 0">
</xsl:if>
if $pos = 0 means that you reached the 3rd row
Here are some good resources to learn more about XSLT and XPath
http://w3schools.com/xsl/default.asp
http://w3schools.com/xpath/default.asp
I have this XML file and I want to create an XSL file to convert it to Excel. Each row should represent a logo. The columns will be the key attributes like color, id, description plus any other key for other logos.
<Top>
<logo>
<field key="id">172-32-1176</field>
<field key="color">Blue</field>
<field key="description"><p>Short Description</p></field>
<field key="startdate">408 496-7223</field>
</logo>
<logo>
<field key="id">111-111-111</field>
<field key="color">Red</field>
</logo>
<!-- ... -->
</Top>
The XSL file is something like this:
<xsl:stylesheet
version="1.0"
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:user="urn:my-scripts"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
>
<xsl:template match="/">
<Workbook
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:html="http://www.w3.org/TR/REC-html40"
>
<xsl:apply-templates/>
</Workbook>
</xsl:template>
<xsl:template match="/*">
<Worksheet>
<xsl:attribute name="ss:Name">
<xsl:value-of> select="local-name(/*)"/>
</xsl:attribute>
<Table x:FullColumns="1" x:FullRows="1">
<Row>
<xsl:for-each select="*/*">
<Cell>
<Data ss:Type="String">
<xsl:value-of select="#key"/>
</Data>
</Cell>
</xsl:for-each>
</Row>
<xsl:apply-templates/>
</Table>
</Worksheet>
</xsl:template>
<xsl:template match="/*/*">
<Row>
<xsl:apply-templates/>
</Row>
</xsl:template>
<xsl:template match="/*/*/*">
<Cell>
<Data ss:Type="String">
<xsl:value-of select="."/>
</Data>
</Cell>
<!-- <xsl:apply-templates/> -->
</xsl:template>
</xsl:stylesheet>
But data are not correctly placed under the columns and column names are repeating. How can this be done?
The columns could be in any order and also column stardate should be empty for second row in excel. Similarly for more .
You were very close. Try to be more specific when it comes to template matching - don't say template match"/*/*/*" when you can say template match="field".
Other than that, this is your approach, only slightly modified:
<xsl:stylesheet
version="1.0"
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:user="urn:my-scripts"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
>
<xsl:output method="xml" encoding="utf-8" indent="yes" />
<xsl:template match="/">
<Workbook
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:html="http://www.w3.org/TR/REC-html40"
>
<xsl:apply-templates select="Top" />
</Workbook>
</xsl:template>
<xsl:template match="Top">
<Worksheet ss:Name="{local-name()}">
<Table x:FullColumns="1" x:FullRows="1">
<Row>
<!-- header row, made from the first logo -->
<xsl:apply-templates select="logo[1]/field/#key" />
</Row>
<xsl:apply-templates select="logo" />
</Table>
</Worksheet>
</xsl:template>
<!-- a <logo> will turn into a <Row> -->
<xsl:template match="logo">
<Row>
<xsl:apply-templates select="field" />
</Row>
</xsl:template>
<!-- convenience: <field> and #key both turn into a <Cell> -->
<xsl:template match="field | field/#key">
<Cell>
<Data ss:Type="String">
<xsl:value-of select="."/>
</Data>
</Cell>
</xsl:template>
</xsl:stylesheet>
Your "repeating column names" problem roots in this expression:
<xsl:for-each select="*/*">
In your context, this selects any third level element in the document (literally all <field> nodes in all <logo>s), and makes a header row out of them. I replaced it with
<xsl:apply-templates select="logo[1]/field/#key" />
which makes a header row out of the first <logo> only.
If a certain column order is required (other than document order) or not all <field> nodes are in the same order for all <logo>s, things get more complex. Tell me if you need that.