Как проверить, является ли строка допустимым именем элемента XML? - PullRequest
20 голосов
/ 26 марта 2010

Мне нужно регулярное выражение или функция в PHP, которая проверит строку как хорошее имя элемента XML.

Форма w3schools:

XML элементы должны следовать этим именам правила:

  1. Имена могут содержать буквы, цифры и другие символы
  2. Имена не могут начинаться с цифры или знака пунктуации
  3. Имена не могут начинаться с букв xml (или XML, или Xml и т. Д.)
  4. Имена не могут содержать пробелы

Я могу написать основное регулярное выражение, которое будет проверять правила 1,2 и 4, но оно не будет учитывать все разрешенные знаки препинания и не будет учитывать третье правило

\w[\w0-9-]

Дружественное обновление

Вот более авторитетный источник для правильно сформированных имен элементов XML :

Имена и токены

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 ]

20 голосов
/ 26 марта 2010

Если вы хотите создать допустимый XML , используйте расширение DOM . Таким образом, вам не нужно беспокоиться о Regex. Если вы попытаетесь ввести недопустимое имя для 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 является частью 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), очевидно, неправильно понял эту часть ( не в первый раз ). Если вы действительно хотите исключить элементы, начинающиеся с xml, добавьте

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

до try/catch.

14 голосов
/ 03 марта 2013

Это было упущено до сих пор, несмотря на тот факт, что вопрос старый: проверка имени с помощью PHP-функций pcre, которые оптимизированы с помощью спецификации XML.

Определение XML довольно ясно о названии элемента в его спецификациях ( Расширяемый язык разметки (XML) 1.0 (Пятое издание) ):

[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)*

Эта запись может быть преобразована в регулярное выражение, совместимое с UTF-8, для использования с preg_match, здесь как строка 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 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;
    }
}
8 голосов
/ 26 марта 2010

Как насчет

/\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
1 голос
/ 27 октября 2010

Вдохновленный mef хорошим ответом, но с и заканчивающимся '$' (в противном случае будут приниматься имена XML, содержащие пробелы типа 'aaa bbb')

$validXmlName = (preg_match('/^(?!XML)[a-z][\w0-9-]*$/i', $subject) != 0);
0 голосов
/ 03 марта 2016

XML, xml и т. Д. Являются допустимыми тегами, они просто «зарезервированы для стандартизации в этой или будущих версиях данной спецификации», что, вероятно, никогда не произойдет. Пожалуйста, проверьте настоящий стандарт на https://www.w3.org/TR/REC-xml/. Статья w3school является неточной.

0 голосов
/ 20 августа 2012

Выражение ниже должно соответствовать действительным именам элементов в юникоде, за исключением xml. Имена, которые начинаются или заканчиваются на xml, все равно будут разрешены. Это проходит тест @ toscho's. Единственное, для чего я не смог понять регулярное выражение, это расширители. Спецификация имени элемента xml гласит:

[4] NameChar :: = Letter | Цифра | '' | '-' | '_' | ':' | CombiningChar | Удлинитель

[5] Имя :: = (Буква | '_' | ':') (NameChar) *

Но нет четкого определения для категории юникода или класса, содержащего расширители.

^[\p{L}_:][\p{N}\p{L}\p{Mc}.\-|:]*((?<!xml)|xml)$
0 голосов
/ 07 августа 2012

Если вы используете платформу DotNet, попробуйте XmlConvert.VerifyName. Он сообщит вам, является ли имя действительным, или использует XmlConvert.EncodeName, чтобы фактически преобразовать недопустимое имя в действительное ...

0 голосов
/ 14 октября 2010

Используйте это регулярное выражение:

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

Это соответствует всем вашим четырем точкам и позволяет использовать символы Юникода.

0 голосов
/ 26 марта 2010

Это должно дать вам примерно то, что вам нужно [Предполагается, что вы используете Unicode]:
( Примечание: Это полностью не проверено.)

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

\p{P} - это синтаксис для Знаки пунктуации Unicode в синтаксисе регулярных выражений PHP.

0 голосов
/ 26 марта 2010
if (substr(strtolower($text), 0, 3) != 'xml') && (1 === preg_match('/^\w[^<>]+$/', $text)))
{
    // valid;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...