How to check contain only characters + space and `p` using regex - xslt

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>

Related

How to not touch a record if text exist in an XML field but process the record in no text exist using XSLT 2

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.

Select preceding elements until I find an element to stop on (XSLT 1.0)

I want to select the a elements that precede bird until I hit dog, but not select the bird or dog. And, I don't know what the elements are. They could be different than in the sample XML. And, I would like to do it in the select of a variable.
Input XML:
<?xml version="1.0" encoding="utf-8"?>
<root>
<a>cat</a>
<a>dog</a>
<a>dog</a>
<a>cat</a>
<a>snake</a>
<a>cat</a>
<a>cat</a>
<a>bird</a>
<a>dog</a>
<a>cat</a>
</root>
Desired Output XML:
<?xml version="1.0" encoding="utf-8"?>
<root>
<a>cat</a>
<a>snake</a>
<a>cat</a>
<a>cat</a>
</root>
XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="nodes">
<xsl:copy-of select="//a"/>
</xsl:variable>
<xsl:variable name="nodeList" select="msxsl:node-set($nodes)"/>
<!-- I want to select the a elements that precede bird until I hit dog, but not select the bird or dog.
And, I don't know what the elements are. They could be different than in the sample XML.
And, I want to do it in the select of the variable below.
-->
<xsl:variable name="subsetOfNodeList" select="$nodeList/a[.='bird']/preceding-sibling::a[. >> $nodeList/a[.='bird']/preceding-sibling::a[.='dog'][1]]"/>
<xsl:template match="root">
<xsl:copy>
<xsl:copy-of select="$subsetOfNodeList"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Given XSLT 1 I think you can select the preceding-siblings of the bird and intersect (remember intersection of node-sets n1 and n2 is done with $n1[count(. | $n2) = count($n2)]) them with anything following the dog, here is an example that additionally uses a key to identify what follows the dog:
<xsl:key name="fol" match="a[not(. = 'dog')]" use="generate-id(preceding-sibling::a[. = 'dog'][1])"/>
<xsl:template match="root">
<xsl:variable name="bird" select="a[. = 'bird']"/>
<xsl:variable name="prec-dog" select="$bird/preceding-sibling::a[. = 'dog'][1]"/>
<xsl:variable name="fol-dog" select="key('fol', generate-id($prec-dog))"/>
<xsl:variable name="prec-siblings" select="$bird/preceding-sibling::a[not(. = 'dog')]"/>
<xsl:variable name="intersect" select="$prec-siblings[count((. | $fol-dog)) = count($fol-dog)]"/>
<xsl:copy>
<xsl:copy-of select="$intersect"/>
</xsl:copy>
</xsl:template>
At https://xsltfiddle.liberty-development.net/eiQZDbr/2 for the input
<root>
<a id="c1">cat</a>
<a id="d1">dog</a>
<a id="d2">dog</a>
<a id="c3">cat</a>
<a id="s1">snake</a>
<a id="c4">cat</a>
<a id="c5">cat</a>
<a>bird</a>
<a>dog</a>
<a>cat</a>
</root>
I get the result
<root>
<a id="c3">cat</a>
<a id="s1">snake</a>
<a id="c4">cat</a>
<a id="c5">cat</a>
</root>
One of possible solutions is to make the "initial" call of a dedicated
template (let's call it print), passing it the last element to be
printed, i.e. the first preceding sibling of bird element (I assume
such a bird element is only one).
This template:
Checks whether the argument passed has content other than
dog (actually a condition to do anything). If this is the case then:
Make a recursive call to itself, passing it the first preceding
sibling of the argument element.
Print the element passed as the argument.
So the whole script can look like below:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template name="print">
<xsl:param name="xx"/>
<xsl:if test="$xx/text() != 'dog'">
<xsl:call-template name="print">
<xsl:with-param name="xx" select="$xx/preceding-sibling::*[1]"/>
</xsl:call-template>
<xsl:copy-of select="$xx"/>
</xsl:if>
</xsl:template>
<xsl:template match="root">
<xsl:copy>
<xsl:call-template name="print">
<xsl:with-param name="xx" select="a[text() = 'bird']/preceding-sibling::*[1]"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
</xsl:transform>
For a working example see http://xsltfiddle.liberty-development.net/gWcDMet

How do I filter my XML list to not get duplicate letters returned case insensitively

I've got some XML that has an Alphabetically ordered list of strings. I want to generate a set of Divs containing the first letter of each set with no duplicate first letters.
XML
<?xml version="1.0" encoding="UTF-8"?>
<textstring>
<example>Delta</example>
<example>delta2</example>
<example>harmony</example>
<example>incognito</example>
<example>Inconvenient</example>
</textstring>
XSL
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="textstring">
<xsl:apply-templates mode="trick" select="/textstring/example"/>
</xsl:template>
<xsl:template match="/textstring/example" mode="trick">
<xsl:variable name="firstLetter" select="substring(text(),1,1)"/>
<div>
<xsl:copy-of select="$firstLetter"/>
</div>
</xsl:template>
</xsl:stylesheet>
Output
D
d
h
i
I
Desired Output
D
H
I
I was thinking of something that would add to the select such as
<xsl:template match="textstring">
<xsl:apply-templates mode="trick" select="/textstring/example[not(starts-with(
substring(
translate(text(),
'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') ,1,1),
substring(following-sibling::example[
translate(text(),
'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') ],1,1)
))]"/>
</xsl:template>
But that results in
D
d
h
Am I completely off base? I didn't really want to switch to a for-each loop since it is somewhat resource intensive, but is checking preceding sibling really any less so?
As told in the comment you should use the Muenchian Method. See the next XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8" indent="yes"/>
<xsl:key name="first-letters" match="example" use="substring(translate(., 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 1, 1)" />
<xsl:template match="#*|node()">
<xsl:apply-templates select="#*|node()" />
</xsl:template>
<xsl:template match="textstring">
<xsl:apply-templates select="example[generate-id() = generate-id(key('first-letters', substring(translate(., 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 1, 1))[1])]" />
</xsl:template>
<xsl:template match="example">
<div>
<xsl:value-of select="substring(translate(., 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 1, 1)" />
</div>
</xsl:template>
</xsl:stylesheet>
This will generate the required output:
<div>D</div>
<div>H</div>
<div>I</div>

XSLT correct approach in writing XPath expression used in multiple condition checking and displaying value

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.

xslt: move all siblings inside the first one

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>