如何检查字符串是不是是有效的 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
(小写或大写字母)开头的元素名称的说法是不正确的。 <XML/>
是一个格式完美的 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.0Name
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”开头的问题基本上是正确的(尽管其他细节错误)——这些名称是有效的,但规范特别保留;我知道的唯一合法用途是xmlns
和 xmlns:
前缀,由 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 元素名称?的主要内容,如果未能解决你的问题,请参考以下文章