Passed
Push — new-api ( f133eb...5677f6 )
by Sebastian
04:24
created

Group::formatting()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6.1308

Importance

Changes 0
Metric Value
cc 6
eloc 12
nc 5
nop 4
dl 0
loc 25
ccs 11
cts 13
cp 0.8462
crap 6.1308
rs 9.2222
c 0
b 0
f 0
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\Rendering;
11
12
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
13
use Seboettg\CiteProc\Rendering\Label\Label;
14
use Seboettg\CiteProc\Rendering\Text\Text;
15
use Seboettg\CiteProc\Styles\ConsecutivePunctuationCharacterTrait;
16
use Seboettg\CiteProc\Styles\StylesRenderer;
17
use Seboettg\CiteProc\Util\Factory;
18
use Seboettg\CiteProc\Util\StringHelper;
19
use Seboettg\Collection\ArrayList;
20
use SimpleXMLElement;
21
22
/**
23
 * Class Group
24
 * The cs:group rendering element must contain one or more rendering elements (with the exception of cs:layout).
25
 * cs:group may carry the delimiter attribute to separate its child elements, as well as affixes and display attributes
26
 * (applied to the output of the group as a whole) and formatting attributes (transmitted to the enclosed elements).
27
 * cs:group implicitly acts as a conditional: cs:group and its child elements are suppressed if a) at least one
28
 * rendering element in cs:group calls a variable (either directly or via a macro), and b) all variables that are
29
 * called are empty. This accommodates descriptive cs:text elements.
30
 *
31
 * @package Seboettg\CiteProc\Rendering
32
 */
33
class Group implements Rendering, HasParent
34
{
35
    use ConsecutivePunctuationCharacterTrait;
36
37
    /**
38
     * @var StylesRenderer
39
     */
40
    private $stylesRenderer;
41
42
    /**
43
     * @var ArrayList
44
     */
45
    private $children;
46
47
    /**
48
     * cs:group may carry the delimiter attribute to separate its child elements
49
     *
50
     * @var
51
     */
52
    private $delimiter = "";
53
54
    private $parent;
55
56
    /**
57
     * @param SimpleXMLElement $node
58
     * @return Group
59
     * @throws InvalidStylesheetException
60
     */
61 94
    public static function factory(SimpleXMLElement $node): Group
62
    {
63 94
        $children = new ArrayList();
64 94
        $group = new self();
65 94
        foreach ($node->children() as $child) {
66 94
            $children->append(Factory::create($child, $group));
67
        }
68 94
        $stylesRenderer = StylesRenderer::factory($node);
69 94
        $group->setChildren($children);
70 94
        $group->setStylesRenderer($stylesRenderer);
71 94
        $group->setDelimiter((string)$node->attributes()['delimiter']);
72 94
        return $group;
73
    }
74
75 94
    public function __construct()
76
    {
77 94
        $this->children = new ArrayList();
78 94
    }
79
80 94
    private function setChildren(ArrayList\ArrayListInterface $children)
81
    {
82 94
        $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...
83 94
    }
84
85 94
    private function setStylesRenderer(StylesRenderer $stylesRenderer)
86
    {
87 94
        $this->stylesRenderer = $stylesRenderer;
88 94
    }
89
90 94
    private function setDelimiter(string $delimiter)
91
    {
92 94
        $this->delimiter = $delimiter;
93 94
    }
94
95
    /**
96
     * @return bool
97
     */
98 26
    public function hasDelimiter(): bool
99
    {
100 26
        return !empty($this->delimiter);
101
    }
102
103
    /**
104
     * @return mixed
105
     */
106 11
    public function getParent()
107
    {
108 11
        return $this->parent;
109
    }
110
111
    /**
112
     * @return string
113
     */
114 80
    public function getDelimiter(): string
115
    {
116 80
        return $this->delimiter;
117
    }
118
119
    /**
120
     * @param  $data
121
     * @param  int|null $citationNumber
122
     * @return string
123
     */
124 88
    public function render($data, $citationNumber = null): string
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...
125
    {
126 88
        $textParts = [];
127 88
        $terms = $variables = $haveVariables = $elementCount = 0;
128 88
        foreach ($this->children as $child) {
129 88
            $elementCount++;
130
131 88
            if (($child instanceof Text) && in_array($child->getSource(), ['term', 'value'])) {
132 31
                ++$terms;
133
            }
134
135 88
            if (($child instanceof Label)) {
136 15
                ++$terms;
137
            }
138 88
            if (method_exists($child, "getSource") && $child->getSource() == 'variable'
139 88
                && !empty($child->getVariable()) && $child->getVariable() != "date"
140 88
                && !empty($data->{$child->getVariable()})
141
            ) {
142 44
                ++$variables;
143
            }
144
145 88
            $text = $child->render($data, $citationNumber);
146 88
            $delimiter = $this->delimiter;
147 88
            if (!empty($text)) {
148 87
                if ($delimiter && ($elementCount < count($this->children))) {
149
                    //check to see if the delimiter is already the last character of the text string
150
                    //if so, remove it so we don't have two of them when the group will be merged
151 79
                    $stext = strip_tags(trim($text));
152 79
                    if ((strrpos($stext, $delimiter[0]) + 1) == strlen($stext) && strlen($stext) > 1) {
153 13
                        $text = str_replace($stext, '----REPLACE----', $text);
154 13
                        $stext = substr($stext, 0, -1);
155 13
                        $text = str_replace('----REPLACE----', $stext, $text);
156
                    }
157
                }
158 87
                $textParts[] = $text;
159
160 87
                if (method_exists($child, "getSource") && $child->getSource() == 'variable' ||
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (method_exists($child, '...($child->getVariable()), Probably Intended Meaning: method_exists($child, 'g...$child->getVariable()))
Loading history...
161
                    (
162 85
                        method_exists($child, "getVariable") &&
163 87
                        $child->getVariable() !== "date" && !empty($child->getVariable())
164
                    )
165
                ) {
166 76
                    $haveVariables++;
167
                }
168
169 87
                if (method_exists($child, "getSource") && $child->getSource() == 'macro') {
170 88
                    $haveVariables++;
171
                }
172
            }
173
        }
174 88
        return $this->formatting($textParts, $variables, $haveVariables, $terms);
175
    }
176
177
    /**
178
     * @param  $textParts
179
     * @param  $variables
180
     * @param  $haveVariables
181
     * @param  $terms
182
     * @return string
183
     */
184 88
    protected function formatting($textParts, $variables, $haveVariables, $terms): string
185
    {
186 88
        if (empty($textParts)) {
187 26
            return "";
188
        }
189
190 87
        if ($variables && !$haveVariables) {
191
            return ""; // there has to be at least one other none empty value before the term is output
192
        }
193
194 87
        if (count($textParts) == $terms) {
195 19
            return ""; // there has to be at least one other none empty value before the term is output
196
        }
197
198 85
        $text = StringHelper::implodeAndPreventConsecutiveChars($this->delimiter, $textParts);
199
200 85
        if (!empty($text)) {
201 85
            return $this->stylesRenderer->renderDisplay(
202 85
                $this->stylesRenderer->renderAffixes(
203 85
                    $this->stylesRenderer->renderFormatting($text)
204
                )
205
            );
206
        }
207
208
        return "";
209
    }
210
}
211