HTML-таблица из XSLT, где узел для каждого «столбца» может отсутствовать в каждой «строке» - PullRequest
2 голосов
/ 19 января 2010

У меня есть этот XML:

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="xsl.xsl"?>
<Response>
    <Result>
        <Date Unix="1263340800">13 Jan 2010 00:00:00</Date>
        <Column>
            <Name>Small</Name>
            <Value>100</Value>
        </Column>
    </Result>
    <Result>
        <Date Unix="1263427200">14 Jan 2010 00:00:00</Date>
        <Column>
            <Name>Small</Name>
            <Value>100</Value>
        </Column>
    </Result>
    <Result>
        <Date Unix="1263485232">14 Jan 2010 16:07:12</Date>
        <Column>
            <Name>Normal</Name>
            <Value>36.170537</Value>
        </Column>
    </Result>
    <Result>
        <Date Unix="1263513600">15 Jan 2010 00:00:00</Date>
        <Column>
            <Name>Small</Name>
            <Value>100</Value>
        </Column>
    </Result>
</Response>

Я хочу представить данные следующим образом:

<table>
    <tr>
        <th>Time</th>
        <th>Small</th>
        <th>Normal</th>
    </tr>
    <tr>
        <td>13 Jan 2010 00:00:00</td>
        <td class="class_Small">100</td>
        <td class="class_Normal"></td>
    </tr>
    <tr>
        <td>14 Jan 2010 00:00:00</td>
        <td class="class_Small">100</td>
        <td class="class_Normal"></td>
    </tr>
    <tr>
        <td>14 Jan 2010 16:07:12</td>
        <td class="class_Small"></td>
        <td class="class_Normal">39.301737</td>
    </tr>
    <tr>
        <td>15 Jan 2010 00:00:00</td>
        <td class="class_Small">100</td>
        <td class="class_Normal"></td>
    </tr>
</table>

Это мой текущий XSL:

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

   <xsl:key name="Friendlies" match="Name" use="."/> 
   <xsl:template match="/"> 

    <table> 
    <tr> 
        <th>Time</th> 
        <!-- Output the Column Names --> 
        <xsl:for-each select="//Column"> 

            <!-- Only ouput the Name if it is the first occurence of this value --> 
            <xsl:if test="generate-id(Name) = generate-id(key('Friendlies',Name)[1])">             

            <th> 
            <xsl:value-of select="Name"/> 
            </th>

            </xsl:if>                
        </xsl:for-each>
         </tr>
    <!-- Loop through all Results --> 
    <xsl:for-each select="//Result"> 
        <xsl:variable name="UnixTimestamp" select="Date/@Unix"/>
        <tr> 
            <td> 
                <xsl:value-of select="Date"/>
            </td> 

            <xsl:for-each select="//Column">
                <xsl:if test="generate-id(Name) = generate-id(key('Friendlies',Name)[1])">      

                <xsl:element name="td">
                    <xsl:attribute name="class">class_<xsl:value-of select="Name"/></xsl:attribute>
                    <xsl:value-of select="[Date/@Unix=$UnixTimestamp]/Value"/>
                </xsl:element>

                </xsl:if>
            </xsl:for-each> 

               </tr>
    </xsl:for-each> 
    </table>
   </xsl:template> 
</xsl:stylesheet> 

Структура HTML выходит нормально. Единственная проблема заключается в том, что значения не появляются в ячейках. Я понимаю, что <xsl:value-of select="[Date/@Unix=$UnixTimestamp]/Value"/> не является правильной ссылкой, потому что элемент Date на самом деле находится на том же уровне, что и элемент Column, а не внутри него. Хотя я не могу придумать, как это сделать.

Кроме того, я не совсем уверен, что я использую лучший метод для этого, потому что кажется, что было бы ужасно много циклов, происходящих для небольшого вывода. По мере того как XML становится больше (намного больше результатов, больше столбцов), мне интересно, повлияет ли это на производительность.

Простите, я новичок в XSL.

Заранее спасибо.


Добавлено после ответа Томалака

Некоторая информация о XML:

  • Любое количество <Column> узлов в каждом <Result> узле
  • Только один <Date> узел в каждом <Result> узле
  • Только один <Name> узел в каждом <Column> узле
  • Значением узла <Name> может быть что угодно, не только «Маленький» или «Нормальный»

1 Ответ

4 голосов
/ 19 января 2010

Вы слишком усложняете дела.Как насчет:

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

  <xsl:template match="Response">
    <table>
      <tr>
        <th>Time</th>
        <th>Small</th>
        <th>Normal</th>
      </tr>
      <xsl:apply-templates select="Result" />
    </table>
  </xsl:template>

  <xsl:template match="Result">
    <tr>
      <td><xsl:value-of select="Date" /></td>
      <td><xsl:value-of select="Column[Name = 'Small']/Value" /></td>
      <td><xsl:value-of select="Column[Name = 'Normal']/Value" /></td>
    </tr>
  </xsl:template>

</xsl:stylesheet>

Вывод в точности соответствует вашим требованиям:

<table>
  <tr>
    <th>Time</th>
    <th>Small</th>
    <th>Normal</th>
  </tr>
  <tr>
    <td>13 Jan 2010 00:00:00</td>
    <td>100</td>
    <td></td>
  </tr>
  <tr>
    <td>14 Jan 2010 00:00:00</td>
    <td>100</td>
    <td></td>
  </tr>
  <tr>
    <td>14 Jan 2010 16:07:12</td>
    <td></td>
    <td>36.170537</td>
  </tr>
  <tr>
    <td>15 Jan 2010 00:00:00</td>
    <td>100</td>
    <td></td>
  </tr>
</table>

Ваш XSLT настолько сложен, в основном из-за следующих факторов:

<xsl:for-each select="//Column"> 

Два советав этом отношении:

  • Не делайте для каждого, пока вы можете избежать этого.В 98% случаев вы предпочитаете применять шаблоны (даже если поначалу сложно обойти это).Не поддавайтесь желанию использовать for-each, это раздувает ваш код.
  • Не используйте оператор // до тех пор, пока вы можете его избежать.Ваш входной XML идеально структурирован, использование //, как вы делаете, делает эту структуру непригодной для использования, полностью сгладив ее.Вместо этого, попробуйте использовать существующую структуру XML в своих интересах, как я сделал.

РЕДАКТИРОВАТЬ

После комментария от ОП.Решение ниже справляется с любым количеством и видом <Name> узлов.Он использует for-each, но как средство абстрактной итерации, а не как средство обработки дочерних узлов.

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

  <xsl:key name="kName" match="Name" use="." />

  <xsl:variable name="vName" select="
    //Name[generate-id() = generate-id(key('kName', .)[1])]
  " />

  <xsl:template match="Response">
    <table>
      <tr>
        <th>Time</th>
        <xsl:for-each select="$vName">
          <th><xsl:value-of select="." /></th>
        </xsl:for-each>
      </tr>
      <xsl:apply-templates select="Result" />
    </table>
  </xsl:template>

  <xsl:template match="Result">
    <xsl:variable name="self" select="." />
    <tr>
      <td><xsl:value-of select="Date" /></td>
      <xsl:for-each select="$vName">
        <td><xsl:value-of select="$self/Column[Name = current()]/Value" /></td>
      </xsl:for-each>
    </tr>
  </xsl:template>

</xsl:stylesheet>

Вы можете использовать <xsl:sort>, чтобы изменить порядок генерирования столбцов.

Для максимальной производительности при работе с большими входными документами другой твик может ускорить это выражение:

<xsl:value-of select="$self/Column[Name = current()]/Value" />

С помощью этого дополнительного ключа:

<xsl:key name="kColumn" match="Column" use="concat(Name,'|',generate-id(..))" />

вы можете переписать его как:

<xsl:value-of select="key('kColumn', concat(.,'|',generate-id($self)))/Value" />
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...