xml to xml (shorter version) with XSL - xslt

ORIGINAL - this is my original XML:
<course acad_year="2012" cat_num="85749" offered="N" next_year_offered="2013">
<term term_pattern_code="4" fall_term="Y" spring_term="Y">full year</term>
<department code="VES">
<dept_long_name>Department of Visual and Environmental Studies</dept_long_name>
<dept_short_name>Visual and Environmental Studies</dept_short_name>
</department>
</course>
DESIRED RESULT - I am trying to create another shorter version of the original XML that will group and list all department codes like in the example below:
<departments>
<department code="some_code" name="some_name"/>
<department code="some_code" name="some_name"/>
<department code="some_code" name="some_name"/>
</departments>
This is what I am trying and not working:
<xsl:template match="/">
<departments>
<xsl:for-each-group select="fas_courses/course" group-by="department[#code]">
<xsl:text disable-output-escaping="yes"> <department code=" </xsl:text>
<xsl:value-of select="department/#code"/>
<xsl:text>" name="</xsl:text>
<xsl:value-of select="dept_short_name"/>
<xsl:text disable-output-escaping="yes">"><department/></xsl:text>
</xsl:for-each-group>
</departments>
</xsl:template>
The error I am getting
F [Saxon-PE 9.4.0.3] The value of attribute "code" associated with an element type "department" must not contain the '<' character.
From the error message, I understand the '<' character is giving me an error inside of the xsl:text element, but how do I put that character then?, I am already using disable-output-escaping="yes", is there anything else???
Thanks!

Using disable-output-escaping is never a good idea, and you really don't need it here. Try this:
<xsl:template match="/">
<departments>
<xsl:for-each-group select="fas_courses/course" group-by="department/#code">
<department code="{department/#code}" name="{department/dept_short_name}" />
</xsl:for-each-group>
</departments>
</xsl:template>

You need to write your text inside <[DATA[...]]> so it doesn't get parsed or use < and > for the tags that you output.

Related

trying to pick key and value in xslt in the following payload i am getting as empty responce as null

this is the xml paylode i am trying to strip by using xslt.
<products>
<sequenceNum>123456</sequenceNum>
<ecostQuoteType>0</ecostQuoteType>
<specifications>
<key>SP_ACC_PROVIDER</key>
<code>VZB</code>
<value> Business - VZB</value>
</specifications>
<specifications>
<key>SP_ACC_TECH</key>
<code>TDM or DWDM</code>
<value>TDM or DWDM</value>
</specifications>
<specifications>
<key>SP_APP_PERF_LEVEL</key>
<code>Platinum</code>
<value>Platinum</value>
</specifications>
this is how i am writing the xslt for pick that key and value from the above xml.
<optimazationspecs>
<xsl:for-each select="./products/specifications">
<xsl:for-each select="key" />
<xsl:for-each select="value" />
</xsl:for-each>
</optimazationspecs>
You've got a couple of empty xsl:for-each elements. So you're saying "for each key do nothing", and "for each value do nothing". So you shouldn't be surprised that the stylesheet essentially does nothing. But you don't say what output you actually want, other than that you are "trying to strip" the document (whatever that means), which makes it hard to correct your code.
What output do you expect? Your for-each-Loops are empty as Michael mentioned. At least add <xsl:apply-templates/> to get the information you need.
Try something like:
<xsl:template match="products">
<xsl:for-each select="specification">
<xsl:for-each select="key">
<xsl:apply-templates/>
</xsl:for-each>
<xsl:for-each select="value">
<xsl:apply-templates/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
But as I said I don't know what sense this should make. And <optimazationspecs> is a XSLT compile error.

hyphenation character in xslt attribute (xsl-fo)

I think it's a very simple question. But although I build very fancy xslt transformation, this simple one cannot be solved by me.
The problem is:
I want to add attributes to xsl-fo nodes, depending on xml data. These attributes have often a hyphen in it. How can I add these with an xslt transformation where xsl:attributes doesn't like the hyphenation character.
In a xml node I have got two attributes (name and value)
Example: name="font_size", value="7pt"
<Report>
<text content="I am a text">
<blockFormat name="font_size" value="7pt" />
</text>
</Report>
(I understand this is not wanted because you want to work with styles etceters. It's just an example with a simplified problem)
Now I want to make a xsl-fo block, and I want to place that attributes in the block element by using the xsl-function xsl:attribute
<fo:block>
<attribute name="{replace(#name,'_','-')}" select="#value" />
....
</fo:block>
goal to achieve after transformation
<fo:block font-size="7pt">
....
</fo:block
It doesn't function and I think this is because in xslt I can't put an hyphen in the attribute name, but in the fo-attribute it is needed.
Is there a way to use the xsl:attribute function for this?
And when not, what kind of working around do you suggest.
Thank you for helping!!!!
There are 1000 ways to do it, here is one (I didn't do anything with your Report element):
Input:
<Report>
<text content="I am a text">
<blockFormat name="font_size" value="7pt" />
</text>
</Report>
XSL:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
version="1.0">
<xsl:template match="Report">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="text">
<fo:block>
<xsl:apply-templates select="blockFormat/#*"/>
<xsl:value-of select="#content"/>
</fo:block>
</xsl:template>
<xsl:template match="#name">
<xsl:attribute name="{translate(.,'_','-')}">
<xsl:value-of select="ancestor::blockFormat/#value"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="#value"/>
</xsl:stylesheet>
Output:
<Report>
<fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format" font-size="7pt">I am a text</fo:block>
</Report>
Use #select instead of #value:
<fo:block>
<attribute name="{replace(#name,'_','-')}" select="#value" />
....
</fo:block>
See https://www.w3.org/TR/xslt20/#creating-attributes
Also, you need to be using XSLT 2.0 or 3.0 to use #select. If you're using XSLT 1.0, you'd have to do it as xsl:attribute/xsl:value-of/#select.
(It would also have helped understanding of your problem if you'd also shown the wrong result that you were getting.)

xsl generate attribute with prefix

I'm totally new to XSL and have the task of generating output xml with xmlns:A.B.C prefix/value as an attribute of the root node. Specifically like this:
<GetVersionResponse xmlns:A.B.C="www.somesite.co.uk/A/B/C">
<Class>GetVersionRequest</Class>
<Errors>
<Error>
<Text>Some message</Text>
</Error>
</Errors>
</GetVersionResponse>
This is my input:
<Error>
<Namespace>xmlns:A.B.C</Namespace>
<NamespaceValue>www.somesite.com/A/B/C</NamespaceValue>
<Class>GetVersionRequest</Class>
<Message>Some message</Message>
</Error>
This is the important snippet from the Error.xsl file:
<xsl:template match="Error">
<xsl:variable name="ResponseType">
<xsl:value-of select="concat(substring-before(Class, 'Request'),'Response')"/>
</xsl:variable>
<xsl:variable name="NS">
<xsl:value-of select="Namespace"/>
</xsl:variable>
<xsl:variable name="NSV">
<xsl:value-of select="NamespaceValue"/>
</xsl:variable>
<xsl:element name="{$ResponseType}" namespace="{$NSV}" xml:space="default">
...
</xsl:template
It generates the following error because I have a colon in the Namespace element of my input:
XSLT 2.0 Debugging Error: Unknown namespace prefix
If I replace the colon with an underscore I get the exact out put I require but only with an underscore of course. Like this:
<GetVersionResponse xmlns_A.B.C="www.somesite.co.uk/A/B/C">
<Class>GetVersionRequest</Class>
<Errors>
<Error>
<Text>Some message</Text>
</Error>
</Errors>
</GetVersionResponse>
I've trawled the web for hours and think I'm trying to insert the xmlns: as text instead of using the proper objects that will generate xmlns:MYTEXT but cannot figure out how to do it.
Many Thanks for any help !!!!
The complicating factor here is that xmlns:... namespace declarations are not attributes as far as the XPath data model is concerned, so you can't create them using xsl:attribute. However, in XSLT 2.0 you can create them using xsl:namespace.
<xsl:template match="Error">
<xsl:element name="{substring-before(Class, 'Request')}Response">
<xsl:namespace name="{substring-after(Namespace, 'xmlns:')}"
select="NamespaceValue" />
<xsl:copy-of select="Class" />
<Errors>
<Error>
<Text><xsl:value-of select="Message" /></Text>
</Error>
</Errors>
</xsl:element>
</xsl:template>
Live demo
The name attribute of xsl:namespace is the prefix you want to bind (so not including xmlns:), and the select gives the URI you want to bind it to.

Using XSL Choose to Substitute Empty String

I'm trying to substitute an empty value in a csv file with a number.
Here's an example:
1111111,,11222
So I tried this:
<xsl:template match="/">
<xsl:apply-templates select="//tr" />
</xsl:template>
<xsl:template match="tr">
<document>
<content name="title">
<xsl:value-of select="td[1]/text()" />
</content>
<content name="loanID">
<xsl:value-of select="td[1]/text()" />
</content>
<content name="cNumber">
<xsl:variable name="score" select="td[2]/text()" />
<xsl:choose>
<xsl:when test="$score=''">
<xsl:value-of select="550" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="td[18]/text()" />
</xsl:otherwise>
</xsl:choose>
</content>
</document>
</xsl:template>
I constantly get a null value for the cNumber node when the value is empty in the row, and I'm expecting my code to substitute the empty value for '550'. What am I doing wrong? I checked this question here: and it seems like this should work. I'm using a special application for this but my guess is the fault lies with me.
Thanks
If the td element is empty, then td/text() returns an empty sequence/node-set, and when you compare an empty sequence to '', the result is false. This is one of the reasons that many people advise against using text(). You're not interested here in the text node, you are interested in the string value of the td element, and to get that you should use
<xsl:variable name="score" select="string(td[2])" />
Your other uses of text() are also incorrect, though you're only likely to see a problem if your XML input contains comments or processing instructions. But you should get out of this coding habit, and replace
<xsl:value-of select="td[1]/text()" />
by
<xsl:value-of select="td[1]" />
As a general rule, when you see /text() in an XPath expression it's usually wrong.

XSLT not generating expected output

Input XML:
<derivatives>
<derivative id="4" name="Audio Content">
<operator id="1" name="Reliance">
<referenceCode code="62033815">
<mobileCircle id="1" name="Maharashtra"/>
</referenceCode>
</operator>
<operator id="22" name="Aircel">
<referenceCode code="811327">
<mobileCircle id="1" name="Maharashtra"/>
</referenceCode>
</operator>
</derivative>
</derivatives>
Expected Output XML:
<hellotune>
<operator>Aircel</operator>
<vcode>811327</vcode>
</hellotune>
Current output (which is wrong):
<hellotune>
<operator>Aircel</operator>
<vcode/>
</hellotune>
XSL (which is not working):
<xsl:if test="derivatives/derivative/operator[#name='Aircel']">
<hellotune>
<operator>Aircel</operator>
<vcode><xsl:value-of select="referenceCode/#code"/></vcode>
</hellotune>
</xsl:if>
Note: Using XSL v1.0. Not mentioned the complete XSL for brevity.
Based on the XSL you provided, it can be presumed that the context node is the root node, but starting from the root node, the path referenceCode/#code does not match anything in your input. Appending derivatives/derivative/operator/ before that path would succeed in finding a referenceCode #code attribute, but it would find the wrong one. Try this push-style approach:
<xsl:template match="/">
<xsl:apply-templates select="derivatives/derivative/operator[#name='Aircel']" />
</xsl:template>
<xsl:template match="operator">
<hellotune>
<operator><xsl:value-of select="#name" /></operator>
<vcode><xsl:value-of select="referenceCode/#code"/></vcode>
</hellotune>
</xsl:template>
The xpath xpression in the element <vcode> is not referring to any node in the input document. The best way to match all the nodes is to use
//
like in your code you can use
//referenceCode/#code
(but this is only for information purpose and using same can't get your result).
you can try this way:
<xsl:template match="/">
<hellotune>
<xsl:for-each select="//operator">
<xsl:if test="./#name='Aircel'">
<operator><xsl:value-of select="#name"/></operator>
<vcode><xsl:value-of select="referenceCode/#code"/></vcode>
</xsl:if>
</xsl:for-each>
</hellotune>
</xsl:template>
Hope this helps :)