XSLT - To move parent attribute value into child first para - xslt

Input is like as,
<section counter="yes" level="5">
<title><target id="page92"/></title>
<section counter="yes" level="6">
<title>Standard 12-lead ECG at Rest</title>
<para>The standard ECG is recorded at rest using 12 leads in order to collect as much information as possible:</para>
<listing type="dash">
<litem><para>Standard limb leads according to Einthoven (I, II, III)</para></litem>
Output should be,
<section counter="yes" level="5">
<title><target /></title>
<section counter="yes" level="6">
<title>Standard 12-lead ECG at Rest</title>
<para id="page92">The standard ECG is recorded at rest using 12 leads in order to collect as much information as possible:</para>
<listing type="dash">
<litem><para>Standard limb leads according to Einthoven (I, II, III)</para></litem>
We wrote xslt as shown below,
<xsl:template match="para[1][parent::section[parent::section[not(normalize-space(title))]]]">
<xsl:choose>
<xsl:when test="position() = 1">
<para>
<xsl:attribute name="id" select="ancestor::section[not(normalize-space(title))]/title/target/#id"/>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates/>
</para>
</xsl:when>
<xsl:otherwise>
<para>
<xsl:apply-templates select="#*|node()"/>
</para>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
While using above xslt, we are unable to meet expected output.
<section counter="yes" level="5">
<title><target /></title>
<section counter="yes" level="6">
<title>Standard 12-lead ECG at Rest</title>
<para id="page92">The standard ECG is recorded at rest using 12 leads in order to collect as much information as possible:</para>
<listing type="dash">
<litem><para id="page92">Standard limb leads according to Einthoven (I, II, III)</para></litem>
The "page ID" value is repeating on following paragraphs which we didn't required. We need to maintain the page ID only on 1st paragraph.
Could you please guide us.

As for getting the result you want, it should be as simple as
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="section[not(normalize-space(title))]/section/para[1]">
<xsl:copy>
<xsl:attribute name="id" select="ancestor::section[not(normalize-space(title))]/title/target/#id"/>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="section/title[not(normalize-space())]/target/#id"/>
</xsl:transform>

Related

How to find updated date in processing instruction - XSLT

I am try to create attribute lastUpdatedOn in p element if find the processing instruction in p element and create the value updated date.
Input XML
<root>
<p content-type="new">Ongoing technological and <?lastUpdatedOn 20/01/2021?>privacy developments present practicing <?lastUpdatedOn 27/01/2021?>attorneys with significant challenges in the field of information privacy and <?lastUpdatedOn 25/01/2020?>security.</p>
<p content-type="new">Developments present <?lastUpdatedOn 26/01/2021?>practicing.</p>
<p content-type="new">Practicing the Labs.</p>
</root>
Expected Output
<root>
<p content-type="new" lastupdatedon="27/01/2021">Ongoing technological and <?lastUpdatedOn 20/01/2021?>privacy developments present practicing <?lastUpdatedOn 27/01/2021?>attorneys with significant challenges in the field of information privacy and <?lastUpdatedOn 25/01/2020?>security.</p>
<p content-type="new" lastupdatedon="26/01/2021">Developments present <?lastUpdatedOn 26/01/2021?>practicing.</p>
<p content-type="new">Practicing the Labs.</p>
</root>
XSLT Code
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:if test="processing-instruction(lastUpdatedOn)">
<xsl:attribute name="lastupdatedon">
<xsl:value-of select="processing-instruction(lastUpdatedOn)"/>
</xsl:attribute>
</xsl:if>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
The right pattern or test is processing-instruction('lastUpdatedOn'). I would stuff it into the match of the p e.g.
<xsl:template match="p[processing-instruction('lastUpdatedOn')]">
<xsl:copy>
<xsl:apply-templates select="#*, processing-instruction('lastUpdatedOn'), node() except processing-instruction('lastUpdatedOn')"/>
</xsl:copy>
</xsl:template>
<xsl:template match="processing-instruction('lastUpdatedOn')">
<xsl:attribute select="{name()}" select="."/>
</xsl:template>
the rest can be handled by the identity transformation with e.g. <xsl:mode on-no-match="shallow-copy"/>.
As you seem to have multiple processing instruction sorting them with
<xsl:template match="p[processing-instruction('lastUpdatedOn')]">
<xsl:copy>
<xsl:apply-templates select="#*, sort(processing-instruction('lastUpdatedOn'), (), function($p) { xs:date(replace($p, '([0-9]{2})/([0-9]{2})/([0-9]{4})', '$3-$2-$1')) })[last()], node() except processing-instruction('lastUpdatedOn')"/>
</xsl:copy>
</xsl:template>
<xsl:template match="processing-instruction('lastUpdatedOn')">
<xsl:attribute name="lastupdatedon" select="."/>
</xsl:template>
can help to just display the last date. Higher-order fn:sort is available in Saxon PE and EE since 9.8 at least and in Saxon HE for 10 and later.
The above would move the date to the attribute but delete the processing instructions from the p, if you need to retain them perhaps
<xsl:template match="p[processing-instruction('lastUpdatedOn')]">
<xsl:copy>
<xsl:apply-templates select="#*, sort(processing-instruction('lastUpdatedOn')!xs:date(replace(., '([0-9]{2})/([0-9]{2})/([0-9]{4})', '$3-$2-$1')))[last()], node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match=".[. instance of xs:date]">
<xsl:attribute name="lastupdatedon" select="."/>
</xsl:template>
is a better approach.

How to test XSLT having multiple mode with XSpec?

Need to write XSpec test case to test the XSLT, in which multiple modes are used for transformation.
But with below test-case, the xspec only tests the output with default mode applied.
I wonder if there is a way to test the final output of the transformation.
<!-- input.xml -->
<body>
<div>
<p class="Title"><span>My first title</span></p>
<p class="BodyText"><span style="font-weight:bold">AAAAAAA</span><span>2 Jan 2020</span></p>
</div>
</body>
<!-- conv.xsl -->
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<!-- default mode : adding text-align attribute where #class=Title -->
<xsl:template match="*[ancestor::body]">
<xsl:choose>
<xsl:when test="#class = 'Title'">
<xsl:element name="{local-name()}">
<xsl:copy-of select="#* except #style"/>
<xsl:attribute name="text-align" select="'center'"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{local-name()}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- bodytext mode : changing element name to <title> where p[#class=Title] -->
<xsl:template match="p[#class]" mode="bodytext">
<xsl:choose>
<xsl:when test="#class = 'Title'">
<title>
<xsl:copy-of select="#* except #class"/>
<xsl:apply-templates mode="bodytext"/>
</title>
</xsl:when>
<xsl:otherwise>
<para>
<xsl:apply-templates mode="bodytext"/>
</para>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="body">
<xsl:variable name="data">
<body>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</body>
</xsl:variable>
<xsl:apply-templates select="$data" mode="bodytext"/>
</xsl:template>
<xsl:template match="node() | #*" mode="#all">
<xsl:copy>
<xsl:apply-templates select="node() | #*" mode="#current"/>
</xsl:copy>
</xsl:template>
O\P for first <p>:
-- after default mode applied: <p class="Title" text-align="center">. [below xspec tests this o\p]
-- final: <title text-align="center">. [Want to test this o\p]
<!-- test.xspec -->
<x:description xmlns:x="http://www.jenitennison.com/xslt/xspec" stylesheet="conv.xsl">
<x:scenario label="XSS00001: Testing 'p[#class=Title]' converts to 'title'">
<x:context href="input.xml" select="/body/div[1]/p[1]"/>
<x:expect label="Testing 'p' converts to 'title'">
<title text-align="center">
<span>My first title</span>
</title>
</x:expect>
</x:scenario>
</x:description>
Any suggestion in this regard would be a great help. Thanks...
I don't think it is solely the use of the modes that doesn't give you the result you want. However, the way you have set up the modes in your XSLT, if you match on that /body/div[1]/p[1] in the XSpec test scenario, you will get the stylesheet applied to only that p element. And obviously for that p there is the match on *[ancestor::body] in the unnamed mode and processing stops in that mode as the other mode is never used from that template.
So you might need to make the body element the context and use a scenario like the following:
<x:scenario label="XSS00002: Testing 'p[#class=Title]' converts to 'title'">
<x:context>
<body>
<div>
<p class="Title">...</p>
<p class="BodyText">...</p>
</div>
</body>
</x:context>
<x:expect label="Testing 'p' converts to 'title'">
<body>
<div>
<title text-align="center">...</title>
<para>...</para>
</div>
</body>
</x:expect>
</x:scenario>
Martin is quite right.
Another way of writing would be:
<x:scenario label="When a document contains 'body//p[#class=Title]'">
<x:context href="input.xml" />
<x:expect label="'p' is converted to 'title[#text-align]'"
test="body/div/title">
<title text-align="center">
<span>My first title</span>
</title>
</x:expect>
</x:scenario>
that is,
Remove #select from x:context, because you and/or conv.xsl seem to assume the transformation to start always from the document node (/).
Add #test to x:expect, because you seem to be interested only in the title element in the transformation result.

Extract comment data except one in xslt

Input xml is like that,
<section level="2" counter="yes">
<title id="c1_3"><!--1.3--> Reeve’s Prosthesis (1972)</title>
<figure counter="yes" id="f1_1">
<legend><para><!--<emph type="bold">Fig. 1.1</emph>--> Reeve’s prosthesis. (Reproduced with permission from Reeves B, Jobbins B, Dowson D, Wright V. A Total Shoulder Endo-Prosthesis. Eng Med 1972;1(3):64–67.)</para></legend>
<para><!--<inline-figure xlink:href="images/copy.jpg"/>--></para>
</figure>
</section>
Output should be,
<section level="2" counter="yes">
<title id="c1_3">1.3 Reeve’s Prosthesis (1972)</title>
<figure counter="yes" id="f1_1">
<legend><para><emph type="bold">Fig. 1.1</emph> Reeve’s prosthesis. (Reproduced with permission from Reeves B, Jobbins B, Dowson D, Wright V. A Total Shoulder Endo-Prosthesis. Eng Med 1972;1(3):64–67.)</para></legend>
<para><!--<inline-figure xlink:href="images/copy.jpg"/>--></para>
</figure>
</section>
My xslt wrote like this,
<xsl:template match="document//comment()">
<xsl:choose>
<xsl:when test="ancestor::para | ancestor::caption | ancestor::section | ancestor::document">
<xsl:text disable-output-escaping="yes"><comment></xsl:text>
<xsl:variable name="commentText0"><xsl:copy-of select="replace(normalize-space(.),'
',' ')"/></xsl:variable>
<xsl:variable name="commentText2"><xsl:value-of select="replace($commentText0, 'Fig([.]) ', 'Fig. ')" disable-output-escaping="yes"/></xsl:variable>
<xsl:variable name="commentText3"><xsl:value-of select="replace($commentText2, 'Table ', 'Table ')" disable-output-escaping="yes"/></xsl:variable>
<xsl:value-of select="replace($commentText3, 'Formula ', 'Formula ')" disable-output-escaping="yes"/>
<xsl:text disable-output-escaping="yes"></comment></xsl:text>
</xsl:when>
<xsl:when test="comment[contains(.,'inline-figure')]">
<xsl:text disable-output-escaping="yes"><!--</xsl:text>
<xsl:apply-templates select="node() | #*"/>
<xsl:text disable-output-escaping="yes">--></xsl:text>
</xsl:when>
</xsl:choose>
</xsl:template>
I want to extract comment content except <inline-figure> element. Could you please guide me that how to write code for it.
There's a lot of stuff in your XSLT that seems to bear no relationship to your stated requirement. Assuming you have an XSLT environment where disable-output-escaping is working, you should be able to simply do:
<xsl:template match="comment()">
<xsl:value-of select="." disable-output-escaping="yes"/>
</xsl:template>
<xsl:template match="comment()[contains(., 'inline-figure')]">
<xsl:copy/>
</xsl:template>
I can't see what the rest of your logic is trying to achieve.
Note also that disable-output-escaping generally works only when writing final serialized output from the transformation, not when writing to a variable.
Asuming XSLT 3.0 (as supported by Saxon 9.8 or Altova 2017/2018) you could use
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="document//comment()[not(contains(., 'inline-figure'))]">
<xsl:copy-of select="parse-xml-fragment(.)"/>
</xsl:template>

applying templates of same type but working on different type of declaration

I've below XMLs.
<emphasis type="italic">
varying from ti,e to time<star.page>58</star.page>Starch
</emphasis>
and
<para indent="no">
<star.page>18</star.page> Further to same.
</para>
here i'm trying to apply-templates on the star.page, but the confusion is if i take <xsl:apply-templates select="./*[1][self::star.page]" mode="first"/>, it is working fine for the first case, but for the second case, the star.page is getting duplicated, if i use <xsl:apply-templates select="./node()[1][self::star.page]" mode="first"/>, in case 2 the star.page that is supposed to appear before div is coming inside div and for case 1, the value is getting duplicated.
Here are the DEmos
Case1-enter link description here
Case2- enter link description here
Expected output are as below.
Case 1:
<span class="font-style-italic">
varying from ti,e to time<?pb label='58'?><a name="pg_58"></a></span>2<span class="font-style-italic">Starch
</span>
Case 2:
<?pb label='18'?><a name="pg_18"></a>
<div class="para">
Further to same.
</div>
Here the condition is as below.
If star.page is immediate child of parent node(though it is para or emphasis), the pb label has to be created first followed by the tag(Case 2 output).
If there is text and in between text there is star.page, then the content should come with pb label inside it.(Case 1 output).
please let me know a common solution on how i can fix theses issues.
I am not clear what the bulk of your XSLT is doing, but I would first make use of a named template to avoid repeated code. There is no issue with giving a matched template a name too.
<xsl:template match="star.page" name="page">
<xsl:processing-instruction name="pb">
<xsl:text>label='</xsl:text>
<xsl:value-of select="."/>
<xsl:text>'</xsl:text>
<xsl:text>?</xsl:text>
</xsl:processing-instruction>
<a name="{concat('pg_',.)}"/>
</xsl:template>
Then the template with the mode "first" becomes this
<xsl:template match="star.page" mode="first">
<xsl:call-template name="page" />
</xsl:template>
You can still call this in the same way, or maybe a slightly different condition will also work
<xsl:apply-templates select="star.page[not(preceding-sibling::node())]" mode="first"/>
Then, all you need is a template to ignore "star.page" that are the first child (because they have already been explicitly selected)
<xsl:template match="star.page[not(preceding-sibling::node())]" />
As a simplified example, try this for starers
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="para|emphasis">
<xsl:apply-templates select="star.page[not(preceding-sibling::node())]" mode="first"/>
<div>
<xsl:choose>
<xsl:when test="./#align">
<xsl:attribute name="class"><xsl:text>para align-</xsl:text><xsl:value-of select="./#align"/></xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="class"><xsl:text>para</xsl:text></xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="star.page[not(preceding-sibling::node())]" />
<xsl:template match="star.page" name="page">
<xsl:processing-instruction name="pb">
<xsl:text>label='</xsl:text>
<xsl:value-of select="."/>
<xsl:text>'</xsl:text>
<xsl:text>?</xsl:text>
</xsl:processing-instruction>
<a name="{concat('pg_',.)}"/>
</xsl:template>
<xsl:template match="star.page" mode="first">
<xsl:call-template name="page" />
</xsl:template>
</xsl:stylesheet>
Do note the use of strip-space here because strictly speaking, the star.page is not the first node in each example, there is a white-space node before it.
When applied to this XML
<root>
<emphasis type="italic">
varying from ti,e to time<star.page>58</star.page>Starch
</emphasis>
<para indent="no">
<star.page>18</star.page> Further to same.
</para>
</root>
The following is output
<div class="para">
varying from time to time<?pb label='58'??><a name="pg_58"/>Starch
</div>
<?pb label='18'??>
<a name="pg_18"/>
<div class="para"> Further to same.
</div>

updating an xml from another one using ids

I have to pick text from translation.xml and update skeleton.xml's respective (where #xid =#id) nodes . in other words IF #id of translation.xml matches with #xid of skeleton.xml, the text content should be placed under skeleton.xml (+ if there is child element present the text content (inline element i, b, etc) also should go in respective parent element: Please see example below for more details:
Note: the inline element could be anything in skeleton.xml (its not restrict to b or i, so it should be generic)
skeleton.xml
<root>
<para a="b" b="c">
<text xid="1">This is first para <b xid="2" a="c" b="d">This is bold <i xid="3" b="d" c="e">This is italics</i> rest of bold</b> rest of para</text>
</para>
<para><text xid="4">This is second para</text></para>
<para><text xid="5">This is unchanged para</text></para>
<para><text xid="6">This is unchanged para</text></para>
</root>
translation.xml
<root>
<TU id="1">
<source>This is first para <g id="2" tagName="b">This is bold <g id="3" tagName="i">This is italics</g> rest of bold</g> rest of para</source>
<target>suum primum para <g id="2" tagName="b">Et hoc confidens, <g id="3" tagName="i">Hoc est, Te Deum</g> Reliqua autem audet,</g> reliqua autem verba haec</target>
</TU>
<TU id="4">
<source>This is second para</source>
<target>Hoc est secundum verba haec</target>
</TU>
</root>
UpdatedSkeleton.xml
<root>
<para a="b" b="c">
<text xid="1">suum primum para <b xid="2" a="c" b="d">Et hoc confidens, <i xid="3" b="d" c="e">Hoc est, Te Deum</i> Reliqua autem audet,</b> reliqua autem verba haec</text>
</para>
<para><text xid="4">Hoc est secundum verba haec</text></para>
<para><text xid="5">This is unchanged para</text></para>
<para><text xid="6">This is unchanged para</text></para>
</root>
I am trying with this code, but facing challenge to place text of inline content at the right place:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:param name="translation" select="'file:/C:/Skeleton.xml'"></xsl:param>
<xsl:variable name="doc">
<xsl:copy-of select="doc($translation)"/>
</xsl:variable>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text">
<xsl:variable name="skelID" select="#xid"/>
<xsl:choose>
<xsl:when test="$doc//*[$skelID=#id]">
<xsl:apply-templates select="$doc//*[$skelID=#id]/target"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
You can copy the <text> node adding <xsl:copy> to your test block, and copy the xid attribute using <xsl:attribute>. For the unmodified nodes, you can use <xsl:copy-of> which copies the full tree, including attributes:
<xsl:when test="$doc//*[$skelID=#id]">
<xsl:copy>
<xsl:attribute name="xid">
<xsl:value-of select="$skelID"/>
</xsl:attribute>
<xsl:apply-templates select="$doc//*[$skelID=#id]/target"/>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
That will also copy the <target> element. You can remove it adding a template:
<xsl:template match="target">
<xsl:apply-templates/>
</xsl:template>
You also aren't replacing the <g> tag. I assume the #tagName attribute says what it's supposed to be transformed into. This should do it:
<xsl:template match="g">
<xsl:element name="{#tagName}">
<xsl:attribute name="xid">
<xsl:value-of select="#id"/>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
I might have missed something, but that will probably solve most of your problem.