Passed
Push — new-api ( 5a646f...d09dbc )
by Sebastian
04:05
created

Layout   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 279
Duplicated Lines 0 %

Test Coverage

Coverage 96.06%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 119
c 2
b 0
f 0
dl 0
loc 279
ccs 122
cts 127
cp 0.9606
rs 9.2
wmc 40

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A factory() 0 18 3
A renderCitations() 0 12 2
A setStyleOptions() 0 3 1
A getNumberOfCitedItems() 0 3 1
A setDelimiter() 0 3 1
A setSorting() 0 3 1
A isGroupedCitations() 0 7 2
A setParent() 0 3 1
A setChildren() 0 3 1
A renderGroupedCitations() 0 14 3
B renderSingle() 0 37 10
A wrapBibEntry() 0 7 1
A htmlentities() 0 4 1
A setStylesRenderer() 0 3 1
A filterCitationItems() 0 9 3
B render() 0 33 7

How to fix   Complexity   

Complex Class

Complex classes like Layout often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Layout, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types=1);
3
/*
4
 * citeproc-php
5
 *
6
 * @link        http://github.com/seboettg/citeproc-php for the source repository
7
 * @copyright   Copyright (c) 2016 Sebastian Böttger.
8
 * @license     https://opensource.org/licenses/MIT
9
 */
10
11
namespace Seboettg\CiteProc\Rendering;
12
13
use Seboettg\CiteProc\CiteProc;
14
use Seboettg\CiteProc\Config\RenderingMode;
15
use Seboettg\CiteProc\Data\DataList;
16
use Seboettg\CiteProc\Config\RenderingState;
17
use Seboettg\CiteProc\Exception\CiteProcException;
18
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
19
use Seboettg\CiteProc\Rendering\Observer\CitedItemsChangedEvent;
20
use Seboettg\CiteProc\Rendering\Observer\RenderingObserver;
21
use Seboettg\CiteProc\Rendering\Observer\RenderingObserverTrait;
22
use Seboettg\CiteProc\Rendering\Observer\StateChangedEvent;
23
use Seboettg\CiteProc\Style\Options\StyleOptions;
24
use Seboettg\CiteProc\Style\Sort\Sort;
25
use Seboettg\CiteProc\Styles\ConsecutivePunctuationCharacterTrait;
26
use Seboettg\CiteProc\Styles\StylesRenderer;
27
use Seboettg\CiteProc\Util\CiteProcHelper;
28
use Seboettg\CiteProc\Util\Factory;
29
use Seboettg\CiteProc\Util\StringHelper;
30
use Seboettg\Collection\ArrayList as ArrayList;
31
use Seboettg\Collection\ArrayList\ArrayListInterface;
32
use SimpleXMLElement;
33
use stdClass;
34
use function Seboettg\CiteProc\array_clone;
35
36
/**
37
 * Class Layout
38
 *
39
 * @package Seboettg\CiteProc\Rendering
40
 *
41
 * @author Sebastian Böttger <[email protected]>
42
 */
43
class Layout implements RenderingObserver
44
{
45
    use ConsecutivePunctuationCharacterTrait,
46
        RenderingObserverTrait;
47
48
    private static $numberOfCitedItems = 0;
49
50
    /**
51
     * @var ArrayList
52
     */
53
    private $children;
54
55
    /**
56
     * When used within cs:citation, the delimiter attribute may be used to specify a delimiter for cites within a
57
     * citation.
58
     *
59
     * @var string
60
     */
61
    private $delimiter = "";
62
63
64
    private $parent;
65
66
    /**
67
     * @var StylesRenderer
68
     */
69
    private $stylesRenderer;
70
71
    /** @var Sort */
72
    private $sorting;
73
74
    /** @var StyleOptions */
75
    private $styleOptions;
76
77
    /**
78
     * @param SimpleXMLElement|null $node
79
     * @return Layout
80
     * @throws InvalidStylesheetException
81
     */
82 173
    public static function factory(?SimpleXMLElement $node): ?Layout
83
    {
84 173
        if (null === $node) {
85
            return null;
86
        }
87 173
        $children = new ArrayList();
88 173
        $layout = new Layout();
89 173
        foreach ($node->children() as $csChild) {
90 173
            $child = Factory::create($csChild);
91 173
            $child->setParent($layout);
92 173
            $children->append($child);
93
        }
94 173
        $stylesRenderer = StylesRenderer::factory($node);
95 173
        $layout->setChildren($children);
96 173
        $layout->setStylesRenderer($stylesRenderer);
97 173
        $layout->setDelimiter((string)$node->attributes()['delimiter']);
98 173
        CiteProc::getContext()->addObserver($layout);
99 173
        return $layout;
100
    }
101
102 173
    public function __construct()
103
    {
104 173
        $this->children = new ArrayList();
105 173
        $this->initObserver();
106 173
    }
107
108
    /**
109
     * @param array|DataList $data
110
     * @return string|array
111
     * @throws CiteProcException
112
     */
113 169
    public function render($data)
114
    {
115 169
        $ret = "";
116 169
        if (!empty($this->sorting)) {
117 50
            $this->setState(RenderingState::SORTING());
118 50
            $this->sorting->sort($data);
119 50
            $this->setState(RenderingState::RENDERING());
120
        }
121
122 169
        if ($this->mode->equals(RenderingMode::BIBLIOGRAPHY())) {
123 80
            foreach ($data as $citationNumber => $item) {
124 80
                ++self::$numberOfCitedItems;
125 80
                CiteProc::getContext()->getResults()->append(
126 80
                    $this->wrapBibEntry($item, $this->renderSingle($item, $citationNumber))
127
                );
128
            }
129 80
            $ret .= implode($this->delimiter, CiteProc::getContext()->getResults()->toArray());
130 80
            $ret = StringHelper::clearApostrophes($ret);
131 80
            return sprintf("<div class=\"csl-bib-body\">%s\n</div>", $ret);
132 103
        } elseif ($this->mode->equals(RenderingMode::CITATION())) {
133 103
            if ($this->citationItems->count() > 0) { //is there a filter for specific citations?
134 9
                if ($this->isGroupedCitations($this->citationItems)) { //if citation items grouped?
135 1
                    return $this->renderGroupedCitations($data, $this->citationItems);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type array; however, parameter $data of Seboettg\CiteProc\Render...enderGroupedCitations() does only seem to accept Seboettg\CiteProc\Data\DataList, 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

135
                    return $this->renderGroupedCitations(/** @scrutinizer ignore-type */ $data, $this->citationItems);
Loading history...
136
                } else {
137 8
                    $data = $this->filterCitationItems($data, $this->citationItems);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type array; however, parameter $data of Seboettg\CiteProc\Render...::filterCitationItems() does only seem to accept Seboettg\CiteProc\Data\DataList, 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

137
                    $data = $this->filterCitationItems(/** @scrutinizer ignore-type */ $data, $this->citationItems);
Loading history...
138 8
                    $ret = $this->renderCitations($data, $ret);
139
                }
140
            } else {
141 94
                $ret = $this->renderCitations($data, $ret);
142
            }
143
        }
144 102
        $ret = StringHelper::clearApostrophes($ret);
145 102
        return $this->stylesRenderer->renderAffixes($ret);
146
    }
147
148
    /**
149
     * @param  $data
150
     * @param  int|null $citationNumber
151
     * @return string
152
     */
153 169
    private function renderSingle($data, $citationNumber = null)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$citationNumber" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$citationNumber"; expected 0 but found 1
Loading history...
154
    {
155 169
        $inMargin = [];
156 169
        $margin = [];
157 169
        foreach ($this->children as $key => $child) {
158 169
            $rendered = $child->render($data, $citationNumber);
159 169
            $this->getChildrenAffixesAndDelimiter($child);
160 169
            if ($this->mode->equals(RenderingMode::BIBLIOGRAPHY())
161 169
                && $this->styleOptions->getSecondFieldAlign() === "flush"
0 ignored issues
show
Bug introduced by
The method getSecondFieldAlign() does not exist on Seboettg\CiteProc\Style\Options\StyleOptions. It seems like you code against a sub-type of Seboettg\CiteProc\Style\Options\StyleOptions such as Seboettg\CiteProc\Style\...ons\BibliographyOptions. ( Ignorable by Annotation )

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

161
                && $this->styleOptions->/** @scrutinizer ignore-call */ getSecondFieldAlign() === "flush"
Loading history...
162
            ) {
163 10
                if ($key === 0 && !empty($rendered)) {
164 10
                    $inMargin[] = $rendered;
165
                } else {
166 10
                    $margin[] = $rendered;
167
                }
168
            } else {
169 169
                $inMargin[] = $rendered;
170
            }
171
        }
172 169
        $inMargin = array_filter($inMargin);
173 169
        $margin = array_filter($margin);
174 169
        if (!empty($inMargin) && !empty($margin) && $this->mode->equals(RenderingMode::BIBLIOGRAPHY())) {
175 9
            $leftMargin = $this->removeConsecutiveChars(
176 9
                $this->htmlentities($this->stylesRenderer->renderFormatting(implode("", $inMargin)))
177
            );
178 9
            $result = $this->htmlentities($this->stylesRenderer->renderFormatting(implode("", $margin)));
179 9
            $result = rtrim($result, $this->stylesRenderer->getAffixes()->getSuffix()) .
180 9
                $this->stylesRenderer->getAffixes()->getSuffix();
181 9
            $rightInline = $this->removeConsecutiveChars($result);
182 9
            $res  = sprintf('<div class="csl-left-margin">%s</div>', trim($leftMargin));
183 9
            $res .= sprintf('<div class="csl-right-inline">%s</div>', trim($rightInline));
184 9
            return $res;
185 161
        } elseif (!empty($inMargin)) {
186 161
            $res = $this->stylesRenderer->renderFormatting(implode("", $inMargin));
187 161
            return $this->htmlentities($this->removeConsecutiveChars($res));
188
        }
189
        return "";
190
    }
191
192
    /**
193
     * @return int
194
     */
195
    public static function getNumberOfCitedItems(): int
196
    {
197
        return self::$numberOfCitedItems;
198
    }
199
200
    /**
201
     * @param stdClass $dataItem
202
     * @param string $value
203
     * @return string
204
     */
205 80
    private function wrapBibEntry(stdClass $dataItem, string $value): string
206
    {
207 80
        $value = $this->stylesRenderer->renderAffixes($value);
208
        return "\n  ".
209
            "<div class=\"csl-entry\">" .
210 80
            $renderedItem = CiteProcHelper::applyAdditionMarkupFunction($dataItem, "csl-entry", $value) .
0 ignored issues
show
Unused Code introduced by
The assignment to $renderedItem is dead and can be removed.
Loading history...
211 80
            "</div>";
212
    }
213
214
    /**
215
     * @param  string $text
216
     * @return string
217
     */
218 169
    private function htmlentities($text)
219
    {
220 169
        $text = preg_replace("/(.*)&([^#38|amp];.*)/u", "$1&#38;$2", $text);
221 169
        return $text;
222
    }
223
224
    /**
225
     * @param $data
226
     * @param string $ret
227
     * @return string
228
     */
229 103
    private function renderCitations($data, string $ret)
230
    {
231 103
        CiteProc::getContext()->getResults()->replace([]);
232 103
        foreach ($data as $citationNumber => $item) {
233 103
            $renderedItem = $this->renderSingle($item, $citationNumber);
234 103
            $renderedItem = CiteProcHelper::applyAdditionMarkupFunction($item, "csl-entry", $renderedItem);
235 103
            CiteProc::getContext()->getResults()->append($renderedItem);
236 103
            $this->citedItems->append($item);
237 103
            $this->notifyAll(new CitedItemsChangedEvent($this->citedItems));
238
        }
239 103
        $ret .= implode($this->delimiter, CiteProc::getContext()->getResults()->toArray());
240 103
        return $ret;
241
    }
242
243
    /**
244
     * @param DataList $data
245
     * @param ArrayListInterface $citationItems
246
     * @return ArrayListInterface
247
     */
248 9
    private function filterCitationItems(DataList $data, ArrayListInterface $citationItems): ArrayListInterface
249
    {
250
        return $data->filter(function ($dataItem) use ($citationItems) {
251 9
            foreach ($citationItems as $citationItem) {
252 9
                if ($dataItem->id === $citationItem->id) {
253 9
                    return true;
254
                }
255
            }
256 3
            return false;
257 9
        });
258
    }
259
260
    /**
261
     * @param  ArrayListInterface $citationItems
262
     * @return bool
263
     */
264 9
    private function isGroupedCitations(ArrayListInterface $citationItems): bool
265
    {
266 9
        $firstItem = array_values($citationItems->toArray())[0];
267 9
        if (is_array($firstItem)) {
268 1
            return true;
269
        }
270 8
        return false;
271
    }
272
273
    /**
274
     * @param DataList $data
275
     * @param ArrayListInterface $citationItems
276
     * @return array|string
277
     */
278 1
    private function renderGroupedCitations(DataList $data, ArrayListInterface $citationItems)
279
    {
280 1
        $group = [];
281 1
        foreach ($citationItems as $citationItemGroup) {
282 1
            $data_ = $this->filterCitationItems($data, new ArrayList(...$citationItemGroup));
283 1
            CiteProc::getContext()->setCitationData($data_);
284 1
            $group[] = $this->stylesRenderer->renderAffixes(
285 1
                StringHelper::clearApostrophes($this->renderCitations($data_, ""))
286
            );
287
        }
288 1
        if (CiteProc::getContext()->isCitationsAsArray()) {
289 1
            return $group;
290
        }
291
        return implode("\n", $group);
292
    }
293
294 173
    public function setChildren(ArrayListInterface $children)
295
    {
296 173
        $this->children = $children;
0 ignored issues
show
Documentation Bug introduced by
$children is of type Seboettg\Collection\ArrayList\ArrayListInterface, but the property $children 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...
297 173
    }
298
299 173
    public function setStylesRenderer(StylesRenderer $stylesRenderer)
300
    {
301 173
        $this->stylesRenderer = $stylesRenderer;
302 173
    }
303
304 173
    public function setDelimiter(string $delimiter)
305
    {
306 173
        $this->delimiter = $delimiter;
307 173
    }
308
309 173
    public function setParent($parent)
310
    {
311 173
        $this->parent = $parent;
312 173
    }
313
314 173
    public function setSorting(?Sort $sorting): void
315
    {
316 173
        $this->sorting = $sorting;
317 173
    }
318
319 173
    public function setStyleOptions(StyleOptions $styleOptions)
320
    {
321 173
        $this->styleOptions = $styleOptions;
322 173
    }
323
}
324