1

This is not the browser type XSLT, this is for processing data (SAP B1 Integration Framework). Suppose we have two SQL Tables, HEADER and LINE and we want to avoid the kind of work where we first SELECT from the HEADER and then launch a separate select for the lines for each, because that requires "visual programming", and we like writing code more than connecting arrows. So we are sending the server a query like SELECT * FROM HEADER, SELECT * FROM LINES and we get an XML roughly like this:

<ResultSets>
<ResultSet>
    <Row><MHeaderNum>1</MHeaderNum></Row>
    <Row><MHeaderNum>2</MHeaderNum></Row>
    <Row><MHeaderNum>3</MHeaderNum></Row>
</ResultSet>

<ResultSet>
    <Row><LineNum>1</LineNum> <HeaderNum>1</HeaderNum></Row>
    <Row><LineNum>2</LineNum> <HeaderNum>2</HeaderNum></Row>
    <Row><LineNum>1</LineNum> <HeaderNum>3</HeaderNum></Row>
    <Row><LineNum>2</LineNum> <HeaderNum>1</HeaderNum></Row>
    <Row><LineNum>1</LineNum> <HeaderNum>2</HeaderNum></Row>
    <Row><LineNum>2</LineNum> <HeaderNum>3</HeaderNum></Row>
</ResultSet>

so we think we are imperative, procedural programmers and pull a

<xsl:for-each select="//ResultSets/Resultset[1]/Row">
   do stuff with header data
   <xsl:for-each select="//ResultSets/Resultset[2]/Row[HeaderNum=MHeaderNum]">
      do stiff with the line data beloning to this particular header
   </xsl:for-each>  
</xsl:for-each>

And of course this does not blinkin' work because MHeaderNum went out of context like grunge went out of fashion, and we cannot save it into a variable either because we will not be update that variable, as XSLT is something sort of an immutable functional programming language.

But fear not, says an inner voice, because XSLT gurus can solve things like that with templates. Templates, if I understand it, are sort of XSLT's take on functions. They can be recursive and stuff like that. So can they be used to solve problems like this?

And of course we are talking about XSLT 1.0 because I don't know whether Java ever bothered to implement the later versions, but SAP certainly did not bother to used said, hypothetical implementation.

Or should I really forget about this and just connect my visual arrows? The thing is, SQL is not supposed to be used in such an iterate through headers then iterate through lines ways. What I am trying to do is what makes an SQL database a happy, get a big ol' chunk of data out of it and then process it somewhere else, not bother it with seventy zillion tiny queries. And in our case the somewhere else is sadly XSLT, although technically I could try JavaScript as well as SAP added a Nashorn to this pile of mess as well, but maybe it is solvable in "pure" XSL?

zx485
  • 28,498
  • 28
  • 50
  • 59
Elek Szid
  • 13
  • 2

3 Answers3

1

Whether XSLT 1 or later and whether with templates and for-each, the current() function exists: //ResultSets/Resultset[2]/Row[HeaderNum=current()/MHeaderNum].

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
1

The best way to resolve cross-references is by using a key.
For example, the following stylesheet:

XSLT 1.0

<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:key name="line-by-header" match="ResultSet[2]/Row" use="HeaderNum" />

<xsl:template match="/ResultSets">
    <output>    
        <xsl:for-each select="ResultSet[1]/Row">
            <header num="{MHeaderNum}"> 
                <xsl:for-each select="key('line-by-header', MHeaderNum)">
                    <line>
                        <xsl:value-of select="LineNum"/>
                    </line>
                </xsl:for-each>
            </header>
        </xsl:for-each>
    </output>
</xsl:template>

</xsl:stylesheet>

when applied to the following input:

XML

<ResultSets>
    <ResultSet>
        <Row><MHeaderNum>1</MHeaderNum></Row>
        <Row><MHeaderNum>2</MHeaderNum></Row>
        <Row><MHeaderNum>3</MHeaderNum></Row>
    </ResultSet>
    <ResultSet>
        <Row><LineNum>1</LineNum> <HeaderNum>1</HeaderNum></Row>
        <Row><LineNum>2</LineNum> <HeaderNum>2</HeaderNum></Row>
        <Row><LineNum>3</LineNum> <HeaderNum>3</HeaderNum></Row>
        <Row><LineNum>4</LineNum> <HeaderNum>1</HeaderNum></Row>
        <Row><LineNum>5</LineNum> <HeaderNum>2</HeaderNum></Row>
        <Row><LineNum>6</LineNum> <HeaderNum>3</HeaderNum></Row>
    </ResultSet>
</ResultSets>

will return:

Result

<?xml version="1.0" encoding="UTF-8"?>
<output>
  <header num="1">
    <line>1</line>
    <line>4</line>
  </header>
  <header num="2">
    <line>2</line>
    <line>5</line>
  </header>
  <header num="3">
    <line>3</line>
    <line>6</line>
  </header>
</output>
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • Thank you, using current() was easier, but I like this solution because it closely mimics using a database foreign key (which the data in fact is). – Elek Szid Jul 15 '20 at 08:22
0

You can try the following XSLT. It is using three XSLT templates.

Because desired output is unknown, I placed some arbitrary processing for each of the header and line item templates.

XSLT

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/">
        <root>
            <xsl:apply-templates/>
        </root>
    </xsl:template>

    <xsl:template match="/ResultSets/ResultSet[1]">
        <ResultSet1>
            <xsl:for-each select="Row">
                <r>
                    <xsl:value-of select="MHeaderNum"/>
                </r>
            </xsl:for-each>
        </ResultSet1>
    </xsl:template>

    <xsl:template match="/ResultSets/ResultSet[2]">
        <ResultSet2>
            <xsl:for-each select="Row">
                    <xsl:copy-of select="."/>
            </xsl:for-each>
        </ResultSet2>
    </xsl:template>
</xsl:stylesheet>
Yitzhak Khabinsky
  • 18,471
  • 2
  • 15
  • 21