Извлечение узла XML из пути, указанного в значении атрибута другого узла - PullRequest
4 голосов
/ 10 июня 2010

Из этого источника XML:

<?xml version="1.0" encoding="utf-8" ?>
<ROOT>
  <STRUCT>
    <COL order="1" nodeName="FOO/BAR" colName="Foo Bar" />
    <COL order="2" nodeName="FIZZ" colName="Fizz" />
  </STRUCT>

  <DATASET>
    <DATA>
      <FIZZ>testFizz</FIZZ>
      <FOO>
        <BAR>testBar</BAR>
        <LIB>testLib</LIB>
      </FOO>
    </DATA>
    <DATA>
      <FIZZ>testFizz2</FIZZ>
      <FOO>
        <BAR>testBar2</BAR>
        <LIB>testLib2</LIB>
      </FOO>
    </DATA>
  </DATASET>
</ROOT>

Я хочу создать этот HTML:

<html>
  <head>
    <title>Test</title>
  </head>
  <body>
    <table border="1">
      <tr>
        <td>Foo Bar</td>
        <td>Fizz</td>
      </tr>
      <tr>
        <td>testBar</td>
        <td>testFizz</td>
      </tr>
      <tr>
        <td>testBar2</td>
        <td>testFizz2</td>
      </tr>
    </table>
  </body>
</html>

Вот XSLT, который у меня сейчас есть:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="html" indent="yes"/>

  <xsl:template match="/ROOT">
    <html>
      <head>
        <title>Test</title>
      </head>
      <body>
        <table border="1">
          <tr>
            <!--Generate the table header-->
            <xsl:apply-templates select="STRUCT/COL">
              <xsl:sort data-type="number" select="@order"/>
            </xsl:apply-templates>
          </tr>
          <xsl:apply-templates select="DATASET/DATA" />
        </table>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="COL">
    <!--Template for generating the table header-->
    <td>
      <xsl:value-of select="@colName"/>
    </td>
  </xsl:template>

  <xsl:template match="DATA">
    <xsl:variable name="pos" select="position()" />
    <tr>
      <xsl:for-each select="/ROOT/STRUCT/COL">
        <xsl:sort data-type="number" select="@order"/>
        <xsl:variable name="elementName" select="@nodeName" />
        <td>
          <xsl:value-of select="/ROOT/DATASET/DATA[$pos]/*[name() = $elementName]" />
        </td>
      </xsl:for-each>
    </tr>
  </xsl:template>

</xsl:stylesheet>

Это почти работает, у меня проблема в том, чтобы получить правильный узел DATA по пути, указанному в значении атрибута "nodeName" блока STRUCT.

Ответы [ 3 ]

7 голосов
/ 10 июня 2010

Вот чистое решение XSLT 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:template match="/">
  <html>
    <head>
      <title>Test</title>
    </head>
    <body>
      <table border="1">
        <xsl:apply-templates select="*/STRUCT"/>
        <xsl:apply-templates select="*/DATASET/DATA"/>
      </table>
    </body>
  </html>
 </xsl:template>

 <xsl:template match="STRUCT">
  <tr>
    <xsl:apply-templates select="COL"/>
  </tr>
 </xsl:template>

 <xsl:template match="COL">
  <td><xsl:value-of select="@colName"/></td>
 </xsl:template>

 <xsl:template match="DATA">
      <tr>
        <xsl:apply-templates select="/*/STRUCT/*/@nodeName">
         <xsl:with-param name="pCurrentNode" select="."/>
        </xsl:apply-templates>
      </tr>
 </xsl:template>

 <xsl:template match="@nodeName" name="getNodeValue">
   <xsl:param name="pExpression" select="string(.)"/>
   <xsl:param name="pCurrentNode"/>

   <xsl:choose>
    <xsl:when test="not(contains($pExpression, '/'))">
      <td><xsl:value-of select="$pCurrentNode/*[name()=$pExpression]"/></td>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="getNodeValue">
        <xsl:with-param name="pExpression"
          select="substring-after($pExpression, '/')"/>
        <xsl:with-param name="pCurrentNode" select=
        "$pCurrentNode/*[name()=substring-before($pExpression, '/')]"/>
      </xsl:call-template>
    </xsl:otherwise>
   </xsl:choose>

 </xsl:template>
</xsl:stylesheet>

, когда это преобразование применяется к предоставленному XML-документу :

<ROOT>
  <STRUCT>
    <COL order="1" nodeName="FOO/BAR" colName="Foo Bar" />
    <COL order="2" nodeName="FIZZ" colName="Fizz" />
  </STRUCT>

  <DATASET>
    <DATA>
      <FIZZ>testFizz</FIZZ>
      <FOO>
        <BAR>testBar</BAR>
        <LIB>testLib</LIB>
      </FOO>
    </DATA>
    <DATA>
      <FIZZ>testFizz2</FIZZ>
      <FOO>
        <BAR>testBar2</BAR>
        <LIB>testLib2</LIB>
      </FOO>
    </DATA>
  </DATASET>
</ROOT>

желаемый, правильный результат получается :

<html>
    <head>
        <title>Test</title>
    </head>
    <body>
        <table border="1">
            <tr>
                <td>Foo Bar</td>
                <td>Fizz</td>
            </tr>
            <tr>
                <td>testBar</td>
                <td>testFizz</td>
            </tr>
            <tr>
                <td>testBar2</td>
                <td>testFizz2</td>
            </tr>
        </table>
    </body>
</html>
2 голосов
/ 10 июня 2010

Проблема с тем, что у вас есть:

<xsl:variable name="elementName" select="@nodeName" />
...
<xsl:value-of select="/ROOT/DATASET/DATA[$pos]/*[name() = $elementName]" />

состоит в том, что предполагается, что elementName - это имя только одного элемента.Если это произвольное выражение XPath, тест не пройден.

Вторая проблема, с которой вы столкнетесь (или, вероятно, уже столкнулись), заключается в том, что шаблоны значений атрибутов недопустимы в предложениях select, поэтому вы не можете сделать это.что-то простое, как это:

<xsl:value-of select="/ROOT/DATASET/DATA[$pos]/{$elementName}" />

Вам нужно что-то, что будет динамически создавать выражение XPath для элемента, который вы ищете, а затем динамически оценивать это выражение.

Для решения я обратился к функции evaluate() EXSLT в библиотеке dynamic.Мне пришлось использовать его дважды: один раз для построения полного выражения XPath, представляющего запрос, и один раз для оценки этого запроса.Преимущество этого подхода заключается в том, что вы получаете доступ к полным возможностям синтаксического анализа и выполнения XPath evaluate.

<xsl:variable name="elementLocation" select="@nodeName" />
<xsl:variable name="query" select="concat('/ROOT/DATASET/DATA[$pos]/',
                                          dyn:evaluate('$elementLocation'))"/>
...
<xsl:value-of select="dyn:evaluate($query)"/>

, где пространство имен dyn вверху объявлено как <a href="http://exslt.org/dynamic" rel="nofollow noreferrer">http://exslt.org/dynamic</a>.Выяснить, где цитировать здесь сложно, и мне потребовалось несколько попыток, чтобы получить правильные значения.

Используя их вместо ваших elementName и выражений value-value, я получаю:

<html xmlns:msxsl="urn:schemas-microsoft-com:xslt"
      xmlns:dyn="http://exslt.org/dynamic">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test</title>
</head>
<body><table border="1">
<tr>
<td>Foo Bar</td>
<td>Fizz</td>
</tr>
<tr>
<td>testBar</td>
<td>testFizz</td>
</tr>
<tr>
<td>testBar2</td>
<td>testFizz2</td>
</tr>
</table></body>
</html>

, вот чтоЯ думаю, что вы ищете.

К сожалению, я не разбираюсь в MSXML, поэтому не могу сказать вам, поддерживает ли ваш конкретный процессор XSLT это расширение или что-то подобное.

0 голосов
/ 15 августа 2010

Оуэн,

ваше решение, использующее dyn:evaluate>(), в порядке, но не работает в браузерах, см. Здесь:

http://www.biglist.com/lists/lists.mulberrytech.com/xsl-list/archives/201008/msg00126.html

Проблема счто вы получили: ... то, что предполагается, что elementName - это имя только одного элемента.Если это произвольное выражение XPath, тест не пройден.

Решение Димитрие не было для общего разбора XPath, и обработку более чем одного узла можно просто добавить к его решению, добавив <xsl:for-each ...> s, см. Разницу ниже:

$ diff -u x.xsl y.xsl
--- x.xsl       2010-08-13 14:53:42.000000000 +0200
+++ y.xsl       2010-08-14 11:59:42.000000000 +0200
@@ -40,15 +40,19 @@

    <xsl:choose>
     <xsl:when test="not(contains($pExpression, '/'))">
-      <td><xsl:value-of select="$pCurrentNode/*[name()=$pExpression]"/></td>
+     <xsl:for-each select="$pCurrentNode/*[name()=$pExpression]">
+      <td><xsl:value-of select="."/></td>
+     </xsl:for-each>
     </xsl:when>
     <xsl:otherwise>
+     <xsl:for-each select="$pCurrentNode/*[name()=substring-before($pExpression, '/')]">
       <xsl:call-template name="getNodeValue">
         <xsl:with-param name="pExpression"
           select="substring-after($pExpression, '/')"/>
         <xsl:with-param name="pCurrentNode" select=
-        "$pCurrentNode/*[name()=substring-before($pExpression, '/')]"/>
+        "."/>
       </xsl:call-template>
+     </xsl:for-each>
     </xsl:otherwise>
    </xsl:choose>

$ 

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...