Names   B
last analyzed

Complexity

Total Complexity 51

Size/Duplication

Total Lines 382
Duplicated Lines 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 123
dl 0
loc 382
rs 7.92
c 2
b 1
f 0
wmc 51

19 Methods

Rating   Name   Duplication   Size   Complexity  
A setEtAl() 0 4 1
A setRenderLabelBeforeName() 0 3 1
A appendLabel() 0 21 5
A isRenderLabelBeforeName() 0 3 1
A filterEmpty() 0 4 1
A getEtAl() 0 3 1
A hasEtAl() 0 3 1
A addCountValues() 0 11 2
A getVariables() 0 3 1
A hasLabel() 0 3 1
A setLabel() 0 3 1
A getDelimiter() 0 3 1
A getLabel() 0 3 1
A setName() 0 4 1
A hasName() 0 3 1
B __construct() 0 40 9
A getParent() 0 3 1
A getName() 0 3 1
F render() 0 68 20

How to fix   Complexity   

Complex Class

Complex classes like Names 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 Names, and based on these observations, apply Extract Interface, too.

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\Exception\CiteProcException;
14
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
15
use Seboettg\CiteProc\Rendering\HasParent;
16
use Seboettg\CiteProc\Rendering\Label;
17
use Seboettg\CiteProc\Rendering\Rendering;
18
use Seboettg\CiteProc\Rendering\Term\Punctuation;
19
use Seboettg\CiteProc\RenderingState;
20
use Seboettg\CiteProc\Style\InheritableNameAttributesTrait;
21
use Seboettg\CiteProc\Styles\AffixesTrait;
22
use Seboettg\CiteProc\Styles\DelimiterTrait;
23
use Seboettg\CiteProc\Styles\FormattingTrait;
24
use Seboettg\CiteProc\Util\Factory;
25
use Seboettg\CiteProc\Util\NameHelper;
26
use Seboettg\Collection\ArrayList;
27
use Seboettg\Collection\Lists\ListInterface;
28
use Seboettg\Collection\Map\MapInterface;
29
use SimpleXMLElement;
30
use stdClass;
31
use function Seboettg\Collection\Lists\listOf;
32
use function Seboettg\Collection\Map\mapOf;
33
34
/**
35
 * Class Names
36
 *
37
 * @package Seboettg\CiteProc\Rendering\Name
38
 *
39
 * @author Sebastian Böttger <[email protected]>
40
 */
41
class Names implements Rendering, HasParent
42
{
43
    use DelimiterTrait,
0 ignored issues
show
Bug introduced by
The trait Seboettg\CiteProc\Styles\AffixesTrait requires the property $single which is not provided by Seboettg\CiteProc\Rendering\Name\Names.
Loading history...
44
        AffixesTrait,
45
        FormattingTrait,
46
        InheritableNameAttributesTrait;
47
48
    /**
49
     * Variables (selected with the required variable attribute), each of which can contain multiple names (e.g. the
50
     * “author” variable contains all the author names of the cited item). If multiple variables are selected
51
     * (separated by single spaces, see example below), each variable is independently rendered in the order specified.
52
     */
53
    private ListInterface $variables;
54
55
    /**
56
     * The Name element, an optional child element of Names, can be used to describe the formatting of individual
57
     * names, and the separation of names within a name variable.
58
     *
59
     * @var Name
60
     */
61
    private $name;
62
63
    /**
64
     * The optional Label element must be included after the Name and EtAl elements, but before
65
     * the Substitute element. When used as a child element of Names, Label does not carry the variable
66
     * attribute; it uses the variable(s) set on the parent Names element instead.
67
     *
68
     * @var Label
69
     */
70
    private $label;
71
72
    /**
73
     * The optional Substitute element, which must be included as the last child element of Names, adds
74
     * substitution in case the name variables specified in the parent cs:names element are empty. The substitutions
75
     * are specified as child elements of Substitute, and must consist of one or more rendering elements (with the
76
     * exception of Layout). A shorthand version of Names without child elements, which inherits the attributes
77
     * values set on the cs:name and EtAl child elements of the original Names element, may also be used. If
78
     * Substitute contains multiple child elements, the first element to return a non-empty result is used for
79
     * substitution. Substituted variables are suppressed in the rest of the output to prevent duplication. An example,
80
     * where an empty “author” name variable is substituted by the “editor” name variable, or, when no editors exist,
81
     * by the “title” macro:
82
     *
83
     * <macro name="author">
84
     *     <names variable="author">
85
     *         <substitute>
86
     *             <names variable="editor"/>
87
     *             <text macro="title"/>
88
     *         </substitute>
89
     *     </names>
90
     * </macro>
91
     *
92
     * @var Substitute
93
     */
94
    private $substitute;
95
96
    /**
97
     * Et-al abbreviation, controlled via the et-al-... attributes (see Name), can be further customized with the
98
     * optional cs:et-al element, which must follow the cs:name element (if present). The term attribute may be set to
99
     * either “et-al” (the default) or to “and others” to use either term. The formatting attributes may also be used,
100
     * for example to italicize the “et-al” term:
101
     *
102
     * @var EtAl
103
     */
104
    private $etAl;
105
106
    /**
107
     * The delimiter attribute may be set on cs:names to separate the names of the different name variables (e.g. the
108
     * semicolon in “Doe, Smith (editors); Johnson (translator)”).
109
     *
110
     * @var string
111
     */
112
    private $delimiter = ", ";
113
114
    private $parent;
115
116
    /**
117
     * @var bool
118
     */
119
    private $renderLabelBeforeName = false;
120
121
    /**
122
     * Names constructor.
123
     *
124
     * @param  SimpleXMLElement $node
125
     * @param  $parent
126
     * @throws InvalidStylesheetException
127
     */
128
    public function __construct(SimpleXMLElement $node, $parent)
129
    {
130
        $this->initInheritableNameAttributes($node);
131
        $this->parent = $parent;
132
        /**
133
         * @var SimpleXMLElement $child
134
         */
135
136
        foreach ($node->children() as $child) {
137
            switch ($child->getName()) {
138
                case "name":
139
                    $this->name = Factory::create($child, $this);
140
                    if ($this->label !== null) {
141
                        $this->renderLabelBeforeName = true;
142
                    }
143
                    break;
144
                case "label":
145
                    $this->label = Factory::create($child);
146
                    break;
147
                case "substitute":
148
                    $this->substitute = new Substitute($child, $this);
0 ignored issues
show
Bug introduced by
It seems like $child can also be of type null; however, parameter $node of Seboettg\CiteProc\Render...bstitute::__construct() does only seem to accept SimpleXMLElement, 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

148
                    $this->substitute = new Substitute(/** @scrutinizer ignore-type */ $child, $this);
Loading history...
149
                    break;
150
                case "et-al":
151
                    $this->etAl = Factory::create($child);
152
            }
153
        }
154
155
        /**
156
         * @var SimpleXMLElement $attribute
157
         */
158
        foreach ($node->attributes() as $attribute) {
159
            if ("variable" === $attribute->getName()) {
160
                $this->variables = listOf(...explode(" ", (string) $attribute));
161
                break;
162
            }
163
        }
164
165
        $this->initDelimiterAttributes($node);
166
        $this->initAffixesAttributes($node);
167
        $this->initFormattingAttributes($node);
168
    }
169
170
    /**
171
     * This outputs the contents of one or more name variables (selected with the required variable attribute), each
172
     * of which can contain multiple names (e.g. the “author” variable contains all the author names of the cited item).
173
     * If multiple variables are selected (separated by single spaces), each variable is independently rendered in the
174
     * order specified, with one exception: when the selection consists of “editor” and “translator”, and when the
175
     * contents of these two name variables is identical, then the contents of only one name variable is rendered. In
176
     * addition, the “editortranslator” term is used if the Names element contains a Label element, replacing the
177
     * default “editor” and “translator” terms (e.g. resulting in “Doe (editor & translator)”).
178
     *
179
     * @param  stdClass $data
180
     * @param  int|null $citationNumber
181
     * @return string
182
     * @throws CiteProcException
183
     */
184
    public function render($data, $citationNumber = null)
185
    {
186
        $str = "";
187
188
        /* when the selection consists of “editor” and “translator”, and when the contents of these two name variables
189
        is identical, then the contents of only one name variable is rendered. In addition, the “editortranslator”
190
        term is used if the cs:names element contains a cs:label element, replacing the default “editor” and
191
        “translator” terms (e.g. resulting in “Doe (editor & translator)”) */
192
        if ($this->variables->contains("editor") && $this->variables->contains("translator")) {
193
            if (isset($data->editor)
194
                && isset($data->translator) && NameHelper::sameNames($data->editor, $data->translator)
195
            ) {
196
                if (isset($this->name)) {
197
                    $str .= $this->name->render($data, 'editor');
198
                } else {
199
                    $arr = [];
200
                    foreach ($data->editor as $editor) {
201
                        $edt = $this->format($editor->family.", ".$editor->given);
202
                        $results[] = NameHelper::addExtendedMarkup('editor', $editor, $edt);
203
                    }
204
                    $str .= implode($this->delimiter, $arr);
205
                }
206
                if (isset($this->label)) {
207
                    $this->label->setVariable("editortranslator");
208
                    $str .= $this->label->render($data);
209
                }
210
                $vars = $this->variables->toArray();
211
                $vars = array_filter($vars, function ($value) {
212
                    return !($value === "editor" || $value === "translator");
213
                });
214
                $this->variables->setArray($vars);
215
            }
216
        }
217
218
        $results = [];
219
        foreach ($this->variables as $var) {
220
            if (!empty($data->{$var})) {
221
                if (!empty($this->name)) {
222
                    $res = $this->name->render($data, $var, $citationNumber);
223
                    $name = $res;
224
                    if (!empty($this->label)) {
225
                        $name = $this->appendLabel($data, $var, $name);
226
                    }
227
                    //add multiple counting values
228
                    if (is_numeric($name) && $this->name->getForm() === "count") {
229
                        $results = $this->addCountValues($res, $results);
230
                    } else {
231
                        $results[] = $this->format($name);
232
                    }
233
                } else {
234
                    foreach ($data->{$var} as $name) {
235
                        $formatted = $this->format($name->given." ".$name->family);
236
                        $results[] = NameHelper::addExtendedMarkup($var, $name, $formatted);
237
                    }
238
                }
239
                // suppress substituted variables
240
                if (CiteProc::getContext()->getRenderingState()->getValue() === RenderingState::SUBSTITUTION) {
241
                    unset($data->{$var});
242
                }
243
            } else {
244
                if (!empty($this->substitute)) {
245
                    $results[] = $this->substitute->render($data);
246
                }
247
            }
248
        }
249
        $results = $this->filterEmpty($results);
250
        $str .= implode($this->delimiter, $results);
251
        return !empty($str) ? $this->addAffixes($str) : "";
252
    }
253
254
255
    /**
256
     * @param  $data
257
     * @param  $var
258
     * @param  $name
259
     * @return string
260
     */
261
    private function appendLabel($data, $var, $name): string
262
    {
263
        $this->label->setVariable($var);
264
        $renderedLabel = trim($this->label->render($data));
265
        if (empty($renderedLabel)) {
266
            return $name;
267
        }
268
        if ($this->renderLabelBeforeName) {
269
            $delimiter = !in_array(
270
                trim($this->label->renderSuffix()),
271
                Punctuation::getAllPunctuations()
272
            ) ? " " : "";
273
            $result = $renderedLabel . $delimiter . trim($name);
274
        } else {
275
            $delimiter = !in_array(
276
                trim($this->label->renderPrefix()),
277
                Punctuation::getAllPunctuations()
278
            ) ? " " : "";
279
            $result = trim($name) . $delimiter . $renderedLabel;
280
        }
281
        return $result;
282
    }
283
284
    /**
285
     * @param  $res
286
     * @param  $results
287
     * @return array
288
     */
289
    private function addCountValues($res, $results)
290
    {
291
        $lastElement = current($results);
292
        $key = key($results);
293
        if (!empty($lastElement)) {
294
            $lastElement += $res;
295
            $results[$key] = $lastElement;
296
        } else {
297
            $results[] = $res;
298
        }
299
        return $results;
300
    }
301
302
    /**
303
     * @return bool
304
     */
305
    public function hasEtAl()
306
    {
307
        return !empty($this->etAl);
308
    }
309
310
    /**
311
     * @return EtAl
312
     */
313
    public function getEtAl()
314
    {
315
        return $this->etAl;
316
    }
317
318
    /**
319
     * @param  EtAl $etAl
320
     * @return $this
321
     */
322
    public function setEtAl(EtAl $etAl)
323
    {
324
        $this->etAl = $etAl;
325
        return $this;
326
    }
327
328
    /**
329
     * @return bool
330
     */
331
    public function hasName()
332
    {
333
        return !empty($this->name);
334
    }
335
336
    /**
337
     * @return Name
338
     */
339
    public function getName()
340
    {
341
        return $this->name;
342
    }
343
344
    /**
345
     * @param  Name $name
346
     * @return $this
347
     */
348
    public function setName(Name $name)
349
    {
350
        $this->name = $name;
351
        return $this;
352
    }
353
354
    /**
355
     * @return string
356
     */
357
    public function getDelimiter()
358
    {
359
        return $this->delimiter;
360
    }
361
362
    /**
363
     * @return ArrayList
364
     */
365
    public function getVariables()
366
    {
367
        return $this->variables;
368
    }
369
370
    /**
371
     * @return bool
372
     */
373
    public function hasLabel()
374
    {
375
        return !empty($this->label);
376
    }
377
378
    /**
379
     * @return Label
380
     */
381
    public function getLabel()
382
    {
383
        return $this->label;
384
    }
385
386
    /**
387
     * @param Label $label
388
     */
389
    public function setLabel($label)
390
    {
391
        $this->label = $label;
392
    }
393
394
    /**
395
     * @return mixed
396
     */
397
    public function getParent()
398
    {
399
        return $this->parent;
400
    }
401
402
    private function filterEmpty(array $results)
403
    {
404
        return array_filter($results, function ($item) {
405
            return !empty($item);
406
        });
407
    }
408
409
    /**
410
     * @return bool
411
     */
412
    public function isRenderLabelBeforeName(): bool
413
    {
414
        return $this->renderLabelBeforeName;
415
    }
416
417
    /**
418
     * @param bool $renderLabelBeforeName
419
     */
420
    public function setRenderLabelBeforeName(bool $renderLabelBeforeName): void
421
    {
422
        $this->renderLabelBeforeName = $renderLabelBeforeName;
423
    }
424
}
425