How to select name and get value from <ul><li> with xpath? - xpath

I'm looking for an easier way to select a value after a name in an html ul li string.
Data is:
<xsl:value-of select="custom_options/custom_option/value" />
Result: <ul><li><strong>Breedte (mm):</strong> 2080</li><li><strong>Hoogte (mm) incl. Kast:</strong> 1420</li><li><strong>Kastmaat:</strong> 150</li><li><strong>Type kast:</strong> Afgeschuind</li><li><strong>Kastkleur:</strong> RAL 6009 Spargroen</li></ul>
Now I use a substring solution.
<xsl:value-of select="substring-before(substring-after(custom_options/custom_option/value, 'Breedte (mm):</strong>'), '</li>')"/>
<xsl:value-of select="substring-before(substring-after(custom_options/custom_option/value, 'Type kast:</strong>'), '</li>')"/>
I would like an xpath solution like:
<xsl:value-of select="#Breedte (mm):" /> 2080
<xsl:value-of select="#Type kast:" /> Afgeschuind
I am using xsl 1.0

I would use this:
<xsl:value-of select="//ul/li/text()[preceding-sibling::strong='Breedte (mm):']"/>
and this:
<xsl:value-of select="//ul/li/text()[preceding-sibling::strong='Type kast:']"/>

Related

How to match the case and apply templates

I've the below XML line of code.
<title><page>651</page>CHAPTER 13 This is <content-style font-style="italic">This goes in content-style</content-style> The title</title>
Here i'm trying to do the below.
Get the number after CHAPTER and concat it with Chapter, i'm able to do it with the below code.
<xsl:value-of select="concat('Chapter ', substring-before(substring-after(child::title,' '),' ')"/>
ignore the page in the title, and i use the below template match and able to do it.
<xsl:template match="title/page"/>
If there is just plain data, i'm using the below to get it.
<xsl:value of select ="substring-after(substring-after(./title,' '),' ')">
But the problem came in the above type, here i need to apply templates on substring-after(substring-after(.,' '),' ') and unfortunately this is not working.
I have the below XSLT
<xsl:template match ="title">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="content-style">
<xsl:variable name="fontStyle">
<xsl:value-of select="concat('font-style-',#font-style)"/>
</xsl:variable>
<span class="{$fontStyle}">
<xsl:value-of select="."/>
<xsl:apply-templates select="para"/>
</span>
</xsl:template>
expected O/P
<div class="chapter-title">
<span class="chapter-num">Chapter 13</span><br /><br /> This is <span class="font-style-italic">This goes in content-style</span> the title</span></div>
Can you please let me know how can i do this.
Thanks
Your expected output has an unmatched span end tag.
Ignoring that, I would do something like:
<xsl:template match ="title">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="content-style">
<span class="font-style-{#font-style}">
<xsl:value-of select="."/>
</span>
</xsl:template>
<xsl:template match="title/text()">
<xsl:analyze-string select="." regex="Chapter [0-9]+">
<xsl:matching-substring>
<span class="chapter-num">
<xsl:value-of select="."/>
</span><br/>>br/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:-nonmatching-substring>
</xsl:analyze-string>
</xsl:template>

Sorting xslt nodes based on sibling

I am having some trouble getting this to work and I could really need some help.
I want to store the smallest variable that has a sibling which is not null.
I have a XML which looks like this:
<Root>
<Item>
<ValueOne>5</ValueOne>
<Item>bababa</Item>
</Item>
<Item>
<ValueOne>3</ValueOne>
<Item>ababa</Item>
</Item>
<Item>
<ValueOne>1</ValueOne>
<Item/>
</Item>
</Root>
I want the smallest ValueOne who's sibling Item has a value.
I was thinking something like:
<xsl:variable name="var">
<xsl:for-each select="Root/Item">
<xsl:if test="not(/Item = '')">
<xsl:sort data-type="text"/>
<xsl:if test="position()=1">
<xsl:value-of select="/Item"/>
</xsl:if>
</xsl:if>
</xsl:for-each>
</xsl:variable>
But this doesn't work and I am not sure why. As you might tell I'm not the best at xslt. I cannot rename anything in the xml or change the structure of it.
Assuming those spaces and returns are non-essential, you could use:
<xsl:variable name="var">
<xsl:for-each select="Root/Item[normalize-space(Item)]">
<xsl:sort select="ValueOne" data-type="number" order="ascending"/>
<xsl:if test="position()=1">
<xsl:value-of select="normalize-space(ValueOne)"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
With all the redundant white space removed, this can be simplified to:
<xsl:variable name="var">
<xsl:for-each select="Root/Item[Item/text()]">
<xsl:sort select="ValueOne" data-type="number" order="ascending"/>
<xsl:if test="position()=1">
<xsl:value-of select="ValueOne"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>

Set Union Operator in xpath

<xsl:variable name="targetReceiverService">
<EMP_EMPLOC_MAL curr="4.0">MAL</EMP_EMPLOC_MAL>
<EMP_EMPLOC_SIN curr="1.6">SIN</EMP_EMPLOC_SIN>
<EMP_EMPLOC_CHN curr="7.8">CHN</EMP_EMPLOC_CHN>
<DEFAULT curr="1.0">NONE</DEFAULT>
</xsl:variable>
<xsl:variable name="targetCountryCode" select="$targetReceiverService/*[name() = $ReceiverService] | $targetReceiverService/DEFAULT"/>
<xsl:value-of select="$targetCountryCode "/>
why value display for $targetCountryCode is only MAL but not included NONE since the "|" mean
xsl:value-of only displays the value of the first node in the set. (at least with XSL 1)
You can probably display all of them with
<xsl:for-each select="$targetCountryCode">
<xsl:value-of select="."/>
</xsl:for-each>

XSLT - How to speed up a complex for-each

I am new to XSLT and i'm having a few speed issues with the following for-each statement. I was hoping someone could give me some pointers as how to optimise this please?
The for-each below is looping through about 4mb of XML. It is testing to ensure that each hotel node has a description and a destination. It is also testing that each hotel has a rating greater than 2 but not 6. The possible values for the rating in the XML are 0, 1, 2, 3, 4, 5 or 6. Ideally i would like it to only select ratings 3, 4 or 5 and ignore the others.
<for-each select="response/results/hotel[
not(#description = '') and
#rating > '2' and
not(#rating = '6') and
not(#destination = '') ]">
<call-template name="hotelparams"/>
<call-template name="upropdata"/>
<call-template name="request"/>
<call-template name="Newline"/>
</for-each>
As request I have added the templates that are being called below. The output is creating tab delimited text files which are then imported in mySQL. By the way please ignore the upropdata template, it will be removed shortly...
<xsl:template name="hotelparams">
<xsl:value-of select="#itemcode"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#cheapestcurrency"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#cheapestprice"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#checkin"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#checkout"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#description"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#destair"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#destination"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#destinationid"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#engine"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#hotelname"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#image"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#nights"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#rating"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#resultkey"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#resultno"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#supplierdestination"/><xsl:value-of select="$tab"/>
<xsl:value-of select="#type"/></xsl:template>
<xsl:template name="upropdata">
<xsl:value-of select="$tab"/>\N<xsl:value-of select="$tab"/>\N<xsl:value-of select="$tab"/>\N<xsl:value-of select="$tab"/>\N<xsl:value-of select="$tab"/>\N<xsl:value-of select="$tab"/>2011-01-01</xsl:template>
<xsl:template name="request">
<xsl:for-each select="/response/request/method"><xsl:value-of select="$tab"/><xsl:value-of select="./#sessionkey"/></xsl:for-each></xsl:template>
<xsl:template name="Newline">
<xsl:text>
</xsl:text></xsl:template>
How about ...
<xsl:for-each select="response/results/hotel
[not(#description = '')]
[#rating = (3,4,5)]">
<xsl:call-template name="hotelparams"/>
<xsl:call-template name="upropdata"/>
<xsl:call-template name="request"/>
<xsl:call-template name="Newline"/>
</xsl:for-each>
Note: I have not included a check for destination, because you did not specify its node name.
Also, if you can eliminate the possibility of empty description attributes (that is to say hotels will have a non empty description or no description attribute at all), then you can use this slightly abbreviated form...
<xsl:for-each select="response/results/hotel
[not(#description)]
[#rating = (3,4,5)]">
<xsl:call-template name="hotelparams"/>
etc...
</xsl:for-each>
Also note, an alternate form for the second predicate would be...
[#rating = (3 to 5)]
One could write...
[(#rating > 2) and (#rating < 6)]
or
[#rating > 2][#rating < 6]
... but I suspect that this would be less efficient, because #rating would have to be fetched twice.
The for-each below is looping through about 4mb of XML. It is testing
to ensure that each hotel node has a description and a destination. It
is also testing that each hotel has a rating greater than 2 but not 6.
The possible values for the rating in the XML are 0, 1, 2, 3, 4, 5 or
6. Ideally i would like it to only select ratings 3, 4 or 5 and ignore the others.
<for-each select="response/results/hotel[
not(#description = '') and
#rating > '2' and
not(#rating = '6') and
not(#destination = '') ]">
<call-template name="hotelparams"/>
<call-template name="upropdata"/>
<call-template name="request"/>
<call-template name="Newline"/>
</for-each>
I believe that the reason for the performance problem is in the templates that are being called (and not provided in the question) -- not in the xsl:for-each itself.
It can be re-written in different alternative ways, but the performance gains would be minimal (milliseconds), if any at all.
Do note, that the provided code doesn't check for the existence of a #destination attribute at all. Any hotel element that satisfies the other conditions, but has no destination attribute is selected.
Exactly the same is true for the description attribute.
One correct way of specifying the xsl:for-each is:
<xsl:for-each select="response/results/hotel[
string(#description)
and
#rating > 2
and
not(#rating > 5)
and
string(#destination)
]">
<xsl:call-template name="hotelparams"/>
<xsl:call-template name="upropdata"/>
<xsl:call-template name="request"/>
<xsl:call-template name="Newline"/>
</xsl:for-each>
Update:
The OP has now provided the code of the called templates.
I will use the following for the hotelparams template:
<xsl:sequence select=
"string-join
(
(#itemcode,
#cheapestcurrency,
#cheapestprice,
#checkin,
#checkout,
#description,
#destair,
#destination,
#destinationid,
#engine,
#hotelname,
#image,
#nights,
#rating,
#resultkey,
#resultno,
#supplierdestination,
#type),
$tab
)
"/>
I would replace the template upropdata with:
this code:
<xsl:sequence select="' \N \N \N \N \N2011-01-01'"/>
Or, if $tab really can be something different than , I will calculate this only once and place the result in a global variable:
<xsl:variable name="vUPropData" select=
"concat($tab,'\N',$tab,'\N',$tab,'\N'$tab,'\N',$tab,'\N2011-01-01')"/>
and then just have:
<xsl:sequence select="$vUPropData"/>
I would replace the request template with:
this code:
<xsl:sequence select=
"concat($tab,string-join(/response/request/method/#sessionkey, $tab))"/>
As this doesn't depend on any context node (is an absolute expression), I would calculate this only once and put it in a global variable (as in the previous case) and only reference this global variable.
Finally, it is not meaningful to generate the same single character in a named template. I will replace the Newline template with a global variable or with a global parameter.
I believe that after this refactoring, the code might execute significantly faster.

is there a way to emulate correctly replace function on XPATH 1?

I have this function which tries to replace dots and/or - with _
I'm limited to use xpath 1 so replace function is NOT an option. The template works not to much fine because if I use something like this:
FOO-BAR.THING-MADRID.html
it gives me out on screen this thing:
FOO-BAR.THING-MADRID.html
the middle dot is not replaced.
Someone could help me?
<xsl:template name="replaceDots">
<xsl:param name="outputString"/>
<xsl:variable name="target">.</xsl:variable>
<xsl:variable name="source">-</xsl:variable>
<xsl:variable name="replacement">_</xsl:variable>
<xsl:choose>
<xsl:when test="contains($outputString,$source)">
<xsl:value-of select="concat(substring-before($outputString,$source),$replacement)" disable-output-escaping="yes"/>
<xsl:call-template name="replaceDots">
<xsl:with-param name="outputString" select="substring-after($outputString,$source)"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($outputString,$target)">
<xsl:value-of select="concat(substring-before($outputString,$target),$replacement)" disable-output-escaping="yes"/>
<xsl:call-template name="replaceDots">
<xsl:with-param name="outputString" select="substring-after($outputString,$target)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$outputString" disable-output-escaping="yes"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
To replace all dots or dashes with underscores, you do not need an <xsl:template>. You can use:
<xsl:value-of select="translate(., '-.', '__')" />
If you want to keep the ".html", you can extend this like so:
<xsl:value-of select="
concat(
translate(substring-before(., '.html'), '-.', '__'),
'.hmtl'
)
" />
For a generic "string replace" template in XSLT, look at this question, for example.

Resources