How to select these elements with Xpath? - xslt

I have a document, something like this:
<root>
<A node="1"/>
<B node="2"/>
<A node="3"/>
<A node="4"/>
<B node="5"/>
<B node="6"/>
<A node="7"/>
<A node="8"/>
<B node="9"/>
</root>
Using xpath, How can I select all B elements that consecutively follow a given A element?
It's something like following-silbing::B, except I want them to be only the immediately following elements.
If I am on A (node==1), then I want to select node 2.
If I am on A (node==3), then I want to select nothing.
If I am on A (node==4), then I want to select 5 and 6.
Can I do this in xpath? EDIT: It is within an XSL stylesheet select statement.
EDIT2: I don't want to use the node attribute on the various elements as a unique identifier. I included the node attribute only for purposes of illustrating my point. In the actual XML doc, I don't have an attribute that I use as a unique identifer. The xpath "following-sibling::UL[preceding-sibling::LI[1]/#node = current()/#node]"
keys on the node attribute, and that's not what I want.

Short answer (assuming current() is ok, since this is tagged xslt):
following-sibling::B[preceding-sibling::A[1]/#node = current()/#node]
Example stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="/">
<xsl:apply-templates select="/root/A"/>
</xsl:template>
<xsl:template match="A">
<div>A: <xsl:value-of select="#node"/></div>
<xsl:apply-templates select="following-sibling::B[preceding-sibling::A[1]/#node = current()/#node]"/>
</xsl:template>
<xsl:template match="B">
<div>B: <xsl:value-of select="#node"/></div>
</xsl:template>
</xsl:stylesheet>
Good luck!

While #Chris Nielsen's answer is the right approach, it leaves an uncertainty in cases where the compared attribute is not unique. The more correct way of solving this is:
following-sibling::B[
generate-id(preceding-sibling::A[1]) = generate-id(current())
]
This makes sure that the preceding-sibling::A is identical to the current A, instead of just comparing some attribute values. Unless you have attributes that are guaranteed to be unique, this is the only safe way.

A solution might be to first gather up all the following nodes using following-sibling::*, grab the first of these and require it to be a 'B' node.
following-sibling::*[position()=1][name()='B']

Related

XSLT 2.0: How to replace() part of an element's value and use the result in a select operation?

I've done a bit of XPath in C++ and C# applications, but this is my first time really using it directly in XSLT. I have an XML file that is formatted like this:
<topLevel>
<foo>
<bar>prefix1_Taxi</bar>
...
</foo>
<foo>
<bar>prefix1_SchoolBus</bar>
...
</foo>
...
<foo>
<bar>prefix2_Taxi</bar>
...
</foo>
<foo>
<bar>prefix2_SchoolBus</bar>
...
</foo>
</topLevel>
First, I want to select only the <foo> elements that have a <bar> element that starts with "prefix1_." This appears to work:
<xsl:for-each select="foo[bar[starts-with(., 'prefix1_')]]">
<!-- Style and format the values from child elements of the "prefix1_" <foo> -->
</xsl:for-each>
From inside the for-each block, I then want to select the <foo> element that contains the corresponding "prefix2_" element. I then want to pull data out of each as I see fit.
For example, when the for-each has selected "prefix1_Taxi", I want to then select the foo element that contains the "prefix2_Taxi" bar element.
<xsl:for-each select="foo[bar[starts-with(., 'prefix1_')]]">
<!-- Retrieve the corresponding "prefix2_" foo -->
<!-- Style and format the values from child elements of the "prefix1_" <foo> -->
<!-- Style and format the values from child elements of the "prefix2_" <foo> -->
</xsl:for-each>
Unfortunately, I have no idea how to go about this. In a traditional program I would do something like the following pseudocode:
String buf = barElement.Name.Replace("prefix1_", String.Empty);
XmlNode otherFoo = document.SelectSingleNode("foo[bar[" + buf + "]]");
However XSLT obviously works with a different paradigm for retrieving values and manipulating data, so I'm trying to break out of my old mode of thinking.
Using what I've gathered from some googling on XSLT, I came up with something pretty ugly:
Select the foo element containing a bar that starts with some text:
foo[bar[starts-with(., ...)
Replace the "prefix1_" in our current <bar> element:
replace(<xsl:value-of select='bar'/>, 'prefix1_', '')
This yields a pretty ugly mess:
<xsl:value-of select="foo[bar[starts-with(., replace(<xsl:value-of select='bar'/>, 'prefix1_', ''))]]" />
I'm also pretty sure that the <xsl:value-of> element isn't correct.
How do I go about this? I suspect that I'm missing some core concepts of how to express this concept in XSLT. I'm slogging through the w3.org page on XSLT but I still need much more study and practice.
This XSL stylesheet should give you some flexibility about what you do with the foo elements that contain "prefix2".
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<!-- Matches the top level element and selects the children with prefix1 text. -->
<xsl:template match="topLevel">
<xsl:copy>
<xsl:apply-templates select="foo[bar[starts-with(., 'prefix1_')]]"/>
</xsl:copy>
</xsl:template>
<!-- We're at a foo element with prefix1 text. Select the siblings of the
same vehicle type, with prefix2 text. -->
<xsl:template match="foo[bar[starts-with(., 'prefix1_')]]">
<xsl:variable name="vehicle" select="substring-after(bar, 'prefix1_')"/>
<xsl:apply-templates select="../foo[bar = concat('prefix2_', $vehicle)]"/>
</xsl:template>
<!-- In this template, you can do whatever you want with the foo element containing
the prefix2 vehicles. This example just copies the element and its children. -->
<xsl:template match="foo[bar[starts-with(., 'prefix2_')]]">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
<xsl:for-each select="foo[bar[starts-with(., 'prefix1_')]]">
<xsl:variable name="new-text" as=xs:string" select="replace(bar/text(), '1', '2')" />
<xsl:value-of select="concat(../foo/bar[$new-text]/text(), ' OR DO SOMETHING MORE USEFUL THAN APPENDING TEXT')" />
</xsl:for-each>
You are on the foo element containing the "prefix1" text within the loop, and then change to the other bar element's text and operate on it, whatever you want to do.
Remark: You might need to add xmlns:xs="http://www.w3.org/2001/XMLSchema" to your XSLT root element for the xs:stringin my sample to work. And probably it would also work without the intermediate variable, but I think it makes the code slightly more readable.

Xpath is not working on variable having xml string

I am facing issue in writing xapth. Let me explain the problem.
I am writing xslt to transform some xml. The xslt also loads one xml file from disk into xslt variable.
PeopleXml.xml:
<TestXml>
<People>
<Person id="MSA1" name="Sachin">
<Profession>
<Role>Developer</Role>
</Profession>
</Person>
<Person id="ZAG4" name="Rahul">
<Profession>
<Role>Tester</Role>
</Profession>
</Person>
</People>
</TestXml>
XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://MyNamespace"
version="2.0">
<xsl:variable name="PeopleXml" select ="document('PeopleXml.xml')"/>
<xsl:variable name="peopleList" select="$PeopleXml/TestXml/People/Person"/>
<xsl:variable name="person1" select="MSA1"/>
<xsl:variable name="person" select="$peopleList/Person[#id=$person1]/#name"/>
<xsl:template match="/">
<xsl:value-of select="$person"/>
</xsl:template>
</xsl:stylesheet>
Issue: The xpath "$peopleList/Person[#id=$person1]/#name" is not returning anything. Infact, $peopleList/Person also does not work. However, I can see two person nodes in $peopleList variable when I debugged the code.
Could anyone help me, what I am doing wrong in xpath?
EDIT
Above xapth issue has been resolved after applying Daniel's solution. Now, only issue remained is with accessing child nodes of person based on some condition.
Following test does not work.
<xsl:variable name="roleDev" select="'Developer'"/>
<xsl:when test="$peopleList/Profession/Role=$roleDev">
<xsl:value-of select="We have atleast one Developer"/>
</xsl:when>
Your problem is here:
<xsl:variable name="person1" select="MSA1"/>
This results in having the $person1 variable empty.
Why?
Because the expression MSA1 is evaluated -- the current node doesn't have any children named "MSA1" and nothing is selected.
Solution:
Specify the wanted string as string literal:
<xsl:variable name="person1" select="'MSA1'"/>
Your Second Question:
Now, only issue remained is with accessing child nodes of person based
on some condition.
Use:
boolean($peopleList[Profession/Role = 'Developer'])
This produces true() exactly when there is a node in $peopleList such that it has at least one Profession/Role crand-child whose string value is the string "Developer"
Since the variable peopleList is already Person nodes, you should access them like this:
<xsl:variable name="person" select="$peopleList[#id=$person1]/#name"/>

XSLT to Select Desired Elements When Nested In Not-Desired Elements

What XSLT would I use to extract some nodes to output, ignoring others, when the nodes to be be extracted are some times nested nodes to be ignored?
Consider:
<alpha_top>This prints.
<beta>This doesn't.
<alpha_bottom>This too prints.</alpha_bottom>
</beta>
</alpha_top>
I want a transform that produces:
<alpha_top>This prints.
<alpha_bottom>This too prints.</alpha_bottom>
</alpha_top>
This answer shows how to select nodes based on the presence of a string in the element tag name.
Ok, here is a better way
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="beta">
<xsl:apply-templates select="*"></xsl:apply-templates>
</xsl:template>
<xsl:template match="/|*|text()">
<xsl:copy>
<xsl:apply-templates select="*|text()"></xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This basically does an identity transform, but for the element you don't want to include I removed the xsl:copy and only applied templates on the child elements.
The following stylesheet works on your particular case, but I suspect you are looking for something a bit more generic. I'm also sure there is a simpler way.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates select="alpha_top"></xsl:apply-templates>
</xsl:template>
<xsl:template match="alpha_top">
<xsl:copy>
<xsl:apply-templates select="beta/alpha_bottom|text()"></xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="*|text()">
<xsl:copy>
<xsl:apply-templates select="*|text()"></xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I think, that once you have a reasonable understand of how XSLT traversal works (hopefully I answered that in your other question) this becomes quite simple.
You have several choices on how to do this. Darrell Miller's answer shows you have to process a whole document and strip out the elements you're not interested in. That's one approach.
Before I go further, I get the impression that you might not entirely 'get' the concept of context in XSLT. This is important and will make your life simpler. At any time in XSLT there is one and only context node. This is the node (element, attribute, comment, etc) currently being 'processed'. Inside a template called via xsl:select the node that has been selected is the context node. So, given your xml:
<alpha_top>This prints.
<beta>This doesn't.
<alpha_bottom>This too prints.</alpha_bottom>
</beta>
</alpha_top>
and the following:
<xsl:apply-templates select='beta'/>
and
<xsl:template match='beta'>...</xsl:template>
the beta node will be the context node inside the template. There's a bit more to it than that but not much.
So, when you start your stylesheet with something like:
<xsl:template match='/'>
<xsl:apply-templates select='alpha_top'/>
</xsl:apply-templates>
you are selecting the children of the document node (the only child element is the alpha_top element). Your xpath statement inside there is relative to the context node.
Now, in that top level template you might decide that you only want to process your alpha_bottom nodes. Then you could put in a statement like:
<xsl:template match='/>
<xsl:apply-templates select='//alpha_top'/>
</xsl:template>
This would walk down the tree and select all alpha_top elements and nothing else.
Alternatively you could process all your elements and simply ignore the content of the beta node:
<xsl:template match='beta'>
<xsl:apply-templates/>
</xsl:template>
(as I mentioned in my other reply to you xsl:apply-templates with no select attribute is the same as using select=''*).
This will ignore the content of the beta node but process all of it's children (assuming you have templates).
So, ignoring elements in your output is basically a matter of using the correct xpath statements in your select attributes. Of course, you might want a good xpath tutorial :)
The probably simplest solution to your problem is this:
<xsl:template match="alpha_top|alpha_bottom">
<xsl:copy>
<xsl:value-of select="text()" />
<xsl:apply-templates />
</xsl:copy>
</xs:template>
<xsl:template match="text()" />
This does not exhibit the same white-space behavior you have in your example, but this is probably irrelevant.

XSLT Selection of Nodes Based on Substring of Element Name

How can I, with XSLT, select nodes based on a substring of the nodes' element name?
For example, consider the XML:
<foo_bar>Keep this.
<foo_who>Keep this, too.
<fu_bar>Don't want this.</fu_bar>
</foo_who>
</foo_bar>
From which I want to output:
<foo_bar>Keep this.
<foo_who>Keep this, too.
</foo_who>
</foo_bar>
Here I want to select for processing those nodes whose names match a regex like "foo.*".
I think I need an XSLT template match attribute expression, or an apply-templates select attribute expression, that applies the regex to the element's name. But maybe this can't be done without some construct like an statement?
Any help would be appreciated.
Here is some XSL that finds elements that start with "foo" to get you started. I don't think regex functionality was added until XSLT 2.0 based on Regular Expression Matching in XSLT 2.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*">
<xsl:variable name="name" select="local-name()"/>
<xsl:if test="starts-with($name, 'foo')">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
It gives this output, which seems to have an extra newline.
<foo_bar>Keep this.
<foo_who>Keep this, too.
</foo_who>
</foo_bar>

XSLT - Filtering

I have a repeating xml tree like this -
<xml>
<head>this is a sample xml file</head>
<item><color>yellow</color><color>red</color></item>
<item><color>blue</color></item>
<item><color>grey</color><color>red</color><color>blue</color></item>
</xml>
As you can see, each item can have a varying number of color tags.
I wish to get all the color tags for the first two items only.
<xsl:template match="xml">
<xsl:apply-templates select="item[position() < 3]/color" />
</xsl:template>
<xsl:template match="color">
<xsl:copy-of select="." />
</xsl:template>
Applied to your XML this yields:
<color>yellow</color>
<color>red</color>
<color>blue</color>
One potential possible way to get the items which is technically perfectly correct and in no way makes assumptions about the structure of your document with respect to namespacing, future requirements or template construction is just a simple:
/xml/item[position() < 3]/color
Try this...
/xml/item[ position() < 3 ]/color
Add an ordinal field to each item and select the first two.