Как преобразовать строку XML в массив PHP с другой структурой?

У меня есть этот метод, который преобразует строку XML в массив PHP с разными ключами и значениями, чтобы полностью понять этот XML должным образом. Однако, когда есть несколько дочерних элементов одного и того же типа, я не получаю желаемый результат от массива и не понимаю, как изменить метод для этого.

Вот как выглядит метод:

/**
 * Converts a XML string to an array
 *
 * @param $xmlString
 * @return array
 */
private function parseXml($xmlString)
{
    $doc = new DOMDocument;
    $doc->loadXML($xmlString);
    $root = $doc->documentElement;
    $output[$root->tagName] = $this->domnodeToArray($root, $doc);

    return $output;
}

/**
 * @param $node
 * @param $xmlDocument
 * @return array|string
 */
private function domNodeToArray($node, $xmlDocument)
{
    $output = [];
    switch ($node->nodeType)
    {
        case XML_CDATA_SECTION_NODE:
        case XML_TEXT_NODE:
            $output = trim($node->textContent);
            break;
        case XML_ELEMENT_NODE:
            for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++)
            {
                $child = $node->childNodes->item($i);
                $v = $this->domNodeToArray($child, $xmlDocument);

                if (isset($child->tagName))
                {
                    $t = $child->tagName;

                    if (!isset($output['value'][$t]))
                    {
                        $output['value'][$t] = [];
                    }
                    $output['value'][$t][] = $v;
                }
                else if ($v || $v === '0')
                {
                    $output['value'] = htmlspecialchars((string)$v, ENT_XML1 | ENT_COMPAT, 'UTF-8');
                }
            }

            if (isset($output['value']) && $node->attributes->length && !is_array($output['value']))
            {
                $output = ['value' => $output['value']];
            }

            if (!$node->attributes->length && isset($output['value']) && !is_array($output['value']))
            {
                $output = ['attributes' => [], 'value' => $output['value']];
            }

            if ($node->attributes->length)
            {
                $a = [];
                foreach ($node->attributes as $attrName => $attrNode)
                {
                    $a[$attrName] = (string)$attrNode->value;
                }
                $output['attributes'] = $a;
            }
            else
            {
                $output['attributes'] = [];
            }

            if (isset($output['value']) && is_array($output['value']))
            {
                foreach ($output['value'] as $t => $v)
                {
                    if (is_array($v) && count($v) == 1 && $t != 'attributes')
                    {
                        $output['value'][$t] = $v[0];
                    }
                }
            }
            break;
    }

    return $output;
}

Вот пример XML:

<?xml version = "1.0" encoding = "UTF-8"?>
<characters>
   <character>
      <name2>Sno</name2>
      <friend-of>Pep</friend-of>
      <since>1950-10-04</since>
      <qualification>extroverted beagle</qualification>
   </character>
   <character>
      <name2>Pep</name2>
      <friend-of>Sno</friend-of>
      <since>1966-08-22</since>
      <qualification>bold, brash and tomboyish</qualification>
   </character>
</characters>

Запуск метода и передача этого XML в качестве параметра приведет к получению этого массива:

array:1 [▼
  "characters" => array:2 [▼
    "value" => array:1 [▼
      "character" => array:2 [▼
        0 => array:2 [▼
          "value" => array:4 [▼
            "name2" => array:2 [▼
              "attributes" => []
              "value" => "Sno"
            ]
            "friend-of" => array:2 [▼
              "attributes" => []
              "value" => "Pep"
            ]
            "since" => array:2 [▼
              "attributes" => []
              "value" => "1950-10-04"
            ]
            "qualification" => array:2 [▼
              "attributes" => []
              "value" => "extroverted beagle"
            ]
          ]
          "attributes" => []
        ]
        1 => array:2 [▼
          "value" => array:4 [▼
            "name2" => array:2 [▼
              "attributes" => []
              "value" => "Pep"
            ]
            "friend-of" => array:2 [▼
              "attributes" => []
              "value" => "Sno"
            ]
            "since" => array:2 [▼
              "attributes" => []
              "value" => "1966-08-22"
            ]
            "qualification" => array:2 [▼
              "attributes" => []
              "value" => "bold, brash and tomboyish"
            ]
          ]
          "attributes" => []
        ]
      ]
    ]
    "attributes" => []
  ]
]

Я хочу, чтобы это получилось (отступ может быть неправильным):

array:1 [▼
  "characters" => array:2 [▼
    "value" => array:2 [▼
      0 => [
        "character" => array:1 [▼
            "value" => array:4 [▼
              "name2" => array:2 [▼
                  "attributes" => []
                  "value" => "Sno"
                ]
                "friend-of" => array:2 [▼
                  "attributes" => []
                  "value" => "Pep"
                ]
                "since" => array:2 [▼
                  "attributes" => []
                  "value" => "1950-10-04"
                ]
                "qualification" => array:2 [▼
                  "attributes" => []
                  "value" => "extroverted beagle"
                ]
              ]
              "attributes" => []
            ]
          ]
        ]
        1 => array:2 [▼
          "character" => array:1 [▼
            "value" => array:4 [▼
              "name2" => array:2 [▼
                "attributes" => []
                "value" => "Pep"
              ]
              "friend-of" => array:2 [▼
                "attributes" => []
                "value" => "Sno"
              ]
              "since" => array:2 [▼
                "attributes" => []
                "value" => "1966-08-22"
              ]
              "qualification" => array:2 [▼
                "attributes" => []
                "value" => "bold, brash and tomboyish"
              ]
            ]
            "attributes" => []
          ]
        ]
      ]
    ]
    "attributes" => []
  ]
]

По сути, я хочу, чтобы ключ characters ключа value был массивом из двух элементов, который в основном включает в себя 2 ключа character. Это может произойти только в том случае, если в одной ветке много одинаковых элементов. То, как это происходит сейчас, когда ключ character представляет собой массив с 2 элементами, в моей ситуации не работает.

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

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
1
0
128
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я внес некоторые изменения в вашу функцию, но я не уверен, что это то, что вам нужно.

private function domNodeToArray($node, $xmlDocument)
{
    $output = ['value' => [], 'attributes' => []];

    switch ($node->nodeType) {
    case XML_CDATA_SECTION_NODE:
    case XML_TEXT_NODE:
        $output = trim($node->textContent);
        break;
    case XML_ELEMENT_NODE:
        for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++) {
            $child = $node->childNodes->item($i);
            $v = $this->domNodeToArray($child, $xmlDocument);

            if (isset($child->tagName)) {
                $t = $child->tagName;

                if (isset($output['value'][$t])) {
                    $output['value'][] = [$t => $output['value'][$t]];
                    $output['value'][] = [$t => $v];
                    unset($output['value'][$t]);
                } else {
                    $output['value'][$t] = $v;
                }
            } elseif (($v && is_string($v)) || $v === '0') {
                $output['value'] = htmlspecialchars((string)$v, ENT_XML1 | ENT_COMPAT, 'UTF-8');
            }
        }

        if ($node->attributes->length) {
            foreach ($node->attributes as $attrName => $attrNode) {
                $output['attributes'][$attrName] = (string) $attrNode->value;
            }
        }

        break;
    }

    return $output;
}

Выход

array:1 [▼
  "characters" => array:2 [▼
    "value" => array:2 [▼
      0 => array:1 [▼
        "character" => array:2 [▼
          "value" => array:4 [▼
            "name2" => array:2 [▼
              "value" => "Sno"
              "attributes" => []
            ]
            "friend-of" => array:2 [▼
              "value" => "Pep"
              "attributes" => []
            ]
            "since" => array:2 [▼
              "value" => "1950-10-04"
              "attributes" => []
            ]
            "qualification" => array:2 [▼
              "value" => "extroverted beagle"
              "attributes" => []
            ]
          ]
          "attributes" => []
        ]
      ]
      1 => array:1 [▼
        "character" => array:2 [▼
          "value" => array:4 [▼
            "name2" => array:2 [▼
              "value" => "Pep"
              "attributes" => []
            ]
            "friend-of" => array:2 [▼
              "value" => "Sno"
              "attributes" => []
            ]
            "since" => array:2 [▼
              "value" => "1966-08-22"
              "attributes" => []
            ]
            "qualification" => array:2 [▼
              "value" => "bold, brash and tomboyish"
              "attributes" => []
            ]
          ]
          "attributes" => []
        ]
      ]
    ]
    "attributes" => []
  ]
]

Это почти то, что мне нужно, но вы удалили много избыточного кода. Спасибо! Итак, как я уже сказал в самом вопросе, мне нужна только эта упаковка массива, если мы имеем дело со многими элементами одного и того же типа в одной ветке / уровне, ваш метод прямо сейчас упаковывает все в массив, даже ключи внутри valuecharacter. Буду очень признателен за вашу постоянную помощь.

aborted 02.05.2018 16:40

@Aborted Взгляните на мой обновленный. Я добавил if перед array_push. Теперь у вас должна получиться нужная структура.

Chin Leung 02.05.2018 16:53

Спасибо еще раз за помощь! Он отлично работает с предоставленным мной XML, однако, с этим другим XML, с которым я его тестировал, я получаю Array to string conversion .

aborted 02.05.2018 17:04

@Aborted Хм, похоже, какой-то ребенок без проблемы с tagName. Обновил elseif.

Chin Leung 02.05.2018 17:07

Спасибо еще раз за помощь! Я немного запутался в выводе ключа значения DISPATCHNOTIFICATION_ITEM. Он имеет как числовые индексы, так и строковые ключи. LINE_ITEM_ID - это прямой ключ, а PRODUCT_ID и некоторые другие содержатся в индексированном массиве. pastebin.com/3XBH8s30

aborted 02.05.2018 17:15

@Aborted Вы пробовали ответить Найджела? Я только что посмотрел, и кажется, что он должен работать.

Chin Leung 02.05.2018 17:20
Ответ принят как подходящий

Проблема в том, когда добавлять новый уровень, а когда продолжать просто добавлять данные. Я изменил эту логику, добавив комментарии к коду, чтобы помочь понять, что и когда происходит ...

private function domNodeToArray($node, $xmlDocument)
{
    $output = [];
    switch ($node->nodeType)
    {
        case XML_CDATA_SECTION_NODE:
        case XML_TEXT_NODE:
            $output = trim($node->textContent);
            break;
        case XML_ELEMENT_NODE:
            for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++)
            {
                $child = $node->childNodes->item($i);
                $v = $this->domNodeToArray($child, $xmlDocument);

                if (isset($child->tagName))
                {
                    $t = $child->tagName;

//                     if (!isset($output['value'][$t]))
//                     {
//                         $output['value'][$t] = [];
//                     }
                    // If the element already exists
                    if (isset($output['value'][$t]))
                    {
                        // Copy the existing value to new level
                        $output['value'][] = [$t => $output['value'][$t]];
                        // Add in new value
                        $output['value'][] = [$t => $v];
                        // Remove old element
                        unset($output['value'][$t]);
                    }
                    // If this has already been added at a new level
                    elseif ( isset($output['value'][0][$t]))   
                    {
                        // Add it to existing extra level
                        $output['value'][] = [$t => $v];
                    }
                    else    {
                        $output['value'][$t] = $v;
                    }
                }
                else if ($v || $v === '0')
                {
                    $output['value'] = htmlspecialchars((string)$v, ENT_XML1 | ENT_COMPAT, 'UTF-8');
                }
            }

            if (isset($output['value']) && $node->attributes->length && !is_array($output['value']))
            {
                $output = ['value' => $output['value']];
            }

            if (!$node->attributes->length && isset($output['value']) && !is_array($output['value']))
            {
                $output = ['attributes' => [], 'value' => $output['value']];
            }

            if ($node->attributes->length)
            {
                $a = [];
                foreach ($node->attributes as $attrName => $attrNode)
                {
                    $a[$attrName] = (string)$attrNode->value;
                }
                $output['attributes'] = $a;
            }
            else
            {
                $output['attributes'] = [];
            }
            break;
    }

    return $output;
}

Я пробовал с ...

<?xml version = "1.0" encoding = "UTF-8"?>
<characters>
   <character>
      <name2>Sno</name2>
      <friend-of>Pep</friend-of>
      <since>1950-10-04</since>
      <qualification>extroverted beagle</qualification>
   </character>
   <character>
      <name2>Pep</name2>
      <friend-of>Sno</friend-of>
      <since>1966-08-22</since>
      <qualification>bold, brash and tomboyish</qualification>
   </character>
   <character>
      <name2>Pep2</name2>
      <friend-of>Sno</friend-of>
      <since>1966-08-23</since>
      <qualification>boldish, brashish and tomboyish</qualification>
   </character>
</characters>

чтобы убедиться, что все элементы <character> добавлены на правильный уровень.

Другие вопросы по теме