How to test XSLT having multiple mode with XSpec? - unit-testing

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.

Related

How to Add Color To a Div in XSL when Color is in the XML document

I have an XSL 1.0 document with the following:
<div class="workgroup_title">
<xsl:value-of select="./#name"/>
</div>
I need to set the color for this element. The color is in the XML file
<abc.xyz.color>FF5733</abc.xyz.color>
To get it, I use this:
<xsl:value-of select="./abc.xyz.color"/>
What I would have liked to do is
<div class="workgroup_title" style="color:"#<xsl:value-of select="./abc.xyz.color"/>>
<xsl:value-of select="./#name"/>
</div>
That's not allowed, though.
Or:
<xsl:attribute style="color:">#<xsl:value-of select="./abc.xyz.color"/></xsl:attribute>
But color is not one of the attributes that can be set like that.
You can use attribute value templates to compute (parts of) the value of a literal result element: <div style="color: #{abc.xyz.color}">...</div>
The following templates should suffice your needs:
<xsl:template match="text()" /> <!-- Removes the text from the <abc.xyz.color>FF5733</abc.xyz.color> element -->
<xsl:template match="/*"> <!-- Copies the root element and its namespace -->
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="div[#class='workgroup_title']"> <!-- Applies the template to the <div> element -->
<xsl:copy>
<xsl:attribute name="style"><xsl:value-of select="concat('color: #',../abc.xyz.color,';')"/></xsl:attribute>
<xsl:copy-of select="node()|#*" />
</xsl:copy>
</xsl:template>
Its output is:
<root xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<div style="color: #FF5733;" class="workgroup_title">
<xsl:value-of select="./#name"/>
</div>
</root>

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>

XSL pass attributes to child elements if child elements do not already have the same attribute

How can I pass attributes to child elements only if the child elements do not already have the same attribute?
XML:
<section>
<container attribute1="container1" attribute2="container2">
<p attribute1="test3"/>
<ol attribute2="test4"/>
<container>
<section/>
Output should look like this:
<section>
<p attribute1="test3" attribute2="test2"/>
<ol attribute1="container1" attribute2="test4"/>
</section>
This is what i tried:
<xsl:template match="container">
<xsl:apply-templates mode="passAttributeToChild"/>
</xsl:template>
<xsl:template match="*" mode="passAttributeToChildren">
<xsl:element name="{name()}">
<xsl:for-each select="#*">
<xsl:choose>
<xsl:when test="name() = name(../#*)"/>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:apply-templates select="*|text()"/>
</xsl:element>
</xsl:template>
Any help would be greatly appreciated ;) Thank you in advance!
Attributes declared more than once overwrite each other, so this is easy.
<xsl:template match="container/*">
<xsl:copy>
<xsl:copy-of select="../#*" /> <!-- take default from parent -->
<xsl:copy-of select="#*" /> <!-- overwrite if applicable -->
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
This assumes you want all parent attributes, as your sample seems to indicate. Of course you can decide which attributes you want to inherit:
<xsl:copy-of select="../#attribute1 | ../#attribute2" />
<xsl:copy-of select="#attribute1 | #attribute2">
Try this.
<!-- root and static content - container -->
<xsl:template match="/">
<section>
<xsl:apply-templates select='section/container/*' />
</section>
</xsl:template>
<!-- iteration content - child nodes -->
<xsl:template match='*'>
<xsl:element name='{name()}'>
<xsl:apply-templates select='#*|parent::*/#*' />
</xsl:element>
</xsl:template>
<!-- iteration content - attributes -->
<xsl:template match='#*'>
<xsl:attribute name='{name()}'><xsl:value-of select='.' /></xsl:attribute>
</xsl:template>
On outputting each child node, we iteratively transfer across its attributes and those of the parent.
<xsl:apply-templates select='#*|parent::*/#*' />
Templates are applied to nodes in the order they appear in the XML. So the parent (container) node appears before the child nodes (of course), so it's the parent's attributes that are handled first by the attributes template.
This is handy because it means the template will always show preference to the child nodes' own attributes if they already exist, because they are handled last and thus take precedence over any attributes with the same name from the parent. Thus, the parent cannot overrule them.
Working demo at this XMLPlayground.

Docbook <bibliosource> gets lost in the transformation

I have the following structure
<informalfigure xmlns="http://docbook.org/ns/docbook">
<info>
<bibliosource>Photo: John Doe</bibliosource>
</info>
<mediaobject>
<imageobject>
<imagedata contentdepth="30mm" contentwidth="35mm" fileref="path/to/img.jpg"/>
</imageobject>
</mediaobject>
<caption>
<para>Here goes the caption for the image</para>
</caption>
</informalfigure>
<imagedata> and the <caption> get rendered but the <bibliosource> is gone after the transformation.
I'm using the docbook-xsl-1.77.0/xhtml/docbook.xsl for the transformation... I need some guidance on where/how to change the xslt so that <bibliosource> gets transformed properly.
Thanks!
Here is a simple solution. Add the following to your XHTML customization layer:
<xsl:template match="d:caption">
<div>
<xsl:apply-templates select="." mode="common.html.attributes"/>
<xsl:call-template name="id.attribute"/>
<xsl:if test="#align = 'right' or #align = 'left' or #align='center'">
<xsl:attribute name="align"><xsl:value-of select="#align"/></xsl:attribute>
</xsl:if>
<xsl:apply-templates/>
</div>
<div><xsl:value-of select="../d:info/d:bibliosource"/></div> <!-- This line added -->
</xsl:template>
The above works with the namespace-aware XSLT stylesheets (docbook-xsl-ns).
If you use the non-namespace-aware stylesheets (docbook-xsl), the corresponding customization becomes:
<xsl:template match="caption">
<div>
<xsl:apply-templates select="." mode="common.html.attributes"/>
<xsl:call-template name="id.attribute"/>
<xsl:if test="#align = 'right' or #align = 'left' or #align='center'">
<xsl:attribute name="align"><xsl:value-of select="#align"/></xsl:attribute>
</xsl:if>
<xsl:apply-templates/>
</div>
<div><xsl:value-of select="../blockinfo/bibliosource"/></div> <!-- This line added; note blockinfo -->
</xsl:template>
The text of the <bibliosource> element will appear in a separate <div> directly below the caption. The original template is in graphics.xsl.

XSLT apply templates to tree part

I have the XML tree:
<text>
<plain>abcd<c>efgh</c>ijklm</plain>
<plain>nopq<c>rst</c>uvw<c>xyz</c></plain>
<rp><first><c>asdasd</c>asf</first><second>asdasd</second></rp>
<plain>aaaaa<c>bbbb</c>ccccc<c>xyz</c></plain>
</text>
Then I have code in my XSLT stylesheet ($product_text contains above tree):
<xsl:template name="text_list">
<xsl:if test="$text_count > 0">
<xsl:apply-templates mode="text_item" select="$product_text/text">
<xsl:sort select="#rating" order="descending" data-type="number" />
</xsl:apply-templates>
</xsl:if>
</xsl:template>
<xsl:template mode="text_item" match="*">
<div class="cmp-post">
<xsl:copy-of select="./*" />
</div>
</xsl:template>
This fragment copies all tree as-is. But I need all "c" nodes to be replaced/modified like this:
<c>efgh</c>
to
<cmp attr="efgh">efgh</c>
<c>rst</c>
to
<cmp attr="rst">rst</c>
etc
(edited) Result I expect:
<div class="cmp-post">
<plain>abcd<c attr="efgh">efgh</c>ijklm</plain>
<plain>nopq<c attr="rst">rst</c>uvw<c attr="xyz">xyz</c></plain>
<rp><first><c attr="asdasd">asdasd</c>asf</first><second>asdasd</second></rp>
<plain>aaaaa<c attr="bbbb">bbbb</c>ccccc<c attr="xyz">xyz</c></plain>
</div>
How should I modify text_item template?
Basically, you want to do apply-templates instead of copy-of. copy-of just copies the node; it doesn't do template matching and invocation on the copied elements.
As such, you'll need a few additional templates to get what you want.
<!-- Copy attributes as-is -->
<xsl:template match="#*" mode="text_item">
<xsl:copy-of select="."/>
</xsl:template>
<!-- By default, copy element and text as-is then apply matching on children -->
<xsl:template match="node()" mode="text_item">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="text_item"/>
</xsl:copy>
</xsl:template>
<!-- For 'text' elements, use div instead of direct copy -->
<xsl:template match="text" mode="text_item">
<div class="cmp-post">
<xsl:apply-templates mode="text_item" />
</div>
</xsl:template>
<xsl:template match="c" mode="text_item">
<xsl:copy>
<xsl:attribute name='attr'><xsl:value-of select="."/></xsl:attribute>
<xsl:apply-templates mode="text_item" />
</xsl:copy>
</xsl:template>
(Note that the #* template is just for completeness. Your current input doesn't have any attributes, but if it did, this would copy them to the output.)
Running the above templates on your input with this as a caller
<xsl:template match="/">
<xsl:apply-templates select="." mode="text_item">
<xsl:sort select="#rating" order="descending" data-type="number" />
</xsl:apply-templates>
</xsl:template>
gives the output
<div class="cmp-post">
<plain>abcd<c attr="efgh">efgh</c>ijklm</plain>
<plain>nopq<c attr="rst">rst</c>uvw<c attr="xyz">xyz</c></plain>
<rp><first><c attr="asdasd">asdasd</c>asf</first><second>asdasd</second></rp>
<plain>aaaaa<c attr="bbbb">bbbb</c>ccccc<c attr="xyz">xyz</c></plain>
</div>
It should be the same when called against a node variable.