Создание навигационной цепочки с помощью xsl из структуры узла

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

Рассмотрим следующую структуру страницы:

<!-- ===== SITE PAGE STRUCTURE ===================================== -->
<index>
   <item section="home" id="index"></item>
   <item section="service" id="index">
      <item id="content-management-systems">
         <item id="p1-1"/>
         <item id="p1-2"/>
         <item id="p1-3"/>
      </item>
      <item id="online-stores"></item>
      <item id="search-engines-and-ir"></item>
      <item id="web-applications"></item>
   </item>

   <item section="solutions" id="index">
      <item id="document-clustering"></item>
   </item>
   <item section="company" id="index">
      <item section="company" id="about"></item>
      <item section="company" id="philosophy" ></item>
      ...
   </item>
...
</item>

Этот индекс сайта представляет структуру сайта страниц контента xml в своей иерархии (считайте, что это меню). Он содержит разделы, представляющие разделы сайта, такие как дом, компания, услуги, решения и т. д. Эти разделы могут содержать подразделы. со страницами или просто с обычными страницами контента. Страница содержимого (ее XML-содержимое, такое как заголовок, текстовое содержимое и т. д.) идентифицируется атрибутом @id в дереве элементов. Атрибут @id в основном используется для получения содержимого всей страницы, которое будет преобразовано в html. Шаблон навигационной цепочки использует атрибут @id узла item для получения заголовка страницы (который будет отображаться в навигационной цепочке).

Я пытаюсь реализовать следующий шаблон, который обходит дерево, проверяя атрибут целевого раздела @section и атрибут целевой страницы @id в дереве. Я ожидаю, что он будет перемещаться по оси вниз до тех пор, пока целевой элемент item_target не будет найден путем сравнения атрибута предков @section и @id с $item_target каждого узла на этой оси.

Например: атрибут *$item_section=service* и идентификатор страницы *target item_target=p1-1* теперь должны рекурсивно «переходить» к ветке section «service» ( depth 1), проверьте, найдена ли целевая страница @id на этом уровне. В этом случае он не найден, поэтому он делает следующий рекурсивный вызов (через apply-templates) на следующий уровень узла item (в данном случае это будут content-management-systems< /strong>, там найдена целевая страница элемента p1-1, поэтому процесс трейла завершен:

Результат должен выглядеть следующим образом:

главная страница >> сервис >> системы управления контентом >> p1-1

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

    <!-- walk item path to generate a breadcrumb trail -->
    <xsl:template name="breadcrumb">
        <a>
            <xsl:attribute name="href">
                <xsl:text>/</xsl:text>
                <xsl:value-of select="$req-lg"/>
                <xsl:text>/home/index</xsl:text>
            </xsl:attribute>
            <xsl:value-of select="'Home'"/>
        </a>

        <xsl:apply-templates select="$content/site/index" mode="Item-Path">
            <xsl:with-param name="item_section" select="'service'"/>
            <xsl:with-param name="item_target" select="'search-engines-and-ir'"/>
            <xsl:with-param name="depth" select="0"/>
        </xsl:apply-templates>
    </xsl:template>

    <xsl:template match="item" mode="Item-Path">
        <xsl:param name="item_section" />
        <xsl:param name="item_target" />
        <xsl:param name="depth" />
        <!--
        depth=<xsl:value-of select="$depth"/>
        count=<xsl:value-of select="count(./node())"/><br/>
-->
        <xsl:variable name="cur-id" select="@id"/>
        <xsl:variable name="cur-section" select="@section"/>
        <xsl:choose>    
            <xsl:when test="@id=$item_target">
                &gt;&gt;
                <a>
                    <xsl:attribute name="href">
                        <xsl:text>/</xsl:text>
                                            <!-- req-lg: global langauge variable -->
                        <xsl:value-of select="$req-lg"/>
                        <xsl:text>/</xsl:text>
                        <xsl:value-of select="$item_section"/>
                        <xsl:text>/</xsl:text>
                        <xsl:if test="$depth = 2">
                            <xsl:value-of select="../@id"/>
                            <xsl:text>/</xsl:text>
                        </xsl:if>
                        <xsl:value-of select="@id"/>
                    </xsl:attribute>
                    <xsl:value-of 
                        select="$content/page[@id=$cur-id]/title"/>
                </a>
            </xsl:when>
            <xsl:otherwise>
                <xsl:if test="ancestor-or-self::item/@section = $item_section and count(./node()) > 0">
                &gt;&gt;:
                <a>
                    <xsl:attribute name="href">
                        <xsl:text>/</xsl:text>
                                            <!-- req-lg: global langauge variable -->
                        <xsl:value-of select="$req-lg"/>
                        <xsl:text>/</xsl:text>
                        <xsl:value-of select="$item_section"/>
                        <xsl:text>/</xsl:text>
                        <xsl:if test="$depth = 2">
                            <xsl:value-of select="../@id"/>
                            <xsl:text>/</xsl:text>
                        </xsl:if>
                        <xsl:value-of select="@id"/>
                    </xsl:attribute>
                    <xsl:value-of 
                        select="$content/page[@id=$cur-id and @section=$item_section]/title"/>
                </a>
                </xsl:if>
            </xsl:otherwise>
        </xsl:choose>

        <xsl:apply-templates select="item" mode="Item-Path">
            <xsl:with-param name="item_section" select="$item_section"/>
            <xsl:with-param name="item_target" select="$item_target"/>
            <xsl:with-param name="depth" select="$depth + 1"/>
        </xsl:apply-templates>

    </xsl:template>

Так как жестко заданные параметры в шаблоне breadcrumb, target section = 'service' и target page = 'search-engines-and-ir', я ожидаю, что вывод будет выглядеть как

главная страница >> сервис >> поисковые системы и поисковые системы

Но выход

главная страница >> сервис >> системы управления контентом >> поисковые системы и IR

что явно не правильно.

Может ли кто-нибудь дать мне подсказку, как исправить эту проблему? Было бы даже элегантнее избежать этой проверки глубины, но пока я не могу придумать другого способа, я уверен, что есть более элегантное решение.

Я работаю с XSLT 1.0 (libxml через PHP5).

Надеюсь, мой вопрос достаточно ясен, если нет, спрашивайте :-) Заранее спасибо за помощь!


person Andreas W. Wylach    schedule 05.10.2012    source источник
comment
Вопрос вообще не ясен. Пожалуйста, отредактируйте и объясните, как отношение родитель-потомок представлено в исходном XML-документе. Также, пожалуйста, объясните, что дано и что должно быть получено в терминах дерева.   -  person Dimitre Novatchev    schedule 05.10.2012
comment
@ Дмитрий Новачев: Извините, если мои вопросы неясны. Я отредактировал свои вопросы и попытался быть более точным, увидеть текст между деревом xml и кодом шаблона xsl. Надеюсь, стало более понятно, чего я пытаюсь достичь...   -  person Andreas W. Wylach    schedule 05.10.2012
comment
В вашем входном документе сервис не является дочерним элементом дома. Разве ваш ожидаемый результат не должен быть просто...service >> search-engines-and-ir ?   -  person Sean B. Durkin    schedule 05.10.2012
comment
Да, на данный момент home жестко запрограммирован в шаблоне breadcrumb. Я мог бы изменить структуру элемента индекса, чтобы она была домашней в качестве родителя, поэтому в будущем она будет проходить как первый узел элемента. Пока это со всеми разделами сайта на первом уровне.   -  person Andreas W. Wylach    schedule 05.10.2012


Ответы (2)


Так просто:

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

 <xsl:key name="kNodeById" match="item" use="@id"/>

 <xsl:template match="/">
  <xsl:text>home</xsl:text>
  <xsl:call-template name="findPath">
   <xsl:with-param name="pStart" select="'service'"/>
   <xsl:with-param name="pEnd" select="'search-engines-and-ir'"/>
  </xsl:call-template>
 </xsl:template>

 <xsl:template name="findPath">
  <xsl:param name="pStart"/>
  <xsl:param name="pEnd"/>

  <xsl:for-each select=
  "key('kNodeById', $pEnd)
       [ancestor::item[@section=$pStart]]
        [1]
         /ancestor-or-self::item
                [not(descendant::item[@section=$pStart])]
  ">

   <xsl:value-of select=
    "concat('>>', @id[not(../@section)], @section)"/>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

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

home>>service>>search-engines-and-ir

Обратите внимание:

  1. Это решение печатает навигационную цепочку от любого узла — в любом месте иерархии до любого из его дочерних узлов — в любом месте иерархии. Точнее, для первого item (в порядке документа) с атрибутом id, равным $pEnd, навигационная цепочка генерируется от его самого внутреннего предка, чей атрибут section равен $pStart, к этому элементу item.

  2. Это решение должно быть гораздо более эффективным, чем любое решение, использующее //, потому что мы используем ключ для эффективного нахождения «конечного» элемента item.


II. Решение XSLT 2.0:

Гораздо короче и проще -- одно выражение XPathe 2.0:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

 <xsl:key name="kNodeById" match="item" use="@id"/>

 <xsl:template match="/">
  <xsl:value-of select=
  "string-join(
       (
        'home',
       key('kNodeById', $pEnd)
          [ancestor::item[@section=$pStart]]
              [1]
                /ancestor-or-self::item
                [not(descendant::item[@section=$pStart])]
                       /(@id[not(../@section)], @section)[1]

        ),
      '>>'
        )
  "/>
 </xsl:template>
</xsl:stylesheet>
person Dimitre Novatchev    schedule 05.10.2012
comment
Хороший ответ! Спасибо за это. На самом деле я еще никогда не использовал функцию key(). Я немного изменил решение xslt 1.0, я изменил ancestor::item на ancestor-or-self::item в цикле for-each, чтобы иметь возможность также получать элементы первого уровня дерево (например, сервис, решения и т. д.). При этом панировочные сухари работают полностью. Также я использовал этот шаблон (технику) для создания URL-адреса (части пути) каждой крошки! - person Andreas W. Wylach; 05.10.2012
comment
@AndreasW.Wylach, добро пожаловать. Ключи — это очень мощная функция XSLT, и стоит познакомиться с ней и использовать ее. Если вы ограничены XSLT 1.0, наиболее эффективным методом группировки является использование ключей (метод группировки по Мюнху). - person Dimitre Novatchev; 05.10.2012

Вы можете просто выполнить итерацию по оси ancestor-or-self::. Это легко сделать без рекурсии. Например...

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

<xsl:template match="/">
  <html><body>
    <xsl:call-template name="bread-crumbs">
      <xsl:with-param name="items" select="*/item" />
      <xsl:with-param name="section" select="'service'" />
      <xsl:with-param name="leaf" select="'p1-2'" />
    </xsl:call-template>  
  </body></html>
</xsl:template>

<xsl:template name="bread-crumbs">
  <xsl:param name="items" />
  <xsl:param name="section" />
  <xsl:param name="leaf" />
  <xsl:value-of select="concat($section,'&gt;&gt;')" />
  <xsl:for-each select="$items/self::item[@section=$section]//item[@id=$leaf]/
                        ancestor-or-self::item[not(@section)]">
    <xsl:value-of select="@id" />
    <xsl:if test="position() != last()">
      <xsl:value-of select="'&gt;&gt;'" />
    </xsl:if>  
  </xsl:for-each>  
</xsl:template>  

</xsl:stylesheet>

... на входном образце получается...

<html>
  <body>service&gt;&gt;content-management-systems&gt;&gt;p1-2</body>
</html> 

...который отображается как...

service>>content-management-systems>>p1-2
person Sean B. Durkin    schedule 05.10.2012
comment
Спасибо за Ваш ответ! Я попробовал это, и это работает. Dimitre предоставил решение, с помощью которого я также мог легко получить URL-адрес (атрибут href) дерева, чтобы добавить его к ссылке одной хлебной крошки. - person Andreas W. Wylach; 05.10.2012