Dynamic document lookup - xslt-2.0

I’m trying to come up with a way to dynamically determine which XML document to use for lookups. I parse the input XML document and based on the value I would like to set the appropriate lookup document to use. Ideally I would to have $ LookupDoc set to the correct document to read. The code snippet I have below doesn’t work. I could switch to XSLT 3.0 if that makes it easier.
<?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" version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="table-lookup" match="Row" use="#Key1"/>
<xsl:variable name="LookupLTE" select="document('HuaweiLTE.xml')/Huawei"/>
<xsl:variable name="LookupHSPA" select="document('HuaweiHSPA.xml')/Huawei"/>
<xsl:template match="measCollecFile/measData">
<xsl:variable name="DeviceName" select="#userLabel"/>
<xsl:choose>
<xsl:when test="substring($DeviceName,1,1)='L'">
<xsl:variable name="LookupDoc" select="$LookupLTE"/>
</xsl:when>
<xsl:when test="substring($DeviceName,1,1)='H'">
<xsl:variable name="LookupDoc" select="$LookupHSPA"/>
</xsl:when>
</xsl:choose>
<root>
<xsl:for-each select="measInfo">
<xsl:call-template name="loop"> </xsl:call-template>
</xsl:for-each>
</root>
</xsl:template>

You have not shown any use of the variable named LookupDoc you seem to want to define but I think you can simply use <xsl:variable name="LookupDoc" select="if (substring($DeviceName,1,1)='L') then $LookupLTE else if (substring($DeviceName,1,1)='H') then $LookupHSPA else ()"/> to define the variable.

Related

Error message 'adc' is an undeclared namespace

All, am im relatively new to XSLT but did some solutions allready. Currently i am trying to write an xslt that moves a file to different folders based on if
<adc:Status>uploaded</adc:Status>
is "uploaded" or not.
Despite declasring the xmlns:adc Namespace i get the error
Exception Occured during TransForm using XSLT 2.0 (SaxonHe):
System.Xml.XmlException: 'adc' is an undeclared namespace. Line 2,
position 4.
I spent hours looking for the error messages, trying to getmyself into namespaces, but currently i cant seem to find out whats the cause of the error.
This is the xslt i wrote
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:adc="http://www.ifra.com/adconnexion/#v2"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs xsi adc">
<xsl:output method="xml" encoding="ISO-8859-1" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="/">
<xsl:variable name="Filename">
<xsl:value-of select="replace(document-uri(.), '.*/', '')"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="/adc:adConnexion/adc:Requests/adc:AdOrder/adc:ProductionDetail/adc:Material/adc:Status='uploaded'">
<!-- Business case -->
<xsl:variable name="OutputFileName" select="concat('file:///C:\UPLOADED\',$Filename)"/>
<xsl:result-document href="{$OutputFileName}">
<xsl:copy-of select="current()"/>
</xsl:result-document>
</xsl:when>
<xsl:otherwise>
<!-- Non Business case -->
<xsl:variable name="OutputFileName" select="concat('file:///C:\ELSE\',$Filename)"/>
<xsl:result-document href="{$OutputFileName}">
<xsl:copy-of select="current()"/>
</xsl:result-document>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The simplified inputfile is
<adc:adConnexion xmlns:adc="http://www.ifra.com/adconnexion/#v2">
<adc:Requests>
<adc:AdOrder messageClass="BusinessTransaction" messageID="FEA*" bookingID="1234" messageCode="AD-O">
<adc:ProductionDetail>
<adc:Material>
<adc:Status>uploaded</adc:Status>
</adc:Material>
</adc:AdOrder>
</adc:Requests>
</adc:adConnexion>
Any help is greatly appreciated!

How to display latest date from N number of month in xslt?

I need to display latest date in N number of months using xslt.
My input:
2016/10/18
2016//10/15
2016/09/29
2016/09/15
and so on.
My output should be like below:
2016/10/18
2016/09/29
Can anyone help me on this?
Given a string of dates in that format you first need to tokenize to extract the date values, then you need to convert to the xs:date format, then you can group by the month and select the maximum value in each group. Using XSLT 3.0 that can be done as follows:
<?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"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="3.0">
<xsl:param name="input" as="xs:string">2016/10/18 2016/10/15 2016/09/29 2016/09/15</xsl:param>
<xsl:variable name="dates" as="xs:date*"
select="tokenize($input, '\s+')!xs:date(replace(., '/', '-'))"/>
<xsl:variable name="max-dates" as="xs:date*">
<xsl:for-each-group select="$dates" group-by="month-from-date(.)">
<xsl:sort select="current-grouping-key()"/>
<xsl:sequence select="max(current-group())"/>
</xsl:for-each-group>
</xsl:variable>
<xsl:template name="main" match="/">
<xsl:value-of select="$max-dates" separator="
"/>
</xsl:template>
</xsl:stylesheet>
In XSLT 2.0 you need to rewrite the date sequence construction a bit:
<?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"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="2.0">
<xsl:param name="input" as="xs:string">2016/10/18 2016/10/15 2016/09/29 2016/09/15</xsl:param>
<xsl:variable name="dates" as="xs:date*"
select="for $dateString in tokenize($input, '\s+') return xs:date(replace($dateString, '/', '-'))"/>
<xsl:variable name="max-dates" as="xs:date*">
<xsl:for-each-group select="$dates" group-by="month-from-date(.)">
<xsl:sort select="current-grouping-key()"/>
<xsl:sequence select="max(current-group())"/>
</xsl:for-each-group>
</xsl:variable>
<xsl:template name="main" match="/">
<xsl:value-of select="$max-dates" separator="
"/>
</xsl:template>
</xsl:stylesheet>
I. Here is a short XSLT 2.0 solution:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:for-each-group select="d" group-by="substring(.,6,2)">
<xsl:sequence select="current-group()[. eq max(current-group()/string())][1]"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document (unordered and multi-year dates -- to make it more interesting):
<t>
<d>2016/10/15</d>
<d>2016/09/15</d>
<d>2016/10/18</d>
<d>2016/09/29</d>
<d>2017/09/17</d>
</t>
the wanted, correct result is produced:
<d>2016/10/18</d>
<d>2017/09/17</d>
II. If the date that has the same month's highest day is wanted -- regardless of the year, this transformation:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:for-each-group select="d" group-by="substring(.,6,2)">
<xsl:sequence select=
"current-group()[substring(.,9,2) eq max(current-group()/substring(.,9,2))][1]"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the same XML document (above), the correct result is produced:
<d>2016/10/18</d>
<d>2016/09/29</d>
III. If the dates are given together as a string:
Just use the tokenize() standard XPath 2.0 fy=unction.
For example, the equivalent of the first transformation above becomes:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vDates"
select="'2016/10/15 2016/09/15 2016/10/18 2016/09/29 2017/09/17'"/>
<xsl:template match="/">
<xsl:for-each-group select="tokenize($vDates, '\s+')[.]" group-by="substring(.,6,2)">
<xsl:sequence select="max(current-group())"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>

creating a tree structure from a delimited string xslt 2.0

I have a input string looks like below
test1->test2->test3
I want to build a tree structure like the below.
-test1
+test2
How can I convert the string to tree structure using xslt 2.0.
The following stylesheet splits the string into a sequence of strings using tokenize() and then recursively calls the "nest" template to create an element for the first item in the sequence and then call the template with the remaining strings to generate the nested elements.
<?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 indent="yes"/>
<xsl:template match="/">
<xsl:variable name="delimited-input" select="'test1->test2->test3'"/>
<xsl:call-template name="nest">
<xsl:with-param name="names" select="tokenize($delimited-input, '->')"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="nest" as="element()*">
<xsl:param name="names" as="xs:string*"/>
<xsl:if test="exists($names)">
<xsl:variable name="head" select="$names[position() = 1]"/>
<xsl:element name="{$head}">
<xsl:call-template name="nest">
<xsl:with-param name="names" select="$names[position() > 1]"/>
</xsl:call-template>
</xsl:element>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Produces the following nested element structure:
<test1>
<test2>
<test3/>
</test2>
</test1>
Assuming that you want to produce HTML, adjust to generate <div> or whatever specific elements necessary.

XSLT set directory where result document ends up

The XSLT below creates result-documents as desired, with one exception: the result document ends up in the directory where the stylesheet was invoked from. I want the result document to be where it was found (i.e. overwrite itself with the transform version).
How can I do that?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0" xpath-default-namespace="http://www.w3.org/1999/xhtml">
<xsl:template match="/">
<xsl:for-each select="collection(iri-to-uri('file:///home/paul/Text/?select=*.xhtml'))">
<xsl:variable name="filename">
<xsl:value-of select="tokenize(document-uri(.), '/')[last()]"/>
</xsl:variable>
<xsl:result-document indent="yes" method="xml" href="{$filename}">
<xsl:apply-templates/>
</xsl:result-document>
</xsl:for-each>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- transform templates removed -->
</xsl:stylesheet>
Try just using href="{document-uri(.)}" to use the full uri as the target rather than doing the tokenize to pull out the last segment.

XSLT mapping and summing source children into a single target attribute

I have a source document with XML structure similar to this:
<FOO>
<BAR>x</BAR>
<BAR>y</BAR>
<BAR>z</BAR>
</FOO>
My target XML must have an attribute with a number that represents the numerical sum of x, y and z. Where x = 1, y = 2 and z = 3
NOTE: The x, y and z are not actually numbers in the source document. They are letters and need to be mapped to the numbers that they represent first.
In this case, the target should look something like:
<Target Sum=6>
</Target>
Anyone have an XSLT example that would do what I need?
Thanks in advance
<?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" indent="yes"/>
<xsl:template match="/">
<Target Sum="{sum(FOO/BAR)}" />
</xsl:template>
EDIT:
This is a bit verbose, and there is probably a more elegant way to do it, but essentially I have a named template here that recursively calls itself to calculate the sum after the hard-coded mapping occurs:
<?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" indent="yes"/>
<xsl:template match="/">
<Target>
<xsl:attribute name="Sum">
<xsl:call-template name="GetSum">
<xsl:with-param name="CurrentNode" select="FOO/BAR[position()=1]"/>
</xsl:call-template>
</xsl:attribute>
</Target>
</xsl:template>
<xsl:template name="GetSum">
<xsl:param name="CurrentNode"/>
<xsl:param name="Number" select="0"/>
<xsl:variable name="Recursive_Result">
<xsl:variable name="MappedNumber">
<xsl:choose>
<xsl:when test="$CurrentNode/. = 'x'">1</xsl:when>
<xsl:when test="$CurrentNode/. = 'y'">2</xsl:when>
<xsl:when test="$CurrentNode/. = 'z'">3</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- if there is a next sibling, recursively call GetSum -->
<xsl:choose>
<xsl:when test="$CurrentNode/following-sibling::BAR[1]">
<xsl:call-template name="GetSum">
<xsl:with-param name="CurrentNode" select="$CurrentNode/following-sibling::BAR[1]"/>
<xsl:with-param name="Number">
<xsl:value-of select="$MappedNumber"/>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$MappedNumber"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- add the recursive_result to the number passed into the template. this will eventually build a sum -->
<xsl:value-of select="$Recursive_Result + $Number"/>
</xsl:template>
</xsl:stylesheet>
A much simpler, shorter and efficient solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kMap" match="#value" use="../#key"/>
<xsl:variable name="vMaps" as="element()*">
<map key="x" value="1"/>
<map key="y" value="2"/>
<map key="z" value="3"/>
</xsl:variable>
<xsl:template match="/*">
<Target Sum="{sum(key('kMap', BAR, document('')))}"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<FOO>
<BAR>x</BAR>
<BAR>y</BAR>
<BAR>z</BAR>
</FOO>
the wanted, correct result is produced:
<Target Sum="6"/>
Explanation: Appropriate use of xsl:key, the 3rd argument of the key() function and AVT.

Resources