XSLT: select distinct but slightly different to other examples - xslt

I have the following XML:
<a>
<b>
<d>D1 content (can include child nodes)</d>
</b>
<b>
<c>C1 content (can include child nodes)</c>
</b>
<b>
<e>E1 content (can include child nodes)</e>
</b>
<b>
<c>C2 content (can include child nodes)</c>
</b>
</a>
Using XSLT 1.0, I need to produce from this simply: "cde"; i.e. a distinct list of the names of the immediate children of /a/b/ ordered by the node name. Each b has exactly one child of arbitrary name.
I can produce "ccde":
<xsl:for-each select="/a/b/*">
<xsl:sort select="name(.)"/>
<xsl:value-of select="name(.)" />
</xsl:for-each>
I've tried using the usual preceding-sibling:: comparison, but as each b only has one child, the preceding sibling is always nothing.

First add this key element to the top of your XSL:-
<xsl:key name="tagNames" match="/a/b/*" use="name()" />
Now your for each loop can look like this:-
<xsl:template match="/*">
<xsl:for-each select="/a/b/*[count(. | key('tagNames', name())[1]) = 1]">
<xsl:sort select="name()" />
<xsl:value-of select="name()" />
</xsl:for-each>
</xsl:template>

You can use Muenchian method:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="groupIndex" match="*" use="name()" />
<xsl:template match="/">
<xsl:apply-templates select="a/b"/>
</xsl:template>
<xsl:template match="b">
<xsl:apply-templates select="*[1][generate-id(.) = generate-id(key('groupIndex', name())[1])]" mode="group" />
</xsl:template>
<xsl:template match="*" mode="group">
<xsl:value-of select="name()"/>
</xsl:template>
</xsl:stylesheet>

Related

XSLT Strip All Tabs In Text Output

Silly, simple question. When I output text, it still get the tabs based on my formatted/indented XSL structure. How do I instruct the transformer to ignore the spacing in the stylesheet while still keeping it neatly formatted?
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates select="Foo/Bar"></xsl:apply-templates>
</xsl:template>
<xsl:template match="Bar">
<xsl:for-each select="AAA"><xsl:for-each select="BBB"><xsl:value-of select="Label"/>|<xsl:value-of select="Value"/><xsl:text>
</xsl:text></xsl:for-each></xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Produces output line by line with no tabs:
SomeLabel|SomeValue
SomeLabel|SomeValue
SomeLabel|SomeValue
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates select="Foo/Bar"></xsl:apply-templates>
</xsl:template>
<xsl:template match="Bar">
<xsl:for-each select="AAA">
<xsl:for-each select="BBB">
<xsl:value-of select="Label"/>|<xsl:value-of select="Value"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Produces output with tabs:
SomeLabel|SomeValue
SomeLabel|SomeValue
SomeLabel|SomeValue
Update:
Adding this does not fix it:
<xsl:output method="text" indent="no"/>
<xsl:strip-space elements="*"></xsl:strip-space>
This is contrived, but you can imagine the XML looks like this:
<Foo>
<Bar>
<AAA>
<BBB>
<Label>SomeLabel1</Label>
<Value>SomeValue1</Value>
</BBB>
<BBB>
<Label>SomeLabel2</Label>
<Value>SomeValue2</Value>
</BBB>
<BBB>
<Label>SomeLabel3</Label>
<Value>SomeValue3</Value>
</BBB>
</AAA>
</Bar>
</Foo>
What you could try is wrapping all your current text nodes in xsl:text. For example, try this
<xsl:for-each select="BBB">
<xsl:value-of select="Label"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="Value"/>
<xsl:text>|</xsl:text>
</xsl:for-each>
Alternatively, you could make use of the concat function.
<xsl:for-each select="BBB">
<xsl:value-of select="concat(Label, '|')"/>
<xsl:value-of select="concat(Value, '|')"/>
</xsl:for-each>
You could even combine the two statements into one if you wanted
<xsl:for-each select="BBB">
<xsl:value-of select="concat(Label, '|', Value, '|')"/>
</xsl:for-each>
EDIT: If you prefer not to enter the separator | so many times, you make use of template matching to output the fileds. First, replace the value-of with apply-templates like so
<xsl:for-each select="BBB">
<xsl:apply-templates select="Label"/>
<xsl:apply-templates select="Value"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
Then you would have one specific template to match Label, where you wouldn't need to output the separator, and another more generic template matching any child of BBB
<xsl:template match="BBB/Label" priority="1">
<xsl:value-of select="." />
</xsl:template>
<xsl:template match="BBB/*">
<xsl:text>|</xsl:text><xsl:value-of select="." />
</xsl:template>
(The priority here is needed to ensure Label is matched by the first template, and not the general one). Of course, you could also not do apply-templates on Label in this case, and just do xsl:value-of for that one.
Furthermore, if the fields were being output in the order they appear in the XML, you could simplify the for-each to just this
<xsl:for-each select="BBB">
<xsl:apply-templates />
<xsl:text>
</xsl:text>
</xsl:for-each>

Map from 2 different nodes to duplicated target node

I have this source and target :
I need that my map would produce a target with 2 nodes of "T".
The first one is with "A" source content, and the second is with "B" source content.
For example:
Input:
<Root>
<A>
<FieldA>FA</FieldA>
<FieldB>FB</FieldB>
</A>
<B>
<FieldC>FC</FieldC>
<FieldD>FD</FieldD>
</B>
</Root>
Requested Output:
<Root>
<T>
<F1>FA</F1>
<F2>FB</F1>
</T>
<T>
<F1>FC</F1>
<F2>FD</F2>
</T>
</Root>
*** There is also a condition regarding the map from "B" to "T"
Place looping functoid
A --> Looping Functoid --> T
B --> Looping Functoid --> T
FieldA --> F1
FieldB --> F2
FieldC --> F1
FieldD --> F2
The following does what you want to achieve:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="Root">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="A|B">
<xsl:element name="T">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="*[parent::A or parent::B]">
<xsl:choose>
<xsl:when test="./name() = 'FieldA' or ./name() = 'FieldC'">
<xsl:element name="F1">
<xsl:value-of select="."/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="F2">
<xsl:value-of select="."/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Besides, please take the time to report on what you have tried so far. SO is not there to do your homework. So, show your effort in your future questions.
Just link A and B to T through a Looping Functoid.

XSLT removing unnecessary element

i am trying to write an xslt code that will check whether the description element exist or not if it exist then it will show the description element but if it does not exist then it should not show the description element.but my code below still show element although there is no value in it.how can we code it so that it wont show out the description element if there is no description for a services.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Service">
<xsl:element name="equipment">
<xsl:if test="description !='' ">
<xsl:value-of select="description" />
</xsl:if>
<xsl:if test="not(description)">
</xsl:if>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
as there is the an empty equipment element being returned.i want it to return only the first 2 equipment element that is not empty.
Updated solution is follows; please check
<xsl:template match="Services">
<xsl:for-each select="Service">
<xsl:if test="count(description) > 0 and description!=''">
<equipment>
<xsl:value-of select="description"/>
</equipment>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
<xsl:template match="/">
<xsl:apply-templates select="//Service"/>
</xsl:template>
<xsl:template match="Service">
<xsl:if test="description !='' ">
<xsl:element name="equipment">
<xsl:value-of select="description" />
</xsl:element>
</xsl:if>
</xsl:template>
or
<xsl:template match="/">
<xsl:apply-templates select="//Service"/>
</xsl:template>
<xsl:template match="Service">
<xsl:if test="child::description[text()]">
<xsl:element name="equipment">
<xsl:value-of select="description" />
</xsl:element>
</xsl:if>
</xsl:template>
Does this work for you?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- place <result /> as root to produce wellformed XML -->
<xsl:template match="/">
<result><xsl:apply-templates /></result>
</xsl:template>
<!-- rewrite those <Service /> that have a <description /> -->
<xsl:template match="Service[./description]">
<equipment><xsl:value-of select="description" /></equipment>
</xsl:template>
<!-- remove those who do not -->
<xsl:template match="Service[not(./description)]" />
</xsl:transform>

How do I get XSLT to express an attribute wherever it appears in the least invasive way?

I have XSLT 1.0 like this:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="paragraph">
<p>
<xsl:apply-templates/>
</p>
</xsl:template>
</xsl:stylesheet>
Only, there are lots and lots of templates that are similar to the first one. I would like to have a specific attribute emitted in each of these templates, but I want to make the least invasive change to pull it off. Here's the first thing I tried:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="paragraph">
<p>
<xsl:apply-templates/>
</p>
</xsl:template>
<xsl:template match="#class">
<xsl:attribute name="class">
<xsl:value-of select="#class"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
But that didn't work. I tried this:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="paragraph">
<p>
<xsl:attribute name="class">
<xsl:value-of select="#class"/>
</xsl:attribute>
<xsl:apply-templates/>
</p>
</xsl:template>
</xsl:stylesheet>
But to make that work in every template would require a lot of code duplication. Is that the best I can do, or is there a more appropriate way to make this work?
In your initial attempt
<xsl:template match="#class">
<xsl:attribute name="class">
<xsl:value-of select="#class"/>
</xsl:attribute>
</xsl:template>
the context node for this template is the class attribute node, therefore the value-of should select .:
<xsl:template match="#class">
<xsl:attribute name="class">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
However you should also note that a bare <xsl:apply-templates/> only applies templates matching children of the current node, and since attribute nodes do not count as children in the XSLT data model this #class template won't fire. You probably need to modify your paragraph template to say
<xsl:template match="paragraph">
<p>
<xsl:apply-templates select="#*|node()"/>
</p>
</xsl:template>
to apply templates that match the paragraph element's attributes as well as its children.
If you really don't want to make any change to the existing template rules, then add a template rule with higher priority than the rules for "p", etc, like this:
<xsl:template match="*[#class]" priority="100">
<xsl:variable name="v">
<xsl:next-match/>
</xsl:variable>
<xsl:apply-templates select="$v" mode="add-attribute">
<xsl:with-param name="att" select="#class"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*" mode="add-attribute">
<xsl:param name="att" as="attribute()"/>
<xsl:copy>
<xsl:copy-of select="#*, $att"/>
<xsl:apply-templates mode="#current"/>
</xsl:copy>
</xsl:template>
In 1.0 you would have to put this in a separate module and use apply-imports rather than next-match.

Is it possible to match "none" in XSL?

Given the following XML fragment:
<foo>
<bar>1</bar>
<bar>2</bar>
<bar>3</bar>
</foo>
The following XSL should work:
<xsl:template match="/">
<xsl:apply-templates
mode="items"
select="bar" />
</xsl:template>
<xsl:template mode="items" match="bar">
<xsl:value-of select="." />
</xsl:template>
Is there a way I can use a similar format to this to apply a template when there are no <bar/> entities? For example:
<xsl:template match="/">
<xsl:apply-templates
mode="items"
select="bar" />
</xsl:template>
<xsl:template mode="items" match="bar">
<xsl:value-of select="." />
</xsl:template>
<xsl:template mode="items" match="none()">
There are no items.
</xsl:template>
Yes.
But the logic should be:
<xsl:template match="foo">
<xsl:apply-templates select="bar"/>
</xsl:template>
<xsl:template match="foo[not(bar)]">
There are no items.
</xsl:template>
Note: It's foo element which is having or not having bar children.
One could also use this pattern to avoid extra chooses:
<xsl:template match="/*">
<xsl:apply-templates select="bar" mode="items"/>
<xsl:apply-templates select="(.)[not(bar)]" mode="show-absence-message"/>
</xsl:template>
<xsl:template match="bar" mode="items">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="/*" mode="show-absence-message">
There are no items.
</xsl:template>
No, when you have apply-templates select="bar" and the context node does not have any bar child elements then no nodes are processed and therefore no templates are applied. You could however change your code in the template doing the apply-templates to e.g.
<xsl:choose>
<xsl:when test="bar">
<xsl:apply-templates select="bar"/>
</xsl:when>
<xsl:otherwise>There are not items.</xsl:otherwise>
</xsl:choose>
Given the following XML fragment:
<foo>
<bar>1</bar>
<bar>2</bar>
<bar>3</bar>
</foo>
The following XSL should work:
<xsl:template match="/">
<xsl:apply-templates mode="items" select="bar" />
</xsl:template>
<xsl:template mode="items" match="bar">
<xsl:value-of select="." />
</xsl:template>
No, the <xsl:apply-templates> above doesn't select any node at all.
Is there a way I can use a similar
format to this to apply a template
when there are no entities?
Yes:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*[not(bar)]">
No <bar/> s.
</xsl:template>
<xsl:template match="/*[bar]">
<xsl:value-of select="count(bar)"/> <bar/> s.
</xsl:template>
</xsl:stylesheet>
when applied to the provided XML document:
<foo>
<bar>1</bar>
<bar>2</bar>
<bar>3</bar>
</foo>
the result is:
3<bar/> s.
When applied to this XML document:
<foo>
<baz>1</baz>
<baz>2</baz>
<baz>3</baz>
</foo>
the result is:
No <bar/> s.