Get parent node from child and rename it - xslt

I have the input bellow and I wrote some xslt that gives me an office with a specific ID but since I get the parent node I also get the tag <e>. My problem is that I don't want to have that <e> tag.
<response>
<offices>
<e>
<id>33701</id>
<name>aa</name>
</e>
.....<e></e>
</offices>
</response>
<xsl:template match="*:response/offices">
<econ:GetOfficesResponse>
<Office>
<xsl:for-each select="e/id">
<xsl:if test="text() = $office_id">
<xsl:copy-of select="parent::node()"/>
</xsl:if>
</xsl:for-each>
</Office>
</econ:GetOfficesResponse>
</xsl:template>
</xsl:stylesheet>
The response that I get:
<econ:GetOfficesResponse>
<Office>
<e>
<id>33701</id>
<name>...</name>
</e>
</Office>
The response that I want:
<econ:GetOfficesResponse>
<Office>
<id>33701</id>
<name>...</name>
</Office>
Can someone please help me with this? I/m using xslt 2.0

It seems like instead of your xsl:for-each you simply want a single <xsl:copy-of select="e[id = $office_id]/*"/>

try this code:
<xsl:template match="*:response/offices">
<econ:GetOfficesResponse>
<Office>
<xsl:for-each select="e/id">
<xsl:if test="text() = $office_id">
<xsl:copy-of select="parent::node()/child::node()"/>
</xsl:if>
</xsl:for-each>
</Office>
</econ:GetOfficesResponse>
</xsl:template>

Related

Making a tree from delimited elements using XSLT

The question is similar to this one but slightly more complex...
I need to transform a flat XML like
<XML>
<A.W>1</A.W>
<A.X>2</A.X>
<B.Y>3</B.Y>
<B.Z>4</B.Z>
<C>5</C>
</XML>
to a proper tree
<XML>
<A>
<W>1</W>
<X>2</X>
</A>
<B>
<Y>3</Y>
<Z>4</Z>
</B>
<C>5</C>
</XML>
using XSLT
So, the levels of the tree are dot-separated. Ideally with unlimited fold, but could be just the 2-levels one. Thank you!
Try this for starters:
<xsl:template match="XML">
<xsl:for-each-group select="*"
group-adjacent="substring-before(local-name(), '.')">
<xsl:element name="{current-grouping-key()}">
<xsl:for-each select="current-group()">
<xsl:element name="{substring-after(local-name(), '.')}">
<xsl:copy-of select="child::node()"/>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each-group>
</xsl:template>

How get first name initial and last name in XSLT

I want yo get the first name initial and last name.
Input :
<root>
<ele name="Samp Huwani"/>
<ele name="Gong Gitry"/>
<ele name="Dery Wertnu"/>
</root>
Output
<names>S Huwani</name>
<names>G Gitry</name>
<names>D Wertnu</name>
Tried Code:
<xsl:template match="root/name">
<names>
<xsl:value-of select="#name" />
</name>
</xsl:template>
I am using XSLT 2.0 . Thank you
With the given example, you could use:
<xsl:template match="/root">
<xsl:copy>
<xsl:for-each select="ele">
<name>
<xsl:value-of select="substring(#name, 1, 1)"/>
<xsl:text> </xsl:text>
<xsl:value-of select="substring-after(#name, ' ')"/>
</name>
</xsl:for-each>
</xsl:copy>
</xsl:template>
However, names often do not conform to the same pattern.
In XSLT 2.0, you could simplify(?) this by using regex, e.g.:
<xsl:value-of select="replace(#name, '^(.{1}).* (.*)', '$1 $2')"/>

in xslt how to compare a string value with another variable containing multiple values

I have the following xml file. I need to fetch all unique "owner" values from this and perform some operations.
<issues>
<issue>
<owner>12345</owner>
</issue>
<issue>
<owner>87654</owner>
</issue>
<issue>
<owner>12345</owner>
</issue>
</issues>
<tests>
<test>
<owner>34598</owner>
</test>
<test>
<owner>12345</owner>
</test>
<test>
<owner>34598</owner>
</test>
<test>
<owner>11111</owner>
</test>
</tests>
I tried using following xslt script.
<xsl:for-each select="issues/issue[not(child::owner=preceding- sibling::issue/owner)]/owner">
<!--some code-->
</xsl:for-each>
<xsl:for-each select="tests/test[not(child::owner=preceding- sibling::test/owner)]/owner">
<xsl:variable name="IrmAs">
<xsl:value-of select="." />
</xsl:variable>
<xsl:variable name="IssueList">
<xsl:value-of select="//issues/issue/owner">
</xsl:variable>
<xsl:if test="not(contains($IssueList,$IrmAs))">
<!--some code-->
</xsl:if>
</xsl:for-each>
But am getting duplicate values. Could anyone please help me with this?
In XSLT 2.0 you can just use for-each-group:
<xsl:for-each-group select="issues/issue | tests/test" group-by="owner">
<!-- in here, . is the first issue/test with a given owner and current-group()
is the sequence of all issue/test elements that share the same owner -->
</xsl:for-each-group>
If you are stuck on 1.0 then you need to use a technique called "Muenchian grouping" - define a key that groups elements with the same owner, then process just the first item in each group using a generate-id trick
<xsl:key name="ownerKey" match="issue | test" use="owner" />
<xsl:for-each select="(issues/issue | tests/test)[generate-id()
= generate-id(key('ownerKey', owner)[1])]">
<!-- one iteration per unique owner, with . being the parent element of the
first occurrence -->
</xsl:for-each>
But am getting duplicate values.
It's not quite clear where you are getting the duplicate values - since your posted code does not output anything. If you had tested something like:
...
<xsl:for-each select="issues/issue[not(child::owner=preceding-sibling::issue/owner)]/owner">
<out>
<xsl:value-of select="." />
</out>
</xsl:for-each>
....
you would have seen that it does work (albeit inefficiently) and returns:
...
<out>12345</out>
<out>87654</out>
...
Similarly, testing the following snippet:
...
<xsl:for-each select="tests/test[not(child::owner=preceding-sibling::test/owner)]/owner">
<xsl:variable name="IrmAs">
<xsl:value-of select="." />
</xsl:variable>
<xsl:variable name="IssueList">
<xsl:value-of select="//issues/issue/owner"/>
</xsl:variable>
<xsl:if test="not(contains($IssueList,$IrmAs))">
<out>
<xsl:value-of select="." />
</out>
</xsl:if>
</xsl:for-each>
...
produces:
...
<out>34598</out>
<out>11111</out>
...
So the problem must be in the part of the code that you haven't posted. Note also that the code you did post has several syntax errors, e.g. :
<xsl:value-of select="//issues/issue/owner">
needs to be:
<xsl:value-of select="//issues/issue/owner"/>

Selectively copy and update xml nodes using XSLT

I'm working with an xml that I need to copy and update to pass on for further processing. The issue I'm having is that I have not figured out an efficient method to do this. Essentially, I want to update some data, conditionally, then copy all the nodes that were not updated. Why this is challenging is due to the volume and variance in the number and name of nodes to be copied. I also want to NOT copy nodes that have no text value. Here is an example:
INPUT XML
<root>
<PersonProfile xmlns:'namespace'>
<ID>0001</ID>
<Name>
<FirstName>Jonathan</FirstName>
<PreferredName>John</PreferredName>
<MiddleName>A</MiddleName>
<LastName>Doe</LastName>
</Name>
<Country>US</Country>
<Biirthdate>01-01-1980</Birthdate>
<BirthPlace>
<City>Townsville</City>
<State>OR</State>
<Country>US</Country>
</Birthplace>
<Gender>Male</Gender>
<HomeState>OR</HomeState>
...
<nodeN>text</nodeN>
</PersonProfile>
</root>
The "PersonProfile" node is just one of several node sets within the "root" element, each with their own subset of data. Such as mailing address, emergency contact info, etc. What I am attempting to do is update nodes if the variable has a new value for them then copy all the nodes that were not updated.
Here is my current XSLT
<xsl:stylesheet version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:variable name='updateData' select='document("report")'/>
<!-- Identity Transform -->
<xsl:template match='#* | node()'>
<xsl:if test'. != ""'>
<xsl:copy>
<xsl:apply-templates select='#* | node()'/>
</xsl:copy>
</xsl:if>
</xsl:template>
<!-- Template to update Person Profile -->
<xsl:template match='PersonProfile'>
<xsl:copy>
<xsl:apply-templates select='*'/>
<xsl:element name='Name'>
<xsl:if test='exists($updateData/Preferred)'>
<xsl:element name='FirstName'>
<xsl:value-of select='$reportData/FirstName'/>
</xsl:element>
</xsl:if>
<xsl:if test='exists($updateData/Preferred)'>
<xsl:element name='PreferredName'>
<xsl:value-of select='$updateData/Preferred'/>
</xsl:element>
</xsl:if>
<xsl:if test='exists($updateData/Middle)'>
<xsl:element name='MiddleName'>
<xsl:value-of select='$updateData/Middle'/>
</xsl:element>
</xsl:if>
<xsl:if test='exists($updateData/LastName)'>
<xsl:element name='LastName'>
<xsl:value-of select='$updateData/wd:LastName'/>
</xsl:element>
</xsl:if>
</xsl:element>
<xsl:if test='exists($updateData/Country)'>
<xsl:element name='Country'>
<xsl:value-of select='$updateData/Country'/>
</xsl:element>
</xsl:if>
....
<!-- follows same structure until end of template -->
</xsl:copy>
</xsl:template>
<!-- More Templates to Update other Node sets -->
</xsl:stylesheet>
What's happening right now, is that it's copying ALL the nodes and then adding the updates values. Using Saxon-PE 9.3.0.5, I'll get an output similar to this:
Sample Output
<root>
<PersonProfile xmlns:'namespace'>
<ID>0001</ID>
<Name>
<FirstName>Jonathan</FirstName>
<PreferredName>John</PreferredName>
<MiddleName>A</MiddleName>
<LastName>Doe</LastName>
</Name>
<Country>US</Country>
<Biirthdate>01-01-1980</Birthdate>
<BirthPlace>
<City>Townsville</City>
<State>OR</State>
<Country>US</Country>
</Birthplace>
<Gender>Male</Gender>
<HomeState>OR</HomeState>
...
<nodeN>text</nodeN>
<PreferredName>Jonathan</PreferredName>
<HomeState>WA</HomeState>
</PersonProfile>
</root>
I realize this is happening because I am applying the templates to all the nodes in PersonProfile and that I could specify which nodes to exclude, but I feel like this is a very poor solution as the volume of nodes could be upwards of 30 or more and that would require a written value for each one. I trust XML has a more elegant solution than to explicitly list each of these nodes. I would like to have an out like this:
Desired Output
<root>
<PersonProfile xmlns:'namespace'>
<ID>0001</ID>
<Name>
<FirstName>Jonathan</FirstName>
<PreferredName>Jonathan</PreferredName>
<MiddleName>A</MiddleName>
<LastName>Doe</LastName>
</Name>
<Country>US</Country>
<Biirthdate>01-01-1980</Birthdate>
<BirthPlace>
<City>Townsville</City>
<State>OR</State>
<Country>US</Country>
</Birthplace>
<Gender>Male</Gender>
<HomeState>WA</HomeState>
...
<nodeN>text</nodeN>
</PersonProfile>
</root>
If anyone could help me create a template structure that would work for the xml structure, I would GREATLY appreciate it. It would need to be "reusable" as there are similar node structures like Person Profile I would have to apply it to, but with different node names and number of elements, etc.
Thanks in advance for any help!
J
This should work for your original question:
<xsl:stylesheet version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:variable name='updateData' select='document("report")'/>
<!-- Identity Transform -->
<xsl:template match='#* | node()' name='copy'>
<xsl:copy>
<xsl:apply-templates select='#* | node()'/>
</xsl:copy>
</xsl:template>
<xsl:template match='*[not(*)]'>
<xsl:variable name='matchingValue'
select='$updateData/*[name() = name(current())]'/>
<xsl:choose>
<xsl:when test='$matchingValue'>
<xsl:copy>
<xsl:apply-templates select='#*' />
<xsl:value-of select='$matchingValue'/>
</xsl:copy>
</xsl:when>
<xsl:when test='normalize-space()'>
<xsl:call-template name='copy' />
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
As far as inserting new elements that are not present in the source XML, that's trickier. Could you open a separate question for that? I may have some ideas for how to approach that.

Match conditionally upon current node value

Given the following XML:
<current>
<login_name>jd</login_name>
</current>
<people>
<person>
<first>John</first>
<last>Doe</last>
<login_name>jd</login_name>
</preson>
<person>
<first>Pierre</first>
<last>Spring</last>
<login_name>ps</login_name>
</preson>
</people>
How can I get "John Doe" from within the current/login matcher?
I tried the following:
<xsl:template match="current/login_name">
<xsl:value-of select="../people/first[login_name = .]"/>
<xsl:text> </xsl:text>
<xsl:value-of select="../people/last[login_name = .]"/>
</xsl:template>
I'd define a key to index the people:
<xsl:key name="people" match="person" use="login_name" />
Using a key here simply keeps the code clean, but you might also find it helpful for efficiency if you're often having to retrieve the <person> elements based on their <login_name> child.
I'd have a template that returned the formatted name of a given <person>:
<xsl:template match="person" mode="name">
<xsl:value-of select="concat(first, ' ', last)" />
</xsl:template>
And then I'd do:
<xsl:template match="current/login_name">
<xsl:apply-templates select="key('people', .)" mode="name" />
</xsl:template>
You want current() function
<xsl:template match="current/login_name">
<xsl:value-of select="../../people/person[login_name = current()]/first"/>
<xsl:text> </xsl:text>
<xsl:value-of select="../../people/person[login_name = current()]/last"/>
</xsl:template>
or a bit more cleaner:
<xsl:template match="current/login_name">
<xsl:for-each select="../../people/person[login_name = current()]">
<xsl:value-of select="first"/>
<xsl:text> </xsl:text>
<xsl:value-of select="last"/>
</xsl:for-each>
</xsl:template>
If you need to access multiple users, then JeniT's <xsl:key /> approach is ideal.
Here is my alternative take on it:
<xsl:template match="current/login_name">
<xsl:variable name="person" select="//people/person[login_name = .]" />
<xsl:value-of select="concat($person/first, ' ', $person/last)" />
</xsl:template>
We assign the selected <person> node to a variable, then we use the concat() function to output the first/last names.
There is also an error in your example XML. The <person> node incorrectly ends with </preson> (typo)
A better solution could be given if we knew the overall structure of the XML document (with root nodes, etc.)
I think what he actually wanted was the replacement in the match for the "current" node, not a match in the person node:
<xsl:variable name="login" select="//current/login_name/text()"/>
<xsl:template match="current/login_name">
<xsl:value-of select='concat(../../people/person[login_name=$login]/first," ", ../../people/person[login_name=$login]/last)'/>
</xsl:template>
Just to add my thoughts to the stack
<xsl:template match="login_name[parent::current]">
<xsl:variable name="login" select="text()"/>
<xsl:value-of select='concat(ancestor::people/child::person[login_name=$login]/child::first/text()," ",ancestor::people/child::person[login_name=$login]/child::last/text())'/>
</xsl:template>
I always prefer to use the axes explicitly in my XPath, more verbose but clearer IMHO.
Depending on how the rest of the XML documents looks (assuming this is just a fragment) you might need to constrain the reference to "ancestor::people" for example using "ancestor::people[1]" to constrain to the first people ancestor.