Remove string before second separator in a node - xslt

I would like to remove string in a node after second separator ( | ):
<A>
<B>First | Second | Third<B>
</A>
<A>
<B>Apple | Orange | Bananas | Kiwi<B>
</A>
<A>
<B>Example<B>
</A>
Output:
<A>
<B>First | Second<B>
</A>
<A>
<B>Apple | Orange<B>
</A>
<A>
<B>Example<B>
</A>
My first idea was to use regex:
<xsl:template match="B">
<xsl:value-of select="replace(., '\|([^|]*)$', '')" />
</xsl:template>
...but it's not really working, maybe there is a better way to do this?

This transformation:
<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="B/text()">
<xsl:value-of select="string-join(tokenize(., ' \| ')[position() lt 3], ' | ')"/>
</xsl:template>
</xsl:stylesheet>
When applied on the provided XML (fragment, severely malformed -- now fixed):
<t>
<A>
<B>First | Second | Third</B>
</A>
<A>
<B>Apple | Orange | Bananas | Kiwi</B>
</A>
<A>
<B>Example</B>
</A>
</t>
produces the wanted, correct result:
<t>
<A>
<B>First | Second</B>
</A>
<A>
<B>Apple | Orange</B>
</A>
<A>
<B>Example</B>
</A>
</t>
Update:
In the question there is a conflict between the description and the provided wanted result.
The solution above produces the provided wanted result.
If really only the first token is needed, then the solution is even simpler:
<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="B/text()">
<xsl:value-of select="tokenize(., ' \| ')[1]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), the correct (for this interpretation of the question) result is produced:
<t>
<A>
<B>First</B>
</A>
<A>
<B>Apple</B>
</A>
<A>
<B>Example</B>
</A>
</t>

To remove the rest of the string after the first separator |, you can of course use a RegEx:
<xsl:template match="B">
<xsl:copy>
<xsl:value-of select="replace(., '(.*?)\s?\|.*?$', '$1')" />
</xsl:copy>
</xsl:template>
Its output is
<root>
<A>
<B>First</B>
</A>
<A>
<B>Apple</B>
</A>
<A>
<B>Example</B>
</A>
</root>
If you want, on the other hand, get the output you gave in your question, you can use a variant of the above RegEx and use this:
replace(., '(.*?)\|(.*?)\s?\|.*?$', '$1|$2')
Its output is
<root>
<A>
<B>First | Second</B>
</A>
<A>
<B>Apple | Orange</B>
</A>
<A>
<B>Example</B>
</A>
</root>

Related

Add parent node to a specific element xslt

I would like to select Parent node without Child node.
Example:
<root>
<p>some text</p>
<ol>
<li>
http://cowherd.com
</li>
</ol>
<p> http://cowherd.com </p>
http://cowherd.com
</root>
Desired Output: I want to add a parent <p> tag to those <a> tags which don't have any parent except <root>.
<root>
<p>some text</p>
<ol>
<li>
http://cowherd.com
</li>
</ol>
<p> http://cowherd.com </p>
<p> http://cowherd.com <p>
</root>
I tried thi but it doesn't work. It adds the <p> tag around all <a> tags.
<xsl:template match="a">
<xsl:if test="parent::*">
<p><a>
<!-- do not forget to copy possible other attributes -->
<xsl:apply-templates select="#* | node()"/>
</a></p>
</xsl:if>
</xsl:template>
I want to add a parent <p> tag to those <a> tags which don't have any parent except <root>.
I believe that boils down to:
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"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/root/a">
<p>
<xsl:copy-of select="."/>
</p>
</xsl:template>
</xsl:stylesheet>
Here's a way this could be done :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="a[not(parent::p)]">
<p>
<xsl:copy-of select="."/>
</p>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
See it working here : https://xsltfiddle.liberty-development.net/93F8dVt

Similar looking XSLT transforms not working

Input XML
<Web-inf>
<A>
<A1>Val1</A1>
<A1>Val1</A1>
<A1>Val1</A1>
</A>
<A>
<A1>Val2</A1>
<A1>Val2</A1>
<A1>Val2</A1>
</A>
<B>
<B1>Hi</B1>
</B>
<B>
<B1>Bye</B1>
</B>
<C>DummyC</C>
<D>DummyD</D>
</Web-inf>
I want to add <B> tag if it doesn't already exist with <B1> value as "Morning" and "Evening". If it exists i don't do anything. I have written following transform but the strange issue is that only the LATER one works and FIRST one is ignored completely. As a result only <B><B1>Evening</B1></B> is only inserted along with <B> tags. Is that a known issue? If yes, how to correct it?
<xsl:output method="xml" indent="yes" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Web-inf[not(B[B1='Morning'])]/B[last()]">
<xsl:copy-of select="*" />
<B>
<B1>Morning</B1>
</B>
</xsl:template>
<xsl:template match="Web-inf[not(B[B1='Evening'])]/B[last()]">
<xsl:copy-of select="*" />
<B>
<B1>Evening</B1>
</B>
</xsl:template>
I want the O/P XML to be as below
Output.xml
<Web-inf>
<A>
<A1>Val1</A1>
<A1>Val1</A1>
<A1>Val1</A1>
</A>
<A>
<A1>Val2</A1>
<A1>Val2</A1>
<A1>Val2</A1>
</A>
<B>
<B1>Hi</B1>
</B>
<B>
<B1>Bye</B1>
</B>
<B>
<B1>Morning</B1>
</B>
<B>
<B1>Evening</B1>
</B>
<C>DummyC</C>
<D>DummyD</D>
</Web-inf>
For you given input XML, both templates for B[last()] will be matched. When two templates match an element with equal priority, this is considered an error. The XSLT processor will either flag the error, or ignore all but the last matching template.
In this case, it might simply be better to have single template matching B[last()] and have the other conditions as xsl:if statements in the template.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Web-inf/B[last()]">
<xsl:copy-of select="*" />
<xsl:if test="not(../B[B1='Morning'])">
<B>
<B1>Morning</B1>
</B>
</xsl:if>
<xsl:if test="not(../B[B1='Evening'])">
<B>
<B1>Evening</B1>
</B>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

xslt-1.0 depth first pre-order traversal numbering

I am looking for an identity transform that adds an ordering attribute to each node. I want to have the explicit document position of each node available as an integer number.
I believe the desired ordering (as in https://en.wikipedia.org/wiki/Tree_traversal#/media/File:Sorted_binary_tree_preorder.svg) is the default ordering indeed as a selection on the descendants axis produces the correct ordering numbers:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method='xml' encoding='utf-8' indent='yes'/>
<xsl:template match="/*">
<root>
<xsl:apply-templates select="descendant::*">
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:attribute name="doc_order">
<xsl:value-of select="position()"/>
</xsl:attribute>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
but I need to preserve the input document structure as well,
as code above generates a flat list of all nodes instead.
example input:
<root>
<f>
<b>
<a/>
<d>
<c/>
<e/>
</d>
</b>
</f>
<g>
<i>
<h/>
</i>
</g>
</root>
desired output with explicit document order:
<root>
<f doc_order="1">
<b doc_order="2">
<a doc_order="3"/>
<d doc_order="4">
<c doc_order="5"/>
<e doc_order="6"/>
</d>
</b>
</f>
<g doc_order="7">
<i doc_order="8">
<h doc_order="9"/>
</i>
</g>
</root>
Learn about xsl:number, it is powerful:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*//*">
<xsl:copy>
<xsl:attribute name="doc_order">
<xsl:number level="any" count="*" from="root"/>
</xsl:attribute>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
See http://xsltransform.net/94rmq6n for an online sample.

How do I build an id and count at the same time?

In the following code, I need, in the place of the two xxxx's, to have the name of the element (name()), so h1, h2 or h3, whatever the match may be. So the second xxxx must be the count of the h1/h2/h3 in that file. The attribute will then look like "h1_4", or h3_15" etc.
How do I do that ?
<xsl:template match="h1[not(#id)] | h2[not(#id)] | h3[not(#id)]" >
<xsl:element name="{name()}" >
<xsl:attribute name="id">xxxx_<xsl:value-of><xsl:number count="xxxx" /></xsl:value-of></xsl:attribute>
</xsl:element>
<xsl:apply-templates/>
</xsl:template>
As I said, the request is ambiguous. The following stylesheet:
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="h1[not(#id)] | h2[not(#id)] | h3[not(#id)]" >
<xsl:variable name="name" select="name()" />
<xsl:copy>
<xsl:attribute name="id">
<xsl:value-of select="$name"/>
<xsl:text>_</xsl:text>
<xsl:value-of select="count(preceding::*[name()=$name]) + 1"/>
</xsl:attribute>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
when applied to the following test input:
<root>
<h1 id="h1_1"/>
<h2 type="abc"/>
<h3 type="xyz"/>
<h1>content</h1>
<h3 id="h3_2" type="efg"/>
<h2/>
</root>
will produce:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<h1 id="h1_1"/>
<h2 id="h2_1" type="abc"/>
<h3 id="h3_1" type="xyz"/>
<h1 id="h1_2">content</h1>
<h3 id="h3_2" type="efg"/>
<h2 id="h2_2"/>
</root>
You're on the right track using xsl:number, how about:
<xsl:template match="h1 | h2 | h3" >
<xsl:copy>
<xsl:attribute name="id">
<xsl:value-of select="name()"/>
<xsl:text>_</xsl:text>
<xsl:number level="any" />
</xsl:attribute>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
I'm assuming an identity template to copy the rest of the document as-is, and that being the case I've simplified the match pattern - you don't need to check for not(#id) as where there is an id attribute in the input it will overwrite the one being created by the xsl:attribute.

xsl match template by attribute except one

I've a global match on an attribut in my stylesheet but I want to exclude the f - element. How can I do that?
Example XML:
<a>
<b formatter="std">...</b>
<c formatter="abc">...</c>
<d formatter="xxx">
<e formatter="uuu">...</e>
<f formatter="iii">
<g formatter="ooo">...</g>
<h formatter="uuu">...</h>
</f>
</d>
</a>
Current solution:
<xsl:template match="//*[#formatter]">
...
</xsl:template>
I've tried something like this, but that didn't worked.
<xsl:template match="f//*[#formatter]">
...
</xsl:template>
<xsl:template match="//f*[#formatter]">
...
</xsl:template>
Either //f[#formatter] or f[#formatter] would have worked (the // is not necessary). When this XSLT is run on your example input:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[#formatter]">
<xsl:element name="transformed-{local-name()}">
<xsl:apply-templates select="#* | node()" />
</xsl:element>
</xsl:template>
<xsl:template match="f[#formatter]">
<xsl:apply-templates select="node()" />
</xsl:template>
</xsl:stylesheet>
The result is:
<a>
<transformed-b formatter="std">...</transformed-b>
<transformed-c formatter="abc">...</transformed-c>
<transformed-d formatter="xxx">
<transformed-e formatter="uuu">...</transformed-e>
<transformed-g formatter="ooo">...</transformed-g>
<transformed-h formatter="uuu">...</transformed-h>
</transformed-d>
</a>
As you can see, the f is excluded. Does this answer your issue, or have I misunderstood what you want to do?