xpath1.0: post-filtering a node set by change in attribute when preceding sibling is not possible - xslt-1.0

Below, I have a subset of car nodes. I then want to process this further by filtering-in by a change in an attribute.
Because preceding-sibling works on the document, the second xpath below will not work if the preceding-sibling node is a non-yellow car. Is there a simple way to achieve this in xpath or with minimal xslt?
<xsl:template match="/">
<xsl:variable name="yellowCars" select="/cars/car[#color = 'yellow']"/>
<changedinspectiondate>
<xsl:apply-templates select="$yellowCars[not(#inspectiondate = preceding-sibling::car[1]/#inspectiondate)]"/>
</changedinspectiondate>
</xsl:template>

Related

Applying templates to an element whose sibling element has a certain attribute value

I am trying to apply a template using XSLT1 and I can't get it to work:
<xsl:apply-templates select="MeetingWorkBook/Meeting[1]/SpecialEvent/Event[/Date[#AllDayEvent='1']]" mode="FirstRow_Pray">
<xsl:with-param name="strClass">cellBold borderRight</xsl:with-param>
</xsl:apply-templates>
I am trying to apply this template:
<!--This is used to insert the Chairman, or Special Event-->
<xsl:template match="Chairman | Event" mode="FirstRow_Pray">
<xsl:param name="strClass"/>
<td class="{$strClass}">
<xsl:if test="self::Event">
<xsl:attribute name="rowspan">2</xsl:attribute>
</xsl:if>
<xsl:value-of select="."/>
</td>
</xsl:template>
But I only want to apply the template if the Event element exists and the sibling Date[#AllDayEvent] is true.
I know I can wrap my apply-templates call with if clause first and that would work(see below).
I know I can improve the logic of the Event template to test the value of the sibling elements attribute.
I have a lot of templates for using specific Event properties and would prefer the ability the select the item Event where its sibling element Date has a certain attribute value.
But my attempt does not work. I saw this answer and tried:
MeetingWorkBook/Meeting[1]/SpecialEvent/Event[Date[#AllDayEvent='1']]
But it still doesn't work.
This will work:
<xsl:if test="MeetingWorkBook/Meeting[1]/SpecialEvent/Date[#AllDayEvent='1']">
<xsl:apply-templates select="MeetingWorkBook/Meeting[1]/SpecialEvent/Event" mode="FirstRow_Pray">
<xsl:with-param name="strClass">cellBold borderRight</xsl:with-param>
</xsl:apply-templates>
</xsl:if>
But I want to avoid needing the if if possible.
I can now see what was wrong with my original attempt. Date is not a child of Event but a sibling.
MeetingWorkBook/Meeting[1]/SpecialEvent/Date
MeetingWorkBook/Meeting[1]/SpecialEvent/Event
So is it possible to apply a template on Event where the sibling Date has an attribute AllDayEvent with a value of 1. Or do I have no choice but to use the if approach?
Sample XML data:
<Meeting BookmarkId="2" PageBreak="0" NumberClasses="1" SpecialEvent="1" CircuitVisit="0">
<Date ThisWeek="W20200217" NextWeek="W20200224">February 17-23</Date>
<WeeklyBibleReading>GENESIS 18-19</WeeklyBibleReading>
<ReviewQuestion></ReviewQuestion>
<SpecialEvent>
<Event>Circuit Assembly - Love Jehovah With All Your Heart</Event>
<Location>Bristol Assembly Hall, Hortham Lane, Bristol, BS32 4JH</Location>
<Date Day="23" DayShort="Sun" DayFull="Sunday" Month="2" MonthShort="Feb" MonthFull="February" Year="2020" Memorial="0" AllDayEvent="1" MidweekEvent="1">23/02/2020</Date>
</SpecialEvent>
</Meeting>
You should be able to add the predicate to SpecialEvent instead of Event...
<xsl:apply-templates select="MeetingWorkBook/Meeting[1]/SpecialEvent[Date/#AllDayEvent='1']/Event" mode="FirstRow_Pray">
<xsl:with-param name="strClass">cellBold borderRight</xsl:with-param>
</xsl:apply-templates>

XSLT Function to pass a node argument concatenate a string then return element value

I'm fairly new to xslt but I searched through the topics to see if there is a match for mine but since I can't really understand most of the language I can't find them useful.
Here's what I'm trying to achieve. Basically, I want a custom function that can concatenate a string to a node path and return the element value without a template match. Maybe you can provide some example of function code that can do that and I can review it.
Basically what I want to do is pass the </root/AccountDetails/plantype> node to a function like a <xsl:value-of select=”testlang(/root/AccountDetails/plantype)”/>
In the function, it will do the language test and return the element value of the node (Please note there are a lot of English and French nodes nested in different levels) that why I'm passing the whole path. I'm limited to XSLT 1.0 and can't change the XML passed
XML Sample
<root>
<AccountDetails>
<plantype>RRSP</plantype>
<plantype_fr>CELI</plantype_fr>
</AccountDetails>
<ClientDetails>
<salutation>Mr.</salutation>
<salutation_fr>Monseiur</salutation_fr>
</ClientDetails>
<lang>fr</lang>
</root>
Possible function code
<xsl:function name=”testlang”>
<xsl:param name=”str”/>
<xsl:choose>
<xsl:when test=”/root/lang[.=’fr’]>
<xsl:value-of select=”concat($str,’_fr’)”/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select=”$str”/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>

xslt copy deep xml if exists if not create

<root>
<xnode>
<Node1/>
<Node2/>
<Node3>
<CNode1>
<CCNode1>
<CCField1>
<CCField2>
<CCCNode1/>
</CCNode1>
<CCNode2>
<CCCNode3/>
</Node3>
<Node4/>
</xnode>
<xnode>
<Node1/>
<Node2/>
<Node3>
<CNode1>
<CCNode2>
<CCCNode3/>
</Node3>
<Node4/>
</xnode>
<xnode>
<Node1/>
<Node2/>
<Node3>
<CNode1>
<CCNode1>
<CCField1>
<CCField2>
<CCCNode1/>
</CCNode1>
<CCNode2>
<CCCNode3/>
</Node3>
<Node4/>
</xnode>
</root>
In the above xml, I need to copy all the nodes and values except for Node3 - CNode1 - CCNode1. i.e. if CCNode1 exists copy as its including the child elements, if not, create CCNode1 with the corresponding fields and child elements. For ex, here the first and third xnode has CCNode1 whereas its missing in the second xnode. So copy the first and 3rd node1 as it is and create the CCNode1 and its child elements in the 2nd xnode with some dummy values.
Please suggest how to achieve this with XSLT.
THanks
So, the best I can tell is that you need an identity template to copy everything. And, then you need a template like the following to select Node3/CNode1 nodes that do not have a CCNode1 node. There you can add your nodes.
<xsl:template match="Node3/CNode1[not(.//CCNode1)]">
<xsl:copy>
Add your ccNode1 and child nodes here.
<!-- Output other child nodes of CNode1 -->
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- Identity. -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>

How to access node attribute through call-template

When I execute a call template, how do I access the attribute of the b node? There are a lot of possible attributes I have to be able to access, is the only way to pass it as a parameter?
XML:
<a>
<b id="anID"></b>
</a>
XSLT:
<xsl:template match="b">
<xsl:call-tempalte name="renderB"/>
</xsl:template>
<xsl:template name="renderB">
<!-- Based on id of b, do something -->
</xsl:template>
xsl:call-template does not change the context. If you called the template from the context of <xsl:template match="b">, then you are still in the context of b and the attributes of b are accessible simply as #id.

XSLT - Remove full parent node if child contains specific text

I just need to transform one XML file to another XML file with the wanted data.
The transformation remove every parent nodes if child nodes doesn't contain the correct category.
Input XML:
<SHOP>
<SHOPITEM>
<ITEM_ID>142</ITEM_ID>
<PRODUCT>Mora MV 1251</PRODUCT>
<DESCRIPTION>XXXXX</DESCRIPTION>
<URL>http://www.xxx.sk/o</URL>
<IMGURL>http://xxx.jpg</IMGURL>
<PRICE>6.74</PRICE>
<PRICE_VAT>8.10</PRICE_VAT>
<VAT>0.20</VAT>
<MANUFACTURER>Mora</MANUFACTURER>
<CATEGORYTEXT>Accessories / Mobile</CATEGORYTEXT>
<EAN>8590371028526</EAN>
<PRODUCTNO></PRODUCTNO>
<WARRANTY>24</WARRANTY>
<DELIVERY_DATE>24</DELIVERY_DATE>
</SHOPITEM>
<SHOPITEM>
<ITEM_ID>XXX</ITEM_ID>
<PRODUCT>Hyundai LLF 22924 DVDR</PRODUCT>
<DESCRIPTION>XXXXX</DESCRIPTION>
<URL>http://www.xxx.sk/t</URL>
<IMGURL>http://xxx.jpg</IMGURL>
<PRICE>173.35</PRICE>
<PRICE_VAT>208.00</PRICE_VAT>
<VAT>0.20</VAT>
<MANUFACTURER>Hyundai</MANUFACTURER>
<CATEGORYTEXT>Main category / TVs</CATEGORYTEXT>
<EAN>xxxxx</EAN>
<PRODUCTNO/>
<WARRANTY>24</WARRANTY>
<DELIVERY_DATE>99999</DELIVERY_DATE>
</SHOPITEM>
</SHOP>
I need to remove every SHOPITEM nodes where the CATEGORYTEXT is not Main category / TVs
I have the below XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="CATEGORYTEXT[not(text() = 'Main category / TVs')]"/>
</xsl:stylesheet>
But this only removes the CATEGORYTEXT node, not the full SHOPITEM node.
Can anyone help me please? :)
I'm new too XSLT, every help is appreciated.
Desired output is:
<SHOP>
<SHOPITEM>
<ITEM_ID>XXX</ITEM_ID>
<PRODUCT>Hyundai LLF 22924 DVDR</PRODUCT>
<DESCRIPTION>XXXXX</DESCRIPTION>
<URL>http://www.xxx.sk/t</URL>
<IMGURL>http://xxx.jpg</IMGURL>
<PRICE>173.35</PRICE>
<PRICE_VAT>208.00</PRICE_VAT>
<VAT>0.20</VAT>
<MANUFACTURER>Hyundai</MANUFACTURER>
<CATEGORYTEXT>Main category / TVs</CATEGORYTEXT>
<EAN>xxxxx</EAN>
<PRODUCTNO/>
<WARRANTY>24</WARRANTY>
<DELIVERY_DATE>99999</DELIVERY_DATE>
</SHOPITEM>
</SHOP>
Replace this
<xsl:template match="CATEGORYTEXT[not(text() = 'Main category / TVs')]"/>
with this:
<xsl:template match="SHOPITEM[not(CATEGORYTEXT = 'Main category / TVs')]"/>
Your current template filters CATEGORYTEXT elements based on the value of their text node children, whereas what you need is to filter SHOPITEM elements based on the value of their CATEGORYTEXT element children.
Note that it is only very rarely that you actually need to use text() in an XPath expression - text() gives you a set of all the individual text nodes that are direct children of the element you're dealing with, whereas usually what you care about is the complete string value of the element itself (which is, by definition, the concatenation of all its descendant text nodes)