Files
SIGE-WEB-snapshot/wp-content/plugins/teachpress/includes/bibtexParse/BIBTEXCREATORPARSE.php

412 lines
13 KiB
PHP

<?php
/**
* This file contains the external class PARSECREATORS of WIKINDX
*
* @see https://wikindx.sourceforge.io/ The WIKINDX SourceForge project
*
* @author The WIKINDX Team
* @license https://www.isc.org/licenses/ ISC License
*/
/*
28/04/2005 - Mark Grimshaw.
Efficiency improvements.
11/02/2006 - Daniel Reidsma.
Changes to preg_matching to account for Latex characters in names such as {\"{o}}
*/
// For a quick command-line test (php -f PARSECREATORS.php) after installation, uncomment these lines:
/***********************
$authors = "Mark \~N. Grimshaw and Bush III, G.W. & M. C. H{\\'a}mmer Jr. and von Frankenstein, Ferdinand Cecil, P.H. & Charles Louis Xavier Joseph de la Vallee P{\\\"{o}}ussin";
$creator = new PARSECREATORS();
$creatorArray = $creator->parse($authors);
print_r($creatorArray);
***********************/
/**
* Parse BibTeX authors
*/
class BIBTEXCREATORPARSE {
/** boolean If true, separate initials from firstname */
public $separateInitials = FALSE;
/** boolean */
public $removeBraces = TRUE;
/** boolean */
public $removeTilde = TRUE;
/** boolean */
public $removeEtAl = TRUE;
/** string */
private $prefix;
/**
* Create writer arrays from bibtex input.
*
* 'author field can be (delimiters between authors are 'and', 'AND', or '&'):
* There are three possible cases:
* 1: First von Last
* 2: von Last, First
* 3: von Last, Jr, First
*
* @param string $input
*
* @return mixed FALSE|array (firstname, initials, surname, jr, von)
*/
public function parse($input) {
if ($this->removeEtAl) {
$input = str_replace("et al.", '', $input);
}
$input = trim($input);
if ($this->removeBraces) {
$input = str_replace(['{', '}'], '', $input);
}
if ($this->removeTilde) {
$input = str_replace('~', ' ', $input);
}
//remove linebreaks
$input = preg_replace('/[\r\n\t]/u', ' ', $input);
if (preg_match('/\s&\s/u', $input)) {
$authorArray = $this->explodeString(" & ", $input);
$input = implode(" and ", $authorArray);
}
elseif (preg_match('/\sAND\s/u', $input)) {
$authorArray = $this->explodeString(" AND ", $input);
$input = implode(" and ", $authorArray);
}
// split on ' and '
$authorArray = $this->explodeString(" and ", $input);
foreach ($authorArray as $value) {
$firstname = $initials = $von = $surname = $jr = "";
$this->prefix = [];
//get rid of multiple spaces
$value = preg_replace("/\\s{2,}/u", ' ', trim($value));
$commaAuthor = $this->explodeString(",", $value);
$size = count($commaAuthor);
if ($size == 1) {
// First von Last
// First: longest sequence of white-space separated words starting with an uppercase and that is not the whole string.
// von: longest sequence of whitespace separated words whose last word starts with lower case and that is not the whole string.
// Then Last is everything else.
// Lastname cannot be empty
$author = $this->explodeString(" ", $value);
if (count($author) == 1) {
$surname = $author[0];
}
else {
$tempFirst = [];
$case = $this->getStringCase($author[0]);
while ((($case == "upper") || ($case == "none")) && (count($author) > 0)) {
$tempFirst[] = array_shift($author);
if (!empty($author)) {
$case = $this->getStringCase($author[0]);
}
}
list($von, $surname) = $this->getVonLast($author);
if ($surname == "") {
$surname = array_pop($tempFirst);
}
$firstname = implode(" ", $tempFirst);
}
}
elseif ($size == 2) {
// we deal with von Last, First
// First: Everything after the comma
// von: longest sequence of whitespace separated words whose last word starts with lower case and that is not the whole string.
// Then Last is everything else.
// Lastname cannot be empty
$author = $this->explodeString(" ", $commaAuthor[0]);
if (count($author) == 1) {
$surname = $author[0];
}
else {
list($von, $surname) = $this->getVonLast($author);
}
$firstname = $commaAuthor[1];
}
else {
// we deal with von Last, Jr, First
// First: Everything after the comma
// von: longest sequence of whitespace separated words whose last word starts with lower case and that is not the whole string.
// Then Last is everything else.
// Lastname cannot be empty
$author = $this->explodeString(" ", $commaAuthor[0]);
if ( count($author) == 1 ) {
$surname = $author[0];
}
else {
list($von, $surname) = $this->getVonLast($author);
}
$jr = $commaAuthor[1];
$firstname = $commaAuthor[2];
}
$firstname = trim($firstname);
$von = trim($von);
$surname = trim($surname);
$jr = trim($jr);
$firstname = $this->formatFirstname($firstname);
if ($this->separateInitials) {
list($firstname, $initials) = $this->separateInitialsFunc($firstname);
}
$creators[] = [$firstname, $initials, $surname, $jr, $von];
}
if (isset($creators)) {
return $creators;
}
else {
return FALSE;
}
}
/**
* converts a first name to initials
*
* @param string $firstname The firstname string
* @param string $punctuation The punctuation after an initial
*
* @return string
*/
public function getInitials($firstname, $punctuation = '.') {
$initials = '';
$name = explode(' ', $firstname);
foreach ($name as $part) {
$size = mb_strlen($part);
if (self::matchSuffix($part, ".") && ($size < 4)) {
$part = str_replace('.', '', $part);
$initials .= $part . $punctuation . " ";
}
// elseif (preg_match("/([A-Z])/u", $part, $firstChar))
elseif (preg_match("/(\\p{Lu})/u", $part, $firstChar)) {
$initials .= $firstChar[0] . $punctuation . " ";
}
}
return trim($initials);
}
/**
* gets the "von" and "last" part from the author array
*
* @param string $author
*
* @return array (von, surname)
*/
private function getVonLast($author) {
$surname = $von = "";
$tempVon = [];
$count = 0;
$bVon = FALSE;
foreach ($author as $part) {
$case = $this->getStringCase($part);
if ($count == 0) {
if ($case == "lower") {
$bVon = TRUE;
if ($case == "none") {
$count--;
}
}
}
if ($bVon) {
$tempVon[] = $part;
}
else {
$surname = $surname . " " . $part;
}
$count++;
}
if (count($tempVon) > 0) {
//find the first lowercase von starting from the end
for ($i = (count($tempVon) - 1); $i > 0; $i--) {
if ($this->getStringCase($tempVon[$i]) == "lower") {
break;
}
else {
$surname = array_pop($tempVon) . " " . $surname;
}
}
if ($surname == "")
{ // von part was all lower chars, the last entry is surname
$surname = array_pop($tempVon);
}
$von = implode(" ", $tempVon);
}
return [trim($von), trim($surname)];
}
/**
* Explodes a string but not when the delimiter occurs within a pair of braces
*
* @param string $delimiter
* @param string $val
*
* @return array
*/
private function explodeString($delimiter, $val) {
$bracelevel = $i = $j = 0;
$len = mb_strlen($val);
$dlen = mb_strlen($delimiter);
$strings = [];
while ($i < $len) {
if ($val[$i] == '{') {
$bracelevel++;
}
elseif ($val[$i] == '}') {
$bracelevel--;
}
elseif (!$bracelevel) {
if (mb_substr($val, $i, $dlen) == $delimiter) {
$strings[] = mb_substr($val, $j, $i - $j);
$j = $i + $dlen;
$i += ($dlen - 1);
}
}
$i++;
}
$strings[] = mb_substr($val, $j);
return $strings;
}
/**
* returns the case of a string
*
* Case determination:
* non-alphabetic chars are caseless
* the first alphabetic char determines case
* if a string is caseless, it is grouped to its neighbour string.
*
* @param string $string
*
* @return string
*/
private function getStringCase($string) {
$caseChar = "";
// $string = preg_replace("/\d/", "", $string);
$string = preg_replace("/\\p{N}/u", "", $string);
if (preg_replace("/{/u", "", $string)) {
$string = preg_replace("/({[^\\\\.]*})/u", "", $string);
}
// if (preg_match("/\w/", $string, $caseChar))
if (preg_match("/\\p{L}/u", $string, $caseChar)) {
if (is_array($caseChar)) {
$caseChar = $caseChar[0];
}
// if (preg_match("/[a-z]/", $caseChar))
if (preg_match("/\\p{Ll}/u", $caseChar)) {
return "lower";
}
// else if (preg_match("/[A-Z]/", $caseChar))
else if (preg_match("/\\p{Lu}/u", $caseChar)) {
return "upper";
}
else {
return "none";
}
}
else {
return "none";
}
}
/**
* separates initials from a firstname
*
* @param string $firstname
*
* @return array (firstname, initials)
*/
private function separateInitialsFunc($firstname) {
$name = $this->explodeString(" ", $firstname);
$initials = [];
$remain = [];
foreach ($name as $part) {
// if (preg_match("/[a-zA-Z]{2,}/u", trim($part)))
if (preg_match("/\\.$/u", trim($part))) {
// find initials indicated by '.' at the end
$initials[] = str_replace(".", " ", trim($part));
}
// if (preg_match("/\p{L}{2,}/u", trim($part))) // match unicode characters
else {
$remain[] = trim($part);
}
// else
// $initials[] = str_replace(".", " ", trim($part));
}
if (isset($initials)) {
$initials_ = '';
foreach ($initials as $initial) {
$initials_ .= ' ' . trim($initial);
}
$initials = $initials_;
}
$firstname = str_replace('.', '', implode(" ", $remain));
return [$firstname, $initials];
}
/**
* Format firstname
*
* @param string $firstname
*
* @return string
*/
private function formatFirstname($firstname) {
if ($firstname == "") {
return "";
}
$name = $this->explodeString(".", $firstname);
$formatName = "";
$count = 1;
$size = count($name);
foreach ($name as $part) {
$part = trim($part);
if ($part != "") {
$formatName .= $part;
if ($count < $size) {
//if the end of part contains an escape character (either just \ or \{, we do not add the extra space
if ( self::matchSuffix($part, "\\") || self::matchSuffix($part, "{")) {
$formatName .= ".";
}
else {
$formatName .= ". ";
}
}
}
$count++;
}
return $formatName;
}
/**
* Check if a suffix match against a string
*
* @param $string A string
* @param $suffix A suffix
*
* @return bool
*/
private static function matchSuffix($string, $suffix) {
return (mb_strtolower(mb_substr($string, -mb_strlen($suffix))) == $suffix);
}
}