Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, Swift, BASIC, and whatever else I happen to feel like hacking at.

Add nodes to SimpleXMLElement

Jerry Stratton, May 22, 2009

Ford Prefect: You know, it’s at times like this, when I’m trapped in a Vogon airlock with a man from Betelgeuse, and about to die of asphyxiation in deep space, that I really wish I’d listened to what my mother told me when I was young.

Arthur Dent: Why, what did she tell you?

Ford Prefect: I don’t know, I didn’t listen. — Douglas Adams (The Hitchhiker’s Guide to the Galaxy)

One of the things I tell our developers is that when a solution starts becoming unbearably complex you’re probably looking at it the wrong way. An obvious example from just this week is that it’s difficult if not impossible to automatically style a border to the right of every element in a list except for the last one, but it’s very easy to style a border to the left of every element except for the first one.

I should listen to myself more often. I just spent far too much time trying to figure out how to create a new node in PHP’s SimpleXML and add it as a child to an existing node.

I have a simple array that I want to serialize into somewhat future-proofed, readable text for storage in a database. XML seemed like the obvious solution. The array contains a dictionary-like list of fields and values. Some of those values can be arrays themselves, but those will be simple one-dimensional lists, and none of the array values will themselves be arrays—the rabbit hole only goes two deep at most.

Adding the non-arrays was easy using SimpleXMLElement::addChild. Creating a list of elements was easy enough, too, creating a new SimpleXMLElement and adding items to it. But adding that list to the parent seemed impossible.

[toggle code]

  • function arrayToXML($myArray) {
    • $arrayXML = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><fields />');
    • foreach ($myArray as $field=>$value) {
      • if (is_array($value)) {
        • //this is a list, and needs to have several items
        • $list = new SimpleXMLElement("<$field />");
        • foreach ($value as $item) {
          • $list->addChild('item', $item);
        • }
        • //there is no such function by any name in SimpleXML
        • $arrayXML->appendChild($list);
      • } else {
        • //scalar, so just add it directly
        • $arrayXML->addChild($field, $value);
      • }
    • }
    • return $arrayXML->asXML();
  • }

First I looked all over for a built-in method to add an existing node to a parent element; all I could find was people overriding SimpleXMLElement in complex ways. Then I discovered that SimpleXMLElements can be accessed simultaneously as DOM elements—but discovered that nodes can’t be added to orphan nodes.

There was no way that something called “SimpleXML” could be so complex for such obvious functionality.

I had just about given up when I noticed some pale green text I’d missed: addChild, which was working fine for me, returned a SimpleXMLElement. The correct solution was not to create a child and add it to the parent. The correct solution is to add the child to the parent, and then create it.

[toggle code]

  • function arrayToXML($myArray) {
    • $arrayXML = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><fields />');
    • foreach ($myArray as $field=>$value) {
      • if (is_array($value)) {
        • //this is a list, and needs to have several items
        • //so first add the child
        • $list = $arrayXML->addChild($field);
        • //and then populate the child
        • foreach ($value as $item) {
          • $list->addChild('item', $item);
        • }
      • } else {
        • //scalar, so just add it directly
        • $arrayXML->addChild($field, $value);
      • }
    • }
    • return $arrayXML->asXML();
  • }

Duh.

October 31, 2009: I updated the base SimpleXMLElement to be UTF-8; without the “<?xml version="1.0" encoding="UTF-8"?>” declaration, it defaults to whatever happens to be the default in the PHP you’re using, which isn’t necessarily going to match what the database this will be stored in is using, nor what the web page this appears in is using.

  1. <- Tiger auth_module
  2. PDO_MYSQL on Leopard ->