<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
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:xs="http://www.w3.org/2001/XMLSchema" xmlns:html="http://www.w3.org/TR/REC-html40"
xmlns:pi="urn:com.workday/picof">
I have the following map:
<xsl:variable name="tabsNames">
<entry><name>A</name><value>one</value></entry>
<entry><name>B</name><value>two</value></entry>
</xsl:variable>
I would like to iterate the map to get each key and value assigned to a variable:
<xsl:for-each select="$tabsNames/element()">
<xsl:variable name="tabName" select="./entry/name"/>
<xsl:variable name="tabValue" select="./entry/value"/>
</xsl:for-each>
How should the select look like in order to get name and value?
Well, a variable alone does nothing but given your variable with a temporary tree containing some entry elements if you want to process them with for-each and have the name and value in your variables inside of the for-each use e.g.
<xsl:for-each select="$tabsNames/entry">
<xsl:variable name="tabName" select="name"/>
<xsl:variable name="tabValue" select="value"/>
</xsl:for-each>
Given the namespaces you have shown in your edit of the question you have two choices, either you need to make sure your temporary elements in the variable do not end up in the default namespace xmlns="urn:schemas-microsoft-com:office:spreadsheet" you have on your stylesheet, you can do that with
<xsl:variable name="tabsNames" xmlns="">
<entry><name>A</name><value>one</value></entry>
<entry><name>B</name><value>two</value></entry>
</xsl:variable>
then my suggestion above remains valid, or you need to adjust your paths with e.g.
<xsl:for-each select="$tabsNames/ss:entry">
<xsl:variable name="tabName" select="ss:name"/>
<xsl:variable name="tabValue" select="ss:value"/>
</xsl:for-each>
or
<xsl:for-each select="$tabsNames/entry" xpath-default-namespace="urn:schemas-microsoft-com:office:spreadsheet">
<xsl:variable name="tabName" select="name"/>
<xsl:variable name="tabValue" select="value"/>
</xsl:for-each>
Related
I would like to build map based on csv file.
Map declaration:
<xsl:variable name="myMap" as="map(xs:string, array(xs:string))">
Csv file:
key1;value1
key1;value2
key2;value3
So map should be composed of two elements:
key1 => array ['value1', 'value2']
key2 => array ['value3']
I've tried to create map like:
<xsl:variable name="myMap" as="map(xs:string, array(xs:string))">
<xsl:map>
<xsl:if test="unparsed-text-available($csv-file, $csv-encoding)">
<xsl:variable name="csv" select="unparsed-text($csv-file, $csv-encoding)"/>
<xsl:analyze-string select="$csv" regex="\r\n?|\n">
<xsl:non-matching-substring>
<xsl:variable name="row" select="tokenize(., '\t')"/>
<xsl:variable name="key" select="$row[1]"/>
<xsl:variable name="array_element" select="$row[2]"/>
<xsl:map-entry key="$key" select="$array_element"/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:if>
</xsl:map>
</xsl:variable>
but I couldn't find a way to merge map entries.
My second approach was to firstly declare map:
<xsl:variable name="myMap" as="map(xs:string, array(xs:string))">
<xsl:map/>
</xsl:variable>
and then I tried to fill it based on csv file content like this:
<xsl:if test="unparsed-text-available($csv-file, $csv-encoding)">
<xsl:variable name="csv" select="unparsed-text($csv-file, $csv-encoding)" />
<xsl:analyze-string select="$csv" regex="\r\n?|\n">
<xsl:non-matching-substring>
<xsl:variable name="row" select="tokenize(., '\t')"/>
<xsl:variable name="key" as="xs:string" select="$row[1]"/>
<xsl:variable name="value" as="xs:string" select="$row[2]"/>
<xsl:choose>
<xsl:when test="map:contains($myMap, $key)">
<xsl:variable name="valueArray" select="map:get($myMap,$key)"/>
<xsl:sequence select="array:append($valueArray, $value)" />
<xsl:sequence select="map:put($myMap, $key, $valueArray) />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="valueArray" as="array(xs:string)" select="[]"/>
<xsl:sequence select="array:append($valueArray, $value)" />
<xsl:sequence select="map:put($myMap, $key, $valueArray) />
</xsl:otherwise>
</xsl:choose>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:if>
Is it possible to invoke array:append and map:put methods from template?
I think, given that you use XSLT 3, you can treat that as a grouping problem where you group the lines from your CSV on the substring-before(., ';'):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="csv-string" as="xs:string">key1;value1
key1;value2
key2;value3</xsl:param>
<xsl:variable name="myMap" as="map(xs:string, array(xs:string))">
<xsl:map>
<xsl:for-each-group select="tokenize($csv-string, '\r?\n')[normalize-space()]" group-by="substring-before(., ';')">
<xsl:map-entry key="current-grouping-key()" select="array{ current-group()!tokenize(substring-after(., ';'), ';') }"/>
</xsl:for-each-group>
</xsl:map>
</xsl:variable>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="json" indent="yes" />
<xsl:template match="/" name="xsl:initial-template">
<xsl:sequence select="$myMap"/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/nc4NzRb
For the example I have inlined the contents from your CSV but you could as well use <xsl:for-each-group select="unparsed-text-lines('file.csv')" group-by="substring-before(., ';')"> instead to load from a file.
As for building and merging maps, if you don't need an array(xs:string) as the map value but can live with a sequence of strings you could use the map:merge function with an option to combine values of the same key:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="csv-string" as="xs:string">key1;value1
key1;value2
key2;value3</xsl:param>
<xsl:variable name="myMap" as="map(xs:string, xs:string*)"
select="map:merge(
tokenize($csv-string, '\r?\n')[normalize-space()]
!
(
let $values := tokenize(., ';')
return map { head($values) : tail($values) }
),
map { 'duplicates' : 'combine' }
)"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="adaptive" indent="yes" />
<xsl:template match="/" name="xsl:initial-template">
<xsl:sequence select="$myMap"/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/nc4NzRb/1
Source XML
<?xml version="1.0" encoding="UTF-8"?>
<root>
<parent1>
<child1>P1-Child-01</child1>
<child2>P1-Child-02</child2>
</parent1>
<parent2>
<child3>P2-Child-03</child3>
<child4>P2-Child-04</child4>
<child5>P2-Child-05</child5>
</parent2>
<parent3>
<child6>P2-Child-03</child6>
<child7>P2-Child-04</child7>
</parent3>
</root>
Required output
<root>
<parent1>
<cld1>P1-Child-01</cld1>
<cld2>P1-Child-02</cld2>
</parent1>
<parent2>
<cld3>P2-Child-03</cld3>
<cld4>P2-Child-04</cld4>
</parent2>
</root>
i tried creating an XSLT with below logic:
1. It will get the number of parent elements
2. based on that for each parent element i want to get the number of child elements and based on string concat(chld,$index) i want to create new elements
i.e
replacing
<child1>P1-Child-01</child1>
with
<cld4>P2-Child-04</cld4>
but i stuck no how to get the count of child elements for each parent
current Xslt
<xsl:template match="/">
<xsl:call-template name="parentforloop"></xsl:call-template>
</xsl:template>
<xsl:template name="parentforloop" >
<xsl:param name="index" select ="1" />
<xsl:param name="total" select="count(/*/*)"/>
<xsl:param name="parentName" select="concat('parent',$index)"/>
<xsl:element name="{concat('parent',$index )}">
</xsl:element>
<xsl:if test="not($index=$total)">
<xsl:call-template name="parentforloop">
<xsl:with-param name="index" select="$index+1"></xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:template>
Please help
thank You
I'd like to display a kmlfiles with just points on a HERE map as a heatmap.
I've seen the heatmap example and KML examples but could someone help me combine the two so I can make a heatmap from my KML file?
Transform your KML data to an array of nokia.maps.heatmap.Overlay.DataPoint elements. The data set can then be used as in the Heat Map Example
The precise transformation will depend on where in the KML your data is held (and whether you need to pass in values as well, but the following XSLT should help get you started:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
data = [
<xsl:for-each select="kml/Document/Folder/Placemark/Point">
{
<xsl:variable name="coordinates">
<xsl:value-of select="*" />
</xsl:variable>
<xsl:variable name="longitude">
<xsl:value-of select="substring-before($coordinates,',')"/>
</xsl:variable>
<xsl:variable name="latitude">
<xsl:value-of select="substring-before(substring-after($coordinates,','),',')"/>
</xsl:variable>
latitude: <xsl:value-of select="$latitude" />,
longitude: <xsl:value-of select="$longitude" />
}
<xsl:if test="position()!=last()">,</xsl:if>
</xsl:for-each>
];
</xsl:template>
</xsl:stylesheet>
Alter kml/Document/Folder/Placemark/Point as necessary if your points aren't in a folder for example.
I have this XSLT to split a 25 MB XHTML file.
<?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" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="html/body"/>
</xsl:template>
<xsl:template match="body">
<xsl:for-each-group select="node()"
group-starting-with="*[position()=1 or #class='toc']">
<xsl:if test="count(current-group()[self::*]) > 0 ">
<xsl:variable name="filename" select="concat('/home/t',position(),'.xml' )"/>
<xsl:apply-templates/>
<xsl:result-document
indent="yes" method="xml" href="$filename}">
<html>
<xsl:copy-of select="/html/#*"/>
<xsl:for-each select="/html/node()">
<xsl:choose>
<xsl:when test="not(self::body)">
<xsl:copy-of select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select="current-group()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</html>
</xsl:result-document>
</xsl:if>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
It currently works at splitting up the file when it finds a #toc. I need to alter this to be sensitive to size of the output file, as opposed to breaking at the #toc.
Desired end state: I want the result document to be about 500KB. I suppose position() might be the best way to regulate the split points?? I tried various string-length() approaches--I could not get one to work. Also, I think white space may be an issue.
By my calculations with these documents, splitting the file at a <p class="i0"> found at or near every 150th position increment should reliably give me the filesize I need.
I guess the best way to get there is to change this:
group-starting-with="*[position()=1 or #class='toc']"
So far I have not succeeded in anything I have changed it to. Thoughts?
UPDATE: I'm not ready to say this is answered, because someone may have a better idea. But right now I'm using group-starting-with="body/*[position()=1 or position() mod 350 = 0]" with some success. It is testing well.
UPDATE 2: The group-starting-with="body/*[position()=1 or position() mod 350 = 0]" is not working well. Problem is that it is the position within the for-each-loop, not the overall file.
The successful solution ended up being an xslt 3.0 accumulator.
As an alternative:
Dmitiri Novatchev solution for XSLT 1.0:
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="vResult">
<xsl:apply-templates/>
</xsl:variable>
Length of output is: <xsl:text/>
<xsl:value-of select="concat(string-length($vResult), '
')"/>
<xsl:if test="string-length($vResult) <= 1800">
<xsl:copy-of select="$vResult"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on this source.xml:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
produces the wanted result:
Length of output is: 51
01
02
03
04
05
06
07
08
09
10
References
XSLT FAQ: WML and HDML - Measuring the size of the output file, in bytes
XSLT 3.0: Accumulator Function
Utilizing new capabilities of XML languages to verify integrity constraints
A Functional Tokenizer (Was: Re: Looping over a CSV in XSL)
XSL Techniques
FXSL:sumTree
In the source XSD of the following map, InvTransTable and ToWarehouse elements are unbounded.
I want to map values only for the first iteration in each case. My problem is, the XSLT generated by the map contains two loops, as can be seen in the following:
<ns0:Record>
<xsl:for-each select="s1:InvTransTable">
<xsl:variable name="var:v4" select="position()" />
<xsl:for-each select="s1:ToWarehouse">
<xsl:variable name="var:v5" select="position()" />
<xsl:variable name="var:v6" select="userCSharp:LogicalEq(string($var:v5) , "1")" />
<xsl:variable name="var:v7" select="userCSharp:LogicalEq(string($var:v4) , "1")" />
<xsl:variable name="var:v8" select="userCSharp:LogicalAnd(string($var:v6) , string($var:v7))" />
<xsl:variable name="var:v12" select="userCSharp:StringUpperCase("B")" />
<xsl:variable name="var:v13" select="userCSharp:StringUpperCase("M")" />
<xsl:variable name="var:v14" select="userCSharp:StringUpperCase("4")" />
<ns0:Header>
<xsl:if test="string($var:v8)='true'">
<xsl:variable name="var:v9" select="s1:EXDRefCustAccount/text()" />
<ns0:AccountNo>
<xsl:value-of select="$var:v9" />
</ns0:AccountNo>
</xsl:if>
<xsl:if test="string($var:v7)='true'">
<xsl:variable name="var:v10" select="../s1:ShipDate/text()" />
<ns0:DateExpected>
<xsl:value-of select="$var:v10" />
</ns0:DateExpected>
</xsl:if>
This results in the created XML containing multiple header elements, which is not what I want!
I've tried to prevent this throug the use of Iteration (checking for a value of 1) and Conditional functoids but this hasn't worked. Can anyone please advise how I can achieve what is needed without resorting to a scripting functoid or replacing the map with XSLT?
If you know you only need to map the first instance of each, use the Index functoid with an index of 1, instead of the Iteration functoid.