Php 如何基于正则表达式格式化字符串?

Php 如何基于正则表达式格式化字符串?,php,regex,Php,Regex,我正在编写一个PHP应用程序,它从一个API(我们称之为a)获取数据,然后写入另一个API(我称之为B)。我正在为一个特定的领域而挣扎:邮政编码 APIA以7位字符串的形式返回所有邮政编码,不带任何分隔符。如果特定邮政编码的位数少于7位,则它会在值的左侧填充0(零)。这样,50-224(波兰邮政编码)就变成了0050224。我无法控制这个输出,它可能是这样存储的。我知道那是波兰的邮政编码,因为回复中还提供了国家代码,PL 问题在于APIB验证邮政编码并要求正确的格式 我找到了一个正则表达式,其中

我正在编写一个PHP应用程序,它从一个API(我们称之为a)获取数据,然后写入另一个API(我称之为B)。我正在为一个特定的领域而挣扎:邮政编码

APIA以7位字符串的形式返回所有邮政编码,不带任何分隔符。如果特定邮政编码的位数少于7位,则它会在值的左侧填充
0
(零)。这样,
50-224
(波兰邮政编码)就变成了
0050224
。我无法控制这个输出,它可能是这样存储的。我知道那是波兰的邮政编码,因为回复中还提供了国家代码,
PL

问题在于APIB验证邮政编码并要求正确的格式

我找到了一个正则表达式,其中包含每个国家的邮政编码格式。像这样:

我要做的是使用该库提供的表达式格式化A返回的值

我当前的代码如下所示:

use CommerceGuys\Addressing\Repository\AddressFormatRepository;

$country = 'US';
$postalcode = '0031401';
$repo = new AddressFormatRepository();
$pattern = $repo
    ->get($country)
    ->getPostalCodePattern()
    ;
$postalcode = preg_replace(
    '/^.*(' . $pattern . ')$/',
    '$1',
    $potalcode
);
对于上面的美国邮政编码,它可以正常工作,因为代码的第二部分在表达式中是可选的:
(\d{5})(?:[\-](\d{4}))?
。当其他国家出现时,我开始出现问题,特别是在邮政编码有字母和数字以外的字符的国家

顺便说一句,我已经在S.O.上看了几个问题,但是,它们似乎都没有问我想要实现什么

更新

尽管上面有波兰的例子,我的代码应该适用于任何国家。我只是想提供一些我想做的事情的背景资料。正如我在问题标题中所说的,我希望利用寻址库中的正则表达式

其他国家还有几个例子:

Country | Postal code
--------+------------
PH      | 0002010
LB      | 0001201
JO      | 0000962

单击给定链接以获取有关

的详细信息。您可以从正则表达式生成所有可能的组合。例如,使用它的
regexify
格式化程序执行

问题是,有效的邮政编码是可能匹配项的子集。例如,美国5位邮政编码正则表达式(
\d{5}
)生成100000个候选项,但只有(大约)5位邮政编码

对我来说,这听起来像是一个典型的犯罪案例。您将获得一个非规范化的数据点,并要求您根据第一原理对其进行规范化。这很难。有时是不可能的

如果我是你,我会从一个简单的格式列表开始,比如(或者如果原件是离线的)基于联合国的列表。然后从输入中一次提取一个字符,以相反的方式进行匹配。让我们举个例子

API A告诉您
0001201
是利比里亚。从列表中,您可以看到利比里亚的格式是
9999
。分别反转这两个字符串:
1021000
9999
。现在一次遍历一个字符的格式,匹配。格式中的第一个字符是
9
,它是一个数字占位符。反向输入的第一个字符是数字吗?是:
1
,记住这一点。好的,第二个字符
9和
0,零匹配,所以记住它。重复此操作,直到格式或输入用完,或者格式不匹配


在本例中,我们将在输入数字之前用完格式数字,并且我们不会遇到错误,发现反向输入
1021
与反向格式
9999
匹配。我们完成了,现在对比赛做最后一个倒转:
1021
变成了
1201
,这是一个有效的利比里亚邮政编码。

你可以用老式的方式手工完成

将该库中的所有模式转储到文本文件中。
删掉标点符号。将捕获组放置在周围
用标点符号分隔的部分。创建一个替换

Country            Regex Validation         Regex Conversion
                                                  Find               Replace
---------------------------------------------------------------------------------
NL Netherlands     \d{4}[ ][A-Z]{2}         (\d{4})([A-Z]{2})$        $1 $2         
 9999 AA

NI Nicaragua       \d{3}-\d{3}-\d           (\d{3})(\d{3})(\d)$       $1-$2-$3
 999-999-9

US United States   \d{5}                    (\d{5})$                  $1
 99999

SH Saint Helena    [A-Z]{4}[ ]\d[A-Z]{2}    ([A-Z]{4})(\d[A-Z]{2})$   $1 $2
 TDCU 1ZZ 

JM Jamaica         [A-Z]{5}\d{2}            ([A-Z]{5}\d{2})$          $1
 JMAAA99

正如其他人所指出的,没有从正则表达式获取原始文本的通用方法,因为通常有很多可能性

但是,由于您拥有“原始文本”的数字,因此可以重新创建文本,以防这些特定数字是模式中丢失的唯一信息;e、 例如,在您的波兰示例
\d{2}-\d{3}
中,您可以将模式中的\d{2}和{3}替换为api A中的postalcode的2位和3位数字,并且该模式将为您提供额外的“-”

无法重建的案例示例:

  • 所以:“[A-Z]{2}[]?\d{5}”因为您无法从api A获取字母,所以无法重建它们
  • BR:“\d{5}[\-]?\d{3}”,因为您没有从api A获得8位数字
  • 任何带有可选内容的东西,因为没有定义这些选项中的哪一个是正确的。根据特殊情况,可能有几种有效的解决方案(例如,对于拥有10000多栋房屋或类似房屋的城市,您必须使用
    \d{4}(-\d{3})
    中的额外3位数字,或者您必须使用
    \d{2}[-]?\d{2}中的
    -
    仅用于州首府,或者您可以随意使用。)这包括
    \d{1-4}
    等术语,因为长度可能取决于其他值。如果代码中允许前导0,您可能会遇到问题:对于输入
    0000001
    1
    01
    001
    0001
    可能是\d{1-4}的正确解决方案(尽管我假设实际上前导0只会以固定长度出现);对于
    \d{4}(-d{3})
    0001002
    可能意味着
    0001-001
    (大城市)或
    1001
    (小城市)
在这些情况下(以及所有情况下,tbh)获得正确邮政编码的通常方法是按城市和街道名称在数据库中查找。(您可以从当地邮政服务处购买对此类数据库的访问权限,或使用openstreetmap数据等创建数据库)

Country Regex Validation Regex Conversion Find Replace --------------------------------------------------------------------------------- NL Netherlands \d{4}[ ][A-Z]{2} (\d{4})([A-Z]{2})$ $1 $2 9999 AA NI Nicaragua \d{3}-\d{3}-\d (\d{3})(\d{3})(\d)$ $1-$2-$3 999-999-9 US United States \d{5} (\d{5})$ $1 99999 SH Saint Helena [A-Z]{4}[ ]\d[A-Z]{2} ([A-Z]{4})(\d[A-Z]{2})$ $1 $2 TDCU 1ZZ JM Jamaica [A-Z]{5}\d{2} ([A-Z]{5}\d{2})$ $1 JMAAA99

use CommerceGuys\Addressing\Repository\AddressFormatRepository;

$country = 'PL';
$postalcodeA = '0031401';
$repo = new AddressFormatRepository();
$pattern = $repo
    ->get($country)
    ->getPostalCodePattern()
    ;

$ok = 1;
$pospattern = 0;
$posA = 0;
$postalcodeB = '';

while ( ($pospattern < strlen($pattern)) and ($ok==1) ) {
  $pospattern += 1;
  $charact = substr($pattern, -$pospattern,1);
  if (strcmp($charact,'}') == 0) {
    if (strcmp(substr($pattern, -$pospattern - 4, 3),'\d{') == 0) {
      $cnt = substr($pattern, -$pospattern - 1,1);
      $postalcodeB = substr($postalcodeA, -$posA - $cnt, $cnt) . $postalcodeB;
      $posA += $cnt;
      $pospattern += 4;
    } else { $ok = 0; }
  } elseif ( ctype_digit($charact) ) {
    if ( strcmp($charact,substr($postalcodeA,-$posA-1,1)) !== 0) {
      $ok = 0;
    }
    $postalcodeB = $charact . $postalcodeB;
    $posA += 1;
  } elseif ( preg_match('/[\(\)\[\]\{\}\$\?\\\]/', $charact) ) {
    $ok = 0;
  } else {
    $postalcodeB = $charact . $postalcodeB;
  }
}

# USE WITH CARE! READ INFO!
# if ($ok == 0) {
#  $postalcodeB = preg_replace(
#    '/^.*(' . $pattern . ')$/',
#        '$1',
#            $postalcodeA
#            );
#  if (strcmp($postalcodeA,$postalcodeB) !== 0) {
#    $ok = 1;
#  }
#}

if (!preg_match('/^' . $pattern . '$/', $postalcodeB)) {
  $ok = 0;
}

if (!$ok) {
  echo "Pattern ",$pattern," not supported or no match to ",$postalcodeA,"\r\n";
} else {
  echo "Pattern ",$pattern," ok: ",$postalcodeA," -> ",$postalcodeB,"\r\n";
}