Given the following fragment:
<recipe>
get a bowl
<ingredient>flour</ingredient>
<ingredient>milk</ingredient>
mix it all together!
</recipe>
How can I match "get a bowl" and "mix it all together!" and wrap them in another element to create the following?
<recipe>
<action>get a bowl</action>
<ingredient>flour</ingredient>
<ingredient>milk</ingredient>
<action>mix it all together!</action>
</recipe>
You could define a template matching text nodes that are a direct child of recipe:
<xsl:template match="recipe/text()">
<action><xsl:value-of select="normalize-space()" /></action>
</xsl:template>
Full XSLT example:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:strip-space elements="*" />
<xsl:output method="xml" indent="yes" />
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()" /></xsl:copy>
</xsl:template>
<xsl:template match="recipe/text()">
<action><xsl:value-of select="normalize-space()" /></action>
</xsl:template>
</xsl:stylesheet>
Note that the normalize-space() is required, even with the xsl:strip-space - that only affects text nodes that contain only whitespace, it doesn't strip off leading and trailing space from nodes that contain any non-whitespace characters. If you had
<action><xsl:value-of select="." /></action>
then the result would be something like
<recipe>
<action>
get a bowl
</action>
<ingredient>flour</ingredient>
<ingredient>milk</ingredient>
<action>
mix it all together!
</action>
</recipe>
Related
Using XSLT 2 how can I skip and not touch a record if a field contains text, in this case a date? I want to only process all the record that don't have a <SurveyDate> and don't touch record that already have a <SurveyDate>.
I tried using a choose statement with a test of "not(SurveyDate/text())" but this is not working. here is my complete XSL code:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:lookup="lookup" xmlns:exsl="http://exslt.org/common" exclude-result-prefixes="lookup exsl">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" encoding="utf-8" media-type="xml/plain" />
<xsl:strip-space elements="*" />
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>
<xsl:template match="Sub">
<!-- This is the final output -->
<xsl:choose>
<xsl:when test="not(SurveyDate/text())">
<xsl:if test= "count(Request/Phase/Status) = count(Request/Phase/Status[matches(. , 'Sup|Ser|Adm|Can')])">
<Request>
<xsl:copy-of select="Request/Code"/>
<SurveyDate>
<xsl:value-of select="format-dateTime(current-dateTime(), '[Y0001]-[M01]-[D01]T[H1]:[m01]:[s01]')"/>
</SurveyDate>
</Request>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<!-- just for testing remove when done -->
<Test>Do nothing</Test>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
And this is my test XML data.
<?xml version='1.0' encoding='UTF-8'?>
<document>
<businessobjects>
<Sub>
<Code>1.02</Code>
<Status>UsrWorkOrderCancelled</Status>
<Request>
<Code>1.00</Code>
<Description>Test 1</Description>
<SurveyDate>2022-11-02T22:55:55</SurveyDate>
<Phase>
<Code>1.01</Code>
<Status>UsrWorkOrderSupervisorApproved</Status>
</Phase>
<Phase>
<Code>1.02</Code>
<Status>UsrWorkOrderCancelled</Status>
</Phase>
</Request>
</Sub>
<Sub>
<Code>2.01</Code>
<Status>UsrWorkOrderSupervisorApproved</Status>
<Request>
<Code>2.00</Code>
<Description>Test 2</Description>
<SurveyDate></SurveyDate>
<Phase>
<Code>2.01</Code>
<Status>UsrWorkOrderSupervisorApproved</Status>
</Phase>
<Phase>
<Code>2.02</Code>
<Status>UsrWorkOrderCancelled</Status>
</Phase>
</Request>
</Sub>
</businessobjects>
</document>
The result XML I need is this:
<document>
<businessobjects>
<Request>
<Code>2.00</Code>
<SurveyDate>2022-11-03T21:45:13</SurveyDate>
</Request>
</businessobjects>
</document>
My advice: forget using xsl:choose or xsl:if, and instead put the conditional logic into the template's match expression:
<xsl:template match="Sub[not(Request/SurveyDate/text())]">
<!-- handle Sub without SurveyDate -->
<!-- ... -->
</xsl:template>
Leave the case where a Sub does have a SurveyDate for the identity template to handle, if you want to copy it unchanged. If you want to remove it (it's not clear from your test code what you want to do with it), you could add another template to do so:
<xsl:template match="Sub"/>
Note that template would have a lower priority than the one above, because its match expression is simpler, so it would apply only to Sub elements which did have a SurveyDate descendant.
I want to check to contain only characters + space and <p> nodes inside <used>.
Input:
<root>
<used><p>String 1</p></used>
<used>string 2<p>string 3</p></used>
<used>string 4</used>
<used><image>aaa.jpg</image>para</used>
The output should be:
<ans>
<abc>string 1</abc>
<abc>string 4</abc>
</ans>
Tried code:
<ans>
<abc>
<xsl:template match="root">
<xsl:choose>
<xsl: when test="getCode/matches(text(),'^[a-zA-Z0-9]+$')">
<xsl:text>text()</xsl:text>
</xsl:when>
</xsl:choose>
</xsl:template>
</abc>
</ans>
My tried code is not working as I am expecting. How can I fix this? Thank you. I am using XSLT 2.0
You can use the following XSLT-2.0 stylesheet to get the desired result:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl= "http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<!-- Handle the <root> element -->
<xsl:template match="/root">
<ans>
<xsl:apply-templates select="used" />
</ans>
</xsl:template>
<!-- Create <abc> elements for every matching element -->
<xsl:template match="used[not(*) and matches(text(),'^[\sa-zA-Z0-9]+$')] | used[not(text()) and matches(p/text(),'^[\sa-zA-Z0-9]+$')]/p">
<abc><xsl:copy-of select="text()" /></abc>
</xsl:template>
<!-- Remove all spurious text nodes -->
<xsl:template match="text()" />
</xsl:stylesheet>
Its result is
<?xml version="1.0" encoding="UTF-8"?>
<ans>
<abc>String 1</abc>
<abc>string 4</abc>
</ans>
I have an XML and I am looking for finding particular tag (in this case "FirstName") and removing space in the value only if there is a - character before the space.
In other words, I want to keep spaces if there is no - front of them. I want to do this using an XSL stylesheet with RegEx matching and replace function.
Expected result is Sam-Louise, removing space between "Sam-" and "Louise"
<?xml version="1.0" encoding="utf-8"?>
<NCV Version="1.14">
<Invoice>
<customer>
<customerId>12785</customerId>
<FirstName>Sam- Louise</FirstName>
<LastName>Jones</LastName>
</customer>
</Invoice>
</NCV>
This is one possible XSLT :
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="html" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="FirstName">
<FirstName>
<xsl:value-of select="replace(., '-\s+', '-')"/>
</FirstName>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:transform>
xsltransform.net demo
output :
<NCV Version="1.14">
<Invoice>
<customer>
<customerId>12785</customerId>
<FirstName>Sam-Louise</FirstName>
<LastName>Jones</LastName>
</customer>
</Invoice>
</NCV>
You can use following RegEx in match
(\<FirstName\>.*?-)\s+
And replace it with the first captured group $1
RegEx (\<FirstName\>.*?-)\s+ matches,
\<FirstName\>.*?-: Literal <FirstName> followed by any character non-greedy, until first hyphen is found. This match is added in the captured group.
\s+: Match one or more of the space characters.
By replacing it with $1, will remove the spaces after hyphen.
Considering this XML,
XML:
<?xml version="1.0" encoding="UTF-8"?>
<items>
<book>
<title>doublebell</title>
<count>available</count>
</book>
<phone>
<brand>nokia</brand>
<model></model>
</phone>
</items>
Mapping Criteria while writing XSLT:
show the newbook/newtitle only if a value is present in input.
show the newbook/newcount only if a value is present in input.
show the newphone/newbrand only if a value is present in input.
show the newphone/newmodel only if a value is present in input.
XSLT:
<?xml version="1.0" encoding="ISO-8859-1"?>
<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:variable name="book" select="items/book" />
<xsl:variable name="phone" select="items/phone" />
<xsl:template match="/">
<items>
<newbook>
<xsl:if test="$book/title!=''">
<newtitle>
<xsl:value-of select="$book/title" />
</newtitle>
</xsl:if>
<xsl:if test="$book/count!=''">
<newcount>
<xsl:value-of select="$book/count" />
</newcount>
</xsl:if>
</newbook>
<xsl:if test="$phone/brand!='' or $phone/model!=''"> <!-- not sure if this condition is required for the above mapping criteria -->
<newphone>
<xsl:if test="$phone/brand!=''">
<newbrand>
<xsl:value-of select="$phone/brand" />
</newbrand>
</xsl:if>
<xsl:if test="$phone/model!=''">
<newmodel>
<xsl:value-of select="$phone/model" />
</newmodel>
</xsl:if>
</newphone>
</xsl:if>
</items>
</xsl:template>
</xsl:stylesheet>
This is my concern:- In my actual XSLT, I have almost 70 conditions like
this, and everytime the XPath search is made twice [or thrice.. ] for
each condition [ for eg: <xsl:if test="$phone/brand!=''"> and <xsl:value-of select="$phone/brand" /> and outer if condition].
Is this much performance overhead? I don't feel it when I ran my application.
I like to hear from experienced people if this is correct way of writing the XSLT. Do I need to save the path in a variable and reuse it as done for $book
and $phone ? In such a case there will be 70+variables just to hold this.
You can approach this quite differently using templates. If you define a template that matches any element whose content is empty and does nothing:
<xsl:template match="*[. = '']" />
or possibly use normalize-space() if you want to consider elements to be empty if they contain only whitespace
<xsl:template match="*[not(normalize-space())]" />
Now with this in place add templates for the elements you are interested in
<xsl:template match="book">
<newbook><xsl:apply-templates /></newbook>
</xsl:template>
<xsl:template match="title">
<newtitle><xsl:apply-templates /></newtitle>
</xsl:template>
and so on. Now the book template will create a newbook element and go on to process its children. When it gets to the title it will have two different templates to choose from and will pick the "most specific" match. If the title is empty then the *[. = ''] template will win and nothing will be output, only if the title is non-empty will it create a newtitle element.
This way you let the template matcher do most of the work for you, you don't need any explicit conditional checks using xsl:if.
<?xml version="1.0" encoding="ISO-8859-1"?>
<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:template match="/">
<items><xsl:apply-templates select="items/*" /></items>
</xsl:template>
<!-- ignore empty elements -->
<xsl:template match="*[not(normalize-space())]" />
<xsl:template match="book">
<newbook><xsl:apply-templates /></newbook>
</xsl:template>
<xsl:template match="title">
<newtitle><xsl:apply-templates /></newtitle>
</xsl:template>
<!-- and so on with similar templates for the other elements -->
</xsl:stylesheet>
Building on Ian's answer, you can also make a generic template that will create the "new" elements for you without having to specify each one individually. That would look like the below:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<items><xsl:apply-templates select="items/*" /></items>
</xsl:template>
<xsl:template match="*[not(normalize-space())]" />
<xsl:template match="*">
<xsl:element name="{concat('new',name())}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
That last template just rebuilds the element by concatenating the word "new" to the front of it.
I've searched through similar questions, but couldn't make any of the suggestions to work. I have the following xml I need to modify it
<XDB>
<ROOT>
<KEY><ID>12345</ID><DATE>5/10/2011</DATE></KEY>
<PERSONAL><ID>1</ID><INFO><LASTNAME>Smith</LASTNAME>...</INFO></PERSONAL>
<CONTACT><ID>1</ID><EMAIL>asmith#yahoo.com</EMAIL>...</CONTACT>
</ROOT>
<ROOT>
<KEY><ID>98765</ID><DATE>5/10/2013</DATE></KEY>
<CONTACT><ID>2</ID><EMAIL>psmithton#yahoo.com</EMAIL>...</CONTACT>
</ROOT>
...
</XDB>
And it needs to look like this:
<XDB>
<ROOT>
<KEY><ID>12345</ID><DATE>5/10/2011</DATE>
<PERSONAL><ID>1</ID><INFO><LASTNAME>Smith</LASTNAME>...</INFO></PERSONAL>
<CONTACT><ID>1</ID><EMAIL>asmith#yahoo.com</EMAIL>...</CONTACT>
</KEY>
</ROOT>
<ROOT>
<KEY><ID>98765</ID><DATE>5/10/2013</DATE>
<CONTACT><ID>2</ID><EMAIL>psmithton#yahoo.com</EMAIL>...</CONTACT>
</KEY>
</ROOT>
...
</XDB>
I need to make 2...n siblings as children of the first 'key' sibling. Essentially, i need to remove the closing < /KEY> and put it before the closing < /ROOT>. I would appreciate your help.
Thanks.
Following xslt based on Identity transform could make this job
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<!-- Copy everything you find... -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<!-- ... but if you find first element inside ROOT ... -->
<xsl:template match="ROOT/node()[1]">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
<!-- ... copy its sibling into it ... -->
<xsl:copy-of select="following-sibling::*" />
</xsl:copy>
</xsl:template>
<!-- ignore other elements inside ROOT element since they are copied in template matching first element -->
<xsl:template match="ROOT/node()[position() > 1]" />
</xsl:stylesheet>