Skip to content

Commit c4b3402

Browse files
committed
add array and string parse
1 parent e3c2e98 commit c4b3402

10 files changed

Lines changed: 361 additions & 2 deletions

File tree

src/Context/Name/GermanTypeName.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
class GermanTypeName extends AbstractName
66
{
77
public function __construct(
8-
private readonly string $firstName,
9-
private readonly string $lastName,
8+
public string $firstName,
9+
public string $lastName,
1010
) {
1111
}
1212

src/Parser/Name/AbstractParser.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace HeimrichHannot\SalutationCreator\Parser\Name;
4+
5+
use HeimrichHannot\SalutationCreator\Context\Name\AbstractName;
6+
use HeimrichHannot\SalutationCreator\Context\Name\GermanTypeName;
7+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\Result;
8+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\RuleInterface;
9+
10+
abstract class AbstractParser
11+
{
12+
private bool $allowIncomplete = false;
13+
14+
public function __construct(
15+
/**
16+
* @var class-string<RuleInterface>[]
17+
*/
18+
protected array $rules,
19+
/** @var class-string<AbstractName> */
20+
protected string $format,
21+
) {
22+
if (!(is_a($this->format, AbstractName::class, true))) {
23+
throw new \InvalidArgumentException(sprintf('Format class "%s" does not exist or is not a subclass of "%s".', $this->format, AbstractName::class));
24+
}
25+
}
26+
27+
public static function create(string $format): static
28+
{
29+
static::validateFormat($format);
30+
$rules = static::defaultRules($format);
31+
32+
$instance = new static($rules, $format);
33+
return $instance;
34+
}
35+
36+
public function allowIncomplete(bool $allow): static
37+
{
38+
$this->allowIncomplete = $allow;
39+
return $this;
40+
}
41+
42+
public static function validateFormat(string $targetFormat): void
43+
{
44+
if (!class_exists($targetFormat) || !is_subclass_of($targetFormat, AbstractName::class)) {
45+
throw new \InvalidArgumentException(sprintf('Target format class "%s" does not exist or is not a subclass of "%s".', $targetFormat, AbstractName::class));
46+
}
47+
}
48+
49+
protected function buildNameFromResult(Result $result): ?AbstractName
50+
{
51+
if (!$result->isComplete() && !$this->allowIncomplete) {
52+
return null;
53+
}
54+
55+
if ($result->isEmpty()) {
56+
return null;
57+
}
58+
59+
/** @var GermanTypeName $name */
60+
$name = new ($this->format)();
61+
$name->firstName = $result->firstName;
62+
$name->lastName = $result->lastName;
63+
64+
return $name;
65+
}
66+
67+
abstract public static function defaultRules(string $format): array;
68+
}

src/Parser/Name/ArrayParser.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace HeimrichHannot\SalutationCreator\Parser\Name;
4+
5+
use HeimrichHannot\SalutationCreator\Context\Name\AbstractName;
6+
use HeimrichHannot\SalutationCreator\Context\Name\GermanTypeName;
7+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\Array\EnglishKeyNameRule;
8+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\Array\GermanKeyNameRule;
9+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\Result;
10+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\RowValue;
11+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\RuleInterface;
12+
use HeimrichHannot\SalutationCreator\Parser\Name\ValueObject\ArrayRow;
13+
use HeimrichHannot\SalutationCreator\Parser\Name\ValueObject\Name;
14+
15+
class ArrayParser extends AbstractParser
16+
{
17+
public function parse(array $data): ?AbstractName
18+
{
19+
$result = new Result(false);
20+
foreach ($data as $key => $value) {
21+
$entry = new RowValue($key, $value);
22+
foreach ($this->rules as $rule) {
23+
if (!(is_a($rule, RuleInterface::class, true))) {
24+
continue;
25+
}
26+
$result->with((new $rule)->apply($entry));
27+
if ($result->isComplete()) {
28+
break 2;
29+
}
30+
}
31+
}
32+
33+
return $this->buildNameFromResult($result);
34+
}
35+
36+
/**
37+
* @param string $format
38+
* @return class-string<RuleInterface>[]
39+
*/
40+
public static function defaultRules(string $format): array
41+
{
42+
return match ($format) {
43+
GermanTypeName::class => [
44+
GermanKeyNameRule::class,
45+
EnglishKeyNameRule::class,
46+
],
47+
default => [
48+
EnglishKeyNameRule::class,
49+
],
50+
};
51+
}
52+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace HeimrichHannot\SalutationCreator\Parser\Name\Rule\Array;
4+
5+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\Result;
6+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\RuleInterface;
7+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\RowValue;
8+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\Value;
9+
10+
class EnglishKeyNameRule implements RuleInterface
11+
{
12+
public function apply(Value $value): Result
13+
{
14+
if (!($value instanceof RowValue)) {
15+
return new Result(false);
16+
}
17+
18+
if (!is_string($value->originalValue)) {
19+
return new Result(false);
20+
}
21+
22+
if ($value->normalizedKey === 'firstname') {
23+
return new Result(true, firstName: $value->originalValue, origin: $value);
24+
}
25+
if (\in_array($value->normalizedKey, ['lastname', 'name'])) {
26+
return new Result(true, lastName: $value->originalValue, origin: $value);
27+
}
28+
29+
return new Result(false);
30+
}
31+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace HeimrichHannot\SalutationCreator\Parser\Name\Rule\Array;
4+
5+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\Result;
6+
use HeimrichHannot\SalutationCreator\Parser\Name\ValueObject\ArrayRow;
7+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\RowValue;
8+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\Value;
9+
10+
class GermanKeyNameRule
11+
{
12+
public function apply(Value $value): Result
13+
{
14+
if (!($value instanceof RowValue)) {
15+
return new Result(false);
16+
}
17+
18+
if (!is_string($value->originalValue)) {
19+
return new Result(false);
20+
}
21+
22+
if (in_array($value->normalizedKey, ['vorname'])) {
23+
return new Result(true, firstName: $value->originalValue, origin: $value);
24+
}
25+
if (in_array($value->normalizedKey, ['nachname', 'zuname', 'name'])) {
26+
return new Result(true, lastName: $value->originalValue, origin: $value);
27+
}
28+
29+
return new Result(false);
30+
}
31+
}

src/Parser/Name/Rule/Result.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace HeimrichHannot\SalutationCreator\Parser\Name\Rule;
4+
5+
class Result
6+
{
7+
public function __construct(
8+
public readonly bool $matched,
9+
public readonly string $firstName = '',
10+
public readonly string $lastName = '',
11+
public readonly mixed $origin = null,
12+
) {}
13+
14+
public function with(Result $result): Result
15+
{
16+
if (!$result->matched) {
17+
return $this;
18+
}
19+
20+
return new self(
21+
true,
22+
$result->firstName !== '' ? $result->firstName : $this->firstName,
23+
$result->lastName !== '' ? $result->lastName : $this->lastName,
24+
$this->origin // oder $result->origin, je nach gewünschter Semantik
25+
);
26+
}
27+
28+
public function isComplete(): bool
29+
{
30+
return $this->matched && $this->firstName !== '' && $this->lastName !== '';
31+
}
32+
33+
public function isEmpty(): bool
34+
{
35+
return $this->firstName === '' && $this->lastName === '';
36+
}
37+
}

src/Parser/Name/Rule/RowValue.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace HeimrichHannot\SalutationCreator\Parser\Name\Rule;
4+
5+
class RowValue extends Value
6+
{
7+
public string $originalKey;
8+
public string $normalizedKey;
9+
10+
public function __construct(
11+
mixed $key,
12+
mixed $value
13+
) {
14+
parent::__construct($value);
15+
16+
$this->originalKey = (string) $key;
17+
$this->normalizedKey = strtolower((string) preg_replace('/[^a-z0-9]/i', '', $this->originalKey));
18+
}
19+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace HeimrichHannot\SalutationCreator\Parser\Name\Rule;
4+
5+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\Result;
6+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\Value;
7+
8+
interface RuleInterface
9+
{
10+
public function apply(Value $value): Result;
11+
}

src/Parser/Name/Rule/Value.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace HeimrichHannot\SalutationCreator\Parser\Name\Rule;
4+
5+
class Value
6+
{
7+
public mixed $originalValue;
8+
9+
public function __construct(mixed $originalValue)
10+
{
11+
$this->originalValue = $originalValue;
12+
}
13+
14+
15+
}

src/Parser/Name/StringParser.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
namespace HeimrichHannot\SalutationCreator\Parser\Name;
4+
5+
use HeimrichHannot\SalutationCreator\Context\Name\AbstractName;
6+
use HeimrichHannot\SalutationCreator\Parser\Name\Rule\Result;
7+
8+
class StringParser extends AbstractParser
9+
{
10+
public static function defaultRules(string $format): array
11+
{
12+
return [];
13+
}
14+
15+
/**
16+
* @todo Refactor this method to rule based parsing
17+
*/
18+
public function parse(string $name): ?AbstractName
19+
{
20+
$name = preg_replace('/\s+/u', ' ', trim($name));
21+
if (empty($name)) {
22+
return null;
23+
}
24+
25+
if (str_contains($name, ',')) {
26+
[$lastName, $firstName] = array_map('trim', explode(',', $name, 2));
27+
return $this->buildNameFromResult(new Result(true, $firstName, $lastName));
28+
}
29+
30+
$parts = preg_split('/\s+/u', $name, -1, PREG_SPLIT_NO_EMPTY);
31+
$count = count($parts);
32+
33+
if ($count === 1) {
34+
return $this->buildNameFromResult(new Result(true, '', $parts[0]));
35+
}
36+
37+
if ($count === 2) {
38+
return $this->buildNameFromResult(new Result(true, $parts[0], $parts[1]));
39+
}
40+
41+
$surnamePrefixes = [
42+
'von',
43+
'vom',
44+
'van',
45+
'de',
46+
'del',
47+
'della',
48+
'der',
49+
'den',
50+
'zu',
51+
'zur',
52+
'zum',
53+
'da',
54+
'di',
55+
'du',
56+
'la',
57+
'le',
58+
'dos',
59+
'das',
60+
'do',
61+
'bin',
62+
'al',
63+
];
64+
65+
// Startpunkt des Nachnamens suchen
66+
$lastNameStartIndex = $count - 1;
67+
68+
while (
69+
$lastNameStartIndex > 0
70+
&& in_array(mb_strtolower($parts[$lastNameStartIndex - 1]), $surnamePrefixes, true)
71+
) {
72+
$lastNameStartIndex--;
73+
}
74+
75+
if ($lastNameStartIndex < $count - 1) {
76+
return $this->buildNameFromResult(
77+
new Result(
78+
true,
79+
implode(' ', array_slice($parts, 0, $lastNameStartIndex)),
80+
implode(' ', array_slice($parts, $lastNameStartIndex)),
81+
)
82+
);
83+
}
84+
85+
// at least we have only wild guessing :)
86+
$splitIndex = (int) ceil($count / 2);
87+
return $this->buildNameFromResult(
88+
new Result(
89+
true,
90+
implode(' ', array_slice($parts, 0, $splitIndex)),
91+
implode(' ', array_slice($parts, $splitIndex))
92+
)
93+
);
94+
}
95+
}

0 commit comments

Comments
 (0)