I am trying to get the last value of a node that belongs to a group.
Given the following XML -
<books>
<author>
<name>R.R.</name>
<titles>
<titlename>North</titlename>
<titlename>King</titilename>
</titles>
</author>
<author>
<name>R.L.</name>
<titles>
<titlename>Dragon</titlename>
<titlename>Wild</titilename>
</titles>
</author>
</books>
I assume it would be something like -
<template match="/">
<for-each-group select="books/author" group-by="name">
<lastTitle>
<name><xsl:value-of select="name"/></name>
<title><xsl:value-of select="last()/name"/></title>
</lastTitle
</for-each-group>
<template>
Thus the result would be -
<lastTitle>
<name>R.R</name>
<title>King</title>
</lastTitle>
<lastTitle>
<name>R.L.</name>
<title>Wild</title>
</lastTitle>
How can I produce this result?
Instead of doing this
<xsl:value-of select="last()/name"/>
The expression you are looking for is this:
<xsl:value-of select="titles/titlename[last()]"/>
However, it might be worth pointing out, this isn't really a 'grouping' problem. (It would only be a grouping problems if you have two different author elements with the same name). You can actually just use a simple xsl:for-each here
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="books/author">
<lastTitle>
<name>
<xsl:value-of select="name"/>
</name>
<title>
<xsl:value-of select="titles/titlename[last()]"/>
</title>
</lastTitle>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Related
XML:
<?xml version="1.0" encoding="UTF-8"?>
<Service>
<Author name="Raymond">
<Book>Master Mind</Book>
<Book>Big Bites</Book>
</Author>
<Author name="CLAYTON">
<Book>Beyond the RACK</Book>
</Author>
</Service>`
using this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:for-each select="//Author">
<xsl:value-of select="#name" />
<xsl:for-each select="//Book">
<xsl:value-of select="." />
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>`
expected output:
Raymond Master Mind Big Bites CLAYTON Beyond the RACK
Use a relative path select="Book" for the inner for-each
<xsl:for-each select="//Book">
selects all Book nodes in the entire document, starting from the / root node. To select only Books that are children of the current Author, try:
<xsl:for-each select="Book">
--
Note: It's not clear to me on what basis do you expect white spaces to be inserted in-between the values written to the output.
For my input XML, I have written the XSLT , But I cannot make the XSLT to generate the <mynewtag> correctly. Please help.
XML input:
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book.child.1>
<title>charithram</title>
<author>sarika</author>
</book.child.1>
<book.child.2>
<title>doublebell</title>
<author>psudarsanan</author>
</book.child.2>
</books>
Expected Output:
<?xml version="1.0" encoding="UTF-8"?>
<newbooks>
<newbook>
<mynewtag id="book1" />
<title>charithram</title>
<author>sarika</author>
</newbook>
<newbook>
<mynewtag id="book2" />
<title>doublebell</title>
<author>psudarsanan</author>
</newbook>
</newbooks>
XSLT that I tried: [I understand the syntax is incorrect for <mynewtag> ]. But I don't know to fix it to get the desired output.
<?xml version="1.0" encoding="ISO-8859-1"?>
<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:template match="/">
<newbooks>
<xsl:for-each select="books/child::*">
<newbook>
<mynewtag id="book<xsl:number value='position()' format='1' />" />
<title>
<xsl:value-of select="title" />
</title>
<author>
<xsl:value-of select="author" />
</author>
</newbook>
</xsl:for-each>
</newbooks>
</xsl:template>
</xsl:stylesheet>
Trying in Online XSLT transformer , http://www.freeformatter.com/xsl-transformer.html
I tried assigning the position to a variable, but still I face the same problem of not knowing how to append it with the attribute value book .
<xsl:variable name="cnt">
<xsl:number value='position()' format='1' />
</xsl:variable>
<xsl:value-of select = "$cnt" />
Note: If I remove the <xsl:number value='position()' format='1' /> from XSL, then the syntax is correct, but then I won't be able to generate book1 book2 etc as <mynewtag> attribute values.
Please help.
Added: <mynewtag> is a required element. It is like any other XML element like <title> that is required in output. It is not an element just to hold the attribute id. Sorry if there is a confusion on this.
Adding Solution here, from the answers obtained to summarize:
<mynewtag>
<xsl:attribute name="id">
<xsl:text>book</xsl:text>
<xsl:number value='position()'/>
</xsl:attribute>
</mynewtag>
or shortly:
<mynewtag id="book{position()}" />"
or
<newbook>
<xsl:variable name="cnt">
<xsl:number value='position()' format='1' />
</xsl:variable>
<mynewtag id="book{$cnt}" />
..........
Also the attribute value templates that IanRoberts mentioned.
You can't place a tag inside another tag. Try either:
<mynewtag>
<xsl:attribute name="id">
<xsl:text>book</xsl:text>
<xsl:number value='position()'/>
</xsl:attribute>
</mynewtag>
or shortly:
<mynewtag id="book{position()}" />"
ADDED:
Not directly related to your question, but I believe the id attribute should be applied to the parent <newbook> element, instead of creating an artificial child element to hold it.
I have got an XML file with a structure similar to
<?xml version="1.0"?>
<medias>
<media>
<id>34500</id>
<refid/>
</media>
<media>
<id>34501</id>
<refid>34500</refid>
</media>
<media>
<id>34502</id>
<refid>34500</refid>
</media>
<media>
<id>34503</id>
<refid>34501</refid>
</media>
<media>
<id>34504</id>
<ref/>
</media>
<media>
<id>34505</id>
<refid>34502</refid>
</media>
</medias>
With XSL 1.0, I want to access all nodes that are not referenced by others.
So I created two variables
<xsl:variable name="origID" select="media/id/text()"/>
<xsl:variable name="refID" select="media/refid/text()"/>
and looked up on how to perform a difference operation between these two element sets
<xsl:variable name="diffID" select="$origID[count(. | $refID) != count($refID)]"/>
The result was:
origID contains 34500, 34501, 34502, 34503, 34504, 34505
refID contains 34500, 34500, 34501, 34502
I expected that
diffID would contain 34503, 34504, 34505
but
diffID still contains 34500, 34501, 34502, 34503, 34504, 34505.
What would be the best approach to accomplish my objective to get all nodes whose ID is referenced by other nodes.
Thanks in advance
Andre
To get the ids which are not referenced by an refid into a variable you may try this:
<xsl:variable name="diffID" select="media[not(id = //media/refid)]/id"/>
To show that it work use:
<xsl:template match="/*">
<xsl:variable name="diffID" select="media[not(id = //media/refid)]/id"/>
<xsl:for-each select="$diffID" >
<xsl:value-of select="."/>
<xsl:text>, </xsl:text>
</xsl:for-each>
</xsl:template>
Which will generate the following output.
34503, 34504, 34505,
If the real file is far bigger you should use xsl:key
And to do the same with id's in variables:
<xsl:template match="/*">
<xsl:variable name="origID" select="media/id"/>
<xsl:variable name="refID" select="media/refid"/>
<xsl:variable name="diffID" select="$origID[not(. = $refID)]"/>
<xsl:for-each select="$diffID" >
<xsl:value-of select="."/>
<xsl:text>, </xsl:text>
</xsl:for-each>
</xsl:template>
With XSLT 2.0 you can use the except operator (as long as you select nodes and not primitive values):
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="medias-with-id" select="//media[id]"/>
<xsl:variable name="referenced-medias" select="key('by-id', //refid)"/>
<xsl:key name="by-id" match="media" use="id"/>
<xsl:template match="/">
<xsl:copy-of select="$medias-with-id except $referenced-medias"/>
</xsl:template>
</xsl:stylesheet>
I am trying to transform
<Address>
<Line>Some street1</Line>
<Line>Some street2</Line>
<Line>Some street3</Line>
...
</Address>
into
<Address1>Some street1</Address1>
<Address2>Some street2</Address2>
<Address3>Some street3</Address3>
<Address4></Address4>
<Address5></Address5>
The first xml is malleable and can be redefined if neccessary, however the second xml is part of a legacy system which cannot me changed.
Most of what I find, correctly, points me to using attributes but unfortunatly, its the element itself that I wish to edit.
Would anyone be able to assist or if not, point me in the right direction?
As easy as this, and probably the shortest solution:
<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="Line">
<xsl:element name="Address{position()}"><xsl:apply-templates/></xsl:element>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<Address>
<Line>Some street1</Line>
<Line>Some street2</Line>
<Line>Some street3</Line>
</Address>
the wanted, correct result is produced:
<Address1>Some street1</Address1>
<Address2>Some street2</Address2>
<Address3>Some street3</Address3>
Explanation:
Proper use of xsl:element and AVTs (Attribute Value Templates).
Have a look at the <xsl:element> element. In its name attribute, you can also supply an expression that is computed while running the XSLT:
<xsl:template match="Line">
<xsl:element name="{concat('Address', position())}"><xsl:value-of select="text()"/></xsl:element>
</xsl:template>
Update: position() is one-based.
It can be done by mangling a new element with the current position() :
<xsl:template match="/Address">
<Addresses>
<xsl:for-each select="Line">
<xsl:variable name="elename" select="concat('Address', string(position()))"></xsl:variable>
<xsl:element name="{$elename}">
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:for-each >
</Addresses>
</xsl:template>
I am in 'group' node. From it, I want to find such 'item' node, that has 'id' attribute equals to current's 'group' node 'ref_item_id' attribute value. So in my case, by being in 'group' node B, I want 'item' node A as output. This works:
<xsl:value-of select="preceding-sibling::item[#id='1']/#description"/>
But this doesn't (gives nothing):
<xsl:value-of select="preceding-sibling::item[#id=#ref_item_id]/#description"/>
When I type:
<xsl:value-of select="#ref_item_id"/>
I have '1' as result. So this attribute is for sure accessible, but I can't find path to it from XPath expression above. I tried many '../' combinations, but couldn't get it work.
Code to test: http://www.xmlplayground.com/7l42fo
Full XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item description="A" id="1"/>
<item description="C" id="2"/>
<group description="B" ref_item_id="1"/>
</root>
Full XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="no"/>
<xsl:template match="root">
<xsl:for-each select="group">
<xsl:value-of select="preceding-sibling::item[#id=#ref_item_id]/#description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This has to do with context. As soon as you enter a predicate, the context becomes the node currently being filtered by the predicate, and no longer the node matched by the template.
You have two options - use a variable to cache the outer scope data and reference that variable in your predicate
<xsl:variable name='ref_item_id' select='#ref_item_id' />
<xsl:value-of select="preceding-sibling::item[#id=$ref_item_id]/#description"/>
or make use of the current() function
<xsl:value-of select="preceding-sibling::item[#id=current()/#ref_item_id]/#description"/>
Your expression searches for an item whose id attribute matches its own ref_item_id. You need to capture the current ref_item_id in an xsl:variable and refer to that xsl:variable in the expression.
One more possible solution using xsl:key
<xsl:key name="kItemId" match="item" use="#id" />
<xsl:template match="root">
<xsl:for-each select="group">
<xsl:value-of select="key('kItemId', #ref_item_id)[1]/#description"/>
</xsl:for-each>
</xsl:template>
Looking at the XML, if I assume that you have <item> and <group> as siblings and in any order.
Then a sample input XML would look like the following.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item description="A" id="1"/>
<item description="C" id="2"/>
<group description="B" ref_item_id="1"/>
<item description="D" id="1"/>
<group description="E" ref_item_id="2"/>
</root>
Now, if the goal is to extract the description of all the <item> nodes whose id is matching with corresponding <group> *nodes ref_item_id*. Then we can simply loop over only such <item> nodes and get their description.
<xsl:output method="text" indent="no"/>
<xsl:template match="root">
<xsl:for-each select="//item[(./#id=following-sibling::group/#ref_item_id) or (./#id=preceding-sibling::group/#ref_item_id)]">
<xsl:value-of select="./#description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Since you say that nodes are having unique id and all nodes are placed before nodes.
I would recommend you to use the following XSL and loop over specific nodes instead of nodes.
<xsl:output method="text" indent="no"/>
<xsl:template match="root">
<xsl:for-each select="//item[./#id=following-sibling::group/#ref_item_id]">
<xsl:value-of select="./#description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>