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

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

Форма w3schools:

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

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

Я могу написать базовое регулярное выражение, которое будет проверять правила 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)* 

Также указывается отдельное правило без токенизации:

Имена, начинающиеся со строки «xml» или с любой строкой, которая будет соответствовать (('X' | 'x') ('M' | 'm') ('L' | 'l')), зарезервированы для стандартизации в этой или будущих версиях этой спецификации.

Как насчет

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

Если вы хотите создать правильный 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 add

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

перед try/catch .

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

Определение XML довольно ясно указывает на имя элемента в его спецификациях ( Extensible Markup Language (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)* 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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