11 private const STR_DELIMITER_PLACEHOLDER =
'#S#';
12 private const REGEX_COMMA_AMONG_EMPTY_SPACE =
'\\s*,\\s*';
13 private const REGEX_GROUP_DELIMITER =
'(\\"([^"\\\\]*|\\\\"|\\\\\\\\|\\\\)*")';
14 private const REGEX_GROUP_FIELD_TEXT = self::REGEX_GROUP_DELIMITER;
15 private const REGEX_GROUP_FIELD_NAME =
'([a-zA-Z][a-zA-Z_0-9]*(:(NU|UN|N|U))?)';
16 private const REGEX_GROUP_FIELD_LIST_END =
'\\s*\\]';
17 private const REGEX_GROUP_END = self::REGEX_GROUP_FIELD_LIST_END;
18 private const REGEX_PART_FROM_DELIMITER_TO_FIELD_LIST =
'\\s*,\\s*\\[\\s*';
19 private const REGEX_GROUP_PART_BEFORE_FIELDS =
20 '(([^\\[\\\\]|\\\\\\[|\\\\\\\\)*)(\\[\\s*)("([^"\\\\]*|\\\\"|\\\\\\\\|\\\\)*")\\s*,\\s*\\[\\s*';
22 private const ERR_PARSE_GROUP_START_POSITION = 1100;
23 private const ERR_PARSE_GROUP_START = 1110;
24 private const ERR_PARSE_GROUP_DELIMITER = 1120;
25 private const ERR_PARSE_PART_FROM_DELIMITER_TO_FIELD_LIST = 1130;
26 private const ERR_PARSE_GROUP_FIELD_TEXT = 1140;
27 private const ERR_PARSE_GROUP_FIELD_NAME = 1150;
28 private const ERR_PARSE_GROUP_FIELD = 1160;
29 private const ERR_PARSE_GROUP_FIELD_LIST = 1170;
30 private const ERR_PARSE_GROUP_FIELD_LIST_DELIMITER = 1180;
31 private const ERR_PARSE_GROUP_FIELD_LIST_END = 1190;
32 private const ERR_PARSE_GROUP_END = 1200;
33 private const ERR_PARSE_GROUP = 1210;
36 private $template =
'';
47 public function __construct(
string $template,
string $delimiter,
bool $htmlEncode,
Format $format =
null)
49 $this->
template = $template;
50 $this->delimiter = $delimiter;
51 $this->htmlEncode = $htmlEncode;
52 $this->format = $format;
55 private function getErrorCodes():
array
57 static $errorMap =
null;
59 if ($errorMap !==
null)
65 $refClass = new \ReflectionClass(__CLASS__);
66 foreach ($refClass->getConstants() as
$name => $value)
68 if (substr(
$name, 0, 4) ===
'ERR_')
70 $errorMap[constant(
"self::{$name}")] =
$name;
81 $errCodes = $this->getErrorCodes();
82 foreach (
$context[
'error'][
'errors'] as $errInfo)
84 $result .=
"Error: {$errInfo['position']}, {$errCodes[$errInfo['code']]}" . PHP_EOL;
85 if (!empty($errInfo[
'info']) && is_array($errInfo[
'info']))
88 foreach($errInfo[
'info'] as $paramName => $paramValue)
91 if (is_string($paramValue))
93 $paramValue =
"\"{$paramValue}\"";
96 elseif (is_int($paramValue) || is_double($paramValue))
100 elseif (is_bool($paramValue))
102 $paramValue = $paramValue ?
'true' :
'false';
105 elseif (is_array($paramValue))
107 $paramValue =
'[...]';
110 elseif (is_object($paramValue))
112 $paramValue =
'{...}';
119 $result .=
" Error info:" . PHP_EOL;
122 $result .=
" {$paramName}: {$paramValue}" . PHP_EOL;
128 $result .=
'Template: "' . str_replace([
"\n",
"\""], [
'\\n',
'\\"'],
$context[
'template']) .
'"'
134 private function createContext():
array
180 private function unescapeText(
string $text): string
184 $length = strlen(
$text);
186 for (
$i = 0;
$i < $length;
$i++)
190 if (($length -
$i) > 1)
207 $delimiterStartPosition =
$context[
'position'];
211 '/' . self::REGEX_GROUP_DELIMITER .
'/msu',
215 $delimiterStartPosition
217 &&
$matches[0][1] === $delimiterStartPosition
221 'position' => $delimiterStartPosition,
222 'end' => $delimiterStartPosition + strlen(
$matches[0][0]),
223 'value' => $this->unescapeText(
226 $delimiterStartPosition + 1,
235 $this->addContextError(
$context, self::ERR_PARSE_GROUP_DELIMITER, $delimiterStartPosition);
243 $textBlockStartPosition =
$context[
'position'];
248 '/' . self::REGEX_GROUP_FIELD_TEXT .
'/msu',
254 &&
$matches[0][1] === $textBlockStartPosition
259 'position' => $textBlockStartPosition,
260 'end' => $textBlockStartPosition + strlen(
$matches[0][0]),
261 'value' => $this->unescapeText(
264 $textBlockStartPosition + 1,
273 $this->addContextError(
$context, self::ERR_PARSE_GROUP_FIELD_TEXT, $textBlockStartPosition);
279 private function splitFieldName(
string $fieldName):
array
281 $fieldParts = explode(
':', $fieldName);
282 $fieldName = $fieldParts[0] ??
'';
283 $fieldModifiers = $fieldParts[1] ??
'';
284 if (!is_string($fieldModifiers))
286 $fieldModifiers =
'';
289 return [$fieldName, $fieldModifiers];
296 private function isTemplateForFieldExists(
string $fieldName): bool
298 return $this->format && $this->format->getTemplate($fieldName) !==
null;
306 private function getFieldValueByTemplate(
string $fieldName, Address $address): ?string
308 if(!$this->isTemplateForFieldExists($fieldName))
313 $template = $this->format->getTemplate($fieldName)->getTemplate();
314 $templateConverter =
new StringTemplateConverter(
321 return $templateConverter->convert($address);
324 private function getAlterFieldValue(Address $address,
int $fieldType): string
327 $localityValue = is_string($localityValue) ? $localityValue :
'';
328 $result = $address->getFieldValue($fieldType);
330 if (
$result !==
'' && $localityValue !==
'')
332 $localityValueUpper = mb_strtoupper($localityValue);
333 $localityValueUpperLength = mb_strlen($localityValueUpper);
334 $targetValueUpper = mb_strtoupper(
$result);
335 $targetValueUpperLength = mb_strlen($targetValueUpper);
336 if ($targetValueUpperLength >= $localityValueUpperLength)
338 $targetValueSubstr = mb_substr(
340 $targetValueUpperLength - $localityValueUpperLength
342 if ($localityValueUpper === $targetValueSubstr)
352 private function getAddressFieldValue(Address $address,
string $fieldName,
string $fieldModifiers): string
358 $addressFieldType = constant(FieldType::class.
'::'.$fieldName);
360 if ($fieldName ===
'ADM_LEVEL_1' || $fieldName ===
'ADM_LEVEL_2')
363 $result = $this->getAlterFieldValue($address, $addressFieldType);
367 $result = $address->getFieldValue($addressFieldType);
372 $result = $this->getFieldValueByTemplate($fieldName, $address);
381 if (strpos($fieldModifiers,
'N') !==
false)
385 if (strpos($fieldModifiers,
'U') !==
false)
396 $fieldNameStartPosition =
$context[
'position'];
400 if (
$context[
'address'] instanceof Address
402 '/' . self::REGEX_GROUP_FIELD_NAME .
'/msu',
408 &&
$matches[0][1] === $fieldNameStartPosition
412 list($fieldName, $fieldModifiers) = $this->splitFieldName(
$matches[0][0]);
413 $fieldValue = $this->getAddressFieldValue(
$context[
'address'], $fieldName, $fieldModifiers);
416 'position' => $fieldNameStartPosition,
418 'modifiers' => $fieldModifiers,
419 'name' => $fieldName,
420 'value' => $fieldValue,
425 $this->addContextError(
$context, self::ERR_PARSE_GROUP_FIELD_NAME, $fieldNameStartPosition);
433 $markerStartPosition =
$context[
'position'];
438 '/' . self::REGEX_COMMA_AMONG_EMPTY_SPACE .
'/msu',
444 &&
$matches[0][1] === $markerStartPosition
451 $this->addContextError(
$context, self::ERR_PARSE_GROUP_FIELD_LIST_DELIMITER, $markerStartPosition);
459 $markerStartPosition =
$context[
'position'];
464 '/' . self::REGEX_GROUP_FIELD_LIST_END .
'/msu',
470 &&
$matches[0][1] === $markerStartPosition
477 $this->addContextError(
$context, self::ERR_PARSE_GROUP_FIELD_LIST_END, $markerStartPosition);
486 $fieldStartPosition =
$context[
'position'];
510 else if (
$context[
'info'][
'position'] > $fieldStartPosition)
513 $this->addContextError(
$context, self::ERR_PARSE_GROUP_START_POSITION, $fieldStartPosition);
521 $fieldInfo[
'isFieldListEnd'] =
false;
539 $fieldInfo[
'isFieldListEnd'] =
true;
546 $this->unshiftError(
$errors, self::ERR_PARSE_GROUP_FIELD, $fieldStartPosition);
559 $fieldListStartPosition =
$context[
'position'];
565 '/' . self::REGEX_PART_FROM_DELIMITER_TO_FIELD_LIST .
'/msu',
571 &&
$matches[0][1] === $fieldListStartPosition
575 $isFieldListEnd =
false;
576 while (!(
$context[
'hasError'] || $isFieldListEnd))
582 isset(
$context[
'info'][
'isFieldListEnd'])
583 &&
$context[
'info'][
'isFieldListEnd']
585 if (
$context[
'info'][
'value'] !==
'')
587 $fieldValues[] =
$context[
'info'][
'value'];
595 $context[
'info'] = [
'fieldValues' => $fieldValues];
600 $this->addContextError(
602 self::ERR_PARSE_PART_FROM_DELIMITER_TO_FIELD_LIST,
603 $fieldListStartPosition
609 $this->addContextError(
$context, self::ERR_PARSE_GROUP_FIELD_LIST, $fieldListStartPosition);
621 '/' . self::REGEX_GROUP_PART_BEFORE_FIELDS .
'/msu',
634 $this->addContextError(
$context, self::ERR_PARSE_GROUP_START,
$context[
'position']);
642 $markerStartPosition =
$context[
'position'];
647 '/' . self::REGEX_GROUP_END .
'/msu',
653 &&
$matches[0][1] === $markerStartPosition
660 $this->addContextError(
$context, self::ERR_PARSE_GROUP_END, $markerStartPosition);
668 $startSearchPosition =
$context[
'position'];
669 $groupStartPosition = 0;
670 $delimiterValue =
'';
681 $groupStartPosition =
$context[
'info'][
'groupStartPosition'];
690 $delimiterValue =
$context[
'info'][
'value'];
698 $fieldValues =
$context[
'info'][
'fieldValues'];
708 'position' => $groupStartPosition,
723 $this->addContextError(
725 self::ERR_PARSE_GROUP,
726 $startSearchPosition,
727 [
'groupStartPosition' => $groupStartPosition]
734 private function appendTextBlock(
array &$blocks,
int $position,
string $value)
736 $lastBlock = end($blocks);
737 $lastBlockIndex = key($blocks);
738 if (is_array($lastBlock) && $lastBlock[
'type'] ===
'text')
740 $blocks[$lastBlockIndex][
'value'] .=
$value;
741 $blocks[$lastBlockIndex][
'length'] += strlen($value);
747 'position' => $position,
748 'length' => strlen($value),
754 private function appendGroupBlock(
array &$blocks,
int $position,
string $value)
758 'position' => $position,
759 'length' => strlen($value),
770 'position' => $position,
780 $context[
'error'][
'position'] = $position;
802 $templateLength = strlen(
$context[
'template']);
803 while (
$context[
'position'] < $templateLength)
805 $blockStartPosition =
$context[
'position'];
816 $errorInfo =
$context[
'error'][
'info'];
817 if (!empty($errorInfo)
818 && is_array($errorInfo)
819 && isset($errorInfo[
'groupStartPosition'])
820 && $errorInfo[
'groupStartPosition'] > $blockStartPosition)
822 $blockLength = $errorInfo[
'groupStartPosition'] - $blockStartPosition + 1;
829 $this->appendTextBlock(
832 substr(
$context[
'template'], $blockStartPosition, $blockLength)
835 $context[
'position'] = $blockStartPosition + $blockLength;
839 $groupStartPosition =
$context[
'info'][
'position'];
840 if ($groupStartPosition > $blockStartPosition)
842 $this->appendTextBlock(
848 $groupStartPosition - $blockStartPosition
853 if (
$context[
'info'][
'value'] !==
'')
855 $this->appendGroupBlock(
868 $context[
'info'] = [
'blocks' => $blocks];
886 foreach (
$context[
'info'][
'blocks'] as $block)
888 if ($block[
'type'] ===
'text')
890 $result .= $this->unescapeText($block[
'value']);
903 array_filter(
$result,
function ($part) {
904 return ($part !==
'');
909 array_walk(
$result,
function (&$part) {