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

时间:2016-04-29 20:29:40

标签: php regex

我正在编写一个从API获取数据的PHP应用程序(让我们称之为 A )并写入另一个(我会称之为)。我在特定领域苦苦挣扎:邮政编码。

API A 将所有邮政编码作为7位数字符串返回,不带任何分隔符。如果特定邮政编码少于7位,则会在左侧填充0(零)值。这样,50-224 - 来自波兰的邮政编码 - 变为0050224。我无法控制此输出,可能是以这种方式存储的。我知道这是波兰的邮政编码,因为回复还为我提供了国家/地区代码PL

问题是API B 验证邮政编码并需要正确的格式。

我发现PHP library on GitHub有一个正则表达式,其中包含每个国家/地区的邮政编码格式。像这样:resources/address_format/PL.json

我想要做的是使用该lib提供的表达式来格式化 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

4 个答案:

答案 0 :(得分:2)

您可以从正则表达式生成所有可能的组合。 Faker例如使用regexify格式化程序来完成它。

问题在于有效的邮政编码是可能匹配的子集。例如,美国5位数邮政编码正则表达式(\d{5})产生100,000个候选人,但只有(大约)43,000个5位邮政编码。

对我而言,这听起来像GIGO - Garbage In, Garbage Out的经典案例。您将获得一个非规范化数据点,并要求根据第一原则对其进行标准化。这很难。有时是不可能的。

如果我是你,我会从简单的格式列表开始,例如基于联合国列表的this one(如果原始版本处于离线状态,则为this one)。然后从输入中一次拉出一个字符,反之并匹配。我们来举个例子。

API A告诉您0001201是利比里亚。从列表中可以看出,利比里亚的格式为9999。反转这两个字符串:10210009999。现在一次走一个字符的格式,匹配。格式中的第一个字符是9,它是一个数字占位符。反转输入的第一个字符是数字吗?是的:1,请记住。好的,第二个角色。 90,零匹配,请记住它。重复,直到我们用完格式或输入,或者我们在格式上遇到不匹配。

在这个例子中,我们在输入数字之前用完了格式数字,我们不会'发现错误,发现反向输入1021与反转格式9999匹配。我们已完成,现在对匹配进行最后反转:1021变为1201,这是有效的利比里亚邮政编码。

答案 1 :(得分:1)

/*Try this out to format your postal code*/

/* preg_replace(pattern, Replacement,values) */
  $result = preg_replace('/(\d{3})(\d{3})$/', '$1-$2', '0050224');

 echo substr($result, 2);

// Out put : 050-224

点击指定的链接,了解有关preg_replace

的更多信息

答案 2 :(得分:1)

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

但是,由于您拥有"原始文本"的数字,您可以重新创建文本,以防这些特定数字是模式中缺少的唯一信息;例如,在您的波兰语示例\d{2}-\d{3}中,您可以将模式中的\ d {2}和{3}替换为来自api A的邮政编码的2位和3位数,并且该模式将为您提供额外的&# 34; - "

您无法重构的案例:

  • SO:" [A-Z] {2} []?\ d {5}"因为你没有收到api A的来信,所以你无法重建它们。
  • BR:" \ d {5} [\ - ]?\ d {3}"因为你没有从api A获得8位数。
  • 任何带有可选内容的东西,原因是,没有定义哪些选项是正确的。可能有几种有效的解决方案可能取决于特殊条件(例如,对于拥有超过10000间房屋的城市,您必须在\d{4}(-\d{3})?中使用额外的3位数字,或者您必须使用{{1} } -只在国家的首都或者你可以随意使用它。)这包括像\d{2}[-]?\d{2}这样的术语,因为长度可能取决于其他值。如果您的代码中允许使用前导0,则可能会遇到问题:输入\d{1-4}0000001101001可能是\ d {1-4}的正确解决方案(虽然我认为实践中的前导0只会以固定的长度发生);对于0001\d{4}(-\d{3})?可能意味着0001002(大城市)或0001-001(小城市)。

在这些(以及所有的tbh)案例中获取正确邮政编码的通常方法是在城市和街道名称的数据库中查找。 (您可以从本地邮政服务购买对此类数据库的访问权限,或者从例如openstreetmap-data创建数据库)。

话虽如此,这里是一些示例代码,它将重建仅缺少固定数字的数字的代码,例如PL(1001)。它也适用于像FK(" FIQQ 1ZZ")这样的模式,只要A的代码为" 0000001"。我认为它适用于约50%-60%的国家。

\d{2}-\d{3}

它将从模式的末尾开始用n个数字替换模式中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"; } 的每个出现。 如果它不了解模式(例如,因为它有可选的东西),您可能想尝试\d{n}。我不会使用它(并将其评论出来)因为它可以给你不可预测和错误的随机结果(见下面的波士顿市政厅的例子),但我添加它以防你想要使用它,因为你例如可以确保api A的客户端永远不会允许输入zip + 4代码。 作为最后一步,它将验证结果是否符合模式。

您可以轻松添加对preg_replace(单个数字)的支持。

您可以尝试添加对\d之类的字词的支持,例如检查api A有多少位数并且在其他术语中没有使用,并使用其余数字(例如\d{1-4}输入\d{2}-\d{1-4}有4位数,第一项使用2 {{ 1}}所以它有两个数字0001245左边,但请记住我上面写的内容:如果零是开头的允许数字,你可能得到错误的结果,例如\d{2},{{ 1}}或\d{1-4}可能是有效的结果(在这种情况下,如果不在数据库中查找城市名称,则无法恢复代码。)并且您将遇到00-1245的麻烦。

您应该添加最终检查,以查看数字位数是否适合A中的数字(例如,您可能希望将结果中的所有数字连接起来,并检查此字符串是否是由填充零的代码给出的代码)。这样可以防止您因例如由此引起的误解。 01-24512-34或其他可选内容。 例如,某人为波士顿市政厅输入\d{1-2}-\d{2-3} zip + 4代码,即preg_replace。你的api A会给你\d{1-2},或者更糟糕的是US,而02201-1020会给你02201102011020,这两者都是完全错误的( preg_replace可能是一个可以接受的妥协,但是在生成此结果时会遇到问题。)

然后,您可以使用随机代码为每个国家/地区运行一次,然后检查不起作用的模式。其中一些不起作用,因为代码不正确(例如20110只有在输入为11020时才会起作用,而对于随机输入通常不是这样。

如果幸运的话,你不需要这些国家。

或者,作为最后一次重新编写,您可以重写一些剩余的错误,但这需要一些手动工作:

某些模式将包含可选内容,例如02201。对于这些情况,您可以检查FK是否依赖于在某些数字或城市名称上,或者它是否真的是可选的。如果它确实是可选的,您必须决定是否需要0000001,然后将其保存为新模式,例如\d{2}[-]?\d{2}。但在大多数情况下,您无法进行常规替换,例如对于-,您可能会决定省略+4,但如果客户输入波士顿市政厅的(正确)zip + 4代码,您仍然无法获得正确的结果,请参阅示例上方。

对于其他模式,可能存在一些允许的可能性,例如: -。对于这种情况,您可以创建2种模式,例如\d{2}-\d{2}US。你可以做同样的事情,例如\d{4}|A-\d{3}并手动生成两种模式\d{4}A-\d{3}。然后,您必须为一个国家测试所有这些模式(将整个事物放在while循环中并为每个子模式执行它)并采用第一个适合的模式。如果模式使用A中的所有给定数字并完成最终模式测试,则模式将适合。虽然如果允许前导零,这通常会失败:输入\d{2}(-\d{2})?可能意味着\d{2}\d{2}-\d{2},因此如果允许零,则可能必须检查其他资源(以及类似的可能仍然会出现波士顿市政厅的问题)。但是这样你就可以重建更多的国家。

但是在大​​多数情况下,它们无法重写它们甚至无需手动生成特定的邮政编码而无需在数据库中查找。

答案 3 :(得分:0)

你可以用老式的方式来做,手工

将该库中的所有模式转储到文本文件中 修剪标点符号。将捕获组放在周围 部分用标点符号分隔。创建替代品。

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