2
votes

I need some XSLT statement that gives me something like a "LEFT JOIN": if the selected node do exist, return all such nodes, otherwise loop just once.

This is different from the xsl:for-each loop, because when there is not such node the for-each loop returns ZERO lines.

Here is a pratical example.

XML file:

<root>
    <sec1>
        <x1/> ... <x1/>
    </sec1>
    <sec2>
        <x2/> ... <x2/>
    </sec2>
    ...
    <sec10>
        <x10/> ... <x10/>
    </sec10>
</root>

Now, I don't know how many "x1", "x2", .. "x10" do I have, and I want to print out all the possible combinations. A easy and wrong solution:

<xsl:for-each select="/root/sec1/x1">
    <xsl:for-each select="/root/sec2/x2">
        ...
        <xsl:for-each select="/root/sec10/x10">
           ...print x1 and x2... and x10
        </xsl:for-each>
        ...
    </xsl:for-each>
</xsl:for-each>

This solution is wrong because, if there is no "x3" it returns 0 lines (just like a FULL JOIN) while I would like to see all the other values (like a LEFT or RIGHT JOIN).

I can use a combination of xsl:choose, xls:when, xsl:foreach and xsl:otherwise, but this is very long.

I have tried to build my own xsl template, but it doesn't work:

<xsl:template name="left-join">
    <xsl:param name="select"/>
    <xsl:param name="template"/>

    <xsl:choose>
        <xsl:when test="$select">
            <xsl:for-each select="$select">
                <xsl:call-template name="$templatename"> <!--WRONG -->
                    <xsl:with-param name="one-parameter" select="$select"/>
                </xsl:call-template>
            </xsl:for-each>
        </xsl:when>
        <xsl:otherwise>
            <xsl:call-template name="$templatename">
                <xsl:with-param name="one-parameter" select="$select"/>
            </xsl:call-template>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>
2

2 Answers

2
votes

Even if I don't understand your question completely I'll try an answer. My understanding is that you are locking for something like an sql left join. (e.g. http://www.w3schools.com/sql/sql_join_left.asp) An XML/XSLT version could be as following.

Input data:

    <root>
        <Persons>
            <Person id="1">
                <Name>Hansen</Name>
            </Person>
            <Person id="2">
                <Name>Svendson</Name>
            </Person>
            <Person id="3">
                <Name>Pettersen</Name>
            </Person>
        </Persons>
        <Orders>
            <Order id="1" >
                <P_Id>3</P_Id>
                <OrderNo>77895</OrderNo>
            </Order>
            <Order id="2">
                <P_Id>3</P_Id>
                <OrderNo>44678</OrderNo>
            </Order>
            <Order id="3">
                <P_Id>1</P_Id>
                <OrderNo>22456</OrderNo>
            </Order>
            <Order id="4">
                <P_Id>1</P_Id>
                <OrderNo>24562</OrderNo>
            </Order>
            <Order id="5">
                <P_Id>15</P_Id>
                <OrderNo>34764</OrderNo>
            </Order>
        </Orders>
    </root>

XSLT

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

        <xsl:template match="root">
            <xsl:call-template name="person_order" />
        </xsl:template>

        <xsl:template name="person_order">
           <orders>
            <xsl:for-each select="//Person">
                <xsl:variable name ="pid" select="@id" />
                <xsl:call-template name="left_join">
                    <xsl:with-param name="jname" select="'order'"/>
                    <xsl:with-param name="left" select="."/>
                    <xsl:with-param name="right" select="//Order[P_Id = $pid]"/>
                </xsl:call-template>
            </xsl:for-each>
           </orders>
        </xsl:template>


        <xsl:template name="left_join">
            <xsl:param name="jname" />
            <xsl:param name="left" />
            <xsl:param name="right" />

            <xsl:choose>
                <xsl:when test="$right">
                    <xsl:for-each select="$right">
                        <xsl:call-template name="print_join">
                            <xsl:with-param name="jname" select="$jname"/>
                            <xsl:with-param name="left" select="$left"/>
                            <xsl:with-param name="right" select="."/>
                        </xsl:call-template>
                    </xsl:for-each>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:call-template name="print_join">
                        <xsl:with-param name="jname" select="$jname"/>
                        <xsl:with-param name="left" select="$left"/>

                    </xsl:call-template>
                </xsl:otherwise>
            </xsl:choose>

        </xsl:template>
        <xsl:template match="node() | @*">
            <xsl:copy>
                <xsl:apply-templates select="node() | @*"/>
            </xsl:copy>
        </xsl:template>

        <xsl:template name="print_join">
            <xsl:param name="jname" />
            <xsl:param name="left" />
            <xsl:param name="right" />
            <xsl:element name="{$jname}" >
                <xsl:for-each select="$left">
                    <xsl:apply-templates select="node() "/>
                </xsl:for-each>
                <xsl:if test="$right">
                    <xsl:for-each select="$right">
                        <xsl:apply-templates select="node() "/>
                    </xsl:for-each>
                </xsl:if>
            </xsl:element>
        </xsl:template>

    </xsl:stylesheet>

Which generates the output:

    <orders>
      <order>
        <Name>Hansen</Name>
        <P_Id>1</P_Id>
        <OrderNo>22456</OrderNo>
      </order>
      <order>
        <Name>Hansen</Name>
        <P_Id>1</P_Id>
        <OrderNo>24562</OrderNo>
      </order>
      <order>
        <Name>Svendson</Name>
      </order>
      <order>
        <Name>Pettersen</Name>
        <P_Id>3</P_Id>
        <OrderNo>77895</OrderNo>
      </order>
      <order>
        <Name>Pettersen</Name>
        <P_Id>3</P_Id>
        <OrderNo>44678</OrderNo>
      </order>
    </orders>
0
votes

Doing this with a nested for-each seems quite wrong: it seems a candidate for recursion. But I'm struggling to understand the exact nature of the problem. Do x1, x2, etc appear (0 or 1) times each? In that case the problem is surely trivial, you don't need a for-each to iterate over a singleton.

If you want to avoid the verbosity of recursive, conditional code then it's high time you moved to XSLT 2.0.