I'm very much a beginner with xsl transformations
I have some xml that I need to insert an attribute into an element when that attribute doesn't exist..
Using the below xml as an example.
<Order Id="IR1598756" Status="2">
<Details>
<SomeInfo>Sample Data</SomeInfo>
</Details>
<Documents>
<Invoice>
<Date>15-02-2011</Date>
<Time>11:22</Time>
<Employee Id="159">James Morrison</Employee>
</Invoice>
<DeliveryNote>
<Reference>DN1235588</Reference>
<HoldingRef>HR1598785</HoldingRef>
<Date>16-02-2011</Date>
<Time>15:00</Time>
<Employee Id="25">Javi Cortez</Employee>
</DeliveryNote>
</Documents>
</Order>
Desired Output
<Order Id="IR1598756" Status="2">
<Details>
<SomeInfo>Sample Data</SomeInfo>
</Details>
<Documents>
<Invoice Id="DN1235588">
<Date>15-02-2011</Date>
<Time>11:22</Time>
<Employee Id="159">James Morrison</Employee>
</Invoice>
</Documents>
</Order>
The <Invoice> element can have an Id attribute <Invoice Id="IR1564897">
How can I check the following.
Check that the attribute exists
If not then Insert the Value of the <Refernce>DN1235588</Reference> as the Id
If there is no <Reference> Use the Value of the <HoldingRef>HR1598785</HoldingRef>
I was looking at implementing something like the following
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="//Order"/>
</xsl:template>
<xsl:template match="Order/Documents/Invoice[not(#Id)]">
<xsl:attribute name="Id">
<xsl:value-of select="//Documents/DeliveryNote/Reference"/>
</xsl:attribute>
</xsl:template>
The above is not outputting the full <Invoice> element.
How can I correct this?
<xsl:if test="Order/Documents/DeliveryNote/Reference">
<xsl:value-of select="//Documents/DeliveryNote/Reference"/>
</xsl:if>
<xsl:if test="Not(Order/Documents/DeliveryNote/Reference)">
<xsl:value-of select="//Documents/DeliveryNote/HoldingRef"/>
</xsl:if>
If either one will always exist will this work to alternate between <Reference> and <HoldingRef>?
With the help of Alex:
The following has worked for me to replace the attribute
<xsl:template match="Order/Documents/Invoice[not(#Id)]">
<Invoice>
<xsl:attribute name="Id">
<xsl:value-of Select="//Documents/DeliveryNote/Reference"/>
</xsl:attribute>
<xsl:apply-templates select="#* | node()"/>
</Invoice>
</xsl:template>
The shortest answer:
<xsl:template match="Invoice[not(#Id)]">
<Invoice Id="{(../DeliveryNote/Reference|
../DeliveryNote/HoldingRef)[1]}">
<xsl:apply-templates select="#* | node()"/>
</Invoice>
</xsl:template>
Give a try:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates select="//Order"/>
</xsl:template>
<!-- Match invoices without ids -->
<!-- [DeliveryNote[Reference or HoldingRef] - defend against empty attributes -->
<xsl:template match="Invoice[not(#id)][DeliveryNote[Reference or HoldingRef]]">
<xsl:copy>
<!-- create an attribute and fetch required data. In case Reference is present then insert reference
otherwise - HoldingRef -->
<xsl:attribute name="Id">
<xsl:value-of select="following-sibling::DeliveryNote[1]/Reference |
following-sibling::DeliveryNote[1]/HoldingRef"/>
</xsl:attribute>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//DeliveryNote"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
As for your questions:
The above is not outputting the full element. How can I correct this?
See my example.
If either one will always exist will this work to alternate between <Reference> and <HoldingRef>?
Either with XPath (as in my example) or with xsl:choose.
What about this?
<xsl:template match="Invoice[not(#Id)]">
<xsl:element name="Invoice">
<xsl:attribute name="Id">
<xsl:variable name="REF" select="../DeliveryNote/Reference"/>
<xsl:choose>
<xsl:when test="not($REF)">
<xsl:value-of select="../DeliveryNote/HoldingRef"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="../DeliveryNote/Reference"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:apply-templates select="*"/>
</xsl:element>
</xsl:template>
Use it instead your <xsl:template match="Order/Documents/Invoice[not(#Id)]">
Related
I want to transform below XML using XSLT. This XML elements has dots to represent hierarchy
<UsrEmployee>
<Code>70068579</Code>
<Initials>F</Initials>
<FirstName>Koichi</FirstName>
<Prefix></Prefix>
<LastName>Nakamura</LastName>
<PropertyRef>70068579</PropertyRef>
<SpaceRef.Code>001</SpaceRef.Code>
<SpaceRef.FloorRef.Code>01</SpaceRef.FloorRef.Code>
<SpaceRef.FloorRef.PropertyRef>70068579</SpaceRef.FloorRef.PropertyRef>
<SpaceRef.propertyRef>70068579</SpaceRef.propertyRef>
</UsrEmployee>
The above XML I want to transform as below XML, in the source XML element names can be any thing, number of dots(depth) is not known(not fixed). I want to create XSLT which would transform any generic XML of any size to hierarchical structure
<UsrEmployee>
<Code>70068579</Code>
<Initials>F</Initials>
<FirstName>Koichi</FirstName>
<Prefix></Prefix>
<LastName>Nakamura</LastName>
<SpaceRef>
<Code>001</Code>
<propertyRef>70068579</propertyRef>
<FloorRef>
<Code>01</Code>
<PropertyRef>70068579</PropertyRef>
</FloorRef>
</SpaceRef>
<PropertyRef>70068579</PropertyRef>
</UsrEmployee>
Can someone help me on this
Just create matching templates for your "dotted" elements and apply them.
Use select to control the "catch-all" identity template (the last template in the xslt)
Of course this may be an oversimplified solution and a more complex transformation will be needed if you have an arbitrary hierarchical structure where selecting your templates like this is not practicable.
<?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="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="UsrEmployee">
<xsl:element name="UsrEmployee">
<xsl:apply-templates select="Code | Initials | FirstName | Prefix | LastName | PropertyRef" />
<xsl:element name="SpaceRef">
<xsl:apply-templates select="SpaceRef.Code | SpaceRef.propertyRef" />
<xsl:element name="FloorRef">
<xsl:apply-templates select="SpaceRef.FloorRef.Code | SpaceRef.FloorRef.PropertyRef" />
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="SpaceRef.Code">
<xsl:element name="Code">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<xsl:template match="SpaceRef.propertyRef">
<xsl:element name="propertyRef">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<xsl:template match="SpaceRef.FloorRef.Code">
<xsl:element name="Code">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<xsl:template match="SpaceRef.FloorRef.PropertyRef">
<xsl:element name="PropertyRef">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Hi I have an xml document which look like that:
<a> <!-- Several nodes "a" with the same structure for children -->
<b>12</b>
<c>12</c>
<d>12</d>
<e>12</e>
<f>12</f>
<g>12</g>
</a>
I'm trying to obtain the following document using xslt 2.0
<a>
<b>12</b>
<c>12</c>
<wrap>
<d>12</d>
<e>12</e>
<f>12</f>
<g>12</g>
</wrap>
</a>
I started my xsl file with
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
And changed it for several cases like replacing a string part, filter some nodes, etc.
But I'm stuck with "selecting four consecutive nodes", any clue on how to achieve the wrapping?
If all your a elements are genuinely exactly the same structure then the simplest would be a brute-force
<xsl:template match="a">
<xsl:copy>
<xsl:apply-templates select="b | c" />
<wrap>
<xsl:apply-templates select="d | e | f | g" />
</wrap>
</xsl:copy>
</xsl:template>
or if you want to be a bit cleverer
<wrap>
<xsl:apply-templates select="* except (b | c)" />
</wrap>
If you want to always "wrap" the last four child elements of a, then how about
<xsl:template match="a">
<xsl:variable name="lastFour" select="*[position() > (last() - 4)]" />
<xsl:copy>
<xsl:apply-templates select="* except $lastFour" />
<wrap>
<xsl:apply-templates select="$lastFour" />
</wrap>
</xsl:copy>
</xsl:template>
With XSLT 2.0 you can also make use of for-each-group group-adjacent:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a">
<xsl:copy>
<xsl:for-each-group select="*" group-adjacent="boolean(self::d | self::e | self::f | self::g)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<wrap>
<xsl:apply-templates select="current-group()"/>
</wrap>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Hope find a guru's help to figure out the next problem.
I have two xml files. Firts one here (text.xml):
<text>
<ref>Author1, Title1, Date1</ref>
<ref>Author75, Title75, Date2</ref>
<ref>Author2, Title2, Date2</ref>
<ref>Author3, Title3, Date3</ref>
<text>
And the second one like this (list.xml):
<list>
<bibl xml:id="1"><author>Author1</author><date>Date1</date></bibl>
<bibl xml:id="2"><author>Author2</author><date>Date2</date></bibl>
<bibl xml:id="3"><author>Author3</author><date>Date3</date></bibl>
</list>
I want to query text.xml and check against list.xml to add #xml:id (from list.xml) to <ref> (from text.xml) wich contain same Author and Date. If not, then just copy original <ref>.
So I want to obtain:
<ref xml:id="1">Author1, Title1, Date1</ref>
<ref>Author75, Title75, Date2</ref>
<ref xml:id="2>Author2, Title2, Date2</ref>
etc.
My XSLT identify well all correpondence:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ref">
<xsl:variable name="ref" select="."/>
<xsl:for-each select="document('list.xml')//bibl">
<xsl:variable name="bibl" select="."/>
<xsl:variable name="author" select="author"/>
<xsl:variable name="date" select="date"/>
<xsl:choose>
<xsl:when test="contains($ref, $author) and contains($ref, $date)">
<ref>
<xsl:attribute name="xml:id">
<xsl:value-of select="$bibl/#xml:id"/>
</xsl:attribute>
<xsl:value-of select="$ref"/>
</ref>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$ref"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
But, then there aren't correpondence it's not just copy right <ref>, but copy all <ref> the number of time I have <bibl> nodes in the second file.
So problem is in <xsl:otherwise><xsl:copy-of select="$ref"/></xsl:otherwise>.
Any ideas how I can obtain only this distinct value I need? I know it's must be very simple actually and I try key, generate-id, for-each-group, distinct-values, but can't figure it out.
The problem is that you are creating a ref element for each iteration of the for-each loop whether there is a match or not.
What you need to do in this case is create the ref element outside of the for-each and then only create the id attribute for the matching element inside the loop
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ref">
<xsl:variable name="ref" select="."/>
<ref>
<xsl:apply-templates select="#* "/>
<xsl:for-each select="document('list.xml')//bibl">
<xsl:variable name="bibl" select="."/>
<xsl:variable name="author" select="author"/>
<xsl:variable name="date" select="date"/>
<xsl:choose>
<xsl:when test="contains($ref, $author) and contains($ref, $date)">
<xsl:attribute name="xml:id">
<xsl:value-of select="$bibl/#xml:id"/>
</xsl:attribute>
</xsl:when>
</xsl:choose>
</xsl:for-each>
<xsl:apply-templates select="node()"/>
</ref>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<text>
<ref xml:id="1">Author1, Title1, Date1</ref>
<ref>Author75, Title75, Date2</ref>
<ref xml:id="2">Author2, Title2, Date2</ref>
<ref xml:id="3">Author3, Title3, Date3</ref>
</text>
However, your current method is not very efficient, as for each ref element you are iterating over all bibl elements. Another approach would be to extract the author and date from the ref elements, and then look up the bibl element directly
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ref">
<xsl:variable name="author" select="normalize-space(substring-before(., ','))"/>
<xsl:variable name="date" select="normalize-space(substring-after(substring-after(., ','), ','))"/>
<ref>
<xsl:apply-templates select="#* "/>
<xsl:apply-templates select="document('list.xml')//bibl[author=$author][date=$date]"/>
<xsl:apply-templates select="node()"/>
</ref>
</xsl:template>
<xsl:template match="bibl">
<xsl:attribute name="xml:id">
<xsl:value-of select="#xml:id"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
This should also give the same results.
EDIT:
humm...too much focus on xsl syntax, i should have seen that earlier...
you have an implicated outer loop over each ref and an inner loop over each bibl. You generate one element for every bibl for every ref regardless of match or no match.
So, instead of the xsl:otherwise you need a check after the for-each loop to see if there was no match and do the copy-of if neccessary.
Not sure how to do the check, though..maybe using position() and count() of the generated <ref>s, sorry...don't have any more time to think about this right now.
not really an explanation, but a workaround:
<xsl:otherwise>
<ref>
<xsl:value-of select="$ref"/>
</ref>
</xsl:otherwise>
My guess is that the problem lies in $ref, which is not a xpath expression at that moment (if i remember xsl-t correctly)
My xml structure contains a program as the parent of both certificates and courses. I want to split the structure up to create an independent listing of certificates and courses without the common program parent. The original structure is:
<root>
<program>
<program-name>Math</program-name>
<certificate>...</certificate> <!-- There can 0 or more of these -->
<course>...</course> <!-- There can 0 or more of these -->
</program>
<program>
...
</program>
</root>
The output should look like so:
<root>
<program-courses>
<program>
<program-name>Math</program-name>
<course/> <!-- There can 0 or more of these -->
</program>
...
</program-courses>
<program-certificates>
<program>
<program-name>Math</program-name>
<certificate/> <!-- There can 0 or more of these -->
</program>
...
</program-certificates>
</root>
Update: Answered using Paul's suggestion for using a mode this is what the relevant portion of the xslt became:
<xsl:template match="root">
<xsl:element name="root">
<xsl:element name="program-courses">
<xsl:apply-templates select="root/program-area" mode="course"/>
</xsl:element>
<xsl:element name="program-certificates">
<xsl:apply-templates select="root/program-area" mode="certificate"/>
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="program-area" mode="course">
<xsl:element name="program-area">
<!-- Get the name here -->
<xsl:element name="course">
<xsl:apply-templates select="course"/>
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="program-area" mode="certificate">
<xsl:element name="program-area">
<!-- Get the name here -->
<xsl:element name="course">
<xsl:apply-templates select="certificate"/>
</xsl:element>
</xsl:element>
</xsl:template>
Note that this solution is pared down from the actual one so it may not work as is against the original input.
Selecting program elements, You could use #mode (on apply-templates and a corresponding template) to differentiate between whether you are operating within the output of program-courses or program-certificates
From root, you could select program/course or program/certificate to generate the output program.
From root, you could use for-each select="program" and for the part that is intended to output program-courses only extract the program-name and course element, and perform the corresponding extraction in the part that outputs program-certificates.
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="program">
<xsl:apply-templates select="course[1]|certificate[1]"/>
</xsl:template>
<xsl:template match="course[1]">
<program-courses>
<program>
<xsl:apply-templates select="../*[not(self::certificate)]"
mode="copy"/>
</program>
</program-courses>
</xsl:template>
<xsl:template match="certificate[1]">
<program-certificates>
<program>
<xsl:apply-templates select="../*[not(self::course)]"
mode="copy"/>
</program>
</program-certificates>
</xsl:template>
<xsl:template match="node()" mode="copy">
<xsl:call-template name="identity"/>
</xsl:template>
</xsl:stylesheet>
Output:
<root>
<program-certificates>
<program>
<program-name>Math</program-name>
<certificate>...</certificate>
</program>
</program-certificates>
<program-courses>
<program>
<program-name>Math</program-name>
<course>...</course>
</program>
</program-courses>
</root>
EDIT: If you want something more "push style" like your posted solution:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="node()" mode="certificate">
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="node()" mode="course">
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="program">
<program-courses>
<program>
<xsl:apply-templates mode="course"/>
</program>
</program-courses>
<program-certificates>
<program>
<xsl:apply-templates mode="certificate"/>
</program>
</program-certificates>
</xsl:template>
<xsl:template match="course" mode="certificate"/>
<xsl:template match="certificate" mode="course"/>
</xsl:stylesheet>
How can i replace an attribute in xml using xsl transformation, depending on its value.
For example, if there is such xml
<Text Style='style1'>
...
</Text>
transform it to
<Text Font='Arial' Bold='true' Color='Red'>
...
</Text>
For Style='style2' set another attributes and values, for example Font='Sans' Italic='true'.
One posible way: ussing attribute sets. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:attribute-set name="style1">
<xsl:attribute name="Font">Arial</xsl:attribute>
<xsl:attribute name="Bold">true</xsl:attribute>
<xsl:attribute name="Color">Red</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="style2">
<xsl:attribute name="Font">Sans</xsl:attribute>
<xsl:attribute name="Italic">true</xsl:attribute>
</xsl:attribute-set>
<xsl:template match="Text[#Style='style1']">
<xsl:copy use-attribute-sets="style1">
<xsl:copy-of select="#*[name()!='Style']"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="Text[#Style='style2']">
<xsl:copy use-attribute-sets="style2">
<xsl:copy-of select="#*[name()!='Style']"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With this input:
<root>
<Text Style='style1'></Text>
<Text Style='style2'></Text>
</root>
Output:
<Text Font="Arial" Bold="true" Color="Red"></Text>
<Text Font="Sans" Italic="true"></Text>
Other way: inline "attribute sets". This stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my"
exclude-result-prefixes="my">
<xsl:output indent="yes"/>
<my:style1 Font="Arial" Bold="true" Color="Red"/>
<my:style2 Font="Sans" Italic="true"/>
<xsl:template match="Text[#Style]">
<xsl:copy>
<xsl:copy-of select="document('')/*/my:*
[local-name()=current()/#Style]/#*"/>
<xsl:copy-of select="#*[name()!='Style']"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You will need to have some kind of rule that you use to convert the style from one to the other. Making the assumption you are inputting html you will need something like.
<xsl:template match="#* | node()">
<xsl:choose>
<xsl:when test="local-name() = 'Style'">
<xsl:apply-templates select="." mode="Style" />
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="#Style" mode="Style">
<xsl:choose>
<xsl:when test="node() = 'style1'">
<xsl:attribute name="Font">Arial</xsl:attribute>
<xsl:attribute name="Bold">true</xsl:attribute>
<xsl:attribute name="Color">Red</xsl:attribute>
</xsl:when>
<xsl:when test="node() = 'style2'">
<xsl:attribute name="Font">Sans</xsl:attribute>
<xsl:attribute name="Bold">true</xsl:attribute>
</xsl:when>
</xsl:choose>
</xsl:template>