开发者

Using XSLT, how can I produce a table with elements at the position of the node's attributes?

Given the following XML:

<items>
    <item>
        <name>A</name>
        <address>0</address>
        <start>0</start>
        <size>2</size>
    </item>
    <item>
        <name>B</name>
        <address>1</address>
        <start>2</start>
        <size>4</size>
    </item>
    <item>
        <name>C</name>
        <address>2</address>
        <start>5</start>
        <size>2</size>
    </item>
</items>

I want to generate the following output including colspan's

+---------+------+------+------+------+------+------+------+------+
| Address | 7    | 6    | 5    | 4    | 3    | 2    | 1    | 0    |
+---------+------+------+------+------+------+------+------+------+
| 0       |      |      |      |  开发者_运维百科    |      |      | A           |
+---------+------+------+------+------+------+------+------+------+
| 1       |      |      | B                         |      |      |
+---------+------+------+------+------+------+------+------+------+
| 2       |      | C           |      |      |      |      |      |
+---------+------+------+------+------+------+------+------+------+
| 3       |      |      |      |      |      |      |      |      |
+---------+------+------+------+------+------+------+------+------+

I think I would be able to accomplish this with a mutable xslt variable, but alas, there's no such thing.

Is it even possible? How?

Edit:

Two more requirements:

  1. It must also be possible for two items to exist at the same address
  2. Empty addresses may exist and must be generated in the output

For example:

<items>
    <item>
        <name>D</name>
        <address>0</address>
        <start>0</start>
        <size>2</size>
    </item>
    <item>
        <name>E</name>
        <address>0</address>
        <start>3</start>
        <size>4</size>
    </item>
    <item>
        <name>F</name>
        <address>7</address>
        <start>5</start>
        <size>2</size>
    </item>
</items>

Should yield:

+---------+------+------+------+------+------+------+------+------+
| Address | 7    | 6    | 5    | 4    | 3    | 2    | 1    | 0    |
+---------+------+------+------+------+------+------+------+------+
| 0       |      | E                         |      | D           |
+---------+------+------+------+------+------+------+------+------+
| 1       |      |      |      |      |      |      |      |      |
+---------+------+------+------+------+------+------+------+------+
| 2       |      |      |      |      |      |      |      |      |
+---------+------+------+------+------+------+------+------+------+
| 3       |      |      |      |      |      |      |      |      |
+---------+------+------+------+------+------+------+------+------+
| 4       |      |      |      |      |      |      |      |      |
+---------+------+------+------+------+------+------+------+------+
| 5       |      |      |      |      |      |      |      |      |
+---------+------+------+------+------+------+------+------+------+
| 6       |      |      |      |      |      |      |      |      |
+---------+------+------+------+------+------+------+------+------+
| 7       |      | F           |      |      |      |      |      |
+---------+------+------+------+------+------+------+------+------+

Output format (text/html) doesn't really matter.


Here is an XSLT 2.0 stylesheet:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xsd"
  version="2.0">

  <xsl:output method="html" indent="yes"/>

  <xsl:variable name="cols" as="xsd:integer*"
    select="reverse(0 to (max(/items/item/xsd:integer((start + size - 1))) + 1))"/>

  <xsl:template match="/">
    <html lang="en">
      <head>
        <title>Example</title>
      </head>
      <body>
        <xsl:apply-templates/>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="items">
    <table border="1">
      <thead>
        <tr>
          <th>Address</th>
          <xsl:for-each select="$cols">
            <th>
              <xsl:value-of select="."/>
            </th>
          </xsl:for-each>
        </tr>
      </thead>
      <tbody>
        <xsl:apply-templates/>
      </tbody>
    </table>
  </xsl:template>

  <xsl:template match="item">
    <xsl:variable name="item" as="element(item)" select="."/>
    <tr>
      <td>
        <xsl:value-of select="address"/>
      </td>
      <xsl:for-each select="$cols[. > $item/(start + size - 1)]">
        <td>&#160;</td>
      </xsl:for-each>
      <td colspan="{size}">
        <xsl:value-of select="name"/>
      </td>
      <xsl:for-each select="$cols[. &lt; $item/start]">
        <td>&#160;</td>
      </xsl:for-each>
    </tr>
  </xsl:template>

</xsl:stylesheet>

You can run XSLT 2.0 stylesheets with Saxon 9 or with AltovaXML tools.

[edit] If you need an XSLT 1.0 solution but you can make use of exsl:node-set then find below an attempt to translate the XSLT 2.0 approach back to an XSLT 1.0 stylesheet:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:exsl="http://exslt.org/common"
  exclude-result-prefixes="exsl"
  version="1.0">

  <xsl:output method="html" indent="yes"/>

  <xsl:variable name="adds-rtf">
    <xsl:for-each select="/items/item">
      <add>
        <xsl:value-of select="start + size - 1"/>
      </add>
    </xsl:for-each>
  </xsl:variable>

  <xsl:variable name="max">
    <xsl:for-each select="exsl:node-set($adds-rtf)/add">
      <xsl:sort select="." data-type="number" order="descending"/>
      <xsl:if test="position() = 1">
        <xsl:value-of select="."/>
      </xsl:if>
    </xsl:for-each>
  </xsl:variable>

  <xsl:variable name="cols-rtf">
    <xsl:call-template name="make-columns">
      <xsl:with-param name="max" select="$max + 1"/>
    </xsl:call-template>
  </xsl:variable>

  <xsl:template name="make-columns">
    <xsl:param name="max"/>
    <xsl:if test="$max > -1">
      <col>
        <xsl:value-of select="$max"/>
      </col>
      <xsl:call-template name="make-columns">
        <xsl:with-param name="max" select="$max - 1"/>
      </xsl:call-template>
    </xsl:if>
  </xsl:template>

  <xsl:variable name="cols" select="exsl:node-set($cols-rtf)/col"/>

  <xsl:template match="/">
    <html lang="en">
      <head>
        <title>Example</title>
      </head>
      <body>
        <xsl:apply-templates/>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="items">
    <table border="1">
      <thead>
        <tr>
          <th>Address</th>
          <xsl:for-each select="$cols">
            <th>
              <xsl:value-of select="."/>
            </th>
          </xsl:for-each>
        </tr>
      </thead>
      <tbody>
        <xsl:apply-templates/>
      </tbody>
    </table>
  </xsl:template>

  <xsl:template match="item">
    <xsl:variable name="item" select="."/>
    <tr>
      <td>
        <xsl:value-of select="address"/>
      </td>
      <xsl:for-each select="$cols[. > ($item/start + $item/size - 1)]">
        <td>&#160;</td>
      </xsl:for-each>
      <td colspan="{size}">
        <xsl:value-of select="name"/>
      </td>
      <xsl:for-each select="$cols[. &lt; $item/start]">
        <td>&#160;</td>
      </xsl:for-each>
    </tr>
  </xsl:template>

</xsl:stylesheet>


As easy as this:

I. An XSLT 2.0 solution:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 exclude-result-prefixes="xs">

 <xsl:output omit-xml-declaration="yes"
  method="html" indent="yes" encoding="utf-8"/>

 <xsl:variable name="vMaxCols" select=
  "xs:integer(max(/*/*/(start + size)))+1
  "/>

 <xsl:template match="/*">
  <html>
    <table border="1">
      <thead>
        <tr>
          <th>Address</th>
          <xsl:for-each select="1 to $vMaxCols">
            <th>
              <xsl:value-of select="$vMaxCols -."/>
            </th>
          </xsl:for-each>
        </tr>
      </thead>
      <tbody>
        <xsl:apply-templates/>
      </tbody>
    </table>
  </html>
 </xsl:template>

 <xsl:template match="item">
  <tr>
    <td width="100"><xsl:sequence select="address"/></td>
    <xsl:for-each select="1 to $vMaxCols - xs:integer(start+size)">
      <td width="100">&#xA0;</td>
    </xsl:for-each>
    <td width="100" colspan="{size}">
      <xsl:value-of select="name"/>
    </td>
    <xsl:for-each select="1 to start">
      <td width="100">&#xA0;</td>
    </xsl:for-each>
  </tr>
 </xsl:template>
</xsl:stylesheet>

II. XSLT 1.0 solution, using FXSL:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common"
 xmlns:f="http://fxsl.sf.net/"
 xmlns:myFun="f:myFun"
 exclude-result-prefixes="ext f myFun"
 >
 <xsl:import href="maximum.xsl"/>
 <xsl:import href="iter.xsl"/>

 <xsl:output method="html" indent="yes" encoding="utf-8"/>

 <xsl:variable name="vrtfEndCols">
  <xsl:for-each select="/*/*">
    <ec><xsl:value-of select="start+size+1"/></ec>
  </xsl:for-each>
 </xsl:variable>

 <xsl:variable name="vEndCols"
  select="ext:node-set($vrtfEndCols)/*"/>

  <xsl:variable name="vMaxCols">
    <xsl:call-template name="maximum">
     <xsl:with-param name="pList" select="$vEndCols"/>
    </xsl:call-template>
  </xsl:variable>

 <myFun:printTh/>

 <myFun:printEmpty/>

 <myFun:header><x></x></myFun:header>

 <xsl:variable name="vFunPrintTh" select=
  "document('')/*/myFun:printTh[1]"/>

 <xsl:variable name="vFunPrintEmpty" select=
  "document('')/*/myFun:printEmpty[1]"/>

  <xsl:variable name="vIterHeader"
   select="document('')/*/myFun:header[1]"/>

  <xsl:variable name="vrtfHeader">
   <xsl:call-template name="iter">
     <xsl:with-param name="pTimes" select="$vMaxCols"/>
     <xsl:with-param name="pFun" select="$vFunPrintTh"/>
     <xsl:with-param name="pX" select="$vIterHeader"/>
   </xsl:call-template>
  </xsl:variable>

 <xsl:template match="/*">
  <html>
  <head />
    <table border="1">
      <thead>
        <tr>
          <th>Address</th>
          <xsl:copy-of select=
          "ext:node-set($vrtfHeader)/*
                         [position() > 1]
          "/>
        </tr>
      </thead>
      <tbody>
        <xsl:apply-templates/>
      </tbody>
    </table>
  </html>
 </xsl:template>

 <xsl:template match="item">
  <tr>
    <td width="100"><xsl:value-of select="address"/></td>
      <xsl:variable name="vrtfLeftBlank">
         <xsl:call-template name="iter">
           <xsl:with-param name="pTimes"
             select="$vMaxCols -(start+size)"/>
           <xsl:with-param name="pFun" select="$vFunPrintEmpty"/>
           <xsl:with-param name="pX" select="$vIterHeader"/>
         </xsl:call-template>
      </xsl:variable>

      <xsl:copy-of select=
        "ext:node-set($vrtfLeftBlank)/*
                         [position() > 1]
        "/>

    <td width="100" colspan="{size}">
      <xsl:value-of select="name"/>
    </td>

      <xsl:variable name="vrtfRightBlank">
         <xsl:call-template name="iter">
           <xsl:with-param name="pTimes" select="start"/>
           <xsl:with-param name="pFun" select="$vFunPrintEmpty"/>
           <xsl:with-param name="pX" select="$vIterHeader"/>
         </xsl:call-template>
      </xsl:variable>

      <xsl:copy-of select=
        "ext:node-set($vrtfRightBlank)/*
                         [position() > 1]
        "/>
  </tr>
 </xsl:template>

  <xsl:template match="myFun:printTh" mode="f:FXSL">
  <xsl:param name="arg1"/>

   <xsl:copy-of select="$arg1"/>
   <th>
     <xsl:value-of select="$vMaxCols -count($arg1/*)"/>
   </th>
 </xsl:template>

 <xsl:template match="myFun:printEmpty" mode="f:FXSL">
  <xsl:param name="arg1"/>

   <xsl:copy-of select="$arg1"/>
   <td width="100">&#xA0;</td>
 </xsl:template>
</xsl:stylesheet>

When the above transformation is applied on the provided XML file, the desired output is produced.


Here is a new XSLT 2.0 stylesheet for the revised question. It is kind of ugly in my view but it should do the job:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xsd"
  version="2.0">

  <xsl:output method="html" indent="yes"/>

  <xsl:variable name="cols" as="xsd:integer*"
    select="reverse(0 to (max(/items/item/xsd:integer((start + size - 1))) + 1))"/>

  <xsl:variable name="addresses" as="xsd:integer*"
    select="0 to max(/items/item/xsd:integer(address))"/>

  <xsl:template match="/">
    <html lang="en">
      <head>
        <title>Example</title>
      </head>
      <body>
        <xsl:apply-templates/>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="items">
    <table border="1">
      <thead>
        <tr>
          <th>Address</th>
          <xsl:for-each select="$cols">
            <th width="100">
              <xsl:value-of select="."/>
            </th>
          </xsl:for-each>
        </tr>
      </thead>
      <tbody>
        <xsl:variable name="items" as="element(item)*" select="item"/>
        <xsl:for-each select="$addresses">
          <tr>
            <th>
              <xsl:value-of select="."/>
            </th>
            <xsl:variable name="cells" as="element(cell)*">
              <xsl:for-each select="$items[address = current()]">
                <xsl:sort select="xsd:integer(start) + xsd:integer(size)" order="descending"/>
                <cell>
                  <xsl:copy-of select="name"/>
                  <start><xsl:value-of select="start + size - 1"/></start>
                  <colspan><xsl:value-of select="size"/></colspan>
                </cell>
              </xsl:for-each>
            </xsl:variable>
            <xsl:for-each select="$cols">
              <xsl:variable name="cell" select="$cells[start = current()]"/>
              <xsl:choose>
                <xsl:when test="$cell">
                  <td colspan="{$cell/colspan}">
                    <xsl:value-of select="$cell/name"/>
                  </td>
                </xsl:when>
                <xsl:when test="$cells[current() &lt; start and current() &gt;= (start - colspan + 1)]"></xsl:when>
                <xsl:otherwise>
                  <td>&#160;</td>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:for-each>
          </tr>
        </xsl:for-each>
      </tbody>
    </table>
  </xsl:template>

</xsl:stylesheet>

When applied with Saxon 9 to the latest XML input the result is as follows:

<html lang="en">
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Example</title>
   </head>
   <body>
      <table border="1">
         <thead>
            <tr>
               <th>Address</th>
               <th width="100">7</th>
               <th width="100">6</th>
               <th width="100">5</th>
               <th width="100">4</th>
               <th width="100">3</th>
               <th width="100">2</th>
               <th width="100">1</th>
               <th width="100">0</th>
            </tr>
         </thead>
         <tbody>
            <tr>
               <th>0</th>
               <td>&nbsp;</td>
               <td colspan="4">E</td>
               <td>&nbsp;</td>
               <td colspan="2">D</td>
            </tr>
            <tr>
               <th>1</th>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
            </tr>
            <tr>
               <th>2</th>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
            </tr>
            <tr>
               <th>3</th>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
            </tr>
            <tr>
               <th>4</th>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
            </tr>
            <tr>
               <th>5</th>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
            </tr>
            <tr>
               <th>6</th>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
            </tr>
            <tr>
               <th>7</th>
               <td>&nbsp;</td>
               <td colspan="2">F</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
            </tr>
         </tbody>
      </table>
   </body>
</html>


Here is the XSLT 2.0 solution for the revised problem:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:my="my:funs"
 exclude-result-prefixes="xs my">

 <xsl:output omit-xml-declaration="yes"
  method="html" indent="yes" encoding="utf-8"/>

 <xsl:key name="kItemByAddress" match="item" use="xs:integer(address)"/>

 <xsl:variable name="vMaxCols" select=
  "xs:integer(max(/*/*/(start + size)))+1
  "/>

 <xsl:variable name="vMinAddress" select=
  "xs:integer(min(/*/*/address))"/>

 <xsl:variable name="vMaxAddress" select=
  "xs:integer(max(/*/*/address))"/>

 <xsl:variable name="vDoc" select="/"/>

 <xsl:template match="/*">
  <html>
    <table border="1">
      <thead>
        <tr>
          <th>Address</th>
          <xsl:for-each select="1 to $vMaxCols">
            <th>
              <xsl:value-of select="$vMaxCols -."/>
            </th>
          </xsl:for-each>
        </tr>
      </thead>
      <tbody>
        <xsl:for-each select="$vMinAddress to $vMaxAddress">
                  <tr>
                    <td width="100"><xsl:sequence select="."/></td>

            <xsl:variable name="vsortedItems" as="element()*">
              <xsl:perform-sort select="key('kItemByAddress', ., $vDoc)">
               <xsl:sort select="end" data-type="number" order="descending"/>
              </xsl:perform-sort>
            </xsl:variable>

            <xsl:for-each select="1 to $vMaxCols">
              <xsl:sequence select="my:cellAtPos($vMaxCols -.,$vsortedItems)"/>
            </xsl:for-each>
                  </tr>
        </xsl:for-each>
      </tbody>
    </table>
  </html>
 </xsl:template>

 <xsl:function name="my:cellAtPos" as="element()?">
  <xsl:param name="pcellNum" as="xs:integer"/>
  <xsl:param name="pSortedItems" as="element()*"/>

  <xsl:variable name="vEmptyCell" as="element()">
    <td width="100">&#xA0;</td>
  </xsl:variable>

  <xsl:variable name="vstartingItem" select=
     "$pSortedItems[(start+size -1) eq $pcellNum][1]"/>

  <xsl:variable name="vInsideItem" select=
    "$pSortedItems[(start+size -1) > $pcellNum
                 and
                   $pcellNum >= start
                  ][1]"/>

  <xsl:choose>
    <xsl:when test="not($vstartingItem | $vInsideItem)">
      <xsl:sequence select="$vEmptyCell"/>
    </xsl:when>
    <xsl:when test="$vstartingItem">
    <td width="100" colspan="{$vstartingItem/size}">
      <xsl:value-of select="$vstartingItem/name"/>
    </td>
    </xsl:when>
    <xsl:otherwise/>
  </xsl:choose>
 </xsl:function>
</xsl:stylesheet>

When this transformation is applied on the provided new XML document:

<items>
    <item>
        <name>D</name>
        <address>0</address>
        <start>0</start>
        <size>2</size>
    </item>
    <item>
        <name>E</name>
        <address>0</address>
        <start>3</start>
        <size>4</size>
    </item>
    <item>
        <name>F</name>
        <address>7</address>
        <start>5</start>
        <size>2</size>
    </item>
</items>

The wanted result is produced:

<html>
   <table border="1">
      <thead>
         <tr>
            <th>Address</th>
            <th>7</th>
            <th>6</th>
            <th>5</th>
            <th>4</th>
            <th>3</th>
            <th>2</th>
            <th>1</th>
            <th>0</th>
         </tr>
      </thead>
      <tbody>
         <tr>
            <td width="100">0</td>
            <td width="100">&nbsp;</td>
            <td width="100" colspan="4">E</td>
            <td width="100">&nbsp;</td>
            <td width="100" colspan="2">D</td>
         </tr>
         <tr>
            <td width="100">1</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
         </tr>
         <tr>
            <td width="100">2</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
         </tr>
         <tr>
            <td width="100">3</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
         </tr>
         <tr>
            <td width="100">4</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
         </tr>
         <tr>
            <td width="100">5</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
         </tr>
         <tr>
            <td width="100">6</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
         </tr>
         <tr>
            <td width="100">7</td>
            <td width="100">&nbsp;</td>
            <td width="100" colspan="2">F</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
            <td width="100">&nbsp;</td>
         </tr>
      </tbody>
   </table>
</html>


Declarative languages like XSLT can do a lot of the same things that mutables can using recursion. This python code can render your countdown from 7 to 0 without using mutables:

def countdown(i):
    if i==0:
       print 0
    else:
       print i
       countdown(i-1)
countdown(7)

For other items you don't even need recursion. You can do them declaratively. Your start and size fields tell you that between start and start+size you are not going to fill in the | character. So think about how you can render this table using conditional statements in a regular language, and it should be translatable to XSLT.


This works for me in Firefox and Opera (the whitespace is all over the place in IE).

Generating it as per your example ASCII table, as adding the appropriate HTML tags would introduce more noise around the core logic you're interested in.

test.xml:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="test.xsl"?>
<items>
    <item>
        <name>A</name>
        <address>0</address>
        <start>0</start>
        <size>2</size>
    </item>
    <item>
        <name>B</name>
        <address>1</address>
        <start>2</start>
        <size>4</size>
    </item>
    <item>
        <name>C</name>
        <address>2</address>
        <start>5</start>
        <size>2</size>
    </item>
    <item>
        <name>D</name>
        <address>3</address>
        <start>3</start>
        <size>1</size>
    </item>
</items>

test.xsl:

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

<xsl:output method="html" encoding="UTF-8" indent="no"
  doctype-public="-//W3C//DTD HTML 4.01//EN"
  doctype-system="http://www.w3.org/TR/html4/strict.dtd"/>

<xsl:template match="/items">
  <html lang="en">
  <head>
    <title>Test</title>
  </head>
  <body>
    <pre><xsl:text>+---------+------+------+------+------+------+------+------+------+
| Address | 7    | 6    | 5    | 4    | 3    | 2    | 1    | 0    |
+---------+------+------+------+------+------+------+------+------+
</xsl:text>
<xsl:apply-templates select="item"/></pre>
  </body>
  </html>
</xsl:template>

<xsl:template match="item">
  <xsl:text>| </xsl:text><xsl:value-of select="address"/><xsl:text>       |</xsl:text>
  <xsl:call-template name="cells">
    <xsl:with-param name="name" select="name"/>
    <xsl:with-param name="start" select="start"/>
    <xsl:with-param name="size" select="size"/>
  </xsl:call-template>
  <xsl:text>+---------+------+------+------+------+------+------+------+------+&#10;</xsl:text>
</xsl:template>

<xsl:template name="cells">
  <xsl:param name="count" select="7"/>
  <xsl:param name="name"/>
  <xsl:param name="start"/>
  <xsl:param name="size"/>

  <xsl:if test="$count &gt; -1">
    <!-- Leading cell space -->
    <xsl:text> </xsl:text>
    <!-- Name or space -->
    <xsl:choose>
      <xsl:when test="$count = $start + $size - 1">
        <xsl:value-of select="$name"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:text> </xsl:text>
      </xsl:otherwise>
    </xsl:choose>
    <!-- Trailing cell space -->
    <xsl:text>    </xsl:text>
    <!-- End cell marker or space to span -->
    <xsl:choose>
      <xsl:when test="$count = 0 or $size = 1">
        <xsl:text>|</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:choose>
          <xsl:when test="$count &gt; $start and $count &lt; $start + $size">
            <xsl:text> </xsl:text>
          </xsl:when>
          <xsl:otherwise>
            <xsl:text>|</xsl:text>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:otherwise>
    </xsl:choose>
    <!-- Line break after last cell -->
    <xsl:if test="$count = 0">
      <xsl:text>&#10;</xsl:text>
    </xsl:if>
    <xsl:call-template name="cells">
      <xsl:with-param name="count" select="$count - 1"/>
      <xsl:with-param name="name" select="$name"/>
      <xsl:with-param name="start" select="$start"/>
      <xsl:with-param name="size" select="$size"/>
    </xsl:call-template>
  </xsl:if>
</xsl:template>

</xsl:stylesheet>

Generated output:

+---------+------+------+------+------+------+------+------+------+
| Address | 7    | 6    | 5    | 4    | 3    | 2    | 1    | 0    |
+---------+------+------+------+------+------+------+------+------+
| 0       |      |      |      |      |      |      | A           |
+---------+------+------+------+------+------+------+------+------+
| 1       |      |      | B                         |      |      |
+---------+------+------+------+------+------+------+------+------+
| 2       |      | C           |      |      |      |      |      |
+---------+------+------+------+------+------+------+------+------+
| 3       |      |      |      |      | D    |      |      |      |
+---------+------+------+------+------+------+------+------+------+
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜