XSLTのページネーション(ページネーション)

時々、死にかけているXSLTテクノロジーがポップアップして、難しい質問をします。 たとえば、式で最大2つの数値を取得する方法、またはサイクルを整理する方法。 これらの問題の多くの組み合わせが、ページネーターであり、いくつかのページでのナビゲーションの結論であり、可能であれば便利です。 Javascriptには、シンプルで便利なページネーターの例が数多くあります。 しかし、サーバーからのページがXMLで出力される場合、平静な考えが生じます。ページネーションを含むページのすべてのレイアウトを静的にXSLTで行わないのはなぜですか。 この静的にJSを含めて、すべてを単純化できるものはありません。 コーシャのアプローチは簡単な方法を探していません。



XSLTの長所と短所



JSの1行ではなく、多くの機能を備えた12行の再帰を記述する必要があります。 マイナスにしましょう。 それにもかかわらず、このタスクは実行可能です。つまり、いつかそのようなソリューションがどこかに現れるということです。



Webでは、2つの数字を追加する方法に関する質問と同様に、「 HTMLからのコメントが削除されないようにXSLTにコメントする方法 」という回答に非常に感謝しています 。 したがって、最小限の知識で、SOの評判があなたに提供されます。



XSLTが長年にわたって存在してきたため、非常に古くて単純なブラウザに加えて、多くのブラウザがXML + XSLTを処理する能力を獲得してきました。 これは、理解しにくい宣言型コードの余分な数百行によっても、サーバーからクライアント(ブラウザー)に作業をシフトすることで使用できます。



多くの場合、宣言性は良いです。 CSSのような一連のルールは、理解と使用が簡単です。 しかし、一般的な構文の問題を伴う宣言型言語で再帰プロシージャを書き始めることは、オタクや絶望的な状況に陥っている人々のための活動です。 したがって、この記事には2つの目標があります-オタクのために、そして読書のために、そして遊びのために-人々のために、実際の例を取り上げて、あなた自身のためにそれを構成します。



Habréでも同様のトピックがここで取り上げられました: habrahabr.ru/post/138740ページネーターの実装)。



ページネーターに必要なもの



XMLから、現在のページ番号と(場合によっては)リストの最後のページ番号のみを受け取ります。 それ以外は、* .xslのページネータスタティックで設定されます。 制作からわかるように、「休み」はあまりありません。 現在のページへのリンクの周りのリンクの数だけ。 しかし、その後、拡張機能が追加されました-数十または別の間隔で次のページの出力。 これは、ページネーターの可能性を示す良い例のように思えました。



1)現在のページを表示します(リンク自体を更新する必要がある場合、またはリンクなしでリンクを使用する場合)。

2)隣接するページのいくつかのリンクを表示します(現在のページの前後)。

3)最初および最後の(極端な)ページ(「隣接」に該当しない場合)。

4)省略記号。隣接するページと極端なページの間に表示されていないページがある場合。

5)オプション-省略されたページ間隔のほぼ中央に移動する楕円上のリンク。

6)エッジが一致するために一部のリンクが表示されない場合、現在のページのリンクの反対側に検出されていない数のリンクを追加します。 つまり、指定された数のリンク内で何を表示するかを表示します。 たとえば、5つのリンク「前」と5つの「後」を示していますが、3番目のページを表示すると、「前」の2つのリンクが表示されます。 したがって、ある場合は「後」に8つのリンクを表示します(最大ページ数を超えないようにします)。



(この要件は部分的に満たされています。ページ番号が最初に近い場合、右側に余分なリンクが表示され、ページ番号が最大に近い場合はリストの半分のみが表示されます。これは、立った。)



7)最後に、paginator関数の二重使用-最初から数十(または5つ、すべてが構成されている)までのページの出力。 非常に大きなリストをすばやく数十ページに移動する必要がある場合に役立ちますが、通常は最初のページにいます。 終了ページが指定されている場合、10のリストは表示されません。



XSLT Taoに飛び込む



ストーリーを有用にするために、この宣言型言語でのプログラミング手法のトレーニングの形でストーリーを構築します。 単純なモデルからますます複雑になるモデルまで、ページネーターを構築します。



構築の基礎として、特定のログファイルを取得します。これは、多くの場合Web管理者によって検出され、表示する必要があります。 ブラウジングを便利にし、プログラミングコストを小さくするために、ページごとにログをXMLで印刷し、すべてのデザインはクライアントXSLTを含むクライアントテクノロジーに割り当てられます。



前述のように、ページネーターは手続き言語で行う方が自然です。 しかし、XSLTはこのタスクにも対応し、定式化のすべての要件を満たします。 実装の多くの例はインターネット上に散らばっており、1つでもHabréで会いました。 しかし、構築規則の説明がない例は、基本から始めて、実装を独立して行わなければならないという事実につながります。 この例は、完全で機能的なページネーターの例を提供するための試みであり、接続が単純であり、その管理が文書化されることが期待されています。



ログには約500のエントリがありますが、ページ分割を行う最も簡単な方法は、ページに10個のリンクを表示し、次のようにHTMLでページ番号を手動で記述することです。

<a href="page.xml?page=2"/>2</a>
      
      







500件を超えるレコードまたはそれよりも深いレコードがめったにない場合、めったに見られません。ページ番号を入力するためのフォームの属性で十分です。 また、出口。 これは、XSLTの深化を必要とせず、xslファイルで一般的に行われます。



 <?xml version="1.0"?> <!DOCTYPE html> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <html> <head><title>Someone Log</title> <meta http-equiv="x-ua-compatible" content="IE=8"/> <style> body{ ..../*   , ,        */ } </style> <script type="text/javascript"> ... </script> </head> <body> <!--    ,   ,   --> <table class="tb1" id="tb1"> <tr> <th>ip + <span class="n">#</span></th> <th>path</th> <th>browser</th> <th>accType</th> <th>fileName</th> <th>settings</th> <th>date</th> </tr> <xsl:for-each select="/ha/actions/action"> <tr class="account-{accountType} {fileName}"> <td class="help leftJust" title2="{@id}"> <div class="full"> <span><xsl:value-of select="@id"/></span> </div> <div class="brief"><xsl:value-of select="ip"/></div> </td> <td class="leftJust"><a href="http://habrahabr.ru{path}" target="_blank"> <xsl:value-of select="path"/> </a></td> <td class="help UA" title2="{agent}" align="center"> <div class="full"> <div class="fullRel"> <span><xsl:value-of select="agent"/></span> </div> </div> <div class="brief"><xsl:value-of select="browser"/></div> </td> <td><xsl:value-of select="accountType"/></td> <td class="fileName {fileName}"><xsl:value-of select="fileName"/></td> <td> <span class="{settings/property/@value}"> <xsl:value-of select="settings/property/@name"/> </span> </td> <xsl:variable name="dt" select="date"/> <td><span title="{substring($dt,1,10)}"> <xsl:value-of select="substring($dt,12,10)"/> </span></td> </tr> </xsl:for-each> </table> <div class="pagination"> <!--    --> <!--      HTML: --> <span class=""> <a href="page.xml?page=1"/>1</a> </span> <span class=""> <a href="page.xml?page=2"/>2</a> </span> <span class=""> <a href="page.xml?page=3"/>3</a> </span> ...<!--   - 10  --> </body> </html>
      
      







最初の問題は、現在のページのクラスを作成して、それを何らかの方法で選択または無効化することさえ難しいことです。 問題ありません、これにはJSがあります。 しかし、計画では-XSLTへのリンクを作成します。 したがって、しぶしぶですが、10行のこの奇妙なループをどのように作成し、XSLTへのリンクのリストを最初に編成する方法を見てみましょう。



再帰関数を作成します。 テンプレート関数は、テンプレート本体から呼び出されます。 すべてのパラメータを転送する必要があります-これらは独立した名前空間であるため、通常の言語のようにグローバルスコープを定義することは不可能です。



 <xsl:template match="/"> ... <xsl:comment>======   ,  10  ======</xsl:comment> <xsl:call-template name="paginate"> <xsl:with-param name="nLinks" select="10"/> <xsl:with-param name="p" select="/ha/page"/> <xsl:with-param name="url" select="$url"/> </xsl:call-template> </div></body></html> </xsl:template> <xsl:comment>====== - -     ======</xsl:comment> <xsl:template name="paginate"> <xsl:param name="i" select="1"/> <xsl:comment> () </xsl:comment> <xsl:param name="nLinks"/> <xsl:param name="p"/> <xsl:param name="url"/> <xsl:if test="$i <= $nLinks"> <span class="{concat('active', number($i = $p)) }"> <a href="{concat($url, $i)}"> <xsl:value-of select="$i"/> </a> </span> <xsl:call-template name="paginate"> <xsl:with-param name="i" select="$i + 1"/> <xsl:with-param name="nLinks" select="$nLinks"/> <xsl:with-param name="p" select="$p"/> <xsl:with-param name="url" select="$url"/> </xsl:call-template> </xsl:if> </xsl:template>
      
      







やあ、サイクルを作ったよ! 5行の呼び出しと15行の関数が仕事をしました-現在のリンクをマークでき、30行のHTMLを書くことはできません! これは成果であり、ページネーションを征服するための最初のステップです。 そして、JSで5のコストがかかり、読みやすくなるということはありません。 主なことは慣れることであり、それから悟りが来るでしょう。



8行のコードを短くするために、トリックが行われました。クラスを指定する場合、choose-when-otherwiseブロックは書き込まれませんが、「active」という単語に1または0が追加されたため、「active1」=現在のページのリンククラスです。



言語機能はこのセクションに表示されます。デフォルトのパラメーターは、呼び出すときに省略できます。 再帰では、必要なすべてのパラメーターをリストする必要があります。 select = "$ i + 1"はサイクルが移動する重要な場所であり、test = "$ i <= $ nLinksはそれが停止する場所です。



&lt; -言語の特性に応じていくつかの文字(<、>、&、/)を記述する必要がある。



ページ数が可変で、要素内の数によって決定される場合は、単に書く

 <xsl:with-param name="nLinks" select="/ha/pageLast"/>
      
      







リンクの前後に対称



次のタスク:限られた数のリンクを表示します。その半分は現在のページのリンクの前に、残りの半分は後になります。 何度も繰り返される式には変数を使用します。 間隔をループしますが、1未満の数のリンクは表示しません。偶数のリンクの場合、「前」にもう1つのリンクがあると考えられます(ほとんどの場合、この数は常に奇数になりますが、すべてのケースでテストする必要があります)。



実装には、追加の「to」パラメータが必要でした。このパラメータでは、最大ページ番号が再帰的に格納および送信されます。



 <div class="pagination"> : <xsl:variable name="url">http://37.230.115.43/actions/last.xml?page=</xsl:variable> <xsl:variable name="p" select="/ha/page"/> <xsl:comment> </xsl:comment> <xsl:variable name="nL" select="9"/> <xsl:comment>   </xsl:comment> <xsl:call-template name="paginate"> <xsl:with-param name="i" select="$p"/> <xsl:with-param name="nLinks" select="$nL"/> <xsl:with-param name="url" select="$u"/> </xsl:call-template> </div></body></html> </xsl:template> <xsl:template name="paginate"> <xsl:param name="i" select="1"/> <xsl:param name="nLinks"/> <xsl:param name="url"/> <xsl:param name="to" select="$i + $nLinks"/> <xsl:variable name="n2" select="floor($nLinks div 2)"/> <xsl:if test="$i < $to"> <xsl:if test="$i - $n2 >= 1"> <span class="{concat('active', number($i = $to - ceiling($nLinks div 2))) }"> <a href="{concat($url, $i - $n2)}"> <xsl:value-of select="$i - $n2"/> </a> </span> </xsl:if> <xsl:call-template name="paginate"> <xsl:with-param name="i" select="$i + 1"/> <xsl:with-param name="url" select="$url"/> <xsl:with-param name="nLinks" select="$nLinks"/> <xsl:with-param name="to" select="$to"/> </xsl:call-template> </xsl:if> </xsl:template>
      
      







不足しているものが2つあります。最初のページへのリンク(必要な場合)、および最初のページにいるときの半分ではなく、完全な数のリンクの表示です。 リンク「1」を表示する必要がある場合、および-ページのリンクの一部をスキップすることを意味する省略記号の場合、検証関数を呼び出す前に追加します。



関数にカウンターを追加します。このカウンターは、$ nLinkの数に応じて、現在とは異なり、実際に追加されたリンクの数をカウントして、$ nLinkに到達するサイクルを停止します。



カウンターを使用したソリューションは簡単です。 この決定は、後で解決する必要がある論理爆弾のペアを置きます。

1)サイクルが終わることはありません。 念のために、別のコントロールカウンターを導入します。念のため、たとえば50の数字を使用します。 うーん、すでに2つのカウンター。 解決策は見た目ほど美しくありません。

2)ページの始まりは簡単に計算できますが、ページのリストの終わり近くです-許容数を超えて表示されない数を予測する必要があります。 しかし、一度にすべてではありません。



これにより、(条件付きで)最初のページがマークされます。



 <xsl:variable name="pn2" select="$p - floor($nL div 2)"/> <xsl:if test="$pn2 > 1"> <span class=""> <a href="{concat($url, 1)}">1</a> <xsl:if test="$pn2 > 2"> <a class="ellip" title="{floor(($pn2 +1) div 2)}" href="{concat($url, floor(($pn2 +1) div 2) )}">...</a> </xsl:if> </span> </xsl:if>
      
      







省略リンクでは、表示されていない間隔のほぼ中央のリンクがツールチップに表示されます。 たとえば、リンクの表示は60ページから始まります-30または29は省略記号付きで作成されます。 番号を表示しないリンクは、より簡潔で、より便利で、追加のスペースはまったく必要ありません。 2番目からリンクが表示される場合、楕円は表示されません。



防衛



パラメーターの変更を開始し、たとえば100万を誤って設定した開発者から-50に等しいstopパラメーターを入力して、再帰の数を制限します。これにより、ページネーターは50回を超える反復を実行しません。



間隔をあけたページネーション(段落7)



フレームワークが作成されると、残りの「機能」が簡単に追加されます(もちろん、開発者が既にテクノロジーを認識している場合)。 これを実証するために、最後のページネーターで、数ページの間隔でリンクを表示する機能を追加します。 ナビゲーションに必要な場合もあります-ページではなくページ上のレコードをカウントするために必要な場合があります。 これは「前と後」のリンクを表示するように構成されているため、ページネーターの少し不適切な使用になります。また、インターバル全体の出力に対して表示されます。 ただし、新しいページネータを記述したり、誤用に合わせて調整したりする代わりに、適切なパラメータを選択する、つまりフロアを追加する($ n2 div 2)方が簡単です。 この警告と追加のパラメーターステップを使用して、ページネーターが機能し始めます。



続きますが 、読者が既製のページネーターを見て使用したい場合は、 spmbt.github.io/spmbt/wk/37.20.115.43.xmlにあります。 ログページのアドレスとリンクは変更され、偶然の一致はランダムです。 これは静的な例であり、常に9ページにあるため、ページ切り替えは無効になっています。 しかし、上部にはspmbt.github.io/spmbt/wk/37.20.115.43.xslを介して構築されたページネーターがあります。 ページネーターに直接関連する行:

XSLTコード
 <?xml version="1.0"?> <!DOCTYPE html> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <html> ... <div class="pagination"> : <xsl:variable name="url">#page=</xsl:variable> <xsl:variable name="p" select="/ha/page"/> <xsl:comment> </xsl:comment> <xsl:variable name="nL" select="11"/> <xsl:comment>   </xsl:comment> <xsl:variable name="pLast" select="/ha/pageLast"/> <xsl:comment> ( ;   ,     )</xsl:comment> <xsl:variable name="pn2" select="$p - floor($nL div 2)"/> <xsl:if test="$pn2 > 1"> <span class=""> <a href="{concat($url, 1)}">1</a> <xsl:if test="$pn2 > 2"> <a class="ellip" title="{floor(($pn2 +1) div 2)}" href="{concat($url, floor(($pn2 +1) div 2) )}">...</a> </xsl:if> </span> </xsl:if> <xsl:call-template name="paginate"> <xsl:with-param name="i" select="$p"/> <xsl:with-param name="nLinks" select="$nL"/> <xsl:with-param name="pLast" select="$pLast"/> <xsl:with-param name="url" select="$url"/> </xsl:call-template> <xsl:if test="string-length($pLast) =0"> <xsl:variable name="nL2" select="5"/> <xsl:variable name="step" select="10"/> <xsl:call-template name="paginate"> <xsl:with-param name="i" select="floor(($p + $nL + $step +1) div $step) * $step + floor($nL2 div 2)"/> <xsl:with-param name="nLinks" select="$nL2"/> <xsl:with-param name="pLast" select="$pLast"/> <xsl:with-param name="step" select="$step"/> <xsl:with-param name="url" select="$url"/> <xsl:with-param name="class" select="'gaps'"/> </xsl:call-template> </xsl:if> <xsl:variable name="pp2" select="$p + floor(($nL -1) div 2)"/> <xsl:if test="$pp2 < $pLast"> <span class=""> <xsl:if test="$pp2 < $pLast -1"> <a class="ellip" title="{$pLast - floor(($pLast - $pp2) div 2)}" href="{concat($url, $pLast - floor(($pLast - $pp2) div 2) )}">...</a> </xsl:if> <a href="{concat($url, $pLast)}"><xsl:value-of select="$pLast"/></a> </span> </xsl:if> </div> </body> </html> </xsl:template> <xsl:template name="paginate"> <xsl:param name="i" select="1"/> <xsl:param name="nLinks"/> <xsl:param name="pLast"/> <xsl:param name="step" select="1"/> <xsl:param name="to" select="$i + $nLinks"/> <xsl:param name="url"/> <xsl:param name="class"/> <xsl:param name="count" select="1"/> <xsl:param name="stop" select="50"/> <xsl:variable name="n2" select="floor($nLinks div 2)"/> <xsl:if test="($i < $to or $count <= $nLinks) and $stop > 0"> <xsl:if test="$i - $n2 >= 1 and $i - $n2 <= $pLast or $i - $n2 >= 1 and string-length($pLast) =0"> <span class="{concat($class,' active', number($i = $to - ceiling($nLinks div 2)))}"> <a href="{concat($url, $i - $n2)}"> <xsl:value-of select="$i - $n2"/> </a> </span> </xsl:if> <xsl:call-template name="paginate"> <xsl:with-param name="i" select="$i + $step"/> <xsl:with-param name="to" select="$to"/> <xsl:with-param name="nLinks" select="$nLinks"/> <xsl:with-param name="pLast" select="$pLast"/> <xsl:with-param name="step" select="$step"/> <xsl:with-param name="url" select="$url"/> <xsl:with-param name="class" select="$class"/> <xsl:with-param name="count" select="$count + number($i - $n2 >= 1 and $i - $n2 <= $pLast or $i - $n2 >= 1 and string-length($pLast) =0)"/> <xsl:with-param name="stop" select="$stop - 1"/> </xsl:call-template> </xsl:if> </xsl:template> </xsl:stylesheet>
      
      





。 メインコードは85行かかりました-これは、2サイクルが実行されるという事実にもかかわらず、良い結果です-ページおよび数十ページで、中央リンクの楕円が含まれています。 IE8 +およびその他の最新のブラウザーでサポートされています。



All Articles