docs: def save(self):
          """
          Updates the list with changes.
          """
          # Based on the documentation at
          # http://msdn.microsoft.com/en-us/library/lists.lists.updatelistitems%28v=office.12%29.aspx
  
          # Note, this ends up un-namespaced. SharePoint doesn't care about
          # namespaces on this XML node, and will bork if any of these elements
          # have a namespace prefix. Likewise Method and Field in
          # SharePointRow.get_batch_method().
          batches = E.Batch(ListVersion='1', OnError='Return')
          # Here's the root element of our SOAP request.
          xml = SP.UpdateListItems(SP.listName(self.id), SP.updates(batches))
  
          # rows_by_batch_id contains a mapping from new rows to their batch
          # IDs, so we can set their IDs when they are returned by SharePoint.
          rows_by_batch_id, batch_id = {}, 1
  
          for row in self._rows:
              batch = row.get_batch_method()
              if batch is None:
                  continue
              # Add the batch ID
              batch.attrib['ID'] = text_type(batch_id)
              rows_by_batch_id[batch_id] = row
              batches.append(batch)
              batch_id += 1
  
          for row in self._deleted_rows:
              batch = E.Method(E.Field(text_type(row.id),
                                       Name='ID'),
                               ID=text_type(batch_id), Cmd='Delete')
              rows_by_batch_id[batch_id] = row
              batches.append(batch)
              batch_id += 1
  
          if len(batches) == 0:
              return
  
          response = self.opener.post_soap(LIST_WEBSERVICE, xml,
                                           soapaction='http://schemas.microsoft.com/sharepoint/soap/UpdateListItems')
  
          for result in response.xpath('.//sp:Result', namespaces=namespaces):
              batch_id, batch_result = result.attrib['ID'].split(',')
              row = rows_by_batch_id[int(batch_id)]
  
              error_code = result.find('sp:ErrorCode', namespaces=namespaces)
              error_text = result.find('sp:ErrorText', namespaces=namespaces)
              if error_code is not None and error_code.text != '0x00000000':
                  raise UpdateFailedError(row, batch_result,
                                          error_code.text,
                                          error_text.text)
  
              if batch_result in ('Update', 'New'):
                  row._update(result.xpath('z:row', namespaces=namespaces)[0],
                              clear=True)
              else:
                  self._deleted_rows.remove(row)
  
          assert not self._deleted_rows
          assert not any(row._changed for row in self.rows)