XSLT combine "params" with "match="#*|node()" - xslt

GOAL
Output an XML with the exact input names and variables as the XML my XSLT receive + 2 params from external sources
PSEUDOCODE - XSLT(1.0) I'm using variables instead of params so it's easier to test
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="StatusCode" select="'11111111111'">
</xsl:variable>
<xsl:variable name="StatusMessage" select="'########'">
</xsl:variable>
<!-- BLOCK 1 -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- ------- -->
<!-- BLOCK 2 -->
<xsl:template match="/">
<StatusCode>
<xsl:value-of select="$StatusCode"/>
</StatusCode>
<StatusMessage>
<xsl:value-of select="$StatusMessage"/>
</StatusMessage>
</xsl:template>
<!-- ------- -->
</xsl:stylesheet>
PROBLEM
If I try "BLOCK 1" or "BLOCK 2" independently, they work, but I couldn't as desired combine them both

Your BLOCK 2 template is applied first, because it matches the root node. This template contains no xsl:apply-templates instructions - so the other template is never executed.
The question is WHERE do you want the added nodes to appear. You could do:
<!-- BLOCK 2 -->
<xsl:template match="/">
<xsl:apply-templates/>
<StatusCode>
<xsl:value-of select="$StatusCode"/>
</StatusCode>
<StatusMessage>
<xsl:value-of select="$StatusMessage"/>
</StatusMessage>
</xsl:template>
<!-- ------- -->
but this would place the added nodes outside the root element, making the result an XML fragment instead of well-formed XML document.

Related

XSLT - Is it possible to output template matches into one span tag

The following is an XSLT Transform that outputs HTML. Is it possible to output both Target A and Target B into one span?
xsl
<xsl:template match="targetA | targetB">
<span> Is it possible to output both targets in one span</span>
</xsl:template>
xml
<doc>
<targetA>
Target A Content
</targetA>
<targetB>
Target B Content
</targetB>
</doc>
expecting
<span>
Target A and Target B
</span>
This is similar to a previous question (Output once if both xml tags occur at the same time), so you can do this by having a template like this....
<xsl:template match="targetA | targetB[not(../targetA)]">
<span>
<xsl:value-of select="../targetA" />
<xsl:value-of select="../targetB" />
</span>
</xsl:template>
(You would probably use an xsl:text if you wanted a space between then).
However, you would also need a template to match, and ignore targetB to avoid that being output again. (Assuming you are using the identity template, for example).
Try this XSLT...
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="targetA | targetB[not(../targetA)]">
<span>
<xsl:value-of select="../targetA" />
<xsl:value-of select="../targetB" />
</span>
</xsl:template>
<xsl:template match="targetB" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note, this approach would fail if you have more that one targetA or targetB under the same parent.

How to iterate each xml tag having different names and values ? How to start iterating from a particular tag ? in xslt 2.0

I have the below xml text,
<SUBSCRIBER>
<Anumber>639081000000</Anumber>
<FirstCallDate>20130430104419</FirstCallDate>
<SetyCode>TNT</SetyCode>
<Status>ACT</Status>
<RoamIndicator/>
<PreloadCode>P1</PreloadCode>
<CreationDate>20130116100037</CreationDate>
<PreActiveEndDate/>
<ActiveEndDate>20130804210541</ActiveEndDate>
<GraceEndDate>20140502210541</GraceEndDate>
<RetailerIndicator/>
<OnPeakAccountID>9100</OnPeakAccountID>
<OnPeakSmsExpDate>20130804210504</OnPeakSmsExpDate>
<UnivWalletAcc/>
<UnliSmsOnCtl>20130606211359</UnliSmsOnCtl>
<UnliSmsTriCtl/>
<UnliSmsGblCtl/>
<UnliMocOnCtl>20130606211359</UnliMocOnCtl>
<UnliMocTriCtl/>
<UnliMocGblCtl/>
<UnliSurfFbcCtl>20130606212353</UnliSurfFbcCtl>
</SUBSCRIBER>
How can I iterate/parse over each xml tag name and get the value ( I need the name of the tag and value in a different variables) ? And also, How can I start iterating from particular tag name ? Ex: I would like to start iterating UnivWalletAcc
Please advise.
So far, I have tried the following,
<xsl:template match="SUBSCRIBER">
<xsl:variable name="tagName">
<xsl:value-of select="/*/*/name()"/>
</xsl:variable>
<xsl:variable name="tagValue">
<xsl:value-of select="/*/*/text()"/>
</xsl:variable>
<xsl:value-of select="$tagName"/>
<xsl:value-of select="$tagValue"/>
</xsl:template>
As an alternative to Veenstra's solution, instead of the xsl:if in the SUBSCRIBER/* template, you can control the iteration in the apply-templates:
<xsl:template match="SUBSCRIBER">
<data>
<xsl:apply-templates select="UnivWalletAcc,
UnivWalletAcc/following-sibling::*" />
</data>
</xsl:template>
With the following XSLT you can loop through all childs of the node SUBSCRIBER:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<!-- Identity template that will loop over all nodes and attributes -->
<xsl:template match="#*|node()">
<xsl:apply-templates select="#*|node()" />
</xsl:template>
<!-- Template to match the root and create new root -->
<xsl:template match="SUBSCRIBER">
<data>
<xsl:apply-templates select="#*|node()" />
</data>
</xsl:template>
<!-- Template to loop over all childs of SUBSCRIBER node -->
<xsl:template match="SUBSCRIBER/*">
<!-- This will test if we have seen the UnivWalletAcc node already, if so, output something, otherwise, output nothing -->
<xsl:if test="preceding-sibling::UnivWalletAcc or self::UnivWalletAcc">
<tag>
<tagName><xsl:value-of select="name()" /></tagName>
<tagValue><xsl:value-of select="." /></tagValue>
</tag>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

Remove whitespace from HTML generated using XSL

Background
Maintain readable XSL source code while generating HTML without excessive breaks that introduce spaces between sentences and their terminating punctuation. From Rethinking XSLT:
White space in XSLT stylesheets is especially problematic because it serves two purposes: (1) for formatting the XSLT stylesheet itself; and (2) for specifying where whitespace should go in the output of XSLT-processed XML data.
Problem
An XSL template contains the following code:
<xsl:if test="#min-time < #max-time">
for
<xsl:value-of select="#min-time" />
to
<xsl:value-of select="#max-time" />
minutes
</xsl:if>
<xsl:if test="#setting">
on <xsl:value-of select="#setting" /> heat
</xsl:if>
.
This, for example, generates the following output (with whitespace exactly as shown):
for
2
to
3
minutes
.
All major browsers produce:
for 2 to 3 minutes .
Nearly flawless, except for the space between the word minutes and the punctuation. The desired output is:
for 2 to 3 minutes.
It might be possible to eliminate the space by removing the indentation and newlines within the XSL template, but that means having ugly XSL source code.
Workaround
Initially the desired output was wrapped in a variable and then written out as follows:
<xsl:value-of select="normalize-space($step)" />.
This worked until I tried to wrap <span> elements into the variable. The <span> elements never appeared within the generated HTML code. Nor is the following code correct:
<xsl:copy-of select="normalize-space($step)" />.
Technical Details
The stylesheet already uses:
<xsl:strip-space elements="*" />
<xsl:output indent="no" ... />
Related
Storing html tags within an xsl variable
Question
How do you tell the XSLT processor to eliminate that space?
Thank you!
Instead of using copy-of you can apply the identity template with an additional template that trims the spaces from the text nodes. You only create one variable like in your first workaround.
You call:
<li><xsl:apply-templates select="$step" mode="nospace" />.</li>
The templates:
<xsl:template match="text()" mode="nospace" priority="1" >
<xsl:value-of select="normalize-space(.)" />
</xsl:template>
<xsl:template match="node() | #*" mode="nospace">
<xsl:copy>
<xsl:apply-templates select="node() | #*" mode="nospace" />
</xsl:copy>
</xsl:template>
I. This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="t[#max-time > #min-time]">
<span>
<xsl:value-of select=
"concat('for ', #min-time, ' to ', #max-time, ' minutes')"/>
</span>
<xsl:apply-templates select="#setting"/>
<xsl:text>.</xsl:text>
</xsl:template>
<xsl:template match="#setting">
<span>
<xsl:value-of select="concat(' on ', ., ' heat')"/>
</span>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document (none has been presented!):
<t min-time="2" max-time="3" setting="moderate"/>
produces the wanted, correct result:
<span>for 2 to 3 minutes</span>
<span> on moderate heat</span>.
and it is displayed by the browser as:
for 2 to 3 minutes
on moderate heat.
When the same transformation is applied on this XML document:
<t min-time="2" max-time="3"/>
again the correct, wanted result is produced:
<span>for 2 to 3 minutes</span>.
and it is displayed by the browser as:
for 2 to 3 minutes.
II. Layout (visual) solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" xmlns:gen="gen:gen" xmlns:gen-attr="gen:gen-attr"
exclude-result-prefixes="my gen">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<my:layout>
<span>for <gen-attr:min-time/> to <gen-attr:max-time/> minutes</span>
<gen-attr:setting><span> on <gen:current/> heat</span></gen-attr:setting>
<gen:literal>.</gen:literal>
</my:layout>
<xsl:variable name="vLayout" select="document('')/*/my:layout/*"/>
<xsl:variable name="vDoc" select="/"/>
<xsl:template match="node()|#*">
<xsl:param name="pCurrent"/>
<xsl:copy>
<xsl:apply-templates select="node()|#*">
<xsl:with-param name="pCurrent" select="$pCurrent"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="$vLayout">
<xsl:with-param name="pCurrent" select="$vDoc/*"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="gen-attr:*">
<xsl:param name="pCurrent"/>
<xsl:value-of select="$pCurrent/#*[name() = local-name(current())]"/>
</xsl:template>
<xsl:template match="gen-attr:setting">
<xsl:param name="pCurrent"/>
<xsl:variable name="vnextCurrent" select=
"$pCurrent/#*[name() = local-name(current())]"/>
<xsl:apply-templates select="node()[$vnextCurrent]">
<xsl:with-param name="pCurrent" select="$vnextCurrent"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="gen:current">
<xsl:param name="pCurrent"/>
<xsl:value-of select="$pCurrent"/>
</xsl:template>
<xsl:template match="gen:literal">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
This transformation gives us an idea how to make a visual (skeletal) representation of the wanted output and use it to "populate" it with the wanted data from the source XML document.
The result is identical with that of the first solution. If this transformation is run "as-is" it will produce a lot of namespaces -- they are harmless and will not be produced if the layout is in a separate XML file.

What is this XSLT code doing?

I'm new to XSLT. I have a block code that I don't understand.
In the following block what does '*','*[#class='vcard']' and '*[#class='fn']' mean?
<?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" encoding="utf-8"/> <xsl:template match="/">
<script type="text/javascript">
<xsl:text><![CDATA[function show_hcard(info) {
win2 = window.open("about:blank", "HCARD", "width=300,height=200," + "scrollbars=no menubar=no, status=no, toolbar=no, scrollbars=no");
win2.document.write("<h1>HCARD</h1><hr/><p>" + info + "</p>"); win2.document.close();
}]]></xsl:text>
</script>
<xsl:apply-templates/> </xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy> </xsl:template>
<xsl:template match="*[#class='vcard']">
<xsl:apply-templates/> </xsl:template>
<xsl:template match="*[#class='fn']">
<u>
<a>
<xsl:attribute name="onMouseDown">
<xsl:text>show_hcard('</xsl:text>
<xsl:value-of select="text()"/>
<xsl:text>')</xsl:text>
</xsl:attribute>
<xsl:value-of select="text()"/>
</a>
</u> </xsl:template> </xsl:stylesheet>
* matches all elements, *[#class='vcard'] pattern matches all elements with class attribute of vcard value. From that you can figure out what *[#class='fn'] may mean ;-)
I'd also suggest that you start here.
Your stylesheet has four template rules. In English these rules are:
(a) starting at the top (match="/"), first output a script element, then process the next level down (xsl:apply-templates) in the input.
(b) the default rule for elements (match="*") is to create a new element in the output with the same name and attributes as the original, and to construct its content by processing the next level down in the input.
(c) the rule for elements with the attribute class="vcard" is to do nothing with this element, other than to process the next level down in the input.
(d) the rule for elements with the attribute class="fn" is to output
<u><a onMouseDown="show_hcard('X')">X</a></u>
where X is the text content of the element being processed.
A more experienced XSLT user would have written the last rule as
<xsl:template match="*[#class='fn']">
<u>
<a onMouseDown="show_hcard('{.}')">
<xsl:value-of select="."/>
</a>
</u>
</xsl:template>

Help with multi-pass XSLT using node-set()

I need to display certain data in a tabular form, and would prefer to use multi-pass
xslt using node-set() so that I can avoid deploying additional tools (like xsltproc).
Right now, I'm able to perform the required task in two steps i.e.
Step 1: convert XML-1 to XMl-2 using identity template (xsl:copy, using xsl:element to
add dynamic elements 'dev' and 'qa`):
<projectteam>
<member>
<name>John</name>
<role>dev</role>
<hrs>100</hrs>
</member>
<member>
<name>Peter</name>
<role>qa</role>
<hrs>80</hrs>
</member>
</projectteam>
To
<projectteam>
<member>
<name>John</name>
<dev>100</dev>
</member>
<member>
<name>Peter</name>
<qa>80</qa>
</member>
<projectteam>
And then, use another XSLT-FO style sheet to transform XML #2 into a PDF document with the required layout:
name | dev | qa |
-----------------
John | 100 | |
Peter| | 80 |
-----------------
Total| 100 | 80 |
I've tried using node-set() (incorrectly I suppose) to combine both the steps, but
it wouldn't work as the result I get is as follows:
name | dev | qa |
-----------------
Total| 0 | 0 |
Stylesheet-1: converts XML-1 to XML-2, imports another stylesheet 'projDisplay.xsl',
uses node-set() to invoke the imported stylesheet, but the data doesn't get displayed?
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:exslt="http://exslt.org/common"
>
<!-- import stylesheet to display XML-2 in tabular form -->
<xsl:import href="projDisplay.xsl"/>
<xsl:template match="/">
<fo:root>
<xsl:apply-templates/>
</fo:root>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:variable name="newXmlData">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:variable>
<!-- ==> This is where my problem is - it goes to the template defined in -->
<!-- projDisplay.xsl (xslt-fo, pretty big one with page layout etc. hence -->
<!-- not included here, but it works on its own though) as I can see the -->
<!-- table header, and an empty totals row, but non of the rows are displayed -->
<xsl:apply-templates
select="exslt:node-set($newXmlData)/projectteam" mode="display"
/>
</xsl:template>
<!-- replace element 'role' with a new element - role name (i.e. dev or qa) -->
<!-- and set its value as 'hrs -->
<xsl:template match="role">
<xsl:element name="{.}"> <xsl:value-of select="../hrs"/> </xsl:element>
</xsl:template>
<!-- eliminate element 'hrs' -->
<xsl:template match="hrs"/>
</xsl:stylesheet>
The commented section in the stylesheet doesn't look right to me. Any suggestions
about how to correct it?
Thanks!
Here's the solution that works. It's based on Tomalak's original solution with some minor
modifications (like mode flags etc.) Again, thanks to Tomalak!!
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common"
version="1.0">
<xsl:import href="projDisplay.xsl"/>
<xsl:template match="/">
<!-- store intermediate form as RTF -->
<xsl:variable name="newXmlData">
<xsl:apply-templates mode="filter"/>
</xsl:variable>
<!-- Now apply templates (with xslt-fo) defined in projDisplay.xsl with -->
<!-- the root template as <xsl:template match="projectteam" mode="display"> -->
<!-- to the above RTF (i.e. after the original XML has be convertedr) -->
<xsl:apply-templates select="exslt:node-set($newXmlData)/projectteam" mode="display"/>
</xsl:template>
<!-- use identity templates to copy and modify original XML -->
<xsl:template match="#*|node()" mode="filter">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="filter"/>
</xsl:copy>
</xsl:template>
<!-- replace element 'role' with a new element - role name (i.e. dev or qa) -->
<!-- and set its value as 'hrs -->
<xsl:template match="member/role" mode="filter">
<xsl:element name="{.}"> <xsl:value-of select="../hrs"/> </xsl:element>
</xsl:template>
<!-- eliminate element 'hrs' -->
<xsl:template match="hrs" mode="filter"/>
</xsl:stylesheet>
What about:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:exslt="http://exslt.org/common"
exclude-result-prefixes="exslt"
>
<xsl:import href="projDisplay.xsl"/>
<xsl:template match="/">
<!-- store intermediary format as a RTF -->
<xsl:variable name="newXmlData">
<xsl:apply-templates />
</xsl:variable>
<!-- now we can the apply imported rules -->
<xsl:apply-templates
select="exslt:node-set($newXmlData)/projectteam"
mode="import"
/>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="member/role">
<xsl:element name="{.}">
<xsl:value-of select="../hrs" />
</xsl:element>
</xsl:template>
<xsl:template match="member/*" />
<xsl:template match="projectteam" mode="import">
<fo:root>
<xsl:apply-imports />
</fo:root>
</xsl:template>
</xsl:stylesheet>