Joe F.
Joe F.

Reputation: 867

XML Formatting Issue

I am using the Salesforce SOAP API for the first time, and so I'm not familiar with SOAP formatting issues, etc. I am using the lxml library to generate the XML, but seem to have a formatting issue.

The error I receive is: "The child of the Envelope element must be either a Header or Body element" which is strange, because when I review the XML generated by my SalesforceLeadConverter.build_xml() method, it looks right. This is the

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <Header>
      <ns0:SessionHeader xmlns:ns0="urn">
          <ns0:sessionId>ldfkjskjdfksdfsdfsdf</ns0:sessionId>
      </ns0:SessionHeader>
   </Header>
   <Body>
      <ns1:convertLead xmlns:ns1="urn">
           <ns1:leadConverts>
               <ns1:leadId>00Qj000000PMV3h</ns1:leadId>
               <ns1:doNotCreateOpportunity>False</ns1:doNotCreateOpportunity>
               <ns1:sendNotificationEmail>False</ns1:sendNotificationEmail>
           </ns1:leadConverts>
       </ns1:convertLead>
   </Body>
</soapenv:Envelope>

Here is the full class and associated method for generating the XML:

from lxml import etree

class SalesforceLeadConverter(object):

    def __init__(self, session_id, lead_id, **kwargs):
        """ Provides functionality for converting a Lead to a new or existing
        Account and create a new Contact or update an existing Contact.

        account_id: Optional; if specified, converts the Lead to a Contact
        associated with this Account.

        contact_id: Optional; if specified, converts the Lead into an existing
        Contact record, preventing the creation of a duplicate.
        """

        self.session_id = session_id
        self.lead_id = lead_id
        self.account_id = kwargs.get('account_id', False)
        self.contact_id = kwargs.get('contact_id', False)
        self.converted_status = kwargs.get('converted_status', False)
        self.do_not_create_opportunity = str(kwargs.get('do_not_create_opportunity', False))
        self.opportunity_name = kwargs.get('opportunity_name', False)
        self.owner_id = kwargs.get('owner_id', False)
        self.send_notification_email = str(kwargs.get('send_notification_email', False))

    def build_xml(self):
        S_NS = 'http://schemas.xmlsoap.org/soap/envelope/'
        S_PRE = '{' + S_NS + '}'
        root = etree.Element(S_PRE + 'Envelope', nsmap={'soapenv': S_NS})
        soapenv = etree.SubElement(root, 'Header')
        header = etree.SubElement(soapenv, '{urn}SessionHeader')
        sid = etree.SubElement(header, '{urn}sessionId').text=self.session_id
        soapenv2 = etree.SubElement(root, 'Body')
        urn2 = etree.SubElement(soapenv2, '{urn}convertLead')
        lead_converts = etree.SubElement(urn2, '{urn}leadConverts')
        lead_id = etree.SubElement(
            lead_converts,
            '{urn}leadId'
            ).text=self.lead_id
        do_not_create_opportunity = etree.SubElement(
            lead_converts,
            '{urn}doNotCreateOpportunity'
            ).text=self.do_not_create_opportunity
        send_notification_email = etree.SubElement(
            lead_converts,
            '{urn}sendNotificationEmail'
            ).text=self.send_notification_email
        xml_meta = """<?xml version="1.1" encoding="utf-8"?>"""

        return xml_meta + etree.tostring(root, encoding='utf-8')

Upvotes: 0

Views: 219

Answers (3)

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns0="your_namespace_here" xmlns:ns1="your_namespace_here">

Upvotes: 0

Joe F.
Joe F.

Reputation: 867

The modified SalesforceLeadConverter class below resolved the header/body namespace issue (kudos to MGorgon for pointing me in the right direction). This StackOverflow post ultimately lead me to the right solution.

Note that while the namespace issue was resolved, the code below still doesn't work...there is another issue that popped up, where I'm getting the following error:

"No operation available for request {enterprise.soap.sforce.com}convertLead"

Anyway, here's my soap.py file:

import requests
from lxml.etree import Element, SubElement, tostring


class SalesforceLeadConverter(object):

    def __init__(self, session_id, lead_id, **kwargs):
        """ Provides functionality for converting a Lead to a new or existing
        Account and create a new Contact or update an existing Contact.

        account_id: Optional; if specified, converts the Lead to a Contact
        associated with this Account.

        contact_id: Optional; if specified, converts the Lead into an existing
        Contact record, preventing the creation of a duplicate.
        """

        self.session_id = session_id
        self.lead_id = lead_id
        self.account_id = kwargs.get('account_id', False)
        self.contact_id = kwargs.get('contact_id', False)
        self.converted_status = kwargs.get('converted_status', False)
        self.do_not_create_opportunity = str(kwargs.get('do_not_create_opportunity', False))
        self.opportunity_name = kwargs.get('opportunity_name', False)
        self.owner_id = kwargs.get('owner_id', False)
        self.send_notification_email = str(kwargs.get('send_notification_email', False))

    def build_xml(self):
        schema_ns = 'http://schemas.xmlsoap.org/soap/envelope/'
        urn_ns = 'enterprise.soap.sforce.com'
        S_PRE = '{' + schema_ns + '}'
        envelope = Element(
            S_PRE + 'Envelope',
            nsmap={'soapenv': schema_ns, 'urn': urn_ns}
            )
        header = SubElement(envelope, '{%s}Header' % schema_ns)
        s_header = SubElement(header, '{%s}SessionHeader' % urn_ns)
        sid = SubElement(s_header, '{%s}sessionId' % urn_ns).text=self.session_id
        body = SubElement(envelope, '{%s}Body' % schema_ns)
        convert_lead = SubElement(body, '{%s}convertLead' % urn_ns)
        lead_converts = SubElement(convert_lead, '{%s}leadConverts' % urn_ns)
        lead_id = SubElement(
            lead_converts,
            '{%s}leadId' % urn_ns
            ).text=self.lead_id
        do_not_create_opportunity = SubElement(
            lead_converts,
            '{%s}doNotCreateOpportunity' % urn_ns
            ).text=self.do_not_create_opportunity
        send_notification_email = SubElement(
            lead_converts,
            '{%s}sendNotificationEmail' % urn_ns
            ).text=self.send_notification_email

        if self.account_id:
            account_id = SubElement(
                lead_converts,
                '{%s}accountId' % urn_ns
                ).text=self.account_id
        if self.contact_id:
            contact_id = SubElement(
                lead_converts,
                '{%s}contactId' % urn_ns
                ).text=self.contact_id
        if self.converted_status:
            converted_status = SubElement(
                lead_converts,
                '{%s}convertedStatus' % urn_ns
                ).text=self.converted_status
        xml_meta = """<?xml version="1.1" encoding="utf-8"?>"""

        return xml_meta + tostring(envelope, encoding='utf-8')

    def post(self):
        xml = self.build_xml()
        headers = {'Content-Type':'text/xml', 'SOAPAction':'convertLead'}
        url = 'https://na1.salesforce.com/services/Soap/u/34.0'
        out = requests.post(url, data=xml, headers=headers)
        return out, out.text

Upvotes: 0

MGorgon
MGorgon

Reputation: 2607

SOAP Body and Header needs a namespace. Also your elements prefixed by ns0, ns1 neads a namespace declaration too.

So your valid SOAP will be:

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns0="your_namespace_here" xmlns:ns1="your_namespace_here">
   <soapenv:Header>
      <ns0:SessionHeader xmlns:ns0="urn">
          <ns0:sessionId>ldfkjskjdfksdfsdfsdf</ns0:sessionId>
      </ns0:SessionHeader>
   </soapenv:Header>
   <soapenv:Body>
      <ns1:convertLead xmlns:ns1="urn">
           <ns1:leadConverts>
               <ns1:leadId>00Qj000000PMV3h</ns1:leadId>
               <ns1:doNotCreateOpportunity>False</ns1:doNotCreateOpportunity>
               <ns1:sendNotificationEmail>False</ns1:sendNotificationEmail>
           </ns1:leadConverts>
       </ns1:convertLead>
   </soapenv:Body>
</soapenv:Envelope>

Upvotes: 2

Related Questions