tesmc
asked on
XSL: How to use generate-id() to obtain unique values
I have the following input file.
I want to obtain each unique TicketNumber | TicketReference. And then use those values to obtain to unique IssuedDocNumber
Currently, I have
<xsl:key name="IssuedDocNumbers" match="IssuedDocNumber" use="." />
<xsl:variable name="tktNumber" select="TicketReference|Se rviceEleme nt/TicketN umber" />
<xsl:variable name="DocumentNumbers" select="/A/Envelope/Body/M iscRS/Fees /Linked/Fe e/Assoc[As socTicketN umber=subs tring($tkt Number,1,1 3)]/Issued DocNumber[ generate-i d()=genera te-id(key( 'IssuedDoc Numbers',. ))][1] | /A/Envelope/Body/MiscRS/Fe es/Linked/ Fee/Issued DocNumber" />
But $ DocumentNumbers only contains 8388209334265.
I expect it to hold 2 values 8388209334265 & 8388209334266.
What am I doing wrong?
<A>
<Request>
<Traveler>
<ServiceElement>
<TicketNumber>83876567104422</TicketNumber>
<CouponNumber>1</CouponNumber>
</ServiceElement>
<ServiceElement>
<TicketNumber>83876567104422</TicketNumber>
<CouponNumber>2</CouponNumber>
</ServiceElement>
<ServiceElement>
<TicketNumber>83876567104422</TicketNumber>
<CouponNumber>4</CouponNumber>
</ServiceElement>
<ServiceElement>
<TicketNumber>83876567104433</TicketNumber>
<CouponNumber>1</CouponNumber>
</ServiceElement>
<ServiceElement>
<TicketNumber>83876567104422</TicketNumber>
<CouponNumber>3</CouponNumber>
</ServiceElement>
<TicketReference>83876567104422</TicketReference>
</Traveler>
</Request>
<Envelope>
<Body>
<MiscRS>
<Fees>
<Linked>
<Fee>
<Assoc>
<AssocTicketNumber couponNumber="1">8387656710442</AssocTicketNumber>
<IssuedDocNumber couponNumber="1">8388209334265</IssuedDocNumber>
</Assoc>
</Fee>
<Fee>
<Assoc>
<AssocTicketNumber couponNumber="2">8387656710442</AssocTicketNumber>
<IssuedDocNumber couponNumber="2">8388209334265</IssuedDocNumber>
</Assoc>
</Fee>
<Fee>
<Assoc>
<AssocTicketNumber couponNumber="4">8387656710442</AssocTicketNumber>
<IssuedDocNumber couponNumber="4">8388209334265</IssuedDocNumber>
</Assoc>
</Fee>
<Fee>
<Assoc>
<AssocTicketNumber couponNumber="5">8387656710443</AssocTicketNumber>
<IssuedDocNumber couponNumber="1">8388209334266</IssuedDocNumber>
</Assoc>
</Fee>
<Fee>
<Assoc>
<AssocTicketNumber couponNumber="3">8387656710442</AssocTicketNumber>
<IssuedDocNumber couponNumber="3">8388209334265</IssuedDocNumber>
</Assoc>
</Fee>
</Linked>
</Fees>
</MiscRS>
</Body>
</Envelope>
</A>
I want to obtain each unique TicketNumber | TicketReference. And then use those values to obtain to unique IssuedDocNumber
Currently, I have
<xsl:key name="IssuedDocNumbers" match="IssuedDocNumber" use="." />
<xsl:variable name="tktNumber" select="TicketReference|Se
<xsl:variable name="DocumentNumbers" select="/A/Envelope/Body/M
But $ DocumentNumbers only contains 8388209334265.
I expect it to hold 2 values 8388209334265 & 8388209334266.
What am I doing wrong?
<xsl:variable name="tktNumber" select="TicketReference|Se rviceEleme nt/TicketN umber" />
As a global variable this one is taking nothing, so this happens inside a particular context I believe
I am not sure based on your XSLT snippet how many TicketReference you are catching
In any case some evil is happening here
Assoc[AssocTicketNumber=su bstring($t ktNumber,1 ,13)]
if you do a substring on a nodeset > 1 node in XSLT 1, you will do the substring on the 1st node of the nodeset
For me this proves you are using XSLT1, because that would be an error in XSLT2
As a global variable this one is taking nothing, so this happens inside a particular context I believe
I am not sure based on your XSLT snippet how many TicketReference you are catching
In any case some evil is happening here
Assoc[AssocTicketNumber=su
if you do a substring on a nodeset > 1 node in XSLT 1, you will do the substring on the 1st node of the nodeset
For me this proves you are using XSLT1, because that would be an error in XSLT2
Just tested, the issue indeed is the substring, working on an alternative
Can you check this?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:key name="IssuedDocNumbers" match="IssuedDocNumber" use="." />
<xsl:template match="/">
<xsl:for-each select="/A/Envelope/Body/MiscRS/Fees/Linked/Fee/Assoc/IssuedDocNumber[generate-id()=generate-id(key('IssuedDocNumbers',.)[1])]">
<xsl:copy-of select="key('IssuedDocNumbers',.)[/A/Request/Traveler/ServiceElement[substring(TicketNumber, 1, 13) = current()/parent::Assoc/AssocTicketNumber]][1]"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This one groups per unique IssuedDocNumber
and then checks within each group whether there is a backreference to an earlier AssocTicketNumber
and then checks within each group whether there is a backreference to an earlier AssocTicketNumber
ASKER
Correct I'm using xslt1
ASKER
@Geert Bormans - ur solution is good . I get
<IssuedDocNumber couponNumber="1">838820933 4265</Issu edDocNumbe r>
<IssuedDocNumber couponNumber="1">838820933 4266</Issu edDocNumbe r>
But I want to store IssuedDocNumber into a variable because I then plan to loop through each value.
i.e -
<xsl:for-each select="$IssuedDocNumber ">
<IssuedDocNumber couponNumber="1">838820933
<IssuedDocNumber couponNumber="1">838820933
But I want to store IssuedDocNumber into a variable because I then plan to loop through each value.
i.e -
<xsl:for-each select="$IssuedDocNumber ">
ASKER
i tried doing
<xsl:variable name="test" >
<xsl:for-each select="/A/Envelope/Body/M iscRS/Fees /Linked/Fe e/Assoc/Is suedDocNum ber[genera te-id()=ge nerate-id( key('Issue dDocNumber s',.)[1])] ">
<xsl:copy-of select="key('IssuedDocNumb ers',.)[/A /Request/T raveler/Se rviceEleme nt[substri ng(TicketN umber, 1, 13) = current()/parent::Assoc/As socTicketN umber]][1] "/>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="$test">
but the for-each=$test would fail with "To use a result tree fragment in a path expression, first convert it to a node-set using the msxsl:node-set() function."
"To use a result tree fragment in a path expression, first convert it to a node-set using the msxsl:node-set() function."
<xsl:variable name="test" >
<xsl:for-each select="/A/Envelope/Body/M
<xsl:copy-of select="key('IssuedDocNumb
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="$test">
but the for-each=$test would fail with "To use a result tree fragment in a path expression, first convert it to a node-set using the msxsl:node-set() function."
"To use a result tree fragment in a path expression, first convert it to a node-set using the msxsl:node-set() function."
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
It used to work when I had
xsl:variable name="tktNumber" select="TicketReference|Se rviceEleme nt/TicketN umber[1]" />
But that's bc $tktNumber was ever returning one instance. But now that I took the [1] out I have a collection that I, trying to match against.
xsl:variable name="tktNumber" select="TicketReference|Se
But that's bc $tktNumber was ever returning one instance. But now that I took the [1] out I have a collection that I, trying to match against.
ASKER
I tried the below, but $thisNumber contains both IssuedDocNumbers instead of looping through each. Am I missing something?
<xsl:variable name="test" >
<xsl:for-each select="/A/Envelope/Body/MiscRS/Fees/Linked/Fee/Assoc/IssuedDocNumber[generate-id()=generate-id(key('IssuedDocNumbers',.)[1])]">
<xsl:copy-of select="key('IssuedDocNumbers',.)[/A/Request/Traveler/ServiceElement[substring(TicketNumber, 1, 13) = current()/parent::Assoc/AssocTicketNumber]][1]"/>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="msxsl:node-set($test)">
<xsl:variable name="thisNumber" select="."/>
</xsl:for-each>
node-set() wraps a document node around the elements
try this
try this
<xsl:for-each select="msxsl:node-set($test)/IssuedDocNumber">
<test>
<xsl:copy-of select="."/>
</test>
</xsl:for-each>
ASKER
But will ur suggestion store into a variable. It's right now outputting into <test> node
Well, first things first.
Your code only created 1 iteration node, my code created two, so the select attribute in my for-each is what you need
A variable in XSLT does not vary and only exists in the context it is created
<xsl:variable name="thisNumber" select="."/>
will never exist outside the for-each and only in the one active 'branch' of the visitor
If you want to create a variable for each node in $test... you can't AND that is not XSLT common practice
Why bother about having different variables if you can do everything you wantw ith one variable
msxsl:node-set($test)/Issu edDocNumbe r[1]
would be the content of your first expected variable, why not use that directly?
msxsl:node-set($test)/Issu edDocNumbe r[2]
would be the content of your second expected variable, why not use that directly?
count(msxsl:node-set($test )/IssuedDo cNumber) would tell you how much nodes there are
Overall the requirement is too vague. I am trying to guide you on a way, but I am still guessing why you need all those variables
Your code only created 1 iteration node, my code created two, so the select attribute in my for-each is what you need
A variable in XSLT does not vary and only exists in the context it is created
<xsl:variable name="thisNumber" select="."/>
will never exist outside the for-each and only in the one active 'branch' of the visitor
If you want to create a variable for each node in $test... you can't AND that is not XSLT common practice
Why bother about having different variables if you can do everything you wantw ith one variable
msxsl:node-set($test)/Issu
would be the content of your first expected variable, why not use that directly?
msxsl:node-set($test)/Issu
would be the content of your second expected variable, why not use that directly?
count(msxsl:node-set($test
Overall the requirement is too vague. I am trying to guide you on a way, but I am still guessing why you need all those variables
ASKER
I was thinking that if I put the nodes into a variable then I could use it as parameter for another xml path.
Also, because i wanted to be able to traverse through the existing node containing that IssuedDocNumber.
i.e.
<xsl:variable name="test" select="../../../AssocTick etNumber | ../../AssocTicketNumber "/>
Also, because i wanted to be able to traverse through the existing node containing that IssuedDocNumber.
i.e.
<xsl:variable name="test" select="../../../AssocTick
Well, a variable gives you a static storage of some nodes (or 1 string, or 1 boolean, or 1 number)
That is teh decision you need to make, store a node-set (and access it with the node-set() function)
or store one single item value
Note that allthrough your XSLT you have access to the entire document tree.
Some smart XPath choice could save you a bunch of variables
That is teh decision you need to make, store a node-set (and access it with the node-set() function)
or store one single item value
Note that allthrough your XSLT you have access to the entire document tree.
Some smart XPath choice could save you a bunch of variables
ASKER
I did the following but $tktNum is empty. cpnNum returned expected results though. What am I doing wrong?
<xsl:variable name="test" >
<xsl:for-each select="/A/Envelope/Body/MiscRS/Fees/Linked/Fee/Assoc/IssuedDocNumber[generate-id()=generate-id(key('IssuedDocNumbers',.)[1])]">
<xsl:copy-of select="key('IssuedDocNumbers',.)[/A/Request/Traveler/ServiceElement[substring(TicketNumber, 1, 13) = current()/parent::Assoc/AssocTicketNumber]][1]"/>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="msxsl:node-set($test)/IssuedDocNumber">
<xsl:variable name="tktNum" select="../AssocTicketNumber"/>
<xsl:variable name="cpnNum" select="@couponNumber"/>
</xsl:for-each>
<xsl:variable name="tktNum" select="..Assoc/AssocTicke tNumber"/>
ASKER
that didn't work. xslt throws error. i also tried
<xsl:variable name="tktNum" select="../Assoc/AssocTick etNumber"/ >
but empty value.
<xsl:variable name="tktNum" select="../Assoc/AssocTick
but empty value.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
what does the "/parent::*" do at the end of $test variable
ASKER
Also, I observed that if i try to access another xpath within the for-loop it is not found.
i.e.
<Issue>
<xsl:value-of select="/A/Request/Travele r/ServiceE lement/Tic ketNumber[ 1]"/>
</Issue>
comes up empty.
i.e.
<Issue>
<xsl:value-of select="/A/Request/Travele
</Issue>
comes up empty.
what does the "/parent::*" do at the end of $test variableI go back up one step, to make sure the variable has a series of Assoc elements, not IssuedDocNumber
to be able to use the Assoc Number in the variable
Also, I observedYes, that is correct.
An XPath always starts from the context, so even if you do "/A..." you start from the current context and jump to the document node of the current context
A for-each changes context, and basically since your for-each uses node-set, the context of the source document is lost
you are now XPathing in a artificial constructed document, no longer in the source.
If that is what you want to do you can save the entire source in a global variable and go from there
$doc/ancestor::*[not(paren
but you are really stretching teh boundaries of XSLT1
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
don't think so
$docNum is a Result Tree Fragment
in order to start an XPtha from there, you need the node-set() function
You can put a select attribute on a variable
<xsl:variable name="doc" select="."/>
Then you can XPath into that variable (simply because you make a reference in the source document, you don't construct a piece of the output tree
You need all of that outside the for-each with the node-set, because you need the document context, not the node-set() context
$docNum is a Result Tree Fragment
in order to start an XPtha from there, you need the node-set() function
You can put a select attribute on a variable
<xsl:variable name="doc" select="."/>
Then you can XPath into that variable (simply because you make a reference in the source document, you don't construct a piece of the output tree
You need all of that outside the for-each with the node-set, because you need the document context, not the node-set() context
ASKER
thanks for answering all my questions.
I was able to use all that you suggested except for your last comment about "in order to start an XPtha from there, you need the node-set() function". I couldn't get that to work.
I'd appreciate if you can supply an example of that. Otherwise, I'll open new ticket if needed. THanks.
I was able to use all that you suggested except for your last comment about "in order to start an XPtha from there, you need the node-set() function". I couldn't get that to work.
I'd appreciate if you can supply an example of that. Otherwise, I'll open new ticket if needed. THanks.
XSLT1 can not contain sequences in a variable, only datatypes are nodeset, string, number, boolean
For sequences in a variable you should use XSLT2
so you would either concatenate all the values or only show the first one