Рекурсивная функция PHP SimpleXML для вывода списка дочерних элементов и атрибутов

Мне нужна помощь в вызовах SimpleXML для рекурсивной функции, которая перечисляет имена и атрибуты элементов. Создание файловой системы конфигурации XML, но каждый сценарий будет иметь свой собственный файл конфигурации, а также новое соглашение об именах. Итак, мне нужен простой способ отобразить все элементы, у которых есть атрибуты, поэтому, как и в примере 1, мне нужен простой способ вызвать все процессы, но я не знаю, как это сделать без жесткого кодирования, имя элемента вызов функции. Есть ли способ рекурсивно вызвать функцию для соответствия имени дочернего элемента? Я видел функциональность xpath, но не понимаю, как использовать ее для атрибутов.

Также правильно ли выглядит XML в примерах? могу ли я структурировать свой XML таким образом?

Пример 1:

<application>
  <processes>
    <process id="123" name="run batch A" />
    <process id="122" name="run batch B" />
    <process id="129" name="run batch C" />
  </processes>
  <connections>
    <databases>
      <database usr="test" pss="test" hst="test" dbn="test" />
    </databases>
    <shells>
      <ssh usr="test" pss="test" hst="test-2" />
      <ssh usr="test" pss="test" hst="test-1" />
    </shells>
  </connections>
</application>

Пример 2:

<config>
  <queues>
    <queue id="1" name="test" />
    <queue id="2" name="production" />
    <queue id="3" name="error" />
  </queues>
</config>

Псевдокод:

// Would return matching process id
getProcess($process_id) {
  return the process attributes as array that are in the XML
}

// Would return matching DBN (database name)
getDatabase($database_name) {
  return the database attributes as array that are in the XML
}

// Would return matching SSH Host
getSSHHost($ssh_host) {
  return the ssh attributes as array that are in the XML
}

// Would return matching SSH User
getSSHUser($ssh_user) {
  return the ssh attributes as array that are in the XML
}

// Would return matching Queue 
getQueue($queue_id) {
  return the queue attributes as array that are in the XML
}

РЕДАКТИРОВАТЬ:

Могу ли я пройти два парама? по первому методу, который вы предложили @Gordon

Я только что понял, спасибо, смотрите ниже

public function findProcessById($id, $name)
{
    $attr = false;
    $el = $this->xml->xpath("//process[@id='$id'][@name='$name']"); // How do I also filter by the name?
    if($el && count($el) === 1) {
        $attr = (array) $el[0]->attributes();
        $attr = $attr['@attributes'];
    }
    return $attr;
}

person Phill Pafford    schedule 04.03.2010    source источник


Ответы (1)


XML выглядит хорошо для меня. Единственное, что я бы не стал делать, это делать name атрибутом в process, потому что он содержит пробелы и должен быть текстовым узлом (на мой взгляд). Но поскольку SimpleXml не жалуется на это, я думаю, все сводится к личным предпочтениям.

Я бы, вероятно, подошел к этому с классом DataFinder, инкапсулирующим запросы XPath, например.

class XmlFinder
{
    protected $xml;
    public function __construct($xml)
    {
        $this->xml = new SimpleXMLElement($xml);
    }
    public function findProcessById($id)
    {
        $attr = false;
        $el = $this->xml->xpath("//process[@id='$id']");
        if($el && count($el) === 1) {
            $attr = (array) $el[0]->attributes();
            $attr = $attr['@attributes'];
        }
        return $attr;
    }
    // ... other methods ...
}

а затем использовать его с

$finder = new XmlFinder($xml);
print_r( $finder->findProcessById(122) );

Выход:

Array
(
    [id] => 122
    [name] => run batch B
)

Учебник XPath:


Альтернативой может быть использование SimpleXmlIterator для перебора элементов. Итераторы могут быть украшен другими итераторами, поэтому вы можете:

class XmlFilterIterator extends FilterIterator
{
    protected $filterElement;
    public function setFilterElement($name)
    {
        $this->filterElement = $name;
    }
    public function accept()
    {
        return ($this->current()->getName() === $this->filterElement);
    }
}

$sxi = new XmlFilterIterator(
           new RecursiveIteratorIterator( 
               new SimpleXmlIterator($xml)));

$sxi->setFilterElement('process');

foreach($sxi as $el) {
    var_dump( $el ); // will only give process elements
}

Вам придется добавить еще несколько методов, чтобы фильтр работал для атрибутов, но это довольно тривиальная задача.

Введение в сплиттераторы:

person Gordon    schedule 04.03.2010
comment
Большое спасибо, первое решение выглядит великолепно, но у меня есть ошибка: Неустранимая ошибка PHP: неперехваченное исключение «Исключение» с сообщением «Строка не может быть проанализирована как XML». Я передаю файл так. $xml = /путь/к/файлу.xml; это правильно? - person Phill Pafford; 04.03.2010
comment
@Phill Нет, он ожидает строку XML, а не путь к файлу. Если вы хотите передать путь к файлу, вы должны передать $data_is_url в качестве третьего параметра. См. de3.php.net/manual/en/simplexmlelement.construct.php - person Gordon; 04.03.2010
comment
@Phill или замени вызов на simplexml_load_file - person Gordon; 04.03.2010
comment
Хм, внес изменения: $this-›xml = new SimpleXMLElement($xml, NULL, TRUE); но все еще получаю сообщение об ошибке: PHP Неустранимая ошибка: необработанное исключение «Исключение» с сообщением «Строка не может быть проанализирована как XML». Я внес изменение в ваш пример класса, это правильно? - person Phill Pafford; 04.03.2010
comment
@Phill или заменить вызов на simplexml_load_file заменить где? В конструкторе? - person Phill Pafford; 04.03.2010
comment
@Phill измените $this->xml = new SimpleXMLElement($xml); на $this->xml = new simplexml_load_file($xml); Если ошибка продолжает появляться, путь, вероятно, неверен. Попробуйте проверить, можете ли вы открыть путь с помощью file_get_contents() и посмотреть, что он содержит. - person Gordon; 04.03.2010
comment
Хорошо, вот что я сделал, а затем получил это: Неустранимая ошибка PHP: класс 'simplexml_load_file' не найден в - person Phill Pafford; 04.03.2010
comment
@Фил извини. Удалить новое ключевое слово из вызова функции - person Gordon; 04.03.2010
comment
ЛОЛ, я тоже по этому скучаю. Спасибо еще раз - person Phill Pafford; 04.03.2010
comment
Привет, Гордон, у меня есть еще один вопрос. См. Редактировать - person Phill Pafford; 08.03.2010
comment
@ Фил Хм, я так думаю. Попробуйте //process[@id='$id' и @name='$name'] для XPath, затем - person Gordon; 08.03.2010
comment
Я попробовал AND, но не пошел, хотя я нашел решение. //процесс[@id='$id'][@name='$name'] - person Phill Pafford; 08.03.2010