title: Convert nested table xml to CSV, where table elements are concatenations of table name, row, and column
body: <p>I have Source XML in this format.  The relevant part of the source is under the <strong>values</strong>  tag, nested about four levels down, and represents one or more tables of data where the elements are of the format: name = <strong>TableName-Row#.FieldName</strong> and value = <strong>fieldValue</strong>.  </p>
  
  <pre><code>&lt;root&gt;
      &lt;someData&gt;1&lt;/someData&gt;
      &lt;otherData&gt;free&lt;/otherData&gt;
      &lt;parent1&gt;
          &lt;parent2&gt;
              &lt;parent3&gt;
                  &lt;values&gt;
                      &lt;data&gt;
                          &lt;name&gt;ComputerInfo-1.CPU&lt;/name&gt;
                          &lt;value&gt;4&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;ComputerInfo-1.Memory&lt;/name&gt;
                          &lt;value&gt;32&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;ComputerInfo-1.Storage&lt;/name&gt;
                          &lt;value&gt;1024&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;ComputerInfo-2.CPU&lt;/name&gt;
                          &lt;value&gt;2&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;ComputerInfo-2.Memory&lt;/name&gt;
                          &lt;value&gt;64&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;ComputerInfo-2.Storage&lt;/name&gt;
                          &lt;value&gt;2048&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;ComputerInfo-3.CPU&lt;/name&gt;
                          &lt;value&gt;4&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;ComputerInfo-3.Memory&lt;/name&gt;
                          &lt;value&gt;16&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;ComputerInfo-3.Storage&lt;/name&gt;
                          &lt;value&gt;512&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;UserInfo-1.firstName&lt;/name&gt;
                          &lt;value&gt;Mary&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;UserInfo-1.lastName&lt;/name&gt;
                          &lt;value&gt;Jones&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;UserInfo-1.login&lt;/name&gt;
                          &lt;value&gt;mjones&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;UserInfo-2.firstName&lt;/name&gt;
                          &lt;value&gt;Doctor&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;UserInfo-2.lastName&lt;/name&gt;
                          &lt;value&gt;Who&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;UserInfo-2.login&lt;/name&gt;
                          &lt;value&gt;dwho&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;UserInfo-3.firstName&lt;/name&gt;
                          &lt;value&gt;John&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;UserInfo-3.lastName&lt;/name&gt;
                          &lt;value&gt;Mellencamp&lt;/value&gt;
                      &lt;/data&gt;
                      &lt;data&gt;
                          &lt;name&gt;UserInfo-3.login&lt;/name&gt;
                          &lt;value&gt;cougar69&lt;/value&gt;
                      &lt;/data&gt;
                  &lt;/values&gt;
              &lt;/parent3&gt;
          &lt;/parent2&gt;
      &lt;/parent1&gt;
  &lt;/root&gt;
  </code></pre>
  
  <p>The desired result is a CSV file with the header row on top, followed by the corresponding rows:</p>
  
  <pre><code>CPU,Memory,Storage
  4,32,1024
  2,64,2048
  4,16,512
  </code></pre>
  
  <p>I also wanted to use a variable to hold the particular table I wish to process, e.g., </p>
  
  <pre><code>&lt;xsl:variable name="table" select="/root/tableName"/&gt;
  </code></pre>
  
  <p>This is because I have the ability via the application to include the tableName in the source XML if I choose to.  However, <strong>xsl:key</strong> doesn't allow me to use variables in my match and I'm also forced to use XSLT 1.0.</p>
  
  <p>My issue is I can do this in steps, but I really need to do this in a single transform.<br>
  Here's what I have so far.</p>
  
  <p>I can pass the Source XML to this transform:</p>
  
  <pre><code>&lt;xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
  
      &lt;xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/&gt;
  
      &lt;xsl:template match="/root/parent1/parent2/parent3/values/data"&gt;
          &lt;xsl:element name="{name}"&gt;
              &lt;xsl:value-of select="value" /&gt;
          &lt;/xsl:element&gt;
      &lt;/xsl:template&gt;
  
      &lt;xsl:template match="@* | node()"&gt;
          &lt;xsl:copy&gt;
              &lt;xsl:apply-templates select="@*|node()"/&gt;
          &lt;/xsl:copy&gt;
      &lt;/xsl:template&gt;
  
      &lt;xsl:template match="/"&gt;
          &lt;values&gt;
              &lt;xsl:apply-templates select="//values/node()"/&gt;
          &lt;/values&gt;
      &lt;/xsl:template&gt;
  
  &lt;/xsl:stylesheet&gt;
  </code></pre>
  
  <p>and it will produce this result:</p>
  
  <pre><code>&lt;values&gt;
      &lt;ComputerInfo-1.CPU&gt;4&lt;/ComputerInfo-1.CPU&gt;
      &lt;ComputerInfo-1.Memory&gt;32&lt;/ComputerInfo-1.Memory&gt;
      &lt;ComputerInfo-1.Storage&gt;1024&lt;/ComputerInfo-1.Storage&gt;
      &lt;ComputerInfo-2.CPU&gt;2&lt;/ComputerInfo-2.CPU&gt;
      &lt;ComputerInfo-2.Memory&gt;64&lt;/ComputerInfo-2.Memory&gt;
      &lt;ComputerInfo-2.Storage&gt;2048&lt;/ComputerInfo-2.Storage&gt;
      &lt;ComputerInfo-3.CPU&gt;4&lt;/ComputerInfo-3.CPU&gt;
      &lt;ComputerInfo-3.Memory&gt;16&lt;/ComputerInfo-3.Memory&gt;
      &lt;ComputerInfo-3.Storage&gt;512&lt;/ComputerInfo-3.Storage&gt;
      &lt;UserInfo-1.firstName&gt;Mary&lt;/UserInfo-1.firstName&gt;
      &lt;UserInfo-1.lastName&gt;Jones&lt;/UserInfo-1.lastName&gt;
      &lt;UserInfo-1.login&gt;mjones&lt;/UserInfo-1.login&gt;
      &lt;UserInfo-2.firstName&gt;Doctor&lt;/UserInfo-2.firstName&gt;
      &lt;UserInfo-2.lastName&gt;Who&lt;/UserInfo-2.lastName&gt;
      &lt;UserInfo-2.login&gt;dwho&lt;/UserInfo-2.login&gt;
      &lt;UserInfo-3.firstName&gt;John&lt;/UserInfo-3.firstName&gt;
      &lt;UserInfo-3.lastName&gt;Mellencamp&lt;/UserInfo-3.lastName&gt;
      &lt;UserInfo-3.login&gt;cougar69&lt;/UserInfo-3.login&gt;
  &lt;/values&gt;
  </code></pre>
  
  <p>I can then take this result and apply the following transform:</p>
  
  <pre><code>&lt;xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
  
      &lt;xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/&gt;
  
      &lt;xsl:key name="elementByRow" match="/*/*[contains(name(), 'ComputerInfo')]" use="substring-before(name(), '.')"/&gt;
  
      &lt;xsl:template match="/*"&gt;
          &lt;values&gt;
              &lt;xsl:apply-templates select="*[generate-id() = generate-id(key('elementByRow', substring-before(name(), '.'))[1])]" /&gt;
          &lt;/values&gt;
      &lt;/xsl:template&gt;
  
      &lt;xsl:template match="*"&gt;
          &lt;Row&gt;
              &lt;xsl:for-each select="key('elementByRow', substring-before(name(), '.'))"&gt;
                  &lt;xsl:element name="{substring-after(name(), '.')}"&gt;
                      &lt;xsl:value-of select="." /&gt;
                  &lt;/xsl:element&gt;
              &lt;/xsl:for-each&gt;
          &lt;/Row&gt;
      &lt;/xsl:template&gt;
  
  &lt;/xsl:stylesheet&gt;
  </code></pre>
  
  <p>which will produce this result:</p>
  
  <pre><code>&lt;values&gt;
      &lt;Row&gt;
          &lt;CPU&gt;4&lt;/CPU&gt;
          &lt;Memory&gt;32&lt;/Memory&gt;
          &lt;Storage&gt;1024&lt;/Storage&gt;
      &lt;/Row&gt;
      &lt;Row&gt;
          &lt;CPU&gt;2&lt;/CPU&gt;
          &lt;Memory&gt;64&lt;/Memory&gt;
          &lt;Storage&gt;2048&lt;/Storage&gt;
      &lt;/Row&gt;
      &lt;Row&gt;
          &lt;CPU&gt;4&lt;/CPU&gt;
          &lt;Memory&gt;16&lt;/Memory&gt;
          &lt;Storage&gt;512&lt;/Storage&gt;
      &lt;/Row&gt;
  &lt;/values&gt;
  </code></pre>
  
  <p>I specifically aimed to get it to this previous format, because I found another posting here ( <a href="https://stackoverflow.com/questions/3056579/convert-xml-document-to-comma-delimited-csv-file-using-xslt-stylesheet">convert xml document to comma delimited (CSV) file using xslt stylesheet</a> ), which allows me to take this result and apply the transform from the aforementioned post to get the desired result.  Here is the transform from that post for reference:</p>
  
  <pre><code>&lt;xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
      &lt;xsl:key name="field" match="/*/*/*" use="name()" /&gt;
      &lt;xsl:output method="text"/&gt;
  
      &lt;xsl:template match="/"&gt;
          &lt;xsl:for-each select="*/*/*[generate-id() = generate-id(key('field',name())[1])]"&gt;
              &lt;xsl:value-of select="name()" /&gt;
              &lt;xsl:if test="position() != last()"&gt;,&lt;/xsl:if&gt;
          &lt;/xsl:for-each&gt;
          &lt;xsl:text&gt;&amp;#10;&lt;/xsl:text&gt;
          &lt;xsl:apply-templates select="*/*" mode="row"/&gt;
      &lt;/xsl:template&gt;
  
      &lt;xsl:template match="*" mode="row"&gt;
          &lt;xsl:variable name="row" select="*" /&gt;
          &lt;xsl:for-each select="/*/*/*[generate-id() = generate-id(key('field',name())[1])]"&gt;
              &lt;xsl:variable name="name" select="name()" /&gt;
              &lt;xsl:apply-templates select="$row[name()=$name]" mode="data" /&gt;
              &lt;xsl:if test="position() != last()"&gt;,&lt;/xsl:if&gt;
          &lt;/xsl:for-each&gt;
          &lt;xsl:text&gt;&amp;#10;&lt;/xsl:text&gt;
      &lt;/xsl:template&gt;
  
      &lt;xsl:template match="*" mode="data"&gt;
          &lt;xsl:choose&gt;
              &lt;xsl:when test="contains(text(),',')"&gt;
                  &lt;xsl:text&gt;&amp;quot;&lt;/xsl:text&gt;
                  &lt;xsl:call-template name="doublequotes"&gt;
                      &lt;xsl:with-param name="text" select="text()" /&gt;
                  &lt;/xsl:call-template&gt;
                  &lt;xsl:text&gt;&amp;quot;&lt;/xsl:text&gt;
              &lt;/xsl:when&gt;
              &lt;xsl:otherwise&gt;
                  &lt;xsl:value-of select="." /&gt;
              &lt;/xsl:otherwise&gt;
          &lt;/xsl:choose&gt;
          &lt;xsl:if test="position() != last()"&gt;,&lt;/xsl:if&gt;
      &lt;/xsl:template&gt;
  
      &lt;xsl:template name="doublequotes"&gt;
          &lt;xsl:param name="text" /&gt;
          &lt;xsl:choose&gt;
              &lt;xsl:when test="contains($text,'&amp;quot;')"&gt;
                  &lt;xsl:value-of select="concat(substring-before($text,'&amp;quot;'),'&amp;quot;&amp;quot;')" /&gt;
                  &lt;xsl:call-template name="doublequotes"&gt;
                      &lt;xsl:with-param name="text" select="substring-after($text,'&amp;quot;')" /&gt;
                  &lt;/xsl:call-template&gt;
              &lt;/xsl:when&gt;
              &lt;xsl:otherwise&gt;
                  &lt;xsl:value-of select="$text" /&gt;
              &lt;/xsl:otherwise&gt;
          &lt;/xsl:choose&gt;
      &lt;/xsl:template&gt;
  &lt;/xsl:stylesheet&gt;
  </code></pre>