naturalsort.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. <?php
  2. /**
  3. * @author AW-UC <git@a-wesemann.de>
  4. * @author Lukas Reschke <lukas@owncloud.com>
  5. * @author Morris Jobke <hey@morrisjobke.de>
  6. * @author Robin McCorkell <rmccorkell@karoshi.org.uk>
  7. * @author Vincent Petry <pvince81@owncloud.com>
  8. *
  9. * @copyright Copyright (c) 2015, ownCloud, Inc.
  10. * @license AGPL-3.0
  11. *
  12. * This code is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU Affero General Public License, version 3,
  14. * as published by the Free Software Foundation.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU Affero General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU Affero General Public License, version 3,
  22. * along with this program. If not, see <http://www.gnu.org/licenses/>
  23. *
  24. */
  25. namespace OC;
  26. class NaturalSort {
  27. private static $instance;
  28. private $collator;
  29. private $cache = array();
  30. /**
  31. * Instantiate a new \OC\NaturalSort instance.
  32. * @param object $injectedCollator
  33. */
  34. public function __construct($injectedCollator = null) {
  35. // inject an instance of \Collator('en_US') to force using the php5-intl Collator
  36. // or inject an instance of \OC\NaturalSort_DefaultCollator to force using Owncloud's default collator
  37. if (isset($injectedCollator)) {
  38. $this->collator = $injectedCollator;
  39. \OC_Log::write('core', 'forced use of '.get_class($injectedCollator), \OC_Log::DEBUG);
  40. }
  41. }
  42. /**
  43. * Split the given string in chunks of numbers and strings
  44. * @param string $t string
  45. * @return array of strings and number chunks
  46. */
  47. private function naturalSortChunkify($t) {
  48. // Adapted and ported to PHP from
  49. // http://my.opera.com/GreyWyvern/blog/show.dml/1671288
  50. if (isset($this->cache[$t])) {
  51. return $this->cache[$t];
  52. }
  53. $tz = array();
  54. $x = 0;
  55. $y = -1;
  56. $n = null;
  57. while (isset($t[$x])) {
  58. $c = $t[$x];
  59. // only include the dot in strings
  60. $m = ((!$n && $c === '.') || ($c >= '0' && $c <= '9'));
  61. if ($m !== $n) {
  62. // next chunk
  63. $y++;
  64. $tz[$y] = '';
  65. $n = $m;
  66. }
  67. $tz[$y] .= $c;
  68. $x++;
  69. }
  70. $this->cache[$t] = $tz;
  71. return $tz;
  72. }
  73. /**
  74. * Returns the string collator
  75. * @return \Collator string collator
  76. */
  77. private function getCollator() {
  78. if (!isset($this->collator)) {
  79. // looks like the default is en_US_POSIX which yields wrong sorting with
  80. // German umlauts, so using en_US instead
  81. if (class_exists('Collator')) {
  82. $this->collator = new \Collator('en_US');
  83. }
  84. else {
  85. $this->collator = new \OC\NaturalSort_DefaultCollator();
  86. }
  87. }
  88. return $this->collator;
  89. }
  90. /**
  91. * Compare two strings to provide a natural sort
  92. * @param string $a first string to compare
  93. * @param string $b second string to compare
  94. * @return int -1 if $b comes before $a, 1 if $a comes before $b
  95. * or 0 if the strings are identical
  96. */
  97. public function compare($a, $b) {
  98. // Needed because PHP doesn't sort correctly when numbers are enclosed in
  99. // parenthesis, even with NUMERIC_COLLATION enabled.
  100. // For example it gave ["test (2).txt", "test.txt"]
  101. // instead of ["test.txt", "test (2).txt"]
  102. $aa = self::naturalSortChunkify($a);
  103. $bb = self::naturalSortChunkify($b);
  104. for ($x = 0; isset($aa[$x]) && isset($bb[$x]); $x++) {
  105. $aChunk = $aa[$x];
  106. $bChunk = $bb[$x];
  107. if ($aChunk !== $bChunk) {
  108. // test first character (character comparison, not number comparison)
  109. if ($aChunk[0] >= '0' && $aChunk[0] <= '9' && $bChunk[0] >= '0' && $bChunk[0] <= '9') {
  110. $aNum = (int)$aChunk;
  111. $bNum = (int)$bChunk;
  112. return $aNum - $bNum;
  113. }
  114. return self::getCollator()->compare($aChunk, $bChunk);
  115. }
  116. }
  117. return count($aa) - count($bb);
  118. }
  119. /**
  120. * Returns a singleton
  121. * @return \OC\NaturalSort instance
  122. */
  123. public static function getInstance() {
  124. if (!isset(self::$instance)) {
  125. self::$instance = new \OC\NaturalSort();
  126. }
  127. return self::$instance;
  128. }
  129. }