143 lines
4.2 KiB
PHP
143 lines
4.2 KiB
PHP
<?php
|
|
/*
|
|
Condorcet PHP - Election manager and results calculator.
|
|
Designed for the Condorcet method. Integrating a large number of algorithms extending Condorcet. Expandable for all types of voting systems.
|
|
|
|
By Julien Boudry and contributors - MIT LICENSE (Please read LICENSE.txt)
|
|
https://github.com/julien-boudry/Condorcet
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace CondorcetPHP\Condorcet\Utils;
|
|
|
|
use CondorcetPHP\Condorcet\Dev\CondorcetDocumentationGenerator\CondorcetDocAttributes\Throws;
|
|
use CondorcetPHP\Condorcet\Throwable\VoteInvalidFormatException;
|
|
use CondorcetPHP\Condorcet\Tools\Converters\CondorcetElectionFormat;
|
|
|
|
// Base Condorcet class
|
|
class VoteEntryParser
|
|
{
|
|
public readonly string $originalEntry;
|
|
|
|
public readonly ?string $comment;
|
|
public readonly int $multiple;
|
|
public readonly ?array $ranking;
|
|
public readonly ?array $tags;
|
|
public readonly int $weight;
|
|
|
|
public function __construct(string $entry)
|
|
{
|
|
$this->originalEntry = $entry;
|
|
|
|
// Disallow < and "
|
|
if (preg_match('/<|"/mi', $entry) === 1) {
|
|
throw new VoteInvalidFormatException("found '<' or '|' in " . $entry);
|
|
}
|
|
|
|
$this->comment = $this->getComment($entry, true);
|
|
|
|
$this->multiple = self::parseIntValueFromVoteStringOffset('*', $entry, true);
|
|
$this->weight = self::parseIntValueFromVoteStringOffset('^', $entry, true);
|
|
|
|
$this->tags = $this->convertTagsFromVoteString($entry, true);
|
|
|
|
$this->ranking = self::convertRankingFromString($entry);
|
|
}
|
|
|
|
// From a string like 'A>B=C=H>G=T>Q'
|
|
public static function convertRankingFromString(string $formula): ?array
|
|
{
|
|
$formula = trim($formula);
|
|
|
|
// Condorcet Election Format special string
|
|
if (empty($formula)) {
|
|
return null;
|
|
} elseif ($formula === CondorcetElectionFormat::SPECIAL_KEYWORD_EMPTY_RANKING) {
|
|
return [];
|
|
} else {
|
|
$ranking = explode('>', $formula);
|
|
|
|
foreach ($ranking as &$rank_vote) {
|
|
$rank_vote = explode('=', $rank_vote);
|
|
$rank_vote = array_filter($rank_vote);
|
|
|
|
// Del space at start and end
|
|
foreach ($rank_vote as &$value) {
|
|
$value = trim($value);
|
|
}
|
|
}
|
|
|
|
return array_filter($ranking);
|
|
}
|
|
}
|
|
|
|
public static function convertTagsFromVoteString(string &$voteString, bool $cut = false): ?array
|
|
{
|
|
$offset = mb_strpos($voteString, '||');
|
|
|
|
if (\is_int($offset)) {
|
|
$tagsPart = mb_substr($voteString, 0, $offset);
|
|
|
|
$tags = explode(',', $tagsPart);
|
|
|
|
array_walk($tags, static function (string &$value): void {
|
|
$value = trim($value);
|
|
});
|
|
|
|
$cut && $voteString = mb_substr($voteString, $offset + 2);
|
|
|
|
return $tags;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static function getComment(string &$voteString, bool $cut = false): ?string
|
|
{
|
|
$offset = mb_strpos($voteString, '#');
|
|
|
|
if (\is_int($offset)) {
|
|
$comment = trim(mb_substr($voteString, $offset + 1));
|
|
|
|
$cut && $voteString = mb_substr($voteString, 0, $offset);
|
|
|
|
return $comment;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
#[Throws(VoteInvalidFormatException::class)]
|
|
public static function parseIntValueFromVoteStringOffset(string $character, string &$entry, bool $cut = false): int
|
|
{
|
|
$offset = mb_strpos($entry, $character);
|
|
|
|
if (\is_int($offset)) {
|
|
$input = trim(mb_substr($entry, $offset + 1));
|
|
|
|
$value = '';
|
|
|
|
foreach (mb_str_split($input) as $char) {
|
|
if (!\in_array($char, ['#', '^', '*', "\n"], true)) {
|
|
$value .= $char;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Errors
|
|
if (!is_numeric($value)) {
|
|
throw new VoteInvalidFormatException("the value '{$value}' is not an integer.");
|
|
}
|
|
|
|
$cut && $entry = mb_substr($entry, 0, $offset);
|
|
|
|
$value = \intval($value);
|
|
return ($value > 0) ? $value : 1;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|