[PHP] SimpleXML & xpath niet context aware?

Pagina: 1
Acties:

Onderwerpen


  • .Johnny
  • Registratie: September 2002
  • Laatst online: 04-07 11:10
Ben voor een project wat met SimpleXML aan het stoeien en ik heb nu iets ontdekt dat ik erg vreemd vind.

Wanneer je op een SimpleXMLElement de xpath methode gebruikt, lijkt het wel alsof het geretourneerde Array niet echt de resultaten van die xpath expressie bevatten. Laat me dat uitleggen met een voorbeeld:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$xml_data='<test>
    <record id="1">
        <item>item1</item>
        <item>item2</item>
    </record>
    <record id="2">
        <item>item3</item>
        <item>item4</item>
    </record>
</test>';

$xml = new SimpleXMLElement($xml_data);
$records_xml=$xml->xpath('//test/record');
foreach ($records_xml  as $record_xml){
    $id=(string)$record_xml[0]['id'];
    $items_xml=$record_xml->xpath('//item');
    foreach ($items_xml  as $item_xml){
        $item=(string)$item_xml[0];
        print 'ID:'.$id."\t".$item."\n";
    }
}

print "\nPoging 2:\n";

foreach($xml->record as $record) {
    $id=(string)$record[id];
    foreach($record->item as $item) {
        print 'ID:'.$id."\t".(string)$item."\n";
    }
}

De 2 pogingen hierboven zouden naar mijn idee hetzelfde resultaat moeten opleveren. De expressie //item zou alleen naar items binnen record mogen kijken, want dat is het xml object waar ik de expressie op los laat. Maar het lijkt wel of hij vergeet dat ik een selectie uit het document heb gebruikt, kijk maar:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
ID:1    item1
ID:1    item2
ID:1    item3
ID:1    item4
ID:2    item1
ID:2    item2
ID:2    item3
ID:2    item4

Poging 2:
ID:1    item1
ID:1    item2
ID:2    item3
ID:2    item4

Bij de xpath methode lijkt het geretourneerde object dus niet zijn eigen context te kennen. Iemand een verklaring voor dit gedrag?

  • Niemand_Anders
  • Registratie: Juli 2006
  • Laatst online: 09-07-2024

Niemand_Anders

Dat was ik niet..

//Item begint gewoon weer vanaf de root van het document. Anders moet je .//Item (let op de punt voor de slashes) gebruiken..

Dit gedrag staat overigens gewoon vermeld in de XML (XPath) documentatie op de W3C website.

[ Voor 27% gewijzigd door Niemand_Anders op 17-02-2011 09:48 ]

If it isn't broken, fix it until it is..


  • .Johnny
  • Registratie: September 2002
  • Laatst online: 04-07 11:10
Mijn vraag blijft nog: waarom gaat hij naar de root van het document kijken? de xpath expressie ligt niet naar het document maar naar het record_xml object. Waarom haalt SimpleXML het hele document er weer bij? dat vind ik behoorlijk counter intuitief.

-edit; mijn vraag gaat niet over xpath, ik snap dat xpath expressies // naar de root van het document gaan. Maar het "document" bij de xpath expressie //item is niet het originele document meer, maar het record element. Vandaar dat ik het nog steeds geen intuitieve reactie van SimpleXML vind.

[ Voor 37% gewijzigd door .Johnny op 17-02-2011 09:51 ]


Verwijderd

Het beantwoord je vraag eigenlijk wel ... en ja ... het is enigszins counter intuitive, gezien je er vanuit gaat dat je root element de node is die je aanspreekt (record_xml in dit geval).

Aan de andere kant geeft de '//' altijd aan dat je vanuit de root van het document wil zoeken, dus welke node je ook aanspreekt, wat dus eigenlijk ook weer zijn voordelen heeft.

Ik moet zeggen dat ik de hele simplexml implementatie niet altijd even 'simple' vind en gebruik daarom meestal DOMDocument .. maar da's voorkeur natuurlijk.

  • Toolskyn
  • Registratie: Mei 2004
  • Laatst online: 22-06 11:01

Toolskyn

€ 500,-

.Johnny schreef op donderdag 17 februari 2011 @ 09:49:
-edit; mijn vraag gaat niet over xpath, ik snap dat xpath expressies // naar de root van het document gaan. Maar het "document" bij de xpath expressie //item is niet het originele document meer, maar het record element. Vandaar dat ik het nog steeds geen intuitieve reactie van SimpleXML vind.
Als je een node selecteert is die node niet plotseling de root van je document geworden, het is alleen maar het geselecteerde element. Als je dus een relatief XPath opgeeft wordt dat element als basis gebruikt. Maar geef je een absoluut XPath op dan wordt gewoon weer begonnen bij de root van het document.

Als we het omkeren wordt het misschien logischer: Stel de geselecteerde node is wel plotseling een root element geworden. Stel dat ik vanuit deze nieuwe root toch graag naar de volgende sibling wil (om maar eens wat te noemen). Dat zou dan niet kunnen, omdat alleen de subtree onder de huidige node beschikbaar zou zijn.

gewooniets.nl


  • tonyisgaaf
  • Registratie: November 2000
  • Niet online
.Johnny schreef op donderdag 17 februari 2011 @ 09:49:
[...]
-edit; mijn vraag gaat niet over xpath, ik snap dat xpath expressies // naar de root van het document gaan. Maar het "document" bij de xpath expressie //item is niet het originele document meer, maar het record element. Vandaar dat ik het nog steeds geen intuitieve reactie van SimpleXML vind.
Ik heb geen ervaring met PHP/SimpleXML, maar volgens mij is het volgende generiek (zoals bijvoorbeeld ook voor .NET's XPathNodeIterator):

Ik denk dat je het resultaat van de XPath methode misinterpreteert. Zoals ik het begrijp is het resultaat van een XPath een pointer (of set van pointers) naar XML nodes. Je retourneert dus niet een ánder document, maar hetzelfde document + context (nl. de pointer(s)). De context bepaalt wat "." (aka "current()") is, maar niet wat de root is, want die blijft hetzelfde voor het document, want het document is ongewijzigd.

Als je overigens uitgebreidere of complexere transformaties moet gaan doen, dan is het veel eenvoudiger XSL transformaties te doen, dan code kloppen.

NL Weerradar widget Euro Stocks widget Brandstofprijzen widget voor 's Dashboard


  • Camulos
  • Registratie: Januari 2009
  • Laatst online: 06-09 22:59

Camulos

Stampert

.Johnny schreef op donderdag 17 februari 2011 @ 09:38:
code:
1
2
3
4
5
6
7
8
9
$records_xml=$xml->xpath('//test/record');
foreach ($records_xml  as $record_xml){
    $id=(string)$record_xml[0]['id'];
    $items_xml=$record_xml->xpath('//item');
    foreach ($items_xml  as $item_xml){
        $item=(string)$item_xml[0];
        print 'ID:'.$id."\t".$item."\n";
    }
}

..
code:
1
2
3
4
5
6
7
8
ID:1    item1
ID:1    item2
ID:1    item3
ID:1    item4
ID:2    item1
ID:2    item2
ID:2    item3
ID:2    item4
Niemand_Anders geeft het juist antwoord...
als je .//item gebruikt dan gaat het wel goed.

Wat je nu doet is:
- Zoek alle records ( //test/record )
- Ga over elke record (for loop)
- - Zoek nu alle items ( //item )
- - Print nu alle record Record - Item combinaties ;)

Je XPath zal nooit locaal op een variabele lopen.. het doorzoekt het gehele document ;)
Wanneer je in een subtree van de XML wilt lopen zul je de PUNT ( . ) moeten gebruiken.

Not just an innocent bystander


  • .Johnny
  • Registratie: September 2002
  • Laatst online: 04-07 11:10
Dank allemaal voor de ijverige xpath oplossingen, daar was ik niet naar op zoek maar toch bedankt voor de moeite :-)
tonyisgaaf schreef op donderdag 17 februari 2011 @ 10:46:
[...]

Ik heb geen ervaring met PHP/SimpleXML, maar volgens mij is het volgende generiek (zoals bijvoorbeeld ook voor .NET's XPathNodeIterator):

Ik denk dat je het resultaat van de XPath methode misinterpreteert. Zoals ik het begrijp is het resultaat van een XPath een pointer (of set van pointers) naar XML nodes.
Daar lijkt het inderdaad op, maar de suggestie die PHP wekt door te zeggen:
SimpleXMLElement->xpath()
Returns an array of SimpleXMLElement objects or FALSE in case of an error.
wijst m.i. toch echt de andere kant op. Ik krijg dus een Array met alle matches als SimpleXMLElement. MAW, het resultaat is een <record> element en niet een pointer. Dat bedoelen ze klaarblijkelijk wel (dat het een pointer is), maar het heeft m.i. de schijn van niet. In wat jij noemt als verwijzing in de .NET XPathNodeIterator zit dat dan ook al een stuk beter in de naam verwerkt. Maar goed, het zal niet de eerste keer zijn dat de naamgeving van een object, methodes en volgorde van parameters in PHP weer voor verwarring zorgt.

  • Voutloos
  • Registratie: Januari 2002
  • Niet online
Sinds PHP 5 krijg je objecten in de regel altijd by reference, en dus geen kopie. SimpleXML bestond daarvoor nog niet, dus de conclusie is duidelijk. ;)

{signature}


  • kluyze
  • Registratie: Augustus 2004
  • Niet online
/ is de root
// geeft elke node vanaf de huidige node.

Dus als je op de root node zit geeft //item de 4 items /item geeft niets.

De uitkomst is dus helemaal niet logisch!
Ergens wel, descendant is niet hetzelfde als child, dus alle overeenkomstige elementen in een lager niveau inclusief dus de childeren van de siblings worden teruggegeven.

http://www.w3.org/TR/xpath/#node-sets
The / and // operators compose an expression and a relative location path. It is an error if the expression does not evaluate to a node-set. The / operator does composition in the same way as when / is used in a location path. As in location paths, // is short for /descendant-or-self::node()/.

[ Voor 65% gewijzigd door kluyze op 17-02-2011 22:19 ]


  • Niemand_Anders
  • Registratie: Juli 2006
  • Laatst online: 09-07-2024

Niemand_Anders

Dat was ik niet..

As in location paths, // is short for /descendant-or-self::node()/.
Maar je leest er ook zeer gemakkelijk overheen. Er staat namelijk /descendant-or-self::node()/.
'//' laat slechts het gedeelte 'descendant-or-self::node()' weg, de rest blijft staan. Die eerste slash geeft de root aan op dezelfde manier zoals /test/record dat doet..

If it isn't broken, fix it until it is..


Acties:
  • 0 Henk 'm!

  • .Johnny
  • Registratie: September 2002
  • Laatst online: 04-07 11:10
kluyze schreef op donderdag 17 februari 2011 @ 22:10:
De uitkomst is dus helemaal niet logisch!
Ergens wel, descendant is niet hetzelfde als child, dus alle overeenkomstige elementen in een lager niveau inclusief dus de childeren van de siblings worden teruggegeven.
Dit klopt ook niet helemaal, het hoeven helemaal geen children van siblings te zijn en het niveau hoeft ook niet lager te zijn. Stel dat er <item>s waren die zelf sibling waren van <record> dan zouden ze ook in de set zitten zolang het evaluatie object het hele document blijft. Misschien bedoel je dat ook wel, maar lijkt me wel zo goed voor de volledigheid.
-edit; vanaf root geldt het natuurlijk wel, maar dat stond er niet :)

[ Voor 4% gewijzigd door .Johnny op 18-02-2011 09:54 ]


Acties:
  • 0 Henk 'm!

  • Voutloos
  • Registratie: Januari 2002
  • Niet online
Vanaf root klopt die uitspraak ook niet, want die heeft geen siblings. B)

[ Voor 8% gewijzigd door Voutloos op 18-02-2011 10:52 ]

{signature}


Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
.Johnny schreef op donderdag 17 februari 2011 @ 09:49:
Mijn vraag blijft nog: waarom gaat hij naar de root van het document kijken?
Omdat je daar specifiek om vraagt. Als je vanuit de huidige node wil zoeken gebruik je een punt, als je vanuit de root zoekt een dubbele slash. Snap niet wat daar zo lastig aan is. Het werkt exact hetzelfde als filepaden.

https://niels.nu


Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 10-09 14:31
Met filepaden heb je hetzelfde: chroot subdir doet niet hetzelfde als cd subdir.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein

Pagina: 1