LDIF (LDAP Data Interchange Format)
###################################

LDIF is a data interchange format for LDAP. It is defined in RFC2849 (June 2000) in two different flavours: *LDIF-CONTENT* and *LDIF-CHANGE*.
LDIF-CONTENT is used to describe LDAP entries in an ASCII stream (i.e. a file), while LDIF-CHANGE is used to describe Add, Delete, Modify and
ModifyDn operations. *These two formats have different purposes and cannot be mixed in the same stream.*
If the DN of the entry or an attribute value contains any unicode character the value must be base64 encoded, as specified in RFC2849.
ldap3 is compliant to the latest LDIF format (version: 1).

ldap3 can stream the output of a search (LDIF-CONTENT) or of an operation (LDIF-CHANGE) to a file object.
It cannot read a file with LDIF content because this is not defined in the LDAPv3 standard.
Each LDAP server has its own tools to import an LDIF-CONTENT or to perform LDIF-CHANGE.


LDIF-CONTENT
============

You can use the LDIF-CONTENT flavour with any search result::

    ...
    # request a few objects from the ldap server
    result = c.search('o=test','(cn=test-ldif*)', SUBTREE, attributes = ['sn', 'objectClass'])
    ldif_output = c.response_to_ldif()
    ...


ldif_output will contain::

    version: 1
    dn: cn=test-ldif-1,o=test
    objectClass: inetOrgPerson
    objectClass: organizationalPerson
    objectClass: Person
    objectClass: ndsLoginProperties
    objectClass: Top
    sn: test-ldif-1

    dn: cn=test-ldif-2,o=test
    objectClass: inetOrgPerson
    objectClass: organizationalPerson
    objectClass: Person
    objectClass: ndsLoginProperties
    objectClass: Top
    sn:: dGVzdC1sZGlmLTItw6DDssO5


    # total number of entries: 2

you can even request a LDIF-CONTENT for a response you saved early::

        # request a few objects from the ldap server
        response1 = c.search('o=test','(cn=test-ldif*)', SUBTREE, attributes = ['sn', 'objectClass'])
        response2 = c.search('o=test','(!(cn=test-ldif*))', SUBTREE, attributes = ['sn', 'objectClass'])
        ldif_output = c.response_to_ldif(response1)

ldif_output will contain the LDIF representation of the response entries.

LDIF-CHANGE
===========

To get the LDIF representation of Add, Modify, Delete and ModifyDn operation you must use the LDIF strategy. With this strategy operations are
not executed on an LDAP server but are converted to an LDIF-CHANGE format that can be sent to an LDAP server.

For example::

    from ldap3 import Connection, LDIF
    connection = Connection(server = None, client_strategy = LDIF)  # no need of real LDAP server
    connection.open()
    connection.add('cn=test-add-operation,o=test'), 'inetOrgPerson',
                   {'objectClass': 'inetOrgPerson', 'sn': 'test-add', 'cn': 'test-add-operation'})


in connection.response you will find::

    version: 1
    dn: cn=test-add-operation,o=test
    changetype: add
    objectClass: inetorgperson
    sn: test-add
    cn: test-add-operation

A more complex modify operation (from the RFC2849 examples)::

    from ldap3 import MODIFY_ADD. MODIFY_DELETE, MODIFY_REPLACE
    connection.open()
    connection.modify('cn=Paula Jensen,ou=Product Development,dc=airius,dc=com',
        {'postaladdress': (MODIFY_ADD, ['123 Anystreet $ Sunnyvale, CA $ 94086']),
         'description': (MODIFY_DELETE, []),
         'telephonenumber': (MODIFY_REPLACE, ['+1 408 555 1234', '+1 408 555 5678']),
         'facsimiletelephonenumber': (MODIFY_DELETE, ['+1 408 555 9876'])
        })

    returns:

    version: 1
    dn: cn=Paula Jensen,ou=Product Development,dc=airius,dc=com
    changetype: modify
    add: postaladdress
    postaladdress: 123 Anystreet $ Sunnyvale, CA $ 94086
    -
    delete: description
    -
    replace: telephonenumber
    telephonenumber: +1 408 555 1234
    telephonenumber: +1 408 555 5678
    -
    delete: facsimiletelephonenumber
    facsimiletelephonenumber: +1 408 555 9876
    -

Streaming the output to a file
==============================
When producing LDIF-CONTENT output you can have all operation results in a single stream. To get this simply set the stream attribute of the Connection to a stream object (for example to a file) and  *open* the connection.
If you don't specify the stream object a StringIO will be used. You can get the value with the c.stream.getvalue() method::

    from ldap3 import Connection, LDIF
    c = Connection(None, client_strategy=LDIF)
    with c:
        c.delete('cn=test1, o=test')
        c.delete('cn=test2, o=test')
        result = c.stream.getvalue()  # needed because the stream is closed when the connection exits the context

result will be::

    version: 1

    dn: cn=test1,o=test
    changetype: delete

    dn: cn=test2,o=test
    changetype: delete


If you just define a file object as stream you'll find the output in the file::

    c = Connection(None, client_strategy=LDIF)
    c.stream = open('output.ldif', 'w')
    with c:
        c.delete('cn=test1, o= test')
        c.delete('cn=test2, o=test')

you will find the LDIF output in the output.ldif file.


When producing LDIF-CONTENT you can pass an existing stream object to the response_to_ldif() method to add the LDIF output to the stream. If the stream is empty the ldif version header will be added.

Custom line separator
=====================
The LDIF stream uses the default line separator (os.linesep) of the system where ldap3 is running as line separator in the LDIF stream.
If you need a different line separator you can specify it in the *c.strategy.line_separator* attribute::

    c.strategy.line_separator = '\\r\\n'


Customizable descriptor order
=============================
RFC 2849 doesn't specify any specific order for the lines in the LDIF output except than *version: 1* in the first line of the stream.
The library starts any new record with the dn and all subsequent *descriptor: value* lines are in the order they are received by the library.
This should no be an issue with an LDIF import in another system, but if you have problems you can force a specific order for the descriptors in any of the LDIF operation:
To achieve this you must set the c.strategy.order attribute to a dict where the keys are set to the names of the operations you want their resulting descriptor order is changed
and the value to a list of descriptor. The LDIF output lines will be ordered following the order of the descriptor in the list.
For example if you add to the previous code::

    c.strategy.order = dict(delRequest = ['changetype:', 'dn:'])

you will get::

    version: 1

    changetype: delete
    dn: cn=test1,o=test

    changetype: delete
    dn: cn=test2,o=test

The possible operation names are: addRequest, delRequest, modifyRequest, modDNRequest.

To change the order of a searchRequest just pass the list in the requested order.
