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.
Related
Take a list of bugs
<?xml version="1.0"?>
<bugs>
<bug>
<status>todo</status>
<content> this is a todo bug </content>
</bug>
<bug>
<status>closed</status>
<content>this is a closed bug</content>
</bug>
<bug>
<status>new</status>
<content>this is a new bug</content>
</bug>
<bug>
<status>deferred</status>
<content>this is a deferred bug</content>
</bug>
<bug>
<status>todo</status>
<content>this is another todo bug</content>
</bug>
</bugs>
What would be an XSLT that would list a given set of "active" bugs and all others?
I was able to construct the "active" half of this question, based on an excellent answer given here: XSLT: Using variables in a key function, which would be the following XSLT:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
>
<xsl:output method="text"/>
<xsl:param name="pActive">
<!-- this is supposed to be the dynamic criteria -->
<a>todo</a>
<a>new</a>
</xsl:param>
<xsl:key name="kBugsByStatus" match="bug" use="normalize-space(status)"/>
<xsl:variable name="vActive" select="ext:node-set($pActive)/*"/>
<xsl:template match="/">
<xsl:text>Active bugs:
</xsl:text>
<xsl:apply-templates select="key('kBugsByStatus',$vActive)"/>
<xsl:text>Other bugs:
</xsl:text>
<!-- This is the question: -->
<xsl:apply-templates select="key('kBugsByStatus',???)"/>
</xsl:template>
<xsl:template match="bug">
<xsl:text>* </xsl:text>
<xsl:value-of select="normalize-space(content)"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Thanks!
Use select="/bugs/bug[not(status = $vActive)]".
Considering this XML,
XML:
<?xml version="1.0" encoding="UTF-8"?>
<items>
<book>
<title>doublebell</title>
<count>available</count>
</book>
<phone>
<brand>nokia</brand>
<model></model>
</phone>
</items>
Mapping Criteria while writing XSLT:
show the newbook/newtitle only if a value is present in input.
show the newbook/newcount only if a value is present in input.
show the newphone/newbrand only if a value is present in input.
show the newphone/newmodel only if a value is present in input.
XSLT:
<?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:variable name="book" select="items/book" />
<xsl:variable name="phone" select="items/phone" />
<xsl:template match="/">
<items>
<newbook>
<xsl:if test="$book/title!=''">
<newtitle>
<xsl:value-of select="$book/title" />
</newtitle>
</xsl:if>
<xsl:if test="$book/count!=''">
<newcount>
<xsl:value-of select="$book/count" />
</newcount>
</xsl:if>
</newbook>
<xsl:if test="$phone/brand!='' or $phone/model!=''"> <!-- not sure if this condition is required for the above mapping criteria -->
<newphone>
<xsl:if test="$phone/brand!=''">
<newbrand>
<xsl:value-of select="$phone/brand" />
</newbrand>
</xsl:if>
<xsl:if test="$phone/model!=''">
<newmodel>
<xsl:value-of select="$phone/model" />
</newmodel>
</xsl:if>
</newphone>
</xsl:if>
</items>
</xsl:template>
</xsl:stylesheet>
This is my concern:- In my actual XSLT, I have almost 70 conditions like
this, and everytime the XPath search is made twice [or thrice.. ] for
each condition [ for eg: <xsl:if test="$phone/brand!=''"> and <xsl:value-of select="$phone/brand" /> and outer if condition].
Is this much performance overhead? I don't feel it when I ran my application.
I like to hear from experienced people if this is correct way of writing the XSLT. Do I need to save the path in a variable and reuse it as done for $book
and $phone ? In such a case there will be 70+variables just to hold this.
You can approach this quite differently using templates. If you define a template that matches any element whose content is empty and does nothing:
<xsl:template match="*[. = '']" />
or possibly use normalize-space() if you want to consider elements to be empty if they contain only whitespace
<xsl:template match="*[not(normalize-space())]" />
Now with this in place add templates for the elements you are interested in
<xsl:template match="book">
<newbook><xsl:apply-templates /></newbook>
</xsl:template>
<xsl:template match="title">
<newtitle><xsl:apply-templates /></newtitle>
</xsl:template>
and so on. Now the book template will create a newbook element and go on to process its children. When it gets to the title it will have two different templates to choose from and will pick the "most specific" match. If the title is empty then the *[. = ''] template will win and nothing will be output, only if the title is non-empty will it create a newtitle element.
This way you let the template matcher do most of the work for you, you don't need any explicit conditional checks using xsl:if.
<?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="/">
<items><xsl:apply-templates select="items/*" /></items>
</xsl:template>
<!-- ignore empty elements -->
<xsl:template match="*[not(normalize-space())]" />
<xsl:template match="book">
<newbook><xsl:apply-templates /></newbook>
</xsl:template>
<xsl:template match="title">
<newtitle><xsl:apply-templates /></newtitle>
</xsl:template>
<!-- and so on with similar templates for the other elements -->
</xsl:stylesheet>
Building on Ian's answer, you can also make a generic template that will create the "new" elements for you without having to specify each one individually. That would look like the below:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<items><xsl:apply-templates select="items/*" /></items>
</xsl:template>
<xsl:template match="*[not(normalize-space())]" />
<xsl:template match="*">
<xsl:element name="{concat('new',name())}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
That last template just rebuilds the element by concatenating the word "new" to the front of it.
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 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>
I must be missing some fundamental concept of processing an XML document. Here is my source XML:
<?xml version="1.0" encoding="ISO-8859-1"?>
<Root>
<Element>visitorNameAlt</Element>
<Element>visitorScore</Element>
<Element>visitorTimeouts</Element>
<Element>Blank</Element>
<Element>homeNameAlt</Element>
<Element>homeScore</Element>
<Element>homeTimeouts</Element>
<Element>Blank</Element>
<Element>period</Element>
<Element>optionalText</Element>
<Element>flag</Element>
<Element>Blank</Element>
<Element>scoreLogo</Element>
<Element>sponsorLogo</Element>
</Root>
And my XSL stylesheet:
<?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" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="/Root">
<xsl:value-of select="position()"/>
<xsl:value-of select="Element"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
All I want is to pluck the "Element" names from the source XML doc with their relative position in front.
My output is just "1" followed by the first element and nothing more.
I am new to XSLT, but have processed other documents successfully with for-each.
Thanks in advance.
Bill
You're looping over Root tags, not Element tags. Try this:
<xsl:template match="/">
<xsl:for-each select="/Root/Element">
<xsl:value-of select="position()"/>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
Note that you must change the second value-of select to "." or "text()".
XSLT is not an imperative programming language. The XSLT processor grabs each element in turn and tries to match it to your stylesheet. The idiomatic way to write this is without a for-each:
<xsl:template match="/Root">
<xsl:apply-templates select="Element"/>
</xsl:template>
<xsl:template match="Element">
<xsl:value-of select="position()"/>
<xsl:value-of select="."/>
</xsl:template>
The first template matches the root and tells the processor to apply the stylesheet to all the Element nodes inside the Root. The second template matches those nodes, and outputs the desired information.