Sqandr
Sqandr

Reputation: 75

Loop through a list of XML nodes and output based on another list the value of an attribute from the first list using XSLT

Here is my input XML:

<AllTitleByOrder>
  <CustProp name="Title of x CP" CPID='x'/>
  <CustProp name="Title of y CP" CPID='y'/>
  <CustProp name="Title of z CP" CPID='z'/>
</AllTitleByOrder>
<ResultSet>
  <ResultItem …>
    <Document ContentType="document" … >
      <CustomProperties>
         <CustPropItem CPID='x' value="value of x for doc"/>
      </CustomProperties>
    </Document>
    <Document ContentType="document" … >
      <CustomProperties>
         <CustPropItem CPID='x' value="value of x for doc"/>
         <CustPropItem CPID='y' value="value of y for doc"/>
         <CustPropItem CPID='z' value="value of z for doc"/>
      </CustomProperties>
    </Document>
  </ResultItem>
  <ResultItem …>
    <Document ContentType="research" … >
      <CustomProperties>
        <CustPropItem CPID='y' value="value of y for rsr"/>
      </CustomProperties>
    </Document>
  </ResultItem>
  <ResultItem>
    <Document ContentType="pleading" … >
      <CustomProperties>
        <CustPropItem CPID='z' value="value of z for pldg"/>
      </CustomProperties>
    </Document>
  </ResultItem>
</ResultSet>

I need the output to look like this:

Title of x CP ---------- Title of y CP ---------- Title of z CP
value of x for doc
value of x for doc       value of y for rsr       value of z for pldg
                         value of y for rsr
                                                  value of z for pldg

My trouble is filtering (output value of x for title X ONLY) since all content is under the same XML tag (namely Document).

I'm looping through //AllTitleByOrder/CustProp/@CPID but I'm not sure how to select the right value (corresponding to the right title) or just add white space.

Upvotes: 0

Views: 600

Answers (2)

Sean B. Durkin
Sean B. Durkin

Reputation: 12729

This XSLT 2.0 stylesheet ...

<xsl:transform
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

<xsl:output method="text" encoding="UTF-8" />
<xsl:strip-space elements="*" />
<xsl:variable name="new-line" select="'&#x0A;&#x0D;'
   (: Adjust as required for your file-system. :)" />

<xsl:template match="/*">
  <!-- Do the header line. -->
  <xsl:value-of select="AllTitleByOrder/CustProp/@name" separator="," />
  <xsl:value-of select="$new-line" />

  <!-- Now the data rows. -->
  <xsl:apply-templates select="ResultSet/ResultItem/Document" />
</xsl:template>

<xsl:template match="Document">
  <xsl:value-of select="
    for $ID in ../../../AllTitleByOrder/CustProp/@CPID
      return concat( CustomProperties/CustPropItem[@CPID eq $ID]/@value, '')"
    separator="," />
  <xsl:value-of select="$new-line" />
</xsl:template>

</xsl:transform>

... when applied to this input document ...

<root>
<AllTitleByOrder>
  <CustProp name="Title of x CP" CPID='x'/>
  <CustProp name="Title of y CP" CPID='y'/>
  <CustProp name="Title of z CP" CPID='z'/>
</AllTitleByOrder>
<ResultSet>
  <ResultItem>
    <Document ContentType="document">
      <CustomProperties>
         <CustPropItem CPID='x' value="value of x for doc"/>
      </CustomProperties>
    </Document>
    <Document ContentType="document">
      <CustomProperties>
         <CustPropItem CPID='x' value="value of x for doc"/>
         <CustPropItem CPID='y' value="value of y for doc"/>
         <CustPropItem CPID='z' value="value of z for doc"/>
      </CustomProperties>
    </Document>
  </ResultItem>
  <ResultItem>
    <Document ContentType="research">
      <CustomProperties>
        <CustPropItem CPID='y' value="value of y for rsr"/>
      </CustomProperties>
    </Document>
  </ResultItem>
  <ResultItem>
    <Document ContentType="pleading">
      <CustomProperties>
        <CustPropItem CPID='z' value="value of z for pldg"/>
      </CustomProperties>
    </Document>
  </ResultItem>
</ResultSet>
</root>

... will yield this csv output document ...

Title of x CP,Title of y CP,Title of z CP
value of x for doc,,
value of x for doc,value of y for doc,value of z for doc
,value of y for rsr,
,,value of z for pldg

Fancy Pants One-Liner

If you want to get all fancy pants, you can solve the whole problem in one line of XPATH ...

<xsl:transform
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

<xsl:output method="text" encoding="UTF-8" />
<xsl:strip-space elements="*" />
<xsl:variable name="new-line" select="'&#x0A;&#x0D;'" />

<xsl:template match="/*">
  <xsl:value-of select="
    string-join( AllTitleByOrder/CustProp/@name, ','), (: Header :)
    ResultSet/ResultItem/Document/string-join(         (: Rows   :)
      for $ID in ../../../AllTitleByOrder/CustProp/@CPID
        return concat( CustomProperties/CustPropItem[@CPID eq $ID]/@value, ''),
      ',')
    " separator="{$new-line}"/>
</xsl:template>

</xsl:transform>

Upvotes: 0

michael.hor257k
michael.hor257k

Reputation: 117165

Assuming that (a) you want an HTML table as the result, and (b) you have a well-formed XML document as the input, you could try something like:

XSLT 1.0

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

<xsl:key name="item-by-rowXcol" match="CustPropItem" use="concat(@CPID, generate-id(../..))" />

<xsl:template match="/root">
    <xsl:variable name="columns" select="AllTitleByOrder/CustProp"/>
    <table border="1">
        <thead>
            <tr>
                <xsl:for-each select="$columns">
                    <th>
                        <xsl:value-of select="@name"/>
                    </th>
                </xsl:for-each>
            </tr>
        </thead>
        <tbody>
            <xsl:for-each select="ResultSet/ResultItem/Document">
                <xsl:variable name="row-id" select="generate-id()" />
                <tr>
                    <xsl:for-each select="$columns">
                        <td>
                            <xsl:value-of select="key('item-by-rowXcol', concat(@CPID, $row-id))/@value" />
                        </td>
                    </xsl:for-each>
                </tr>
            </xsl:for-each>
        </tbody>
    </table>
</xsl:template>

</xsl:stylesheet>

Applied to the following test input:

XML

<root>
  <AllTitleByOrder>
    <CustProp name="Title of x CP" CPID="x"/>
    <CustProp name="Title of y CP" CPID="y"/>
    <CustProp name="Title of z CP" CPID="z"/>
  </AllTitleByOrder>
  <ResultSet>
    <ResultItem>
      <Document ContentType="document">
        <CustomProperties>
          <CustPropItem CPID="x" value="value of x for doc"/>
        </CustomProperties>
      </Document>
      <Document ContentType="document">
        <CustomProperties>
          <CustPropItem CPID="x" value="value of x for doc"/>
          <CustPropItem CPID="y" value="value of y for doc"/>
          <CustPropItem CPID="z" value="value of z for doc"/>
        </CustomProperties>
      </Document>
    </ResultItem>
    <ResultItem>
      <Document ContentType="research">
        <CustomProperties>
          <CustPropItem CPID="y" value="value of y for rsr"/>
        </CustomProperties>
      </Document>
    </ResultItem>
    <ResultItem>
      <Document ContentType="pleading">
        <CustomProperties>
          <CustPropItem CPID="z" value="value of z for pldg"/>
        </CustomProperties>
      </Document>
    </ResultItem>
  </ResultSet>
</root>

the result (rendered) will be:

enter image description here

Upvotes: 3

Related Questions