Is there a way to conditionally sort in xslt? - xpath

I need to sort some content but only when an attribute is equal to CAT. I think I should be able to pass a property from my ant build file to the use-when attribute but it is not working. Any help would be appreciated
Here is the xslt that I have:
<xsl:for-each select="document(#fileRef)/foo/bar">
<xsl:sort select="translate(child::Title/text(), '>', '')" order="ascending" use-when="system-property('customerCode')='CAT'"
collation="http://www.w3.org/2005/xpath-functions/collation/html-ascii-case-insensitive"/>
<!-- do some stuff here -->
</xsl:for-each>

Using oXygen I got the following to work in an Ant file:
<xslt in="sample1.xml" out="sample1-transformed.xml" force="true" style="sheet1.xsl">
<factory name="net.sf.saxon.TransformerFactoryImpl"/>
<sysproperty key="cat" value="bar"/>
</xslt>
XML sample1.xml is e.g.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>c</item>
<item>a</item>
</root>
XSLT is
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:comment select="system-property('cat')"/>
<xsl:next-match/>
</xsl:template>
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="item">
<xsl:sort select="." use-when="system-property('cat') = 'foo'"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
and then the output items are sorted only if the Ant sets e.g. <sysproperty key="cat" value="foo"/>.

Related

XSLT Sort on child subelements

I am trying to output this entire XML but with the EVENT elements sorted by ID. Being new to XSLT I thought I would still give it a try but after many attempts and reading other examples and how to guides I still can't get what I thought was a simple thing to work.
<?xml version="1.0" encoding="UTF-8"?>
<PublishWCWORKORDEROUT xmlns="http://www.xcessteel.com/maxo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" creationDateTime="2021-05-10T08:23:18+00:00" transLanguage="EN" baseLanguage="EN" messageID="3116171.1620634998889850919" maxoVersion="7 6 20190514-1348 V7611-365" event="1">
<WCWORKORDEROUTSet>
<WORKORDER action="Replace">
<ACTCATEGORY />
<X_3857>1.1838832494481975E7</X_3857>
<Y_3857>-2766476.1752903816</Y_3857>
<SPEC>
<ALNVALUE />
<REFID xsi:nil="true" />
<ASSETATTRID>ACCOUNT_NO</ASSETATTRID>
<CHANGEBY>ADMIN</CHANGEBY>
</SPEC>
<SPEC>
<ALNVALUE />
<REFID xsi:nil="true" />
</SPEC>
<SPEC>
<ALNVALUE />
<REFID xsi:nil="true" />
<ASSETATTRID>METER_LOCATION</ASSETATTRID>
</SPEC>
<EVENT>
<ID>CCC333</ID>
<WORKTYPE>UNPLANNED</WORKTYPE>
</EVENT>
<EVENT>
<ID>AAA111</ID>
<WORKTYPE>PLANNED</WORKTYPE>
</EVENT>
<EVENT>
<ID>BBB222</ID>
<WORKTYPE>SCHEDULED</WORKTYPE>
</EVENT>
<ASSIGNMENT>
<AMCREW />
<WPLABORID>209336</WPLABORID>
</ASSIGNMENT>
<WCWODETAILS>
<REFID xsi:nil="true" />
<CUSTOMERNAME />
<WCREGION>SWR</WCREGION>
<ID>96057400</ID>
</WCWODETAILS>
</WORKORDER>
</WCWORKORDEROUTSet>
</PublishWCWORKORDEROUT>
I have tried with this XSLT but clearly it is not correct.
<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"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/PublishWCWORKORDEROUT">
<xsl:copy>
<xsl:apply-templates select="EVENT">
<xsl:sort select="ID"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You have to declare the namespace xmlns="http://www.xcessteel.com/maxo" and then use that prefix in your match and the sorting could be done like this:
EDIT on 2021-05-24 on 10:19: attribute action="Replace" was missing
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:maxo="http://www.xcessteel.com/maxo"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="maxo:WORKORDER">
<xsl:copy>
<!-- Following line was missing -->
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="maxo:EVENT[1]/preceding-sibling::*"/>
<xsl:apply-templates select="maxo:EVENT">
<xsl:sort select="maxo:ID"/>
</xsl:apply-templates>
<xsl:apply-templates select="maxo:EVENT[ position()=last()]/following-sibling::*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
And as an alternative, the following would work as well:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:maxo="http://www.xcessteel.com/maxo"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="maxo:EVENT[not(preceding-sibling:: maxo:EVENT)]">
<xsl:for-each select=".|following-sibling:: maxo:EVENT">
<xsl:sort select="maxo:ID"/>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:for-each>
</xsl:template>
<xsl:template match="maxo:EVENT[ preceding-sibling:: maxo:EVENT]"/>
</xsl:stylesheet>

XSLT 1.0 to modify config file replace function

I am trying to write an XSLT to modify a config file. I have tried to use the replace function but that is only supported in 2.0 and i have tried to use translate but "true" trnaslated to "false" gets truncated to 'fals'. I cant just replace the whole modules section since our customers are in a distributed environment and I don't know if they have added any thing else to the section.
What I am starting with:
<modules runAllManagedModulesForAllRequests="true">
<remove name="FormsAuthentication" />
</modules>
Desired output:
<modules runAllManagedModulesForAllRequests="false">
<remove name="FormsAuthentication" />
</modules>
This is what I thought would do the trick.
<xsl:template match="/configuration/system.webServer/modules">
<xsl:choose>
<xsl:when test="#name=runAllManagedModulesForAllRequests">
<xsl:copy>
<xsl:copy-of select="<modules runAllManagedModulesForAllRequests="false">"/>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:copy-of select="#*"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Would this work for you?
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="#runAllManagedModulesForAllRequests[.='true']">
<xsl:attribute name="runAllManagedModulesForAllRequests">false</xsl:attribute>
</xsl:template>
</xsl:stylesheet>

XPath Select Parent and Multiple Children

In Xpath how would I get my results to select all documents and display them like
<Document>
<Size>100</Size>
<Offset>200</Offset>
<FileName>x.doc</FileName>
</Document>
from
<Documents>
<Document>
<Size>100</Size>
<Offset>200</Offset>
<FileName>x.doc</FileName>
<Details>
<Header>asdfasdf</Header>
<Footer>adfasdfas</Footer>
<Author>asdfasdfs</Author>
<Date>20140101</Date>
</Details>
<Document>
</Documents>
Thanks.
With XSLT you get the result with
<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="//Documents//*">
<xsl:if test="local-name(parent::*)!='Details' and local-name(.) != 'Details'">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
So you would get a copy of all Documents excluding the Details.

Xpath Query Help. Selecting data from multiple paths as a single data set

I have an xml structure like the following :
<doc>
<line void="false">
<lineNumber>1</lineNumber>
<info1>ddddd</info1>
<info2>aaaaa</info2>
</line>
<line void="true">
<lineNumber>2</lineNumber>
<voidLineNumber>1</voidLineNumber>
<voidValue>2.00</voidValue>
</line>
</doc>
I need one single set of data. I would like to select all the lines where void = false as well as the voidLineNumber and voidValue data from the line where void = true and the voidLineNumber = lineNumber from the original line.
Is this possible? Any help would be appreciated. Thanks
As Michael Kay noted, XPath itself can only be used to select nodes, not to transform them. You can do what you want with XSLT:
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8" indent="yes" />
<xsl:template match="doc">
<xsl:apply-templates select="line" />
</xsl:template>
<xsl:template match="line">
<xsl:variable name="voidvalue"><xsl:value-of select="#void" /></xsl:variable>
<xsl:if test="$voidvalue='false'">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:apply-templates />
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:if test="name(.)='lineNumber'">
<xsl:value-of select="."/>
</xsl:if>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

Spring Integration xslt-transformer doesn't work with namespace

I use Spring Integration:
<int-xml:xslt-transformer input-channel="partnerTransformation"
output-channel="serviceChannel"
xsl-resource="classpath:/META-INF/vendorTransformerXml.xsl"/>
The input XML message:
<ns1:persons xmlns:ns1="http://com.test.xslt/test" xmlns:ns3="http://universal.consumerrequest.schema.model.tci.ca">
<ns3:person>
<ns3:name>John</ns3:name>
<ns3:family>Smith</ns3:family>
</ns3:person>
<ns3:person>
<ns3:name>Raza</ns3:name>
<ns3:family>Abbas</ns3:family>
</ns3:person>
and the XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="http://com.test.xslt/test" xmlns:ns3="http://universal.consumerrequest.schema.model.tci.ca">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:for-each select="ns1:persons/ns3:person">
<xsl:value-of select="ns3:family"/>
<xsl:value-of select="ns3:name"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
it doesn't work, but it works well without namesapce, appreciate if you can help.
The problem is the way Java XML parsers handle namespaces by default. There's a few things you can do:
Convert the input XML to a String before feeding it to the XSLT transformer. This works fine in my testing
If you are building the document yourself, e.g.
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(...);
If you don't have to worry about namespaces, probably the easiest thing is to change your XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns1="http://com.test.xslt/test"
xmlns:ns3="http://universal.consumerrequest.schema.model.tci.ca">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:for-each select="//*[local-name()='person']">
<xsl:value-of select="*[local-name()='family']" />
<xsl:value-of select="*[local-name()='name']" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Resources