Sort   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 134
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 56
dl 0
loc 134
rs 10
c 1
b 0
f 0
wmc 22

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 3
A sort() 0 11 3
A flatten() 0 7 1
A getSortingKeys() 0 3 1
C performSort() 0 58 14
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
12
use Seboettg\CiteProc\CiteProc;
13
use Seboettg\CiteProc\Data\DataList;
14
use Seboettg\CiteProc\Exception\CiteProcException;
15
use Seboettg\CiteProc\Util\DateHelper;
16
use Seboettg\CiteProc\Util\Variables;
17
use Seboettg\Collection\ArrayList;
18
use SimpleXMLElement;
19
20
/**
21
 * Class Sort
22
 *
23
 * cs:citation and cs:bibliography may include a cs:sort child element before the cs:layout element to specify the
24
 * sorting order of respectively cites within citations, and bibliographic entries within the bibliography.
25
 *
26
 * The cs:sort element must contain one or more cs:key child elements. The sort key, set as an attribute on cs:key, must
27
 * be a variable (see Appendix IV - Variables) or macro name. For each cs:key element, the sort direction can be set to
28
 * either “ascending” (default) or “descending” with the sort attribute.
29
 *
30
 * @package Seboettg\CiteProc\Style
31
 *
32
 * @author Sebastian Böttger <[email protected]>
33
 */
34
class Sort
35
{
36
    /**
37
     * ordered list contains sorting keys
38
     *
39
     * @var ArrayList
40
     */
41
    private $sortingKeys;
42
43
    /**
44
     * @var SimpleXMLElement $node
45
     */
46
    public function __construct(SimpleXMLElement $node)
47
    {
48
        $this->sortingKeys = new ArrayList();
49
        /** @var SimpleXMLElement $child */
50
        foreach ($node->children() as $child) {
51
            if ("key" === $child->getName()) {
52
                $this->sortingKeys->add(new Key($child));
0 ignored issues
show
Bug introduced by
It seems like $child can also be of type null; however, parameter $node of Seboettg\CiteProc\Style\Sort\Key::__construct() does only seem to accept SimpleXMLElement, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

52
                $this->sortingKeys->add(new Key(/** @scrutinizer ignore-type */ $child));
Loading history...
53
            }
54
        }
55
    }
56
57
    /**
58
     * Function in order to sort a set of csl items by one or multiple sort keys.
59
     * Sort keys are evaluated in sequence. A primary sort is performed on all items using the first sort key.
60
     * A secondary sort, using the second sort key, is applied to items sharing the first sort key value. A tertiary
61
     * sort, using the third sort key, is applied to items sharing the first and second sort key values. Sorting
62
     * continues until either the order of all items is fixed, or until the sort keys are exhausted. Items with an
63
     * empty sort key value are placed at the end of the sort, both for ascending and descending sorts.
64
     *
65
     * @param DataList|array $data reference
66
     */
67
    public function sort(&$data)
68
    {
69
        if (is_array($data)) {
70
            $dataList = new DataList();
71
            $dataList->setArray($data);
72
            $data = $dataList;
73
        }
74
        $dataToSort = $data->toArray();
75
        try {
76
            $data->setArray($this->performSort(0, $dataToSort));
77
        } catch (CiteProcException $e) {
78
            //nothing to do, because $data is passed by referenced
79
        }
80
    }
81
82
    /**
83
     * Recursive function in order to sort a set of csl items by one or multiple sort keys.
84
     * All items will be distributed by the value (defined in respective sort key) in an associative array (grouped).
85
     * Afterwards the array will be sorted by the array key. If a further sort key exist, each of these groups will be
86
     * sorted by a recursive function call. Finally the array will be flatted.
87
     *
88
     * @param $keyNumber
89
     * @param array $dataToSort
90
     * @return array
91
     * @throws CiteProcException
92
     */
93
    private function performSort($keyNumber, $dataToSort)
94
    {
95
        if (count($dataToSort) < 2) {
96
            return $dataToSort;
97
        }
98
99
        /** @var Key $key */
100
        $key = $this->sortingKeys->get($keyNumber);
101
        $variable = $key->getVariable();
102
        $groupedItems = [];
103
104
        if ($key->isDateVariable()) {
105
            if (DateHelper::hasDateRanges($dataToSort, $variable, "all")) {
106
                $newKey = clone $key;
107
                $newKey->setRangePart(2);
108
                $key->setRangePart(1);
109
                $this->sortingKeys->add($newKey);
110
            }
111
        }
112
113
        //grouping by value
114
        foreach ($dataToSort as $citationNumber => $dataItem) {
115
            if ($key->isNameVariable()) {
116
                $sortKey = Variables::nameHash($dataItem, $variable);
117
            } elseif ($key->isNumberVariable()) {
118
                $sortKey = $dataItem->{$variable};
119
            } elseif ($key->isDateVariable()) {
120
                $sortKey = DateHelper::getSortKeyDate($dataItem, $key);
121
            } elseif ($key->isMacro()) {
122
                $sortKey = mb_strtolower(strip_tags(CiteProc::getContext()->getMacro(
123
                    $key->getMacro()
124
                )->render($dataItem, $citationNumber)));
125
            } elseif ($variable === "citation-number") {
126
                $sortKey = $citationNumber + 1;
127
            } else {
128
                $sortKey = mb_strtolower(strip_tags($dataItem->{$variable}));
129
            }
130
            $groupedItems[$sortKey][] = $dataItem;
131
        }
132
133
        if ($this->sortingKeys->count() > ++$keyNumber) {
134
            foreach ($groupedItems as $group => &$array) {
135
                if (count($array) > 1) {
136
                    $array = $this->performSort($keyNumber, $array);
137
                }
138
            }
139
        }
140
141
        //sorting by array keys
142
        if ($key->getSort() === "ascending") {
143
            ksort($groupedItems); //ascending
144
        } else {
145
            krsort($groupedItems); //reverse
146
        }
147
148
        //the flattened array is the result
149
        $sortedDataGroups = array_values($groupedItems);
150
        return $this->flatten($sortedDataGroups);
151
    }
152
153
    public function flatten($array)
154
    {
155
        $returnArray = [];
156
        array_walk_recursive($array, function ($a) use (&$returnArray) {
157
            $returnArray[] = $a;
158
        });
159
        return $returnArray;
160
    }
161
162
    /**
163
     * @return ArrayList
164
     */
165
    public function getSortingKeys()
166
    {
167
        return $this->sortingKeys;
168
    }
169
}
170