summaryrefslogtreecommitdiffstats
path: root/views_natural_sort.inc
blob: d2fa6ed244998044ce219ec846e045ad7e450a26 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
<?php

/**
 * @file
 * The Views Natural Sort module include file.
 */

/**
 * Remove all the configured words from the beginning of the string only.
 *
 * @param string $string
 *   The string we wish to transform.
 *
 * @return string
 *   The transformed string.
 */
function views_natural_sort_remove_beginning_words($string) {
  $beginning_words = [
    t('The'),
    t('A'),
    t('An'),
    t('La'),
    t('Le'),
    t('Il'),
  ];
  if (empty($beginning_words)) {
    return $string;
  }

  array_walk($beginning_words, 'preg_quote');
  return preg_replace(
    '/^(' . implode('|', $beginning_words) . ')\s+/i',
    '',
    $string
  );
}

/**
 * Remove all the configured words from the string.
 *
 * @param string $string
 *   The string we wish to transform.
 *
 * @return string
 *   The transformed string.
 */
function views_natural_sort_remove_words($string) {
  $words = [
    t('and'),
    t('or'),
    t('of'),
  ];
  if (empty($words)) {
    return $string;
  }

  array_walk($words, 'preg_quote');
  return preg_replace(
    array(
      '/\s(' . implode('|', $words) . ')\s+/i',
      '/^(' . implode('|', $words) . ')\s+/i',
    ),
    array(
      ' ',
      '',
    ),
    $string
  );
}

/**
 * Remove all the configured symbols from the string.
 *
 * @param string $string
 *   The string we wish to transform.
 *
 * @return string
 *   The transformed string.
 */
function views_natural_sort_remove_symbols($string) {
  $symbols = "#\"'\\()[]";
  if (strlen($symbols) == 0) {
    return $string;
  }
  return preg_replace(
    '/[' . preg_quote($symbols) . ']/',
    '',
    $string
  );
}

/**
 * Transform numbers in a string into a natural sortable string.
 *
 * Rules are as follows:
 *  - Embedded numbers will sort in numerical order. The following possibilities
 *    are supported
 *    - A leading dash indicates a negative number, unless it is preceded by a
 *      non-whitespace character, which case it is considered just a dash.
 *    - Leading zeros are properly ignored so as to not influence sort order
 *    - Decimal numbers are supported using a period as the decimal character
 *    - Thousands separates are ignored, using the comma as the thous. character
 *    - Numbers may be up to 99 digits before the decimal, up to the precision
 *      of the processor.
 *
 * @param string $string
 *   The string we wish to transform.
 */
function views_natural_sort_numbers($string) {
  // Find an optional leading dash (either preceded by whitespace or the first
  // character) followed by either:
  // - an optional series of digits (with optional embedded commas), then a
  //   period, then an optional series of digits
  // - a series of digits (with optional embedded commas)
  return preg_replace_callback(
    '/(\s-|^-)?(?:(\d[\d,]*)?\.(\d+)|(\d[\d,]*))/',
    '_views_natural_sort_number_transform_match_callback',
    $string
  );
}

/**
 * Transforms a string representing numbers into a special format.
 *
 * This special format can be sorted as if it was a number but in reality is
 *   being sorted alphanumerically.
 *
 * @param array $match
 *   Array of matches passed from preg_replace_callback
 *   $match[0] is the entire matching string
 *   $match[1] if present, is the optional dash, preceded by optional whitespace
 *   $match[2] if present, is whole number portion of the decimal number
 *   $match[3] if present, is the fractional portion of the decimal number
 *   $match[4] if present, is the integer (when no fraction is matched).
 *
 * @return string
 *   String representing a numerical value that will sort numerically in an
 *   alphanumeric search.
 */
function _views_natural_sort_number_transform_match_callback(array $match) {

  // Remove commas and leading zeros from whole number.
  $whole = (string) (int) str_replace(',', '', (isset($match[4]) && strlen($match[4]) > 0) ? $match[4] : $match[2]);
  // Remove traililng 0's from fraction, then add the decimal and one trailing
  // 0 and a space. The space serves as a way to always sort shorter decimal
  // numbers that match exactly as less than longer ones.
  // Ex: 3.05 and 3.05011.
  $fraction = trim('.' . $match[3], '0') . '0 ';
  $encode = sprintf('%02u', strlen($whole)) . $whole . $fraction;
  if (strlen($match[1])) {
    // Negative number. Make 10's complement. Put back any leading white space
    // and the dash requires intermediate to avoid double-replacing the same
    // digit. str_replace() seems to work by copying the source to the result,
    // then successively replacing within it, rather than replacing from the
    // source to the result.
    // In this case since rules are reverced we also have to use a character
    // that would be sorted higher than a space when a number is being compared
    // against a longer one that is identical in negative numbers. This is so
    // that longer numbers are always LESS than sorter numbers that have
    // identical beginnings. Ex: -3.05 and -3.05011.
    $digits       = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ');
    $intermediate = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k');
    $rev_digits   = array('9', '8', '7', '6', '5', '4', '3', '2', '1', '0', ':');
    $encode       = $match[1] . str_replace($intermediate, $rev_digits, str_replace($digits, $intermediate, $encode));
  }
  return $encode;
}