dsml.py 8.2 KB

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