Jump to content

Accessing multiple nodes in for each loops


ColtSanderus

Recommended Posts

Hi,I would like to parse data to a html table for each z:row the attributes defined by "AttributeType name" in the top of the same xml document.I tried something like:$activedatanode[@*[name()=$nodename]]"/to read out an attribute for all rows. The next loop should read out the second attribute for all rows. Does anybody have a better idea?XML:<xml xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882' xmlns:dt='uuid:C2F41010-65B3-11d1-A29F-00AA00C14882' xmlns:rs='urn:schemas-microsoft-com:rowset' xmlns:z='#RowsetSchema'><s:Schema id='RowsetSchema'> <s:ElementType name='row' content='eltOnly' rs:updatable='true'> <s:AttributeType name='Date' rs:number='1' rs:write='true'> <s:datatype dt:type='string' dt:maxLength='255' rs:precision='0' rs:maybenull='false'/> </s:AttributeType> <s:AttributeType name='Time' rs:number='2' rs:write='true'> <s:datatype dt:type='string' dt:maxLength='255' rs:precision='0' rs:maybenull='false'/> </s:AttributeType> <s:AttributeType name='TCC18' rs:number='3' rs:write='true'> <s:datatype dt:type='string' dt:maxLength='255' rs:precision='0' rs:maybenull='false'/> </s:AttributeType> <s:AttributeType name='TCC19' rs:number='4' rs:write='true'> <s:datatype dt:type='string' dt:maxLength='255' rs:precision='0' rs:maybenull='false'/> </s:AttributeType> <s:AttributeType name='TCC20' rs:number='5' rs:write='true'> <s:datatype dt:type='string' dt:maxLength='255' rs:precision='0' rs:maybenull='false'/> </s:AttributeType> <s:extends type='rs:rowbase'/> </s:ElementType></s:Schema><rs:data> <rs:insert> <z:row Date='13.05.2006' Time='09:15:20' TCC18='50' TCC19='40' TCC20='30'/> <z:row Date='13.05.2006' Time='09:15:22' TCC18='60' TCC19='40' TCC20='10'/> <z:row Date='13.05.2006' Time='09:15:23' TCC18='70' TCC19='70' TCC20='30'/> <z:row Date='13.05.2006' Time='09:15:24' TCC18='80' TCC19='40' TCC20='30'/></xml>XSLT: <xsl:for-each select="/xml/rs:data/rs:insert/z:row" > <xsl:variable name="activedatanode" select="."/> <tr> <xsl:for-each select="/xml/s:Schema/s:ElementType/s:AttributeType"> <td> <xsl:variable name="nodename" select="@name"/> <xsl:value-of select="$activedatanode[@*[name()=$nodename]]"/> </td> </xsl:for-each> </tr> </xsl:for-each>

Link to comment
Share on other sites

I'm not sure I understand what you want... some samples of expected output will be good.A variable is converted to a string when used in value-of, so putting a predicate on it is really an error.To convert a variable to a node-set that you can then access, you can use the exsl:node-set() function or the msxml:node-set equivalent if you're using a Microsfot processor.

Link to comment
Share on other sites

I didn't explain the problem very well....... so....First I wrote this xslt:<?xml version='1.0'?><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:rs='urn:schemas-microsoft-com:rowset' xmlns:z='#RowsetSchema' xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882'> <xsl:template match="/"> <h1>Data table</h1> <table border="1" bgcolor="#ADFF2F"> <!-- Write all header names to the table --> <tr> <xsl:for-each select="xml/s:Schema/s:ElementType/s:AttributeType"> <xsl:element name="td"> <xsl:attribute name="class">heading</xsl:attribute> <xsl:value-of select="@name"/> </xsl:element> </xsl:for-each> </tr> <!-- Write data into the table --> <xsl:for-each select="/xml/rs:data/rs:insert/z:row" > <xsl:variable name="activedatanode" select="."/> <tr> <xsl:for-each select="/xml/s:Schema/s:ElementType/s:AttributeType"> <td> <xsl:value-of select="@Date"/> </td> <td> <xsl:value-of select="@Time"/> </td> <td> <xsl:value-of select="@TCC18"/> </td> <td> <xsl:value-of select="@TCC19"/> </td> <td> <xsl:value-of select="@TCC20"/> </td> </tr> </xsl:for-each> </table> </xsl:template></xsl:stylesheet>This example outputs the content of attributes named "Date", "Time", "@TCC18", etc. The extension I want to implement now is that these attributes "Date", "Time", ... aren't fix, but they should be read from the xml file in the <s:Schema id='RowsetSchema'> section.So I would like to replace the section: <td> <xsl:value-of select="@Date"/> </td> <td> <xsl:value-of select="@Time"/> </td> <td> <xsl:value-of select="@TCC18"/> </td> <td> <xsl:value-of select="@TCC19"/> </td> <td> <xsl:value-of select="@TCC20"/> </td>by something like: <xsl:for-each select="/xml/s:Schema/s:ElementType/s:AttributeType"> <td> <xsl:variable name="nodename" select="@name"/> <xsl:value-of select="$activedatanode[@*[name()=$nodename]]"/> </td> </xsl:for-each>But this seems not to be correct.....This is the output I need:<table><tr><td class="heading">Date</td><td class="heading">Time</td><td class="heading">TCC18</td><td class="heading">TCC19</td><td class="heading">TCC20</td></tr><tr><td>13.05.2006</td><td>09:15:20</td><td>50</td><td>40</td><td>30</td></tr><tr><td>13.05.2006</td><td>09:15:22</td><td>60</td><td>40</td><td>10</td></tr><tr><td>13.05.2006</td><td>09:15:23</td><td>70</td><td>70</td><td>30</td></tr><tr><td>13.05.2006</td><td>09:15:24</td><td>80</td><td>40</td><td>30</td></tr><tr><td>13.05.2006</td><td>09:15:25</td><td>90</td><td>40</td><td>50</td></tr></table>

Link to comment
Share on other sites

That's just it. It's not. I tried telling you that from the start. A variable is a converted to a single string, not a node-set.But wait... I think I see what you're trying to do. For the desired output, wouldn't this part of the XML be enough:

<z:row Date='13.05.2006' Time='09:15:20' TCC18='50' TCC19='40' TCC20='30'/><z:row Date='13.05.2006' Time='09:15:22' TCC18='60' TCC19='40' TCC20='10'/><z:row Date='13.05.2006' Time='09:15:23' TCC18='70' TCC19='70' TCC20='30'/><z:row Date='13.05.2006' Time='09:15:24' TCC18='80' TCC19='40' TCC20='30'/>

So you could simply use:

<table><thead><tr><xsl:for-each select="/xml/rs:data/rs:insert/z:row/@*"><th><xsl:value-of select="name()"/></th></xsl:for-each></tr></thead><tbody><xsl:for-each select="/xml/rs:data/rs:insert/z:row" ><tr><xsl:for-each select="/xml/rs:data/rs:insert/z:row/@*"><td><xsl:value-of select="."/></td></xsl:for-each></tr></xsl:for-each></tbody></table>

Link to comment
Share on other sites

Hi,Indeed... this could be a solution but....It is not guarenteed that each line holds all three measurements "TCCxx". Therefore all column headers should be read from the top part of the xml source.So I still need a 'something' to store a node in it, to increment it in the outer for-each loop...I will try the node-set() function.

Link to comment
Share on other sites

I was intrigued by this and did it two ways. The first way simply takes the attributes (names and data) from the z:row and builds the table. The second follows your original request to obtain the names from the s:attribute nodes and then build the table. Either way worksThe trick for each is breaking on the row for each time the z:row changes. For that I used a variation of the recursive table builder that I posted a while back. Try these and see if they fit your needs:This XML (non-essential data was removed)

<xml xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882'	xmlns:dt='uuid:C2F41010-65B3-11d1-A29F-00AA00C14882'	xmlns:rs='urn:schemas-microsoft-com:rowset'	xmlns:z='#RowsetSchema'><s:Schema id='RowsetSchema'><s:ElementType name='row' content='eltOnly' rs:updatable='true'><s:AttributeType name='Date' rs:number='1' rs:write='true'><s:datatype dt:type='string' dt:maxLength='255' rs:precision='0' rs:maybenull='false'/></s:AttributeType><s:AttributeType name='Time' rs:number='2' rs:write='true'><s:datatype dt:type='string' dt:maxLength='255' rs:precision='0' rs:maybenull='false'/></s:AttributeType><s:AttributeType name='TCC18' rs:number='3' rs:write='true'><s:datatype dt:type='string' dt:maxLength='255' rs:precision='0' rs:maybenull='false'/></s:AttributeType><s:AttributeType name='TCC19' rs:number='4' rs:write='true'><s:datatype dt:type='string' dt:maxLength='255' rs:precision='0' rs:maybenull='false'/></s:AttributeType><s:AttributeType name='TCC20' rs:number='5' rs:write='true'><s:datatype dt:type='string' dt:maxLength='255' rs:precision='0' rs:maybenull='false'/></s:AttributeType><s:extends type='rs:rowbase'/></s:ElementType></s:Schema><rs:insert>	<z:row Date='13.05.2006' Time='09:15:20' TCC18='50' TCC19='40' TCC20='30'/>	<z:row Date='13.05.2006' Time='09:15:22' TCC18='60' TCC19='40' TCC20='10'/>	<z:row Date='13.05.2006' Time='09:15:23' TCC18='70' TCC19='70' TCC20='30'/>	<z:row Date='13.05.2006' Time='09:15:24' TCC18='80' TCC19='40' TCC20='30'/></rs:insert></xml>

The first XSLT (my 1st choice, its simpler):

<xsl:stylesheet 	xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 	xmlns="http://maven.apache.org/POM/4.0.0"	xmlns:rs='urn:schemas-microsoft-com:rowset'	xmlns:z='#RowsetSchema'	xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882'	version="1.0">	<xsl:output method="html"/>			<xsl:template match="/">		<table border="1">			<tr><xsl:apply-templates select="//z:row[1]/@*" mode="heading"/></tr>			<xsl:call-template name="DoARow" />		</table>	</xsl:template>		<xsl:template name="DoARow">		<xsl:param name="RowNumber" select="1"/>						<xsl:param name="TotalRows" select="count(//z:row)" />			<tr><xsl:apply-templates select="//z:row[$RowNumber]/@*" mode="data"/></tr>		<xsl:if test="$RowNumber < $TotalRows">			<xsl:call-template name="DoARow" >				<xsl:with-param name="RowNumber" select="$RowNumber + 1"/>			</xsl:call-template>		</xsl:if>	</xsl:template>	<xsl:template match="@*" mode="heading">		<td><xsl:value-of select="name()"/></td>	</xsl:template>			<xsl:template match="@*" mode="data">		<td><xsl:value-of select="." /></td>	</xsl:template></xsl:stylesheet>

The second XSLT, which matches your original question of using the s:attribute nodes to obtain the names. It uses the node-set()

<xsl:stylesheet 	xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 	xmlns="http://maven.apache.org/POM/4.0.0"	xmlns:rs='urn:schemas-microsoft-com:rowset'	xmlns:z='#RowsetSchema'	xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882'	xmlns:msxsl="urn:schemas-microsoft-com:xslt"	version="1.0">	<xsl:output method="html"/>			<xsl:variable name="Attributes" select="//s:AttributeType" />			<xsl:template match="/">	  		<table border="1">	 		<tr><xsl:apply-templates select="msxsl:node-set($Attributes)/@*[name()='name']" /></tr>			  <xsl:call-template name="DoARow2" />		</table>		</xsl:template>	<xsl:template match="@*">		<td><xsl:value-of select="." /></td>	</xsl:template>	<xsl:template name="DoARow2">		<xsl:param name="RowNumber" select="1"/>						<xsl:param name="TotalRows" select="count(//z:row)" />			<tr><xsl:apply-templates select="//z:row[$RowNumber]"/></tr>		<xsl:if test="$RowNumber < $TotalRows">			<xsl:call-template name="DoARow2" >				<xsl:with-param name="RowNumber" select="$RowNumber + 1"/>			</xsl:call-template>		</xsl:if>	</xsl:template>	<xsl:template match="z:row">		<xsl:variable name="zrow" select="."/>		<xsl:for-each select="msxsl:node-set($Attributes)/@*[name()='name']">			<xsl:variable name="attrib" select="."/>			<td>				<xsl:value-of select="$zrow/@*[name() = $attrib]" />			</td>		</xsl:for-each>	</xsl:template></xsl:stylesheet>

This result (for both solutions):

Date Time TCC18 TCC19 TCC20 13.05.2006 09:15:20 50 40 30 13.05.2006 09:15:22 60 40 10 13.05.2006 09:15:23 70 70 30 13.05.2006 09:15:24 80 40 30

Link to comment
Share on other sites

Hi!Thanks for your advice!Your 2nd solution looks great. I already did something similar to your recursive loop to perform a for, next loop.Because I have been using XSLT only for 2 weeks now, I also found other new stuff in your example.But I was running your implementation in my Visual Web Developper 2005 and it generates this error:'msxsl:node-set()' has failedIs a namespace reference sufficient to 'implement' an external funtion?------------ begin error description ------------ Server Error in '/ParseXMLWithXSLTToSVG' Application.--------------------------------------------------------------------------------Cannot convert the operand to 'Result tree fragment'. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. Exception Details: System.Xml.Xsl.XsltException: Cannot convert the operand to 'Result tree fragment'.Source Error: An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below. Stack Trace: [XsltException: Cannot convert the operand to 'Result tree fragment'.] System.Xml.Xsl.XsltOld.XsltFunctionImpl.ToNavigator(Object argument) +109 System.Xml.Xsl.XsltOld.FuncNodeSet.Invoke(XsltContext xsltContext, Object[] args, XPathNavigator docContext) +49 MS.Internal.Xml.XPath.FunctionQuery.Evaluate(XPathNodeIterator nodeIterator) +362[XPathException: Function 'msxsl:node-set()' has failed.] MS.Internal.Xml.XPath.FunctionQuery.Evaluate(XPathNodeIterator nodeIterator) +436 MS.Internal.Xml.XPath.BaseAxisQuery.Evaluate(XPathNodeIterator nodeIterator) +44------------ end error description ------------

Link to comment
Share on other sites

Strange. I thought VS would be using MSXML (be it 3,4,5 or 6). This error is certanly a surprice.The first exception:[XsltException: Cannot convert the operand to 'Result tree fragment'.]is due to a small error in aalbetski's stylesheet. There's a msxsl:node-set() missing. It's:

<xsl:value-of select="msxsl:node-set($zrow)/@*[name() = $attrib]" />

This of course would only imply if msxsl:node-set() was supported to begin with. It won't make a difference in your case.Here's an exhanced solution of mine. Note that I'm aiming at "extension free" way:

<xsl:stylesheet 	xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 	xmlns="http://maven.apache.org/POM/4.0.0"	xmlns:rs='urn:schemas-microsoft-com:rowset'	xmlns:z='#RowsetSchema'	xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882'	version="1.0"><xsl:template match="/"><table><thead><tr><xsl:for-each select="//s:AttributeType/@name"><th><xsl:value-of select="."/></th></xsl:for-each></tr></thead><tbody><xsl:for-each select="/xml/rs:data/rs:insert/z:row"><xsl:variable name="currentPosition" select="@*/position()"/><xsl:variable name="data" select="@*[name() = string(//s:AttributeType[position() = $currentPosition]/@name)]"/><tr><xsl:for-each select="//s:AttributeType"><td><xsl:value-of select="$data"/></td></xsl:for-each></tr></xsl:for-each></tbody></table></xsl:template></xsl:stylesheet>

Would something like that work for you (note I haven't tested it)?

Link to comment
Share on other sites

just a clarification, the line that robot 'corrected' is not an error.

<xsl:value-of select="msxsl:node-set($zrow)/@*[name() = $attrib]" />
the original line is correct
<xsl:value-of select="$zrow/@*[name() = $attrib]" />

The $zrow variable referenced is the current node in the context of the template.

	<xsl:template match="z:row">--->>>>>		<xsl:variable name="zrow" select="."/>		<xsl:for-each select="msxsl:node-set($Attributes)/@*[name()='name']">			<xsl:variable name="attrib" select="."/>			<td>				<xsl:value-of select="$zrow/@*[name() = $attrib]" />			</td>		</xsl:for-each>	</xsl:template>

Additionally, your code will not work

<xsl:variable name="currentPosition" select="@*/position()"/>

The position() function is intended to return the value of the context position, it cannot be used to determine the position of an attribute as you attempted to do.

Link to comment
Share on other sites

Uhhh.... so what data type is it then :) ? Result tree fragments can't be accessed with XPath expressions the same way node sets can as far as I know. Context template or not, it's just not defined as a legal action I mean.

Link to comment
Share on other sites

Its a reference to a node-set, but it is not necessary to use the node-set() extension (even though your 'correction' works, my original text was not incorrect). I suggest that you copy the XSLT and XML (from my first post) and transform them. They work (at least they do using XMLDOM and Javascript, jury still out on VS 2005)

Link to comment
Share on other sites

I reproduced your error using ASP.NET 2.0 and discovered that the msxsl:node-set extension is no longer needed (or supported) by VS 2005. This revised code works fine. I have tested it and it renders the table just fine using VS 2005. If you are going to invoke the MSXML parser using new ActiveXObject (or CreateObject in vbscript) then you will still need the node-set extension.

<xsl:stylesheet 	xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 	xmlns="http://maven.apache.org/POM/4.0.0"	xmlns:rs='urn:schemas-microsoft-com:rowset'	xmlns:z='#RowsetSchema'	xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882'	version="1.0">	<xsl:output method="html"/>			<xsl:variable name="Attributes" select="//s:AttributeType" />			<xsl:template match="/">	  		<table border="1">			 <tr><xsl:apply-templates select="$Attributes/@*[name()='name']" /></tr>			  <xsl:call-template name="DoARow2" />		</table>		</xsl:template>	<xsl:template match="@*">		<td><xsl:value-of select="." /></td>	</xsl:template>	<xsl:template name="DoARow2">		<xsl:param name="RowNumber" select="1"/>						<xsl:param name="TotalRows" select="count(//z:row)" />			<tr><xsl:apply-templates select="//z:row[$RowNumber]"/></tr>		<xsl:if test="$RowNumber < $TotalRows">			<xsl:call-template name="DoARow2" >				<xsl:with-param name="RowNumber" select="$RowNumber + 1"/>			</xsl:call-template>		</xsl:if>	</xsl:template>	<xsl:template match="z:row">		<xsl:variable name="zrow" select="."/>		<xsl:for-each select="$Attributes/@*[name()='name']">			<xsl:variable name="attrib" select="."/>			<td>			 		<xsl:value-of select="$zrow/@*[name() = $attrib]" />			</td>		</xsl:for-each>	</xsl:template></xsl:stylesheet>

p.s. Just to clarify, I did run this in VS 2005 (albeit using deprecated methods, but thats a different problem) and it works OKHeres the ASP.NET code-behind

	protected void Page_Load(object sender, EventArgs e)	{		XslTransform xsl = new XslTransform();		xsl.Load(MapPath(@"items1.xsl"));		XmlDocument xml = new XmlDocument();		xml.Load(MapPath(@"items1.xml"));		this.Xml1.Document = xml;   		this.Xml1.Transform = xsl;	}

and yes, I know that these objects and methods are deprecated , but that wasn't the issue at hand

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...