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

Layout::renderSingle()   B

Complexity

Conditions 10
Paths 12

Size

Total Lines 37
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 10.0045

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 29
nc 12
nop 2
dl 0
loc 37
ccs 27
cts 28
cp 0.9643
crap 10.0045
rs 7.6666
c 1
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
            $clone = clone $this->citationItems->map(function ($element) {
119 4
                return (is_array($element) ? array_clone($element) : (is_object($element) ? clone $element : $element));
120 50
            });
121 50
            $this->sorting->sort($data);
122 50
            $this->setCitationItems($clone);
123 50
            $this->setState(RenderingState::RENDERING());
124
        }
125
126 169
        if ($this->mode->equals(RenderingMode::BIBLIOGRAPHY())) {
127 80
            foreach ($data as $citationNumber => $item) {
128 80
                ++self::$numberOfCitedItems;
129 80
                CiteProc::getContext()->getResults()->append(
130 80
                    $this->wrapBibEntry($item, $this->renderSingle($item, $citationNumber))
131
                );
132
            }
133 80
            $ret .= implode($this->delimiter, CiteProc::getContext()->getResults()->toArray());
134 80
            $ret = StringHelper::clearApostrophes($ret);
135 80
            return sprintf("<div class=\"csl-bib-body\">%s\n</div>", $ret);
136 103
        } elseif ($this->mode->equals(RenderingMode::CITATION())) {
137 103
            if ($this->citationItems->count() > 0) { //is there a filter for specific citations?
138 9
                if ($this->isGroupedCitations($this->citationItems)) { //if citation items grouped?
139 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

139
                    return $this->renderGroupedCitations(/** @scrutinizer ignore-type */ $data, $this->citationItems);
Loading history...
140
                } else {
141 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

141
                    $data = $this->filterCitationItems(/** @scrutinizer ignore-type */ $data, $this->citationItems);
Loading history...
142 8
                    $ret = $this->renderCitations($data, $ret);
143
                }
144
            } else {
145 94
                $ret = $this->renderCitations($data, $ret);
146
            }
147
        }
148 102
        $ret = StringHelper::clearApostrophes($ret);
149 102
        return $this->stylesRenderer->renderAffixes($ret);
150
    }
151
152
    /**
153
     * @param  $data
154
     * @param  int|null $citationNumber
155
     * @return string
156
     */
157 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...
158
    {
159 169
        $inMargin = [];
160 169
        $margin = [];
161 169
        foreach ($this->children as $key => $child) {
162 169
            $rendered = $child->render($data, $citationNumber);
163 169
            $this->getChildrenAffixesAndDelimiter($child);
164 169
            if ($this->mode->equals(RenderingMode::BIBLIOGRAPHY())
165 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

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