XSLT - Creating Dynamic Grid

2019-04-14 21:48发布

问题:

I am creating dynamic table(grid) using xslt,

XSLT :

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

     <xsl:key name="RowAttribsByName" match="Row/@*"

      use="concat(generate-id(..), '|', name())"/>

 <xsl:variable name="vColNames" select=
  "/*/Columns/*[not(@Hidden = 'true')]/@Name"/>

 <xsl:template match="/*">
  <table border="1">
    <tr>
     <xsl:apply-templates select="Columns/*"/>
    </tr>
    <xsl:apply-templates select="Rows/Row"/>
  </table>
 </xsl:template>

 <xsl:template match="Column[not(@Hidden = 'true')]">
  <td><xsl:value-of select="@Caption"/></td>
 </xsl:template>

 <xsl:template match="Row">
  <tr>
   <xsl:apply-templates select="$vColNames">
     <xsl:with-param name="pRowId"
          select="generate-id()"/>
   </xsl:apply-templates>
  </tr>
 </xsl:template>

 <xsl:template match="Column/@*">
  <xsl:param name="pRowId"/>

  <td width="50%">
    <xsl:value-of select=
      "key('RowAttribsByName',
           concat($pRowId, '|', .)
           )
    "/>
  </td>
 </xsl:template>
</xsl:stylesheet>

XML Data :

<TableData>
    <Columns>
        <Column Name="ID" Hidden="true" />
        <Column Name="Name" Caption="Item Name" Link="Yes" Sort="Yes"/>
        <Column Name="Desc" Caption="Item Description" />
    </Columns>
    <Rows>
        <Row ID="0" Name="A" />
        <Row ID="1" Name="B" Desc="Some description"/>
        <Row ID="3" Name="C" />
    </Rows>
</TableData>

Expected Output:

<table border="1">
    <tbody>
        <tr>
            <td>
                <a onclick="javascript:SortColumn('Item Name')">Item Name</a>
            </td>
            <td>
                <a onclick="javascript:SortColumn('Item Description')">Item Name</a></td>
        </tr>
        <tr>
            <td width="50%">
                <a onclick="javascript:OpenDifferentPage('A','0')">A</a>
            </td>
            <td width="50%"></td>
        </tr>
        <tr>
            <td width="50%">B</td>
            <td width="50%">Some description</td>
        </tr>
        <tr>
            <td width="50%">C</td>
            <td width="50%"></td>
        </tr>
    </tbody>
</table>

I am beginner in XSLT,

I want to check here that If column have "Link" attribute ="yes" then i need to display Data in between anchor tag(Name).

I am create many complex functionality on this column. So here can i made template for particular columns(Column are 15 but it depend on user selection, if user select 8 column for display and also it must maintain ORDER of Columns)

It is best that if i can create new templates for all columns with maintain column Order as per Columns data are passed.

Thank you for your anticipation

回答1:

For a beginner in XSLT, you are already off to a very good start here, especially with the use of xsl:key.

To answer your immediate question, what you could do, instead of having a single template matching the attributes of the Column elements as presently....

<xsl:template match="Column/@*">

You can have an explicit template matching it when the Link attribute is set

<xsl:template match="Column[@Link='Yes']/@*">

In this template, you could add extra code to output the a link. Note that, it might be slightly simpler if you pass in the actual Row as a parameter, as opposed to the value of generate-id for the Row, as this would make it slightly less verbose to get the ID attribute.

 <xsl:template match="Column[@Link='Yes']/@*">
  <xsl:param name="pRow"/>
  <xsl:variable name="pRowId" select="generate-id($pRow)"/>
  <xsl:variable name="pValue" select="key('RowAttribsByName', concat($pRowId, '|', .))" />
  <td width="50%">
     <a onclick="javascript:OpenDifferentPage('{$pValue}','{$pRow/@ID}')">
        <xsl:value-of select="$pValue"/>
     </a>
  </td>
 </xsl:template>

Note the use of Attribute Value Templates here, in creating the onclick attributes. The curly braces indicate an expression to be evaluated, rather than output literally.

For this template to work though, you would also need to amend the other template to explicit match attributes where the Link attribute was not set to 'Yes'

<xsl:template match="Column[not(@Link='Yes')]/@*">

This is because otherwise the original template would match the case of Link being "Yes" with the same priority as the new template, which is not allowed.

Try this XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output omit-xml-declaration="yes" indent="yes"/>
   <xsl:key name="RowAttribsByName" match="Row/@*" use="concat(generate-id(..), '|', name())"/>
   <xsl:variable name="vColNames" select="/*/Columns/*[not(@Hidden = 'true')]/@Name"/>

   <xsl:template match="/*">
      <table border="1">
         <tr>
            <xsl:apply-templates select="Columns/*"/>
         </tr>
         <xsl:apply-templates select="Rows/Row"/>
      </table>
   </xsl:template>

   <xsl:template match="Column[not(@Hidden = 'true')]">
      <td>
         <xsl:value-of select="@Caption"/>
      </td>
   </xsl:template>

   <xsl:template match="Row">
      <tr>
         <xsl:apply-templates select="$vColNames">
            <xsl:with-param name="pRow" select="."/>
         </xsl:apply-templates>
      </tr>
   </xsl:template>

   <xsl:template match="Column[not(@Link='Yes')]/@*">
      <xsl:param name="pRow"/>
      <xsl:variable name="pRowId" select="generate-id($pRow)"/>
      <xsl:variable name="pValue" select="key('RowAttribsByName', concat($pRowId, '|', .))"/>
      <td width="50%">
         <xsl:value-of select="$pValue"/>
      </td>
   </xsl:template>

   <xsl:template match="Column[@Link='Yes']/@*">
      <xsl:param name="pRow"/>
      <xsl:variable name="pRowId" select="generate-id($pRow)"/>
      <xsl:variable name="pValue" select="key('RowAttribsByName', concat($pRowId, '|', .))"/>
      <td width="50%">
         <a onclick="javascript:OpenDifferentPage('{$pValue}','{$pRow/@ID}')">
            <xsl:value-of select="$pValue"/>
         </a>
      </td>
   </xsl:template>
</xsl:stylesheet>

This approach has a couple of drawbacks. There is some repeated code, and also it would be harder to manager if you had other attributes on the Column elements which affected the output of the columns.

Here is another version of the XSLT, which makes use of the mode element to repeatedly match the Column attribute, allowing you to potentially output more elements if required

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output omit-xml-declaration="yes" indent="yes"/>
   <xsl:key name="RowAttribsByName" match="Row/@*" use="concat(generate-id(..), '|', name())"/>
   <xsl:variable name="vColNames" select="/*/Columns/*[not(@Hidden = 'true')]/@Name"/>

   <xsl:template match="/*">
      <table border="1">
         <tr>
            <xsl:apply-templates select="Columns/*"/>
         </tr>
         <xsl:apply-templates select="Rows/Row"/>
      </table>
   </xsl:template>

   <xsl:template match="Column[not(@Hidden = 'true')]">
      <td>
         <xsl:value-of select="@Caption"/>
      </td>
   </xsl:template>

   <xsl:template match="Row">
      <tr>
         <xsl:apply-templates select="$vColNames">
            <xsl:with-param name="pRow" select="."/>
         </xsl:apply-templates>
      </tr>
   </xsl:template>

   <xsl:template match="Column/@Name">
      <xsl:param name="pRow"/>
      <xsl:variable name="pRowId" select="generate-id($pRow)"/>
      <xsl:variable name="pValue" select="key('RowAttribsByName', concat($pRowId, '|', .))"/>
      <td width="50%">
         <xsl:apply-templates select=".." mode="link">
            <xsl:with-param name="pRow" select="$pRow"/>
            <xsl:with-param name="pValue" select="$pValue"/>
         </xsl:apply-templates>
      </td>
   </xsl:template>

   <xsl:template match="Column[@Link='Yes']" mode="link">
      <xsl:param name="pRow"/>
      <xsl:param name="pValue"/>
      <a onclick="javascript:OpenDifferentPage('{$pValue}','{$pRow/@ID}')">
         <xsl:apply-templates select="self::*" mode="value">
            <xsl:with-param name="pRow" select="$pRow"/>
            <xsl:with-param name="pValue" select="$pValue"/>
         </xsl:apply-templates>
      </a>
   </xsl:template>

   <xsl:template match="Column" mode="link">
      <xsl:param name="pRow"/>
      <xsl:param name="pValue"/>
      <xsl:apply-templates select="self::*" mode="value">
         <xsl:with-param name="pRow" select="$pRow"/>
         <xsl:with-param name="pValue" select="$pValue"/>
      </xsl:apply-templates>
   </xsl:template>

   <xsl:template match="Column" mode="value">
      <xsl:param name="pRow"/>
      <xsl:param name="pValue"/>
      <xsl:value-of select="$pValue"/>
   </xsl:template>
</xsl:stylesheet>

Note in this example, there are two templates for the link

<xsl:template match="Column[@Link='Yes']" mode="link">

<xsl:template match="Column" mode="link">

In this case, the second does not need a check on the Link attribute. Where a template just matches an explicit element name, it will have a lower priority than one that has been qualified with an xpath expression. Therefore the second template will not ever match the case where Link is Yes.



回答2:

This XSLT 1.0 stylesheet ...

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" doctype-system="about:legacy-compat" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />

<xsl:key name="linked-cols" match="Column[@Link='Yes']" use="@Name" />

<xsl:template match="TableData">
  <table border="1">
    <tbody>
      <xsl:variable name="cols" select="count(Columns/Column[not(@Hidden='true')])" />
      <xsl:apply-templates>
        <xsl:with-param name="col-width" select="concat(100 div $cols,'%')" />
      </xsl:apply-templates> 
    </tbody>
  </table>
</xsl:template>

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

<xsl:template match="Column[@Hidden='true']" />

<xsl:template match="Column[@Sort='Yes']">
  <td>
    <a onclick="javascript:SortColumn('{@Caption}')"><xsl:value-of select="@Caption" /></a>
  </td>
</xsl:template>

<xsl:template match="Column">
  <td><xsl:value-of select="@Caption" /></td>
</xsl:template>

<xsl:template match="Rows">
  <xsl:param name="col-width" />
  <xsl:apply-templates>
    <xsl:with-param name="col-width" select="$col-width" />
  </xsl:apply-templates>
</xsl:template>

<xsl:template match="Row">
  <xsl:param name="col-width" />
  <tr>
    <xsl:variable name="Row" select="." />
    <xsl:for-each select="../../Columns/Column[not(@Hidden='true')]">
      <td width="{$col-width}">
        <xsl:variable name="col-name" select="@Name" />
        <xsl:apply-templates select="$Row/@*[local-name() = $col-name]" />
      </td>  
    </xsl:for-each>
  </tr>
</xsl:template>

<xsl:template match="Row/@*">
  <xsl:value-of select="." />
</xsl:template>

<xsl:template match="Row/@*[key('linked-cols',local-name())]">
  <a onclick="javascript:OpenDifferentPage('{.}','{../@ID}')"><xsl:value-of select="." /></a>
</xsl:template>

</xsl:stylesheet>

... when applied to this document ...

<TableData>
    <Columns>
        <Column Name="ID" Hidden="true" />
        <Column Name="Name" Caption="Item Name" Link="Yes" Sort="Yes"/>
        <Column Name="Desc" Caption="Item Description" />
    </Columns>
    <Rows>
        <Row ID="0" Name="A" />
        <Row ID="1" Name="B" Desc="Some description"/>
        <Row ID="3" Name="C" />
    </Rows>
</TableData>

... will yield ...

<!DOCTYPE html SYSTEM "about:legacy-compat">
<table border="1">
  <tbody>
    <tr>
      <td><a onclick="javascript:SortColumn('Item Name')">Item Name</a></td>
      <td>Item Description</td>
    </tr>
    <tr>
      <td width="50%"><a onclick="javascript:OpenDifferentPage('A','0')">A</a></td>
      <td width="50%"></td>
    </tr>
    <tr>
      <td width="50%"><a onclick="javascript:OpenDifferentPage('B','1')">B</a></td>
      <td width="50%">Some description</td>
    </tr>
    <tr>
      <td width="50%"><a onclick="javascript:OpenDifferentPage('C','3')">C</a></td>
      <td width="50%"></td>
    </tr>
  </tbody>
</table>