XSLT - nested foreach Excel provides no output - excel

I am attempting to pull out some values and display an array of arrays in Excel dynamically. However, my nested for-each doesn't and I receive no output no matter what I try. I have, of course, left out some stuff within the sheet and data, so right now they seem as if they're unnecessarily nested, but that isn't the case.
XML:
<report>
<sheet>
<data>
<table>
<row>
<column value="Germany" />
<column value="Berlin" />
<column value="2321341" />
</row>
<row>
<column value="USA" />
<column value="Washington DC" />
<column value="11111" />
</row>
</table>
</data>
</sheet>
</report>
The XSL nested for-each I try to get to work. It works when I remove the inner for-each and try displaying the first value by doing <xsl:value-of select="column/#value">, so it creates two new rows, each containing the name of the country (which is the first element of the column list).
<xsl:for-each select="report/sheet/data/table/row">
<ss:Row>
<xsl:for-each select="row/columm">
<ss:Cell>
<ss:Data ss:Type="String">
<xsl:value-of select="#value" />
</ss:Data>
</ss:Cell>
</xsl:for-each>
</ss:Row>
</xsl:for-each>

First, you have a typo in:
<xsl:for-each select="row/columm">
The name of the element is column, not columm.
More importantly, the instruction:
<xsl:for-each select="report/sheet/data/table/row">
puts you in the context of row. From this context,
<xsl:for-each select="row/column">
selects nothing, because row is not a child of itself. It needs to be:
<xsl:for-each select="column">

Related

Update XSLT for XLSX

I have an old XSLT stylesheet that still works, but only transforms to Excel xls files. I've used ClosedXml to convert grid data to Excel xlsx files and it works great, but I have some formatted Excel documents that are not grids and require some formatting and layout. I would like to convert those old XSLT stylesheets to create the new(er) XLSX format files but only replacing
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
with
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/
doesn't work.
Has anyone updated an XSLT stylesheet to produce output to Excel xlsx? If yes, please share what you needed to do to accomplish it.
Here's the XSLT stylesheet that I'm trying to convert:
<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">
<Styles>
.. styles
</Styles>
<Worksheet ss:Name="name">
<Table ss:ExpandedColumnCount="9" x:FullColumns="1" x:FullRows="1" ss:DefaultRowHeight="13.2">
<Column ss:Index="2" ss:Width="77"/>
<Column ss:Index="5" ss:Width="54"/>
<Column ss:Index="8" ss:Width="178"/>
<xsl:for-each select="ReportRows/ReportRow">
<xsl:if test="position()=1">
<Row>
<Cell ss:MergeAcross="8" ss:StyleID="s30">
<Data ss:Type="String">
<xsl:value-of select="SE_Year" /> Title #Lab
</Data>
</Cell>
</Row>
</xsl:if>
<xsl:if test="Pr_Short_Name != Prev_Project">
<Row>
<Cell ss:MergeAcross="8" ss:StyleID="s24" />
</Row>
<Row>
<Cell ss:MergeAcross="8" ss:StyleID="s27">
<Data ss:Type="String">
<xsl:value-of select="Pr_Long_Name" />
</Data>
</Cell>
</Row>
<Row ss:AutoFitHeight="0">
cells...
</Row>
</xsl:if>
<xsl:if test="GR_Short_Name != Prev_SampleTypeGroup">
<Row>
<Cell ss:MergeAcross="8" ss:StyleID="s31" />
</Row>
<Row>
<Cell ss:MergeAcross="8" ss:StyleID="s32">
<Data ss:Type="String">
<xsl:value-of select="GR_Short_Name" />
</Data>
</Cell>
</Row>
</xsl:if>
<Row ss:Height="41.4">
cells...
</Row>
</xsl:for-each>
<Row ss:Height="13.8">
<Cell ss:MergeAcross="8" ss:MergeDown="1" ss:StyleID="m15419890" />
</Row>
</Table>
<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">
<PageSetup>
<Header x:Margin="0" />
<Footer x:Margin="0.3" x:Data="&L&8Form Print Date/Time: &D &T&C&8Page &P of &N" />
<PageMargins x:Bottom="0.95" x:Left="0.5" x:Right="0.5" x:Top="0.5" />
</PageSetup>
<Print>
<ValidPrinterInfo />
<Scale>100</Scale>
<HorizontalResolution>600</HorizontalResolution>
<VerticalResolution>600</VerticalResolution>
</Print>
<Selected />
<DoNotDisplayGridlines />
<Panes>
<Pane>
<Number>3</Number>
<ActiveRow>8</ActiveRow>
<ActiveCol>10</ActiveCol>
</Pane>
</Panes>
<ProtectObjects>False</ProtectObjects>
<ProtectScenarios>False</ProtectScenarios>
</WorksheetOptions>
</Worksheet>
</Workbook>
</xsl:template>
</xsl:stylesheet>

XSLT - parse date and time

I am attempting to set the Type attribute of a cell in Excel via XSL. It should look at report/sheet/definition/column and see if there's a type attribute. If yes, it should set the Cell Type to that type and format it in full time (or datetime) manner.
My xml data:
<report>
<sheet>
<definition>
<columns>
<column name="Code" footerText="ISO Code" width="80" />
<column name="Time" footerText="Time" format="DateTime" width="58" type="DateTime" />
<column name="Country" footerText="Country" width="100" />
<column name="SomeNumber" footerText="Country" width="100" type="Number"/>
</columns>
</definition>
<data>
<table>
<row>
<column value="2020-04-02" key="DATE" />
</row>
<row>
<column value="123" />
<column value="10:30" />
<column value="Belarus" />
<column value="0" />
</row>
<row>
<column value="321" />
<column value="12:00" />
<column value="Austria" />
<column value="0" />
</row>
<row>
<column value="2020-04-03" key="DATE" />
</row>
<row>
<column value="456" />
<column value="08:00" />
<column value="USA" />
<column value="24" />
</row>
</table>
</data>
</sheet>
</report>
I have tried printing out $type and $index in the cell and they truly print out properly, but when I try to take the value of $type and use for <xsl:attribute name="ss:Type">, Excel provides me with an error saying it had problems during the load. After checking out the logs, it turns out that it can't parse values like 12:00 into a Time (or shall I say DateTime?). I have two questions:
Is that expected behavior that it can't parse timespan values into DateTime?
Is it normal behavior that it can't parse 2020-01-01 as DateTime as well?
<xsl:for-each select="data/table/row">
<ss:Row>
<xsl:variable name="set" select="column" />
<xsl:variable name="count" select="count($set)"/>
<xsl:for-each select="column">
<ss:Cell>
<xsl:variable name="index" select="position()"/>
<xsl:variable name="type" select="../../../definition/columns/column[$index]/#type"/>
<xsl:attribute name="ss:MergeAcross">
<xsl:choose>
<xsl:when test="$count>1">
<xsl:value-of select="0"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$mergeAcrossHeader"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<ss:Data>
<xsl:attribute name="ss:Type">
<xsl:choose>
<xsl:when test="$type!=''">
<xsl:value-of select="$type" />
</xsl:when>
<xsl:otherwise>
<xsl:text>String</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:value-of select="#value" />
</ss:Data>
</ss:Cell>
</xsl:for-each>
</ss:Row>
</xsl:for-each>

String replace and concatenation

I have this structure
<ROWS>
<ROW>
<TEXT> This is a #good# #day# </TEXT>
<good>great</good>
<day>month</day>
</ROW>
<ROW>
<TEXT> This is a #good# #day# </TEXT>
<good>Fun</good>
<day>morning</day>
</ROW>
</ROWS>
How do I change that to
<statement> This is a great month, this is a Fun morning </statement>
Using only XSLT 1.0?
The original XML can change tag name. But not the structure! Any ideas?
This seems somewhat similar to creating form letters from a template. Assuming the example is not to be meant literally, you could try something like:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<statements>
<xsl:for-each select="ROWS/ROW/TEXT">
<statement>
<xsl:call-template name="merge">
<xsl:with-param name="string" select="."/>
</xsl:call-template>
</statement>
</xsl:for-each>
</statements>
</xsl:template>
<xsl:template name="merge">
<xsl:param name="string"/>
<xsl:param name="sep" select="'#'"/>
<xsl:choose>
<xsl:when test="contains($string, $sep) and contains(substring-after($string, $sep), $sep)">
<xsl:value-of select="substring-before($string, $sep)" />
<xsl:variable name="placeholder" select="substring-before(substring-after($string, $sep), $sep)" />
<xsl:value-of select="../*[name() = $placeholder]" />
<!-- recursive call -->
<xsl:call-template name="merge">
<xsl:with-param name="string" select="substring-after(substring-after($string, $sep), $sep)" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Given an input of:
<ROWS>
<ROW>
<TEXT>The quick brown #animal# jumps over the #property# dog.</TEXT>
<animal>fox</animal>
<property>lazy</property>
</ROW>
<ROW>
<TEXT>A journey of a #number# miles #action# with a single #act#.</TEXT>
<number>thousand</number>
<action>begins</action>
<act>step</act>
</ROW>
</ROWS>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<statements>
<statement>The quick brown fox jumps over the lazy dog.</statement>
<statement>A journey of a thousand miles begins with a single step.</statement>
</statements>
Look at the XSLT 1.0 spec; find the section on string functions. Study the contains, substring-before, and substring-after functions. The solution to your problem should become clear; if it doesn't, you should at least be able to get far enough on your problem to pose a question that does not look as if it could be paraphrased as "Please do my homework for me."

Formatting cells in XSLX files generated by XSLT

I'm using XSLT to generate a report with a date column. In the spreadsheet XLSX file, if I pass through the date as a string, when opening it with a spreadsheet package such as Excel, it will assume it's a date (which is correct) but then format it as MM/dd/yyyy when what we actually want is dd/MM/yyyy. This appears to be the spreadsheet package that is causing this, however I was wondering if you could define cell formatting from the XSLT?
Here is the code I'm using to do this:
<xsl:template match="SomeTemplate">
<Row>
...
<Cell>
<xsl:value-of select="ms:format-date(StartTime, 'dd/MM/yyyy')" />
<xsl:text> </xsl:text>
<xsl:value-of select="ms:format-time(StartTime, 'HH:mm:ss')" />
</Cell>
...
</Row>
</xsl:template>
I've attempted other ways around this such as starting the cell with ' but strangely this didn't work!
Here's an XML sample relevant to the XSLT above:
...
<Destination>
<StartTime>2013-03-07T00:01:09</StartTime>
<EndTime>2013-03-07T10:41:09</EndTime>
...
</Destination>
...
The cell formatting in Excel xml can be done via styles.
I.e you fill the cells with dates in xs:datetime format (you do not need any ms:format-date conversion at all in your example) but specify the cell style though ss:styleId:
<Destination>
<StartTime>2013-03-07T00:01:09</StartTime>
<EndTime>2013-03-07T10:41:09</EndTime>
</Destination>
+
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ms="urn:schemas-microsoft-com:xslt"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
version="1.0">
<xsl:template match="/">
<Workbook>
<Styles>
<Style ss:ID="style_default">
<NumberFormat ss:Format="Short Date"/>
</Style>
<Style ss:ID="style_ddslashmmslashyy">
<NumberFormat ss:Format="dd/mm/yy;#"/>
</Style>
</Styles>
<Worksheet ss:Name="Sheet1">
<Table>
<xsl:apply-templates/>
</Table>
</Worksheet>
</Workbook>
</xsl:template>
<xsl:template match="Destination">
<Row>
<Cell ss:StyleID="style_default">
<Data ss:Type="DateTime"><xsl:value-of select="StartTime"/></Data>
</Cell>
<Cell ss:StyleID="style_ddslashmmslashyy">
<Data ss:Type="DateTime"><xsl:value-of select="StartTime"/></Data>
</Cell>
</Row>
</xsl:template>
</xsl:stylesheet>

Filtering based on dropdown in DVWP

I have created a DVWP dropdown for filtering a list which I've shown using a DVWP. I've set an year list as the dropdown DVWP data source and on selecting a year, i wanna filter the list DVWP to show the items filtered by year.
The dropdown:
<select name="ID" size="1"
onchange="document.location.href='http://server/site.aspx' + '?' + 'year' + '=' + this.options[selectedIndex].value">.
I've added a QueryString parameter param1 which takes its value from Year to this dropdown as well as the list DVWP. In the list DVWP, I've added a filter condition which is: Year equals [Param1]. Note: Year here is a calculated column which gets year from a date field.
My question is even though the dropdown gets post back after selecting a value, it doesn't get filtered. What have I done wrong in this? I've been racking my brains on this but I can't get this to work no matter what I try. Please help.
<xsl:template name="dvt_1">
<xsl:variable name="dvt_StyleName">Table</xsl:variable>
<xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/>
<xsl:variable name="dvt_FieldNameNoAtSign" select="substring-after($dvt_filterfield, '#')" />
<xsl:variable name="dvt_FilteredRowsText" select="$Rows[.=$dvt_filterval or ($dvt_filtertype='date' and substring-before($dvt_filterval,'T') = substring-before(.,'T'))]" />
<xsl:variable name="dvt_FilteredRows" select="$Rows[normalize-space(*[name()=$dvt_filterfield])=$dvt_filterval or ($dvt_filtertype='date' and substring-before($dvt_filterval,'T') = substring-before(normalize-space(*[name()=$dvt_filterfield]),'T'))]" />
<xsl:variable name="dvt_FilteredRowsAttr" select="$Rows[normalize-space(#*[name()=$dvt_FieldNameNoAtSign])=$dvt_filterval or ($dvt_filtertype='date' and substring-before($dvt_filterval,'T') = substring-before(normalize-space(#*[name()=$dvt_FieldNameNoAtSign]),'T'))]" />
<xsl:variable name="dvt_RowCount">
<xsl:choose>
<xsl:when test="$dvt_adhocfiltermode != 'query' and $dvt_filterfield">
<xsl:choose>
<xsl:when test="starts-with($dvt_filterfield, '#')"><xsl:value-of select="count($dvt_FilteredRowsAttr)" /></xsl:when>
<xsl:when test="$dvt_filterfield = '.'"><xsl:value-of select="count($dvt_FilteredRowsText)" /></xsl:when>
<xsl:otherwise><xsl:value-of select="count($dvt_FilteredRows)" /></xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise><xsl:value-of select="count($Rows)" /></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="RowLimit" select="10" />
<xsl:variable name="FirstRow" select="$dvt_firstrow" />
<xsl:variable name="LastRow">
<xsl:choose>
<xsl:when test="($FirstRow + $RowLimit - 1) > $dvt_RowCount"><xsl:value-of select="$dvt_RowCount" /></xsl:when>
<xsl:otherwise><xsl:value-of select="$FirstRow + $RowLimit - 1" /></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="IsEmpty" select="$dvt_RowCount = 0 or $RowLimit = 0" />
<xsl:variable name="dvt_IsEmpty" select="$dvt_RowCount = 0"/>
<xsl:call-template name="dvt_1.toolbar">
<xsl:with-param name="Rows" select="$Rows" />
</xsl:call-template>
<xsl:choose>
<xsl:when test="$dvt_IsEmpty">
<xsl:call-template name="dvt_1.empty"/>
</xsl:when>
Is this what you were looking for?
Umm, if this helps, in the list that I'm using to populate the dropdown, the 'Year' field has an internal name #Title. Would that have something to do with this?
<xsl:template name="dvt_1.rowview">
<option>
<xsl:value-of select="#Title" />
</option>
</xsl:template>
. But it also has #Year, so not sure which is which.
<td class="ms-vb">
<xsl:value-of select="#Year"/>
</td>
Oops, sorry for confusing. The 2nd #Year is the calculated column which I'd created.
UPDATE 2: The code you'd requested:
<td class="ms-toolbar" nowrap="nowrap">
<xsl:call-template name="dvt.filterfield">
<xsl:with-param name="fieldname">#Year</xsl:with-param>
<xsl:with-param name="fieldtitle">Year</xsl:with-param>
<xsl:with-param name="Rows" select="$Rows" />
<xsl:with-param name="fieldtype">text</xsl:with-param>
</xsl:call-template>
Something like this has been created for both the dvwp's:
<xsl:param name="ListID">{6889CA36-79AC-4FA8-9F0A-C013C944B3C5}</xsl:param>
<xsl:param name="Param1" />
I just noticed(thanks to a colleague of mine) that Year which is a calculated column is as a string. So, for 2013, it was storing it as 2,013 because of which I couldn't filter the DVWP properly. I used =TEXT(([columnname], "yyyy") instead of YEAR([colname]) and everything worked fine.
If your parameter name is called param1, then I believe your dropdown will need to pass a query string parameter called "param1" into the URL instead of "year". Could you try modifying that select tag to be like this?:
<select name="ID" size="1"
onchange="document.location.href='http://server/site.aspx?param1='
+ this.options[selectedIndex].value">

Resources