【问题标题】:Regular expression for a language tag (as defined by BCP47)语言标签的正则表达式(由 BCP47 定义)
【发布时间】:2011-08-12 05:11:31
【问题描述】:

我需要BCP 47 定义的language tag 的正则表达式。

我知道完整的 BNF 语法可在 http://www.rfc-editor.org/rfc/bcp/bcp47.txt 获得,我可以用它来编写我自己的,但希望已经有一个。

【问题讨论】:

  • 哈哈。几周前我为自己做了这个,但不幸的是我的磁盘(这是一个全新的 SSD)死了。我再看看:)
  • @PaulPRO:语言标签绝对是常规的。
  • 如果您不需要像这样全面的正则表达式,而只是在寻找能够捕获大多数常用语言环境的东西check out this answer

标签: regex bnf


【解决方案1】:

看起来像这样:

^((?<grandfathered>(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|
i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|
cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?<language>
([A-Za-z]{2,3}(-(?<extlang>[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})
(-(?<script>[A-Za-z]{4}))?(-(?<region>[A-Za-z]{2}|[0-9]{3}))?(-(?<variant>[A-Za-z0-9]{5,8}
|[0-9][A-Za-z0-9]{3}))*(-(?<extension>[0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*
(-(?<privateUse>x(-[A-Za-z0-9]{1,8})+))?)|(?<privateUse>x(-[A-Za-z0-9]{1,8})+))$

这是生成它的代码(在 C# 中):

var regular = "(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)";
var irregular = "(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)";
var grandfathered = "(?<grandfathered>" + irregular + "|" + regular + ")";
var privateUse = "(?<privateUse>x(-[A-Za-z0-9]{1,8})+)";
var singleton = "[0-9A-WY-Za-wy-z]";
var extension = "(?<extension>" + singleton + "(-[A-Za-z0-9]{2,8})+)";
var variant = "(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3})";
var region = "(?<region>[A-Za-z]{2}|[0-9]{3})";
var script = "(?<script>[A-Za-z]{4})";
var extlang = "(?<extlang>[A-Za-z]{3}(-[A-Za-z]{3}){0,2})";
var language = "(?<language>([A-Za-z]{2,3}(-" + extlang + ")?)|[A-Za-z]{4}|[A-Za-z]{5,8})";
var langtag = "(" + language + "(-" + script + ")?" + "(-" + region + ")?" + "(-" + variant + ")*" + "(-" + extension + ")*" + "(-" + privateUse + ")?" + ")";
var languageTag = @"^(" + grandfathered + "|" + langtag + "|" + privateUse + ")$";

Console.WriteLine(languageTag);

我不能保证它的正确性(我可能打错字了),但它在附录 A 中的示例上运行良好。

根据您的环境,您可能需要删除命名的捕获组"?&lt;...&gt;"

【讨论】:

  • 太棒了,谢谢!在进行一些更改后与 PHP 一起工作得很好。我在问自己,那些“命名捕获组”是做什么的?据我所知,这不是 PHP 正则表达式语法中可用的功能,但我只是感兴趣。
  • @Tim 它只允许您按名称而不是索引访问组。例如match.Groups["variant"]
  • 我认为这不会正确解决“常规祖父”部分(它们将被解析为普通标签而不是它们的特殊代码)。在定义languageTag 期间,您需要将grandfathered 变量放在langtag 变量之前
  • @GrantPeters:你是对的;有 9 个项目匹配任一方式(art-lojban、cel-gaulish、no-bok、no-nyn、zh-guoyu、zh-hakka、zh-min、zh-min-nan、zh-xiang)。我会更新,所以祖父标签首先出现
  • TypeScript 抱怨 privateUse 捕获组名称被使用了两次。你能解决这个问题吗?
【解决方案2】:

适用于 PHP 的优化版本。

/^(?<grandfathered>(?:en-GB-oed|i-(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|t(?:a[oy]|su))|sgn-(?:BE-(?:FR|NL)|CH-DE))|(?:art-lojban|cel-gaulish|no-(?:bok|nyn)|zh-(?:guoyu|hakka|min(?:-nan)?|xiang)))|(?:(?<language>(?:[A-Za-z]{2,3}(?:-(?<extlang>[A-Za-z]{3}(?:-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(?:-(?<script>[A-Za-z]{4}))?(?:-(?<region>[A-Za-z]{2}|[0-9]{3}))?(?:-(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(?:-(?<extension>[0-9A-WY-Za-wy-z](?:-[A-Za-z0-9]{2,8})+))*)(?:-(?<privateUse>x(?:-[A-Za-z0-9]{1,8})+))?$/Di

【讨论】:

    【解决方案3】:

    Javascript 策略重复命名捕获组,因此您必须将 ?&lt;privateUse&gt; 的第二次使用更改为例如?&lt;privateUse1&gt;。编译为:

    /^((?<grandfathered>(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?<language>([A-Za-z]{2,3}(-(?<extlang>[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?<script>[A-Za-z]{4}))?(-(?<region>[A-Za-z]{2}|[0-9]{3}))?(-(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-(?<extension>[0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(?<privateUse>x(-[A-Za-z0-9]{1,8})+))?)|(?<privateUse1>x(-[A-Za-z0-9]{1,8})+))$/
    

    这是一种构造方法:

    let privateUseUsed = 0
    const privateUse = () => "(?<privateUse" + (privateUseUsed++) + ">x(-[A-Za-z0-9]{1,8})+)"
    const grandfathered = "(?<grandfathered>" +
          /* irregular */ (
            "en-GB-oed" +
              "|" + "i-(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)" +
              "|" + "sgn-(?:BE-FR|BE-NL|CH-DE)"
          ) +
          "|" + /* regular */ (
            "art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang"
          ) +
          ")"
    const langtag = "(" +
          "(?<language>" + (
            "([A-Za-z]{2,3}(-" +
              "(?<extlang>[A-Za-z]{3}(-[A-Za-z]{3}){0,2})" +
              ")?)|[A-Za-z]{4,8})"
          ) +
          "(-" + "(?<script>[A-Za-z]{4})" + ")?" +
          "(-" + "(?<region>[A-Za-z]{2}|[0-9]{3})" + ")?" +
          "(-" + "(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3})" + ")*" +
          "(-" + "(?<extension>" + (
            /* singleton */ "[0-9A-WY-Za-wy-z]" +
              "(-[A-Za-z0-9]{2,8})+)"
          ) +
          ")*" +
          "(-" + privateUse() + ")?" +
          ")"
    const languageTagReStr = "^(" + grandfathered + "|" + langtag + "|" + privateUse() + ")$";
    

    编辑:原来 ff 不支持命名的捕获组,所以你必须用.replace(/\?&lt;a-zA-Z&gt;/g, '') 将它们去掉,或者一开始就把它们排除在外:

    const grandfathered = "(" +
          /* irregular */ "(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)" +
          "|" +
          /* regular */ "(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)" +
          ")";
    const langtag = "(" +
          "(" + (
            "([A-Za-z]{2,3}(-" +
              "([A-Za-z]{3}(-[A-Za-z]{3}){0,2})" +
              ")?)|[A-Za-z]{4}|[A-Za-z]{5,8})"
          ) +
          "(-" + "([A-Za-z]{4})" + ")?" +
          "(-" + "([A-Za-z]{2}|[0-9]{3})" + ")?" +
          "(-" + "([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3})" + ")*" +
          "(-" + "(" + (
            /* singleton */ "[0-9A-WY-Za-wy-z]" +
              "(-[A-Za-z0-9]{2,8})+)"
          ) +
          ")*" +
          "(-" + "(x(-[A-Za-z0-9]{1,8})+)" + ")?" +
          ")";
    const languageTag = RegExp("^(" + grandfathered + "|" + langtag + "|" + "(x(-[A-Za-z0-9]{1,8})+)" + ")$");
    

    languageTag.test('en-us')测试

    【讨论】:

      【解决方案4】:

      如果使用基于 CLDR 的函数集,例如 PHP 的 intl 扩展,您可以使用以下函数检查 intl 数据库中是否存在语言环境:

      <?php
       function is_locale($locale=''){
        // STANDARDISE INPUT
        $locale=locale_canonicalize($locale);
      
        // LOAD ARRAY WITH LOCALES
        $locales=resourcebundle_locales(NULL);
      
        // RETURN WHETHER FOUND
        return (array_search($locale,$locales)!==F);
       }
      ?>
      

      加载和搜索数据大约需要半毫秒,因此不会对性能造成太大影响。

      当然,它只会在所使用的 PHP 版本提供的 CLDR 版本的数据库中找到那些,但会随着每个后续的 PHP 版本更新。

      【讨论】:

        猜你喜欢
        • 2013-11-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-20
        相关资源
        最近更新 更多