Подтвердить что ты не робот

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

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

Форма w3schools:

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

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

Я могу написать основное регулярное выражение, которое будет проверять правила 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')), являются зарезервированных для стандартизации в этой или будущих версиях этой спецификации.

4b9b3361

Ответ 1

Как насчет

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

Ответ 2

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

Ответ 3

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

Определение XML довольно ясно о имени элемента в нем specs (Расширяемый язык разметки (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, здесь в виде строки с одной кавычкой, которая будет скопирована дословно:

'~^[: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.
  • если первый символ не является двоеточием, он подтверждает символ XMLNS 1.0 QName

Итак, первый символ принимает решение о режиме сравнения.

Регулярное выражение специально написано для того, что нужно проверить, вот символ 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;
    }
}

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

[4] NameChar:: = Letter | Цифры | '' | '-' | '_' | ':' | КомбинированиеЧары | Удлинитель

[5] Имя:: = (Letter | '_' | ':') (NameChar) *

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

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

Ответ 8

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

Ответ 9

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

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

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

Ответ 10

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