У меня есть этот метод, который преобразует строку 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
кажется довольно сложным.
Я внес некоторые изменения в вашу функцию, но я не уверен, что это то, что вам нужно.
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" => []
]
]
@Aborted Взгляните на мой обновленный. Я добавил if перед array_push
. Теперь у вас должна получиться нужная структура.
Спасибо еще раз за помощь! Он отлично работает с предоставленным мной XML, однако, с этим другим XML, с которым я его тестировал, я получаю Array to string conversion
.
@Aborted Хм, похоже, какой-то ребенок без проблемы с tagName. Обновил elseif
.
Спасибо еще раз за помощь! Я немного запутался в выводе ключа значения DISPATCHNOTIFICATION_ITEM
. Он имеет как числовые индексы, так и строковые ключи. LINE_ITEM_ID
- это прямой ключ, а PRODUCT_ID
и некоторые другие содержатся в индексированном массиве. pastebin.com/3XBH8s30
@Aborted Вы пробовали ответить Найджела? Я только что посмотрел, и кажется, что он должен работать.
Проблема в том, когда добавлять новый уровень, а когда продолжать просто добавлять данные. Я изменил эту логику, добавив комментарии к коду, чтобы помочь понять, что и когда происходит ...
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>
добавлены на правильный уровень.
Это почти то, что мне нужно, но вы удалили много избыточного кода. Спасибо! Итак, как я уже сказал в самом вопросе, мне нужна только эта упаковка массива, если мы имеем дело со многими элементами одного и того же типа в одной ветке / уровне, ваш метод прямо сейчас упаковывает все в массив, даже ключи внутри
value
character
. Буду очень признателен за вашу постоянную помощь.