How to make XSLT xsl:if work - xslt

I am trying to make this (xml 1.0) code work . I am new to this and already exhausted myself in
trying different ways. Does someone know my mistake?
<xsl:for-each select="News/Sport">
<xsl:if test="local-name()='Basketball'">
<p>
<xsl:text>Basketball Sport</xsl:text>
</p>
<xsl:value-of select="News/Sport/Basketball/Phrases/Phrase"/>
</xsl:if>
</xsl:for-each>
When I transform it into an HTML file the content doesn't show up. When I remove the xsl:for each and the xsl:if statements the content is successfully presented. I only wish that the content is first checked (if it is available in the XML file) and if yes, that it is taken from the XML content.
Thank you in advance for your help!
EDIT:
This is my XML code
<News>
<Sport>
<Basketball>
<Phrases>
<Phrase>Zach Randolph recovered the opening tipoff in Game 1 of the Western Conference Finals, and he didn’t touch the ball again until the possession following the Grizzlies’ first timeout.
</Phrase>
<Phrases>
</Basketball>
</Sport>
</News>
EDIT2:
Could you tell me why I cannot apply a template inside this below function? Only the text works now:(
<xsl:for-each select="News/Sport[Basketball]">
<xsl:apply-templates select="News/Sport/*" />
</xsl:for-each>
<xsl:template match="Basketball">
<p>
<xsl:text>Basketball Sport</xsl:text>
</p>
<xsl:apply-templates select="Phrases/Phrase"/>
</xsl:template>

<xsl:for-each select="News/Sport">
<xsl:if test="local-name()='Basketball'">
In this if test, the context node is a Sport element, so local-name() will always be Sport and will never equal Basketball.
I only wish that the content is first checked (if it is available in the XML file) and if yes, that it is taken from the XML content.
The usual way to handle this sort of thing in XSLT is to define templates matching the various nodes that might be present and then applying templates to all the nodes that are actually found. If there are no nodes of a particular type then the corresponding template will not fire
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" />
<xsl:template match="/">
<html>
<body>
<!-- apply templates that match all elements inside Sport, which may
be Basketball, Football, etc. -->
<xsl:apply-templates select="News/Sport/*" />
</body>
</html>
</xsl:template>
<!-- when we find a Basketball element ... -->
<xsl:template match="Basketball">
<p>
<xsl:text>Basketball Sport</xsl:text>
</p>
<xsl:apply-templates select="Phrases/Phrase"/>
</xsl:template>
<!-- when we find a Football element ... -->
<xsl:template match="Football">
<p>
<xsl:text>Football Sport</xsl:text>
</p>
<!-- whatever you need to do for Football elements -->
</xsl:template>
<xsl:template match="Phrase">
<p><xsl:value-of select="." /></p>
</xsl:template>
</xsl:stylesheet>
That way you don't need any explicit for-each or if, the template matching logic handles it all for you.

You are missing the idea of a context node. Within a xsl:for-each, everything you refer to is about or relative to the selected nodes. So, for instance, within <xsl:for-each select="News/Sport">, the context node is the Sport elements, and a test like <xsl:if test="local-name()='Basketball'"> is always going to be false because the local name is always Sport.
It looks like you want just <xsl:if test="Basketball"> which tests whether there are any Basketball child nodes of the current Sport node.
The same thing applies to <xsl:value-of select="News/Sport/Basketball/Phrases/Phrase"/>. Because everything is relative to the Sport node, XSLT is looking for News/Sport/Basketball/Phrases/Phrase within the Sport element, which never exists.
In addition, you can just put text in literally: there is no need for an xsl:text element here, so your code should look like
<xsl:for-each select="News/Sport">
<xsl:if test="Basketball">
<p>Basketball Sport</p>
<xsl:value-of select="Basketball/Phrases/Phrase"/>
</xsl:if>
</xsl:for-each>
You can refine this further by adding a predicate to the for-each selection, so that it matches only Sport elements with a Basketball child. Like this
<xsl:for-each select="News/Sport[Basketball]">
<p>Basketball Sport</p>
<xsl:value-of select="Basketball/Phrases/Phrase"/>
</xsl:for-each>

Related

Attempts to use following-sibling to convert

I try to convert my old html by xslt-script to my new xml stucture.
I have a Problem to converting the folowing source to my needed xml structure.
Source
<p>
<a class="DropDown">Example Text</a>
</p>
<div class="collapsed">
<table>..</table>
<p>..</p>
</div>
xml structure
<lq>
<p>Example Text</p>
<table>..</table>
<p>..</p>
</lp>
I tried the following xls, but the div class="collapsed" is not adopted into the lp tag.
<xsl:template match="p/a[#class='DropDown']">
<lp>
<p><xsl:apply-templates select="text()"/></p>
<xsl:if test="/p/a/following-sibling::*[1][self::div]">
<xsl:apply-templates select="*|text()"/>
</xsl:if>
</lp>
</xsl:template>
Can anyone tell me what I did wrong ore where the mistake is?
Thanks much
IMHO, you want to do:
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p[a/#class='DropDown']">
<lp>
<p>
<xsl:value-of select="a"/>
</p>
<xsl:copy-of select="following-sibling::*[1][self::div]/node()"/>
</lp>
</xsl:template>
<xsl:template match="div[preceding-sibling::*[1][self::p/a/#class='DropDown']]"/>
As for your mistake:
You are testing the existence of some p that is the root element
and contains an a whose following sibling is div. None of these are true in the given example;
xsl:if does not change the context: your <xsl:apply-templates
select="*|text()"/> applies templates to the child nodes of the
current a;
Presumably you don't want the div to appear again in the original place -
so if you have another template to suppress it, you cannot use
<xsl:apply-templates> to insert it at the place you do want it -
at least not without using another mode.

optimization of XSLT code

While searching for some profiling tools for XSLT, I came across this post. Since a lot of people there suggested to just post the code and offered to give feedback on that, I was wondering if anyone could give me some feedback on mine. I tried this (http://www.saxonica.com/documentation/#!using-xsl/performanceanalysis), but the output html is not very detailed.
I'm new to XSLT and usually work with python/perl, where regex support is much better (however, I won't rule out the possibility that it's just my very basic understanding of XSLT). For the purpose of this project however, I had to work with XSLT. It could be that I'm forcing it to do things in a very unnatural way. Any comments -on performance in particular, but anything else is also welcome, as I'd like to learn- are welcome!
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:template name="my_terms">
<xsl:variable name="excludes" select="not (codeblock or draft-comment or filepath or shortdesc or uicontrol or varname)"/>
<!-- leftover example of how to work with excludes var -->
<!--<xsl:if test=".//*[$excludes]/text()[contains(.,'access management console')]"><li class="prodterm"><b>PB QA:access management console should be "AppCenter"</b></li></xsl:if>-->
<!-- Loop through all sentences and check for deprecated stuff -->
<xsl:for-each select=".//*[$excludes]/text()">
<xsl:variable name="sentenceList" select="tokenize(., '[\.!\?:;]\s+')"/>
<xsl:variable name="segment" select="."/>
<!-- main sentence loop -->
<xsl:for-each select="$sentenceList">
<xsl:variable name="sentence" select="."/>
<!-- very rudimentary sentence length check -->
<xsl:if test="count(tokenize(., '\W+')) > 30"> <li class="prodterm"><b>Sentence too long:</b> <xsl:value-of select="."/></li></xsl:if>
<!-- efforts to flag the shady case of the gerund -->
<xsl:if test="matches(., '\w+ \w+ing (the|a)')">
<!-- some extra checks to weed out the false positives -->
<xsl:if test="not(matches(., '\b(on|about|for|before|while|when|after|by|a|the|an|some|all|every) \w+ing (the|a)', '!i')) and not(matches(., 'during'))">
<li class="prodterm"><b>Possible unclear usage of gerund. If so, consider rewriting:</b> <xsl:value-of select="."/></li>
</xsl:if>
</xsl:if>
<!-- comma's after certain starting phrases -->
<xsl:if test="matches(., '^\s*Therefore[^,]')"><li class="prodterm"><b>Use a comma after starting a sentence with 'Therefore':</b> <xsl:value-of select="."/></li></xsl:if>
<xsl:if test="matches(., '^\s*(If you|Before|When)[^,]+$')"><li class="prodterm"><b>Use a comma after starting a sentence with 'Before', 'If you' or 'When':</b> <xsl:value-of select="."/></li></xsl:if>
<!-- experimenting with phrasal verbs (if there are a lot of verbs in phrasalVerbs.xml, it will be better to have this as the main loop (and do it outside the sentence loop)) -->
<xsl:for-each select="document('phrasalVerbs.xml')/verbs/verb[matches($sentence, concat('.* ', ./#text, ' .*'))]">
<xsl:variable name="verbPart" select="."/>
<xsl:for-each select="$verbPart/particles/particle/#text[matches($sentence, .) and not(matches($sentence, concat($verbPart/#text, ' ', .)))]">
<xsl:variable name="particle" select="."/>
<li class="prodterm"><b>Separated phrasal verb found in:</b> <xsl:value-of select="$sentence"/></li>
</xsl:for-each>
</xsl:for-each>
<!-- checking if conditionals (should be followed by then) -->
<xsl:if test="matches($sentence, '^\s*If\b', '!i') and not(matches($sentence, '\bthen\b', '!i'))"><li class="prodterm"><b>Conditional If found, but no then:</b> <xsl:value-of select="."/></li></xsl:if>
<!-- very dodgy way of detecting passive voice -->
<!--<xsl:if test="matches($sentence, '\b(are|can be|must be) \w+ed\b', '!i')"><li class="prodterm"><b>PB QA:Possible passive voice. If so, consider using active voice for:</b> <xsl:value-of select="."/></li></xsl:if>-->
<xsl:for-each select='document("generalDeprecatedTermsAndPhrases.xml")/terms/dt'>
<xsl:variable name="pattern" select="./#pattern"/>
<xsl:variable name="message" select="./#message"/>
<xsl:variable name="regexFlag" select="./#regexFlag"/>
<!-- <xsl:if test="matches($sentence, $pattern, $regexFlag)"> -->
<xsl:if test="matches($sentence, concat('(^|\W)', $pattern, '($|\W)'), $regexFlag)"> <!-- This is the work around for not being able to use \b when variable is passed on inside matches() -->
<li class="prodterm"><b><xsl:value-of select="$message"/> in: </b> <xsl:value-of select="$sentence"/> </li>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
To get an idea, the stripped down version of my "generalDeprecatedTermsAndPhrases.xml" looks like this:
<dt pattern='to be able to' message="Use 'to' instead of 'to be able to'" regexFlag="i"></dt>
</terms>
The reason that Saxon's profile is not very detailed is that your code is so monolithic: it's all in one great big template rule.
However, being monolithic isn't by itself the cause of any performance problems.
First observation is a functionality problem: your variable
<xsl:variable name="excludes" select="not (codeblock or draft-comment or filepath or shortdesc or uicontrol or varname)"/>
doesn't do what you think. It's evaluated with the root document node as the context item, and its value is a boolean which is true if the outermost element has a name which is not one of those listed. So I think your xsl:for-each that uses [$excludes] as a predicate is applying to all elements, whereas I suspect you intended it to apply to selected elements. I don't know how much that affects the performance.
The main influence on performance will be the cost of evaluating the regular expressions. The best way to find out which ones are causing the problem is to measure the impact of removing them one-by-one. When you've isolated the problem, there may be a way of rewriting the regular expression to make it perform better (e.g. by making it avoid backtracking).

Preceding sibling in XSLT

I am trying to put a specific condition on the basis of preceding sibling check.
I tried various options but nothing worked.
Here is the sample XML
<abc>
<title>something</title>
<element>1</element>
<element>2</element>
<element>3</element>
<element>4</element>
</abc>
I have a template match for <element> tag and I want to check if its immediate element is <title> Do some additioanl processing else do some other processing. Any pointers appriciated.
<xsl:template match="element">
I am boring. <xsl:value-of select="." />
</xsl:template>
<xsl:template match="element[preceding-sibling::*[1][self::title]]">
I am special. <xsl:value-of select="." />
</xsl:template>
If the current context is an element element then you can get its nearest preceding sibling element (regardless of name) using
preceding-sibling::*[1]
and so to check whether that element is a title you can use
preceding-sibling::*[1][self::title]
<xsl:template match="element[preceding-sibling::element()[1][self::title]]"/>

XSLT to process XML with very loose standards (EAD)

I've been having a hell of a week trying to write XSLT code that can process XML documents that conform to the (very permissive) EAD standards.
The useful information in an EAD document is hard to locate precisely. Different EAD documents can place the same bit of information in entirely different parts of the data tree. In addition, within a single EAD document, the same tag can be used numerous times in different locations for different information. For an example of this, please see this SO post. This makes it hard to design a single XSLT file that properly handles these different files.
In general terms, the problem can be described as:
How do I select a specific EAD node which is in an unknown location,
Without accidentally selecting unwanted nodes that have the same name()?
I've finally put together the XSLT I needed and thought it would be best to drop a generic version of the code here so others can benifit from it or improve upon it.
I'd love to tag this question with an "EAD" tag, but I don't have enough rep. If anyone with the appropriate amount of rep thinks it would be useful, please do so.
First a quick description of the solution, followed by the code.
Check if this EAD document contains component (child) records (designated with a <cXX>). If not, we don't have to worry about duplicate EAD tags. The tags can still be burried under arbitrary wrappers. To find them, see step 3.
If child records exist, be careful to not process the <dsc> tag until other tags are processed. To find the other tags, see step 3, then step 4 to process child records.
Recurse through the various wrappers with a template that matches them and calls apply-template on any element node farther down the tree.
We are now processing a child record. Do this by repeating step 2 (carefully process all other tags before tackling the children of this child record), then step 4.
Here's the (generic version of the) XSLT code I came up with:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="ISO-8859-1" indent="yes"/>
<xsl:template match="/ead">
<records>
<xsl:if test="//dsc">
<!-- if there are <cXX> nodes, we'll handle the main record differently.
<cXX> nodes are always found in the 'dsc' node, which contains nothing else -->
<xsl:call-template name="carefully_process"/>
</xsl:if>
<xsl:if test="not(//dsc)">
<record>
<!-- Just process the existing nodes -->
<xsl:apply-templates select="*"/>
</record>
</xsl:if>
</records>
</xsl:template>
<xsl:template name="carefully_process">
<!-- first we'll process all the nodes for the main
record. Then we'll call the child records -->
<record>
<!-- have to be careful not to process //archdesc/dsc yet -->
<xsl:apply-templates select="*[not(self::archdesc)]"/>
<xsl:apply-templates select="archdesc/*[not(self::dsc)]"/>
<!-- Now we can close off the master record, -->
</record>
<!-- and process the child records -->
<xsl:apply-templates select="/ead/archdesc/dsc"/>
</xsl:template>
<xsl:template match="dsc">
<!-- Start processing the child records (we use for-each to get a good position() -->
<xsl:for-each select="*[starts-with(name(),'c0') or starts-with(name(),'c1') or name() = 'c']">
<xsl:apply-templates select=".">
<!-- we pass the unittitle and unitid of the master record, so that child
records can be linked to it. We pass the position of the child so that
a unitid can be created if it doesn't exist -->
<xsl:with-param name="partitle" select="normalize-space(/ead/archdesc/did/unittitle)"/>
<xsl:with-param name="parid" select="normalize-space(/ead/archdesc/did/unitid)"/>
<xsl:with-param name="pos" select="position()"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<!-- process child nodes -->
<xsl:template match="*[starts-with(name(),'c0') or starts-with(name(),'c1') or name() = 'c']" >
<xsl:param name="partitle"/>
<xsl:param name="parid"/>
<xsl:param name="pos"/>
<!-- start this child record -->
<record>
<!-- EAD does not require a unitid, but my code does.
If it doesn't exist, create it -->
<xsl:if test="not(./did/unitid)">
<atom name="unitid">
<xsl:value-of select="$parid"/><xsl:text>-</xsl:text><xsl:value-of select="$pos"/>
</atom>
</xsl:if>
<!-- get the level of this component -->
<atom name="eadlevel">
<xsl:value-of select="concat(translate(substring(#level,1,1),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ'),substring(#level,2))"/>
</atom>
<!-- Do *something* to attach this record to it's parent.
Probably involves $partitle and $parid. For example: -->
<ref>
<atom name="unittitle"><xsl:value-of select="$partitle"/></atom>
<atom name="unitid"><xsl:value-of select="$parid"/></atom>
</ref>
<!-- now process all the other nodes -->
<xsl:apply-templates select="*[not(starts-with(name(),'c0') or starts-with(name(),'c1') or name() = 'c')]"/>
<!-- finish this child record -->
</record>
<!-- prep the variables we'll need for attaching any child records (<cXX+1>) to this record -->
<xsl:variable name="this_title">
<xsl:value-of select="normalize-space(./did/unittitle)"/>
</xsl:variable>
<xsl:variable name="this_id">
<xsl:if test="./did/unitid">
<xsl:value-of select="./did/unitid"/>
</xsl:if>
<xsl:if test="not(./did/unitid)">
<xsl:value-of select="$parid"/><xsl:text>-</xsl:text><xsl:value-of select="$pos"/>
</xsl:if>
</xsl:variable>
<!-- now process the children of this node -->
<xsl:for-each select="*[starts-with(name(),'c0') or starts-with(name(),'c1') or name() = 'c']">
<xsl:apply-templates select=".">
<xsl:with-param name="partitle" select="$this_title"/>
<xsl:with-param name="parid" select="$this_id"/>
<xsl:with-param name="pos" select="position()"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<!-- these are usually just wrappers. Go one level deeper -->
<xsl:template match="descgrp|eadheader|revisiondesc|filedesc|titlestmt|profiledesc|archdesc|archdescgrp|daogrp|langusage|did|frontmatter">
<xsl:apply-templates select="*"/>
</xsl:template>
<!-- below this point, add templates for processing specific EAD units
of information. For example, the template might look like
<xsl:template match="titleproper">
<atom name="titleproper">
<xsl:value-of select="normalize-space(.)"/>
</atom>
</xsl:template>
-->
<!-- instead of having a template for each EAD information unit, consider
a generic template that handles them all the same way. For example:
-->
<xsl:template match="*">
<atom>
<xsl:attribute name="name"><xsl:value-of select="name()"/></xsl:attribute>
<xsl:value-of select="normalize-space(.)"/>
</atom>
</xsl:template>
</xsl:stylesheet>

Hyperlinks within XSLT Templates

I am trying to create hyperlinks using XML information and XSLT templates. Here is the XML source.
<smartText>
Among individual stocks, the top percentage gainers in the S. and P. 500 are
<smartTextLink smartTextRic="http://investing.domain.com/research/stocks/snapshot
/snapshot.asp?ric=HBAN.O">Huntington Bancshares Inc</smartTextLink>
and
<smartTextLink smartTextRic="http://investing.domain.com/research/stocks/snapshot
/snapshot.asp?ric=EK">Eastman Kodak Co</smartTextLink>
.
</smartText>
I want the output to look like this, with the company names being hyperlinks based on the "smartTextLink" tags in the Xml.
Among individual stocks, the top percentage gainers in the S.&P. 500 are Eastman Kodak Co and Huntington Bancshares Inc.
Here are the templates that I am using right now. I can get the text to display, but not the hyperlinks.
<xsl:template match="smartText">
<p class="smartText">
<xsl:apply-templates select="child::node()" />
</p>
</xsl:template>
<xsl:template match="smartTextLink">
<a>
<xsl:apply-templates select="child::node()" />
<xsl:attribute name="href">
<xsl:value-of select="#smartTextRic"/>
</xsl:attribute>
</a>
</xsl:template>
I have tried multiple variations to try to get the hyperlinks to work correctly. I am thinking that the template match="smartTextLink" is not being instantiated for some reason. Does anyone have any ideas on how I can make this work?
EDIT: After reviewing some of the answers, it is still not working in my overall application.
I am calling the smartText template from within my main template
using the following statement...
<xsl:value-of select="marketSummaryModuleData/smartText"/>
Could this also be a part of the problem?
Thank you
Shane
Either move the xsl:attribute before any children, or use an attribute value template.
<xsl:template match="smartTextLink">
<a href="{#smartTextRic}">
<xsl:apply-templates/>
</a>
</xsl:template>
From the creating attributes section of the XSLT 1 spec:
The following are all errors:
Adding an attribute to an element after children have been added to it; implementations may either signal the error or ignore the attribute.
Try this - worked for me:
<xsl:template match="smartText">
<p class="smartText">
<xsl:apply-templates/>
</p>
</xsl:template>
<xsl:template match="smartTextLink">
<a>
<xsl:attribute name="href">
<xsl:value-of select="#smartTextRic"/>
</xsl:attribute>
<xsl:value-of select="text()"/>
</a>
</xsl:template>
Trick is - <xsl:attribute> first, before you do any other processing.
Marc