【问题标题】:How to check if string is a valid XML element name?如何检查字符串是否是有效的 XML 元素名称?
【发布时间】:2011-01-31 23:13:37
【问题描述】:

我需要一个正则表达式或 PHP 中的函数来验证一个字符串是否是一个好的 XML 元素名称。

形成 w3schools:

XML 元素必须遵循这些命名 规则:

  1. 名称可以包含字母、数字和其他字符
  2. 名称不能以数字或标点符号开头
  3. 名称不能以字母 xml(或 XML、Xml 等)开头
  4. 名称不能包含空格

我可以编写一个基本的正则表达式来检查规则 1,2 和 4,但它不会考虑所有允许的标点符号,也不会考虑第三条规则

\w[\w0-9-]

友情更新

这里是well-formed XML Element names的更权威来源:

名称和令牌

NameStartChar   ::=
    ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] |
    [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | 
    [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | 
    [#x10000-#xEFFFF]

NameChar    ::=
    NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]

Name    ::=
    NameStartChar (NameChar)*

还指定了一个单独的非标记化规则:

以字符串“xml”开头的名称,或任何匹配 (('X'|'x') ('M'|'m') ('L'|'l')) 的字符串,是保留用于本规范本版本或未来版本的标准化。

【问题讨论】:

  • 您真的从 w3schools 获得了这份名单吗?规则#1 措辞很糟糕;除了字母和数字,XML 名称中只允许使用极少数的标点符号。
  • 我认为约束列表在this page (XML.com) 上有更好的解释。
  • 您可能需要根据 W3C 的实际规范(不隶属于 w3schools)仔细检查 w3schools(已知在他们的网站上有很多事实错误)声明:w3.org/TR/REC-xml/#dt-element

标签: php xml regex


【解决方案1】:

XML、xml 等是有效的标签,它们只是“保留用于本规范的这个或未来版本的标准化”,这可能永远不会发生。请在https://www.w3.org/TR/REC-xml/查看真实标准。 w3school 的文章不准确。

【讨论】:

    【解决方案2】:

    尽管问题很老,但到目前为止,这一点一直被忽略:通过 PHP 的 pcre 函数进行名称验证,这些函数已通过 XML 规范进行了简化。

    XML 的定义非常清楚其规范中的元素名称 (Extensible Markup Language (XML) 1.0 (Fifth Edition)):

    [4]  NameStartChar  ::=   ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
    [4a] NameChar       ::=   NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
    [5]  Name           ::=   NameStartChar (NameChar)*
    

    此表示法可以转换为与preg_match 一起使用的 UTF-8 兼容正则表达式,这里作为要逐字复制的单引号 PHP 字符串:

    '~^[:A-Z_a-z\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\x{2FF}\\x{370}-\\x{37D}\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}\\x{10000}-\\x{EFFFF}][:A-Z_a-z\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\x{2FF}\\x{370}-\\x{37D}\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}\\x{10000}-\\x{EFFFF}.\\-0-9\\xB7\\x{0300}-\\x{036F}\\x{203F}-\\x{2040}]*$~u'
    

    或者以更易读的方式作为具有命名子模式的另一个变体:

    '~
    # XML 1.0 Name symbol PHP PCRE regex <http://www.w3.org/TR/REC-xml/#NT-Name>
    (?(DEFINE)
        (?<NameStartChar> [:A-Z_a-z\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\x{2FF}\\x{370}-\\x{37D}\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}\\x{10000}-\\x{EFFFF}])
        (?<NameChar>      (?&NameStartChar) | [.\\-0-9\\xB7\\x{0300}-\\x{036F}\\x{203F}-\\x{2040}])
        (?<Name>          (?&NameStartChar) (?&NameChar)*)
    )
    ^(?&Name)$
    ~ux'
    

    请注意,此模式包含冒号 :,出于 XML 命名空间验证的原因(例如,对 NCName 的测试),您可能希望排除该冒号(第一个模式中有两个外观,第二个出现一个)。

    用法示例:

    $name    = '::...';
    $pattern = '~
    # XML 1.0 Name symbol PHP PCRE regex <http://www.w3.org/TR/REC-xml/#NT-Name>
    (?(DEFINE)
        (?<NameStartChar> [:A-Z_a-z\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\x{2FF}\\x{370}-\\x{37D}\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}\\x{10000}-\\x{EFFFF}])
        (?<NameChar>      (?&NameStartChar) | [.\\-0-9\\xB7\\x{0300}-\\x{036F}\\x{203F}-\\x{2040}])
        (?<Name>          (?&NameStartChar) (?&NameChar)*)
    )
    ^(?&Name)$
    ~ux';
    
    $valid = 1 === preg_match($pattern, $name); # bool(true)
    

    不可能以XML(小写或大写字母)开头的元素名称的说法是不正确的。 &lt;XML/&gt; 是一个格式完美的 XML,XML 是一个格式完美的元素名称。

    只是这些名称位于格式良好的元素名称的子集中,为标准化保留(XML 版本 1.0 及更高版本)。很容易测试一个(格式良好的)元素名称是否通过字符串比较保留:

    $reserved = $valid && 0 === stripos($name, 'xml'));
    

    或者另一个正则表达式:

    $reserved = $valid && 1 === preg_match('~^[Xx][Mm][Ll]~', $name);
    

    PHP's DOMDocument 可以测试保留名称至少我不知道如何做到这一点,而且我一直在寻找。

    一个有效的元素名称需要一个唯一元素类型声明,这似乎超出了这里的问题范围,因为没有提供这样的声明。因此,答案没有考虑到这一点。如果有元素类型声明,您只需要针对所有(区分大小写)名称的白名单进行验证,因此这将是一个简单的区分大小写的字符串比较。


    游览:DOMDocument 与正则表达式有何不同?

    DOMDocument / DOMElement 相比,有效元素名称的限定存在一些差异。 DOM 扩展处于某种混合模式,这使得它验证的内容难以预测。以下短途旅行说明了该行为并展示了如何控制它。

    让我们以$name 实例化一个元素:

    $element = new DOMElement($name);
    

    结果取决于:

    所以第一个字符决定了比较模式。

    正则表达式专门编写了要检查的内容,这里是 XML 1.0 Name 符号。

    您可以使用DOMElement 在名称前加上冒号来达到同样的效果:

    function isValidXmlName($name)
    {
    
        try {
            new DOMElement(":$name");
            return TRUE;
        } catch (DOMException $e) {
            return FALSE;
        }
    }
    

    要显式检查QName,可以通过将其转换为PrefixedName 来实现,以防它是UnprefixedName

    function isValidXmlnsQname($qname)
    {
        $prefixedName = (!strpos($qname, ':') ? 'prefix:' : '') . $qname;
    
        try {
            new DOMElement($prefixedName, NULL, 'uri:ns');
            return TRUE;
        } catch (DOMException $e) {
            return FALSE;
        }
    }
    

    【讨论】:

      【解决方案3】:

      如果您想创建valid XML,请使用DOM Extension。这样您就不必担心任何正则表达式。如果您尝试在 DomElement 中输入无效名称,则会收到错误消息。

      function isValidXmlName($name)
      {
          try {
              new DOMElement($name);
              return TRUE;
          } catch(DOMException $e) {
              return FALSE;
          }
      }
      

      这会给

      var_dump( isValidXmlName('foo') );      // true   valid localName
      var_dump( isValidXmlName(':foo') );     // true   valid localName
      var_dump( isValidXmlName(':b:c') );     // true   valid localName
      var_dump( isValidXmlName('b:c') );      // false  assumes QName
      

      并且对于您想做的事情可能已经足够了。

      迂腐注解 1

      注意localName 和QName 之间的区别。如果冒号前有前缀,则 ext/dom 假定您正在使用命名空间元素,这会限制名称的形成方式。从技术上讲,b:b 是一个有效的本地名称,因为NameStartChar is part of NameChar。如果要包含这些,请将函数更改为

      function isValidXmlName($name)
      {
          try {
              new DOMElement(
                  $name,
                  null,
                  strpos($name, ':') >= 1 ? 'http://example.com' : null
              );
              return TRUE;
          } catch(DOMException $e) {
              return FALSE;
          }
      }
      

      迂腐笔记2

      请注意,元素可能以“xml”开头。 W3schools(不隶属于 W3c)显然把这部分弄错了(wouldn't be the first time)。如果你真的想排除以xml开头的元素添加

      if(stripos($name, 'xml') === 0) return false;
      

      try/catch 之前。

      【讨论】:

      • 这会引入大量开销来检查元素名称。当我准备好进行实际的 XML 处理时,我会使用 DOM 对象。
      • @xsaero00 好吧,首先:我们通常不会对我们不接受的所有答案投反对票。给出的所有答案都包含解决问题的有效方法。其次,我已经将我的解决方案(包括 strpos)与公认的解决方案进行了基准测试,顺便说一下,我的解决方案快了 250%。不信,自己做个基准测试。
      • 实际上,w3schools 关于不以“xml”开头的问题基本上是正确的(尽管其他细节错误)——这些名称是有效的,但规范特别保留;我知道的唯一合法用途是 xmlnsxmlns: 前缀,由 XML 命名空间规范定义为属性名称。
      【解决方案4】:

      下面的表达式应该匹配除 xml 之外的有效 unicode 元素名称。仍允许以 xml 开头或结尾的名称。这通过了@toscho 的äøñ 测试。我无法弄清楚正则表达式的一件事是扩展程序。 xml 元素名称规范说:

      [4] NameChar ::= 字母 |数字 | '。' | '-' | '_' | ':' | 组合字符 |扩展器

      [5] 姓名 ::= (字母 | '_' | ':') (NameChar)*

      但是对于包含扩展器的 unicode 类别或类没有明确的定义。

      ^[\p{L}_:][\p{N}\p{L}\p{Mc}.\-|:]*((?<!xml)|xml)$
      

      【讨论】:

        【解决方案5】:

        如果您使用的是 DotNet 框架,请尝试 XmlConvert.VerifyName。它会告诉您名称是否有效,或者使用 XmlConvert.EncodeName 将无效名称实际转换为有效名称...

        【讨论】:

          【解决方案6】:

          使用这个正则表达式:

          ^_?(?!(xml|[_\d\W]))([\w.-]+)$

          这匹配您所有的四个点并允许使用 unicode 字符。

          【讨论】:

          • 这不会转义 '.' (句点/句号)元字符。
          【解决方案7】:

          怎么样

          /\A(?!XML)[a-z][\w0-9-]*/i
          

          用法:

          if (preg_match('/\A(?!XML)[a-z][\w0-9-]*/i', $subject)) {
              # valid name
          } else {
              # invalid name
          }
          

          解释:

          \A  Beginning of the string
          (?!XML)  Negative lookahead (assert that it is impossible to match "XML")
          [a-z]  Match a non-digit, non-punctuation character
          [\w0-9-]*  Match an arbitrary number of allowed characters
          /i  make the whole thing case-insensitive
          

          【讨论】:

          • 这与从 XML 1.1 开始是有效的 Nmtoken 的 不匹配。见w3.org/TR/xml11/#sec-common-syn
          • 这个表达式加上一些 unicode 的 mod 加上 filter_var() 应该可以完成这项工作。谢谢。
          • 这也没有提到'.' (句点/句号),这在 XML 元素名称中也有效。
          • 对于正则表达式中的 unicode,\p{L} 用于字母,\p{N} 用于数字。它们应该匹配 unicode 规范认为的所有字母或数字。这可能与 xml 1.1 考虑字母/数字不同,我对规范了解不够
          【解决方案8】:
          if (substr(strtolower($text), 0, 3) != 'xml') && (1 === preg_match('/^\w[^<>]+$/', $text)))
          {
              // valid;
          }
          

          【讨论】:

            【解决方案9】:

            这应该可以大致满足您的需求[假设您使用的是 Unicode]:
            注意:这是完全未经测试的。)

            [^\p{P}xX0-9][^mMlL\s]{2}[\w\p{P}0-9-]
            

            \p{P} 是 PHP 正则表达式语法中 Unicode Punctuation marks 的语法。

            【讨论】:

            • 除其他问题外,它不会匹配以“x”开头或第二个或第三个字符为“m”或“l”的任何内容。这不仅仅是“xml”。
            • @Alan;非常有效的观点。你能用消极的前瞻来代替吗? (更多的是出于好奇。戈登的方式比我临时发布的要好得多。)
            • 没错。 @Mef 的回答有其自身的问题,但它演示了如何在该部分工作中使用前瞻。
            猜你喜欢
            • 2020-10-15
            • 1970-01-01
            • 2011-11-08
            • 2018-03-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-02-13
            • 1970-01-01
            相关资源
            最近更新 更多