0

Sorry if my title isn't very clear: it is hard for me to come up with a proper way of phrasing my problem concisely.

Here is what my XML structure looks like:

<library>
    <books>
        <book writers="007,911">
            <title>Two authors wrote me</title>
            <price>4.00</price>
        </book>
        <book writers="911">
            <title>Only one here</title>
            <price>2.50</price>
        </book>
    </books>
    <authors>
        <author id="007">
            <name>James</name>
        </author>
        <author id="911">
            <name>Police</name>
        </author>
        <author id="666">
            <name>Diablo</name>
        </author>
    </authors>
</library>

My goal, for every author: to list their children (here I only present name), but also to list all of the books where the current id matches at least one of the writers. If no book-match is found, then the list of books for that specific author shouldn't appear. That list should be sorted in descending order of price for all the matches.

Here is the essential XSLT parts of code I've done so far:

<xsl:template match="/"> <!-- I'm skipping all the irrelevant <html> stuff, of course. -->

    <body>
        <ol> 
            <xsl:for-each select="//author">
                <li>
                    <b>ID: </b><xsl:value-of select="@id"/>
                    <ul>
                        <xsl:call-template name="auth_infos"></xsl:call-template>
                        <xsl:call-template name="books_stuff"></xsl:call-template>
                    </ul>
                </li>
            </xsl:for-each>
        </ol>
    </body>

</xsl:template>

<xsl:template name="auth_infos">
    <li><b>Name: </b><xsl:value-of select="name"/></li>
</xsl:template>

<xsl:template name="books_stuff">
    <xsl:if test="//book[//author/@id = @writers]"> <!-- This is supposed to make sure that if the author doesn't have any books listed in the database, then there shouldn't be an empty list of books. Only his name should be presented. -->
        <li><b>Book(s): </b>
            <xsl:for-each select="//book[//author/@id = @writers]">
                <xsl:sort select="price" order="descending"/> <!-- Because I would also like to sort them based on their price. -->
                <li><b>Title: </b><xsl:value-of select="//title"/></li>
            </xsl:for-each>
        </li>
    </xsl:if>
</xsl:template>

Obviously I'm having trouble with this particular XPath expression: "//book[//author/@id = @writers]". I can't seem to understand how I can make sure that I only verify the match for the id of the current author being iterated on. Also, since books can have multiple writers (IDREFS to author's id), I'm unsure how to apply a contains operator.

The result would be something like this:

-ID: 007
    -Name: James
    -Book(s):
        -Title: Two authors wrote me
-ID: 911
    -Name: Police
    -Book(s):
        -Title: Two authors wrote me
        -Title: Only one here
-ID: 666
    -Name: Diablo
payne
  • 4,691
  • 8
  • 37
  • 85
  • The main problem here is that `007` is not equal to `007,911`. The rest is rather trivial. Does your processor support XSLT 2.0? – michael.hor257k Feb 09 '19 at 01:24
  • @michael.hor257k humm, I'm unsure. I use `Oxygen XML Editor 19.1` if that's your question. – payne Feb 09 '19 at 01:25
  • No, this is my question: https://stackoverflow.com/questions/25244370/how-can-i-check-which-xslt-processor-is-being-used-in-solr/25245033#25245033 Also, Oxygen is a testing environment. What matters is the processor you'll be using in production. – michael.hor257k Feb 09 '19 at 01:43
  • @michael.hor257k This is not for deployment, this is for educational purposes. I do not think I should have to worry about the version of the processor to be used. For the most part, [this](https://learn.microsoft.com/fr-fr/previous-versions/ms256086(v=vs.120)) is what we were given as a cheat-sheet, but it seems rather incomplete. – payne Feb 09 '19 at 01:46
  • The reason I ask is because this task is easier to perform in XSLT 2.0. I don't speak French, and I don't understand what *"this is what we were given as a cheat-sheet"* means in this context. Is this a homework? – michael.hor257k Feb 09 '19 at 01:52
  • @michael.hor257k it is indeed part of an assignment, but we are left on our own for the most part, so it is kind of hard to figure it out just like that. The link provided is basically what we were presented as a reference for when we'd be asking ourselves questions about XPath. It looks like a rather incomplete descriptive list of how to formulate XPath expressions. – payne Feb 09 '19 at 01:55

1 Answers1

1

Assuming you're limited to XSLT 1.0, this is going to be a little awkward:

XSLT 1.0

<xsl:stylesheet version="1.0"   
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">    
<xsl:output method="html" encoding="utf-8"/>  

<xsl:template match="/library">    
   <body>
        <ol> 
            <xsl:for-each select="authors/author">
                <xsl:variable name="books" select="../../books/book[contains(concat(',', @writers, ','), concat(',', current()/@id, ','))]"/>
                <li>
                    <b>ID: </b>
                    <xsl:value-of select="@id"/>
                    <ul>
                        <li>
                            <b>Name: </b>
                            <xsl:value-of select="name"/>
                        </li>
                        <xsl:if test="$books">
                            <li>
                                <b>Book(s): </b>
                                <ul>
                                    <xsl:for-each select="$books">
                                        <xsl:sort select="price" data-type="number" order="descending"/> 
                                        <li><b>Title: </b><xsl:value-of select="title"/></li>
                                    </xsl:for-each>
                                </ul>
                            </li>
                        </xsl:if>
                    </ul>
                </li>
            </xsl:for-each>
        </ol>
    </body>
</xsl:template>    

</xsl:stylesheet>    

Applied to your example, the result s:

<body><ol>
<li>
<b>ID: </b>007<ul>
<li>
<b>Name: </b>James</li>
<li>
<b>Book(s): </b><ul><li>
<b>Title: </b>Two authors wrote me</li></ul>
</li>
</ul>
</li>
<li>
<b>ID: </b>911<ul>
<li>
<b>Name: </b>Police</li>
<li>
<b>Book(s): </b><ul>
<li>
<b>Title: </b>Two authors wrote me</li>
<li>
<b>Title: </b>Only one here</li>
</ul>
</li>
</ul>
</li>
<li>
<b>ID: </b>666<ul><li>
<b>Name: </b>Diablo</li></ul>
</li>
</ol></body>

Rendered as:

enter image description here

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • This works. `"../../books/book[contains(concat(',', @writers, ','), concat(',', current()/@id, ','))]"` : interesting line, there! Not sure I would have figured that out on my own. Feel free to add a bit more details as of the workings of that expression. I'm also now curious about how much nicer a `XSLT 2.0` version would look like. – payne Feb 09 '19 at 02:09
  • 1
    @payne It works by searching for `,007,` in `,007,911,` - IOW, it will find the search string unambiguously regardless of its position in the searched text. In XSLT 2.0, you could use the `tokenize()` function to parse the searched text and then do a `=` comparison, or - even better - use it as a [key](https://www.w3.org/TR/1999/REC-xslt-19991116#key). – michael.hor257k Feb 09 '19 at 02:18
  • I've just realized that my XML was not properly formed: the way to reference multiple IDs within the same attribute is by separating them with `SPACES ( )` and not `COMMAS (,)`. :( – payne Feb 11 '19 at 23:11
  • It makes it much simpler! Solution: `//book[contains(@writers, current()/@id)]`. Thank you for your help. – payne Feb 11 '19 at 23:37
  • 1
    @payne It doesn't matter which separator you use. The problem is false positives: (e.g. `127` contains `12` or `27`). Your solution will only work if all IDs have the same length. – michael.hor257k Feb 11 '19 at 23:44