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

Names::filterEmpty()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 1
eloc 2
c 2
b 1
f 0
nc 1
nop 1
dl 0
loc 4
ccs 2
cts 2
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\Rendering\Name;
11
12
use Seboettg\CiteProc\CiteProc;
13
use Seboettg\CiteProc\Config\RenderingMode;
14
use Seboettg\CiteProc\Exception\CiteProcException;
15
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
16
use Seboettg\CiteProc\Rendering\HasParent;
17
use Seboettg\CiteProc\Rendering\Label\Label;
18
use Seboettg\CiteProc\Rendering\Observer\RenderingObserver;
19
use Seboettg\CiteProc\Rendering\Observer\RenderingObserverTrait;
20
use Seboettg\CiteProc\Rendering\Rendering;
21
use Seboettg\CiteProc\Config\RenderingState;
22
use Seboettg\CiteProc\Style\Options\NameOptions;
23
use Seboettg\CiteProc\Styles\StylesRenderer;
24
use Seboettg\CiteProc\Util\Factory;
25
use Seboettg\CiteProc\Util\NameHelper;
26
use Seboettg\Collection\ArrayList as ArrayList;
27
use SimpleXMLElement;
28
use stdClass;
29
30
/**
31
 * Class Names
32
 *
33
 * @package Seboettg\CiteProc\Rendering\Name
34
 *
35
 * @author Sebastian Böttger <[email protected]>
36
 */
37
class Names implements Rendering, HasParent, RenderingObserver
38
{
39
    use RenderingObserverTrait;
40
41
    /**
42
     * Variables (selected with the required variable attribute), each of which can contain multiple names (e.g. the
43
     * “author” variable contains all the author names of the cited item). If multiple variables are selected
44
     * (separated by single spaces, see example below), each variable is independently rendered in the order specified.
45
     *
46
     * @var ArrayList
47
     */
48
    private $variables;
49
50
    /**
51
     * The Name element, an optional child element of Names, can be used to describe the formatting of individual
52
     * names, and the separation of names within a name variable.
53
     *
54
     * @var Name
55
     */
56
    private $name;
57
58
    /**
59
     * The optional Label element must be included after the Name and EtAl elements, but before
60
     * the Substitute element. When used as a child element of Names, Label does not carry the variable
61
     * attribute; it uses the variable(s) set on the parent Names element instead.
62
     *
63
     * @var Label
64
     */
65
    private $label;
66
67
    /**
68
     * The optional Substitute element, which must be included as the last child element of Names, adds
69
     * substitution in case the name variables specified in the parent cs:names element are empty. The substitutions
70
     * are specified as child elements of Substitute, and must consist of one or more rendering elements (with the
71
     * exception of Layout). A shorthand version of Names without child elements, which inherits the attributes
72
     * values set on the cs:name and EtAl child elements of the original Names element, may also be used. If
73
     * Substitute contains multiple child elements, the first element to return a non-empty result is used for
74
     * substitution. Substituted variables are suppressed in the rest of the output to prevent duplication. An example,
75
     * where an empty “author” name variable is substituted by the “editor” name variable, or, when no editors exist,
76
     * by the “title” macro:
77
     *
78
     * <macro name="author">
79
     *     <names variable="author">
80
     *         <substitute>
81
     *             <names variable="editor"/>
82
     *             <text macro="title"/>
83
     *         </substitute>
84
     *     </names>
85
     * </macro>
86
     *
87
     * @var Substitute
88
     */
89
    private $substitute;
90
91
    /**
92
     * Et-al abbreviation, controlled via the et-al-... attributes (see Name), can be further customized with the
93
     * optional cs:et-al element, which must follow the cs:name element (if present). The term attribute may be set to
94
     * either “et-al” (the default) or to “and others” to use either term. The formatting attributes may also be used,
95
     * for example to italicize the “et-al” term:
96
     *
97
     * @var EtAl
98
     */
99
    private $etAl;
100
101
    /**
102
     * The delimiter attribute may be set on cs:names to separate the names of the different name variables (e.g. the
103
     * semicolon in “Doe, Smith (editors); Johnson (translator)”).
104
     *
105
     * @var string
106
     */
107
    private $delimiter = ", ";
108
109
    private $parent;
110
111
    /** @var StylesRenderer */
112
    protected $stylesRenderer;
113
114
    /** @var NameOptions[] */
115
    private $nameOptions;
116
117
    /**
118
     * @param SimpleXMLElement $node
119
     * @return Names
120
     * @throws InvalidStylesheetException
121
     */
122 118
    public static function factory(SimpleXMLElement $node): Names
123
    {
124 118
        $nameOptions[RenderingMode::CITATION] = NameOptions::updateNameOptions($node, RenderingMode::CITATION());
0 ignored issues
show
Comprehensibility Best Practice introduced by
$nameOptions was never initialized. Although not strictly required by PHP, it is generally a good practice to add $nameOptions = array(); before regardless.
Loading history...
125 118
        $nameOptions[RenderingMode::BIBLIOGRAPHY] = NameOptions::updateNameOptions(
126 118
            $node,
127 118
            RenderingMode::BIBLIOGRAPHY()
128
        );
129 118
        $stylesRenderer = StylesRenderer::factory($node);
130 118
        $delimiter = (string) ($node->attributes()['delimiter'] ?? ', ');
131 118
        $names = new self($stylesRenderer, $nameOptions, $delimiter);
132 118
        foreach ($node->children() as $child) {
133 118
            switch ($child->getName()) {
134 118
                case "name":
135
                    /** @var Name $name */
136 118
                    $name = Factory::create($child, $names);
137 118
                    $names->setName($name);
138 118
                    break;
139 62
                case "label":
140 55
                    $label = Factory::create($child);
141 55
                    $names->setLabel($label);
142 55
                    break;
143 57
                case "substitute":
144 55
                    $substitute = Substitute::factory($child, $names);
145 55
                    $names->setSubstitute($substitute);
146 55
                    break;
147 14
                case "et-al":
148 14
                    $etAl = Factory::create($child);
149 118
                    $names->setEtAl($etAl);
150
            }
151
        }
152
153 118
        $variables = new ArrayList(...explode(" ", (string)$node['variable']));
154 118
        $names->setVariables($variables);
155 118
        CiteProc::getContext()->addObserver($names);
156 118
        return $names;
157
    }
158
159
    /**
160
     * Names constructor.
161
     * @param StylesRenderer $stylesRenderer
162
     * @param NameOptions[]
163
     * @param string $delimiter
164
     */
165 118
    public function __construct(StylesRenderer $stylesRenderer, array $nameOptions, string $delimiter)
166
    {
167 118
        $this->stylesRenderer = $stylesRenderer;
168 118
        $this->nameOptions = $nameOptions;
169 118
        $this->delimiter = $delimiter;
170 118
        $this->variables = new ArrayList();
171 118
        $this->initObserver();
172 118
    }
173
174
    /**
175
     * This outputs the contents of one or more name variables (selected with the required variable attribute), each
176
     * of which can contain multiple names (e.g. the “author” variable contains all the author names of the cited item).
177
     * If multiple variables are selected (separated by single spaces), each variable is independently rendered in the
178
     * order specified, with one exception: when the selection consists of “editor” and “translator”, and when the
179
     * contents of these two name variables is identical, then the contents of only one name variable is rendered. In
180
     * addition, the “editortranslator” term is used if the Names element contains a Label element, replacing the
181
     * default “editor” and “translator” terms (e.g. resulting in “Doe (editor & translator)”).
182
     *
183
     * @param  stdClass $data
184
     * @param  int|null $citationNumber
185
     * @return string
186
     * @throws CiteProcException
187
     */
188 112
    public function render($data, $citationNumber = null)
189
    {
190 112
        $str = "";
191
192
        /* when the selection consists of “editor” and “translator”, and when the contents of these two name variables
193
        is identical, then the contents of only one name variable is rendered. In addition, the “editortranslator”
194
        term is used if the cs:names element contains a cs:label element, replacing the default “editor” and
195
        “translator” terms (e.g. resulting in “Doe (editor & translator)”) */
196 112
        if ($this->variables->hasElement("editor") && $this->variables->hasElement("translator")) {
197 15
            if (isset($data->editor)
198 15
                && isset($data->translator) && NameHelper::sameNames($data->editor, $data->translator)
199
            ) {
200 1
                if (isset($this->name)) {
201 1
                    $str .= $this->name->render($data, 'editor');
202
                } else {
203
                    $arr = [];
204
                    foreach ($data->editor as $editor) {
205
                        $edt = $this->stylesRenderer->renderFormatting(
206
                            sprintf("%s, %s", $editor->family, $editor->given)
207
                        );
208
                        $results[] = NameHelper::addExtendedMarkup('editor', $editor, $edt);
209
                    }
210
                    $str .= implode($this->delimiter, $arr);
211
                }
212 1
                if (isset($this->label)) {
213 1
                    $this->label->setVariable("editortranslator");
214 1
                    $str .= $this->label->render($data);
215
                }
216
                $this->variables = $this->variables->filter(function ($value) {
217 1
                    return $value !== "editor" && $value !== "translator";
218 1
                });
219
            }
220
        }
221
222 112
        $results = [];
223 112
        foreach ($this->variables as $var) {
224 111
            if (!empty($data->{$var})) {
225 105
                if (!empty($this->name)) {
226 104
                    $res = $this->name->render($data, $var, $citationNumber);
227 104
                    $name = $res;
228 104
                    if (!empty($this->label)) {
229 32
                        $name = $this->appendLabel($data, $var, $name);
230
                    }
231
                    //add multiple counting values
232 104
                    if (is_numeric($name) && $this->name->getForm() === "count") {
233 5
                        $results = $this->addCountValues($res, $results);
234
                    } else {
235 104
                        $results[] = $this->stylesRenderer->renderFormatting($name);
236
                    }
237
                } else {
238 1
                    foreach ($data->{$var} as $name) {
239 1
                        $formatted = $this->stylesRenderer->renderFormatting(
240 1
                            sprintf("%s %s", $name->given, $name->family)
241
                        );
242 1
                        $results[] = NameHelper::addExtendedMarkup($var, $name, $formatted);
243
                    }
244
                }
245
                // suppress substituted variables
246 105
                if ($this->state->equals(RenderingState::SUBSTITUTION())) {
247 105
                    unset($data->{$var});
248
                }
249
            } else {
250 36
                if (!empty($this->substitute)) {
251 111
                    $results[] = $this->substitute->render($data);
252
                }
253
            }
254
        }
255 112
        $results = array_filter($results);
256 112
        $str .= implode($this->delimiter, $results);
257 112
        return !empty($str) ? $this->stylesRenderer->renderAffixes($str) : "";
258
    }
259
260
261
    /**
262
     * @param  $data
263
     * @param  $var
264
     * @param  $name
265
     * @return string
266
     */
267 32
    private function appendLabel($data, $var, $name)
268
    {
269 32
        $this->label->setVariable($var);
270 32
        if (in_array($this->label->getForm(), ["verb", "verb-short"])) {
271 1
            $name = $this->label->render($data) . $name;
272
        } else {
273 32
            $name .= $this->label->render($data);
274
        }
275 32
        return $name;
276
    }
277
278
    /**
279
     * @param  $res
280
     * @param  $results
281
     * @return array
282
     */
283 5
    private function addCountValues($res, $results)
284
    {
285 5
        $lastElement = current($results);
286 5
        $key = key($results);
287 5
        if (!empty($lastElement)) {
288 3
            $lastElement += $res;
289 3
            $results[$key] = $lastElement;
290
        } else {
291 5
            $results[] = $res;
292
        }
293 5
        return $results;
294
    }
295
296 69
    public function hasEtAl(): bool
297
    {
298 69
        return !empty($this->etAl);
299
    }
300
301 13
    public function getEtAl(): ?EtAl
302
    {
303 13
        return $this->etAl;
304
    }
305
306 14
    public function setEtAl(?EtAl $etAl): void
307
    {
308 14
        $this->etAl = $etAl;
309 14
    }
310
311 105
    public function getVariables(): ArrayList\ArrayListInterface
312
    {
313 105
        return $this->variables;
314
    }
315
316 118
    public function setVariables(ArrayList\ArrayListInterface $variables): void
317
    {
318 118
        $this->variables = $variables;
0 ignored issues
show
Documentation Bug introduced by
$variables is of type Seboettg\Collection\ArrayList\ArrayListInterface, but the property $variables 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...
319 118
    }
320
321 54
    public function hasLabel(): bool
322
    {
323 54
        return !empty($this->label);
324
    }
325
326 45
    public function getLabel(): ?Label
327
    {
328 45
        return $this->label;
329
    }
330
331 55
    public function setLabel(?Label $label)
332
    {
333 55
        $this->label = $label;
334 55
    }
335
336 54
    public function hasName(): bool
337
    {
338 54
        return !empty($this->name);
339
    }
340
341 54
    public function getName(): ?Name
342
    {
343 54
        return $this->name;
344
    }
345
346 118
    public function setName(Name $name): void
347
    {
348 118
        $this->name = $name;
349 118
    }
350
351 55
    public function setParent($parent): void
352
    {
353 55
        $this->parent = $parent;
354 55
    }
355
356
    public function getParent()
357
    {
358
        return $this->parent;
359
    }
360
361 55
    private function setSubstitute(Substitute $substitute): void
362
    {
363 55
        $this->substitute = $substitute;
364 55
    }
365
366
    /**
367
     * @param RenderingMode $mode
368
     * @return NameOptions
369
     */
370 118
    public function getNameOptions(RenderingMode $mode): NameOptions
371
    {
372 118
        return $this->nameOptions[(string)$mode];
373
    }
374
}
375