Match node except for grandchild node - xslt

Background
Converting a document from OpenOffice to DocBook format.
Problem
Parts of the document include the following:
<ul><li><ul><li><ul><li><p>...</p></li></ul></li></ul></li></ul>
While other parts of the document include:
<ul><li><p>...</p></li></ul>
I tried to match just the inner-most ul tag using:
<xsl:template match="xhtml:ul[not(child::xhtml:li/child::xhtml:ul)]">
...
</xsl:template>
But this does not match. The following expression:
<xsl:template match="xhtml:ul">
Will create, as expected:
<itemizedlist>
<itemizedlist>
<itemizedlist>
...
</itemizedlist>
</itemizedlist>
</itemizedlist>
Desired Output
The desired output format, regardless of ul nesting, is:
<itemizedlist>
...
</itemizedlist>
Question
What's the correct syntax for matching the innermost child ul node?
Ideas
There are a few ways to resolve this:
Search/replace the original document (there are only ~20 instances).
Use xsl:test within the li node to see if the child node is ul.
What XPath expression would work? For example:
<xsl:template match="xhtml:ul[not(grandchild::xhtml:ul)]">
Thank you!

<xsl:template match="xhtml:ul[not(descendant::xhtml:ul)]">
<itemizedlist><xsl:apply-templates /></itemizedlist>
</xsl:template>

Related

How do I flatten text nodes and nested nodes?

I’m trying to flatten an element’s text nodes and nested inlined elements
<e>something <inline>rather</inline> else</e>
into
<text>something </text>
<text-inline>rather</text-inline>
<text> else</text>
Using e/text() would return both text nodes but how do I flatten all nodes in order for arbitrarily inlined elements (even nested)?
I am not sure "flatten" is the right term for this. It seems all you want to do is change some text nodes into elements containing the same text. This can be done by a template matching these text nodes:
<xsl:template match="e/text()">
<text>
<xsl:copy/>
</text>
</xsl:template>
Demo: https://xsltfiddle.liberty-development.net/ncdD7n4
Of course, if you also want to rename inline to text-inline, you will need another template for that:
<xsl:template match="inline">
<text-inline>
<xsl:apply-templates />
</text-inline>
</xsl:template>

Apply-templates with in Analyze string

I've the below XML.
<?xml version="1.0" encoding="UTF-8"?>
<para align="center">
<content-style font-style="bold">A.1 This is the first text</content-style> (This is second text)
</para>
Below are my 2 Questions.
here i've declared a regex to match the content-style, But when i run this the second one is caught where as it should be div class="para", but in the output i get <div class="para align-center">. please let me know where am i going wrong.
Is there a way i can apply-templates with in the match. when i tried it throws me an error. I want it like below.
if (para)
xsl:apply-templates select child::node()[not(self::text)]
else
xsl:apply-templates
Working Example
Thanks
If you want to use apply-templates inside the analyze-string then you need to store the context node outside of analyze-string in a variable <xsl:variable name="context-node" select="."/>, then you can use <xsl:apply-templates select="$context-node/node()"/> for instance to process the child nodes.
Whether you need that approach I am not sure, I wonder whether you can not simply use the matches functions in a pattern e.g. <xsl:template match="para[content-style[matches(., '(\w+)\.(\w+)')]]">...</xsl:template>.

how to get text with xpath from bad xml?

I have a "bad xml structure" file:
<cars>
<car>Toyota
<country>Japan</coutry>
....
</car>
</cars>
How to correctly get the right word (Toyota) using Xpath?
I tried:
<xsl:value-of select = "cars/car/text()"/>.
It works, but I think there are more appropriate methods.
Thanks.
Use:
/cars/car/text()[1]
or if you want to discard most of the white space in the text node selected above, use:
normalize-space(/cars/car/text()[1])
Do note that while in XSLT 1.0 <xsl:value-of> outputs the string valu only of the first node of the node-set selected by the expression in the select attribute, <xsl:copy-of> will output all the nodes in the node-set. In XSLT 2.0 even <xsl:value-of> outputs all the nodes in the node-set.
Therefore, for purposes of portability, upgradability and simply for avoiding errors, it is better to specify which exactlyy node from the nodeset is to be output -- even when using <xsl:value-of>

Trying to match more than one class in XSLT

I'm very new to XSLT and trying to format some text for pdf's and I need to match and hide a few elements.
I am currently using:
<xsl:template match="*[#outputclass='LC ACaseName']">
to match:
<p outputclass="LC ACaseName">
and it works just fine.
What I now need to do is match 4 or 5 more
<p outputclass="<somestring>">
and apply the same style to them. I could easily just duplicate the above line substituting the different outputclass names each time but this is lazy and I know there must be a correct way of doing this which I should learn.
I hope I have provided enough info here. If I have missed anything please say.
thanks,
Hedley Phillips
You can specify multiple conditions in the predicate:
<xsl:template match="*[#outputclass='test' or #outputclass='blah']">
I couldn't find the duplicate...
In XSLT/XPath 1.0:
<xsl:template match="*[contains(
'|LC ACaseName|other class|',
concat('|',#outputclass,'|')
)
]">
<!-- Content Template -->
<xsl:template>
In XSLT/XPath 2.0:
<xsl:template match="*[#outputclass = ('LC ACaseName','other class')]">
<!-- Content Template -->
<xsl:template>
Note: For XSLT/XPath 1.0 solution you need a separator not being part of any item content.

XSLT, finding out if last child node is a specific element

Look at the following two examples:
<foo>some text <bar/> and maybe some more</foo>
and
<foo>some text <bar/> and a last <bar/></foo>
Mixed text nodes and bar elements within the foo element. Now I am in foo, and want to find out if the last child is a bar. The first example should prove false, as there are text after the bar, but the second example should be true.
How can I accomplish this with XSLT?
Just select the last node of the <foo> element and then use self axis to resolve the node type.
/foo/node()[position()=last()]/self::bar
This XPath expression returns an empty set (which equates to boolean false) if the last node is not an element. If you want to specifically get value true or false, wrap this expression in the XPath function boolean(). Use self::* instead of self::bar to match any element as the last node.
Input XML document:
<root>
<foo>some text <bar/> and maybe some more</foo>
<foo>some text <bar/> and a last <bar/></foo>
</root>
XSLT document example:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="foo">
<xsl:choose>
<xsl:when test="node()[position()=last()]/self::bar">
<xsl:text>bar element at the end
</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>text at the end
</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Output of the stylesheet:
text at the end
bar element at the end
Now I am in foo, and want to find
out if the last child is a bar
Use:
node()[last()][self::bar]
The boolean value of any non-empty node-set is true() and it is false() for otherwise. You can use the above expression directly (unmodified) as the value of the test attribute of any <xsl:if> or <xsl:when>.
Better, use:
foo/node()[last()][self::bar]
as the match attribute of an <xsl:template> -- thus you write in pure "push" style.
Update: This answer addresses the requirement stated in the original question title, "finding out if last child node is a text node." But the question body suggests a different requirement, and it seems that the latter requirement was the one intended by the OP.
The previous two answers explicitly test whether the last child is a bar element, rather than directly testing whether it is a text node. This is correct if foo contains only "mixed text nodes and bar elements" and never has zero children.
But you may want to test directly whether the last child is a text node:
For readability of stylesheet logic
In case the element contains other children besides elements and text: e.g. comments or processing instructions
In case the element has no children
Maybe you know the latter two will never occur in your case (but from your question I would guess that #3 could). Or maybe you think so but aren't sure, or maybe you hadn't thought about it. In either case, it's safer to test directly for what you actually want to know:
test="node()[last()]/self::text()"
Thus, building on #Dimitre's example code and input, the following XML input:
<root>
<foo>some text <bar/> and maybe some more</foo>
<foo>some text <bar/> and a pi: <?foopi param=yes?></foo>
<foo>some text <bar/> and a comment: <!-- baz --></foo>
<foo>some text and an element: <bar /></foo>
<foo noChildren="true" />
</root>
With this XSLT template:
<xsl:template match="foo">
<xsl:choose>
<xsl:when test="node()[last()]/self::text()">
<xsl:text>text at the end;
</xsl:text>
</xsl:when>
<xsl:when test="node()[last()]/self::*">
<xsl:text>element at the end;
</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>neither text nor element child at the end;
</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
yields:
text at the end;
neither text nor element child at the end;
neither text nor element child at the end;
element at the end;
neither text nor element child at the end;