dsml.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. """
  2. dsml - generate and parse DSMLv1 data
  3. (see http://www.oasis-open.org/committees/dsml/)
  4. See http://www.python-ldap.org/ for details.
  5. $Id: dsml.py,v 1.23 2011/11/27 15:27:00 stroeder Exp $
  6. Python compability note:
  7. Tested with Python 2.0+.
  8. """
  9. __version__ = '2.4.6'
  10. import string,base64
  11. special_entities = (
  12. ('&','&'),
  13. ('<','&lt;'),
  14. ('"','&quot;'),
  15. ("'",'&apos;'),
  16. )
  17. def replace_char(s):
  18. for char,entity in special_entities:
  19. s = string.replace(s,char,entity)
  20. return s
  21. class DSMLWriter:
  22. """
  23. Class for writing LDAP entry records to a DSMLv1 file.
  24. Arguments:
  25. f
  26. File object for output.
  27. base64_attrs
  28. Attribute types to be base64-encoded.
  29. dsml_comment
  30. Text placed in comment lines behind <dsml:dsml>.
  31. indent
  32. String used for indentiation of next nested level.
  33. """
  34. def __init__(
  35. self,f,base64_attrs=[],dsml_comment='',indent=' '
  36. ):
  37. self._output_file = f
  38. self._base64_attrs = {}.fromkeys(map(string.lower,base64_attrs))
  39. self._dsml_comment = dsml_comment
  40. self._indent = indent
  41. def _needs_base64_encoding(self,attr_type,attr_value):
  42. if self._base64_attrs:
  43. return self._base64_attrs.has_key(string.lower(attr_type))
  44. else:
  45. try:
  46. unicode(attr_value,'utf-8')
  47. except UnicodeError:
  48. return 1
  49. else:
  50. return 0
  51. def writeHeader(self):
  52. """
  53. Write the header
  54. """
  55. self._output_file.write('\n'.join([
  56. '<?xml version="1.0" encoding="UTF-8"?>',
  57. '<!DOCTYPE root PUBLIC "dsml.dtd" "http://www.dsml.org/1.0/dsml.dtd">',
  58. '<dsml:dsml xmlns:dsml="http://www.dsml.org/DSML">',
  59. '%s<dsml:directory-entries>\n' % (self._indent),
  60. ])
  61. )
  62. if self._dsml_comment:
  63. self._output_file.write('%s<!--\n' % (self._indent))
  64. self._output_file.write('%s%s\n' % (self._indent,self._dsml_comment))
  65. self._output_file.write('%s-->\n' % (self._indent))
  66. def writeFooter(self):
  67. """
  68. Write the footer
  69. """
  70. self._output_file.write('%s</dsml:directory-entries>\n' % (self._indent))
  71. self._output_file.write('</dsml:dsml>\n')
  72. def unparse(self,dn,entry):
  73. return self.writeRecord(dn,entry)
  74. def writeRecord(self,dn,entry):
  75. """
  76. dn
  77. string-representation of distinguished name
  78. entry
  79. dictionary holding the LDAP entry {attr:data}
  80. """
  81. # Write line dn: first
  82. self._output_file.write(
  83. '%s<dsml:entry dn="%s">\n' % (
  84. self._indent*2,replace_char(dn)
  85. )
  86. )
  87. objectclasses = entry.get('objectclass',entry.get('objectClass',[]))
  88. self._output_file.write('%s<dsml:objectclass>\n' % (self._indent*3))
  89. for oc in objectclasses:
  90. self._output_file.write('%s<dsml:oc-value>%s</dsml:oc-value>\n' % (self._indent*4,oc))
  91. self._output_file.write('%s</dsml:objectclass>\n' % (self._indent*3))
  92. attr_types = entry.keys()[:]
  93. try:
  94. attr_types.remove('objectclass')
  95. attr_types.remove('objectClass')
  96. except ValueError:
  97. pass
  98. attr_types.sort()
  99. for attr_type in attr_types:
  100. self._output_file.write('%s<dsml:attr name="%s">\n' % (self._indent*3,attr_type))
  101. for attr_value_item in entry[attr_type]:
  102. needs_base64_encoding = self._needs_base64_encoding(
  103. attr_type,attr_value_item
  104. )
  105. if needs_base64_encoding:
  106. attr_value_item = base64.encodestring(attr_value_item)
  107. else:
  108. attr_value_item = replace_char(attr_value_item)
  109. self._output_file.write('%s<dsml:value%s>\n' % (
  110. self._indent*4,
  111. ' encoding="base64"'*needs_base64_encoding
  112. )
  113. )
  114. self._output_file.write('%s%s\n' % (
  115. self._indent*5,
  116. attr_value_item
  117. )
  118. )
  119. self._output_file.write('%s</dsml:value>\n' % (
  120. self._indent*4,
  121. )
  122. )
  123. self._output_file.write('%s</dsml:attr>\n' % (self._indent*3))
  124. self._output_file.write('%s</dsml:entry>\n' % (self._indent*2))
  125. return
  126. try:
  127. import xml.sax,xml.sax.handler
  128. except ImportError:
  129. pass
  130. else:
  131. class DSMLv1Handler(xml.sax.handler.ContentHandler):
  132. """
  133. Content handler class for DSMLv1
  134. """
  135. def __init__(self,parser_instance):
  136. self._parser_instance = parser_instance
  137. xml.sax.handler.ContentHandler.__init__(self)
  138. def startDocument(self):
  139. pass
  140. def endDocument(self):
  141. pass
  142. def startElement(self,raw_name,attrs):
  143. assert raw_name.startswith(''),'Illegal name'
  144. name = raw_name[5:]
  145. if name=='dsml':
  146. pass
  147. elif name=='directory-entries':
  148. self._parsing_entries = 1
  149. elif name=='entry':
  150. self._dn = attrs['dn']
  151. self._entry = {}
  152. elif name=='attr':
  153. self._attr_type = attrs['name'].encode('utf-8')
  154. self._attr_values = []
  155. elif name=='value':
  156. self._attr_value = ''
  157. self._base64_encoding = attrs.get('encoding','').lower()=='base64'
  158. # Handle object class tags
  159. elif name=='objectclass':
  160. self._object_classes = []
  161. elif name=='oc-value':
  162. self._oc_value = ''
  163. # Unhandled tags
  164. else:
  165. raise ValueError,'Unknown tag %s' % (raw_name)
  166. def endElement(self,raw_name):
  167. assert raw_name.startswith('dsml:'),'Illegal name'
  168. name = raw_name[5:]
  169. if name=='dsml':
  170. pass
  171. elif name=='directory-entries':
  172. self._parsing_entries = 0
  173. elif name=='entry':
  174. self._parser_instance.handle(self._dn,self._entry)
  175. del self._dn
  176. del self._entry
  177. elif name=='attr':
  178. self._entry[self._attr_type] = self._attr_values
  179. del self._attr_type
  180. del self._attr_values
  181. elif name=='value':
  182. if self._base64_encoding:
  183. attr_value = base64.decodestring(self._attr_value.strip())
  184. else:
  185. attr_value = self._attr_value.strip().encode('utf-8')
  186. self._attr_values.append(attr_value)
  187. del attr_value
  188. del self._attr_value
  189. del self._base64_encoding
  190. # Handle object class tags
  191. elif name=='objectclass':
  192. self._entry['objectClass'] = self._object_classes
  193. del self._object_classes
  194. elif name=='oc-value':
  195. self._object_classes.append(self._oc_value.strip().encode('utf-8'))
  196. del self._oc_value
  197. # Unhandled tags
  198. else:
  199. raise ValueError,'Unknown tag %s' % (raw_name)
  200. def characters(self,ch):
  201. if self.__dict__.has_key('_oc_value'):
  202. self._oc_value = self._oc_value + ch
  203. elif self.__dict__.has_key('_attr_value'):
  204. self._attr_value = self._attr_value + ch
  205. else:
  206. pass
  207. class DSMLParser:
  208. """
  209. Base class for a DSMLv1 parser. Applications should sub-class this
  210. class and override method handle() to implement something meaningful.
  211. Public class attributes:
  212. records_read
  213. Counter for records processed so far
  214. Arguments:
  215. input_file
  216. File-object to read the DSMLv1 input from
  217. ignored_attr_types
  218. Attributes with these attribute type names will be ignored.
  219. max_entries
  220. If non-zero specifies the maximum number of entries to be
  221. read from f.
  222. line_sep
  223. String used as line separator
  224. """
  225. def __init__(
  226. self,
  227. input_file,
  228. ContentHandlerClass,
  229. ignored_attr_types=None,
  230. max_entries=0,
  231. ):
  232. self._input_file = input_file
  233. self._max_entries = max_entries
  234. self._ignored_attr_types = {}.fromkeys(map(string.lower,(ignored_attr_types or [])))
  235. self._current_record = None,None
  236. self.records_read = 0
  237. self._parser = xml.sax.make_parser()
  238. self._parser.setFeature(xml.sax.handler.feature_namespaces,0)
  239. content_handler = ContentHandlerClass(self)
  240. self._parser.setContentHandler(content_handler)
  241. def handle(self,*args,**kwargs):
  242. """
  243. Process a single DSMLv1 entry record. This method should be
  244. implemented by applications using DSMLParser.
  245. """
  246. import pprint
  247. pprint.pprint(args)
  248. pprint.pprint(kwargs)
  249. def parse(self):
  250. """
  251. Continously read and parse DSML records
  252. """
  253. self._parser.parse(self._input_file)