XSLT 2.0: проблемы при создании таблицы HTML с динамическими строками и столбцами - PullRequest
1 голос
/ 15 июня 2019

Я создаю таблицу HTML, основанную на динамических столбцах (имя хоста) и строках (VLAN).Я сталкиваюсь с проблемой после записи данных первой позиции (1 строка для всех хостов) в таблицу;выбирает данные 2-й позиции просто отлично, но переменная $ vCol возвращает их к первой строке переменной $ vCols.

Оцените любое направление, которое вы можете предложить.Спасибо

XSLT-2.0 код:

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

  <xsl:key name="kHostNameByValue" match="Hostname" use="."/>
  <xsl:key name="kVLAN" match="Hostname" use="."/>
  <xsl:variable name="vCols" select="/*/*/Hostname[generate-id()=generate-id(key('kHostNameByValue',.)[1])]"/> 

  <xsl:variable name="vMaxRows">
      <xsl:for-each select="$vCols">
        <xsl:sort data-type="number" order="descending" select="count(key('kVLAN', .))"/>
          <xsl:if test="position() = 1">
             <xsl:value-of select="count(key('kVLAN', .))"/>
          </xsl:if>
      </xsl:for-each>
  </xsl:variable>

    <xsl:template match="DocumentRoot">
      <table border="1">
        <!--  Print out column headings by Hostname    -->
       <tr>
            <xsl:apply-templates select="$vCols"/>
       </tr>

          <!--  Print out VLANs by Hostname    -->
          <xsl:for-each-group select="(//Row)[not(position() > $vMaxRows)]" group-by="VLAN">
              <tr>     
                 <xsl:variable name="vPos" select="position()"/>
                  <!-- Issue on 2nd position when $vCols goes back to 1st hostname at line 3 -->
                  <xsl:for-each select="$vCols"> 
                    <td>  
                        <xsl:value-of select="..[$vPos]/VLAN"/>
                    </td>    
                 </xsl:for-each> 
              </tr>
            </xsl:for-each-group>
      </table>
    </xsl:template>

    <xsl:template match="Hostname">
        <td>
            <b>
                <xsl:value-of select="." />
            </b>
        </td>   
    </xsl:template>

    <xsl:template match="text()"/>
</xsl:stylesheet>

Вот пример данных XML.

<?xml version="1.0" encoding="UTF-8"?>
<DocumentRoot>
    <Row>
        <Hostname>switch-1</Hostname>
        <HostIP>10.29.178.102</HostIP>
        <VLAN>10</VLAN>
        <VLANName>VLAN-10</VLANName>
    </Row>
    <Row>
        <Hostname>switch-1</Hostname>
        <HostIP>10.29.178.102</HostIP>
        <VLAN>500</VLAN>
        <VLANName>VLAN-500</VLANName>
    </Row>
    <Row>
        <Hostname>switch-2</Hostname>
        <HostIP>10.29.178.103</HostIP>
        <VLAN>11</VLAN>
        <VLANName>VLAN-11</VLANName>
    </Row>
    <Row>
        <Hostname>switch-2</Hostname>
        <HostIP>10.29.178.103</HostIP>
        <VLAN>501</VLAN>
        <VLANName>VLAN-500</VLANName>
    </Row>
    <Row>
        <Hostname>switch-3</Hostname>
        <HostIP>10.29.170.1</HostIP>
        <VLAN>15</VLAN>
        <VLANName>VLAN-15</VLANName>
    </Row>
    <Row>
        <Hostname>switch-3</Hostname>
        <HostIP>10.29.170.1</HostIP>
        <VLAN>25</VLAN>
        <VLANName>VLAN-25</VLANName>
    </Row>
    <Row>
        <Hostname>switch-3</Hostname>
        <HostIP>10.29.170.1</HostIP>
        <VLAN>35</VLAN>
        <VLANName>VLAN-35</VLANName>
    </Row>
    <Row>
        <Hostname>switch-3</Hostname>
        <HostIP>10.29.170.1</HostIP>
        <VLAN>45</VLAN>
        <VLANName>VLAN-45</VLANName>
    </Row>
    <Row>
        <Hostname>switch-3</Hostname>
        <HostIP>10.29.170.1</HostIP>
        <VLAN>55</VLAN>
        <VLANName>VLAN-55</VLANName>
    </Row>
</DocumentRoot>

Вывод (фактический и требуемый):

Output

Ответы [ 3 ]

1 голос
/ 16 июня 2019

Во-первых, я думаю, что ваша maxRows переменная может быть упрощена до этого

<xsl:variable name="maxRows" select="max(//Row/count(key(hostNameByValue, Hostname)))" />

Где я определил ключ hostNameByValue так:

<xsl:key name="hostNameByValue" match="Row" use="Hostname"/>

Вы также можете использовать distinct-values, чтобы получить отдельные имена столбцов

<xsl:variable name="cols" select="distinct-values(//Row/Hostname)" />

Таким образом, предполагая, что $rowNum является текущим числом (в блоке <xsl:for-each select="1 to $maxRows"> код для получения текущего значения ячейки будет следующим:

<xsl:for-each select="$cols">
  <th><xsl:value-of select="key('hostNameByValue', ., $doc)[position() = $rowNum]/VLAN"/></th>
</xsl:for-each>

(где $doc - это ссылка на исходный документ XML, поскольку в xsl:for-each теперь есть последовательность атомарных значений)

Попробуйте это XSLT

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

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

  <xsl:key name="hostNameByValue" match="Row" use="Hostname"/>

  <xsl:variable name="cols" select="distinct-values(//Row/Hostname)" />
  <xsl:variable name="maxRows" select="max(//Row/count(key('hostNameByValue', Hostname)))" />
  <xsl:variable name="doc" select="/" />

  <xsl:template match="DocumentRoot">
    <table>
    <tr>
      <xsl:for-each select="$cols">
        <th><xsl:value-of select="."/></th>
      </xsl:for-each>
      </tr>
      <xsl:for-each select="1 to $maxRows">
        <xsl:variable name="rowNum" select="position()"/>
        <tr>
          <xsl:for-each select="$cols">
            <th><xsl:value-of select="key('hostNameByValue', ., $doc)[position() = $rowNum]/VLAN"/></th>
          </xsl:for-each>
        </tr>
      </xsl:for-each>
    </table>
  </xsl:template>
</xsl:stylesheet>

См. В действии на http://xsltfiddle.liberty -development.net / 6r5Gh3N

1 голос
/ 16 июня 2019

Я не думаю, что в XSLT 2 или 3, когда вы используете for-each-group, вам нужен любой из ключей, вы можете просто сохранить результат группировки и затем обработать его, например, чтобы сохранить результат группировки как XML вXSLT 2 или 3, который вы можете использовать

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    expand-text="yes"
    version="3.0">

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

  <xsl:template match="/">
    <html>
      <head>
        <title>.NET XSLT Fiddle Example</title>
      </head>
      <body>
        <xsl:apply-templates/>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="DocumentRoot">
      <table>
          <xsl:variable name="cols" as="element(col)*">
              <xsl:for-each-group select="Row" group-by="Hostname">
                  <col name="{current-grouping-key()}">
                      <xsl:sequence select="current-group()"/>
                  </col>
              </xsl:for-each-group>
          </xsl:variable>
          <thead>
              <tr>
                  <xsl:for-each select="$cols">
                      <th>{@name}</th>
                  </xsl:for-each>
              </tr>
          </thead>
          <tbody>
              <xsl:variable name="rows" select="max($cols!count(Row))"/>
              <xsl:for-each select="1 to $rows">
                  <xsl:variable name="row" select="."/>
                  <tr>
                      <xsl:for-each select="$cols">
                          <td>{Row[$row]/VLAN}</td>
                      </xsl:for-each>
                  </tr>
              </xsl:for-each>
          </tbody>
      </table>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty -development.net / 94rmq6Q / 3 - это XSLT 3 с оператором карты ! и шаблонами текстовых значений, но https://xsltfiddle.liberty -development.net / 94rmq6Q / 4 переписывает это как XSLT 2 вместо value-of.

Или в XSLT 3 вы можете сохранить результат группировки в последовательности массивов:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:array="http://www.w3.org/2005/xpath-functions/array"
    exclude-result-prefixes="#all"
    expand-text="yes"
    version="3.0">

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

  <xsl:template match="/">
    <html>
      <head>
        <title>.NET XSLT Fiddle Example</title>
      </head>
      <body>
        <xsl:apply-templates/>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="DocumentRoot">
      <table>
          <xsl:variable name="cols" as="array(element(Row))*">
              <xsl:for-each-group select="Row" group-by="Hostname">
                  <xsl:sequence select="array{ current-group() }"/>
              </xsl:for-each-group>
          </xsl:variable>
          <thead>
              <tr>
                  <xsl:for-each select="$cols">
                      <th>{?1/Hostname}</th>
                  </xsl:for-each>
              </tr>
          </thead>
          <tbody>
              <xsl:variable name="rows" select="max($cols!array:size(.))"/>
              <xsl:for-each select="1 to $rows">
                  <xsl:variable name="row" select="."/>
                  <tr>
                      <xsl:for-each select="$cols">
                          <td>{if ($row le array:size(.)) 
                               then .($row)/VLAN 
                               else ()}</td>
                      </xsl:for-each>
                  </tr>
              </xsl:for-each>
          </tbody>
      </table>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty -development.net / 94rmq6Q / 2

1 голос
/ 16 июня 2019

Я думаю, что использование группы может сделать это более сложным, чем нужно. В основном для каждой строки вам нужно перебрать все столбцы и вывести ячейку, если она существует, или пустую ячейку в противном случае. Это означает, что вы должны перебирать индекс, а не элементы строки:

<xsl:for-each select="1 to $numRows">
    <xsl:variable name="rowIndex" select="position()" />
    <tr>
    <xsl:for-each select="$vCols">
        <xsl:variable name="cell" select="//Row[string(Hostname) = .][position() = $rowIndex]" />
        <xsl:apply-templates select="$cell">
        <xsl:if test="not($cell)">
            <td></td>
        </xsl:if>
    </xsl:for-each>
    <tr>
</xsl:for.each>
...