如何检查string是否是有效的XML元素名称?

我需要在PHP中的正则expression式或函数,将validationstring是一个很好的XML元素名称。

表格w3schools:

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

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

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

\w[\w0-9-] 

友好的更新

以下是格式正确的XML元素名称的更权威来源:

名称和标记

 NameStartChar ::= ":" | [AZ] | "_" | [az] | [#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)* 

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

名称以string“xml”开头,或与任何匹配(('X'|'x')('M'|'m')('L'|'l'))的string保留用于标准化在本规范的这个或者将来的版本中。

怎么样

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

用法:

 if (preg_match('/\A(?!XML)[az][\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") [az] Match a non-digit, non-punctuation character [\w0-9-]* Match an arbitrary number of allowed characters /i make the whole thing case-insensitive 

如果您想创build有效的XML ,请使用DOM扩展 。 这样你就不必担心任何正则expression式。 如果您尝试向DomElementinput无效的名称,则会出现错误。

 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是一个有效的本地名,尽pipe因为NameStartChar是NameChar的一部分 。 如果你想包括这些,改变function

 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)显然弄错了这部分( 不会是第一次 )。 如果你真的想排除从XML添加开始的元素

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

try/catch之前。

到目前为止,这个问题已经被遗漏了,尽pipe问题在于这个问题:通过PHP的pcre函数进行名称validation,这些函数是用XML规范简化的。

XML的定义非常清晰地描述了规范中的元素名称( 可扩展标记语言(XML)1.0(第五版) ):

 [4] NameStartChar ::= ":" | [AZ] | "_" | [az] | [#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兼容正则expression式,在这里作为单引号的PHPstring被逐字复制:

 '~^[: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名称空间validation原因(例如对NCName的testing)排除(第一个模式中的两个副本,第二个副本中的两个副本)。

用法示例:

 $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 (大写或小写)开头的元素名不可能是不正确的。 <XML/>是一个完美格式化的XML, XML是一个非常完美的元素名称。

只是这些名字是为标准化保留的格式良好的元素名称的子集(XML版本1.0及以上)。 很容易testing(格式良好的)元素名称是否与string比较保留:

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

或者另一个正则expression式:

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

PHP的DOMDocument 不能testing保留名称,至less我不知道如何做到这一点,我一直在寻找很多东西。

有效的元素名称需要唯一的元素types声明 ,这似乎超出了这个问题的范围,因为没有提供这样的声明。 因此,答案并没有考虑到这一点。 如果会有元素types声明,则只需要对所有(区分大小写)的名称进行validation,所以这将是一个简单的区分大小写的string比较。


游览: DOMDocument对正则expression式有什么不同?

DOMDocument / DOMElement相比,有效的元素名称有一些不同之处。 DOM扩展处于某种混合模式,这使得它不太可预测它的validation。 下面的游览说明了行为并展示了如何控制它。

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

 $element = new DOMElement($name); 

结果取决于:

  • 如果第一个字符是冒号,则只validationXML 1.0 Name符号 。
  • 如果第一个字符不是冒号,则validationXMLNS 1.0 QName符号

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

一个正则expression式是专门写什么来检查的,这里是XML 1.0的Name符号。

您可以通过在冒号前添加名称来达到与DOMElement相同的效果:

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

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

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

受到mef好的回答的启发,但是以'$'结尾,否则将接受包含'aaa bbb'之类空格的XML名称。

 $validXmlName = (preg_match('/^(?!XML)[az][\w0-9-]*$/i', $subject) != 0); 

使用这个正则expression式:

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

这匹配你所有的四个点,并允许unicode字符。

如果您正在使用DotNet框架,请尝试XmlConvert.VerifyName。 它会告诉你,如果名称是有效的,或使用XmlConvert.EncodeName实际上将一个无效的名称转换为有效的名称…

下面的expression式应该匹配除xml以外的有效unicode元素名称。 以xml开始或结束的名称仍将被允许。 这通过@ toscho'stesting。 我无法弄清楚正则expression式的一个原因是扩展。 XML元素名称规范说:

[4] NameChar :: = Letter | 数字| '' | ' – '| '_'| ':'| CombiningChar | 扩展

[5] Name :: =(Letter |'_'|':')(NameChar)*

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

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

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

这应该给你粗略的你需要[假设你使用Unicode]:
注意:这是完全未经testing的。)

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

\p{P}是PHP正则expression式语法中Unicode标点符号的语法。

 if (substr(strtolower($text), 0, 3) != 'xml') && (1 === preg_match('/^\w[^<>]+$/', $text))) { // valid; }