Open Journal Systems  3.3.0
Name.php
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 
11 
20 use Seboettg\CiteProc\Styles\DelimiterTrait;
26 use SimpleXMLElement;
27 use stdClass;
28 
39 class Name implements HasParent
40 {
41  use InheritableNameAttributesTrait,
42  FormattingTrait,
43  AffixesTrait,
44  DelimiterTrait;
45 
49  protected $nameParts;
50 
56  private $delimiter = ", ";
57 
61  private $parent;
62 
66  private $node;
67 
71  private $etAl;
72 
76  private $variable;
77 
85  public function __construct(SimpleXMLElement $node, Names $parent)
86  {
87  $this->node = $node;
88  $this->parent = $parent;
89 
90  $this->nameParts = [];
91 
95  foreach ($node->children() as $child) {
96  switch ($child->getName()) {
97  case "name-part":
99  $namePart = Factory::create($child, $this);
100  $this->nameParts[$namePart->getName()] = $namePart;
101  }
102  }
103 
104  foreach ($node->attributes() as $attribute) {
105  switch ($attribute->getName()) {
106  case 'form':
107  $this->form = (string) $attribute;
108  break;
109  }
110  }
111 
112  $this->initFormattingAttributes($node);
113  $this->initAffixesAttributes($node);
114  $this->initDelimiterAttributes($node);
115  }
116 
124  public function render($data, $var, $citationNumber = null)
125  {
126  $this->variable = $var;
127  $name = $data->{$var};
128  if (!$this->attributesInitialized) {
129  $this->initInheritableNameAttributes($this->node);
130  }
131  if ("text" === $this->and) {
132  $this->and = CiteProc::getContext()->getLocale()->filter('terms', 'and')->single;
133  } elseif ('symbol' === $this->and) {
134  $this->and = '&#38;';
135  }
136 
137  $resultNames = $this->handleSubsequentAuthorSubstitution($name, $citationNumber);
138 
139  if (empty($resultNames)) {
140  return CiteProc::getContext()->getCitationItems()->getSubsequentAuthorSubstitute();
141  }
142 
143  $resultNames = $this->prepareAbbreviation($resultNames);
144 
145  /* When set to “true” (the default is “false”), name lists truncated by et-al abbreviation are followed by
146  the name delimiter, the ellipsis character, and the last name of the original name list. This is only
147  possible when the original name list has at least two more names than the truncated name list (for this
148  the value of et-al-use-first/et-al-subsequent-min must be at least 2 less than the value of
149  et-al-min/et-al-subsequent-use-first). */
150  if ($this->etAlUseLast) {
151  $this->and = "…"; // set "and"
152  $this->etAl = null; //reset $etAl;
153  }
154 
155  /* add "and" */
156  $this->addAnd($resultNames);
157 
158  $text = $this->renderDelimiterPrecedesLast($resultNames);
159 
160  if (empty($text)) {
161  $text = implode($this->delimiter, $resultNames);
162  }
163 
164  $text = $this->appendEtAl($name, $text, $resultNames);
165 
166  /* A third value, “count”, returns the total number of names that would otherwise be rendered by the use of the
167  cs:names element (taking into account the effects of et-al abbreviation and editor/translator collapsing),
168  which allows for advanced sorting. */
169  if ($this->form == 'count') {
170  return (int) count($resultNames);
171  }
172 
173  return $text;
174  }
175 
182  private function formatName($nameItem, $rank)
183  {
184  $nameObj = $this->cloneNamePOSC($nameItem);
185 
186  $useInitials = $this->initialize && !is_null($this->initializeWith) && $this->initializeWith !== false;
187  if ($useInitials && isset($nameItem->given)) {
188  $nameObj->given = StringHelper::initializeBySpaceOrHyphen($nameItem->given, $this->initializeWith);
189  }
190 
191  $renderedResult = $this->getNamesString($nameObj, $rank);
192  CiteProcHelper::applyAdditionMarkupFunction($nameItem, $this->parent->getVariables()[0], $renderedResult);
193  return trim($renderedResult);
194  }
195 
202  private function getNamesString($name, $rank)
203  {
204  $text = "";
205 
206  if (!isset($name->family)) {
207  return $text;
208  }
209 
210  $text = $this->nameOrder($name, $rank);
211 
212  //contains nbsp prefixed by normal space or followed by normal space?
213  $text = htmlentities($text);
214  if (strpos($text, " &nbsp;") !== false || strpos($text, "&nbsp; ") !== false) {
215  $text = preg_replace("/[\s]+/", "", $text); //remove normal spaces
216  return preg_replace("/&nbsp;+/", " ", $text);
217  }
218  $text = html_entity_decode(preg_replace("/[\s]+/", " ", $text));
219  return $this->format(trim($text));
220  }
221 
226  private function cloneNamePOSC($name)
227  {
228  $nameObj = new stdClass();
229  if (isset($name->family)) {
230  $nameObj->family = $name->family;
231  }
232  if (isset($name->given)) {
233  $nameObj->given = $name->given;
234  }
235  if (isset($name->{'non-dropping-particle'})) {
236  $nameObj->{'non-dropping-particle'} = $name->{'non-dropping-particle'};
237  }
238  if (isset($name->{'dropping-particle'})) {
239  $nameObj->{'dropping-particle'} = $name->{'dropping-particle'};
240  }
241  if (isset($name->{'suffix'})) {
242  $nameObj->{'suffix'} = $name->{'suffix'};
243  }
244  return $nameObj;
245  }
246 
253  protected function appendEtAl($data, $text, $resultNames)
254  {
255  //append et al abbreviation
256  if (count($data) > 1
257  && !empty($resultNames)
258  && !empty($this->etAl)
259  && !empty($this->etAlMin)
260  && !empty($this->etAlUseFirst)
261  && count($data) != count($resultNames)
262  ) {
263  /* By default, when a name list is truncated to a single name, the name and the “et-al” (or “and others”)
264  term are separated by a space (e.g. “Doe et al.”). When a name list is truncated to two or more names, the
265  name delimiter is used (e.g. “Doe, Smith, et al.”). This behavior can be changed with the
266  delimiter-precedes-et-al attribute. */
267 
268  switch ($this->delimiterPrecedesEtAl) {
269  case 'never':
270  $text = $text . " " . $this->etAl;
271  break;
272  case 'always':
273  $text = $text . $this->delimiter . $this->etAl;
274  break;
275  case 'contextual':
276  default:
277  if (count($resultNames) === 1) {
278  $text .= " " . $this->etAl;
279  } else {
280  $text .= $this->delimiter . $this->etAl;
281  }
282  }
283  }
284  return $text;
285  }
286 
291  protected function prepareAbbreviation($resultNames)
292  {
293  $cnt = count($resultNames);
294  /* Use of et-al-min and et-al-user-first enables et-al abbreviation. If the number of names in a name variable
295  matches or exceeds the number set on et-al-min, the rendered name list is truncated after reaching the number of
296  names set on et-al-use-first. */
297 
298  if (isset($this->etAlMin) && isset($this->etAlUseFirst)) {
299  if ($this->etAlMin <= $cnt) {
300  if ($this->etAlUseLast && $this->etAlMin - $this->etAlUseFirst >= 2) {
301  /* et-al-use-last: When set to “true” (the default is “false”), name lists truncated by et-al
302  abbreviation are followed by the name delimiter, the ellipsis character, and the last name of the
303  original name list. This is only possible when the original name list has at least two more names
304  than the truncated name list (for this the value of et-al-use-first/et-al-subsequent-min must be at
305  least 2 less than the value of et-al-min/et-al-subsequent-use-first).*/
306 
307  $lastName = array_pop($resultNames); //remove last Element and remember in $lastName
308  }
309  for ($i = $this->etAlUseFirst; $i < $cnt; ++$i) {
310  unset($resultNames[$i]);
311  }
312 
313  $resultNames = array_values($resultNames);
314 
315  if (!empty($lastName)) { // append $lastName if exist
316  $resultNames[] = $lastName;
317  }
318 
319  if ($this->parent->hasEtAl()) {
320  $this->etAl = $this->parent->getEtAl()->render(null);
321  return $resultNames;
322  } else {
323  $this->etAl = CiteProc::getContext()->getLocale()->filter('terms', 'et-al')->single;
324  return $resultNames;
325  }
326  }
327  return $resultNames;
328  }
329  return $resultNames;
330  }
331 
338  protected function renderSubsequentSubstitution($data, $preceding)
339  {
340  $resultNames = [];
341  $subsequentSubstitution = CiteProc::getContext()->getCitationItems()->getSubsequentAuthorSubstitute();
342  $subsequentSubstitutionRule = CiteProc::getContext()->getCitationItems()->getSubsequentAuthorSubstituteRule();
343 
348  foreach ($data as $rank => $name) {
349  switch ($subsequentSubstitutionRule) {
350  /* “partial-each” - when one or more rendered names in the name variable match those in the preceding
351  bibliographic entry, the value of subsequent-author-substitute substitutes for each matching name.
352  Matching starts with the first name, and continues up to the first mismatch. */
353  case SubsequentAuthorSubstituteRule::PARTIAL_EACH:
354  if (NameHelper::precedingHasAuthor($preceding, $name)) {
355  $resultNames[] = $subsequentSubstitution;
356  } else {
357  $resultNames[] = $this->formatName($name, $rank);
358  }
359  break;
360  /* “partial-first” - as “partial-each”, but substitution is limited to the first name of the name
361  variable. */
362  case SubsequentAuthorSubstituteRule::PARTIAL_FIRST:
363  if ($rank === 0) {
364  if ($preceding->author[0]->family === $name->family) {
365  $resultNames[] = $subsequentSubstitution;
366  } else {
367  $resultNames[] = $this->formatName($name, $rank);
368  }
369  } else {
370  $resultNames[] = $this->formatName($name, $rank);
371  }
372  break;
373 
374  /* “complete-each” - requires a complete match like “complete-all”, but now the value of
375  subsequent-author-substitute substitutes for each rendered name. */
376  case SubsequentAuthorSubstituteRule::COMPLETE_EACH:
377  try {
378  if (NameHelper::identicalAuthors($preceding, $data)) {
379  $resultNames[] = $subsequentSubstitution;
380  } else {
381  $resultNames[] = $this->formatName($name, $rank);
382  }
383  } catch (CiteProcException $e) {
384  $resultNames[] = $this->formatName($name, $rank);
385  }
386  break;
387  }
388  }
389  return $resultNames;
390  }
391 
398  private function handleSubsequentAuthorSubstitution($data, $citationNumber)
399  {
400  $hasPreceding = CiteProc::getContext()->getCitationItems()->hasKey($citationNumber - 1);
401  $subsequentSubstitution = CiteProc::getContext()->getCitationItems()->getSubsequentAuthorSubstitute();
402  $subsequentSubstitutionRule = CiteProc::getContext()->getCitationItems()->getSubsequentAuthorSubstituteRule();
403  $preceding = CiteProc::getContext()->getCitationItems()->get($citationNumber - 1);
404 
405 
406  if ($hasPreceding && !is_null($subsequentSubstitution) && !empty($subsequentSubstitutionRule)) {
410  if ($subsequentSubstitutionRule == SubsequentAuthorSubstituteRule::COMPLETE_ALL) {
411  try {
412  if (NameHelper::identicalAuthors($preceding, $data)) {
413  return [];
414  } else {
415  $resultNames = $this->getFormattedNames($data);
416  }
417  } catch (CiteProcException $e) {
418  $resultNames = $this->getFormattedNames($data);
419  }
420  } else {
421  $resultNames = $this->renderSubsequentSubstitution($data, $preceding);
422  }
423  } else {
424  $resultNames = $this->getFormattedNames($data);
425  }
426  return $resultNames;
427  }
428 
429 
435  protected function getFormattedNames($data)
436  {
437  $resultNames = [];
438  foreach ($data as $rank => $name) {
439  $formatted = $this->formatName($name, $rank);
440  $resultNames[] = NameHelper::addExtendedMarkup($this->variable, $name, $formatted);
441  }
442  return $resultNames;
443  }
444 
449  protected function renderDelimiterPrecedesLastNever($resultNames)
450  {
451  $text = "";
452  if (!$this->etAlUseLast) {
453  if (count($resultNames) === 1) {
454  $text = $resultNames[0];
455  } elseif (count($resultNames) === 2) {
456  $text = implode(" ", $resultNames);
457  } else { // >2
458  $lastName = array_pop($resultNames);
459  $text = implode($this->delimiter, $resultNames)." ".$lastName;
460  }
461  }
462  return $text;
463  }
464 
469  protected function renderDelimiterPrecedesLastContextual($resultNames)
470  {
471  if (count($resultNames) === 1) {
472  $text = $resultNames[0];
473  } elseif (count($resultNames) === 2) {
474  $text = implode(" ", $resultNames);
475  } else {
476  $text = implode($this->delimiter, $resultNames);
477  }
478  return $text;
479  }
480 
484  protected function addAnd(&$resultNames)
485  {
486  $count = count($resultNames);
487  if (!empty($this->and) && $count > 1 && empty($this->etAl)) {
488  $new = $this->and.' '.end($resultNames); // add and-prefix of the last name if "and" is defined
489  // set prefixed last name at the last position of $resultNames array
490  $resultNames[count($resultNames) - 1] = $new;
491  }
492  }
493 
498  protected function renderDelimiterPrecedesLast($resultNames)
499  {
500  $text = "";
501  if (!empty($this->and) && empty($this->etAl)) {
502  switch ($this->delimiterPrecedesLast) {
503  case 'after-inverted-name':
504  //TODO: implement
505  break;
506  case 'always':
507  $text = implode($this->delimiter, $resultNames);
508  break;
509  case 'never':
510  $text = $this->renderDelimiterPrecedesLastNever($resultNames);
511  break;
512  case 'contextual':
513  default:
514  $text = $this->renderDelimiterPrecedesLastContextual($resultNames);
515  }
516  }
517  return $text;
518  }
519 
520 
528  private function nameOrder($data, $rank)
529  {
530  $nameAsSortOrder = (($this->nameAsSortOrder === "first" && $rank === 0) || $this->nameAsSortOrder === "all");
531  $demoteNonDroppingParticle = CiteProc::getContext()->getGlobalOptions()->getDemoteNonDroppingParticles();
532  $normalizedName = NameHelper::normalizeName($data);
533  if (StringHelper::isLatinString($normalizedName) || StringHelper::isCyrillicString($normalizedName)) {
534  if ($this->form === "long"
535  && $nameAsSortOrder
536  && ((string) $demoteNonDroppingParticle === DemoteNonDroppingParticle::NEVER
537  || (string) $demoteNonDroppingParticle === DemoteNonDroppingParticle::SORT_ONLY)
538  ) {
539  // [La] [Fontaine], [Jean] [de], [III]
540  NameHelper::prependParticleTo($data, "family", "non-dropping-particle");
541  NameHelper::appendParticleTo($data, "given", "dropping-particle");
542 
543  list($family, $given) = $this->renderNameParts($data);
544 
545  $text = $family.(!empty($given) ? $this->sortSeparator.$given : "");
546  $text .= !empty($data->suffix) ? $this->sortSeparator.$data->suffix : "";
547  } elseif ($this->form === "long"
548  && $nameAsSortOrder
549  && (is_null($demoteNonDroppingParticle)
550  || (string) $demoteNonDroppingParticle === DemoteNonDroppingParticle::DISPLAY_AND_SORT)
551  ) {
552  // [Fontaine], [Jean] [de] [La], [III]
553  NameHelper::appendParticleTo($data, "given", "dropping-particle");
554  NameHelper::appendParticleTo($data, "given", "non-dropping-particle");
555  list($family, $given) = $this->renderNameParts($data);
556  $text = $family;
557  $text .= !empty($given) ? $this->sortSeparator.$given : "";
558  $text .= !empty($data->suffix) ? $this->sortSeparator.$data->suffix : "";
559  } elseif ($this->form === "long" && $nameAsSortOrder && empty($demoteNonDroppingParticle)) {
560  list($family, $given) = $this->renderNameParts($data);
561  $text = $family;
562  $text .= !empty($given) ? $this->delimiter.$given : "";
563  $text .= !empty($data->suffix) ? $this->sortSeparator.$data->suffix : "";
564  } elseif ($this->form === "short") {
565  // [La] [Fontaine]
566  NameHelper::prependParticleTo($data, "family", "non-dropping-particle");
567  $text = $data->family;
568  } else {// form "long" (default)
569  // [Jean] [de] [La] [Fontaine] [III]
570  NameHelper::prependParticleTo($data, "family", "non-dropping-particle");
571  NameHelper::prependParticleTo($data, "family", "dropping-particle");
572  NameHelper::appendParticleTo($data, "family", "suffix");
573  list($family, $given) = $this->renderNameParts($data);
574  $text = !empty($given) ? $given." ".$family : $family;
575  }
576  } elseif (StringHelper::isAsianString(NameHelper::normalizeName($data))) {
577  $text = $this->form === "long" ? $data->family . $data->given : $data->family;
578  } else {
579  $text = $this->form === "long" ? $data->family . " " . $data->given : $data->family;
580  }
581  return $text;
582  }
583 
588  private function renderNameParts($data)
589  {
590  $given = "";
591  if (array_key_exists("family", $this->nameParts)) {
592  $family = $this->nameParts["family"]->render($data);
593  } else {
594  $family = $data->family;
595  }
596  if (isset($data->given)) {
597  if (array_key_exists("given", $this->nameParts)) {
598  $given = $this->nameParts["given"]->render($data);
599  } else {
600  $given = $data->given;
601  }
602  }
603  return [$family, $given];
604  }
605 
606 
610  public function getForm()
611  {
612  return $this->form;
613  }
614 
618  public function isNameAsSortOrder()
619  {
620  return $this->nameAsSortOrder;
621  }
622 
626  public function getDelimiter()
627  {
628  return $this->delimiter;
629  }
630 
634  public function setDelimiter($delimiter)
635  {
636  $this->delimiter = $delimiter;
637  }
638 
642  public function getParent()
643  {
644  return $this->parent;
645  }
646 }
Seboettg\CiteProc\Rendering\Name\Name\renderDelimiterPrecedesLast
renderDelimiterPrecedesLast($resultNames)
Definition: Name.php:512
Seboettg\CiteProc\Rendering\Name\Name\getParent
getParent()
Definition: Name.php:656
Seboettg\CiteProc\Styles\AffixesTrait
trait AffixesTrait
Definition: AffixesTrait.php:21
Seboettg\CiteProc\Styles\FormattingTrait
trait FormattingTrait
Definition: FormattingTrait.php:21
Seboettg\CiteProc\Util\NameHelper
Definition: NameHelper.php:21
Seboettg\CiteProc\Style\getForm
getForm()
Definition: InheritableNameAttributesTrait.php:621
Seboettg\CiteProc\Style\InheritableNameAttributesTrait
trait InheritableNameAttributesTrait
Definition: InheritableNameAttributesTrait.php:37
Seboettg\CiteProc\Rendering\Name\Name\appendEtAl
appendEtAl($data, $text, $resultNames)
Definition: Name.php:267
Seboettg\CiteProc\Rendering\Name\Name\getDelimiter
getDelimiter()
Definition: Name.php:640
Seboettg\CiteProc\Rendering\Name\Name\renderDelimiterPrecedesLastContextual
renderDelimiterPrecedesLastContextual($resultNames)
Definition: Name.php:483
Seboettg\CiteProc\Exception\InvalidStylesheetException
Definition: InvalidStylesheetException.php:10
Seboettg\CiteProc\Rendering\Name\Name\render
render($data, $var, $citationNumber=null)
Definition: Name.php:138
Seboettg\CiteProc\Rendering\HasParent
Definition: HasParent.php:17
Seboettg\CiteProc\Rendering\Name
Definition: EtAl.php:10
Seboettg\CiteProc\Rendering\Name\Name\getFormattedNames
getFormattedNames($data)
Definition: Name.php:449
Seboettg\CiteProc\Rendering\Name\Name\isNameAsSortOrder
isNameAsSortOrder()
Definition: Name.php:632
Seboettg\CiteProc\Style\initInheritableNameAttributes
initInheritableNameAttributes(SimpleXMLElement $node)
Definition: InheritableNameAttributesTrait.php:273
Seboettg\CiteProc\Rendering\Name\Name\addAnd
addAnd(&$resultNames)
Definition: Name.php:498
Seboettg\CiteProc\Style\Options\DemoteNonDroppingParticle
Definition: DemoteNonDroppingParticle.php:24
Seboettg\CiteProc\Util\StringHelper
Definition: StringHelper.php:22
Seboettg\CiteProc\CiteProc
Definition: CiteProc.php:32
Seboettg\CiteProc\Rendering\Name\Name
Definition: Name.php:39
Seboettg\CiteProc\Rendering\Name\Names
Definition: Names.php:36
Seboettg\Collection\count
count()
Definition: ArrayListTrait.php:253
Seboettg\CiteProc\Rendering\Name\Name\__construct
__construct(SimpleXMLElement $node, Names $parent)
Definition: Name.php:99
Seboettg\CiteProc\Rendering\Name\Name\prepareAbbreviation
prepareAbbreviation($resultNames)
Definition: Name.php:305
Seboettg\CiteProc\Exception\CiteProcException
Definition: CiteProcException.php:20
Seboettg\CiteProc\Util\CiteProcHelper\applyAdditionMarkupFunction
static applyAdditionMarkupFunction($dataItem, $valueToRender, $renderedText)
Definition: CiteProcHelper.php:26
Seboettg\CiteProc\Style\Options\SubsequentAuthorSubstituteRule
Definition: SubsequentAuthorSubstituteRule.php:23
Seboettg\CiteProc\Rendering\Name\Name\getForm
getForm()
Definition: Name.php:624
Seboettg\CiteProc\Rendering\Name\Name\setDelimiter
setDelimiter($delimiter)
Definition: Name.php:648
Seboettg\CiteProc\Rendering\Name\Name\$nameParts
$nameParts
Definition: Name.php:52
Seboettg\CiteProc\CiteProc\getContext
static getContext()
Definition: CiteProc.php:45
Seboettg\CiteProc\Util\StringHelper\initializeBySpaceOrHyphen
static initializeBySpaceOrHyphen($string, $initializeSign)
Definition: StringHelper.php:180
Seboettg\CiteProc\Util\CiteProcHelper
Definition: CiteProcHelper.php:15
Seboettg\CiteProc\Util\Factory
Definition: Util/Factory.php:21
Seboettg\CiteProc\Styles\format
format($text)
Definition: FormattingTrait.php:81
Seboettg\CiteProc\Util\Factory\create
static create($node, $param=null)
Definition: Util/Factory.php:55
Seboettg\CiteProc\Rendering\Name\Name\renderSubsequentSubstitution
renderSubsequentSubstitution($data, $preceding)
Definition: Name.php:352
Omnipay\Common\initialize
initialize(array $parameters=[])
Definition: ParametersTrait.php:63
Seboettg\CiteProc\Rendering\Name\Name\renderDelimiterPrecedesLastNever
renderDelimiterPrecedesLastNever($resultNames)
Definition: Name.php:463