Итоговое решение в результате доработки исходного кода:
//Создание DOM-документа
$dom = new DOMDocument('1.0', 'windows-1251');
$dom->formatOutput = True;
//Создание корневого элемента <ТОВАРЫ>
$root = $dom->createElement("Товары");
$dom->appendChild($root);
//=========================================================================================================
//Функция создания элементов/узлов второго уровня (дочерние по отношению к <Товар>)
function addChild2($dom, $node, $tempNameElement, $tempCreate = NULL, $tempSetNameAttribute = NULL, $tempSetAttribute = NULL)
{
$child = $dom->createElement($tempNameElement); //создали <элемент>
$node->appendChild($child); //добавили <элемент> к элементу <Товар> в качестве дочернего
if($tempCreate !== NULL) {
$child->appendChild($dom->createTextNode($tempCreate)); //(вывели текст в элементе <>...</>) создали дочерний Текстовый узел для текущего узла.
}
if($tempSetNameAttribute != NULL && $tempSetAttribute != NULL) {
$child->setAttribute($tempSetNameAttribute,$tempSetAttribute); //присваиваем атрибут "тип цены" элементу <Цена>
}
return $child;
};
//=========================================================================================================
//Функция создания элементов/узлов третьего уровня
function addChild3($dom, $node, $child, $tempNameElement, $tempCreate = NULL, $tempSetNameAttribute = NULL, $tempSetAttribute = NULL)
{
$child2 = $dom->createElement($tempNameElement); //создали <элемент>
$child->appendChild($child2); //добавили <элемент> к элементу <...> в качестве дочернего
if($tempCreate !== NULL) {
$child2->appendChild($dom->createTextNode($tempCreate)); //вывели значение элемента в теге <>...</>
}
if($tempSetNameAttribute != NULL && $tempSetAttribute != NULL) {
$child2->setAttribute($tempSetNameAttribute,$tempSetAttribute); //присваиваем атрибут (наименование="значение") текущему дочернему элементу
}
return $child2;
};
//=========================================================================================================
//Функция проверки элементов на дубли
function checkDuplicate2($childKey, $checkMassive, $row)
{
//Перебираем в массиве узлов ($elements[$key]) детей найденного в массиве записей из БД MySQL ($rows[]) узла <ТОВАР>
foreach($checkMassive->childNodes as $childN) { //на каждой итерации цикла проверяем один очередной дочерний узел узла <Товар> (например: Товар->Цена или Товар->Свойства или Товар->Разделы)
//Проверка на дубли по наименованию узла
switch ($childKey) {
case 'Цена':
if($childN->getAttribute('Тип') == $row['Тип']) { //проверяем атрибут 'Тип' узла <Цена>
$add = FALSE; //Индикатор записи (если записывали в массив ранее)
break 2; //выходим из switch и из foreach.
} else {
$add = TRUE;
break; //выходим только из switch
}
case 'Свойства':
if('Свойства' == $childN->nodeName) {
$add = FALSE; //Индикатор записи (если записывали в массив ранее)
break 2; //выходим из switch и из foreach.
} else {
$add = TRUE;
break; //выходим только из switch
}
//case 'Разделы':
case 'Раздел':
if('Разделы' == $childN->nodeName) {
$add = FALSE; //Индикатор записи (если записывали в массив ранее)
break 2; //выходим из switch и из foreach.
} else {
$add = TRUE;
break; //выходим только из switch (так как требуется продолжать проверку по всем детям текущего узла, пока не будет совпадение или конец списка)
}
}
}
return $add;
}
//=========================================================================================================
//Функция проверки элементов 3го уровня на дубли
function checkDuplicate3($childKey, $childValue, $checkMassive, $row)
{
//Перебираем в массиве узлов ($elements[$key]) детей найденного в массиве записей из БД MySQL ($rows[]) узла <ТОВАР>
foreach($checkMassive->childNodes as $childN) { //на каждой итерации цикла проверяем один очередной дочерний узел узла <Товар> (например: Товар->Цена)
foreach($childN->childNodes as $childValue3) { //<Разделы> -> <Раздел> или...
switch ($childKey) {
case 'Раздел':
if($childValue == $childValue3->nodeValue) { //$childValue3->nodeValue -это значение (содержимое) узла <Раздел>
$add = FALSE; //Индикатор записи (если записывали в массив ранее)
break 3; //выходим из switch и из обоих foreach.
} else {
$add = TRUE;
break; //выходим только из switch (так как требуется продолжать проверку по всем детям текущего узла, пока не будет совпадение или конец списка)
}
case 'Свойства':
if($childValue == $childValue3->nodeName && $row['значение свойства'] == $childValue3->nodeValue) { //$childValue3->nodeName -это наименование (ключ) дочернего узла <Свойства>, а $childValue3->nodeValue -значение (содержимое) этого дочернего узла
$add = FALSE;
break 3; //выходим из switch и из обоих foreach.
} else {
$add = TRUE;
break; //выходим только из switch
}
}
}
}
return $add;
}
//=========================================================================================================
//ЛОГИКА. Получаем записи из таблиц и формируем из них дерево DOM
$i = 0; //инициализация индекса Массива $rows[$i] (полученных из БД MySQL записей)
$elements = []; //Массив $node (узлов DOM)
$rows = []; //Массив полученных из БД MySQL записей
while($row = $result->fetch_assoc()) { //получение записей из таблиц БД MySQL
//Если такой ID пока не сохранён в Массиве записей из БД MySQL (избавляемся от дублей по ID)
if(FALSE === ($key = array_search($row['id'], array_column($rows, 'id')))) { //$key - индекс строки в Массиве записей из БД MySQL ($rows[])
$rows[$i] = $row; //Массив записей из БД MySQL
//Создаем элемент <Товар>
$node = $dom->createElement('Товар');
$root->appendChild($node);
$elements[$i] = $node; //Массив узлов
//Получаем наименования для дочерних узлов узла <Товар> из функции fetch_assoc()
//АТРИБУТЫ <ТОВАРА>
$elements[$i]->setAttribute("Код", $rows[$i]['Код']); //присваиваем атрибуты 'код' и 'название' элементу <Товар>
$elements[$i]->setAttribute("Название", $rows[$i]['Название']);
//<ЦЕНА> - узел 1 уровня
addChild2($dom, $elements[$i], 'Цена', $row['Цена'], 'Тип', $row['Тип']);
$i++; //увеличиваем индекс
}else{ //Если <ТОВАР> есть в Массиве записей из БД MySQL ($rows[]) (и, соответственно, есть $key с ключом его ID, а также есть соответствующий в Массиве узлов ($elements[]))
foreach ($row as $childKey => $childValue) { //на каждой итерации цикла проверяем один очередной элемент записи из БД MySQL (например: $row['Цена'] или $row['Свойства'] или $row['Раздел'])
$add = checkDuplicate2($childKey, $elements[$key], $row);
//Результат проверки == FALSE или TRUE для узлов 2-го уровня(<ЦЕНА> или <Свойства> или <Раздел>)
if($add === TRUE) {
if($childKey == 'Цена') {
addChild2($dom, $elements[$key], $childKey, $childValue, 'Тип', $row['Тип']); //то записываем в найденный в Массиве узлов ($elements[]) узел <ТОВАР> - новый узел <ЦЕНА>
}
if($childKey == 'Раздел') {
$childR = addChild2($dom, $elements[$key], 'Разделы');
}
if($childKey == 'Свойства') {
$childP = addChild2($dom, $elements[$key], 'Свойства');
}
}
//Результат проверки == FALSE или TRUE для узлов 3-го уровня(<Плотность> или <Белизна> или <Формат> или <Тип> или <Раздел>)
if($childKey == 'Раздел') {
$add3 = checkDuplicate3($childKey, $childValue, $elements[$key], $row);
if($add3 === TRUE) {
$child2 = addChild3($dom, $elements[$key], $childR, $childKey, $childValue);
}
}
if($childKey == 'Свойства') {
$add4 = checkDuplicate3($childKey, $childValue, $elements[$key], $row);
if($add4 === TRUE) {
$child2 = addChild3($dom, $elements[$key], $childP, $childValue, $row['значение свойства']);
}
}
} //end foreach
}
};
foreach ($root->childNodes as $q) {
if ($q->lastChild->nodeName == $q->firstChild->nodeName) {
//public DOMNode::replaceChild ( DOMNode $node , DOMNode $child ) : DOMNode|false. Возвращает старый узел или false в случае возникновения ошибки.
$w = $q->replaceChild($q->lastChild, $q->childNodes[1]); //функция DOMNode::replaceChild заменяет дочерний узел child новым узлом.
$q->appendChild($w);
$w = $q->replaceChild($q->lastChild, $q->childNodes[2]);
$q->appendChild($w);
}
}
$dom->save( 'target.xml' ); //сохранение объекта (DOM-документа) в файл.
//Закрываем подключение к серверу БД
mysqli_close($connect);
Полученный результат:
<?xml version="1.0" encoding="windows-1251"?>
<Товары>
<Товар Код="201" Название="Бумага А4">
<Цена Тип="Базовая">11.50</Цена>
<Цена Тип="Москва">12.50</Цена>
<Свойства>
<Плотность>100</Плотность>
<Белизна>100</Белизна>
</Свойства>
<Разделы>
<Раздел>Бумага</Раздел>
</Разделы>
</Товар>
<Товар Код="302" Название="Принтер Canon">
<Цена Тип="Базовая">3010.00</Цена>
<Цена Тип="Москва">3500.00</Цена>
<Свойства>
<Тип>Лазерный</Тип>
<Формат>A4</Формат>
<Формат>A3</Формат>
</Свойства>
<Разделы>
<Раздел>МФУ</Раздел>
<Раздел>Принтеры</Раздел>
</Разделы>
</Товар>
...
</Товары>