Попытка использовать XSLT для создания таблицы HTML из XML

Я борюсь с XSLT. Я застрял в процедурной стране. По сути, у меня есть некоторый XML, который создается из базы данных, которая выглядит примерно так:

<?xml version="1.0" encoding="iso-8859-1"?>
<report>
    <generated_dtm>2013-03-08T18:57:26+00:00</generated_dtm>

    <range>
        <start_dtm>2013-02-21T17:52:00+00:00</start_dtm>
        <end_dtm>2013-03-08T17:52:00+00:00</end_dtm>
    </range>

    <sensor site_code="A0001" unit_no="1" sensor_no="1">
        <name>Food</name>
        <mu_symbol>°C</mu_symbol>
    </sensor>

    <sensor site_code="A0001" unit_no="1" sensor_no="2">
        <name>Air</name>
        <mu_symbol>°C</mu_symbol>
    </sensor>

    <readings>
        <slot slot_dtm="2013-02-21T17:50:00+00:00">
            <sensor sensor_no="1">
                <v>10</v>
                <status_code>IR</status_code>
                <status_desc>In Range</status_desc>
            </sensor>
            <sensor sensor_no="2">
                <v>20</v>
                <status_code>Lo</status_code>
                <status_desc>Low</status_desc>
                </sensor>
        </slot>

        <slot slot_dtm="2013-02-21T18:00:00+00:00">
            <sensor sensor_no="2">
                <v>21</v>
                <status_code>Lo</status_code>
                <status_desc>Low</status_desc>
            </sensor>
            <sensor sensor_no="1">
                <v>11</v>
                <status_code>IR</status_code>
                <status_desc>In Range</status_desc>
            </sensor>
        </slot>
    </readings>
</report>

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

Time                      | Food | Air
-------------------------------------
2013-02-21T17:50:00+00:00 | 10   | 11
2013-02-21T18:00:00+00:00 | 20   | 22

Хотя порядок временных интервалов гарантированно восходящий, поэтому мне не нужно их сортировать (могут быть 1000), проблема в том, что в каждом временном интервале порядок датчиков не может быть гарантирован, поэтому я подумал Я бы перебирал датчики, которые я использовал для создания заголовков таблицы, каждый раз и выбирал правильный датчик из каждого слота, когда я перебирал слоты. Хотя это не работает, вы, вероятно, получите то, что я пытался сделать (теперь я понимаю, почему это не работает... переменные ведут себя не так, как я ожидал!): -

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="report">
  <html>
  <head>
    <title>Report</title>
  </head>
  <body>
      <table border="0" width="100%" bgcolor="#ffffff" cellspacing="0" cellpadding="2">
        <tr>
          <td class="column_head_above" width="70">Time</td>
          <xsl:for-each select="sensor">
            <td class="column_head_above"><xsl:value-of select="name"/><xsl:text> </xsl:text><xsl:value-of select="mu_symbol"/></td>
          </xsl:for-each>
        </tr>

        <!-- go through each time slot -->

        <xsl:for-each select="readings/slot">
          <tr>
            <xsl:variable name="sdtm" select="@slot_dtm" /> 

            <td class="table_data"><xsl:value-of select="$sdtm"/></td>

            <!-- go through each sensor header -->

            <xsl:for-each select="../sensor">
              <xsl:variable name="sno" select="@sensor_no" /> 
              <td>
                <xsl:value-of select="../readings/slot[@slot_dtm=$sdtm]/sensor[@sensor_no=$sno]/v"/>
                <xsl:value-of select="../readings/slot[@slot_dtm=$sdtm]/sensor[@sensor_no=$sno]/status_desc"/>
              </td>
            </xsl:for-each>

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

        <!-- end: go through each time slot -->

      </table>
  </body>
  </html>
</xsl:template>
</xsl:stylesheet>

Может быть 100 или даже 1000 временных интервалов, это всего лишь небольшой пример. Я могу настроить иерархию XML, если это поможет, но я не могу упорядочить датчики в каждом временном интервале без серьезной доработки запроса к базе данных. Я надеюсь, что это не нужно.

Первоначально у меня был XML, в котором слоты были разделены следующим образом:

<readings>
    <slot slot_dtm="2013-02-21T17:50:00+00:00">
        <sensor sensor_no="1">
            <v>10</v>
            <status_code>IR</status_code>
            <status_desc>In Range</status_desc>
        </sensor>
    </slot>

    <slot slot_dtm="2013-02-21T17:50:00+00:00">
        <sensor sensor_no="2">
            <v>20</v>
            <status_code>Lo</status_code>
            <status_desc>Low</status_desc>
            </sensor>
    </slot>

    <slot slot_dtm="2013-02-21T18:00:00+00:00">
        <sensor sensor_no="1">
            <v>11</v>
            <status_code>IR</status_code>
            <status_desc>In Range</status_desc>
        </sensor>
    </slot>

    <slot slot_dtm="2013-02-21T18:00:00+00:00">
        <sensor sensor_no="2">
            <v>21</v>
            <status_code>Lo</status_code>
            <status_desc>Low</status_desc>
        </sensor>
    </slot>
</readings>

Что включало гораздо более простой запрос к базе данных! Здесь я мог бы гарантировать порядок, но процессор XQuery, который я использую (QXmlQuery Qt), не поддерживает для каждой группы, поэтому я не смог найти способ группировки по времени.

Извините, что так долго, я надеюсь, что кто-то может помочь, по крайней мере, указать мне правильное направление.

Спасибо.


person Mark    schedule 08.03.2013    source источник


Ответы (2)


Это должно сделать это:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:variable name="allSensors" select="/report/sensor" />

  <xsl:template match="report">
    <html>
      <head>
        <title>Report</title>
      </head>
      <body>
        <table border="0" width="100%" bgcolor="#ffffff"
               cellspacing="0" cellpadding="2">
          <tr>
            <td class="column_head_above" width="70">Time</td>
            <xsl:apply-templates select="sensor" />
          </tr>
          <xsl:apply-templates select="readings/slot" />
        </table>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="report/sensor">
    <td class="column_head_above">
      <xsl:value-of select="concat(name, ' ', mu_symbol)"/>
    </td>
  </xsl:template>

  <xsl:template match="slot">
    <xsl:variable name="currentSensors" select="sensor" />
    <tr>
      <td class="table_data">
        <xsl:value-of select="@slot_dtm"/>
      </td>

      <xsl:apply-templates select="$allSensors/@sensor_no">
        <xsl:with-param name="currentSlot" select="current()/@slot_dtm" />
      </xsl:apply-templates>
    </tr>
  </xsl:template>

  <xsl:template match="@sensor_no">
    <xsl:param name="currentSlot" />

    <td>
      <xsl:variable name="matchingSensor"
                    select="/report/readings/slot[@slot_dtm = $currentSlot]
                            /sensor[@sensor_no = current()]" />
      <xsl:value-of select="concat($matchingSensor/v, ' - ', 
                                       $matchingSensor/status_desc)" />
    </td>
  </xsl:template>
</xsl:stylesheet>

Я сделал здесь некоторую очистку, но основные моменты таковы:

  • Сохранение переменных ссылок на определения датчиков для легкого доступа
  • Создание переменной ссылки на датчики текущего слота, на которые можно ссылаться внутри файла for-each.

При запуске на вашем образце ввода это производит:

<html>
  <head>
    <META http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Report</title>
  </head>
  <body>
    <table border="0" width="100%" bgcolor="#ffffff" cellspacing="0" cellpadding="2">
      <tr>
        <td class="column_head_above" width="70">Time</td>
        <td class="column_head_above">Food °C</td>
        <td class="column_head_above">Air °C</td>
      </tr>
      <tr>
        <td class="table_data">2013-02-21T17:50:00+00:00</td>
        <td>10 - In Range</td>
        <td>20 - Low</td>
      </tr>
      <tr>
        <td class="table_data">2013-02-21T18:00:00+00:00</td>
        <td>11 - In Range</td>
        <td>21 - Low</td>
      </tr>
    </table>
  </body>
</html>
person JLRishe    schedule 08.03.2013
comment
Спасибо! С тех пор, как я написал вопрос, я начал что-то делать с ним, и я недалеко от того, что у вас есть выше. Я думаю, что после изучения того, что вы внимательно изложили, это будет щелкнуть. Я поставлю то, что у меня есть, как отдельный ответ, но я отмечу ваш ответ как правильный. Спасибо еще раз :-) - person Mark; 09.03.2013
comment
Проблема: я обновил свою версию на основе вашего кода, и она фактически не работает. Вероятно, это связано с процессором XSLT, который я использую (это тот, что в Qt 4.8.4, который, я думаю, использует libxml2), но в основном, как только я вхожу в цикл for-each, $currentSensors становится пустым, как будто не существует. Это в основном проблема, с которой мне пришлось начать, что, как только я вхожу в цикл for-each, кажется, что ничего из предыдущей области не существует. есть идеи? - person Mark; 09.03.2013
comment
Хм, это очень странно. Не могли бы вы попробовать вышеуказанную модификацию? Я создал отдельный шаблон для td и передаю $currentSensors в качестве параметра. Интересно, будет ли в этом случае по-прежнему игнорироваться переменная? - person JLRishe; 09.03.2013
comment
Я получил следующую ошибку: Требуемый тип — node(), но item() был найден. - person Mark; 09.03.2013
comment
Похоже, это процессор Qt 4.8 XSLT. Если я использую xsltproc (поставляется с libxslt), он работает нормально, и если вместо этого я использую libxml и libxslt в своем программном обеспечении, это также работает нормально. Это просто боль, поскольку это означает еще больше зависимостей, но, похоже, мне придется прекратить использовать Qt для обработки XSLT. - person Mark; 09.03.2013
comment
Если у вас есть еще предложения, это было бы здорово. Я многому научился из того, что ты мне рассказал. - person Mark; 09.03.2013
comment
Последняя идея, которая у меня есть, состоит в том, чтобы изменить это: <xsl:param name="currentSensors" /> на это: <xsl:param name="currentSensors" select="/.." />, но вероятность того, что это поможет, составляет около 10%. Если решение, которое вы разместили, работает в вашем процессоре XSLT, делает все, что вам нужно, и это ограничение, о котором я упоминал в комментариях, не является проблемой, то, вероятно, имеет смысл использовать вашу реализацию. - person JLRishe; 09.03.2013
comment
Выше я разместил еще одно потенциальное решение, которое передает дату и время в качестве параметра вместо набора узлов. Не могли бы вы попробовать? - person JLRishe; 09.03.2013
comment
Подойдет, хотя, скорее всего, я буду использовать другой XSLT-процессор, иначе он начнет запутываться, пытаясь найти обходные пути. Ваше исходное решение было самым изящным, поэтому было бы неплохо его использовать. - person Mark; 09.03.2013

Вот обновление после того, как я потратил некоторое время на его выяснение. Спасибо JLRishe за его ответ. Между этим и тем, что я разработал, это начинает проясняться (до следующей проблемы!).

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="report">
  <html>
  <head>
    <title>Report</title>
  </head>
  <body>
      <table border="0" width="100%" bgcolor="#ffffff" cellspacing="0" cellpadding="2">
        <tr>
          <td class="column_head_above" width="70">Time</td>
          <xsl:apply-templates select="sensors/sensor">
            <xsl:sort select="@sensor_no" />
          </xsl:apply-templates>
        </tr>

        <!-- go through each time slot -->

        <xsl:for-each select="readings/slot">
          <tr>
            <td class="table_data"><xsl:value-of select="@slot_dtm"/></td>

            <!-- go through each sensor header -->

            <xsl:apply-templates select="sensor">
              <xsl:sort select="@sensor_no" />
            </xsl:apply-templates>

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

        <!-- end: go through each time slot -->

      </table>
  </body>
  </html>
</xsl:template>

<xsl:template match="slot/sensor">
    <td>
        <xsl:value-of select="@sensor_no"/> -
        <xsl:value-of select="v"/> -
        <xsl:value-of select="status_desc"/>
    </td>
</xsl:template>

<xsl:template match="sensors/sensor">
    <td class="column_head_above">
        <xsl:value-of select="name"/><xsl:text> </xsl:text><xsl:value-of select="mu_symbol"/>
    </td>
</xsl:template>

</xsl:stylesheet>
person Mark    schedule 08.03.2013
comment
Одним из ограничений этого подхода является то, что у вас могут отсутствовать <td>, если в каком-либо из элементов <slot> отсутствует <sensor> (хотя я не знаю, может ли это произойти в вашем случае). Я бы предложил добавить data-type="numeric" к xsl:sort, если идентификаторы всегда будут числовыми. - person JLRishe; 09.03.2013