Комментарии 30
XML должен умереть, как формат плохо читаемый человеком, очень редундантный и требующий ненужный код для своей обработки.
рассмотрим как выглядели бы данные из примера в JSON, или например как принято в Питоне, просто в виде массива, потому что поля постоянные:
в таком формате данные легко парсятся стандартными средствама практически любого нормального языка.
тогда подсчитать сумму можно лаконично в одну строку виде оператора эквивалентного select- where
рассмотрим как выглядели бы данные из примера в JSON, или например как принято в Питоне, просто в виде массива, потому что поля постоянные:
array = [
[product, Some product name. ID:0, 0,0,],
[service, Some product name. ID:1, 1 , 15],
[product, Some product name. ID:2, 2 , 30],
...]
fields = ['type' , 'name' , 'gtg', 'price']
поле 'id' может по умолчанию быть положением элемента в массиве.
в таком формате данные легко парсятся стандартными средствама практически любого нормального языка.
тогда подсчитать сумму можно лаконично в одну строку виде оператора эквивалентного select- where
summ_products = summ(array[[0] == 'product' ,3 ])
-5
xml хорош самодокументированностью.
Например, что означает «30» в вашем json?
[product, Some product name. ID:2, 2, 30]
Например, что означает «30» в вашем json?
[product, Some product name. ID:2, 2, 30]
+1
Сделайте в JSON документ вложенностью 5 уровней и потом приходите рассказывайте, про плохую читаемость человеком.
Сделайте вложенность побольше. Потом сходите возьмите openapi и сделайте описание по его стандарту. Ой у нас получился xml.
в таком формате данные легко парсятся стандартными средствама практически любого нормального языка.
Сделайте вложенность побольше. Потом сходите возьмите openapi и сделайте описание по его стандарту. Ой у нас получился xml.
+1
Абсолютно согласен с тем, что XML — не всегда оптимальный вариант представления информации, но зачастую мы можем работать только с тем, что имеем по условию задачи, например, при работе со сторонним API и т.д.
0
Мне кажется создать классы под структуру было бы более удобно, — тогда бы работал автокомплит в IDE.
+2
- $structure: ассоциативный массив, полностью описывающий то, как мы должны работать с нашим файлом. Подразумевается, что его вид заранее известен, и мы точно знаем, с какими тегами и что мы должны делать.
Получается, что структура строго детерминирована и никаких вариантов не предусматривает…
А как насчет того, чтобы не задавать это жестко, а верифицировать XML по XSD и создавать структуру динамически?
+1
Хорошее решение. На самом деле, достаточно универсальное. Человек разобрал предметную область проблемы и дал общее решение. Не в каждом коде такое увидишь.
0
Не лучше ли будет подключить Serializer Component, создать на основе xsd схемы набор иммутабельных классов и потом десериализовать в них?
Да, там будет магия и много рефлексии, но на выходе вы получаете набор классов, с которыми можно работать, добавлять в них свою логику, вычислимые поля и прочее. А потом еще и сохранить через репозиторий в базу данных.
Единственный минус — если слишком большой xml, то все держать в памяти не самое лучшее решение. В таком случае ваше решение будет полезно.
Да, там будет магия и много рефлексии, но на выходе вы получаете набор классов, с которыми можно работать, добавлять в них свою логику, вычислимые поля и прочее. А потом еще и сохранить через репозиторий в базу данных.
Единственный минус — если слишком большой xml, то все держать в памяти не самое лучшее решение. В таком случае ваше решение будет полезно.
0
Как разобрать сложный XML-файл и не утонуть в собственном коде
Не увидел у вас сложных xml файлов, а вот ваш класс имеет отвратительный код (вложенность, длина метода, стиль кодирования, мусорный эхо дебаг внутри класса), и еще более сложную в чтении структуру с кэлбеками обработчиками.
ЗЫ длинную запись array
уже так давно не видел, спасибо понастальгировал)
+1
Да, я не стал приводить в пример 1С-Bitrix CommerceML2, сберегая время и нервы читателей, хотя для обработки именно 1С-овских файлов написал эту несложную надстройку, что впоследствии сэкономило мне время. Для понимания принципа это посчитал ненужным.
Дебаг оставил намеренно, его убрать несложно — это для тех, кто захочет проверить порядок выполнения.
Постарался реализовать все одним методом, даже в ущерб эстетике, но это критично по производительности.
Дебаг оставил намеренно, его убрать несложно — это для тех, кто захочет проверить порядок выполнения.
Постарался реализовать все одним методом, даже в ущерб эстетике, но это критично по производительности.
0
Добавил Release-вариант без комментариев и $debug. Спасибо.
0
закрытие root->b->x
нет там закрытия
Пусть есть XML-файл
<?xml version="1.0" encoding="UTF-8"?> <root> <a attr_1="123" attr_2="456">Abc</a> <b> <x>This is node <x> inside <b></x> </b> <c></c> <d> <x>This is node <x> inside <d></x> </d> <e></e> </root>
структура неправильная в плане вложенности тегов, и обрабатывается все не правильно
0
Dom если разобраться нормально, нормальный код можно написать.
Класс напомнил php 5.3
+1
Согласен, можно.
Я старался не загонять XML объектом или массивом в память, т.к. он может быть очень большим. Даже у XMLReader::xml() есть недостаток такого плана — строковый аргумент тела xml, хотя и меньший по объему хранения, нежели в виде объекта (на мой взгляд).
Однако и это обходится — можно, например, наш класс надстроить не над XMLReader, а написать аналогичный по функционалу парсер тегов низкого уровня (это не сложно), который будет читать не из строки, а порционно из файла, например.
Я старался не загонять XML объектом или массивом в память, т.к. он может быть очень большим. Даже у XMLReader::xml() есть недостаток такого плана — строковый аргумент тела xml, хотя и меньший по объему хранения, нежели в виде объекта (на мой взгляд).
Однако и это обходится — можно, например, наш класс надстроить не над XMLReader, а написать аналогичный по функционалу парсер тегов низкого уровня (это не сложно), который будет читать не из строки, а порционно из файла, например.
0
а чем DomDocument не угодил? там можно даже все не читать при желании, а парсить просто xPath-ом
-1
Код конечно отвратительный. Я удивляюсь почему люди до сих пор не пользуются тайп хинтами для аргументов
0
Как насчёт такого варианта?
<?php
class XMLReaderStruct extends XMLReader
{
/**
* @param string $source
* @param $structure
* @param string|null $encoding
* @param int $options
* @param callable|null $debug
* @return bool
*/
public function xmlStruct($source, $structure, $encoding = null, $options = 0, $debug = null)
{
$this->XML($source, $encoding, $options);
$stack = [];
$node = &$structure;
$skipToDepth = false;
while ($this->read()) {
switch ($this->nodeType) {
case self::ELEMENT:
if (false === $this->element($skipToDepth, $node, $stack, $debug)) {
return false;
}
break;
case self::TEXT:
if (false === $this->text($skipToDepth, $node, $debug)) {
return false;
}
break;
case self::END_ELEMENT:
if (false === $this->endElement($skipToDepth, $node, $debug)) {
return false;
}
break;
}
}
return true;
}
/**
* @param $skipToDepth
* @param $node
* @param $stack
* @param $debug
* @return bool
*/
private function element(&$skipToDepth, &$node, &$stack, $debug)
{
// Если текущая ветка не входит в структуру, то просто игнорируем открытие тегов, иначе смотрим: если текущий узел структуры содержит
// текущий тег, то открываем его, предварительно запоминая в стеке текущую позицию, чтобы при закрытии можно было вернуться. Если
// не содержит, то открываем режим пропуска, пока не встретим закрывающий тег с текущей глубиной.
if ($skipToDepth !== false) {
if ($debug) {
$debug("( Открытие ): {$this->name} - в режиме пропуска тегов.");
}
return true;
}
if (!isset($node[$this->name])) {
$skipToDepth = $this->depth;
if ($debug) {
$debug("[ Открытие ]: {$this->name} - не найден в структуре. Запуск режима пропуска тегов до достижения вложенности {$skipToDepth}.");
}
return true;
}
if ($debug) {
$debug("[ Открытие ]: {$this->name} - найден в структуре. Спуск по структуре.");
}
$stack[$this->depth] = &$node;
$node = &$node[$this->name];
if (isset($node['__open'])) {
if ($debug) {
$debug(" Найден обработчик открытия {$this->name} - выполняю.");
}
if (false === $node['__open']()) {
return false;
}
}
if (isset($node['__attrs'])) {
if ($debug) {
$debug(" Найден обработчик атрибутов {$this->name} - выполняю.");
}
$attrs = [];
if ($this->hasAttributes) {
while ($this->moveToNextAttribute()) {
$attrs[$this->name] = $this->value;
}
}
if (false === $node['__attrs']($attrs)) {
return false;
}
}
if (!$this->isEmptyElement) {
return true;
}
if ($debug) {
$debug(" Элемент {$this->name} пустой. Возврат по структуре.");
}
if (isset($node['__close'])) {
if ($debug) {
$debug(" Найден обработчик закрытия {$this->name} - выполняю.");
}
if (false === $node['__close']()) {
return false;
}
}
$node = &$stack[$this->depth];
return true;
}
/**
* @param $skipToDepth
* @param $node
* @param $debug
* @return bool
*/
private function text(&$skipToDepth, &$node, $debug)
{
if ($skipToDepth !== false) {
if ($debug) {
$debug("( Текст ): {$this->value} - в режиме пропуска тегов.");
}
return true;
}
if ($debug) {
$debug("[ Текст ]: {$this->value} - в структуре.");
}
if (!isset($node['__text'])) {
return true;
}
if ($debug) {
$debug(' Найден обработчик текста - выполняю.');
}
return (false !== $node['__text']($this->value));
}
/**
* @param $skipToDepth
* @param $node
* @param $debug
* @return bool
*/
private function endElement(&$skipToDepth, &$node, $debug)
{
if (false === $skipToDepth) {
// Если $skipToDepth не установлен, то это значит, что предшествующее ему открытие тега было внутри структуры,
// и поэтому текущий узел структуры надо откатить.
if ($debug) {
$debug("[ Закрытие ]: {$this->name} - мы в структуре. Подьем по структуре.");
}
if (isset($node['__close'])) {
if ($debug) {
$debug(" Найден обработчик закрытия {$this->name} - выполняю.");
}
if (false === $node['__close']()) {
return false;
}
}
$node = &$stack[$this->depth];
return true;
}
if ($this->depth === $skipToDepth) {
// Если $skipToDepth установлен, то игнорируем все, что имеет бОльшую глубину, пока не дойдем до закрытие игнора с текущей глубиной.
if ($debug) {
$debug("[ Закрытие ]: {$this->name} - достигнута вложенность {$skipToDepth}. Отмена режима пропуска тегов.");
}
$skipToDepth = false;
return true;
}
if ($debug) {
$debug("( Закрытие ): {$this->name} - в режиме пропуска тегов.");
}
return true;
}
}
-1
То есть для XML вложенностью уровней 15 и больше вы предлагаете соорудить соответствующий массив в 15+ уровней глубиной?
Аналога для XPath "//elementName" нет?
Как отлавливать first/nth child?
В __open/__close не передается совсем никакой информации (уровень вложенности, количество предыдущих соседей)?
Аналога для XPath "//elementName" нет?
Как отлавливать first/nth child?
В __open/__close не передается совсем никакой информации (уровень вложенности, количество предыдущих соседей)?
0
Класс простой и (по возможности) быстрый. Создать конкуренцию по удобству xPath целью не было. За элегантность всегда приходится платить ресурсами. DOM старался не использовать.
Все обработчики реализуются в зависимости от места в структуре, в котором определены. Это в том числе и уровень вложенности.
Обработчик для n-го дочернего элемента можно реализовать условием в теле обработчика. Возможно, получится органично вкрутить эту логику и в метод xmlStruct, если понадобится, пока не думал над этим, т.к. не надобилось.
На мой взгляд, это интересно для тех, кто в силу экономии ресурсов отказался от DOM.
Все обработчики реализуются в зависимости от места в структуре, в котором определены. Это в том числе и уровень вложенности.
Обработчик для n-го дочернего элемента можно реализовать условием в теле обработчика. Возможно, получится органично вкрутить эту логику и в метод xmlStruct, если понадобится, пока не думал над этим, т.к. не надобилось.
На мой взгляд, это интересно для тех, кто в силу экономии ресурсов отказался от DOM.
0
если вдруг кому-то нужен path в XMLReader
https://gist.github.com/SerginhoLD/65a66b7e33e10873dc4ed2dfb836b1ae
0
Это не XPath, это просто путь к элементу. Нужны конструкции в духе:
//someEl/description[contains(./@lang, 'en')]/countryOfOrigin/text()
Желательно с parent и previous-sibling осями. Но это уже все сложно, надо парсить XPath, хранить где-то внутри всех родительские и сестринские элементы, матчить выражения.
А для хранения текущего пути отдельный класс не нужен. Это делается несколькими пользовательскими строками.
//someEl/description[contains(./@lang, 'en')]/countryOfOrigin/text()
Желательно с parent и previous-sibling осями. Но это уже все сложно, надо парсить XPath, хранить где-то внутри всех родительские и сестринские элементы, матчить выражения.
А для хранения текущего пути отдельный класс не нужен. Это делается несколькими пользовательскими строками.
0
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Зачем такое создавать таблицу на много байт для того чтобы посчитать два поля, ладно бы я понял если взять xml файлы отсюда fias.nalog.ru/Updates.aspx и их да лучше будет сначала распарсить и занести в базу, потом работать с базой, но код который в этом пост-хабре, поможет прочитать атрибуты у тега, безболезненно)
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
PHP: Как разобрать сложный XML-файл и не утонуть в собственном коде