2

I'm using zeep to handle some SOAP requests in Python and I've come across a situation for which I couldn't find any documentation (neither here, on SO, or their official docs).

First of, I created a factory:

factory = client.type_factory('ns0')

Some of the attributes of this factory looks like this:

In [76]: factory.IctAdditionalInfos()
Out[76]: 
{
    'item': []
}

Inside the item I can have the following data:

In [79]: factory.IctAdditionalInfos(item={})
Out[79]: 
{
    'item': {
        'PersonId': None,
        'PersonIdExt': None,
        'Sex': None,
        'FirstName': None,
        'LastName': None,
        'Telephone': None,
        'MobilePhone': None,
        'Fax': None,
        'Email': None
    }
}

Now, what I want to happen, and I couldn't get my head around, is:

  • when trying to build and return the XML instead of sending it to the server using

    client.create_message(service, wsdl_interface_name, **self.data)

(self.data contains IctAdditionalInfos) and when factory.IctAdditionalInfos doesn't contain any data in item, I'd like to get:

<IctAdditionalInfos></IctAdditionalInfos>

or

<IctAdditionalInfos/>

Instead of the above I get:

<IctAdditionalInfos>
    <item/>
</IctAdditionalInfos>

because item is mandatory.

The wsdl definition of IctIncidentAdditionalInfos looks like this:

<xsd:complexType name="IctIncidentAdditionalInfos">
  <xsd:sequence>
    <xsd:element name="item" type="IctIncidentAdditionalInfo" minOccurs="0" maxOccurs="unbounded"/>
  </xsd:sequence>
</xsd:complexType>

How can I achieve this behaviour using zeep?

Grajdeanu Alex
  • 388
  • 6
  • 20
  • this might help - https://www.infopathdev.com/blogs/greg/archive/2004/09/16/The-Difference-Between-Optional-and-Not-Required.aspx – Ashish Shetkar Jan 16 '19 at 10:42

1 Answers1

1

If the response from the service always returns a mandatory <item/> tag, you can pass the response as an argument to a function that removes the empty <item/> tag and returns the desired xml. There's not much you can do with zeep here as their API does not provide any functionality to modify the response.

The best way to achieve the above is to create two functions. The first one returns the xml string from the message and the second one scans this string for empty <item/> tags. If the latter finds empty tags, it modifies the string as required otherwise returns the same xml string again. I've used the lxml library to parse the xml.

import lxml.etree as et

#retrieve xml string
def create_message():
    #your code here#
    node = client.create_message(service, wsdl_interface_name, **self.data)     
    return node

#resolve empty <item/> xml tags
def resolve_empty_xml_tag(xml_string):   
    tree = et.fromstring(xml_string)
    #traverse DOM for <item/> tag
    for elem in tree.xpath("//item"):
        #check for empty tag
        if elem.text is None:
            elem.getparent().remove(elem)
            new_xml = et.tostring(tree, pretty_print=True,encoding='unicode')
            new_xml_splitted = new_xml.split('\n')
            final_xml = [i.strip(' ') for i in new_xml_splitted if i is not '']
            return ''.join(final_xml)
    xml_string = et.tostring(tree, pretty_print=True,encoding='unicode')
    return xml_string

#function calls
node = create_message()
resolved_xml_string = resolve_empty_xml_tag(node)

#print to verify xml string
print(resolved_xml_string)

When I pass an xml string with an empty <item/> tag to the resolve_empty_xml_tag(xml_string) function, I get the desired result:

xml = """<IctAdditionalInfos>
         <item/>
    </IctAdditionalInfos>"""
print(resolve_empty_xml_tag(xml))

#Output
<IctAdditionalInfos></IctAdditionalInfos>

When I pass an xml string with an <item/> tag which has some data, I get the same xml string:

xml = """<IctAdditionalInfos>
           <item>abc</item>
         </IctAdditionalInfos>"""
print(resolve_empty_xml_tag(xml))

#Output
<IctAdditionalInfos>
   <item>abc</item>
 </IctAdditionalInfos>
amanb
  • 5,276
  • 3
  • 19
  • 38