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
|
|
|
use Seboettg\CiteProc\CiteProc; |
12
|
|
|
use Seboettg\CiteProc\Rendering\HasParent; |
13
|
|
|
use Seboettg\CiteProc\Style\InheritableNameAttributesTrait; |
14
|
|
|
use Seboettg\CiteProc\Style\Options\SubsequentAuthorSubstituteRule; |
15
|
|
|
use Seboettg\CiteProc\Styles\AffixesTrait; |
16
|
|
|
use Seboettg\CiteProc\Styles\DelimiterTrait; |
17
|
|
|
use Seboettg\CiteProc\Styles\FormattingTrait; |
18
|
|
|
use Seboettg\CiteProc\Util\Factory; |
19
|
|
|
use Seboettg\CiteProc\Util\StringHelper; |
20
|
|
|
|
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Class Name |
24
|
|
|
* |
25
|
|
|
* The cs:name element, an optional child element of cs:names, can be used to describe the formatting of individual |
26
|
|
|
* names, and the separation of names within a name variable. |
27
|
|
|
* |
28
|
|
|
* @package Seboettg\CiteProc\Rendering\Name |
29
|
|
|
* |
30
|
|
|
* @author Sebastian Böttger <[email protected]> |
31
|
|
|
*/ |
32
|
|
|
class Name implements HasParent |
33
|
|
|
{ |
34
|
|
|
use InheritableNameAttributesTrait, |
35
|
|
|
FormattingTrait, |
36
|
|
|
AffixesTrait, |
37
|
|
|
DelimiterTrait; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var array |
41
|
|
|
*/ |
42
|
|
|
protected $nameParts; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* Specifies the text string used to separate names in a name variable. Default is ”, ” (e.g. “Doe, Smith”). |
46
|
|
|
* @var |
47
|
|
|
*/ |
48
|
|
|
private $delimiter = ", "; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @var Names |
52
|
|
|
*/ |
53
|
|
|
private $parent; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @var \SimpleXMLElement |
57
|
|
|
*/ |
58
|
|
|
private $node; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* @var string |
62
|
|
|
*/ |
63
|
|
|
private $etAl; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Name constructor. |
67
|
|
|
* @param \SimpleXMLElement $node |
68
|
|
|
* @param Names $parent |
69
|
|
|
*/ |
70
|
|
|
public function __construct(\SimpleXMLElement $node, Names $parent) |
71
|
|
|
{ |
72
|
|
|
$this->node = $node; |
73
|
|
|
$this->parent = $parent; |
74
|
|
|
|
75
|
|
|
$this->nameParts = []; |
76
|
|
|
|
77
|
|
|
/** @var \SimpleXMLElement $child */ |
78
|
|
|
foreach ($node->children() as $child) { |
79
|
|
|
|
80
|
|
|
switch ($child->getName()) { |
81
|
|
|
case "name-part": |
82
|
|
|
/** @var NamePart $namePart */ |
83
|
|
|
$namePart = Factory::create($child, $this); |
84
|
|
|
$this->nameParts[$namePart->getName()] = $namePart; |
85
|
|
|
} |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
foreach ($node->attributes() as $attribute) { |
89
|
|
|
switch ($attribute->getName()) { |
90
|
|
|
case 'form': |
91
|
|
|
$this->form = (string) $attribute; |
92
|
|
|
break; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
$this->initFormattingAttributes($node); |
98
|
|
|
$this->initAffixesAttributes($node); |
99
|
|
|
$this->initDelimiterAttributes($node); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* @param array $data |
104
|
|
|
* @param integer|null $citationNumber |
105
|
|
|
* @return string |
106
|
|
|
*/ |
107
|
|
|
public function render($data, $citationNumber = null) |
108
|
|
|
{ |
109
|
|
|
$text = ""; |
110
|
|
|
if (!$this->attributesInitialized) { |
111
|
|
|
$this->initInheritableNameAttributes($this->node); |
112
|
|
|
} |
113
|
|
|
if ("text" === $this->and) { |
114
|
|
|
$this->and = CiteProc::getContext()->getLocale()->filter('terms', 'and')->single; |
115
|
|
|
} elseif ('symbol' === $this->and) { |
116
|
|
|
$this->and = '&'; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
$resultNames = []; |
120
|
|
|
|
121
|
|
|
$hasPreceding = CiteProc::getContext()->getCitationItems()->hasKey($citationNumber - 1); |
122
|
|
|
$subsequentSubstitution = CiteProc::getContext()->getCitationItems()->getSubsequentAuthorSubstitute(); |
123
|
|
|
$subsequentSubstitutionRule = CiteProc::getContext()->getCitationItems()->getSubsequentAuthorSubstituteRule(); |
124
|
|
|
$useSubseqSubstitution = !is_null($subsequentSubstitution) && !empty($subsequentSubstitutionRule); |
125
|
|
|
$preceding = CiteProc::getContext()->getCitationItems()->get($citationNumber - 1); |
126
|
|
|
|
127
|
|
|
|
128
|
|
|
if ($hasPreceding && $useSubseqSubstitution) { |
129
|
|
|
/** @var \stdClass $preceding */ |
130
|
|
|
$identicalAuthors = $this->identicalAuthors($preceding, $data); |
131
|
|
|
if ($subsequentSubstitutionRule == SubsequentAuthorSubstituteRule::COMPLETE_ALL) { |
132
|
|
|
if ($identicalAuthors) { |
133
|
|
|
return $subsequentSubstitution; |
134
|
|
|
} else { |
135
|
|
|
$resultNames = $this->getFormattedNames($data, $resultNames); |
136
|
|
|
} |
137
|
|
|
} else { |
138
|
|
|
$resultNames = $this->renderSubsequentSubstitution($data, $preceding); |
139
|
|
|
} |
140
|
|
|
} else { |
141
|
|
|
$resultNames = $this->getFormattedNames($data, $resultNames); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
$resultNames = $this->prepareAbbreviation($resultNames); |
145
|
|
|
|
146
|
|
|
if ($this->etAlUseLast) { |
147
|
|
|
/* When set to “true” (the default is “false”), name lists truncated by et-al abbreviation are followed by |
148
|
|
|
the name delimiter, the ellipsis character, and the last name of the original name list. This is only |
149
|
|
|
possible when the original name list has at least two more names than the truncated name list (for this |
150
|
|
|
the value of et-al-use-first/et-al-subsequent-min must be at least 2 less than the value of |
151
|
|
|
et-al-min/et-al-subsequent-use-first). */ |
152
|
|
|
$this->and = "…"; // set "and" |
153
|
|
|
$this->etAl = null; //reset $etAl; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/* add "and" */ |
157
|
|
|
$count = count($resultNames); |
158
|
|
|
if (!empty($this->and) && $count > 1 && empty($this->etAl)) { |
159
|
|
|
$new = $this->and . ' ' . end($resultNames); // add and-prefix of the last name if "and" is defined |
160
|
|
|
$resultNames[count($resultNames) - 1] = $new; //set prefixed last name at the last position of $resultNames array |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
if (!empty($this->and) && empty($this->etAl)) { |
164
|
|
|
switch ($this->delimiterPrecedesLast) { |
165
|
|
|
case 'after-inverted-name': |
166
|
|
|
//TODO: implement |
167
|
|
|
break; |
168
|
|
|
case 'always': |
169
|
|
|
$text = implode($this->delimiter, $resultNames); |
170
|
|
|
break; |
171
|
|
|
case 'never': |
172
|
|
|
if (!$this->etAlUseLast) { |
173
|
|
|
if (count($resultNames) === 1) { |
174
|
|
|
$text = $resultNames[0]; |
175
|
|
View Code Duplication |
} else if (count($resultNames) === 2) { |
|
|
|
|
176
|
|
|
$text = implode(" ", $resultNames); |
177
|
|
|
} else { // >2 |
178
|
|
|
$lastName = array_pop($resultNames); |
179
|
|
|
$text = implode($this->delimiter, $resultNames) . " " . $lastName; |
180
|
|
|
} |
181
|
|
|
} /*else { |
|
|
|
|
182
|
|
|
if (count($resultNames) === 1) { |
183
|
|
|
$text = $resultNames[0]; |
184
|
|
|
} else if (count($resultNames) === 2) { |
185
|
|
|
$text = implode(" ", $resultNames); |
186
|
|
|
} else { // >2 |
187
|
|
|
$lastName = array_pop($resultNames); |
188
|
|
|
$text = implode($this->delimiter, $resultNames) . ", " . $lastName; |
189
|
|
|
} |
190
|
|
|
}*/ |
191
|
|
|
|
192
|
|
|
break; |
193
|
|
|
case 'contextual': |
194
|
|
|
default: |
195
|
|
View Code Duplication |
if (count($resultNames) === 1) { |
|
|
|
|
196
|
|
|
$text = $resultNames[0]; |
197
|
|
|
} else if (count($resultNames) === 2) { |
198
|
|
|
$text = implode(" ", $resultNames); |
199
|
|
|
} else { |
200
|
|
|
$text = implode($this->delimiter, $resultNames); |
201
|
|
|
} |
202
|
|
|
} |
203
|
|
|
} |
204
|
|
|
if (empty($text)) { |
205
|
|
|
$text = implode($this->delimiter, $resultNames); |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
//append et al abbreviation |
209
|
|
|
if (count($data) > 1 && |
210
|
|
|
!empty($resultNames) && |
211
|
|
|
!empty($this->etAl) && |
212
|
|
|
!empty($this->etAlMin) && |
213
|
|
|
!empty($this->etAlUseFirst)) { |
214
|
|
|
|
215
|
|
|
$text = $this->appendEtAl($text, $resultNames); |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/* A third value, “count”, returns the total number of names that would otherwise be rendered by the use of the |
219
|
|
|
cs:names element (taking into account the effects of et-al abbreviation and editor/translator collapsing), |
220
|
|
|
which allows for advanced sorting. */ |
221
|
|
|
|
222
|
|
|
if ($this->form == 'count') { |
223
|
|
|
return (int) count($resultNames); |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
return $text; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* @param \stdClass $name |
231
|
|
|
* @param int $rank |
232
|
|
|
* @return string |
233
|
|
|
*/ |
234
|
|
|
private function formatName($name, $rank) |
235
|
|
|
{ |
236
|
|
|
|
237
|
|
|
$nameObj = $this->cloneNamePOSC($name); |
238
|
|
|
|
239
|
|
|
$useInitials = $this->initialize && !is_null($this->initializeWith) && $this->initializeWith !== false; |
240
|
|
|
if ($useInitials && isset($name->given)) { |
241
|
|
|
$nameObj->given = StringHelper::initializeBySpaceOrHyphen($name->given, $this->initializeWith); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
// format name-parts |
245
|
|
|
if (count($this->nameParts) > 0) { |
246
|
|
|
/** @var NamePart $namePart */ |
247
|
|
|
foreach ($this->nameParts as $namePart) { |
248
|
|
|
$nameObj = $namePart->render($nameObj); |
249
|
|
|
} |
250
|
|
|
} |
251
|
|
|
$ret = $this->getNamesString($nameObj, $rank); |
252
|
|
|
|
253
|
|
|
return trim($ret); |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* @param \stdClass $name |
258
|
|
|
* @param int $rank |
259
|
|
|
* @return string |
260
|
|
|
*/ |
261
|
|
|
private function getNamesString($name, $rank) |
262
|
|
|
{ |
263
|
|
|
$text = ""; |
264
|
|
|
|
265
|
|
|
if (!isset($name->family)) { |
266
|
|
|
return $text; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
$given = !empty($name->given) ? trim($name->given) : ""; |
270
|
|
|
$nonDroppingParticle = isset($name->{'non-dropping-particle'}) ? $name->{'non-dropping-particle'} : ""; |
271
|
|
|
$droppingParticle = isset($name->{'dropping-particle'}) ? $name->{'dropping-particle'} : ""; |
272
|
|
|
$suffix = (isset($name->{'suffix'})) ? $name->{'suffix'} : ""; |
273
|
|
|
|
274
|
|
|
if (isset($name->family)) { |
275
|
|
|
$family = $name->family; |
276
|
|
|
if ($this->form == 'short') { |
277
|
|
|
$text = (!empty($nonDroppingParticle) ? $nonDroppingParticle . " " : "") . $family; |
278
|
|
|
} else { |
279
|
|
|
switch ($this->nameAsSortOrder) { |
280
|
|
|
|
281
|
|
|
case 'all': |
282
|
|
|
case 'first': |
283
|
|
|
if ($this->nameAsSortOrder === "first" && $rank !== 0) { |
284
|
|
|
break; |
285
|
|
|
} |
286
|
|
|
/* |
|
|
|
|
287
|
|
|
use form "[non-dropping particel] family name, |
288
|
|
|
given name [dropping particle], [suffix]" |
289
|
|
|
*/ |
290
|
|
|
$text = !empty($nonDroppingParticle) ? "$nonDroppingParticle " : ""; |
291
|
|
|
$text .= $family; |
292
|
|
|
$text .= !empty($given) ? $this->sortSeparator . $given : ""; |
293
|
|
|
$text .= !empty($droppingParticle) ? " $droppingParticle" : ""; |
294
|
|
|
$text .= !empty($suffix) ? $this->sortSeparator . $suffix : ""; |
295
|
|
|
|
296
|
|
|
//remove last comma when no suffix exist. |
297
|
|
|
$text = trim($text); |
298
|
|
|
$text = substr($text, -1) === $this->sortSeparator ? substr($text, 0, strlen($text) - 1) : $text; |
299
|
|
|
break; |
300
|
|
|
default: |
301
|
|
|
/* |
|
|
|
|
302
|
|
|
use form "given name [dropping particles] [non-dropping particles] family name [suffix]" |
303
|
|
|
e.g. [Jean] [de] [La] [Fontaine] [III] |
304
|
|
|
*/ |
305
|
|
|
$text = sprintf( |
306
|
|
|
"%s %s %s %s %s", |
307
|
|
|
$given, |
308
|
|
|
$droppingParticle, |
309
|
|
|
$nonDroppingParticle, |
310
|
|
|
$family, |
311
|
|
|
$suffix); |
312
|
|
|
|
313
|
|
|
} |
314
|
|
|
} |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
//contains nbsp prefixed by normal space or followed by normal space? |
318
|
|
|
$text = htmlentities($text); |
319
|
|
|
if (strpos($text, " ") !== false || strpos($text, " ") !== false) { |
320
|
|
|
|
321
|
|
|
$text = preg_replace("/[\s]+/", "", $text); //remove normal spaces |
322
|
|
|
return preg_replace("/ +/", " ", $text); |
323
|
|
|
} |
324
|
|
|
$text = html_entity_decode(preg_replace("/[\s]+/", " ", $text)); |
325
|
|
|
return $this->format(trim($text)); |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* @param $name |
330
|
|
|
* @return \stdClass |
331
|
|
|
*/ |
332
|
|
|
private function cloneNamePOSC($name) |
333
|
|
|
{ |
334
|
|
|
$nameObj = new \stdClass(); |
335
|
|
|
if (isset($name->family)) { |
336
|
|
|
$nameObj->family = $name->family; |
337
|
|
|
} |
338
|
|
|
if (isset($name->given)) { |
339
|
|
|
$nameObj->given = $name->given; |
340
|
|
|
} |
341
|
|
|
if (isset($name->{'non-dropping-particle'})) { |
342
|
|
|
$nameObj->{'non-dropping-particle'} = $name->{'non-dropping-particle'}; |
343
|
|
|
} |
344
|
|
|
if (isset($name->{'dropping-particle'})) { |
345
|
|
|
$nameObj->{'dropping-particle'} = $name->{'dropping-particle'}; |
346
|
|
|
} |
347
|
|
|
if (isset($name->{'suffix'})) { |
348
|
|
|
$nameObj->{'suffix'} = $name->{'suffix'}; |
349
|
|
|
} |
350
|
|
|
return $nameObj; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* @param $text |
355
|
|
|
* @param $resultNames |
356
|
|
|
* @return string |
357
|
|
|
*/ |
358
|
|
|
protected function appendEtAl($text, $resultNames) |
359
|
|
|
{ |
360
|
|
|
/* By default, when a name list is truncated to a single name, the name and the “et-al” (or “and others”) |
361
|
|
|
term are separated by a space (e.g. “Doe et al.”). When a name list is truncated to two or more names, the |
362
|
|
|
name delimiter is used (e.g. “Doe, Smith, et al.”). This behavior can be changed with the |
363
|
|
|
delimiter-precedes-et-al attribute. */ |
364
|
|
|
|
365
|
|
|
switch ($this->delimiterPrecedesEtAl) { |
366
|
|
|
case 'never': |
367
|
|
|
$text = $text . " " . $this->etAl; |
368
|
|
|
break; |
369
|
|
|
case 'always': |
370
|
|
|
$text = $text . $this->delimiter . $this->etAl; |
371
|
|
|
break; |
372
|
|
|
case 'contextual': |
373
|
|
|
default: |
374
|
|
|
if (count($resultNames) === 1) { |
375
|
|
|
$text .= " " . $this->etAl; |
376
|
|
|
} else { |
377
|
|
|
$text .= $this->delimiter . $this->etAl; |
378
|
|
|
} |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
return $text; |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
/** |
385
|
|
|
* @param $resultNames |
386
|
|
|
* @return array |
387
|
|
|
*/ |
388
|
|
|
protected function prepareAbbreviation($resultNames) |
389
|
|
|
{ |
390
|
|
|
$cnt = count($resultNames); |
391
|
|
|
/* Use of et-al-min and et-al-user-first enables et-al abbreviation. If the number of names in a name variable |
392
|
|
|
matches or exceeds the number set on et-al-min, the rendered name list is truncated after reaching the number of |
393
|
|
|
names set on et-al-use-first. */ |
394
|
|
|
|
395
|
|
|
if (isset($this->etAlMin) && isset($this->etAlUseFirst)) { |
396
|
|
|
|
397
|
|
|
if ($this->etAlMin <= $cnt) { |
398
|
|
|
if ($this->etAlUseLast && $this->etAlMin - $this->etAlUseFirst >= 2) { |
399
|
|
|
/* et-al-use-last: When set to “true” (the default is “false”), name lists truncated by et-al |
400
|
|
|
abbreviation are followed by the name delimiter, the ellipsis character, and the last name of the |
401
|
|
|
original name list. This is only possible when the original name list has at least two more names |
402
|
|
|
than the truncated name list (for this the value of et-al-use-first/et-al-subsequent-min must be at |
403
|
|
|
least 2 less than the value of et-al-min/et-al-subsequent-use-first).*/ |
404
|
|
|
|
405
|
|
|
$lastName = array_pop($resultNames); //remove last Element and remember in $lastName |
406
|
|
|
|
407
|
|
|
} |
408
|
|
|
for ($i = $this->etAlUseFirst; $i < $cnt; ++$i) { |
409
|
|
|
unset($resultNames[$i]); |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
$resultNames = array_values($resultNames); |
413
|
|
|
|
414
|
|
|
if (!empty($lastName)) { // append $lastName if exist |
415
|
|
|
$resultNames[] = $lastName; |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
if ($this->parent->hasEtAl()) { |
419
|
|
|
$this->etAl = $this->parent->getEtAl()->render(null); |
|
|
|
|
420
|
|
|
return $resultNames; |
421
|
|
|
} else { |
422
|
|
|
$this->etAl = CiteProc::getContext()->getLocale()->filter('terms', 'et-al')->single; |
423
|
|
|
return $resultNames; |
424
|
|
|
} |
425
|
|
|
} |
426
|
|
|
return $resultNames; |
427
|
|
|
} |
428
|
|
|
return $resultNames; |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
/** |
432
|
|
|
* @return Names |
433
|
|
|
*/ |
434
|
|
|
public function getParent() |
435
|
|
|
{ |
436
|
|
|
return $this->parent; |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
/** |
440
|
|
|
* @param $data |
441
|
|
|
* @param \stdClass $preceding |
442
|
|
|
* @return array |
443
|
|
|
*/ |
444
|
|
|
protected function renderSubsequentSubstitution($data, $preceding) |
445
|
|
|
{ |
446
|
|
|
$resultNames = []; |
447
|
|
|
$subsequentSubstitution = CiteProc::getContext()->getCitationItems()->getSubsequentAuthorSubstitute(); |
448
|
|
|
$subsequentSubstitutionRule = CiteProc::getContext()->getCitationItems()->getSubsequentAuthorSubstituteRule(); |
449
|
|
|
|
450
|
|
|
/** |
451
|
|
|
* @var string $type |
452
|
|
|
* @var array $name |
453
|
|
|
*/ |
454
|
|
|
foreach ($data as $rank => $name) { |
455
|
|
|
|
456
|
|
|
switch ($subsequentSubstitutionRule) { |
457
|
|
|
|
458
|
|
|
/* “partial-each” - when one or more rendered names in the name variable match those in the preceding |
459
|
|
|
bibliographic entry, the value of subsequent-author-substitute substitutes for each matching name. |
460
|
|
|
Matching starts with the first name, and continues up to the first mismatch. */ |
461
|
|
View Code Duplication |
case SubsequentAuthorSubstituteRule::PARTIAL_EACH: |
|
|
|
|
462
|
|
|
|
463
|
|
|
if ($this->precedingHasAuthor($preceding, $name)) { |
464
|
|
|
$resultNames[] = $subsequentSubstitution; |
465
|
|
|
} else { |
466
|
|
|
$resultNames[] = $this->formatName($name, $rank); |
467
|
|
|
} |
468
|
|
|
break; |
469
|
|
|
|
470
|
|
|
|
471
|
|
|
/* “partial-first” - as “partial-each”, but substitution is limited to the first name of the name |
472
|
|
|
variable. */ |
473
|
|
|
case SubsequentAuthorSubstituteRule::PARTIAL_FIRST: |
|
|
|
|
474
|
|
|
|
475
|
|
|
if ($rank === 0) { |
476
|
|
|
if ($preceding->author[0]->family === $name->family) { |
477
|
|
|
$resultNames[] = $subsequentSubstitution; |
478
|
|
|
} else { |
479
|
|
|
$resultNames[] = $this->formatName($name, $rank); |
480
|
|
|
} |
481
|
|
|
} else { |
482
|
|
|
$resultNames[] = $this->formatName($name, $rank); |
483
|
|
|
} |
484
|
|
|
break; |
485
|
|
|
|
486
|
|
|
/* “complete-each” - requires a complete match like “complete-all”, but now the value of |
487
|
|
|
subsequent-author-substitute substitutes for each rendered name. */ |
488
|
|
View Code Duplication |
case SubsequentAuthorSubstituteRule::COMPLETE_EACH: |
|
|
|
|
489
|
|
|
if ($this->identicalAuthors($preceding, $data)) { |
490
|
|
|
$resultNames[] = $subsequentSubstitution; |
491
|
|
|
} else { |
492
|
|
|
$resultNames[] = $this->formatName($name, $rank); |
493
|
|
|
} |
494
|
|
|
break; |
495
|
|
|
} |
496
|
|
|
} |
497
|
|
|
return $resultNames; |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
/** |
501
|
|
|
* @param \stdClass $preceding |
502
|
|
|
* @param \stdClass $name |
503
|
|
|
* @return bool |
504
|
|
|
*/ |
505
|
|
|
public function precedingHasAuthor($preceding, $name) |
506
|
|
|
{ |
507
|
|
|
foreach ($preceding->author as $author) { |
508
|
|
|
if ($author->family === $name->family && $author->given === $name->given) { |
509
|
|
|
return true; |
510
|
|
|
} |
511
|
|
|
} |
512
|
|
|
return false; |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
/** |
516
|
|
|
* @param \stdClass $precedingItem |
517
|
|
|
* @param array $currentAuthor |
518
|
|
|
* @return bool |
519
|
|
|
*/ |
520
|
|
|
private function identicalAuthors($precedingItem, $currentAuthor) |
521
|
|
|
{ |
522
|
|
|
if (count($precedingItem->author) !== count($currentAuthor)) { |
523
|
|
|
return false; |
524
|
|
|
} |
525
|
|
|
foreach ($currentAuthor as $current) { |
526
|
|
|
if ($this->precedingHasAuthor($precedingItem, $current)) { |
527
|
|
|
continue; |
528
|
|
|
} |
529
|
|
|
return false; |
530
|
|
|
} |
531
|
|
|
return true; |
532
|
|
|
} |
533
|
|
|
|
534
|
|
|
/** |
535
|
|
|
* @param $data |
536
|
|
|
* @param $resultNames |
537
|
|
|
* @return array |
538
|
|
|
*/ |
539
|
|
|
protected function getFormattedNames($data, $resultNames) |
540
|
|
|
{ |
541
|
|
|
foreach ($data as $rank => $name) { |
542
|
|
|
$resultNames[] = $this->formatName($name, $rank); |
543
|
|
|
} |
544
|
|
|
return $resultNames; |
545
|
|
|
} |
546
|
|
|
|
547
|
|
|
/** |
548
|
|
|
* @return string |
549
|
|
|
*/ |
550
|
|
|
public function getForm() |
551
|
|
|
{ |
552
|
|
|
return $this->form; |
553
|
|
|
} |
554
|
|
|
|
555
|
|
|
/** |
556
|
|
|
* @return string |
557
|
|
|
*/ |
558
|
|
|
public function isNameAsSortOrder() |
559
|
|
|
{ |
560
|
|
|
return $this->nameAsSortOrder; |
561
|
|
|
} |
562
|
|
|
|
563
|
|
|
/** |
564
|
|
|
* @return mixed |
565
|
|
|
*/ |
566
|
|
|
public function getDelimiter() |
567
|
|
|
{ |
568
|
|
|
return $this->delimiter; |
569
|
|
|
} |
570
|
|
|
|
571
|
|
|
/** |
572
|
|
|
* @param mixed $delimiter |
573
|
|
|
*/ |
574
|
|
|
public function setDelimiter($delimiter) |
575
|
|
|
{ |
576
|
|
|
$this->delimiter = $delimiter; |
577
|
|
|
} |
578
|
|
|
|
579
|
|
|
|
580
|
|
|
} |
|
|
|
|
581
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.