Xml set unparsed entity decl handler: примеры (PHP)

Работа с неразобранными объявлениями сущностей в XML через PHP
Раздел: XML
xml_set_unparsed_entity_decl_handler(resource parser, callable handler): bool

Описание функции xml_set_unparsed_entity_decl_handler

Функция xml_set_unparsed_entity_decl_handler() устанавливает обработчик для неразобранных объявлений сущностей в XML-документе. Она используется при работе с XML-парсером Expat в PHP, когда требуется обработка внешних или бинарных сущностей, которые парсер не может обработать самостоятельно.

Аргументы функции
  • parser (resource) - ссылка на XML-парсер, созданный функцией xml_parser_create()
  • handler (callable) - имя функции-обработчика, которая будет вызвана при обнаружении неразобранного объявления сущности
Функция-обработчик

Обработчик принимает шесть параметров:

  1. parser - ссылка на XML-парсер
  2. entity_name - имя сущности
  3. base - базовый URL для разрешения системного идентификатора
  4. system_id - системный идентификатор
  5. public_id - публичный идентификатор
  6. notation_name - имя нотации

Функция возвращает логическое значение: true при успешной установке обработчика, false при ошибке.

Примеры использования xml_set_unparsed_entity_decl_handler

Базовый пример с обработкой внешней сущности
<?php
function unparsedHandler($parser, $entity_name, $base, $system_id, $public_id, $notation_name) {
    echo "Обнаружена неразобранная сущность: $entity_name\n";
    echo "Системный идентификатор: $system_id\n";
    return true;
}

$xml_parser = xml_parser_create();
xml_set_unparsed_entity_decl_handler($xml_parser, 'unparsedHandler');

$xml = '<!DOCTYPE doc [
    <!ENTITY logo SYSTEM "logo.png" NDATA png>
]>
<doc>Пример документа</doc>';

xml_parse($xml_parser, $xml);
xml_parser_free($xml_parser);
?>
Обнаружена неразобранная сущность: logo
Системный идентификатор: logo.png
Пример с анонимной функцией
<?php
$xml_parser = xml_parser_create();

xml_set_unparsed_entity_decl_handler($xml_parser, 
    function($parser, $entity, $base, $system, $public, $notation) {
        file_put_contents('log.txt', 
            "[$entity] $system\n", 
            FILE_APPEND);
        return true;
    }
);

$xml = '<?xml version="1.0"?>
<!DOCTYPE article [
    <!ENTITY photo SYSTEM "image.jpg" NDATA jpeg>
]>
<article>Текст статьи</article>';

xml_parse($xml_parser, $xml);
?>
// Содержимое файла log.txt:
[photo] image.jpg

Альтернативные функции в PHP

DOMDocument

Класс DOMDocument предоставляет более современный и объектно-ориентированный подход к работе с XML. Он включает методы для работы с DTD и сущностями, автоматически обрабатывая многие аспекты разбора.

SimpleXML

SimpleXML упрощает работу с XML, преобразуя элементы в объекты. Этот модуль не предоставляет прямого доступа к обработке объявлений сущностей, но подходит для большинства задач чтения XML.

XMLReader

Класс XMLReader реализует pull-парсер с низким потреблением памяти. Он предоставляет метод setParserProperty() для настройки обработки сущностей, но без детального контроля над неразобранными объявлениями.

Выбор зависит от требований: Expat подходит для потоковой обработки больших файлов, DOMDocument — для манипуляций с деревом, SimpleXML — для простого доступа к данным, XMLReader — для последовательного чтения.

Альтернативы в других языках программирования

Python: xml.sax.handler.EntityDeclHandler
import xml.sax

class UnparsedHandler(xml.sax.handler.EntityDeclHandler):
    def unparsedEntityDecl(self, name, public_id, system_id, notation_name):
        print(f"Неразобранная сущность: {name}")
        print(f"Файл: {system_id}")
        return

parser = xml.sax.make_parser()
parser.setEntityDeclHandler(UnparsedHandler())
parser.feed('''<!DOCTYPE doc [
    <!ENTITY img SYSTEM "picture.png" NDATA png>
]>
<doc/>''')
Неразобранная сущность: img
Файл: picture.png
JavaScript: обработка через DOMParser
const parser = new DOMParser();
const xmlString = `<!DOCTYPE doc [
    <!ENTITY example SYSTEM "data.bin" NDATA bin>
]>
<doc>Содержимое</doc>`;

try {
    const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
    console.log(xmlDoc.doctype.internalSubset);
} catch(e) {
    console.error('Ошибка разбора:', e);
}
<!ENTITY example SYSTEM "data.bin" NDATA bin>
Java: SAXParser и EntityResolver
import org.xml.sax.*;

public class CustomEntityResolver implements EntityResolver {
    public InputSource resolveEntity(String publicId, String systemId) {
        System.out.println("Сущность: " + systemId);
        return null; // Парсер использует стандартное разрешение
    }
}

// Использование:
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
XMLReader reader = parser.getXMLReader();
reader.setEntityResolver(new CustomEntityResolver());

В отличие от PHP, Python и Java используют объектно-ориентированный интерфейс, а JavaScript работает через DOM API. PHP функция предоставляет более низкоуровневый контроль.

Типичные ошибки при использовании

Неверное имя обработчика
<?php
$parser = xml_parser_create();
$result = xml_set_unparsed_entity_decl_handler($parser, 'nonexistent_function');
// $result === false
?>
Попытка использования после начала разбора
<?php
$parser = xml_parser_create();
xml_parse($parser, '<root>');
// Следующая строка не будет иметь эффекта
xml_set_unparsed_entity_decl_handler($parser, 'handler');
?>
Отсутствие возвращаемого значения в обработчике
<?php
function handler($parser, $entity) {
    // Нет return
}

$parser = xml_parser_create();
xml_set_unparsed_entity_decl_handler($parser, 'handler');
// Может вызвать неопределенное поведение
?>
Использование несуществующего парсера
<?php
$result = xml_set_unparsed_entity_decl_handler(999, 'handler');
// Warning: xml_set_unparsed_entity_decl_handler() expects parameter 1 to be resource
?>

Для избежания ошибок следует проверять существование функций-обработчиков, устанавливать обработчики до начала разбора и всегда возвращать логическое значение из callback-функции.

Изменения в версиях PHP

PHP 8.0.0

Тип параметра parser изменен с resource на XMLParser (объект). Функции Expat теперь используют объектно-ориентированный интерфейс.

<?php
// До PHP 8.0
$parser = xml_parser_create();
xml_set_unparsed_entity_decl_handler($parser, 'handler');

// PHP 8.0 и выше
$parser = xml_parser_create();
xml_set_unparsed_entity_decl_handler($parser, 'handler');
// Тип $parser теперь XMLParser, а не resource
?>
PHP 7.3.0

Добавлена поддержка передача null в качестве обработчика для отключения ранее установленного обработчика.

<?php
$parser = xml_parser_create();
xml_set_unparsed_entity_decl_handler($parser, null);
// Отключает обработчик неразобранных сущностей
?>
Общие изменения

В современных версиях PHP расширение XML (Expat) сохраняет обратную совместимость, но рекомендуется использовать новые объектно-ориентированные интерфейсы для будущей совместимости.

Расширенные примеры использования

Обработка нескольких типов сущностей
Пример php
<?php
class EntityHandler {
    private $entities = [];
    
    public function handle($parser, $name, $base, $system, $public, $notation) {
        $this->entities[] = [
            'name' => $name,
            'system' => $system,
            'public' => $public,
            'notation' => $notation,
            'base' => $base
        ];
        
        if ($notation === 'png') {
            $this->processImage($system);
        }
        
        return true;
    }
    
    private function processImage($path) {
        // Логика обработки изображений
        echo "Обработка изображения: $path\n";
    }
    
    public function getStats() {
        return count($this->entities);
    }
}

$handler = new EntityHandler();
$parser = xml_parser_create();
xml_set_unparsed_entity_decl_handler($parser, [$handler, 'handle']);

$xml = '<!DOCTYPE data [
    <!ENTITY img1 SYSTEM "1.png" NDATA png>
    <!ENTITY img2 SYSTEM "2.jpg" NDATA jpeg>
    <!ENTITY sound SYSTEM "audio.mp3" NDATA mp3>
]>
<data/>';

xml_parse($parser, $xml);
echo "Найдено сущностей: " . $handler->getStats();
?>
Обработка изображения: 1.png
Найдено сущностей: 3
Валидация внешних ссылок
Пример php
<?php
function validateEntities($parser, $name, $base, $system, $public, $notation) {
    $allowed = ['png', 'jpg', 'gif'];
    
    if (!in_array($notation, $allowed)) {
        xml_parser_get_option($parser, XML_OPTION_CASE_FOLDING) ?
            trigger_error("Недопустимая нотация: $notation", E_USER_WARNING) : null;
        return false;
    }
    
    if (!filter_var($system, FILTER_VALIDATE_URL) && !file_exists($system)) {
        trigger_error("Недоступный ресурс: $system", E_USER_NOTICE);
    }
    
    return true;
}

$parser = xml_parser_create();
xml_set_unparsed_entity_decl_handler($parser, 'validateEntities');
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);

$xml = '<!DOCTYPE doc [
    <!ENTITY secure SYSTEM "https://example.com/image.png" NDATA png>
    <!ENTITY invalid SYSTEM "script.exe" NDATA exe>
]>
<doc/>';

xml_parse($parser, $xml);
?>
Warning: Недопустимая нотация: exe in ...
Кэширование внешних ресурсов
Пример php
<?php
$cache = [];

function cacheHandler($parser, $name, $base, $system, $public, $notation) {
    global $cache;
    
    $key = md5($system);
    if (!isset($cache[$key])) {
        $content = @file_get_contents($system);
        $cache[$key] = $content !== false ? $content : null;
        echo "Загружено в кэш: $system\n";
    } else {
        echo "Использован кэш для: $system\n";
    }
    
    return true;
}

$parser = xml_parser_create();
xml_set_unparsed_entity_decl_handler($parser, 'cacheHandler');

$xml = '<!DOCTYPE data [
    <!ENTITY bg SYSTEM "background.png" NDATA png>
    <!ENTITY logo SYSTEM "logo.png" NDATA png>
    <!ENTITY bg2 SYSTEM "background.png" NDATA png>
]>
<data/>';

xml_parse($parser, $xml);
?>
Загружено в кэш: background.png
Загружено в кэш: logo.png
Использован кэш для: background.png
Интеграция с системой логирования
Пример php
<?php
function loggingHandler($parser, $name, $base, $system, $public, $notation) {
    $logEntry = sprintf(
        "[%s] Entity: %s, System: %s, Notation: %s\n",
        date('Y-m-d H:i:s'),
        $name,
        $system,
        $notation
    );
    
    // Запись в несколько мест
    error_log($logEntry, 3, 'entities.log');
    
    if (strpos($system, 'http') === 0) {
        error_log($logEntry, 3, 'external_entities.log');
    }
    
    return true;
}

$parser = xml_parser_create();
xml_set_object($parser, new stdClass());
xml_set_unparsed_entity_decl_handler($parser, 'loggingHandler');

$xml = '<!DOCTYPE doc [
    <!ENTITY local SYSTEM "file.bin" NDATA binary>
    <!ENTITY remote SYSTEM "https://site.com/resource" NDATA data>
]>
<doc/>';

xml_parse($parser, $xml);
?>
// Содержимое entities.log:
[2024-01-15 10:30:00] Entity: local, System: file.bin, Notation: binary
[2024-01-15 10:30:00] Entity: remote, System: https://site.com/resource, Notation: data

PHP xml_set_unparsed_entity_decl_handler function comments

En
Xml set unparsed entity decl handler Set up unparsed entity declaration handler