XSLT 1.0 Select Name field based on evaluation of Date and Time fields - xslt-1.0

I have XML form data coming in from a vendor app that allows me to select values for fields on the form by using this format.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsl:stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="text" /><xsl:template match="/"><xsl:apply-templates/></xsl:template>
<xsl:template match="DataSet/diffgr:diffgram/Forms">
<xsl:for-each select="Form">
<xsl:value-of select="FieldName1"/>
<xsl:value-of select="FieldName2"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I can't see the XML, but I can surmise from this that all of the fields are siblings within the <form> and looks something like this:
<form>
<CosignerName1 value="John Doe"/>
<CosignerDate1 value="1/1/1970"/>
<CosignerTime1 value="8:46 PM"/>
<CosignerName2 value="Jane Smith"/>
<CosignerDate2 value="2/2/1972"/>
<CosignerTime2 value="11:46 AM"/>
...
<CosignerName12 value="Will Hunting"/>
<CosignerDate12 value="12/12/1982"/>
<CosignerTime12 value="1:00 AM"/>
</form>
<form>
<CosignerName1 value="Bill Thomas"/>
<CosignerDate1 value="5/5/2020"/>
<CosignerTime1 value="8:46 PM"/>
<CosignerName2 value="Bev Poole"/>
<CosignerDate2 value="6/6/2022"/>
<CosignerTime2 value="11:46 AM"/>
...
<CosignerName12 value="Bob Ross"/>
<CosignerDate12 value="12/12/1982"/>
<CosignerTime12 value="1:00 AM"/>
</form>
Known factors:
Each form has 12 cosigners, each with numbered field names i.e. CosignerName1, CosignerName2, etc.
Each named cosigner also has a CosignerDate and a CosignerTime field associated by number. i.e. CosignerName1 has CosignerDate1 and CosignerTime1 fields that, when combined, show when that user signed the form.
I am using the word 'idnum' in my code and in this question to refer to the number at the end of each label that shows they are part of a set i.e CosignerName1, CosignerDate1, and CosignerTime1 all share idnum=1
Goal: Identify and select only the most recent CosignerName, based on CosignerDate and CosignerTime.
To do this, I figure I need to:
Find the most recent CosignerDate/CosignerTime combination
Read the label of either of the identified fields to extract the idnum from the end
Use the idnum to to identify the correct CosignerName and output it
I have some pieces of the solution, but no way to bring them together:
Create a Timestamp
I can combine a set of CosignerDate[idnum] and CosignerTime[idnum] fields, then translate them into a timestamp. If I could create an array of these timestamps, then I could sort and find the most recent one, but as I understand it XSLT 1.0 doesn't have arrays. So I am not sure what to do with this.
<xsl:variable name="mm" select="substring-before(CosignerDate1,'/')" />
<xsl:variable name="mmyyyy" select="substring-after(CosignerDate1,'/')"/>
<xsl:variable name="dd" select="substring-before($mmyyyy,'/')" />
<xsl:variable name="yyyy" select="substring-after($mmyyyy,'/')" />
<xsl:variable name="ampm" select="substring-after(CosignerTime1,' ')" />
<xsl:variable name="time">
<xsl:choose>
<xsl:when test="$ampm = 'AM'">
<xsl:value-of select="number(translate(substring-before(CosignerTime1,' '),':',''))" />
</xsl:when>
<xsl:when test="$ampm = 'PM'">
<xsl:value-of select="number(translate(substring-before(CosignerTime1,' '),':',''))+number('1200')" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="concat('Timestamp:',$yyyy,$mm,$dd,$time)"/>
The code above outputs something like: Timestamp:202209191128
Get idnums
I can loop through all of the Cosigners and extract the identifying number shared by their labels.
<xsl:for-each select="*[starts-with(local-name(), 'CosignerName')]">
<xsl:variable name="idnum">
<xsl:value-of select="translate(local-name(),'CosignerName','')" />
</xsl:variable>
<xsl:value-of select="$idnum"/>
<xsl:value-of select="','"/>
</xsl:for-each>
This code above outputs something like: 1,4,7,12
But, this is where I get lost.
Failed Attempt
I tried something like the code below. The logic was to loop through each Cosigner name (12 iterations), get the idnum from the label, use that to get the CosignerDate and CosignerTime, create a timestamp, then sort the timestamps, if the timestamp we are looking at is the top one (most recent) then use the idnum to select the correct CosignerName and output it.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsl:stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="text" /><xsl:template match="/"><xsl:apply-templates/></xsl:template>
<xsl:template match="DataSet/diffgr:diffgram/Forms">
<xsl:for-each select="Form">
<xsl:for-each select="*[starts-with(local-name(), 'CosignerName')]">
<xsl:variable name="idnum">
<xsl:value-of select="translate(local-name(),'CosignerName','')" />
</xsl:variable>
<xsl:variable name="mm" select="substring-before(concat(CosignerDate,$idnum),'/')" />
<xsl:variable name="mmyyyy" select="substring-after(concat(CosignerDate,$idnum),'/')"/>
<xsl:variable name="dd" select="substring-before($mmyyyy,'/')" />
<xsl:variable name="yyyy" select="substring-after($mmyyyy,'/')" />
<xsl:variable name="ampm" select="substring-after(concat(CosignerTime,$idnum),' ')" />
<xsl:variable name="time">
<xsl:choose>
<xsl:when test="$ampm = 'AM'">
<xsl:value-of select="number(translate(substring-before(concat(CosignerTime,$idnum),' '),':',''))" />
</xsl:when>
<xsl:when test="$ampm = 'PM'">
<xsl:value-of select="number(translate(substring-before(concat(CosignerTime,$idnum),' '),':',''))+number('1200')" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:variable name="timestamp" select="concat('Timestamp: ',$yyyy,$mm,$dd,$time)"/>
<xsl:sort select="$timestamp" data-type="number" order="descending"/>
<xsl:if test="position() = 1"><xsl:value-of select="."/></xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This of course fails for several reasons:
the concat with the $idnum doesn't actually pull values from fields
the sort has to be the first thing in the for-each
the sort has to happen after all of the timestamps are created
the sorted list of timestamps still needs to reference which idnum they were generated from
Without being able to create an array of timestamps and idnums, I don't know how you can accomplish this.
Does anyone know how can I get at the CosignerName[idnum] that is correlated with the most recent CosignerDate[idnum]/CosignerTime[idnum]?

I believe you could just sort the CosignerNameX nodes by the individual components of their associated dates and times, as shown in the two other answers I linked to:
https://stackoverflow.com/a/59288030/3016153
https://stackoverflow.com/a/30631073/3016153
However, it might be more efficient - and certainly more readable - to do this in two steps:
Construct a dateTime value for each cosigner;
Find the cosigner with the most recent value.
Consider the following example:
XML
<DataSet xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
<diffgr:diffgram>
<Forms>
<form>
<CosignerName1 value="John Doe"/>
<CosignerDate1 value="1/1/1970"/>
<CosignerTime1 value="8:46 PM"/>
<CosignerName2 value="Jane Smith"/>
<CosignerDate2 value="2/2/1972"/>
<CosignerTime2 value="11:46 AM"/>
<CosignerName12 value="Will Hunting"/>
<CosignerDate12 value="12/12/1982"/>
<CosignerTime12 value="1:00 AM"/>
</form>
<form>
<CosignerName1 value="Bill Thomas"/>
<CosignerDate1 value="5/5/2020"/>
<CosignerTime1 value="8:46 PM"/>
<CosignerName2 value="Bev Poole"/>
<CosignerDate2 value="6/6/2022"/>
<CosignerTime2 value="11:46 AM"/>
<CosignerName12 value="Bob Ross"/>
<CosignerDate12 value="12/12/1982"/>
<CosignerTime12 value="1:00 PM"/>
</form>
</Forms>
</diffgr:diffgram>
</DataSet>
XSLT 1.0 + EXSLT node-set()
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="diffgr exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/DataSet">
<output>
<xsl:for-each select="diffgr:diffgram/Forms/form">
<xsl:variable name="datetimes">
<xsl:for-each select="*[starts-with(local-name(), 'CosignerName')]">
<!-- identify values -->
<xsl:variable name="i" select="substring-after(local-name(), 'CosignerName')" />
<xsl:variable name="date" select="../*[local-name()=concat('CosignerDate', $i)]/#value" />
<xsl:variable name="time" select="../*[local-name()=concat('CosignerTime', $i)]/#value" />
<!-- extract date components -->
<xsl:variable name="year" select="substring-after(substring-after($date, '/'), '/')" />
<xsl:variable name="month" select="substring-before($date, '/')" />
<xsl:variable name="day" select="substring-before(substring-after($date, '/'), '/')" />
<!-- extract time components -->
<xsl:variable name="hour12" select="substring-before($time, ':')" />
<xsl:variable name="minute" select="substring-before(substring-after($time, ':'), ' ')" />
<xsl:variable name="pm" select="contains($time,'PM')" />
<xsl:variable name="hour" select="$hour12 mod 12 + 12*$pm"/>
<!-- construct dateTime -->
<datetime cosigner="{#value}" index="{$i}">
<xsl:value-of select="$year"/>
<xsl:value-of select="format-number($month, '-00')"/>
<xsl:value-of select="format-number($day, '-00')"/>
<xsl:value-of select="format-number($hour, 'T00')"/>
<xsl:value-of select="format-number($minute, ':00')"/>
<xsl:text>:00</xsl:text>
</datetime>
</xsl:for-each>
</xsl:variable>
<!-- output -->
<form>
<xsl:for-each select="exsl:node-set($datetimes)/datetime">
<xsl:sort select="." data-type="text" order="descending"/>
<xsl:if test="position()=1">
<last-cosigner index="{#index}" dateTime="{.}" >
<xsl:value-of select="#cosigner"/>
</last-cosigner>
</xsl:if>
</xsl:for-each>
</form>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>
<form>
<last-cosigner index="12" dateTime="1982-12-12T01:00:00">Will Hunting</last-cosigner>
</form>
<form>
<last-cosigner index="2" dateTime="2022-06-06T11:46:00">Bev Poole</last-cosigner>
</form>
</output>
P.S. With a Microsoft processor you may need to change the namespace declaration:
xmlns:exsl="http://exslt.org/common"
to:
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
and then change the exsl prefix in lines #5 and #39 to msxsl.

Related

XSLT 1.0 split variable string to populate specific tags

I am transferring data from one xml format to another with xslt 1.0. In the input xml, address is in a single field as a string separated by spaces. As far as I can tell, it can come in the following formats considering the identifiers in brackets [] are optional.
<Field Name="address"...>number [cardinal-dir] name st-type [unit#]</Field>
OR
<Field Name="address"...>number [cardinal-dir] name st-type APT APT#</Field>
so the data may look like:
"123 3RD AVE" or "123 NE 3RD AVE" or "123 NE 3RD AVE APT 321" ... you get the picture.
And I need it to appropriately populate the following output format:
<mstns:Address>
<mstns:HouseNumber>123</mstns:HouseNumber>
<mstns:StreetName>NE</mstns:StreetName>
<mstns:StreetType>AVE</mstns:StreetType>
<mstns:Apt>APT</mstns:Apt>
</mstns:Address>
I've seen some similar posts about tokenizing strings in xlst 1.0 but combined with the variable input I'm a bit lost.
As I said in the comments, this is an awful lot of work and the quality of the result will depend on how consistent your data is.
Assuming that:
the first word (and only the first word) is the street number;
the second word may be a street direction identifier, from a known list of street direction identifiers;
the street name is followed by a street type identifier, from a known list of street type identifiers,
you could use the following stylesheet as your starting point:
XSLT 1.0 (+EXSLT node-set)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Field[#Name='address']">
<!-- tokenize to words -->
<xsl:variable name="tokens">
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="words" select="exsl:node-set($tokens)/token" />
<!-- street direction exists? -->
<xsl:variable name="street-direction-exists" select="$words[2]='N' or $words[2]='E' or $words[2]='S' or $words[2]='W' or $words[2]='NE' or $words[2]='NW' or $words[2]='SE' or $words[2]='SW'" />
<!-- find position of street type -->
<xsl:variable name="street-type-index">
<xsl:call-template name="get-street-type-index">
<xsl:with-param name="words" select="$words"/>
</xsl:call-template>
</xsl:variable>
<!-- output -->
<Address>
<HouseNumber>
<xsl:value-of select="$words[1]"/>
</HouseNumber>
<xsl:if test="$street-direction-exists">
<StreetDirection>
<xsl:value-of select="$words[2]"/>
</StreetDirection>
</xsl:if>
<StreetName>
<xsl:for-each select="$words[1 + $street-direction-exists < position() and position() < $street-type-index]">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</StreetName>
<StreetType>
<xsl:value-of select="$words[number($street-type-index)]"/>
</StreetType>
<xsl:if test="count($words) > $street-type-index">
<Unit>
<xsl:for-each select="$words[position() > $street-type-index]">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</Unit>
</xsl:if>
</Address>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="' '"/>
<token>
<xsl:value-of select="substring-before(concat($text, $delimiter), $delimiter)" />
</token>
<xsl:if test="contains($text, $delimiter)">
<!-- recursive call -->
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="get-street-type-index">
<xsl:param name="words"/>
<xsl:variable name="street-types">
<street-type>AVE</street-type>
<street-type>STR</street-type>
<!-- add more types here (or use an external XML document for them) -->
</xsl:variable>
<xsl:choose>
<xsl:when test="$words[last()] = exsl:node-set($street-types)/street-type">
<xsl:value-of select="count($words)"/>
</xsl:when>
<xsl:when test="$words">
<!-- recursive call -->
<xsl:call-template name="get-street-type-index">
<xsl:with-param name="words" select="$words[position() != last()]"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Applied to the following test input:
XML
<Fields>
<Field Name="address">123 Old Oak Tree AVE</Field>
<Field Name="address">45B NE Broadway STR</Field>
<Field Name="address">6789 Maple Syrup AVE Room 800</Field>
<Field Name="address">1024 W Three Elm Trees AVE APT 321</Field>
<Field Name="address">1024 False AVE Maria STR Unit 8071</Field>
</Fields>
the result will be:
Result
<?xml version="1.0" encoding="utf-16"?>
<Fields>
<Address>
<HouseNumber>123</HouseNumber>
<StreetName>Old Oak Tree</StreetName>
<StreetType>AVE</StreetType>
</Address>
<Address>
<HouseNumber>45B</HouseNumber>
<StreetDirection>NE</StreetDirection>
<StreetName>Broadway</StreetName>
<StreetType>STR</StreetType>
</Address>
<Address>
<HouseNumber>6789</HouseNumber>
<StreetName>Maple Syrup</StreetName>
<StreetType>AVE</StreetType>
<Unit>Room 800</Unit>
</Address>
<Address>
<HouseNumber>1024</HouseNumber>
<StreetDirection>W</StreetDirection>
<StreetName>Three Elm Trees</StreetName>
<StreetType>AVE</StreetType>
<Unit>APT 321</Unit>
</Address>
<Address>
<HouseNumber>1024</HouseNumber>
<StreetName>False AVE Maria</StreetName>
<StreetType>STR</StreetType>
<Unit>Unit 8071</Unit>
</Address>
</Fields>
Note: You may need to use Microsoft's own node-set() extension function, instead of EXSLT.

I want to split the string stored in a variable and store the obtained values in variable using xslt

<xsl:variable name="AAM" select="//AAM"/>
AAM will have string value_1,value_2,value_3,value_4
I then want to split this and store in 4 variables :
seg1,
seg2,
seg3,
seg4
Assuming the below as your input:
<?xml version="1.0" encoding="UTF-8"?>
<body>
<AAM>value_1,value_2,value_3,value_4</AAM>
</body>
An XSLT 2.0 solution to split a comma separated string can be:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:variable name="AAM" select="//AAM" />
<xsl:template match="body">
<body>
<xsl:variable name="separator" select="','" />
<xsl:for-each select="tokenize($AAM,$separator)">
<xsl:element name="seg{position()}">
<xsl:value-of select="normalize-space(.)" />
</xsl:element>
</xsl:for-each>
</body>
</xsl:template>
http://xsltransform.net/6qaFCET/1
Edit: (Based on commented)
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:variable name="AAM" select="//AAM" />
<xsl:template match="body">
<body>
<xsl:variable name="separator" select="','" />
<xsl:for-each select="tokenize($AAM,$separator)">
<xsl:choose>
<xsl:when test="position() = 1">
<xsl:variable name="seg1">
<xsl:value-of select="normalize-space(.)" />
</xsl:variable>
<xsl:copy-of select="$seg1" />
<xsl:text>
</xsl:text>
</xsl:when>
<xsl:when test="position() = 2">
<xsl:variable name="seg2">
<xsl:value-of select="normalize-space(.)" />
</xsl:variable>
<xsl:copy-of select="$seg2" />
<xsl:text>
</xsl:text>
</xsl:when>
<xsl:when test="position() = 3">
<xsl:variable name="seg3">
<xsl:value-of select="normalize-space(.)" />
</xsl:variable>
<xsl:copy-of select="$seg3" />
<xsl:text>
</xsl:text>
</xsl:when>
<xsl:when test="position() = 4">
<xsl:variable name="seg4">
<xsl:value-of select="normalize-space(.)" />
</xsl:variable>
<xsl:copy-of select="$seg4" />
<xsl:text>
</xsl:text>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</body>
</xsl:template>
http://xsltransform.net/6qaFCET/2
Note: following lines can be avoided. It has been added to populate value of variables.
<xsl:copy-of select="$seg2" />
<xsl:text>
</xsl:text>
Assuming you have <AAM>value_1,value_2,value_3,value_4</AAM> and <xsl:variable name="AAM" select="//AAM"/> you can of course use e.g. <xsl:variable name="value-sequence" select="tokenize($AAM, ',')"/> and if then needed access e.g. $value-sequence[1], $value-sequence[2] and so on, thus if you know there are only four values you can declare <xsl:variable name="seq1" select="$value-sequence[1]"/>, <xsl:variable name="seq2" select="$value-sequence[2]"/> and so on. The tokenize function is part of XPath 2 and later so works with XSLT 2 or 3 processors like Saxon 9 or AltovaXML or XmlPrime.
Answering late.
Maybe this is not an exact solution. But this may help you.
This is how I'm able to split a string from an XML node into two variables using XSLT 1.0
<xsl:when test="NodeName">
<xsl:variable name="var" select="NodeName"/>
<xsl:variable name="var1" select="substring-before($var, '=')"/>
<xsl:variable name="var2" select="substring-after($var, '=')"/>
<xsl:value-of select="$var1"/>-
<xsl:value-of select="$var2"/>
</xsl:when>
For Further reference Click Here

Reading values of XML tag in variables inside XSLT

I could not find a similar question anywhere, so thats why posting this new question.
I have an XML that I want to read and convert into SQL using XSLT. The trick part is that the XML elements(i.e. the names) are not known and the XSD for the XML is generated on the fly.
But what is known is certain attributes of the elements.
The XML looks like this :
<?xml version="1.0" encoding="UTF-8"?>
<et:ItemRecordList xmlns:et="urn:org:easetech:easytest:schema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:org:easetech:easytest:schema ItemRecord.xsd ">
<ItemRecord recordId="idvalue0" tableName="item_record">
<itemId columnName="item_id" idColumn="true" length="36" nullable="false">itemId</itemId>
<databaseInstitution columnName="database_institution" length="255" nullable="false">0</databaseInstitution>
<lastModifiedDate columnName="last_modified_date" length="255" nullable="false">2001-12-31T12:00:00</lastModifiedDate>
</ItemRecord>
</et:ItemRecordList>
I want to use this XML and convert it into an INSERT SQL statement using XXSLT.
I created an XSLT like this :
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8" />
<xsl:template match="/">
<xsl:for-each select="/*/*">
INSERT INTO
<xsl:value-of select="#tableName" />
(
<xsl:for-each select="/*/*/*">
<xsl:variable name="columnName"><xsl:value-of select="#columnName"/></xsl:variable>
<xsl:choose>
<xsl:when test="#columnName">
<xsl:value-of select="$columnName"/>
<xsl:if test="position()!=last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:when>
</xsl:choose>
</xsl:for-each>
)
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
which gives me the output like this :
INSERT INTO item_record (item_id, database_institution, last_modified_date)
But I don't know how to create the value part of the query. The value is the value we get using
<xsl:value-of select="." />
I tried concatenating the values, but unfortunately concat didnt work for me. I also played around with xsl:variable but couldnt make it work. If someone could help me with the XSLT that I can use to create to output like below that would be really appreciated.
INSERT INTO item_record (item_id, database_institution, last_modified_date) values (itemId,0,2001-12-31T12:00:00)
How about:
<xsl:template match="/">
<xsl:for-each select="/*/*">
<xsl:text>INSERT INTO </xsl:text>
<xsl:value-of select="#tableName" />
<xsl:text> (</xsl:text>
<xsl:for-each select="*[#columnName]">
<xsl:value-of select="#columnName"/>
<xsl:if test="position()!=last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>) values (</xsl:text>
<xsl:for-each select="*[#columnName]">
<xsl:value-of select="."/>
<xsl:if test="position()!=last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
The next XSLT will generate the query as wanted
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:variable name="newline" select="'
'" />
<xsl:template match="/">
<xsl:apply-templates select="*/*/#tableName" />
</xsl:template>
<xsl:template match="#tableName">
<xsl:value-of select="concat('INSERT INTO ', ., ' (')" />
<xsl:apply-templates select="parent::*/*/#columnName" />
<xsl:value-of select="') VALUES ('" />
<xsl:apply-templates select="parent::*/*" mode="values" />
<xsl:value-of select="concat(')', $newline)" />
</xsl:template>
<xsl:template match="#columnName">
<xsl:choose>
<xsl:when test="position() = last()">
<xsl:value-of select="." />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(., ', ')" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*" mode="values">
<xsl:choose>
<xsl:when test="position() = last()">
<xsl:value-of select="." />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(., ', ')" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

XSL match multiple variables with XML

I'm trying to match xsl variables i.e. key1, key2 with xml node strings.
Problem: the xsl variables can vary like key1, key2, key3, key4, until key.length...
Question: How can I modify my xsl so when the key[i] is used, then ti will display all the xml node matches.
Here's my XML:
<?xml version="1.0" encoding="UTF-8"?>
<document>
<metadata>
<field>marketing business</field>
<field>PageTitle1 One</field>
<field>marketing business link</field>
<field>planning development</field>
<field>PageTitle2 Two</field>
<field>planning development link</field>
<field>learning development</field>
<field>PageTitle3 Threee</field>
<field>learning development link</field>
</metadata>
</document>
Here's my XSL:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="key1">marketing</xsl:variable>
<xsl:variable name="key2">business</xsl:variable>
<xsl:for-each select="document/metadata/field">
<xsl:choose>
<xsl:when test="contains(.,$key1) and contains(.,$key2)">
match <xsl:value-of select="." /><br/>
</xsl:when>
<xsl:when test="contains(.,$key2)">
match <xsl:value-of select="." /><br/>
</xsl:when>
<!--... add other options here-->
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
Result:
match marketing business
match marketing business link
Any help? or is there a way to put this in an array-like variable or any different approach?...
Consider putting your "keys" in a separate XML document, call it "keys.xml"
<keys>
<key>marketing</key>
<key>business</key>
</keys>
Then, you can create a single variable in your XSLT to reference this document
<xsl:variable name="keys" select="document('keys.xml')/keys" />
With this variable you can then, for example, check if your field element matches all the keys like so:
<xsl:variable name="matches" select="count($keys/key[contains(current(), .)])" />
<xsl:choose>
<xsl:when test="$matches = count($keys/key)">
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:variable name="keys" select="document('keys.xml')/keys" />
<xsl:variable name="totalkeys" select="count($keys/key)" />
<xsl:template match="/">
<xsl:for-each select="document/metadata/field">
<xsl:variable name="matches" select="count($keys/key[contains(current(), .)])" />
<xsl:choose>
<xsl:when test="$matches = $totalkeys">
matches all <xsl:value-of select="." /><br/>
</xsl:when>
<xsl:when test="$matches = 1">
matches one <xsl:value-of select="." /><br/>
</xsl:when>
<xsl:when test="$matches > 0">
matches some <xsl:value-of select="." /><br/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

selecting sorted xml node based on some conditions

I have sample xml like this:
User
name A
Id 8
User
name B
Id 5
User
name C
Id 16
User
name D
Id 10
...
This is what i expect, if input is
A only --> output should be A.
B only --> output should be B.
A and B only --> output should be B.
Any users other than A and B --> output should be name of user with lowest id.
A, B and other users --> user with lowest id except A and B.
I have tried different variants of below template
<xsl:template name="getPriorityName">
<xsl:for-each select="//*[local-name()='User']">
<xsl:sort select="./*[local-name()='Id']" data-type="number"/>
<xsl:variable name="name">
<xsl:value-of select="./*[local-name()='name']"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$name!='A' and $name!='B'">
<xsl:if test="position()=1">
<xsl:value-of select="$name"/>
</xsl:if>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
But since i am unable to club name condition with for-each it does not work in case when A and B are there in input
Thanks Jirka :)
I modified the tempalte a little bit and now it looks ok:
<?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="/Users">
<xsl:variable name="UsersCount" select="count(User)" />
<xsl:variable name="UsersExceptAB" select="User[Name != 'A' and Name !='B']" />
<xsl:variable name="UsersAB" select="User[Name = 'A' or Name ='B']" />
<xsl:variable name="UserInABWithMinimumId" select="$UsersAB[not($UsersAB/Id < ./Id)]" />
<xsl:variable name="UserExceptABWithMinimumId" select="$UsersExceptAB[not($UsersExceptAB/Id < ./Id)]" />
<result>
<xsl:choose>
<xsl:when test="$UsersExceptAB">
<xsl:copy-of select="$UserExceptABWithMinimumId" />
</xsl:when>
<xsl:when test="$UsersAB">
<xsl:copy-of select="$UserInABWithMinimumId" />
</xsl:when>
<xsl:otherwise>
<xsl:comment>Oops, something is wrong.</xsl:comment>
</xsl:otherwise>
</xsl:choose>
</result>
</xsl:template>
</xsl:stylesheet>
Is there a way i can just assign the name to a variable; let's say; priorityName that i can use later, i have some other logic based on this priorityName in my application. I tried to access name from node, but since what we finally have is a node set(always of size 1 - either UserInABWithMinimumId or UserExceptABWithMinimumId). This is what i tried:
<xsl:variable name="priorityName">
<xsl:choose>
<xsl:when test="$UsersExceptAB">
<xsl:value-of select="$UserExceptABWithMinimumId/User/Name" />
</xsl:when>
<xsl:when test="$UsersAB">
<xsl:value-of select="$UserInABWithMinimumId/User/Name" />
</xsl:when>
<xsl:otherwise>
<xsl:comment>Oops, something is wrong.</xsl:comment>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
Thanks!!!
You could try following XSLT
<?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="/Users">
<xsl:variable name="UsersCount" select="count(User)" />
<xsl:variable name="UsersExceptAB" select="User[Name != 'A' and Name !='B']" />
<xsl:variable name="UsersExceptABWithMinimumId" select="$UsersExceptAB[not($UsersExceptAB/Id < ./Id)]" />
<result>
<xsl:choose>
<!-- There exists some User with other name than A or B-->
<xsl:when test="$UsersExceptAB">
<xsl:copy-of select="$UsersExceptABWithMinimumId" />
</xsl:when>
<!-- There exists no User with other than A or B but there is a User with name B -->
<xsl:when test="User[Name = 'B']">
<xsl:copy-of select="User[Name = 'B']" />
</xsl:when>
<!-- There is no User with other than A -->
<xsl:when test="User[Name='A']">
<xsl:copy-of select="User[Name = 'A']" />
</xsl:when>
<!-- Probably there is no User -->
<xsl:otherwise>
<xsl:comment>Oops, something is wrong.</xsl:comment>
</xsl:otherwise>
</xsl:choose>
</result>
</xsl:template>
</xsl:stylesheet>
When I used this input
<?xml version="1.0" encoding="UTF-8"?>
<Users>
<User>
<Name>A</Name>
<Id>8</Id>
</User>
<User>
<Name>B</Name>
<Id>9</Id>
</User>
<User>
<Name>C</Name>
<Id>6</Id>
</User>
<User>
<Name>D</Name>
<Id>10</Id>
</User>
<User>
<Name>E</Name>
<Id>11</Id>
</User>
</Users>
(and commented different User for testing purpose) it returned me desired output.