PHP4 без XML-парсера
Мне тут по работе пришлось решить странную, по нынешним временам задачу — принимать XML в программе на PHP4.3+, где никакого парсера XML может не оказаться. Таковы реалии продукта и среды, где он будет использоваться (это код внешнего проекта). Поэтому мне пришлось написать небольшой парсер, который, правда, расcчитывает, что XML будет well-formed (я его получаю из доверенного источника).
<?
// Very Simple XML SAX Parser by Evgeny Stepanischev
// /
class VerySimple_XMLParser
{
var $saxCallback;
function VerySimple_XMLParser($saxCallback)
{
$this->saxCallback = $saxCallback;
}
function parse($xml)
{
if (preg_match_all('#<![CDATA[.*?]]>|</?[^>]*>|[^<]+#us', $xml, $matches)) {
foreach ($matches[0] as $match) {
$this->parseThis($match);
}
}
}
function parseThis($match)
{
$calls = array(
'<!--' => 'COMMENT',
'<?' => 'PI',
'<![CDATA[' => 'CDATA',
'<!' => 'DTD',
'</' => 'TAGCLOSE',
'<' => 'TAGOPEN',
);
foreach ($calls as $need => $type) {
if (strpos($match, $need) === 0) {
return $this->parseByType($type, $match);
}
}
return $this->parseByType('OTHER', $match);
}
function parseByType($type, $text)
{
if ($type == 'COMMENT') {
return $this->addChunk($type, substr($text, 4, -3));
}
if ($type == 'CDATA') {
return $this->addChunk('TEXT', substr($text, 9, -3));
}
if ($type == 'OTHER') {
return $this->addChunk('TEXT', $text);
}
if ($type == 'TAGCLOSE') {
$end = strcspn($text, ' >');
return $this->addChunk('TAGCLOSE', substr($text, 2, $end - 2));
}
if ($type == 'TAGOPEN') {
$this->addChunk('TAGOPEN', $this->parseTag($text));
if (substr($text, -2) == '/>') {
$end = strcspn($text, ' >');
return $this->addChunk('TAGCLOSE', substr($text, 1, $end - 2));
}
}
return '';
}
function parseTag($text)
{
$end = strcspn($text, ' />');
$name = substr($text, 1, $end - 1);
return array($name, $this->parseTagAttributes(substr($text, $end)));
}
function parseTagAttributes($text)
{
$nameStart = '(?:[A-Za-z_:]|[^\x00-\x7F])';
$nameChar = '(?:[A-Za-z0-9_:\.-]|[^\x00-\x7F])';
$attrValue = '(?:\'[^\']*\'|"[^"]*")';
if (preg_match_all("/{$nameStart}{$nameChar}*=$attrValue/us", $text, $matches)) {
return $this->parseAttributes($matches[0]);
}
return array();
}
function parseAttributes($rawAttributes)
{
$attributes = array();
foreach ($rawAttributes as $rawAttr) {
list($name, $value) = explode('=', $rawAttr, 2);
if ($value[0] == '"' || $value[0] == "'") {
$value = substr($value, 1, -1);
}
$attributes[$name] = $value;
}
return $attributes;
}
function addChunk($type, $arg)
{
call_user_func($this->saxCallback, $type, $arg);
}
}
// Пример использования
$xml =<<<XML
<!-- комментарий -->
<?xml version="1.0"?>
<root attribute="a.a" v="a">
<v><![CDATA[data]]></v>
<v>data</v>
<v/>
</root>
XML;
// Понятно, что можно написать класс и передать в качестве
// callback в парсер метод объекта, но я для простоты, как пример,
// написал очень простую функцию
function VerySimple_XMLParser_Collector()
{
static $tree = array();
if (func_num_args() == 2) {
$tree[] = func_get_args();
} else {
return $tree;
}
}
$sax = &new VerySimple_XMLParser('VerySimple_XMLParser_Collector');
$sax->parse($xml);
echo '<plaintext>';
var_dump(VerySimple_XMLParser_Collector());
В качестве иллюстрации я использую в коде крайне простую функцию, которая не строит дерево, а просто собирает вызовы в массив, но понятно, что собрать дерево тоже ничего не стоит.
expat был вроде с древнейших времен?
вот этот:
http://ru.php.net/manual/en/book.xml.php
мне его на юбых шаредах добавляли по просьбе
Комментарий для fantaseour.ya.ru:
«This extension is enabled by default. It may be disabled by using the following option at compile time: --disable-xml»
Я видел хостинги где оставляли какой-то минимум и не ставили потом ничего. Ничего, люди пользовались — цены невысокие, а парсить XML не всем нужно.
Комментарий для fantaseour.ya.ru:
Кстати, вообще-то можно запретить даже PCRE (до версии 5.3), но я не видел ни разу хостинга без него и, хочется верить, таких не будет. :)
Я тут для интересно посмотрел 27 хостеров и выписал какие XML-модули там установлены. Это, конечно, не показатель (тем более, что у большинства PHP5), но раз уж собрал статистику, пусть будет:
DOM/XML — 100%
libxml — 96%
SimpleXML — 92%
xml — 96%
xmlreader — 89%
Комментарий для Евгения Степанищева:
http://spectator.ru/technology/php/simple_XML
:D
Комментарий для hshhhhh.name:
Это псевдоXML, а у меня не псевдо.
Комментарий для fantaseour.ya.ru:
Коля Мациевский ( http://sunnybear.habrahabr.ru/ ) подтвердил (а он сталкивается по работе с самыми странными хостингами), что на наличие XML-парсера лучше не рассчитывать.
Комментарий для Евгения Степанищева:
ну у него наверное выборка по-больше. однако еще раз скажу, -- там, где этот expat был выключен, даже на каком-то замшелом французском хостинге со стабильным дебианом и мохнатой четверкой. По первой же вежливой просьбе мне его добавляли. Может Коля просто не просил?
кстати в 4-ке был XSLT, в виде модуля sablotron, который был очень капризный и ставился не легко. А я как раз тогда млел от XML-XSLT и написал какой-то усеченный процессинг, которого мне хватало, как раз пользуясь expat. И так потом оно довольно долго за мной ездило и требовало expat на любом шареде. И если бы этот expat не ставили по первой же просьбе, я бы с этого костыля слез значительно раньше.
я решил бросить экстремальный бизнес по разработке сайтов для шаред хостингов. надеюсь, что в ближайшем будущем буду работать с php 5.3 и иногда с досадой пользоваться более ранними версиями php5. Хотя может быть это у меня слишком радужные надежды :)
sax парсер кмк незаменим при защите от XSS, как у Кукуца когда-то было в SafeHTML
Комментарий для fantaseour.ya.ru:
Не просят Колины клиенты, а не Коля.
Я тоже вхожу в ряд авторов SafeHTML :) Там использовался HTMLSax3 из PEAR, он намного сложнее моего парсера и может разбирать даже очень покорёженный HTML.