Groupby distinct how can I do that? - xslt

<?xml version="1.0"?>
<Products>
<product>
<productId >1</productId>
<textdate>11/11/2011</textdate>
<price>200</price>
</product>
<product>
<productId >6</productId>
<textdate>11/11/2011</textdate>
<price>100</price>
</product>
<product>
<productId >1</productId>
<textdate>16/11/2011</textdate>
<price>290</price>
</product>
</Products>
I've this xml and I want an xslt transformation that regroup product something like this :
{ product 1 :
11/11/2011 - 200
16/11/2011 - 290 }
{ product 6
11/11/2011 - 100 }
I work with xslt 1.0 Asp .net C# XslCompiledTransformation

Use Muenchian grouping as explained here: http://www.jenitennison.com/xslt/grouping/index.xml. If you need help with writing the code then please state whether you want plain text output or HTML output in the format you posted.

This 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="text"/>
<xsl:strip-space elements="*"/>
<xsl:key name="groupById" match="product" use="productId"/>
<xsl:template match="/*">
<xsl:apply-templates select="product[
generate-id() =
generate-id( key( 'groupById', productId ) )
]"/>
</xsl:template>
<xsl:template match="product">
<xsl:text>{ product </xsl:text>
<xsl:value-of select="concat(productId, ' :
')"/>
<xsl:apply-templates select="key( 'groupById', productId )" mode="inner-content"/>
<xsl:text> }
</xsl:text>
</xsl:template>
<xsl:template match="product" mode="inner-content">
<xsl:value-of select="concat( textdate, ' - ', price )"/>
<xsl:if test="position() != last()">
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Applied to your code sample, it will produce this result:
{ product 1 :
11/11/2011 - 200
16/11/2011 - 290 }
{ product 6 :
11/11/2011 - 100 }

Related

How to remove duplicate entry - XSLT

I am try to remove duplicate entry after entity § and if contains the , in entry and after tokenize the start-with the ( round bracket then entry e.g (17200(b)(2), (4)–(6)) s/b e.g (<p>17200(b)(2)</p><p>17200(b)(4)–(6)</p>).
Input XML
<root>
<p>CC §1(a), (b), (c)</p>
<p>Civil Code §1(a), (b)</p>
<p>CC §§2(a)</p>
<p>Civil Code §3(a)</p>
<p>CC §1(c)</p>
<p>Civil Code §1(a), (b), (c)</p>
<p>Civil Code §17200(b)(2), (4)–(6), (8), (12), (16), (20), and (21)</p>
</root>
Expected Output
<root>
<sec specific-use="CC">
<title content-type="Sta_Head3">CIVIL CODE</title>
<p>1(a)</p>
<p>1(b)</p>
<p>1(c)</p>
<p>2(a)</p>
<p>3(a)</p>
<p>17200(b)(2)</p>
<p>17200(b)(4)–(6)</p>
<p>17200(b)(8)</p>
<p>17200(b)(12)</p>
<p>17200(b)(16)</p>
<p>17200(b)(20)</p>
<p>17200(b)(21)</p>
</sec>
</root>
XSLT Code
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="p[(starts-with(., 'CC ') or starts-with(., 'Civil Code'))]" group-by="replace(substring-before(., ' §'), 'Civil Code', 'CC')">
<xsl:text>
</xsl:text>
<sec specific-use="{current-grouping-key()}">
<xsl:text>
</xsl:text>
<title content-type="Sta_Head3">CIVIL CODE</title>
<xsl:for-each-group select="current-group()" group-by="replace(substring-after(., '§'), '§', '')">
<xsl:sort select="replace(current-grouping-key(), '[^0-9.].*$', '')" data-type="number" order="ascending"/>
<xsl:for-each
select="distinct-values(
current-grouping-key() !
(let $tokens := tokenize(current-grouping-key(), ', and |, | and ')
return (head($tokens), tail($tokens) ! (substring-before(head($tokens), '(') || .)))
)" expand-text="yes">
<p>{.}</p>
</xsl:for-each>
</xsl:for-each-group>
</sec>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
You could do it like this, in a two-step approach where you first compute the list of existing elements and then use a for-each-group to remove duplicates.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="listP">
<xsl:apply-templates select="root/p"/>
</xsl:variable>
<xsl:for-each-group select="$listP" group-by="p">
<p><xsl:value-of select="current-grouping-key()"/></p>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="p">
<xsl:variable name="input" select="replace(substring-after(.,'§'),'§','')"/>
<xsl:variable name="chapter" select="substring-before($input,'(')"/>
<xsl:for-each select="tokenize(substring-after($input, $chapter),',')">
<p><xsl:value-of select="concat($chapter,replace(replace(.,' ',''),'and',''))"/></p>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
See it working here : https://xsltfiddle.liberty-development.net/gVrvcxQ

Compare data of 2 xmls in xslt

I am new to XSL. Hence please help me with the below.
I have 2 xmls. I have to do the following in XSL transformation.
if Employee/EmployeeInfo/FirstName = EmployeeSegment/EmployeeSummary/GivenName and Employee/EmployeeInfo/LastName = EmployeeSegment/EmployeeSummary/Surname
employeeId = EmployeeSegment/EmployeeSummary/EmpId
XML1
<Employee>
<EmployeeInfo>
<FirstName>ABC</FirstName>
<LastName>DEF</LastName>
</EmployeeInfo>
</Employee>
XML2
<EmployeeSegment>
<EmployeeSummary>
<EmpId>1234</EmpId>
<GivenName>ABC</GivenName>
<Surname>DEF</Surname>
</EmployeeSummary>
</EmployeeSegment>
I have tried the following. It is not working.
<xsl:param name="cjEmployeeSegment" select="document('CJ_Response.xml')"/>
<xsl:for-each select="/ns3:Employee/ns3:EmployeeInfo">
<xsl:variable name="empFirstName">
<xsl:value-of select="ns1:FirstName"/>
</xsl:variable>
<xsl:variable name="empLastName">
<xsl:value-of select="ns1:LastName"/>
</xsl:variable>
<xsl:for-each select="$cjEmployeeSegment/v32:EmployeeSegment/v31:EmployeeSummary">
<xsl:if test="$empFirstName=v31:GivenName and $empLastName=v31:Surname">
<ns12:EmployeeIdentifier>
<ns12:EmployeeID>
<xsl:value-of select="v31:EmpId"/>
</ns12:EmployeeID>
</ns12:EmployeeIdentifier>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
Assuming you are processing the following input:
XML
<Employee>
<EmployeeInfo>
<FirstName>ABC</FirstName>
<LastName>DEF</LastName>
</EmployeeInfo>
</Employee>
and there is another XML document named CJ_Response.xml:
<EmployeeSegment>
<EmployeeSummary>
<EmpId>1234</EmpId>
<GivenName>ABC</GivenName>
<Surname>DEF</Surname>
</EmployeeSummary>
</EmployeeSegment>
you can use the following stylesheet:
XSLT 1.0
<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:param name="cj_Response" select="document('CJ_Response.xml')"/>
<xsl:template match="/Employee">
<root>
<xsl:for-each select="EmployeeInfo">
<xsl:variable name="lookup" select="$cj_Response/EmployeeSegment/EmployeeSummary[GivenName = current()/FirstName and Surname = current()/LastName]" />
<xsl:if test="$lookup">
<EmployeeIdentifier>
<EmployeeID>
<xsl:value-of select="$lookup/EmpId"/>
</EmployeeID>
</EmployeeIdentifier>
</xsl:if>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
to return:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<EmployeeIdentifier>
<EmployeeID>1234</EmployeeID>
</EmployeeIdentifier>
</root>
Of course, this will fail miserably if there are two or more employees with the same name.

In XSLT replace value

I have this XML document :Now I want to replace LineNo so that the output will be line no will 1 ,2 . I have tried some thing like this.
<xsl:value-of select="replace( '000010',1)"/>
<Rder>
<Order>
<OrderNo>458</OrderNo>
<LineNo>000010</LineNo>
<SerialNO>96</SerialNO>
<VNo>543</VNo>
</Order>
<Order>
<OrderNo>458</OrderNo>
<LineNo>000020</LineNo>
<SerialNO>32</SerialNO>
<VNo>543</VNo>
</Order>
</Rder>
I want to replace the value of LineNo= 000010 ,000020 by 1,2 in XSLT below one i have tried.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="orders" match="Order" use="OrderNo" />
<xsl:template match="/*">
<SalesOrders>
<xsl:for-each select="Rder/Order[generate-id() = generate-id(key('orders', OrderNo)[1])]">
<Order VNo="{VNo}" OrderNo="{OrderNo}">
<OrderLines>
<xsl:apply-templates select="key('orders', OrderNo)" />
</OrderLines>
</Order>
</xsl:for-each>
</SalesOrders>
</xsl:template>
<xsl:template match="Order">
<OrderLine LineNo="{LineNo}" SerialNO="{SerialNO}"/>
</xsl:template>
</xsl:stylesheet>
Actually I getting those lineno details in same format i have tried couple cases its doesn't giving that expected format.
Any help would be appreciated.
Why don't you do simply:
<xsl:template match="Order">
<OrderLine LineNo="{position()}" SerialNO="{SerialNO}"/>
</xsl:template>
or:
<xsl:template match="Order">
<OrderLine LineNo="{number(LineNo) div 10}" SerialNO="{SerialNO}"/>
</xsl:template>

Biztalk Mapper XSLT to strip out string from input

I have an source schema with a node forenames (containing forename + ' ' + middlename), which I need to separate out the middle name in the destination schema so this goes out to OtherNames.
I currently have the following xslt template:
<xsl:template name="StringSplit">
<xsl:param name="valFirstnames" />
<xsl:choose>
<xsl:when test="contains($valFirstnames, ' ')">
<xsl:call-template name="StringSplit">
<xsl:with-param name="valFirstnames" select="substring-after($valFirstnames, ' ')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<OtherFirstNames><xsl:value-of select="$valFirstnames" /></OtherFirstNames>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Current output for this xslt template is writing out the Middlename twice rather than once:
<OtherFirstName>Middlename</OtherFirstName>
<OtherFirstName>Middlename</OtherFirstName>
Expected:
<OtherFirstName>Middlename</OtherFirstName>
Sample Input
<Data>
<SubjectName>
<forenames>first middle</forenames>
</SubjectName>
<SubjectPartner>
<forenames>first middle</forenames>
<Otherforenames>first middle</Otherforenames>
</SubjectPartner>
<etc./>
</Data>
Sample Output
<Data>
<SubjectName>
<firstname>first</firstname>
<OtherFirstName>middle</OtherFirstname>
</SubjectName>
<SubjectPartner>
<firstname>first</firstname>
<OtherFirstName>middle</OtherFirstName>
<OtherFirstName>middle</OtherFirstName>
</SubjectPartner>
<etc./>
</Data>
I'm looking at correcting the current xslt and updating to incorporate other partner elements that have the same child elements forenames.
Your examples do not make much sense to me. I am guessing you want to do something like:
XSLT 1.0
<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:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="forenames">
<firstname><xsl:value-of select="substring-before(concat(., ' '), ' ')"/></firstname>
<xsl:if test="contains(., ' ')">
<OtherFirstName><xsl:value-of select="substring-after(., ' ')"/></OtherFirstName>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Note that this assumes a person has at most two forenames. When applied to the following test input:
<Data>
<SubjectName>
<forenames>Alan Benjamin</forenames>
<surname>Adams</surname>
</SubjectName>
<SubjectPartner>
<forenames>Cecily Diana</forenames>
<surname>Crown</surname>
</SubjectPartner>
<Single>
<forenames>Eve</forenames>
<surname>Evans</surname>
</Single>
<Triple>
<forenames>Frank George Herbert</forenames>
<surname>Forrester</surname>
</Triple>
</Data>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<Data>
<SubjectName>
<firstname>Alan</firstname>
<OtherFirstName>Benjamin</OtherFirstName>
<surname>Adams</surname>
</SubjectName>
<SubjectPartner>
<firstname>Cecily</firstname>
<OtherFirstName>Diana</OtherFirstName>
<surname>Crown</surname>
</SubjectPartner>
<Single>
<firstname>Eve</firstname>
<surname>Evans</surname>
</Single>
<Triple>
<firstname>Frank</firstname>
<OtherFirstName>George Herbert</OtherFirstName>
<surname>Forrester</surname>
</Triple>
</Data>

Convert short form days of the week to day names in xslt

I have some short form day names like so:
M -> Monday
T -> Tuesday
W -> Wednesday
R -> Thursday
F -> Friday
S -> Saturday
U -> Sunday
How can I convert an xml element like <days>MRF</days> into the long version <long-days>Monday,Thursday,Friday</long-days> using xslt?
Update from comments
Days will not be repeated
This stylesheet
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:d="day"
exclude-result-prefixes="d">
<d:d l="M" n="Monday"/>
<d:d l="T" n="Tuesday"/>
<d:d l="W" n="Wednesday"/>
<d:d l="R" n="Thursday"/>
<d:d l="F" n="Friday"/>
<d:d l="S" n="Saturday"/>
<d:d l="U" n="Sunday"/>
<xsl:variable name="vDays" select="document('')/*/d:d"/>
<xsl:template match="days">
<long-days>
<xsl:apply-templates
select="$vDays[contains(current(),#l)]"/>
</long-days>
</xsl:template>
<xsl:template match="d:d">
<xsl:value-of select="#n"/>
<xsl:if test="position()!=last()">,</xsl:if>
</xsl:template>
</xsl:stylesheet>
With this input:
<days>MRF</days>
Output:
<long-days>Monday,Thursday,Friday</long-days>
Edit: For those who wander, retaining the sequence order:
<xsl:variable name="vCurrent" select="current()"/>
<xsl:apply-templates
select="$vDays[contains($vCurrent,#l)]">
<xsl:sort select="substring-before($vCurrent,#l)"/>
</xsl:apply-templates>
Note: Because days wouldn't be repeated, this is the same as looking up for item existence in sequence with empty string separator.
That should do it... (There might be more elegant solutions though... ;-)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/days">
<long-days>
<xsl:if test="contains(.,'M')">Monday<xsl:if test="string-length(substring-before(.,'M'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'T')">Tuesday<xsl:if test="string-length(substring-before(.,'T'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'W')">Wednesday<xsl:if test="string-length(substring-before(.,'W'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'R')">Thursday<xsl:if test="string-length(substring-before(.,'R'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'F')">Friday<xsl:if test="string-length(substring-before(.,'F'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'S')">Saturday<xsl:if test="string-length(substring-before(.,'S'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'U')">Sunday<xsl:if test="string-length(substring-before(.,'U'))=string-length(.)-1">,</xsl:if></xsl:if>
</long-days>
</xsl:template>
The currently accepted solution always displays the long days names in chronological order and in addition, it doesn't display repeating (with same code) days.
Suppose we have the following XML document:
<days>STMSU</days>
I. This XSLT 1.0 transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" exclude-result-prefixes="my" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<my:days>
<M>Monday</M>
<T>Tuesday</T>
<W>Wednesday</W>
<R>Thursday</R>
<F>Friday</F>
<S>Saturday</S>
<U>Sunday</U>
</my:days>
<xsl:key name="kLongByShort" match="my:days/*"
use="name()"/>
<xsl:variable name="vstylesheet"
select="document('')"/>
<xsl:template match="days">
<long-days>
<xsl:call-template name="expand"/>
</long-days>
</xsl:template>
<xsl:template name="expand">
<xsl:param name="pcodeString" select="."/>
<xsl:if test="$pcodeString">
<xsl:variable name="vchar" select=
"substring($pcodeString,1,1)"/>
<xsl:for-each select="$vstylesheet">
<xsl:value-of select=
"concat(key('kLongByShort',$vchar),
substring(',',1,string-length($pcodeString)-1)
)
"/>
</xsl:for-each>
<xsl:call-template name="expand">
<xsl:with-param name="pcodeString" select=
"substring($pcodeString,2)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the above document, produces the wanted, correct result:
<long-days>Saturday,Tuesday,Monday,Saturday,Sunday</long-days>
II. This XSLT 2.0 transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vshortCodes" as="xs:integer+"
select="string-to-codepoints('MTWRFSU')"/>
<xsl:variable name="vlongDays" as="xs:string+"
select="'Monday','Tuesday','Wenesday','Thursday',
'Friday','Saturday','Sunday'
"/>
<xsl:template match="days">
<long-days>
<xsl:for-each select="string-to-codepoints(.)">
<xsl:value-of separator="" select=
"for $pos in position() ne last()
return
($vlongDays[index-of($vshortCodes,current())],
','[$pos])
"/>
</xsl:for-each>
</long-days>
</xsl:template>
</xsl:stylesheet>
when applied on the same XML document:
<days>STMSU</days>
produce the wanted, correct result:
<long-days>Saturday,Tuesday,Monday,Saturday,Sunday</long-days>