XSLT Назначение значения элемента переменной: работает с Altova XML Spy, но не работает в приложении .NET - PullRequest
0 голосов
/ 08 января 2010

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

У меня есть простое XML-сообщение, которое выглядит так:

<?xml version="1.0" encoding="utf-8"?>
<message>
    <cmd id="instrument_status">
        <status_id>1</status_id>
    </cmd>
</message>

Веб-сервис на устройстве, с которым я работаю, возвращает несколько таких сообщений, и я конвертирую их в другой формат. Для приведенного выше сообщения новый формат будет выглядеть следующим образом:

<?xml version="1.0" encoding="UTF-8"?>
<grf:message xmlns:grf="http://www.company.com/schemas/device/version001">
    <grf:messageHeader>
        <grf:messageType>instrumentStatus</grf:messageType>
    </grf:messageHeader>
    <grf:messageBody>
        <grf:instrumentStatusBody>
            <grf:statusId>Running</grf:statusId>
        </grf:instrumentStatusBody>
    </grf:messageBody>
</grf:message>

Существует отображение целочисленных значений status_id в XML следующим образом:

status-id    Meaning  
=========    =======
0            Ready  
1            Running  
2            NotReady  
3            PoweringUp  
4            PoweringDown  
5            PoweredUp  
6            PoweredDown  
7            Tuning  
8            Error  

Мой XSLT работает правильно и выдает правильный вывод при использовании Altova XMLSpy, но когда я запускаю свое приложение .NET, я получаю ошибку в тот момент, когда отображение целого числа status_id преобразуется в одно из допустимые перечисляемые строки. Вместо получения перечисляемого значения процессор MS XSLT возвращает пустую строку, и я получаю пустой элемент в выходном XML.

Ниже приведен мой XSLT-код с некоторыми удаленными разделами, чтобы уменьшить объем пространства:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:fo="http://www.w3.org/1999/XSL/Format"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:fn="http://www.w3.org/2005/xpath-functions"
        xmlns:msxsl="urn:schemas-microsoft-com:xslt"
        xmlns:grf="http://www.company.com/schemas/device/version001"
        exclude-result-prefixes="#default">

 <xsl:template match="/">
  <xsl:apply-templates select="message"/>
 </xsl:template>

 <xsl:template match="message">
  <xsl:element name="grf:message">
   <xsl:apply-templates select="/message/cmd/@id"/>
  </xsl:element>
 </xsl:template>

 <xsl:template match="/message/cmd/@id">
  <xsl:variable name="_commandType" select="/message/cmd/@id"/>
        <!-- Following line works in Altova XMLSpy, but fails in .NET app. ??? -->
  <xsl:variable name="_statusIdValue" select="/message/cmd/status_id"/>
  <xsl:element name="grf:messageHeader">
   <xsl:element name="grf:messageType">
    <xsl:choose>
     <xsl:when test="$_commandType = 'api_info'">
      <xsl:text>apiInfo</xsl:text>
     </xsl:when>
     <xsl:when test="$_commandType = 'instrument_status'">
      <xsl:text>instrumentStatus</xsl:text>
     </xsl:when>
    </xsl:choose>
   </xsl:element>
  </xsl:element>
  <xsl:element name="grf:messageBody">
   <xsl:choose>
    <xsl:when test="$_commandType = 'api_info'">
     <xsl:element name="grf:apiInfoBody">
      <xsl:element name="grf:apiVersion">
       <xsl:value-of select="/message/cmd/api-version"/>
      </xsl:element>
      <xsl:element name="grf:apiBuild">
       <xsl:value-of select="/message/cmd/api-build"/>
      </xsl:element>
     </xsl:element>
    </xsl:when>
    <xsl:when test="$_commandType = 'instrument_status'">
     <xsl:element name="grf:instrumentStatusBody">
      <xsl:element name="grf:statusId">
       <xsl:choose>
        <xsl:when test="$_statusIdValue = '0'">
         <xsl:text>Ready</xsl:text>
        </xsl:when>
        <xsl:when test="$_statusIdValue = '1'">
         <xsl:text>Running</xsl:text>
        </xsl:when>
        <xsl:when test="$_statusIdValue = '2'">
         <xsl:text>NotReady</xsl:text>
        </xsl:when>
        <xsl:when test="$_statusIdValue = '3'">
         <xsl:text>PoweringUp</xsl:text>
        </xsl:when>
        <xsl:when test="$_statusIdValue = '4'">
         <xsl:text>PoweringDown</xsl:text>
        </xsl:when>
        <xsl:when test="$_statusIdValue = '5'">
         <xsl:text>PoweredUp</xsl:text>
        </xsl:when>
        <xsl:when test="$_statusIdValue = '6'">
         <xsl:text>PoweredDown</xsl:text>
        </xsl:when>
        <xsl:when test="$_statusIdValue = '7'">
         <xsl:text>Tuning</xsl:text>
        </xsl:when>
        <xsl:when test="$_statusIdValue = '8'">
         <xsl:text>Error</xsl:text>
        </xsl:when>
       </xsl:choose>
      </xsl:element>
     </xsl:element>
    </xsl:when>
   </xsl:choose>
  </xsl:element>
 </xsl:template>
</xsl:stylesheet>

Существует ли код XSLT 1.0, который будет вести себя одинаково как в Altova XMLSpy, так и в процессоре MS XSLT?

Спасибо

AlarmTripper

Ответы [ 5 ]

2 голосов
/ 09 января 2010

Следует отметить, что в шаблоне, который соответствует элементу «message», вы делаете это

<xsl:apply-templates select="/message/cmd/@id"/> 

Это фактически попытается сопоставить самое первое сообщение в XML относительно корня документа, независимо от того, какое сообщение вы сейчас используете. Это не выбор относительно текущего узла. В вашем случае, похоже, будет только одно сообщение, поэтому здесь это не будет проблемой, но в других случаях.

Вероятно, чаще встречается сопоставление элементов, а не атрибутов, особенно если вы хотите обработать дочерние элементы элемента. Таким образом, вы, вероятно, замените вышеприведенную строку вместо этого

<xsl:apply-templates select="cmd"/> 

Затем для шаблона, который соответствует ему, вместо того, чтобы делать это в настоящее время

<xsl:template match="/message/cmd/@id"> 

Вы бы сделали это вместо

<xsl:template match="cmd"> 

Далее, в этом шаблоне вы можете попробовать заменить переменные более простыми операторами выбора

<xsl:variable name="_commandType" select="@id"/> 
<xsl:variable name="_statusIdValue" select="status_id"/> 

Посмотрите, имеет ли это значение.

1 голос
/ 11 января 2010

Вы способ чрезмерно усложняете вашу трансформацию. Попробуйте эту значительно более простую таблицу стилей:

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

  <!-- main template / entry point -->
  <xsl:template match="message">
    <message>
      <messageHeader>
        <xsl:apply-templates select="cmd" mode="head" />
      </messageHeader>
      <messageBody>
        <xsl:apply-templates select="cmd" mode="body" />
      </messageBody>
    </message>
  </xsl:template>

  <!-- header templates -->
  <xsl:template match="cmd[@id = 'api_info']" mode="head">
    <messageType>apiInfo</messageType>
  </xsl:template>

  <xsl:template match="cmd[@id = 'instrument_status']" mode="head">
    <messageType>instrumentStatus</messageType>
  </xsl:template>

  <!-- body templates -->
  <xsl:template match="cmd[@id = 'api_info']" mode="body">
    <apiInfoBody>
      <apiVersion><xsl:value-of select="api-version" /></apiVersion>
      <apiBuild><xsl:value-of select="api-build" /></apiBuild>
    </apiInfoBody>
  </xsl:template>

  <xsl:template match="cmd[@id = 'instrument_status']" mode="body">
    <instrumentStatusBody>
      <statusId>
        <xsl:choose>
          <xsl:when test="status_id = 0">Ready</xsl:when>
          <xsl:when test="status_id = 1">Running</xsl:when>
          <xsl:when test="status_id = 2">NotReady</xsl:when>
          <xsl:when test="status_id = 3">PoweringUp</xsl:when>
          <xsl:when test="status_id = 4">PoweringDown</xsl:when>
          <xsl:when test="status_id = 5">PoweredUp</xsl:when>
          <xsl:when test="status_id = 6">PoweredDown</xsl:when>
          <xsl:when test="status_id = 7">Tuning</xsl:when>
          <xsl:when test="status_id = 8">Error</xsl:when>
          <!-- just in case… -->
          <xsl:otherwise>
            <xsl:text>Unknown status_id: </xsl:text>
            <xsl:value-of select="status_id" />
          </xsl:otherwise>
        </xsl:choose>
      </statusId>
    </instrumentStatusBody>
  </xsl:template>

</xsl:stylesheet>

Я избавился от всех ваших, казалось бы, лишних определений пространства имен (добавьте их обратно по мере необходимости) и поместил вашу таблицу стилей в пространство имен по умолчанию. Это означает, что вам больше не нужен префикс 'grf:' для каждого элемента, без изменения фактического результата:

<?xml version="1.0" encoding="utf-8"?>
<message xmlns="http://www.company.com/schemas/device/version001">
  <messageHeader>
    <messageType>instrumentStatus</messageType>
  </messageHeader>
  <messageBody>
    <instrumentStatusBody>
      <statusId>Running</statusId>
    </instrumentStatusBody>
  </messageBody>
</message>

Обратите внимание, как я использую разные выражения соответствия и разные режимы шаблонов для вывода соответствующих элементов в нужных ситуациях. Таким образом, любые <xsl:variable> или <xsl:choose> становятся ненужными, что делает таблицу стилей более чистой и удобной в обслуживании.

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

Извините, что не могу точно сказать, почему ваша таблица стилей работает не так, как задумано. Это работает для меня, и выглядит хорошо (иш).

Не стесняйтесь спрашивать, если что-либо из перечисленного неясно.

0 голосов
/ 08 января 2010

Это такое ужасное кодирование, я на самом деле рад, что оно не работает в .NET, я предлагаю вам переписать свою таблицу стилей.

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

<xsl:stylesheet version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns:grf="http://www.company.com/schemas/device/version001">

<xsl:template match="message">
   <xsl:variable name="commmand-type" select="cmd/@id"/>
   <xsl:variable name="status-id" select="cmd/status_id/text()"/>

      <grf:message>
         <grf:messageHeader>
            <grf:messageType>
               <xsl:choose> 
                  <xsl:when test="$commmand-type = 'api_info'">apiInfo</xsl:when> 
                  <xsl:when test="$commmand-type = 'instrument_status'">instrumentStatus</xsl:when> 
               </xsl:choose> 
            </grf:messageType>
         </grf:messageHeader>

         <grf:messageBody>
            <xsl:choose> 
               <xsl:when test="$commmand-type = 'api_info'"> 
                  <grf:apiInfoBody>
                     <grf:apiVersion>
                        <xsl:value-of select="cmd/api-version"/>
                     </grf:apiVersion>
                  </grf:apiInfoBody>
                  <grf:apiBuild> 
                     <xsl:value-of select="cmd/api-build"/> 
                  </grf:apiBuild> 
               </xsl:when> 
               <xsl:when test="$commmand-type = 'instrument_status'"> 
                  <grf:instrumentStatusBody>
                     <grf:statusId>
                        <xsl:choose> 
                           <xsl:when test="$status-id = '0'">Ready</xsl:when> 
                           <xsl:when test="$status-id = '1'">Running</xsl:when> 
                           <xsl:when test="$status-id = '2'">NotReady</xsl:when> 
                           <xsl:when test="$status-id = '3'">PoweringUp</xsl:when> 
                           <xsl:when test="$status-id = '4'">PoweringDown</xsl:when> 
                           <xsl:when test="$status-id = '5'">PoweredUp</xsl:when> 
                           <xsl:when test="$status-id = '6'">PoweredDown</xsl:when> 
                           <xsl:when test="$status-id = '7'">Tuning</xsl:when> 
                           <xsl:when test="$status-id = '8'">Error</xsl:when> 
                        </xsl:choose>
                     </grf:statusId>
                  </grf:instrumentStatusBody>
               </xsl:when> 
            </xsl:choose>
        </grf:messageBody>
    </grf:message>
</xsl:template>

</xsl:stylesheet>

Я использую только один <xsl:template>, вы можете выполнить рефакторинг, если считаете, что он подходит.

0 голосов
/ 09 января 2010

ОК, я обнаружил, что если я использую следующую строку для присвоения переменной _statusIdValue, то код будет правильно работать с классом XslCompiledTransform:

<xsl:variable name="_statusIdValue" select="msxsl:node-set(/message/cmd/*)/text()"/>

Это заменяет оригинальную строку, которая была:

<xsl:variable name="_statusIdValue" select="/message/cmd/status_id"/>

Однако назначение, которое работает для класса XslCompiledTransform, не работает с Altova XMLSpy. Есть ли вариант назначения, который будет работать правильно как в редакторе Altova XMLSpy, так и в классе XslCompiledTransform?

Спасибо

AlarmTripper

0 голосов
/ 08 января 2010

Прошло много времени с тех пор, как я кодировал любые xslt, но на основании того, что я вижу, вы могли бы изменить эту строку:

<xsl:variable name="_statusIdValue" select="/message/cmd/status_id"/>

до

<xsl:variable name="_statusIdValue" select="/message/cmd/status_id/."/>

Это должно сказать ему, чтобы выбрать содержимое элемента против самого узла.

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

Например, если вы хотите выплюнуть номер идентификатора статуса, вы можете использовать следующее:

<xsl:value-of select="/message/cmd/status_id/."/>
...