1

I'm trying to apply transformation to xml so that the output is the xpath of all the child elements. so for example:

    <envelope>
        <body>
            <result>
                <Testimonial>
                    <directional>
                        <allowed >&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt; hi john &lt;how is it&gt;</allowed>
                        <test>&lt;hi&gt;</test>
                        <test>&lt;hi&gt;</test>
                    </directional>
                    <directional>
                        <allowed >&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt; hi john &lt;how is it&gt;</allowed>
                        <test>&lt;hi&gt;</test>
                    </directional>
                </Testimonial>
            </result>
        </body>
    </envelope>

should be transformed into:

/envelope[1]/body[1]/result[1]/Testimonial[1]/directional[1]/allowed[1] /envelope[1]/body[1]/result[1]/Testimonial[1]/directional[1]/test[1] /envelope[1]/body[1]/result[1]/Testimonial[1]/directional[1]/test[2] /envelope[1]/body[1]/result[1]/Testimonial[1]/directional[2]/allowed[1] /envelope[1]/body[1]/result[1]/Testimonial[1]/directional[2]/test[1]

Below is my XSLT, the problem i have is that i cannot get it to target child elements only. In current form below the parents are also included in output which isn't what i want.

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="text" media-type="text/plain"/>
        <xsl:template match="/">
            <xsl:apply-templates/>
        </xsl:template>

        <xsl:template match="*|@*">
            <xsl:param name="path" select="''"/>

            <xsl:variable name="this-path">
                <xsl:value-of select="concat($path, '/')"/>
                <xsl:if test="../@*[name() = name(current())]">
                    <xsl:text>@</xsl:text>
                </xsl:if>
                <xsl:value-of select="name()"/>
                <xsl:if test="count(../*[name() = name(current())]) &gt; 1">
                    <xsl:value-of select="concat('[', count(preceding-sibling::*[name()
    = name(current())]) + 1, ']')"/>
                </xsl:if>
                <xsl:if test="count(../*[name() = name(current())]) &lt;= 1">
                    <xsl:value-of select="'[1]'"/>
                </xsl:if>
            </xsl:variable>
            <xsl:if test="preceding::*|ancestor::*">
                <xsl:text>&#10;</xsl:text>
            </xsl:if>
            <xsl:value-of select="$this-path"/>
            <xsl:apply-templates select="*|@*">
                <xsl:with-param name="path" select="$this-path"/>
            </xsl:apply-templates>
        </xsl:template>
        <xsl:template match="comment()|text()|processing-instruction()"/>
    </xsl:stylesheet>

How can i make the output to be the xpaths of just the child elements?

1

2 Answers 2

3

You just need to change the output logic a little bit - instead of

<xsl:if test="preceding::*|ancestor::*">
    <xsl:text>&#10;</xsl:text>
</xsl:if>
<xsl:value-of select="$this-path"/>

try something like

<xsl:if test="not(*)">
    <xsl:value-of select="$this-path"/>
    <xsl:text>&#10;</xsl:text>
</xsl:if>

The not(*) checks whether this is a leaf node (it will be true for any attribute node, as well as any element node that does not have child elements).

As an aside, you can simplify the logic for creating the [number], since you don't need two cases for >1 and <=1. The same will work for both:

<xsl:value-of select="concat('[', count(preceding-sibling::*[name()
                                       = name(current())]) + 1, ']')"/>

And also ../@*[name() = name(current())] is not a reliable way to determine whether the current node is an attribute - it will give false positives in a case like

<directional allowed="yes">
  <allowed>...</allowed>

A better test would be

count(../@* | .) = count(../@*)

which takes advantage of the fact that XPath expressions select node sets, not lists - count(x | y) = count(x) is true exactly when y is a subset of x.

Or better still, make a separate template for attribute nodes and you can take out the conditional logic for attributes altogether. Here's my version of your stylesheet with all these changes:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" media-type="text/plain"/>

    <xsl:template match="*">
        <xsl:param name="path" select="''"/>

        <xsl:variable name="this-path"
          select="concat($path, '/', name(), '[',
              count(preceding-sibling::*[name() = name(current())]) + 1,
              ']')"/>
        <xsl:if test="not(*)">
            <xsl:value-of select="$this-path"/>
            <xsl:text>&#10;</xsl:text>
        </xsl:if>
        <xsl:apply-templates select="*|@*">
            <xsl:with-param name="path" select="$this-path"/>
        </xsl:apply-templates>
    </xsl:template>

    <xsl:template match="@*">
        <xsl:param name="path"/>
        <xsl:value-of select="concat($path, '/@', name(), '&#10;')" />
    </xsl:template>

    <xsl:template match="comment()|text()|processing-instruction()"/>
</xsl:stylesheet>
Sign up to request clarification or add additional context in comments.

Comments

0

If you test if you have a child element, you can skip printing the paths that do and keep only the five strings that represent full paths:

<xsl:if test="not(child::*)">
    <xsl:value-of select="$this-path"/>
</xsl:if>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.