Ошибка оси рекурсии XSL? - PullRequest
2 голосов
/ 01 ноября 2010

У меня есть следующий XML-файл Sitemap:

<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="NewSiteMap.xsl"?>
<siteMap>
  <siteMapNode
    url="~/UsingMasterTemplate.aspx?id=1"
    title="Home"
    description="AAAAAAAAAAAAAAAAAAA">
    <siteMapNode
      url="~/UsingMasterTemplate.aspx?id=2"
      title="Profile"
      description="BBBBBBBBBBBBBBBBBB" />
    <siteMapNode
      url="~/UsingMasterTemplate.aspx?id=3"
      title="People"
      description="CCCCCCCCCCCCCCCCCCCCCCCC" />
    <siteMapNode
      url="~/UsingMasterTemplate.aspx?id=5"
      title="New Page"
      description="DDDDDDDDDDDDDDDDDDDD" />
  </siteMapNode>
</siteMap>

И следующий xsl-файл для рекурсии и вывода в ul:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method='xml' version='1.0' omit-xml-declaration="yes" encoding='UTF-8' indent='yes'/>

<xsl:template match="siteMap">
<!--  
<xsl:variable name='siteMapNode'>
 <xsl:value-of select='siteMap/siteMapNode'/>
</xsl:variable> 
-->
<html>
  <head>
     <link rel="stylesheet" href="xSiteMap.css" type="text/css" />  
  </head>

  <body>
       <h2>SiteMap:</h2>
   <ul>
    <!-- Check for empty sitemap -->
    <xsl:if test='siteMapNode'>
     <xsl:call-template name='BuildNavList'>
      <xsl:with-param name='siteMapNode' select='siteMapNode'/>
     </xsl:call-template>
    </xsl:if>     
      </ul>
  </body>

</html> 

</xsl:template> 

<xsl:template name='BuildNavList'>
 <xsl:param name='siteMapNode'/>
 <li> 
  <a>
   <xsl:attribute name="href">
    <xsl:value-of select="$siteMapNode/@url"/>
   </xsl:attribute>
   <xsl:attribute name="title">
    <xsl:value-of select="$siteMapNode/@description"/>       
   </xsl:attribute>
   <xsl:value-of select="$siteMapNode/@title"/>
  </a> 
    <!-- test for node-children, if true then recursion -->
  <xsl:if test='$siteMapNode/node()'>
   <ul>
    <xsl:for-each select="$siteMapNode/node()">
       <xsl:call-template name='BuildNavList'>
      <xsl:with-param name='siteMapNode' select='$siteMapNode/node()'/>
     </xsl:call-template>
    </xsl:for-each>  
   </ul>
  </xsl:if> 
 </li> 
</xsl:template>

</xsl:stylesheet>

Но, похоже, ошибка вмой рекурсивный вызов (возможно, ошибка оси в моем операторе for-each)!Что здесь не так?

Ответы [ 3 ]

2 голосов
/ 01 ноября 2010

В дополнение к ответу Габи вы, возможно, захотите знать, что использование call-template и передача одного параметра, узла, это просто обходной способ сказать apply-templates этому узлу (без сопоставления шаблона). Apply-templates - это обычный способ XSLT делать то, что вы делаете, и он менее многословен.

Итак, ваш начальный шаблон вызова

<xsl:if test='siteMapNode'>
 <xsl:call-template name='BuildNavList'>
  <xsl:with-param name='siteMapNode' select='siteMapNode'/>
 </xsl:call-template>
</xsl:if>   

может стать

 <xsl:apply-templates select='siteMapNode'/>

, который будет применяться к дочерним узлам контекста с именем siteMapNode.

Тогда ваш рекурсивный шаблон становится

<xsl:template match="siteMapNode">
 <li> 
  <a href="{@url}" title="{@description}">
   <xsl:value-of select="@title"/>
  </a> 
    <!-- test for siteMapNode element children, if true then recur -->
  <xsl:if test='siteMapNode'>
   <ul>
     <xsl:apply-templates select="siteMapNode" />
   </ul>
  </xsl:if> 
 </li> 
</xsl:template>

Обратите внимание, что мы удалили много ссылок на параметр $ siteMapNode, поскольку теперь это узел контекста. Обратите внимание также на шаблоны значений атрибутов, используемые для <a href="" и title="">. Гораздо лаконичнее и удобочитаемее!

XSLT действительно удобнее, когда вы понимаете и используете его так, как оно было задумано!

1 голос
/ 01 ноября 2010

После хороших ответов @LarsH и @Gaby позвольте мне показать мой предпочтительный способ решения этой проблемы.

В XSLT любое условие (<xsl:if> или <xsl:when>) указывает на то, что полная мощность сопоставления с шаблоном XSLT не использовалась.

Вместо таких условий попытайтесь использовать как можно больше совпадений с образцом в атрибуте match <xsl:template>.

Мое решение :

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="/*">
    <html>
      <head>
         <link rel="stylesheet" href="xSiteMap.css" type="text/css" />
      </head>

      <body>
        <h2>SiteMap:</h2>
          <xsl:apply-templates select="siteMapNode"/>
      </body>
  </html>
 </xsl:template>

 <xsl:template match="siteMapNode[1]">
  <ul>
   <xsl:call-template name="buildNav"/>
   <xsl:apply-templates select="following-sibling::siteMapNode"
        mode="inList"/>
  </ul>
 </xsl:template>

 <xsl:template match="siteMapNode" name="buildNav">
  <li>
    <a href="{@url}" title="{@description}">
      <xsl:value-of select="@title"/>
    </a>
    <xsl:apply-templates select="siteMapNode"/>
  </li>
 </xsl:template>

 <xsl:template match="siteMapNode" mode="inList">
  <xsl:call-template name="buildNav"/>
 </xsl:template>
 <xsl:template match="siteMapNode[position() > 1]"/>
</xsl:stylesheet>

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

<siteMap>
    <siteMapNode
    url="~/UsingMasterTemplate.aspx?id=1"
    title="Home"
    description="AAAAAAAAAAAAAAAAAAA">

        <siteMapNode
         url="~/UsingMasterTemplate.aspx?id=2"
         title="Profile"
         description="BBBBBBBBBBBBBBBBBB" />

        <siteMapNode
      url="~/UsingMasterTemplate.aspx?id=3"
      title="People"
      description="CCCCCCCCCCCCCCCCCCCCCCCC" />

        <siteMapNode
      url="~/UsingMasterTemplate.aspx?id=5"
      title="New Page"
      description="DDDDDDDDDDDDDDDDDDDD" /></siteMapNode>
</siteMap>

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

<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

      <link rel="stylesheet" href="xSiteMap.css" type="text/css">
   </head>
   <body>
      <h2>SiteMap:</h2>
      <ul>
         <li><a href="~/UsingMasterTemplate.aspx?id=1" title="AAAAAAAAAAAAAAAAAAA">Home</a><ul>
               <li><a href="~/UsingMasterTemplate.aspx?id=2" title="BBBBBBBBBBBBBBBBBB">Profile</a></li>
               <li><a href="~/UsingMasterTemplate.aspx?id=3" title="CCCCCCCCCCCCCCCCCCCCCCCC">People</a></li>
               <li><a href="~/UsingMasterTemplate.aspx?id=5" title="DDDDDDDDDDDDDDDDDDDD">New Page</a></li>
            </ul>
         </li>
      </ul>
   </body>
</html>

Обратите внимание : как <xsl:if> s исчезли «волшебным образом».

1 голос
/ 01 ноября 2010

В шаблоне BuildNavList измените внутренний вызов шаблона на

<xsl:for-each select="$siteMapNode/siteMapNode">
   <xsl:call-template name='BuildNavList'>
  <xsl:with-param name='siteMapNode' select='.'/>
 </xsl:call-template>
</xsl:for-each>

важно использовать . в xsl:with-param, потому что вы уже внутри цикла узлов ...

секундная проблема for-each select. В этом случае я использую /siteMapNode, чтобы игнорировать пробелы между элементами, потому что альтернатива node() учитывает пробелы как текстовые узлы и запутывается.

Если вам нужно использовать nodes() версию ( в for-each select), тогда вы можете добавить <xsl:strip-space elements="*"/> в верхней части xslt, чтобы он удалил их ..

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