0

I have a code like this

<Library>
    <Books>
        <Book>
            <ISBN>123</ISBN>
            <Name>Book 1</Name>
            <Author-Ref>/Library/Authors/Author[2]</Author-Ref>
        </Book>
        <Book>
            <ISBN>425</ISBN>
            <Name>Book 2</Name>
            <Author-Ref>/Library/Authors/Author[1]</Author-Ref>
        </Book>     
    </Books>
    <Authors>
        <Author>
            <Name>John smith</Name>
            <Nationality>American</Nationality>
            <BirthDate>08051977</BirthDate>
        </Author>
        <Author>
            <Name>Sandra Johns</Name>
            <Nationality>American</Nationality>
            <BirthDate>03091981</BirthDate>
        </Author>       
    </Authors>
</Library>

And i want to write xslt code, to print Book Name, Auther Name.

How do i parse the reference to the Auther name using the XSLT?

<xsl:for-each select="/Library/Books/Book">
    Book Name: <xsl:value-of select="./Name"/>
    Auther Name: ?????
</xsl:for-each>
  • This is not currently possible in standard XSLT. However, [this link](http://stackoverflow.com/a/4593170/18157) might help. – Jim Garrison Jul 11 '13 at 18:34
  • possible duplicate of [Evaluate dynamic string as an XPath expression?](http://stackoverflow.com/questions/4591452/evaluate-dynamic-string-as-an-xpath-expression) – Jim Garrison Jul 11 '13 at 18:34
  • @JimGarrison It is possible using XSLT 3.0 using the `xsl:evaluate` function. This is implemented e.g. in Saxon (although a commercial licence is required). Details at http://www.saxonica.com/documentation/conformance/xslt30.html – dirkk Jul 11 '13 at 19:03
  • Is there any possibility of the XML being changed to use a simple ID as the reference? – Tim C Jul 11 '13 at 21:21
  • Runtime evaluation of XPath expressions is available in XSLT 1.0 via extension functions, e.g., the EXSLT `dyn:evaluate` or Saxon's `saxon:evaluate`. If you have any control over the XML serialization of your data, you can also consider ID/IDREF or similar linkage, which would not require extension functions. –  Jul 12 '13 at 00:38

3 Answers3

1

I would do this in 2 phases; First phase is to add the path to the Author elements. Second phase to use the added path to produce the desired output.

This could be done in a single XSLT (2.0) by putting the new input with the path added in a variable, but this would cause memory issues with larger documents.

XML Input (input.xml)

<Library>
    <Books>
        <Book>
            <ISBN>123</ISBN>
            <Name>Book 1</Name>
            <Author-Ref>/Library/Authors/Author[2]</Author-Ref>
        </Book>
        <Book>
            <ISBN>425</ISBN>
            <Name>Book 2</Name>
            <Author-Ref>/Library/Authors/Author[1]</Author-Ref>
        </Book>     
    </Books>
    <Authors>
        <Author>
            <Name>John smith</Name>
            <Nationality>American</Nationality>
            <BirthDate>08051977</BirthDate>
        </Author>
        <Author>
            <Name>Sandra Johns</Name>
            <Nationality>American</Nationality>
            <BirthDate>03091981</BirthDate>
        </Author>       
    </Authors>
</Library>

First XSLT (pass1.xsl)

This will add the attribute path to the Author elements.

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

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:if test="self::Author">
                <xsl:attribute name="path">
                    <xsl:for-each select="ancestor-or-self::*">
                        <xsl:value-of select="concat('/',local-name())"/>
                        <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>
                    </xsl:for-each>
                </xsl:attribute>
            </xsl:if>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

XML Output (temp.xml)

Notice @path has been added.

<Library>
   <Books>
      <Book>
         <ISBN>123</ISBN>
         <Name>Book 1</Name>
         <Author-Ref>/Library/Authors/Author[2]</Author-Ref>
      </Book>
      <Book>
         <ISBN>425</ISBN>
         <Name>Book 2</Name>
         <Author-Ref>/Library/Authors/Author[1]</Author-Ref>
      </Book>
   </Books>
   <Authors>
      <Author path="/Library/Authors/Author[1]">
         <Name>John smith</Name>
         <Nationality>American</Nationality>
         <BirthDate>08051977</BirthDate>
      </Author>
      <Author path="/Library/Authors/Author[2]">
         <Name>Sandra Johns</Name>
         <Nationality>American</Nationality>
         <BirthDate>03091981</BirthDate>
      </Author>
   </Authors>
</Library>

Second XSLT (pass2.xsl)

<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="/*/Books/Book">
        <xsl:value-of select="concat('Book Name: ',Name,'&#xA;')"/>
        <xsl:value-of select="concat('Author Name: ',
        /*/Authors/Author[@path=current()/Author-Ref]/Name,'&#xA;')"/>
    </xsl:template>

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

</xsl:stylesheet>

Final Output

Book Name: Book 1
Author Name: Sandra Johns
Book Name: Book 2
Author Name: John smith
Daniel Haley
  • 51,389
  • 6
  • 69
  • 95
0

One way is to mix a bit of unix shell script with the xmlstarlet tool. The xmlstarlet command that serves input to the while loop

xmlstarlet sel -T -t \
    -m "/Library/Books/Book" \
    -v "concat( Name, '>', Author-Ref )" \
    -n \
xmlfile

searches for the Name and the Author-Ref content and prints both separated with character >. It would fail if either of both fields contains that character.

Book 1>/Library/Authors/Author[2]
Book 2>/Library/Authors/Author[1]

Inside the loop print the name and execute a second xmlstarlet command with the xpath of author_ref to extract the author name.

The complete command:

while IFS=">" read -r name author_ref; do
    printf "Book name: %s\n" "$name"
    printf "Author Name: %s\n" "$(xmlstarlet sel -T -t -v "${author_ref}/Name" xmlfile)"
done < <(xmlstarlet sel -T -t -m "/Library/Books/Book" -v "concat( Name, '>', Author-Ref )" -n xmlfile)

That yields:

Book name: Book 1
Author Name: Sandra Johns
Book name: Book 2
Author Name: John smith
Birei
  • 35,723
  • 2
  • 77
  • 82
0

Here is an XSLT 1.0 stylesheet

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:dyn="http://exslt.org/dynamic"
    extension-element-prefixes="dyn"
    version="1.0">

    <xsl:output method="text"/>

    <xsl:template match="/">
        <xsl:for-each select="/Library/Books/Book">
            <xsl:text>Book Name: </xsl:text>
            <xsl:value-of select="./Name"/>
            <xsl:text>&#x000A;</xsl:text>
            <xsl:text>Auther Name: </xsl:text>
            <xsl:value-of select="dyn:evaluate(Author-Ref)/Name"/>
            <xsl:text>&#x000A;</xsl:text>                
        </xsl:for-each>
    </xsl:template>

</xsl:stylesheet>

tested with Xalan and Xsltproc to produce this output with the provided input:

Book Name: Book 1
Auther Name: Sandra Johns
Book Name: Book 2
Auther Name: John smith

Note that you will need to have the EXSLT modules available to your processor. I am not a Xalan user, but based on this answer, there is a xalan:evaluate as well. If you're using Saxon, you can update with

xmlns:saxon="http://saxon.sf.net/"
extension-element-prefixes="saxon"

and

<xsl:value-of select="saxon:evaluate(Author-Ref)/Name"/>
Community
  • 1
  • 1