XSLT 2.0: regex extract and modify element value - regex

I have this XML:
<xml>
<row>
<image><![CDATA[javascript: open_window_zoom('http://example.com/image.php?image=/images/test/example.png&pID=46391&download=noid_90.png&name=Test name', 975, 366);]]></image>
<quantity>0</quantity>
</row>
<row>
<image><![CDATA[javascript: open_window_zoom('http://example.com/image.php?image=/images/test/another.png&pID=06395&download=anotherfile.png&name=Test name', 975, 366);]]></image>
<quantity>0</quantity>
</row>
</xml>
It is possible to extract pID=NUMBERHERE&download=FILENAMEHERE.png (and add new url before it)from <image> element?
The output should be like this:
<xml>
<row>
<image>http://newurl.com/pID=46391&download=noid_90.png</image>
<quantity>0</quantity>
</row>
<row>
<image>http://newurl.com/pID=06395&download=anotherfile.png</image>
<quantity>0</quantity>
</row>
</xml>
I tried some things, but I can't get the desidered result. For start I can copy the current structure:
<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:stylesheet>

This XSLT 2.0 stylesheet splits the value by & and then uses a predicate to filter only those that matches() the supplied regex where the parameter names are either pID or download. Using the #separator for xsl:value-of to join the values with &:
<xsl:stylesheet version="2.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="image">
<xsl:copy>
<xsl:text>http://newurl.com/</xsl:text>
<xsl:value-of select="tokenize(., '&')[matches(., '(pID|download).*')]"
separator="&"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Alternatively, this XSLT 2.0 stylesheet uses xsl:analyze-string to extract the text that matches the provided regex:
<xsl:stylesheet version="2.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="image">
<xsl:copy>
<xsl:text>http://newurl.com/</xsl:text>
<xsl:analyze-string select="." regex=".*(pID=.*&download=.*)&.*">
<xsl:matching-substring>
<xsl:value-of select="regex-group(1)"/>
</xsl:matching-substring>
<xsl:non-matching-substring></xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Try this XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="image">
<xsl:copy>
<xsl:value-of select="concat('http://newurl.com/pID=', substring-before(substring-after(text(), '&pID='), '&name='))"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Related

XSLT create new tag if exists else update tag if exists

Is there a way for me to update the label element to Selma if "LastName" exists and if the label LastName doesn't exist then add the "LastName" and "label" elements to the XML?
<xml>
<udfs>
<udf>
<desc>FirstName</desc>
<label>Sam</label>
</udf>
<udf>
<desc>LastName</desc>
<label>Selman</label>
</udf>
</udfs>
</xml>
Here's what I have right now:
<xsl:stylesheet>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="udf[desc='LastName']/fieldValue">
<xsl:value-of select="'Selma'"/>
</xsl:template>
<xsl:template match="udf[not(desc='LastName')]">
<desc>LastName</desc>
<label>Selma</label>
</xsl:template>
</xsl:stylesheet>
I think you want to do:
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="udf[desc='LastName']/label">
<label>Selma</label>
</xsl:template>
<xsl:template match="udfs[not(udf/desc='LastName')]">
<xsl:copy>
<xsl:apply-templates/>
<udf>
<desc>LastName</desc>
<label>Selma</label>
</udf>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

XSLT 1.0 - Move certain elements into a new parent

Thank you for taking the time to look at my question
I have a source xml that looks like so
<root>
<a>asdf</a>
<b>asdfdsaf</b>
<c>adfasd</c>
<d>asddf</d>
<e></e>
<others>
<foo>sdfd</foo>
<bar>hghg</bar>
</others>
</root>
I need to wrap certain elements Eg:a,b,c into a new parent and remove empty elements.
so output like so
<root>
<newparent>
<a>asdf</a>
<b>asdfdsaf</b>
<c>adfasd</c>
</newparent>
<d>asddf</d>
<others>
<foo>sdfd</foo>
<bar>hghg</bar>
</others>
</root>
I managed to remove the empty elements using this XSL -
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(#*) and not(*) and (not(text()) or .=-1)]"/>
<xsl:template match="name">
</xsl:template>
</xsl:stylesheet>
Getting stuck at adding the newparent node. Any pointers/help is much appreciated.
Try it this way:
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="/root">
<xsl:copy>
<newparent>
<xsl:apply-templates select="a|b|c"/>
</newparent>
<xsl:apply-templates select="*[not(self::a or self::b or self::c)]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(#* or node())]"/>
</xsl:stylesheet>
<xsl:template match="root">
<xsl:copy>
<newparent>
<xsl:copy-of select="a|b|c"/>
</newparent>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[.= '' or name() ='a' or name()='b' or name()='c']"/>
check it if it is fullfill your requirement.

Filter out elements and replace element value in one step

I am trying to filter out elements, and rename element value, but I can't get it to work:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" indent="yes" method="xml"/>
<xsl:template match="xml">
<xsl:copy>
<xsl:for-each select="product[matches(code, 'C17.*[^V]$')]">
<xsl:copy>
<xsl:copy-of select="#*|node()"/>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="title">
<xsl:copy>
<xsl:value-of select="replace(.,'Apple','Carrot')"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Example input data:
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<product>
<code>C17020</code>
<title>Apple</title>
</product>
<product>
<code>C1723V</code>
<title>Samsung</title>
</product>
</xml>
I want to leave <product>'s starting with C17, but not ending to V. I use C17.*[^V]$ regex for this. This part is working.
The problem is with renaming title function. If I add this step to a new XSLT with code:
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
at the begin, then it works.
What I am doing wrong here?
The problem is you are doing <xsl:copy-of select="#*|node()"/> in your template matching xml. This will copy the attributes and child nodes, buy will not apply any templates. So your template matching title is just not used.
You need to use xsl:apply-templates here, but also include the identity template (the template you mention using in your new XSLT code) which ensures code gets copied too
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" indent="yes" method="xml"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xml">
<xsl:copy>
<xsl:for-each select="product[matches(code, 'C17.*[^V]$')]">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="title">
<xsl:copy>
<xsl:value-of select="replace(.,'Apple','Carrot')"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note you can actually simplify your XSLT. Rather than being explicit in what you want to copy, by using the identity template you can instead have templates to remove what you don't want to copy....
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" indent="yes" method="xml"/>
<xsl:strip-space elements="*" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="product[not(matches(code, 'C17.*[^V]$'))]" />
<xsl:template match="title">
<xsl:copy>
<xsl:value-of select="replace(.,'Apple','Carrot')"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Another thing to note is that matches and replace is for XSLT 2.0 only.

how to skip elements in xslt based on position()

Here is my xslt:
This one works, but only if hardcode '1,2,'
<xsl:template match="row[contains('1,2,',concat(position(),','))]"
working xslt:
<xsl:stylesheet version="1.0" exclude-result-prefixes="msxsl" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="positions"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row[contains('1,2,',concat(position(),','))]" name="skiprow"/>
</xsl:stylesheet>
But I want to pass the position values as parameter. But it doesn't work.
I checked the value of the parameter by adding a line, the parameter is good.
<xsl:value-of select="$positions"/>
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
Non-working xslt:
<xsl:stylesheet version="1.0" exclude-result-prefixes="msxsl" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="positions"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row[contains('$positions',concat(position(),','))]" name="skiprow"/>
</xsl:stylesheet>
Sample Xml:
<root>
<row>
<column1>7004275</column1>
<column2>NUVCFDK</column2>
</row>
<row>
<column1>1001459</column1>
<column2>CAN</column2>
<column3>12</column3>
<column4>646.80</column4>
<column5>23-06-2009</column5>
<column6>31-12-2009</column6>
<column7/>
</row>
<row>
<column1>1001461</column1>
<column2>CAN</column2>
<column3>1</column3>
<column4>9.50</column4>
<column5>23-06-2009</column5>
<column6>31-12-2009</column6>
<column7/>
</row>
</root>
Non-working xslt:
<xsl:template match="row[contains('$positions',concat(position(),','))]" name="skiprow"/>
In XSLT 1.0 a match pattern is forbidden to contain a variable/parameter reference.
Use instead:
<xsl:template match="row>
<xsl:if test=
"not(contains($vPositions, concat(position(),',')))">
<xsl:call-template name="identity"/>
<xsl:if>
</xsl:template>
The complete transformation becomes:
<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:param name="vPositions" select="'1,2,'"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row">
<xsl:if test=
"not(contains($vPositions, concat(position(),',')))">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
and when this transformation is applied on the provided XML document:
<root>
<row>
<column1>7004275</column1>
<column2>NUVCFDK</column2>
</row>
<row>
<column1>1001459</column1>
<column2>CAN</column2>
<column3>12</column3>
<column4>646.80</column4>
<column5>23-06-2009</column5>
<column6>31-12-2009</column6>
<column7/>
</row>
<row>
<column1>1001461</column1>
<column2>CAN</column2>
<column3>1</column3>
<column4>9.50</column4>
<column5>23-06-2009</column5>
<column6>31-12-2009</column6>
<column7/>
</row>
</root>
the wanted, correct result is produced (rows 1 and 2 "deleted"):
<root>
<row>
<column1>1001461</column1>
<column2>CAN</column2>
<column3>1</column3>
<column4>9.50</column4>
<column5>23-06-2009</column5>
<column6>31-12-2009</column6>
<column7/>
</row>
</root>
Do note, however, that your conditions aren't strong enough -- a parameter value of "11,13," will delete four rows -- 1, 11, 3 and 13.
A good condition to use is:
not(contains($vPositions, concat(',',position(),',')))
That means that the parameter must start and end with the comma character.
Here is the complete, corrected transformation:
<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:param name="vPositions" select="',1,2,'"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row">
<xsl:if test=
"not(contains($vPositions, concat(',',position(),',')))">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

Remove elements based on other element's value -- XSLT

I have a style-sheet that I am using to remove certain elements based on the value of an other element. However, it is not working ...
Sample Input XML
<Model>
<Year>1999</Year>
<Operation>ABC</Operation>
<Text>Testing</Text>
<Status>Ok</Status>
</Model>
If Operation value is 'ABC' then remove Text and Status nodes from XML.
And gives the following output.
<Model>
<Year>1999</Year>
<Operation>ABC</Operation>
</Model>
Here is my style sheet that I am using but it is removing Text and Status nodes from all XMLs even when operation is not 'ABC'.
<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:variable name="ID" select="//Operation"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Text | Status">
<xsl:if test ="$ID ='ABC'">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Thanks in Advance
How would I do the same when namespace is present like
<ns0:next type="Sale" xmlns:ns0="http://Test.Schemas.Inside_Sales">
Here is a complete XSLT transformation -- short and simple (no variables, no xsl:if, xsl:choose, xsl:when, xsl:otherwise):
<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=
"*[Operation='ABC']/Text | *[Operation='ABC']/Status"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<Model>
<Year>1999</Year>
<Operation>ABC</Operation>
<Text>Testing</Text>
<Status>Ok</Status>
</Model>
the wanted, correct result is produced:
<Model>
<Year>1999</Year>
<Operation>ABC</Operation>
</Model>
Change your xsl:if as follows:
<xsl:if test="../Operation!='ABC'">
and you can get rid of xsl:variable.
A better pattern in XSLT than using <xsl:if> is to add new templates with match conditions:
<xsl:template match="(Text | Status)[../Operation != 'ABC']"/>
I found this works:
<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="/Model">
<xsl:choose>
<xsl:when test="Operation[text()!='ABC']">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="Year"/>
<xsl:apply-templates select="Operation"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>