Passed
Push — new-api ( 4bfe18...7ec1cc )
by Sebastian
05:06
created

Sort::flatten()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 1
rs 10
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 Seboettg\Collection\ArrayList\ArrayListInterface;
19
use SimpleXMLElement;
20
21
/**
22
 * Class Sort
23
 *
24
 * cs:citation and cs:bibliography may include a cs:sort child element before the cs:layout element to specify the
25
 * sorting order of respectively cites within citations, and bibliographic entries within the bibliography.
26
 *
27
 * The cs:sort element must contain one or more cs:key child elements. The sort key, set as an attribute on cs:key, must
28
 * be a variable (see Appendix IV - Variables) or macro name. For each cs:key element, the sort direction can be set to
29
 * either “ascending” (default) or “descending” with the sort attribute.
30
 *
31
 * @package Seboettg\CiteProc\Style
32
 *
33
 * @author Sebastian Böttger <[email protected]>
34
 */
35
class Sort
36
{
37
    /**
38
     * ordered list contains sorting keys
39
     *
40
     * @var ArrayList
41
     */
42
    private $sortingKeys;
43
44 173
    public static function factory(?SimpleXMLElement $node): ?Sort
45
    {
46 173
        if ($node && $node->children()->count() > 0) {
47 60
            $sortingKeys = new ArrayList();
48
            /** @var SimpleXMLElement $child */
49 60
            foreach ($node->children() as $child) {
50 60
                if ("key" === $child->getName()) {
51 60
                    $sortingKeys->append(Key::factory($child));
52
                }
53
            }
54 60
            return new Sort($sortingKeys);
55
        }
56 138
        return null;
57
    }
58
59 60
    public function __construct(ArrayListInterface $sortingKeys)
60
    {
61 60
        $this->sortingKeys = $sortingKeys;
0 ignored issues
show
Documentation Bug introduced by
$sortingKeys is of type Seboettg\Collection\ArrayList\ArrayListInterface, but the property $sortingKeys was declared to be of type Seboettg\Collection\ArrayList. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

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