3

I have always used PHP's SimpleXML to parse XML files, but I am now facing a challenge trying to parse a plist file.

An example taken from the Apple Mac Developer Library:

<?xml version="1.0" encoding="UTF-8"?>
       <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
               "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
       <plist version="1.0">
       <dict>
           <key>Year Of Birth</key>
           <integer>1965</integer>
           <key>Pets Names</key>
           <array/>
           <key>Picture</key>
           <data>
               PEKBpYGlmYFCPA==
           </data>
           <key>City of Birth</key>
           <string>Springfield</string>
           <key>Name</key>
           <string>John Doe</string>
           <key>Kids Names</key>
           <array>
               <string>John</string>
               <string>Kyra</string>
           </array>
       </dict>
       </plist>

In a case like this, things are easy enough: if I wanted, for example, to get the year of birth, I would just do $xml -> dict -> integer since there is only one integer in this list.

However, how can I return the value of a field using its key name (like select the integer with key Year of Birth)?

I could just use the order of elements but I am ideally looking for a solution that would continue working if the elements get rearranged in the XML file in the future.

user1301428
  • 1,743
  • 3
  • 25
  • 57
  • 1
    you cannot do "select the integer with key Year of Birth" because that is not represented in the XML. However, you can select any following sibling of a key element – Gordon Feb 11 '16 at 15:47
  • @Gordon hah, that's true. However, do I have to use an XPath expression for that or is there something built in SimpleXML? – user1301428 Feb 11 '16 at 16:01
  • 1
    SimpleXml has an xpath() method. I find it's behavior odd though. In DOM, you could use `/plist/dict/key[text()="Year Of Birth"]/following-sibling::*[1]/text()'` – Gordon Feb 11 '16 at 16:05
  • @Gordon that helped, thanks. I hope the question will be reopened so I can upvote your answer. – user1301428 Feb 11 '16 at 16:15
  • @Gordon There's not that much odd about the SimpleXML XPath method. It uses the node it's called on as the "context" of the XPath call (rather than defaulting to the document root) and always returns an array of SimpleXML objects (never strings), so the `/text()` part is ignored. But `'/plist/dict/key[text()="Year Of Birth"]/following-sibling::*[1]'` [works fine to find the appropriate node](https://3v4l.org/lANvR). – IMSoP Feb 11 '16 at 16:36
  • @IMSoP yes, it ignores things I put into the query. That makes it odd behavior ;) – Gordon Feb 11 '16 at 17:41
  • @Gordon SimpleXML doesn't have a representation of a "text node", so it has to fall back to giving you the element node. It is a simplified API, after all. ;) I'm not aware of any other oddities in its `xpath()` method though, so the generalisation seemed a bit, um, odd. :P – IMSoP Feb 11 '16 at 18:21
  • @IMSoP agreed, it just strikes me as odd each time I give it a try over DOM and don't get the results I expect. – Gordon Feb 11 '16 at 19:57
  • @Gordon Fair enough. I find the DOM weird because it asks me to work with half a dozen different objects for every simple task. – IMSoP Feb 11 '16 at 22:01

1 Answers1

4

The following XPath would do that for you:

/plist/dict/key[text()="Year Of Birth"]/following-sibling::*[1]

With SimpleXml

$plist = simplexml_load_string($xml);
$query = '/plist/dict/key[text()="Year Of Birth"]/following-sibling::*[1]';       
$results = $plist->xpath($query);
echo (string) $results[0]; // 1965
Gordon
  • 312,688
  • 75
  • 539
  • 559