Completed
Push — master ( c434f2...7ac5be )
by Sebastian
03:19
created

Sort   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 105
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 14
lcom 1
cbo 4
dl 0
loc 105
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 3
A sort() 0 4 1
C performSort() 0 42 8
A flatten() 0 5 1
A getSortingKeys() 0 4 1
1
<?php
2
/**
3
 * citeproc-php
4
 *
5
 * @link        http://github.com/seboettg/citeproc-php for the source repository
6
 * @copyright   Copyright (c) 2016 Sebastian Böttger.
7
 * @license     https://opensource.org/licenses/MIT
8
 */
9
10
namespace Seboettg\CiteProc\Style\Sort;
11
use Seboettg\CiteProc\Util\Variables;
12
use Seboettg\CiteProc\Util\Date;
13
use Seboettg\Collection\ArrayList;
14
15
16
/**
17
 * Class Sort
18
 *
19
 * cs:citation and cs:bibliography may include a cs:sort child element before the cs:layout element to specify the
20
 * sorting order of respectively cites within citations, and bibliographic entries within the bibliography.
21
 *
22
 * The cs:sort element must contain one or more cs:key child elements. The sort key, set as an attribute on cs:key, must
23
 * be a variable (see Appendix IV - Variables) or macro name. For each cs:key element, the sort direction can be set to
24
 * either “ascending” (default) or “descending” with the sort attribute.
25
 *
26
 * @package Seboettg\CiteProc\Style
27
 *
28
 * @author Sebastian Böttger <[email protected]>
29
 */
30
class Sort
31
{
32
    /**
33
     * ordered list contains sorting keys
34
     *
35
     * @var ArrayList
36
     */
37
    private $sortingKeys;
38
39
    /**
40
     * @var \SimpleXMLElement $node
41
     */
42
    public function __construct(\SimpleXMLElement $node)
43
    {
44
        $this->sortingKeys = new ArrayList();
45
        /** @var \SimpleXMLElement $child */
46
        foreach ($node->children() as $child) {
47
            if ("key" === $child->getName()) {
48
                $this->sortingKeys->append(new Key($child));
49
            }
50
        }
51
    }
52
53
    /**
54
     * Function in order to sort a set of csl items by one or multiple sort keys.
55
     * Sort keys are evaluated in sequence. A primary sort is performed on all items using the first sort key.
56
     * A secondary sort, using the second sort key, is applied to items sharing the first sort key value. A tertiary
57
     * sort, using the third sort key, is applied to items sharing the first and second sort key values. Sorting
58
     * continues until either the order of all items is fixed, or until the sort keys are exhausted. Items with an
59
     * empty sort key value are placed at the end of the sort, both for ascending and descending sorts.
60
     *
61
     * @param array $data reference
62
     */
63
    public function sort(&$data)
64
    {
65
        $data = $this->performSort(0, $data);
66
    }
67
68
    /**
69
     * Recursive function in order to sort a set of csl items by one or multiple sort keys.
70
     * All items will be distributed by the value (defined in respective sort key) in an associative array (grouped).
71
     * Afterwards the array will be sorted by the array key. If a further sort key exist, each of these groups will be
72
     * sorted by a recursive function call. Finally the array will be flatted.
73
     *
74
     * @param $keyNumber
75
     * @param $dataToSort
76
     * @return array
77
     */
78
    private function performSort($keyNumber, $dataToSort)
79
    {
80
        if (count($dataToSort) < 2) {
81
            return $dataToSort;
82
        }
83
84
        /** @var Key $key */
85
        $key = $this->sortingKeys->get($keyNumber);
86
        $variable = $key->getVariable();
87
        $groupedItems = [];
88
89
        //grouping by value
90
        foreach ($dataToSort as $dataItem) {
91
            if ($key->isNameVariable()) {
92
                $groupedItems[Variables::nameHash($dataItem, $variable)][] = $dataItem;
93
            }
94
            if ($key->isNumberVariable()) {
95
                $groupedItems[$dataItem->{$variable}][] = $dataItem;
96
            }
97
            if ($key->isDateVariable()) {
98
                $groupedItems[Date::serializeDate($dataItem->{$variable})][] = $dataItem;
99
            }
100
        }
101
102
        // there are further keys ?
103
        if ($this->sortingKeys->count() > ++$keyNumber) {
104
            array_walk($groupedItems, function(&$group) use ($keyNumber){
105
                $group = $this->performSort($keyNumber, $group); //recursive call for next sort key
106
            });
107
        }
108
109
        //sorting by array keys
110
        if ($key->getSort() === "ascending") {
111
            ksort($groupedItems); //ascending
112
        } else {
113
            krsort($groupedItems); //reverse
114
        }
115
116
        //the flattened array is the result
117
        $sortedDataGroups = array_values($groupedItems);
118
        return $this->flatten($sortedDataGroups);
119
    }
120
121
    public function flatten(array $array) {
122
        $returnArray = [];
123
        array_walk_recursive($array, function($a) use (&$returnArray) { $returnArray[] = $a; });
124
        return $returnArray;
125
    }
126
127
    /**
128
     * @return ArrayList
129
     */
130
    public function getSortingKeys()
131
    {
132
        return $this->sortingKeys;
133
    }
134
}