SoCo/SoCo — pull request #256 by lawrenceakka (2014-11-08 21:26:41+00:00)
Title: Didl fiddle

This is a fairly extensive re-organisation of the DIDL handling code. It does various things:

1) Bring us more into line with the DIDL-Lite spec, as adopted by Sonos. We are pretty much there now.

2) Handles the fact that DIDL objects can have multiple URIs, embedded in a element. Previously we were hardcoding the element, which could cause problems.

```python
from soco import data_structures
res = data_structures.DidlResource('a%20uri', 'a:protocol:info:xx')
didl_object = data_structures.DidlMusicTrack(title='a_title',parent_id='pid',
item_id='iid', creator='a_creator', resources=[res]) # Note that resources is a list
print data_structures.to_didl_string(didl_object)
```
will output:
```xml
a_titlea%20uria_creatorobject.item.audioItem.musicTrackRINCON_AssociatedZPUDN

```

3) Handles the fact that a DIDL element can contain multiple `
` and `` elements. Our code had various workarounds for this (such as searching through the XML for `
` elements, but now handling them is much easier

4) Generally simplifying the metadata interface. In theory we can now roundtrip metadata (eg get track metadata and then resubmit it later)

5) Improves error handling. `DidlCannotCreateMetadata` has been renamed `DidlMetadataError`, and we should now be able to pick up on all sorts of anomalies, and prevent creation of invalid metadata objects

There are new unit tests to accompany all this. I will work on getting the old tests modified, but for the moment, they are disabled (but still there).

The main interfaces are likely to be the `from_didl_string` and `to_didl_string` functions, which replace the old `get_ml_item`. The first takes a string in the form `
...
...
` and returns a *list* of `DidlObject` instances (or instances of one of the subclasses), e.g.

```python
[,
,
]
```

So you can feed a string received from the network to the function, and get a list of metadata objects. There is no need to encode, or build XML structures first. That is all handled for you.

`to_didl_string` will do the opposite, and you can then pass the string back over the network:

```python
>>> didl = """

Your Heart Is an Empty Room
object.item.audioItem.musicTrack
SA_RINCON2823_nathan

"""

>>> print didl.to_dict()
{u'creator': u'a_creator',
u'item_id': u'iid',
u'parent_id': u'pid',
u'restricted': True,
u'title': u'a_title'}

```
Of course, you can still create DidlObjects manually, using the normal constructor:
```python
didl_object = data_structures.DidlMusicTrack(title='a_title',parent_id='pid',
item_id='iid', creator='a_creator')
```
or by passing a dict:
```python
the_dict = {
'title': 'a_title',
'parent_id': 'pid',
'item_id': 'iid',
'creator': 'a_creator',
'restricted: True}
d = data_structures.DidlObject.from_dict(the_dict)
```

or by passing in an ElementTree Element (if you insist)

In the process, the xml module has been improved to output namespaces better (no more `nso:`, `ns1:`), and there is a function to compare XML elements sensibly, which should help us in future testing.

As always, comments welcome.

Lawrence