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.
Advertisement
Answer
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"