I am practicing with XML DOM PHP parsing. I have such XML file (shorter version):
<?xml version="1.0" encoding="UTF-8"?>
<tabela_kursow typ="A" uid="21a184">
<numer_tabeli>184/A/NBP/2021</numer_tabeli>
<data_publikacji>2021-09-22</data_publikacji>
<pozycja>
<nazwa_waluty>bat (Tajlandia)</nazwa_waluty>
<przelicznik>1</przelicznik>
<kod_waluty>THB</kod_waluty>
<kurs_sredni>0,1181</kurs_sredni>
</pozycja>
<pozycja>
<nazwa_waluty id="2">dolar amerykański</nazwa_waluty>
<przelicznik>1</przelicznik>
<kod_waluty>USD</kod_waluty>
<kurs_sredni>3,9460</kurs_sredni>
</pozycja>
</tabela_kursow>
I created attribute with name "id" and value "2" for two Tag elements.
<?php
$doc = new DOMDocument();
$doc->load("kursy_walut.xml");
$doc->preserveSpace = false;
$doc->formatOutput = true;
$a = $doc->getElementsByTagName("nazwa_waluty");
$b = count($a);
for ($i=0; $i<$b; $i ){
$c = $a[$i]->childNodes[0]->nodeValue;
if ($c === 'dolar amerykański'){
$a[$i]->setAttribute('id', '2');
}
if ($c === 'euro'){
$a[$i]->setAttribute('id', '2');
}
}
$doc->save('output.xml');
Now I would like to:
- Get only Tag elements which did not contain attribute 'id="2"';
- Get only Tag elements which contain only 'id="2"'.
For first case I created such code. Don't know exactly how to create second case.
$doc3 = new DOMDocument();
$doc3->preserveWhiteSpace = false;
$doc3->load('output.xml');
$xpath2 = new DOMXPath($doc3);
$id3 = $xpath2->query('//*/pozycja/nazwa_waluty[@id="2"]');
foreach($id3 as $attr){
$attr->parentNode->removeChild($attr);
}
$doc3->formatOutput = true;
$doc3->save('output3.xml');
Maybe someone can help me with second case? Thank you.
CodePudding user response:
If I understand you source then you would like to add an id depending on the currency string. However your are using a language specific string. The language code would be a better option. It is defined and unique already.
DOMNode::getElementsByTagName()
returns a node list, you could use foreach
to iterate it. However the node list is "LIVE". It reacts to changes on the nodes. This is an issue if you add/remove nodes. Using Xpath expressions avoids this and allows for more specific fetches.
More important, take a look at the DOMNode::$textContent
property. It reads/writes the whole text inside of a node. For elements this includes any descendant text node.
Using this you can simplify the code:
$document = new DOMDocument();
// configure the parser - need to be set before load
$document->preserveWhiteSpace = false;
$document->loadXML($xml);
$xpath = new DOMXpath($document);
$currencyMap = [
"USD" => 2,
"EUR" => 4
];
// iterate the "nazwa_waluty" elements
foreach ($xpath->evaluate('//pozycja/kod_waluty') as $kodWaluty) {
// get the currency code
$currencyCode = trim($kodWaluty->textContent);
if (isset($currencyMap[$currencyCode])) {
// add id for currency
$kodWaluty->setAttribute('id', $currencyMap[$currencyCode]);
}
}
// configure the serializer - need to be set before save
$document->formatOutput = true;
echo $document->saveXML();
Output:
<?xml version="1.0" encoding="UTF-8"?>
<tabela_kursow typ="A" uid="21a184">
<numer_tabeli>184/A/NBP/2021</numer_tabeli>
<data_publikacji>2021-09-22</data_publikacji>
<pozycja>
<nazwa_waluty>bat (Tajlandia)</nazwa_waluty>
<przelicznik>1</przelicznik>
<kod_waluty>THB</kod_waluty>
<kurs_sredni>0,1181</kurs_sredni>
</pozycja>
<pozycja>
<nazwa_waluty>dolar amerykański</nazwa_waluty>
<przelicznik>1</przelicznik>
<kod_waluty id="2">USD</kod_waluty>
<kurs_sredni>3,9460</kurs_sredni>
</pozycja>
</tabela_kursow>
Now to fetch the data with an specific id. I would expect that you would want the pozycja
elements and its children.
DOMXpath::evaluate()
can not just return node lists, but scalar values. It is not that much code:
foreach ($xpath->evaluate('//pozycja[kod_waluty/@id = "2"]') as $pozycja) {
var_dump(
[
'przelicznik' => $xpath->evaluate('string(przelicznik)', $pozycja),
'kurs_sredni' => $xpath->evaluate('string(kurs_sredni)', $pozycja),
]
);
}
Output:
array(2) {
["przelicznik"]=>
string(1) "1"
["kurs_sredni"]=>
string(6) "3,9460"
}
However because the language code is already defined and unique, you could use it directly:
foreach ($xpath->evaluate('//pozycja[normalize-space(kod_waluty) = "USD"]') as $pozycja) {
var_dump(
[
'przelicznik' => $xpath->evaluate('string(przelicznik)', $pozycja),
'kurs_sredni' => $xpath->evaluate('string(kurs_sredni)', $pozycja),
]
);
}
And if you need the "kurs_serdni" content for a specific currency code then you can do this with a single Xpath expression:
var_dump(
$xpath->evaluate(
'string(//pozycja[normalize-space(kod_waluty) = "USD"]/kurs_sredni)'
)
);
Output:
string(6) "3,9460"