naturalsort.php 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. <?php
  2. /**
  3. * Copyright (c) 2014 Vincent Petry <PVince81@owncloud.com>
  4. * This file is licensed under the Affero General Public License version 3 or
  5. * later.
  6. * See the COPYING-README file.
  7. *
  8. */
  9. namespace OC;
  10. class NaturalSort {
  11. private static $instance;
  12. private $collator;
  13. private $cache = array();
  14. /**
  15. * Instantiate a new \OC\NaturalSort instance.
  16. * @param object $injectedCollator
  17. */
  18. public function __construct($injectedCollator = null) {
  19. // inject an instance of \Collator('en_US') to force using the php5-intl Collator
  20. // or inject an instance of \OC\NaturalSort_DefaultCollator to force using Owncloud's default collator
  21. if (isset($injectedCollator)) {
  22. $this->collator = $injectedCollator;
  23. \OC_Log::write('core', 'forced use of '.get_class($injectedCollator), \OC_Log::DEBUG);
  24. }
  25. }
  26. /**
  27. * Split the given string in chunks of numbers and strings
  28. * @param string $t string
  29. * @return array of strings and number chunks
  30. */
  31. private function naturalSortChunkify($t) {
  32. // Adapted and ported to PHP from
  33. // http://my.opera.com/GreyWyvern/blog/show.dml/1671288
  34. if (isset($this->cache[$t])) {
  35. return $this->cache[$t];
  36. }
  37. $tz = array();
  38. $x = 0;
  39. $y = -1;
  40. $n = null;
  41. while (isset($t[$x])) {
  42. $c = $t[$x];
  43. // only include the dot in strings
  44. $m = ((!$n && $c === '.') || ($c >= '0' && $c <= '9'));
  45. if ($m !== $n) {
  46. // next chunk
  47. $y++;
  48. $tz[$y] = '';
  49. $n = $m;
  50. }
  51. $tz[$y] .= $c;
  52. $x++;
  53. }
  54. $this->cache[$t] = $tz;
  55. return $tz;
  56. }
  57. /**
  58. * Returns the string collator
  59. * @return \Collator string collator
  60. */
  61. private function getCollator() {
  62. if (!isset($this->collator)) {
  63. // looks like the default is en_US_POSIX which yields wrong sorting with
  64. // German umlauts, so using en_US instead
  65. if (class_exists('Collator')) {
  66. $this->collator = new \Collator('en_US');
  67. }
  68. else {
  69. $this->collator = new \OC\NaturalSort_DefaultCollator();
  70. }
  71. }
  72. return $this->collator;
  73. }
  74. /**
  75. * Compare two strings to provide a natural sort
  76. * @param string $a first string to compare
  77. * @param string $b second string to compare
  78. * @return int -1 if $b comes before $a, 1 if $a comes before $b
  79. * or 0 if the strings are identical
  80. */
  81. public function compare($a, $b) {
  82. // Needed because PHP doesn't sort correctly when numbers are enclosed in
  83. // parenthesis, even with NUMERIC_COLLATION enabled.
  84. // For example it gave ["test (2).txt", "test.txt"]
  85. // instead of ["test.txt", "test (2).txt"]
  86. $aa = self::naturalSortChunkify($a);
  87. $bb = self::naturalSortChunkify($b);
  88. for ($x = 0; isset($aa[$x]) && isset($bb[$x]); $x++) {
  89. $aChunk = $aa[$x];
  90. $bChunk = $bb[$x];
  91. if ($aChunk !== $bChunk) {
  92. // test first character (character comparison, not number comparison)
  93. if ($aChunk[0] >= '0' && $aChunk[0] <= '9' && $bChunk[0] >= '0' && $bChunk[0] <= '9') {
  94. $aNum = (int)$aChunk;
  95. $bNum = (int)$bChunk;
  96. return $aNum - $bNum;
  97. }
  98. return self::getCollator()->compare($aChunk, $bChunk);
  99. }
  100. }
  101. return count($aa) - count($bb);
  102. }
  103. /**
  104. * Returns a singleton
  105. * @return \OC\NaturalSort instance
  106. */
  107. public static function getInstance() {
  108. if (!isset(self::$instance)) {
  109. self::$instance = new \OC\NaturalSort();
  110. }
  111. return self::$instance;
  112. }
  113. }