выровнять текст по горизонтали и вертикали в алфавитном порядке

У меня есть несколько заголовков, которые я получаю с помощью цикла for вокруг некоторого xml (это может быть n заголовков). Я хочу отображать их по горизонтали в 3 столбца, но по вертикали в алфавитном порядке:

Если у меня есть 3 названия, я представляю их только алфавитом (я могу подсчитать количество названий.

A              B              C                    -----count(3)

4 titles:
A              C              D                    -----count(4)
B    

5 Titles: 
A              C              E                    -----count(5)
B              D   

7 Titles:
A              D              F                     -----count(7)
B              E              G
C

Я использую xsl 1.0, и сейчас он у меня такой

<div class="navigation">
<ul>
<xsl:foreach select="/Custom/Alphabet/titles">

   <li>
     <xsl:value-of select="." />
   </li>
</xsl:foreach>
</ul>
</div>

person Jack    schedule 14.12.2011    source источник
comment
@_Jack: Был ли мой ответ полезен?   -  person Dimitre Novatchev    schedule 16.12.2011
comment
@_Jack: Смотрите мое обновление — я также предоставил решение XSLT 1.0, и его можно использовать непосредственно в браузере.   -  person Dimitre Novatchev    schedule 16.12.2011
comment
@_Jack: я добавил в конце своего ответа окончательное, нерекурсивное и, вероятно, более эффективное решение на чистом XSLT 1.0, которое вообще не использует никаких функций расширения. Пожалуйста, сообщите нам, если это решение полезно для вас.   -  person Dimitre Novatchev    schedule 17.12.2011
comment
@_Jack: Вы читали мой последний обновленный ответ и решает ли он проблему? Если у вас все еще есть какие-либо проблемы, каковы они?   -  person Dimitre Novatchev    schedule 19.12.2011
comment
К сожалению, не было возможности проверить это раньше. Последнее обновление xsl 1.0, которое у вас есть, действительно полезно. Я не использовал его точно, но я извлек из него логику и реализовал его. Я отмечаю это ответом .. Спасибо !!   -  person Jack    schedule 19.12.2011


Ответы (2)


Отличный вопрос!

И. Вот решение XSLT 2.0 (65 строк, можно преобразовать в XSLT 1.0 практически механически):

<xsl:stylesheet version="2.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:xs="http://www.w3.org/2001/XMLSchema"
     xmlns:my="my:my" exclude-result-prefixes="xs my">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>

     <xsl:variable name="vItems" select="/*/*/string(.)"
      as="item()+"/>


     <xsl:template match="/">
      <xsl:sequence select="my:fill($vItems, 3)"/>
     </xsl:template>

     <xsl:function name="my:fill" as="element()+">
      <xsl:param name="pItems" as="item()*"/>
      <xsl:param name="pK" as="xs:integer"/>

      <xsl:variable name="pN" select="count($pItems)"/>

      <xsl:choose>
       <xsl:when test="$pN le $pK">
        <xsl:sequence select="my:fillRow($pItems)"/>
       </xsl:when>
       <xsl:otherwise>
        <xsl:variable name="vColSize"
         select="ceiling($pN div $pK)"/>

        <xsl:variable name="vCol-1" select=
         "$pItems[position() le $vColSize]"/>

        <xsl:variable name="vSubTable"
         select="my:fill($pItems[position() gt $vColSize],
                         $pK -1
                        )
         "/>

         <xsl:sequence select="my:merge($vCol-1, $vSubTable)"/>
       </xsl:otherwise>
      </xsl:choose>
     </xsl:function>

     <xsl:function name="my:fillRow" as="element()">
      <xsl:param name="pItems" as="item()*"/>

      <row>
       <xsl:for-each select="$pItems">
        <cell><xsl:sequence select="."/></cell>
       </xsl:for-each>
      </row>
     </xsl:function>

     <xsl:function name="my:merge" as="element()*">
      <xsl:param name="pCol" as="item()*"/>
      <xsl:param name="pTable" as="element()*"/>

      <xsl:for-each select="$pCol">
       <xsl:variable name="vrowPos" select="position()"/>
       <row>
        <cell><xsl:sequence select="."/></cell>
        <xsl:sequence select="$pTable[position() eq $vrowPos]/cell"/>
       </row>
      </xsl:for-each>
     </xsl:function>
</xsl:stylesheet>

когда это преобразование применяется к представленному (наиболее сложному) случаю из 7 элементов:

<titles>
 <t>A</t>
 <t>B</t>
 <t>C</t>
 <t>D</t>
 <t>E</t>
 <t>F</t>
 <t>G</t>
</titles>

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

<row>
   <cell>A</cell>
   <cell>D</cell>
   <cell>F</cell>
</row>
<row>
   <cell>B</cell>
   <cell>E</cell>
   <cell>G</cell>
</row>
<row>
   <cell>C</cell>
</row>

Я убедился, что ожидаемый правильный результат выдается для каждого N = 1 to 7.

Объяснение:

Мы строим необходимую таблицу рекурсивно по количеству элементов во входной последовательности (pN):

  1. база рекурсии определяется для любого $pN, не превышающего $pK (необходимое количество столбцов). В этом базовом случае таблица имеет одну строку.

  2. В общем случае $pN > $pK ; затем мы строим крайний левый столбец $vCol-1 и рекурсивно меньшую таблицу с остальными элементами и новым количеством необходимых столбцов: $pK -1.

  3. В случае 2. выше мы, наконец, объединяем столбец и подтаблицу, чтобы создать результирующую таблицу.


II. Эквивалентное решение XSLT 2.0, написанное в «большем стиле XSLT 2.0» (60 строк):

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:my="my:my" exclude-result-prefixes="xs my">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:variable name="vItems" select="/*/*/string(.)"
  as="item()+"/>


 <xsl:template match="/">
  <xsl:sequence select="my:fill($vItems, 3)"/>
 </xsl:template>

 <xsl:function name="my:fill" as="element()+">
  <xsl:param name="pItems" as="item()*"/>
  <xsl:param name="pK" as="xs:integer"/>

  <xsl:sequence select=
   "for $vN in count($pItems)
     return
       if($vN le $pK)
         then my:fillRow($pItems)
         else
           (for $vColSize in xs:integer(ceiling($vN div $pK))
             return
               my:merge((for $i in 1 to $vColSize
                           return $pItems[$i]),
                             my:fill((for $i in $vColSize+1 to $vN
                                       return $pItems[$i]),
                                      $pK -1
                                     )
                        )
            )
   "/>
 </xsl:function>

 <xsl:function name="my:fillRow" as="element()">
  <xsl:param name="pItems" as="item()*"/>

  <row>
   <xsl:for-each select="$pItems">
    <cell><xsl:sequence select="."/></cell>
   </xsl:for-each>
  </row>
 </xsl:function>

 <xsl:function name="my:merge" as="element()*">
  <xsl:param name="pCol" as="item()*"/>
  <xsl:param name="pTable" as="element()*"/>

  <xsl:for-each select="$pCol">
   <xsl:variable name="vrowPos" select="position()"/>
   <row>
    <cell><xsl:sequence select="."/></cell>
    <xsl:sequence select="$pTable[position() eq $vrowPos]/cell"/>
   </row>
  </xsl:for-each>
 </xsl:function>
</xsl:stylesheet>

III. Решение XSLT 1.0 (75 строк)

Это первое решение XSLT 2.0 (см. выше), почти механически переведенное в XSLT 1.0:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>

     <xsl:variable name="vItems" select="/*/*"/>

     <xsl:template match="/">
      <xsl:call-template name="fill">
       <xsl:with-param name="pItems" select="$vItems"/>
       <xsl:with-param name="pK" select="3"/>
      </xsl:call-template>
     </xsl:template>

     <xsl:template name="fill">
      <xsl:param name="pItems"/>
      <xsl:param name="pK"/>

      <xsl:variable name="vN" select="count($pItems)"/>

      <xsl:choose>
       <xsl:when test="not($vN > $pK)">
        <row>
         <xsl:call-template name="fillRow">
          <xsl:with-param name="pItems" select="$pItems"/>
         </xsl:call-template>
        </row>
       </xsl:when>
       <xsl:otherwise>
        <xsl:variable name="vColSize"
         select="ceiling($vN div $pK)"/>

        <xsl:variable name="vCol-1" select=
         "$pItems[not(position() > $vColSize)]"/>

        <xsl:variable name="vrtfSubtable">
         <xsl:call-template name="fill">
          <xsl:with-param name="pItems" select=
           "$pItems[position() > $vColSize]"/>
          <xsl:with-param name="pK" select="$pK -1"/>
         </xsl:call-template>
        </xsl:variable>

        <xsl:variable name="vSubTable" select=
        "ext:node-set($vrtfSubtable)/*"/>

         <xsl:call-template name="merge">
          <xsl:with-param name="pCol" select="$vCol-1"/>
          <xsl:with-param name="pTable" select="$vSubTable"/>
         </xsl:call-template>
       </xsl:otherwise>
      </xsl:choose>
     </xsl:template>

     <xsl:template name="fillRow">
      <xsl:param name="pItems"/>

       <xsl:for-each select="$pItems">
        <cell><xsl:value-of select="."/></cell>
       </xsl:for-each>
     </xsl:template>

     <xsl:template name="merge">
      <xsl:param name="pCol"/>
      <xsl:param name="pTable"/>

      <xsl:for-each select="$pCol">
       <xsl:variable name="vrowPos" select="position()"/>
       <row>
        <cell><xsl:value-of select="."/></cell>
        <xsl:copy-of select="$pTable[position() = $vrowPos]/cell"/>
       </row>
      </xsl:for-each>
     </xsl:template>
</xsl:stylesheet>

IV. Наконец, чистое генеративное (нерекурсивное) решение 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:param name="pK" select="3"/>

 <xsl:variable name="vItems" select="/*/*"/>

 <xsl:template match="/">
  <xsl:call-template name="genTable"/>
 </xsl:template>

 <xsl:template name="genTable">
  <xsl:param name="pItems" select="$vItems"/>
  <xsl:param name="pK" select="$pK"/>

  <xsl:variable name="vN" select=
   "count($vItems)"/>
  <xsl:variable name="vnumRows"
     select="ceiling($vN div $pK)"/>

  <table>
   <xsl:for-each select=
     "$pItems[not(position() > $vnumRows)]">
     <xsl:call-template name="genRow">
      <xsl:with-param name="pRowInd" select="position()"/>
      <xsl:with-param name="pItems" select="$vItems"/>
      <xsl:with-param name="pK" select="$pK"/>
     </xsl:call-template>
   </xsl:for-each>
  </table>
 </xsl:template>

 <xsl:template name="genRow">
  <xsl:param name="pRowInd" select="position()"/>
  <xsl:param name="pItems" select="$vItems"/>
  <xsl:param name="pK" select="$pK"/>

  <xsl:variable name="vN" select=
   "count($vItems)"/>
  <xsl:variable name="vFullCols" select=
   "$vN mod $pK"/>
  <xsl:variable name="vFullColSize" select=
   "ceiling($vN div $pK)"/>

   <tr>
     <td><xsl:value-of select="$pItems[number($pRowInd)]"/></td>

   <xsl:for-each select=
    "$pItems[position() > 1
           and
             not(position() > $pK)
            ]">
    <xsl:variable name="vX" select="position()+1"/>

    <xsl:variable name="vMinFullColsAndX" select=
    "($vX > $vFullCols) * $vFullCols
     +
      not($vX > $vFullCols) * $vX
    "/>

    <xsl:variable name="vAmmt1" select=
    "$vMinFullColsAndX * $vFullColSize
    "/>

    <xsl:variable name="vAmmt2" select=
    "($vX -1 - $vMinFullColsAndX) * ($vFullColSize -1)
    "/>

    <xsl:variable name="vValue" select=
    "$vAmmt1 + $vAmmt2"/>

    <xsl:if test="not(($pRowInd -1) * $pK +$vX > $vN)">
     <td><xsl:value-of select=
     "$pItems[position()=$pRowInd+$vValue]"/>
     </td>
    </xsl:if>
   </xsl:for-each>
  </tr>

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

при применении к тому же XML-документу (см. выше) создается желаемый правильный результат:

<table>
   <tr>
      <td>A</td>
      <td>D</td>
      <td>F</td>
   </tr>
   <tr>
      <td>B</td>
      <td>E</td>
      <td>G</td>
   </tr>
   <tr>
      <td>C</td>
   </tr>
</table>
person Dimitre Novatchev    schedule 15.12.2011

CSS3 на самом деле определяет отличное свойство, которое делает это за вас, называемое column-count. Он поддерживается в хороших браузерах и IE10. Если обратная совместимость не является проблемой, это довольно приятное решение, близкое к тому, что вы хотите.

http://jsfiddle.net/CMeXC/

person Andrew    schedule 15.12.2011
comment
Это, конечно, чистое решение с несколькими строками кода CSS, но я бы не смог его использовать. Эта функциональность также должна поддерживать старые браузеры, такие как IE 7,8,9. Спасибо.. - person Jack; 15.12.2011