📜 ⬆️ ⬇️

Recursive patterns in XSLT

Hello to all!
I want to talk about the use of recursive templates in XSLT, as many beginners working with XSLT are faced with tasks that require their use and do not know how to solve such problems.
Take a couple of common examples:
1. There is a node with a string, it is necessary to split it into parts according to a certain character (in our case, take a space character) and decorate each part in different colors.
2. Make a page number (pager) output based on the fact that we know the total number of objects (for example, forum topics), the number of objects on a page and the page number on which we are currently located.

To begin with, I will give an example basis where we will derive several numbers using recursive patterns:

< xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
  1. < xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
  2. < xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
  3. < xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
  4. < xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
  5. < xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
  6. < xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
  7. < xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
  8. < xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
  9. < xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
  10. < xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
  11. < xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
  12. < xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
  13. < xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
  14. < xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
  15. < xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
  16. < xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .
< xsl:template name ="numbers" > < xsl:param name ="current-number" /> < xsl:param name ="max-number" /> < xsl:value-of select ="$current-number" /> <!-- --> < xsl:if test ="$current-number < $max-number" > <!-- , --> < xsl:text > </ xsl:text > <!-- --> < xsl:call-template name ="numbers" > < xsl:with-param name ="current-number" select ="$current-number + 1" /> < xsl:with-param name ="max-number" select ="$max-number" /> </ xsl:call-template > </ xsl:if > </ xsl:template > * This source code was highlighted with Source Code Highlighter .


Here, the condition in xsl: if, as many have already guessed, is intended to exit from recursion.
')
Calling a pattern with code (input XML you can specify any valid XML file)
  1. < xsl: template match = "/" >
  2. < xsl: call-template name = "numbers" >
  3. < xsl: with-param name = "current-number" select = "1" />
  4. < xsl: with-param name = "max-number" select = "50" />
  5. </ xsl: call-template >
  6. </ xsl: template >
* This source code was highlighted with Source Code Highlighter .

get the output of all numbers from 1 to 50 through the gap.

The one who understood the basic idea will immediately understand where this can and should be applied and can skip the next part of the article.

So, real examples.

Example â„–1


We have XML:
  1. <? xml version = "1.0" ? >
  2. < strings >
  3. < string > bla1 bla2 bla1 bla2 bla1 </ string >
  4. </ strings >
* This source code was highlighted with Source Code Highlighter .

It is necessary to break the line inside the string node by spaces and each odd element to display in green, and even every element in red.
XSLT / XPATH 2.0 has a great tokenize function that can break a line into parts and, accordingly, with xsl: for-each, we can run over it and do everything we want with each part. But XSLT 2.0, as far as I know, is well supported only by the Saxon processor. The embedded XSLT processors of browsers and libxslt in PHP do not have its support, so we will use a recursive template.
  1. <? xml version = '1.0' ? >
  2. < xsl: stylesheet version = "1.0" xmlns: xsl = "http://www.w3.org/1999/XSL/Transform" >
  3. <! - you can match and just string,
  4. then the contents of the node will be converted to text
  5. but if there are more nodes inside the string (for example, <br />),
  6. we can get an error
  7. Do not forget that if we are going to work with the text,
  8. you need to match the text,
  9. make your code work obvious ->
  10. < xsl: template match = "string / text ()" >
  11. < xsl: call-template name = "colorer" >
  12. < xsl: with-param name = "text" select = "." />
  13. <! - the remaining values ​​will be taken from the default values ​​->
  14. </ xsl: call-template >
  15. </ xsl: template >
  16. < xsl: template name = "colorer" >
  17. < xsl: param name = "text" />
  18. <! - default delimiter - space ->
  19. < xsl: param name = "delimeter" select = "''" />
  20. <! - by default the element is colored as odd ->
  21. < xsl: param name = "even" select = "false" />
  22. < xsl: variable name = "color" >
  23. < xsl: choose >
  24. < xsl: when test = "$ even" >
  25. < xsl: text > red </ xsl: text >
  26. </ xsl: when >
  27. < xsl: otherwise >
  28. < xsl: text > green </ xsl: text >
  29. </ xsl: otherwise >
  30. </ xsl: choose >
  31. </ xsl: variable >
  32. < xsl: choose >
  33. <! - if the line contains a separator ->
  34. < xsl: when test = "contains ($ text, $ delimeter)" >
  35. <! - then print the string before the delimiter ->
  36. < span class = "{$ color}" > < xsl: value-of select = "substring-before ($ text, $ delimeter)" /> </ span >
  37. <! - and once again call the template for the remaining line ->
  38. < xsl: call-template name = "colorer" >
  39. < xsl: with-param name = "delimeter" select = "$ delimeter" />
  40. < xsl: with-param name = "even" select = "not ($ even)" />
  41. < xsl: with-param name = "text" select = "substring-after ($ text, $ delimeter)" />
  42. </ xsl: call-template >
  43. </ xsl: when >
  44. < xsl: otherwise >
  45. < span class = "{$ color}" > < xsl: value-of select = "$ text" /> </ span >
  46. </ xsl: otherwise >
  47. </ xsl: choose >
  48. </ xsl: template >
  49. </ xsl: stylesheet >
* This source code was highlighted with Source Code Highlighter .

This example is contrived, it is unlikely that someone will need such an implementation, but very similar tasks are often encountered, so I decided to show it with such an example.

Example 2


This is a real working example that someone may need in their work, I think that with a few alterations everyone will be able to use it in their projects.
It turned out to be quite large, because here not only recursive templates are used, but also all sorts of beautiful are induced, not directly related to recursive templates, but making the result as close as possible to combat conditions.
So, we have XML of a type:
  1. <? xml version = "1.0" ? >
  2. < forum >
  3. < pages >
  4. < current-page number = "3" />
  5. < topics-per-page count = "15" />
  6. < topics count = "150" />
  7. < link href = "page.php? number =" />
  8. </ pages >
  9. < themes >
  10. < theme > theme1 </ theme >
  11. < theme > theme2 </ theme >
  12. < theme > theme3 </ theme >
  13. < theme > theme4 </ theme >
  14. < theme > theme5 </ theme >
  15. < theme > theme6 </ theme >
  16. < theme > theme7 </ theme >
  17. < theme > theme8 </ theme >
  18. < theme > theme9 </ theme >
  19. < theme > theme10 </ theme >
  20. < theme > theme11 </ theme >
  21. < theme > theme12 </ theme >
  22. < theme > theme13 </ theme >
  23. < theme > theme14 </ theme >
  24. < theme > theme15 </ theme >
  25. </ themes >
  26. </ forum >
* This source code was highlighted with Source Code Highlighter .

It is necessary to display page numbers (pager). Before and after the page numbers you need to display the transition to the previous and next page, provided that we are not on the first or the last page. We also set a condition that it is necessary to display only links to go to several nearby pages, and not to all at once, because then the pager can turn out very large.
We get this pretty big template:
  1. <? xml version = "1.0" ? >
  2. < xsl: stylesheet version = "1.0" xmlns: xsl = "http://www.w3.org/1999/XSL/Transform" >
  3. < xsl: template match = "pages" >
  4. < xsl: call-template name = "page-numbers" >
  5. < xsl: with-param name = "total-results" select = "topics / @ count" />
  6. < xsl: with-param name = "results-per-page" select = "topics-per-page / @ count" />
  7. < xsl: with-param name = "max-from-current-page" select = "3" />
  8. < xsl: with-param name = "current-page" select = "current-page / @ number" />
  9. < xsl: with-param name = "href" select = "link / @ href" />
  10. </ xsl: call-template >
  11. </ xsl: template >
  12. < xsl: template name = "page-numbers" >
  13. < xsl: param name = "total-results" />
  14. < xsl: param name = "results-per-page" />
  15. < xsl: param name = "max-from-current-page" />
  16. < xsl: param name = "current-page" />
  17. < xsl: param name = "href" />
  18. <! - How many pages we have ->
  19. < xsl: variable name = "max-page" select = "ceiling ($ total-results div $ results-per-page)" />
  20. <! - If there are more than one page, then print the page numbers ->
  21. < xsl: if test = "1 <$ max-page" >
  22. <! - From which page to start displaying page numbers ->
  23. < xsl: variable name = "from-page" >
  24. < xsl: choose >
  25. <! - If the number of the current page is greater than the maximum distance ->
  26. < xsl: when test = "$ current-page> $ max-from-current-page" >
  27. <! - This will be the first page that is deleted a specified number of pages from the current one ->
  28. < xsl: value-of select = "$ current-page - $ max-from-current-page" />
  29. </ xsl: when >
  30. < xsl: otherwise > 1 </ xsl: otherwise >
  31. </ xsl: choose >
  32. </ xsl: variable >
  33. <! - Which page to finish printing page numbers ->
  34. < xsl: variable name = "to-page" >
  35. < xsl: choose >
  36. <! - If the number of the current page is removed from the last page number more than the maximum distance ->
  37. < xsl: when test = "$ max-page - $ current-page> $ max-from-current-page" >
  38. <! - Then the last page will be deleted for a specified number of pages from the current one ->
  39. < xsl: value-of select = "$ current-page + $ max-from-current-page" />
  40. </ xsl: when >
  41. < xsl: otherwise >
  42. < xsl: value-of select = "$ max-page" />
  43. </ xsl: otherwise >
  44. </ xsl: choose >
  45. </ xsl: variable >
  46. <! - If the current page is not the first, then output arrows with a link to the previous page ->
  47. < xsl: if test = "1! = $ current-page" >
  48. <a href = "{$ href} {$ current-page - 1}"> <<</ a >
  49. < xsl: text > </ xsl: text >
  50. </ xsl: if >
  51. <! - Call the page numbering template with initial values ​​->
  52. < xsl: call-template name = "page-number" >
  53. < xsl: with-param name = "max-page-number" select = "$ to-page" />
  54. < xsl: with-param name = "current-number" select = "$ from-page" />
  55. < xsl: with-param name = "current-page" select = "$ current-page" />
  56. < xsl: with-param name = "href" select = "$ href" />
  57. </ xsl: call-template >
  58. <! - If the current page is not the last, then output arrows with a link to the next page ->
  59. < xsl: if test = "$ max-page! = $ current-page" >
  60. < xsl: text > </ xsl: text >
  61. <a href = "{$ href} {$ current-page + 1}"> >> </ a >
  62. </ xsl: if >
  63. </ xsl: if >
  64. </ xsl: template >
  65. < xsl: template name = "page-number" >
  66. < xsl: param name = "max-page-number" />
  67. < xsl: param name = "current-number" />
  68. < xsl: param name = "current-page" />
  69. < xsl: param name = "href" />
  70. < xsl: choose >
  71. <! - If we display the number of the current page, then without reference ->
  72. < xsl: when test = "$ current-number = $ current-page" >
  73. < xsl: value-of select = "$ current-number" />
  74. </ xsl: when >
  75. <! - Numbers of other pages with a link ->
  76. < xsl: otherwise >
  77. <a href = "{$ href} {$ current-number}">
  78. < xsl: value-of select = "$ current-number" />
  79. </ a >
  80. </ xsl: otherwise >
  81. </ xsl: choose >
  82. <! - If the current number is not the last one, then we call the template for the output of the next number ->
  83. < xsl: if test = "$ current-number <$ max-page-number" >
  84. < xsl: text > | </ xsl: text >
  85. < xsl: call-template name = "page-number" >
  86. < xsl: with-param name = "max-page-number" select = "$ max-page-number" />
  87. < xsl: with-param name = "current-number" select = "$ current-number + 1" />
  88. < xsl: with-param name = "current-page" select = "$ current-page" />
  89. < xsl: with-param name = "href" select = "$ href" />
  90. </ xsl: call-template >
  91. </ xsl: if >
  92. </ xsl: template >
  93. < xsl: template match = "@ * | node ()" >
  94. < xsl: copy >
  95. < xsl: apply-templates select = "@ * | node ()" />
  96. </ xsl: copy >
  97. </ xsl: template >
  98. </ xsl: stylesheet >
* This source code was highlighted with Source Code Highlighter .

In principle, according to the comments in the code, everything should be understood quite easily, but if you have questions, I will answer them with pleasure.

Useful reading: Alexey Valikov - Technology XSLT - Russian Bible XSLT :)

In the future I want to write about the use of keys and modes in XSLT, as well as to reveal a bunch of typical small errors beginners. I will also try to write about the performance of XSLT transformations, but this is unlikely to be a serious article with a bunch of statistical calculations, most likely just consider the performance bottlenecks. I can not give any guarantees how quickly these articles will appear, but I will try not to pull too much with them.

Source: https://habr.com/ru/post/47545/


All Articles