如何检查字符串是不是是有效的 XML 元素名称?

Posted

技术标签:

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

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

形成 w3schools:

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

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

我可以编写一个基本的正则表达式来检查规则 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 【参考方案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-\\x2FF\\x370-\\x37D\\x37F-\\x1FFF\\x200C-\\x200D\\x2070-\\x218F\\x2C00-\\x2FEF\\x3001-\\xD7FF\\xF900-\\xFDCF\\xFDF0-\\xFFFD\\x10000-\\xEFFFF][: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.\\-0-9\\xB7\\x0300-\\x036F\\x203F-\\x2040]*$~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-\\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)*)
)
^(?&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-\\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)*)
)
^(?&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 symbol。 如果第一个字符不是冒号,则验证XMLNS 1.0 QName symbol

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

正则表达式专门编写了要检查的内容,这里是 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 类别或类没有明确的定义。

^[\pL_:][\pN\pL\pMc.\-|:]*((?<!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() 应该可以完成这项工作。谢谢。 我添加了我的answer with an Unicode compatible PCRE regex。 这也没有提到'.' (句点/句号),这在 XML 元素名称中也有效。 对于正则表达式中的 unicode,\pL 用于字母,\pN 用于数字。它们应该匹配 unicode 规范认为的所有字母或数字。这可能与 xml 1.1 考虑字母/数字不同,我对规范了解不够【参考方案8】:
if (substr(strtolower($text), 0, 3) != 'xml') && (1 === preg_match('/^\w[^<>]+$/', $text)))

    // valid;

【讨论】:

【参考方案9】:

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

[^\pPxX0-9][^mMlL\s]2[\w\pP0-9-]

\pP 是 PHP 正则表达式语法中 Unicode Punctuation marks 的语法。

【讨论】:

除其他问题外,它不会匹配以“x”开头或第二个或第三个字符为“m”或“l”的任何内容。这不仅仅是“xml”。 @Alan;非常有效的观点。你能用消极的前瞻来代替吗? (更多的是出于好奇。戈登的方式比我临时发布的要好得多。) 没错。 @Mef 的回答有其自身的问题,但它演示了如何在该部分工作中使用前瞻。

以上是关于如何检查字符串是不是是有效的 XML 元素名称?的主要内容,如果未能解决你的问题,请参考以下文章

Java - 如何检查字符串是不是包含字符串数组的元素?

创建 XML 时如何有效地检查 NSDictionay 键是不是具有值

如何检查元素是不是有效 JSX

检查 JSON 和 XML 是不是有效? C#

如何检查字符串是不是包含int? -迅速

XML_03_XML文档类型定义DTD