Have the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<core:renderedItem renderedContentUUID="e8c957a5-03e0-41f4-83a5-297e43bd660f" family="ResearchOutput" type="ContributionToJournal" rendering="harvard" workflows="researchoutput" workflowStates="approved" external="false" classification="/dk/atira/pure/researchoutput/researchoutputtypes/contributiontojournal/article" state="/dk/atira/pure/publication/status/inpress">
<div class="rendering rendering_researchoutput rendering_researchoutput_harvard rendering_contributiontojournal rendering_harvard rendering_contributiontojournal_harvard">
<span>Reid, CT</span>
& Nsoh, W 2014, '
<span class="harvard_title">Whose Ecosystem is it Anyway: Private and Public Rights under New Approaches to Biodiversity Conservation</span>
'
<span>
<em>Journal of Human Rights and the Environment</em>
</span>
.
</div>
</core:renderedItem>
I'm new to XSLT and I'm trying to output the XML using XSLT in the following HTML format:
<div class="publications">
<p>
<span>Reid, CT</span>
& Nsoh, W 2014, '
<span class="harvard_title">Whose Ecosystem is it Anyway: Private and Public Rights under New Approaches to Biodiversity Conservation</span>
'
<span><em>Journal of Human Rights and the Environment</em></span>
.
</p>
</div>
The href link always starts with 'http://some.website.com/portal/en/research/' and the URL needs to be built up from that base + renderedContentUUID. I've managed this (sort of!) with help from others here but I'm stuck with the need to change the HTML output to build in the href etc. Also, the "harvard_title" string has to be changed to all lower case and if there is a colon in the title, I need to just take the string up to the colon. Then all spaces have to be changed into '-'s. This is how the URL looks at present:
http://somewebsite.com/portal/en/research/whose-ecosystem-is-it-anyway(e8c957a5-03e0-41f4-83a5-297e43bd660f).html
My xslt so far:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:core="http://atira.dk/schemas/pure4/model/core/stable"
xmlns:x="http://www.w3.org/1999/xhtml"
exclude-result-prefixes="x">
<xsl:output method="html"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="lower">
abcdefghijklmnopqrstuvwxyz
</xsl:variable>
<xsl:variable name="upper">
ABCDEFGHIJKLMNOPQRSTUVWXYZ
</xsl:variable>
<xsl:variable name="pubURL"/>
<xsl:template match="//*">
<html>
<body>
<h1>Staff Publications</h1>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="//*">
<h2>Journal Articles</h2>
<xsl:for-each select="//core:renderedItem">
<xsl:sort select="#type"/>
<xsl:choose>
<xsl:when test ="#type = 'ContributionToJournal'">
<xsl:variable name="uuid" select ="#renderedContentUUID"/>
<xsl:variable name="show" select="translate(translate((div/span[#class = 'harvard_title']),' ','-'), $upper, $lower)" />
<xsl:if test="contains($show, ':')">
<xsl:variable name="pubURL" select="concat('http://somewebsite.com/portal/en/research/', substring-before($show, ':'),'(',$uuid,').html')"/>
</xsl:if>
<xsl:if test="not(contains($show, ':'))">
<xsl:variable name="pubURL" select="concat('http://somewebsite.com/portal/en/research/',$show,'(',$uuid,').html')"/>
</xsl:if>
<xsl:value-of select ="$pubURL"/>
<div class="publications">
<p>
<xsl:copy-of select="div"/>
</p>
</div>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Can anyone shed some light into this for me. I'm unsure about how to proceed constructing the href from the pubURL and 'inserting' it into the in the HTML output.
It's very difficult to answer your question because (1) your XML is not valid and (2) your current stylesheet is confusing (for lack of better word).
To pinpoint the answer to the exact problem you asked about: if your stylesheet had the following template (in the appropriate place):
<xsl:template match="span[#class='harvard_title']">
<span class="harvard_title">
<a>
<xsl:attribute name="href">
<xsl:text>http://some.website.com/portal/en/research/</xsl:text>
<xsl:value-of select="translate(translate(substring-before(., ':'), $upper, $lower), ' ', '-') "/>
<xsl:value-of select="concat('(', ../../#renderedContentUUID, ').html')"/>
</xsl:attribute>
<xsl:copy-of select="node()"/>
</a>
</span>
</xsl:template>
then this template would write the following output to the result tree:
<span class="harvard_title">Whose Ecosystem is it Anyway: Private and Public Rights under New Approaches to Biodiversity Conservation</span>
Related
For the below simplified transformation sheet, I'd like to add the following functions:
For "Turnover 2019" and "Turnover 2020" I'd like to get the values below 1mio in a red badge and over 1mio in a green badge. I tried to implement this for "Turnover 2019", however I just get a blank output. The problem seems to be with the <xsl:if test="...">-part, as I do get the correct value if I just enter <xsl:value-of select="key('keyToCreditcard', id)/turnover_2019"/> (see "Turnover 2020").
For "Total Turnover" I need to sum up the values of "Turnover 2019" and "Turnover 2020". How do I do that?
XSL:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="keyToPerson" match="person" use="id"/>
<xsl:key name="keyToCreditcard" match="creditcard" use="cardowner"/>
<xsl:template match="/">
<xsl:for-each select="data/persons/person">
<xsl:if test="turnoverYear1 < 1000000">
<span class="badge bg-warning mx-2"><xsl:value-of select="key('keyToCreditcard', id)/turnoverYear1"/></span>
</xsl:if>
<xsl:if test="turnoverYear1 > 999999.99">
<span class="badge bg-danger mx-2"><xsl:value-of select="key('keyToCreditcard', id)/turnoverYear1"/></span>
</xsl:if>
<xsl:value-of select="key('keyToCreditcard', id)/turnoverYear2"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Input:
<data>
<persons>
<person>
<id>1</id>
<name>Jeramie Bischoff</name>
<city>Luzern</city>
<birthdate>04/16/1951</birthdate>
</person>
</persons>
<creditcards>
<creditcard>
<id>1</id>
<cardtype>Visa</cardtype>
<cardnumber>4041592612048990</cardnumber>
<turnoverYear1>952411.33</turnoverYear1>
<turnoverYear2>6135840.0</turnoverYear2>
<cardowner>1</cardowner>
</creditcard>
</creditcards>
</data>
Expected Output:
Name:Jeramie Bischoff
City: Luzern
birthdate: 04/16/1951
Card Type: Visa
Card Number: 4041592612048990
Turnover 2019: 952411.33
Turnover 2020: 6135840.0
Total Turnover: 7088251.33
Based on your sample XML, a good approach would be to split things up into multiple templates.
One to handle creating the basic HTML document structure (match="/"), one to handle <person> elements, one to handle the turnover badges, and so on. In general it's beneficial to prefer <xsl:apply-templates> over cramming everything into a single template with a bunch of nested <xsl:for-each>.
Since you have an <xsl:key> that links person IDs to credit cards, use that to fetch the <creditcard> into a variable ($cc) and work with it.
<xsl:output method="html" indent="yes" />
<xsl:key name="keyToCreditcard" match="creditcard" use="cardowner"/>
<xsl:template match="/">
<html>
<!-- ... -->
<xsl:apply-templates select="data/persons/person" />
<!-- ... -->
</html>
</xsl:template>
<xsl:template match="person">
<div class="person">
<xsl:variable name="cc" select="key('keyToCreditcard', id)" />
<!-- your question #1 -->
<div>
<xsl:text>Turnover 2019: </xsl:text>
<xsl:apply-templates select="$cc/turnoverYear1" />
</div>
<div>
<xsl:text>Turnover 2020: </xsl:text>
<xsl:apply-templates select="$cc/turnoverYear2" />
</div>
<!-- your question #2 -->
<div>
<xsl:text>Total Turnover: </xsl:text>
<xsl:value-of select="$cc/turnoverYear1 + $cc/turnoverYear2" />
</div>
</div>
</xsl:template>
<xsl:template match="turnoverYear1|turnoverYear2">
<span>
<xsl:attribute name="class">
<xsl:text>badge mx-2 </xsl:text>
<xsl:choose>
<xsl:when test=". < 1000000">bg-warning</xsl:when>
<xsl:when test=". < 99999.99">bg-danger</xsl:when>
</xsl:choose>
</xsl:attribute>
<xsl:value-of select="." />
</span>
</xsl:template>
which results in
<html>
<div class="person">
<div>Turnover 2019: <span class="badge mx-2 bg-warning">952411.33</span></div>
<div>Turnover 2020: <span class="badge mx-2">6135840.0</span></div>
<div>Total Turnover: 7088251.33</div>
</div>
</html>
Because the year numbers and XML element names are a moving target in your XML, you would have to keep adjusting the XSLT code every year. That's completely unnecessary. It's a lot smarter to keep the year out of the XML element names. Move it into an attribute:
<creditcards>
<creditcard>
<id>1</id>
<cardtype>Visa</cardtype>
<cardnumber>4041592612048990</cardnumber>
<turnover year="2019">952411.33</turnover>
<turnover year="2020">6135840.0</turnover>
<cardowner>1</cardowner>
</creditcard>
</creditcards>
With a <creditcard> setup like this, the XSLT code gets more generic.
<xsl:template match="person">
<div class="person">
<xsl:variable name="cc" select="key('keyToCreditcard', id)" />
<!-- your question #1 -->
<xsl:apply-templates select="$cc/turnover" />
<!-- your question #2 -->
<div>
<xsl:text>Total Turnover: </xsl:text>
<xsl:value-of select="sum($cc/turnover)" />
</div>
</div>
</xsl:template>
<xsl:template match="turnover">
<div>
<xsl:value-of select="concat('Turnover ', #year, ': ')" />
<span>
<xsl:attribute name="class">
<xsl:text>badge mx-2 </xsl:text>
<xsl:choose>
<xsl:when test=". < 1000000">bg-warning</xsl:when>
<xsl:when test=". < 99999.99">bg-danger</xsl:when>
</xsl:choose>
</xsl:attribute>
<xsl:value-of select="." />
</span>
</div>
</xsl:template>
I got a question, I try to convert an XML to JSON using XSLT version 1.0.
As far as the name goes I get it right but the value is another story.
<datapost>
<fields>
<field>
<id>emailId</id>
<name>emailName</name>
<values>
<value>info#example.com</value>
</values>
</field>
</fields>
AS IT CURRENTLY IS:
At the moment I get only the "name" correct but the "value" (emailIdName & emailId & info#example.com) is all the values squashed together what I obviously don't want.
{
"emailName":{
"emailIdemailNameinfo#example.com"
}
}
EXPECTED TO BE:
I want to get only the "name" and the "value" in values (info#example.com)
This is the result that I WANT to get:
{
"emailName":{
"info#example.com"
}
}
This is the code I use:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="no" encoding="UTF-16" omit-xml-declaration="yes"/>
<xsl:template match="/">
<body>{
<xsl:call-template name="fieldsName"></xsl:call-template>
<xsl:text>
</xsl:text>
</body>
</xsl:template>
<xsl:template name="fieldsName">
<xsl:for-each select="//datapost[position()=1]/fields/field">
"
<xsl:value-of select="name"/>" :
<xsl:call-template name="fieldsValue"/>}
</xsl:for-each>
</xsl:template>
<!-- Array Element -->
<xsl:template match="*" mode="ArrayElement">
<xsl:call-template name="fieldsValue"/>
</xsl:template>
<!-- Object Properties -->
<xsl:template name="fieldsValue">
<xsl:variable name="childName" select="//datapost[position()=1]/fields/field/values/value"/>
<xsl:choose>
<xsl:when test="not(*|#*)">"
<xsl:value-of select="."/>"
</xsl:when>
<xsl:when test="count(*[name()=$childName]) > 1">{ "
<xsl:value-of select="$childName"/>" :[
<xsl:apply-templates select="*" mode="ArrayElement"/>] }
</xsl:when>
<xsl:otherwise>{
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="*"/>
}
</xsl:otherwise>
</xsl:choose>
<xsl:if test="following-sibling::*">,</xsl:if>
</xsl:template>
<!-- Attribute Property -->
<xsl:template match="#*">"
<xsl:value-of select="name"/>" : "
<xsl:value-of select="."/>",
</xsl:template>
</xsl:stylesheet>
The element names in your XML are poorly chosen, and this has probably confused you (it certainly confused me). One would expect "field" to contain a single field, but it actually contains all of them. So change
<xsl:for-each select="//datapost[position()=1]/fields/field">
to
<xsl:for-each select="//datapost[position()=1]/fields/field/*">
or better still,
<xsl:for-each select="datapost/fields/field/*">
since the other parts of the expression are redundant verbiage.
Then you need to look at the template containing the variable
<xsl:variable name="childName" select="//datapost[position()=1]/fields/field/values/value"/>
This is selecting all the values in the document, not just the values of the current element. I'm not entirely sure what you're trying to achieve here, and it doesn't seem to be covered by your test data, but I suspect you're trying to do some kind of grouping of elements that have the same name. For that you need to use grouping facilities: xsl:for-each-group in XSLT 2.0+, Muenchian grouping in XSLT 1.0. You haven't said which XSLT version you're using, but all of this would be an awful lot easier if you used XSLT 2.0+.
If someone would be kind enough to tell me why the following <xsl:call-template name="Log"> won't work?
XML File:
<?xml version="1.0" encoding="UTF-8"?>
<TextMessages>
<Message>[Step]</Message>
<Message>Step ID: 1</Message>
<Message>Description</Message>
</TextMessages>
XSLT File:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<html>
<body>
<h2>My CD Collection ></h2>
<p>
<xsl:call-template name="Log">
</xsl:call-template>
</p>
</body>
</html>
</xsl:template>
<xsl:template name ="Log">
<xsl:variable name="break"><br/></xsl:variable>
<xsl:for-each select="TextMessages">
<p>
<xsl:value-of select="."/>
<xsl:value-of select="$break"/>
</p>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The wanted output:
My CD Collection >
[Step]<br/>
Step ID: 1<br/>
Description<br/>
The real problem has also parameters which I call with parameters, but not sure if that is the problem.
<p>
<xsl:call-template name="Log">
<xsl:with-param name="testId" select="#testId" />
</xsl:call-template>
</p>
<xsl:template name ="Log">
<xsl:param name="testId" />
<xsl:variable name="break"><br/></xsl:variable>
<xsl:for-each select="/t:TestRun/t:Results/t:UnitTestResult[#testId=$testId]/t:Output/t:TextMessages/t:Message">
<p>
<xsl:value-of select="."/>
<xsl:value-of select="$break"/>
</p>
</xsl:for-each>
</xsl:template>
If i am guessing correctly, you want to change:
<xsl:template name ="Log">
<xsl:variable name="break"><br/></xsl:variable>
<xsl:for-each select="TextMessages">
<p>
<xsl:value-of select="."/>
<xsl:value-of select="$break"/>
</p>
</xsl:for-each>
</xsl:template>
to:
<xsl:template name="Log">
<xsl:for-each select="TextMessages/Message">
<p>
<xsl:value-of select="."/>
</p>
<hr/>
</xsl:for-each>
</xsl:template>
Note:
You should never have to use a hack like <br/> to output HTML or XML valid markup;
I am not sure why you need to call a named template here, instead of simply including the xsl:for-each in the first template, or just applying templates to the Message elements.
Adding the line
<xsl:output method="text" version="1.0" encoding="UTF-8" />
to your XSLT file directly after the <xsl:stylesheet... > line will you the (partly desired) result of
My CD Collection >
[Step]
Step ID: 1
Description
With "partly" I do refer to the first line - which you explicitly want to be output by including <h2>My CD Collection ></h2> in your XSLT - but not mention in your output text.
I have a HTML tree where I use the 'string()' query on the root to get all the text from the nodes.
However, I'd like to add a space between each nodes.
I.e.
string() on '<root><div>abc</div><div>def</div></root>' will become 'abcdef'
string() on '<root><div>abc</div><div>def</div></root>' should become 'abc def '
It's not clear what output you want when the XML is more complex than shown, or when it involves mixed content. In XSLT 1.0 you'll have to do a recursive descent of the tree, involving something like
<xsl:template match="div">
<xsl:if test="not(position()=1)"> </xsl:if>
<xsl:value-of select="."/>
</xsl:template>
'<root><div>abc</div><div>def</div></root>' should become 'abc def '
In XSLT 1.0, this would be done as:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/root">
<xsl:for-each select="div">
<xsl:value-of select="."/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Or perhaps you meant to retrieve all text nodes, regardless of the document structure. This could be done by:
<xsl:template match="/">
<xsl:for-each select="//text()">
<xsl:value-of select="."/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
You can try with itertext() method, that iterates over all text content:
from lxml import etree
root = etree.XML('<root><div>abc</div><div>def</div></root>')
print(' '.join(e for e in root.itertext()))
It yields:
abc def
I am making a website using a CMS, in the home page I need to get some Pictures and discriptions randomly,, I need each time i refresh the page to get new nodes.
This is how I am calling the nodes in XSL:
<xsl:for-each select ="TSPRoot/Blocks/Block[#ID=134]/Articles/Article[position() < 4]">
<div class="layout-08">
<a class="title" href="detailed.aspx?id={ID}">
<xsl:choose >
<xsl:when test ="string-length(ImageURL) > 0">
<img src="_handlers/resizeimage.ashx?src={ImageURL}&height=149&width=206" title="{Headline}"/>
</xsl:when>
<xsl:otherwise>
<img src="_frontend/ux/images/en_logo.png" width="206" height="149" title="{Headline}"/>
</xsl:otherwise>
</xsl:choose>
</a>
<div class="bg">
<xsl:value-of select ="Headline"/>
</div>
</div>
</xsl:for-each>
This gets the newest (3) nodes, I need random (3) nodes each time i refresh.
I would give you only some ideas of possible solution.
Technically XSLT doesn't offer a method random numbers. However, this problem can be solved in at least three different ways:
Generate pseudo-random number in Java/C#/PHP code before executing XSLT template and pass the generated number as an XSLT parameter.
Use the extension function: http://exslt.org/random/functions/random-sequence/index.html
Use current-dateTime() (XSLT 2.0) function as a seed for your random number procedure. With a couple of string and math operations you could convert it into desired quasi-random number. I think that for your needs this solution is enough.
EDIT - Ad 3.
I have made some experiments with this idea. And I have created some sample code. I know that this code generate a sequence of really pseudo random numbers, but for simple web page like in the question it might be enough.
The following XSLT:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:rnd="http://whatever.org/random">
<xsl:output indent="yes" />
<xsl:function name="rnd:pseudoRandomInternal">
<xsl:param name="numSeed" as="xs:decimal"/>
<xsl:variable name="squareSeed" select="xs:string($numSeed*$numSeed)"/>
<xsl:variable name="oneDivSeed" select="substring($squareSeed, 3, string-length($squareSeed) - 3)"/>
<xsl:variable name="cc" select="if (string-length($oneDivSeed) > 6) then
substring($oneDivSeed, string-length($oneDivSeed) - 6) else
$oneDivSeed"/>
<xsl:value-of select="xs:decimal($cc)"/>
</xsl:function>
<xsl:function name="rnd:pseudoRandom" as="xs:decimal*">
<xsl:param name="range" as="xs:integer"/>
<xsl:choose>
<xsl:when test="$range = 1">
<xsl:variable name="seed" select="substring(xs:string(current-time()), 1, 12)" as="xs:string"/>
<xsl:variable name="numSeed" select="xs:decimal(translate($seed, ':.+-', ''))"/>
<xsl:sequence select="(rnd:pseudoRandomInternal($numSeed))" />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="subSequence" select="rnd:pseudoRandom($range - 1)"/>
<xsl:variable name="newValue" select="rnd:pseudoRandomInternal($subSequence[1])" as="xs:decimal*"/>
<xsl:sequence select="($newValue, $subSequence)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:template match="/">
<xsl:variable name="rr" select="rnd:pseudoRandom(10)" as="xs:decimal*"/>
<output>
<xsl:for-each select="$rr">
<item>
<xsl:value-of select=". * 3 mod 11" />
</item>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
generated the following output:
<output>
<item>10</item>
<item>9</item>
<item>6</item>
<item>9</item>
<item>9</item>
<item>10</item>
<item>3</item>
<item>5</item>
<item>9</item>
<item>4</item>
</output>