K.B.
K.B.

Reputation: 3

Is it possible in XSLT version 1 to join 2 xml files by key but having none of them on the input?

Let's say we have 2 XML files a.xml:

<Reports>
<Fields>
 <PositionID>000101</PositionID>
 <Date>2021-04-19</Date>
 <Name>XXX</Name>
</Fields>
<Fields>
 <PositionID>000100</PositionID>
 <Date>2021-04-19</Date>
 <Name></Name>
</Fields>
</Reports>

and b.xml:

    <AOID>
      <Employee>
        <Name>YYY</Name>
        <PositionID>000100</PositionID>
      </Employee>
      <Employee>
        <Name>XXX</Name>
        <PositionID>000101</PositionID>
      </Employee>
    </AOID>

I'm looking for a v1 xslt that would join the two extracts by PositionID and add the Name field from xml b if it's empty or not existing in XML a.

The problem I have is that none of these are on the input instead I have them in a variable like below:

<xsl:variable name="ExtractA" select="document('a.xml')" /> <xsl:variable name="ExtractB" select="document('b.xml')" />

Unfortunately, i cannot make it work in xslt v1 but I would need it because of some limitations of the platform I use.

I have a working example in v3 xslt for the test, so I would need the same functionality in v1:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes" omit-xml-declaration="yes" />
<xsl:variable name="ExtractA">
    <Reports>
<Fields>
 <PositionID>000101</PositionID>
 <Date>2021-04-19</Date>
 <Name></Name>
</Fields>
<Fields>
 <PositionID>000100</PositionID>
 <Date>2021-04-19</Date>
 <Name></Name>
</Fields>
</Reports>
    </xsl:variable>
    <xsl:variable name="ExtractB">
    <AOID>
      <Employee>
        <Name>YYY</Name>
        <PositionID>000100</PositionID>
      </Employee>
      <Employee>
        <Name>XXX</Name>
        <PositionID>000101</PositionID>
      </Employee>
    </AOID>
    </xsl:variable>


<xsl:template match="/">

          <xsl:if test="$ExtractA/Reports[1]/Fields[1]/Name != ''">
              <xsl:copy-of select="$ExtractA" />
          </xsl:if>
          <xsl:if test="$ExtractA/Reports[1]/Fields[1]/Name = ''">
<Reports>
             <xsl:for-each-group select="$ExtractA/Reports/Fields, $ExtractB/AOID/Employee" group-by="PositionID">
                                      <xsl:copy>
                                          <xsl:copy-of select="current-group()[1]/Date, PositionID, current-group()[2]/Name"/>
                                      </xsl:copy>
                      </xsl:for-each-group>
</Reports>

          </xsl:if>

</xsl:template>
</xsl:stylesheet>

Upvotes: 0

Views: 52

Answers (2)

zx485
zx485

Reputation: 29022

You can do this better without the xsl:key instruction:

<xsl:variable name="ExtractA" select="document('a.xml')" /> 
<xsl:variable name="ExtractB" select="document('b.xml')" />

<xsl:template match="/">
    <Reports>
        <xsl:for-each select="$ExtractA/Reports/Fields">
            <Fields>
                <xsl:variable name="curItemID" select="PositionID" />
                <xsl:variable name="secItem"   select="$ExtractB/AOID/Employee[PositionID=$curItemID]" />
                <xsl:choose>
                    <xsl:when test="not(normalize-space(Name))">
                        <xsl:copy-of select="$secItem/Name" />
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:copy-of select="Name" />
                    </xsl:otherwise>
                </xsl:choose>
                <xsl:copy-of select="Date" />
                <xsl:copy-of select="PositionID" />
            </Fields>
        </xsl:for-each>
    </Reports>
</xsl:template>

The expression not(normalize-space(Name)) matches either empty strings or not-present elements.
The output for your sample is

<Reports>
    <Fields>
        <Name>XXX</Name>
        <Date>2021-04-19</Date>
        <PositionID>000101</PositionID>
    </Fields>
    <Fields>
        <Name>YYY</Name>
        <Date>2021-04-19</Date>
        <PositionID>000100</PositionID>
    </Fields>
</Reports>

The downside of this approach is, that the output only contains items from file A. If file B contains more items, they'd be ignored.

Upvotes: 0

michael.hor257k
michael.hor257k

Reputation: 116957

Try something along the lines of:

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:template match="/">
    <Reports>
        <xsl:for-each select="document('a.xml')/Reports/Fields">
            <xsl:copy>
                <xsl:copy-of select="PositionID | Date"/>
                <xsl:choose>
                    <xsl:when test="Name/text()">
                        <xsl:copy-of select="Name"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:copy-of select="document('b.xml')/AOID/Employee[PositionID=current()/PositionID]/Name"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:copy>
        </xsl:for-each>
    </Reports>  
</xsl:template>

</xsl:stylesheet>

When this is applied to any XML input, the result will be:

<?xml version="1.0" encoding="UTF-8"?>
<Reports>
  <Fields>
    <PositionID>000101</PositionID>
    <Date>2021-04-19</Date>
    <Name>XXX</Name>
  </Fields>
  <Fields>
    <PositionID>000100</PositionID>
    <Date>2021-04-19</Date>
    <Name>YYY</Name>
  </Fields>
</Reports>

provided that the two documents, a.xml and b.xml, are in the same location as the XSL stylesheet.

A more efficient solution would use a key to get the corresponding Name, but this is more difficult to do in XSLT 1.0 - see, for example, https://stackoverflow.com/a/34663060/3016153.

Upvotes: 0

Related Questions