Xpath How to test if self is the first child element of parent? - xpath

I have this XML fragment:
<seg><lb break="y" n="5">Curabitur eget lectus laoreet, facilisis ante in, suscipit nisl. Nulla facilisi. Vivamus in ullamcorper risus.<lb break="y" n="6"/>Duis arcu neque, tincidunt quis pulvinar non, pretium sed quam. Maecenas vitae felis<lb break="y" n="7"/>sed diam tempor porta hendrerit non eros. Vestibulum efficitur turpis eu<lb break="y" n="8"/>odio imperdiet. Quisque feugiat tincidunt ex.</seg>
In XSL 3.0 I want to replace the <lb> elements with   only if the <lb> is not the first child element of <seg>.
I know that this doesn't work (but it gets the idea across):
<xsl:template match="lb">
<xsl:choose>
<xsl:when test="#break='y' and ./parent::seg[/node()[1] != .]"> </xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:template>
Yet I don't know how to rewrite it to perform the test needed.
Demo here.
Many thanks in advance.

You could use these two templates:
<!-- check for the first node() and if it is a lb with #break='y', skip it -->
<xsl:template match="seg/node()[1][self::lb[#break='y']]"/>
<!-- check for every following node() and if it is a lb with #break='y', use > -->
<xsl:template match="seg/node()[position() gt 1][self::lb[#break='y']]">
<xsl:text> </xsl:text>
</xsl:template>

You can use the preceding-sibling axis to see if the lb is the first child element of its parent. If the lb is the first child element of its parent then the preceding-sibling axis must have no elements on it, i.e. preceding-sibling::* is the empty set.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="/seg"/>
</xsl:template>
<xsl:template match="seg">
<p><xsl:apply-templates/></p>
</xsl:template>
<!-- possibly the test below should be "preceding-sibling::lb" ? -->
<xsl:template match="seg/lb[#break='y' and preceding-sibling::*]"> </xsl:template>
</xsl:stylesheet>

Related

XSL - store unique and sorted data in a variable

Using XSLT 2.0 and Apache FOP I want to be able to create a new variable, have unique and sorted values inside it by category but preserve the nodes. So the new variable should have the following nodes:
<category>1. First Aid</category>
<category>2. Access control</category>
<category>3. Fire safety</category>
<category>4. Recognition</category>
The input XML is the following:
<equipment>
<E0132>
<category>1. First Aid</category>
<description>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</description>
</E0132>
<E0133>
<category>1. First Aid</category>
<description>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</description>
</E0133>
<E4122>
<category>3. Fire safety</category>
<description>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</description>
</E4122>
<E4182>
<category>3. Fire safety</category>
<description>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</description>
</E4182>
<E4622>
<category>2. Access control</category>
<description>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</description>
</E4622>
<E5225>
<category>4. Recognition</category>
<description>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</description>
</E5225>
</equipment>
In regard to the XSL, this is what I have so far:
<xsl:variable name="equipment">
<xsl:for-each-group select="//equipment/node()" group-by="category">
<xsl:sort select="." order="ascending" />
<xsl:value-of select="."/>
</xsl:for-each-group>
</xsl:variable>
But it's not working as expected. It doesn't contain the category nodes as I would like to and I don't know how to integrate distinct-values() XSL function here in order to achieve unicity.
You can use the current-grouping-key() function to store the values. Below is the updated variable declaration.
<xsl:variable name="equipment">
<xsl:for-each-group select="//equipment/*/category" group-by=".">
<xsl:sort select="." order="ascending" />
<category>
<xsl:value-of select="current-grouping-key()"/>
</category>
</xsl:for-each-group>
</xsl:variable>
To check the variable contents
<xsl:copy-of select="$equipment" />
gives output as
<category>1. First Aid</category>
<category>2. Access control</category>
<category>3. Fire safety</category>
<category>4. Recognition</category>
EDIT: To print the variable values within a loop, try the below
<!-- print variable values -->
<xsl:for-each select="$equipment/category" >
<xsl:value-of select="." />
<xsl:text>
</xsl:text>
</xsl:for-each>
Output
1. First Aid
2. Access control
3. Fire safety
4. Recognition

Sorting in xslt does not work

I am trying to sort the xml based on the field value person_id_external.
The code which I am using is:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="A/B">
<xsl:copy>
<xsl:apply-templates>
<xsl:sort select="C/person_id_external" order="ascending" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The payload is:
<A>
<B>
<C>
<logon_user_name>10027</logon_user_name>
<person_id>1100111</person_id>
<person_id_external>10027</person_id_external>
</C>
</B>
<B>
<C>
<logon_user_name>428122</logon_user_name>
<person_id>11141</person_id>
<person_id_external>111358</person_id_external>
</C>
</B>
<B>
<C>
<logon_user_name>428122</logon_user_name>
<person_id>100441</person_id>
<person_id_external>10636</person_id_external>
</C>
</B>
</A>
The result provides a copy of the input but does not sort.
Expected result is :
<A>
<B>
<C>
<logon_user_name>10027</logon_user_name>
<person_id>1100111</person_id>
<person_id_external>10027</person_id_external>
</C>
</B>
<B>
<C>
<logon_user_name>428122</logon_user_name>
<person_id>11141</person_id>
<person_id_external>10636</person_id_external>
</C>
</B>
<B>
<C>
<logon_user_name>428122</logon_user_name>
<person_id>100441</person_id>
<person_id_external>111358</person_id_external>
</C>
</B>
</A>
Cheers,
Vikcy
-- edited in response to your edit --
In your example, each B node has only one C node. Therefore, you must sort the B nodes in order to get the expected result - and you must do so from the context of their parent A:
<xsl:template match="A">
<xsl:copy>
<xsl:apply-templates>
<xsl:sort select="C/person_id_external" order="ascending"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
Note that the default sort data-type is text (i.e. alphabetical).
The below code works:
<xsl:template match="A">
<xsl:copy>
<xsl:apply-templates>
<xsl:sort select="C/person_id_external" data-type="number" order="ascending"/>
</xsl:apply-templates>
</xsl:copy>
The incoming payload had the type as number.
Cheers,
Vikas Singh

Breaking a lengthy row into multiple rows using Xpath expression in Altova Stylevision's Autocalc functionality

I have a XML row which has a lengthy sentence for instance:
<ROW>4.)Whatever universe a professor believes in must at any rate be a universe that lends itself to lengthy discourse. A universe definable in two sentences is something for which the professorial intellect has no use. No faith in anything of that cheap kind!
I need to break the sentence into smaller sentences and display them in multiple lines in Altova Stylevision.
Iam using Autocalc to display the contents in row dynamically. Is there any Xpath expression which I can use to split the row into multiple rows based on the number of words or number of characters in each line.So that I can display the row as:
Whatever universe a professor believes in must at any rate
be a universe that lends itself to lengthy discourse.
and so on.
It seems you are trying to break your string input into multiple pieces (word/String length). I have created a sample for you to make it easy. Please go through and check if it can help you:
input xml
<?xml version="1.0" encoding="UTF-8"?>
<ROW>4.)Whatever universe a professor believes in must at any rate be a universe that lends itself to lengthy discourse. A universe definable in two sentences is something for which the professorial intellect has no use. No faith in anything of that cheap kind!11</ROW>
XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="ROW">
<output>
<xsl:variable name="Length" select="string-length(.)"/>
<xsl:variable name="Words" select="count(tokenize(.,'\s'))"/>
<xsl:variable name="ByLength" select="50"/>
<xsl:variable name="ByWord" select="5"/>
<!-- To get ouput by words -->
<xsl:if test="$ByWord">
<xsl:variable name="Result">
<xsl:call-template name="GetBreakingWords">
<xsl:with-param name="Value" select="."/>
<xsl:with-param name="ByWord" select="$ByWord"/>
<xsl:with-param name="Words" select="$Words"/>
</xsl:call-template>
</xsl:variable>
<p>By word example</p>
<xsl:for-each select="$Result/row">
<xsl:sort select="#id" data-type="number"/>
<!--<row><xsl:value-of select="."/></row>-->
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:if>
<!-- To get ouput by string length -->
<xsl:if test="$ByLength">
<xsl:variable name="Result">
<xsl:call-template name="GetBreakingStrings">
<xsl:with-param name="Value" select="."/>
<xsl:with-param name="ByLength" select="$ByLength"/>
<xsl:with-param name="Length" select="$Length"/>
</xsl:call-template>
</xsl:variable>
<p>By String example</p>
<xsl:for-each select="$Result/row">
<xsl:sort select="#id" data-type="number"/>
<!--<row><xsl:value-of select="."/></row>-->
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:if>
</output>
</xsl:template>
<xsl:template name="GetBreakingWords">
<xsl:param name="ByWord"/>
<xsl:param name="Value"/>
<xsl:param name="Words"/>
<xsl:param name="counter" select="1"/>
<xsl:choose>
<xsl:when test="($Words gt $ByWord)">
<xsl:variable name="CurrentRow" select="normalize-space(string-join(for $x in tokenize($Value, '\s')[position() ge 1 and position() le $ByWord] return $x, ' '))"/>
<xsl:call-template name="GetBreakingWords">
<xsl:with-param name="ByWord" select="$ByWord"/>
<xsl:with-param name="Words" select="count(tokenize(substring-after($Value, $CurrentRow),'\s'))"/>
<xsl:with-param name="Value" select="substring-after($Value, $CurrentRow)"/>
<xsl:with-param name="counter" select="sum($counter + 1)"></xsl:with-param>
</xsl:call-template>
<row id="{$counter}"><xsl:value-of select="$CurrentRow"/></row>
</xsl:when>
<xsl:when test="not($Words gt $ByWord) and $Value">
<row id="{$counter}"><xsl:value-of select="$Value"/></row>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="GetBreakingStrings">
<xsl:param name="ByLength"/>
<xsl:param name="Value"/>
<xsl:param name="Length"/>
<xsl:param name="counter" select="1"/>
<xsl:choose>
<xsl:when test="($Length gt $ByLength)">
<xsl:variable name="CurrentRow" select="normalize-space(string-join(substring($Value,0,$ByLength),' '))"/>
<xsl:call-template name="GetBreakingStrings">
<xsl:with-param name="ByLength" select="$ByLength"/>
<xsl:with-param name="Length" select="string-length(substring-after($Value, $CurrentRow))"/>
<xsl:with-param name="Value" select="substring-after($Value, $CurrentRow)"/>
<xsl:with-param name="counter" select="sum($counter + 1)"></xsl:with-param>
</xsl:call-template>
<row id="{$counter}"><xsl:value-of select="$CurrentRow"/></row>
</xsl:when>
<xsl:when test="not($Length gt $ByLength) and $Value">
<row id="{$counter}"><xsl:value-of select="$Value"/></row>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
output:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<p>By word example</p>
<row id="1">4.)Whatever universe a professor believes</row>
<row id="2">in must at any</row>
<row id="3">rate be a universe</row>
<row id="4">that lends itself to</row>
<row id="5">lengthy discourse. A universe</row>
<row id="6">definable in two sentences</row>
<row id="7">is something for which</row>
<row id="8">the professorial intellect has</row>
<row id="9">no use. No faith</row>
<row id="10">in anything of that</row>
<row id="11"> cheap kind!11</row>
<p>By String example</p>
<row id="1">4.)Whatever universe a professor believes in must</row>
<row id="2">at any rate be a universe that lends itself to l</row>
<row id="3">engthy discourse. A universe definable in two sen</row>
<row id="4">tences is something for which the professorial in</row>
<row id="5">tellect has no use. No faith in anything of that</row>
<row id="6"> cheap kind!11</row>
</output>

sorting based on alphabetical order

I've the below XML document.
<case.considered>
<case.ref BVtable="yes">
<citetitle type="case" full="Lee Ting Lam v Leung Kam Ming" legtype="ord">Lee Ting Lam v Leung Kam Ming</citetitle>
<citecitation full="[1980] HKLR 657">[1980] HKLR 657</citecitation>
</case.ref>
</case.considered>
<case.considered>
<case.ref BVtable="yes">
<citetitle type="case" full="Chan Pui Ki v Leung On" legtype="ord">Chan Pui Ki v Leung On</citetitle>
<citecitation full="[1996] 2 HKLR 401, [1996] 2 HKC 565">[1996] 2 HKLR 401</citecitation>
</case.ref>
</case.considered>
<case.considered>
<case.ref BVtable="yes" annotation="considered">
<citetitle type="case" full="Sung Fuk Wah v Lam Wai Leuk" legtype="ord">Sung Fuk Wah v Lam Wai Leuk</citetitle>
<citecitation full="(unrep., HCA 3676/1994, [1995] HKLY 527)">(unrep., HCA 3676/1994)</citecitation>
</case.ref>
</case.considered>
<case.considered>
<case.ref BVtable="yes" annotation="distinguished">
<citetitle type="case" full="Blamire v South Cumbria Health Authority" legtype="ord">Blamire v South Cumbria Health Authority</citetitle>
<citecitation full="[1993] PIQR Q1">[1993] PIQR Q1</citecitation>
</case.ref>
</case.considered>
<case.considered>
<case.ref BVtable="yes" annotation="distinguished">
<citetitle type="case" full="W (A child) v Hammersmith Hospitals NHS Trust" legtype="ord">W (A child) v Hammersmith Hospitals NHS Trust</citetitle>
<citecitation full="[2002] 3 QR 5, [2002] All ER (D) 397">[2002] All ER (D) 397</citecitation>
</case.ref>
</case.considered>
and the below XSLT
<xsl:template match="ref.group" name="ref.group">
<xsl:if test="leg.mentioned">
<xsl:for-each select="./leg.ref">
<xsl:if test="./#considered='no'">
<div class="section-sect1">
<xsl:text>Legislation mentioned in the judgment</xsl:text>
</div>
<div class="para">
<xsl:value-of select="citetitle"/>
<xsl:text>, </xsl:text>
<xsl:for-each select="./leg.ptr.group/leg.ptr">
<xsl:value-of select="."/>
<xsl:if test="not(position() = last())">
<xsl:text disable-output-escaping="yes">, </xsl:text>
</xsl:if>
</xsl:for-each>
</div>
</xsl:if>
</xsl:for-each>
</xsl:if>
<xsl:if test="//case.considered">
<div class="section-sect1">
<xsl:text>Case cited in the judgment</xsl:text>
</div>
<xsl:apply-templates select="//case.considered" mode="x"/>
</xsl:if>
<xsl:if test="./other.mentioned">
<div class="section-sect1">
<xsl:text>Other materials mentioned in the judgment</xsl:text>
</div>
<xsl:apply-templates select="./other.mentioned"/>
</xsl:if>
<xsl:apply-templates select="//judgment"/>
</xsl:template>
<xsl:template match="case.considered" mode="x">
<div class="para">
<xsl:apply-templates select="case.ref" mode="x"/>
</div>
</xsl:template>
<xsl:template match="case.ref" mode="x">
<span class="font-style-italic">
<xsl:value-of select="./citetitle[#full]"/>
</span>
<xsl:text> </xsl:text>
<xsl:value-of select="./citecitation/#full"/>
</xsl:template>
here i'm unable to know how to sort the data based on the text in citetitle
please let me know how to sort the data.
Thanks
I am guessing it is the case.considered you wish to add the sorting to. Currently, you are doing this
<xsl:apply-templates select="//case.considered" mode="x"/>
To do sorting, you would need to change it to this
<xsl:apply-templates select="//case.considered" mode="x">
<xsl:sort select="case.ref/citetitle" />
</xsl:apply-templates>
This does assume only one case.ref element per case.considered element though. If there are more than one, only the first one is used in the sorting.

"Swap italics algorithm" for pure XSLT, do you know one?

Working with XHTML and XML tools, sometimes we need to invert the italics <i> blocks. Example:
<!-- original text: -->
<p id="p1"><i>Several more</i> Homo sapiens <i>fossils were discovered</i>.</p>
<!-- same text, swapping italics: -->
<p id="p2">Several more <i>Homo sapiens</i> fossils were discovered.</p>
So, looks like this,
Several more Homo sapiens fossils were discovered.
Several more Homo sapiens fossils were discovered.
There are many ways to transform a "mixed italics" text into a "inverted italics": see
What the correct algorithm to invert italics in a mixed text?...
... But I not see any way to do it with "pure XSLT" (without external processing dependences): do you see?
Something like this:
<xsl:template match="i" mode="invert-italic">
<xsl:apply-templates mode="invert-italic"/>
</xsl:template>
<xsl:template match="text()[not(ancestor::i)]" mode="invert-italic">
<i><xsl:copy-of select="."/></i>
</xsl:template>
<xsl:template match="node()" mode="invert-italic">
<xsl:copy>
<xsl:copy select="#*"/>
<xsl:apply-templates mode="invert-italic"/>
</xsl:copy>
</xsl:template>
I'm not sure if this would cover all cases, but you could do this:
XML Input
<html>
<!-- original text: -->
<p id="p1"><i>Several more</i> Homo sapiens <i>fossils were discovered</i>.</p>
<!-- same text, swapping italics: -->
<p id="p2">Several more <i>Homo sapiens</i> fossils were discovered.</p>
<p>Leave me alone!</p>
<p><b><i>O</i>RIGINAL <big><i>with italics</i> and </big> withOUT</b></p>
</html>
XSLT 1.0
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[i]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="node()" mode="swapItal"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()" mode="swapItal" priority="1">
<i><xsl:value-of select="."/></i>
</xsl:template>
<xsl:template match="i" mode="swapItal">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="#*|node()" mode="swapItal">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="swapItal"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
XML Output
<html>
<!-- original text: -->
<p id="p1">Several more<i> Homo sapiens </i>fossils were discovered<i>.</i></p>
<!-- same text, swapping italics: -->
<p id="p2"><i>Several more </i>Homo sapiens<i> fossils were discovered.</i></p>
<p>Leave me alone!</p>
<p><b>O<i>RIGINAL </i><big>with italics<i> and </i></big><i> withOUT</i></b></p>
</html>
Input Rendered
Several more Homo sapiens fossils were discovered.
Several more Homo sapiens fossils were discovered.
Leave me alone!
ORIGINAL with italics and withOUT
Output Rendered
Several more Homo sapiens fossils were discovered.
Several more Homo sapiens fossils were discovered.
Leave me alone!
ORIGINAL with italics and withOUT
Hello? Someone else will edit? ... Ok, I need "100% solution", them I am adding one, only for finesh, but was not "my".
#MichaelKay and #DanielHaley show good clues and near to fineshed solutions (!).
XML Input
<html>
<p><i>Several more</i> Homo sapiens <i>fossils were discovered</i>.</p>
<p>Several more <i>Homo sapiens</i> fossils were discovered.</p>
<p>Leave me alone!</p>
<p><b><i>F</i>RAGMENT <big><i>with italics</i> and </big> withOUT</b></p>
<p><i><sup><sub><sup><sub>Deep tags</sub></sup></sub></sup> here</i>
<big><b><small>and here</small></b></big>!
</p>
</html>
XSLT 1.0 implemention
#DanielHaley show better results (only the <p>Leave me alone!</p> not inverted), but #MichaelKay's solution is more elegant: I merged both to produce this "100% solution". Now I am using this XSLT as "swap italics algorithm" on my system... No bugs until now (!).
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:preserve-space elements="p"/>
<xsl:template match="#*|node()"> <!-- copy all -->
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="i"> <!-- remove tag i -->
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="text()[not(ancestor::i)]"> <!-- inlcude tag i -->
<i><xsl:copy-of select="."/></i>
</xsl:template>
</xsl:stylesheet>
Outlining as an "drive by event algorithm" in a copy process:
remove i tags: copy any thing of "<i> thing </i>" as " thing ".
include i tags: copy any text as "<i> text </i>", when the text is not into a context of "italic parents (or another ancestor)". PS: text is a terminal node of the DOM tree.

Resources