2

I am trying to generate xpaths for elements in an xml document. I found this solution Generate/get xpath from XML node java which is very close to what I need except I would like for it to list all of the elements attributes in one path instead of regenerating the same path. For example

/root/elemA[2][@attribute1='first'][@attribute2='second']

instead of

/root/elemA[2][@attribute1='first']
/root/elemA[2][@attribute2='second']

I am very new to xslt and have been playing with this template, but I cannot seem to figure out how to change the output.

Community
  • 1
  • 1
Sean
  • 541
  • 7
  • 26

1 Answers1

3

Try this...

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="text()"/>

    <xsl:template match="*">
        <xsl:for-each select="ancestor-or-self::*">
            <xsl:value-of select="concat('/',local-name())"/>
            <!--Predicate is only output when needed.-->
            <xsl:if test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]">
                <xsl:value-of select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')"/>
            </xsl:if>
            <!--Output attributes.-->
            <xsl:if test="@*">
                <xsl:text>[</xsl:text>
                <xsl:apply-templates select="@*"/>
                <xsl:text>]</xsl:text>
            </xsl:if>
        </xsl:for-each>
        <xsl:text>&#xA;</xsl:text>
        <xsl:apply-templates select="node()"/>
    </xsl:template>

    <xsl:template match="@*">
        <xsl:if test="position() != 1">
            <xsl:text>][</xsl:text>
        </xsl:if>
        <xsl:value-of select="concat('@',local-name(),'=&quot;',.,'&quot;')"/>
    </xsl:template>

</xsl:stylesheet>

Using the input from the linked question:

<root>
    <elemA>one</elemA>
    <elemA attribute1='first' attribute2='second'>two</elemA>
    <elemB>three</elemB>
    <elemA>four</elemA>
    <elemC>
        <elemB>five</elemB>
    </elemC>
</root>

produces the following output:

/root
/root/elemA[1]
/root/elemA[2][@attribute1="first"][@attribute2="second"]
/root/elemB
/root/elemA[3]
/root/elemC
/root/elemC/elemB

EDIT

Here's an updated XSLT based on the comment Is there anyway to make it only output the attributes if it is the child? For example /body/text[@attr='1'] and then /body/text/h1[@attr='1']

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="text()"/>

    <xsl:template match="*">
        <xsl:variable name="id" select="generate-id()"/>
        <xsl:for-each select="ancestor-or-self::*">
            <xsl:value-of select="concat('/',local-name())"/>
            <!--Predicate is only output when needed.-->
            <xsl:if test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]">
                <xsl:value-of select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')"/>
            </xsl:if>
            <!--Output attributes.-->
            <xsl:if test="@* and generate-id() = $id">
                <xsl:text>[</xsl:text>
                <xsl:apply-templates select="@*"/>
                <xsl:text>]</xsl:text>
            </xsl:if>
        </xsl:for-each>
        <xsl:text>&#xA;</xsl:text>
        <xsl:apply-templates select="node()"/>
    </xsl:template>

    <xsl:template match="@*">
        <xsl:if test="position() != 1">
            <xsl:text>][</xsl:text>
        </xsl:if>
        <xsl:value-of select="concat('@',local-name(),'=&quot;',.,'&quot;')"/>
    </xsl:template>

</xsl:stylesheet>
Daniel Haley
  • 51,389
  • 6
  • 69
  • 95
  • This is very close!! Is there anyway to make it only output the attributes if it is the child? For example /body/text[@attr='1'] and then /body/text/h1[@attr='1'] – Sean Aug 01 '14 at 18:22
  • @Sean - I added an updated XSLT that only outputs the attributes for the original context. – Daniel Haley Aug 01 '14 at 18:28