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

时间:2010-03-25 22:20:57

标签: php xml regex

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

表格w3schools:

  

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

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

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

\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')),是保留用于本规范的此版本或未来版本中的标准化。

10 个答案:

答案 0 :(得分:20)

如果您想创建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 add

开头的元素
if(stripos($name, 'xml') === 0) return false;

try/catch

之前

答案 1 :(得分:14)

到目前为止,这已经被遗漏了,尽管事实上问题是旧的:通过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开头的元素名称(以小写或大写字母开头)的说法不正确。 <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.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; } }

{{1}}

答案 2 :(得分:8)

怎么样

/\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

答案 3 :(得分:1)

受到mef好答案的启发,但结尾为'$'(否则将接受包含'aaa bbb'等空格的XML名称)

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

答案 4 :(得分:0)

使用此正则表达式:

  

<强> ^ _((XML |?![_ \ d \ W]))([\瓦特.-] +)$

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

答案 5 :(得分:0)

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

答案 6 :(得分:0)

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

  

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

     

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

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

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

答案 7 :(得分:0)

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

答案 8 :(得分:-1)

这应该大致给出你需要的东西[假设你正在使用Unicode]:
注意:这是完全未经测试的。)

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

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

答案 9 :(得分:-3)

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