Add timestamp offset for block header
[p2pool.git] / SOAPpy / Parser.py
1 # SOAPpy modules
2 from Config    import Config
3 from Types     import *
4 from NS        import NS
5 from Utilities import *
6
7 import string
8 import fpconst
9 import xml.sax
10 from wstools.XMLname import fromXMLname
11
12 try: from M2Crypto import SSL
13 except: pass
14
15 ident = '$Id: Parser.py 1497 2010-03-08 06:06:52Z pooryorick $'
16 from version import __version__
17
18
19 ################################################################################
20 # SOAP Parser
21 ################################################################################
22 class RefHolder:
23     def __init__(self, name, frame):
24         self.name = name
25         self.parent = frame
26         self.pos = len(frame)
27         self.subpos = frame.namecounts.get(name, 0)
28
29     def __repr__(self):
30         return "<%s %s at %d>" % (self.__class__, self.name, id(self))
31
32     def __str__(self):
33         return "<%s %s at %d>" % (self.__class__, self.name, id(self))
34
35 class SOAPParser(xml.sax.handler.ContentHandler):
36     class Frame:
37         def __init__(self, name, kind = None, attrs = {}, rules = {}):
38             self.name = name
39             self.kind = kind
40             self.attrs = attrs
41             self.rules = rules
42
43             self.contents = []
44             self.names = []
45             self.namecounts = {}
46             self.subattrs = []
47
48         def append(self, name, data, attrs):
49             self.names.append(name)
50             self.contents.append(data)
51             self.subattrs.append(attrs)
52
53             if self.namecounts.has_key(name):
54                 self.namecounts[name] += 1
55             else:
56                 self.namecounts[name] = 1
57
58         def _placeItem(self, name, value, pos, subpos = 0, attrs = None):
59             self.contents[pos] = value
60
61             if attrs:
62                 self.attrs.update(attrs)
63
64         def __len__(self):
65             return len(self.contents)
66
67         def __repr__(self):
68             return "<%s %s at %d>" % (self.__class__, self.name, id(self))
69
70     def __init__(self, rules = None):
71         xml.sax.handler.ContentHandler.__init__(self)
72         self.body       = None
73         self.header     = None
74         self.attrs      = {}
75         self._data      = None
76         self._next      = "E" # Keeping state for message validity
77         self._stack     = [self.Frame('SOAP')]
78
79         # Make two dictionaries to store the prefix <-> URI mappings, and
80         # initialize them with the default
81         self._prem      = {NS.XML_T: NS.XML}
82         self._prem_r    = {NS.XML: NS.XML_T}
83         self._ids       = {}
84         self._refs      = {}
85         self._rules    = rules
86
87     def startElementNS(self, name, qname, attrs):
88
89         def toStr( name ):
90             prefix = name[0]
91             tag    = name[1]
92             if self._prem_r.has_key(prefix):
93                tag = self._prem_r[name[0]] + ':' + name[1]
94             elif prefix:
95                tag = prefix + ":" + tag
96             return tag
97         
98         # Workaround two sax bugs
99         if name[0] == None and name[1][0] == ' ':
100             name = (None, name[1][1:])
101         else:
102             name = tuple(name)
103
104         # First some checking of the layout of the message
105
106         if self._next == "E":
107             if name[1] != 'Envelope':
108                 raise Error, "expected `SOAP-ENV:Envelope', " \
109                     "got `%s'" % toStr( name )
110             if name[0] != NS.ENV:
111                 raise faultType, ("%s:VersionMismatch" % NS.ENV_T,
112                     "Don't understand version `%s' Envelope" % name[0])
113             else:
114                 self._next = "HorB"
115         elif self._next == "HorB":
116             if name[0] == NS.ENV and name[1] in ("Header", "Body"):
117                 self._next = None
118             else:
119                 raise Error, \
120                     "expected `SOAP-ENV:Header' or `SOAP-ENV:Body', " \
121                     "got `%s'" % toStr( name )
122         elif self._next == "B":
123             if name == (NS.ENV, "Body"):
124                 self._next = None
125             else:
126                 raise Error, "expected `SOAP-ENV:Body', " \
127                       "got `%s'" % toStr( name )
128         elif self._next == "":
129             raise Error, "expected nothing, " \
130                   "got `%s'" % toStr( name )
131                   
132
133         if len(self._stack) == 2:
134             rules = self._rules
135         else:
136             try:
137                 rules = self._stack[-1].rules[name[1]]
138             except:
139                 rules = None
140
141         if type(rules) not in (NoneType, DictType):
142             kind = rules
143         else:
144             kind = attrs.get((NS.ENC, 'arrayType'))
145
146             if kind != None:
147                 del attrs._attrs[(NS.ENC, 'arrayType')]
148
149                 i = kind.find(':')
150                 if i >= 0:
151                     try:
152                         kind = (self._prem[kind[:i]], kind[i + 1:])
153                     except:
154                         kind = None
155                 else:
156                     kind = None
157
158         self.pushFrame(self.Frame(name[1], kind, attrs._attrs, rules))
159
160         self._data = [] # Start accumulating
161
162     def pushFrame(self, frame):
163         self._stack.append(frame)
164
165     def popFrame(self):
166         return self._stack.pop()
167
168     def endElementNS(self, name, qname):
169         # Workaround two sax bugs
170         if name[0] == None and name[1][0] == ' ':
171             ns, name = None, name[1][1:]
172         else:
173             ns, name = tuple(name)
174
175         name = fromXMLname(name) # convert to SOAP 1.2 XML name encoding
176
177         if self._next == "E":
178             raise Error, "didn't get SOAP-ENV:Envelope"
179         if self._next in ("HorB", "B"):
180             raise Error, "didn't get SOAP-ENV:Body"
181
182         cur = self.popFrame()
183         attrs = cur.attrs
184
185         idval = None
186
187         if attrs.has_key((None, 'id')):
188             idval = attrs[(None, 'id')]
189
190             if self._ids.has_key(idval):
191                 raise Error, "duplicate id `%s'" % idval
192
193             del attrs[(None, 'id')]
194
195         root = 1
196
197         if len(self._stack) == 3:
198             if attrs.has_key((NS.ENC, 'root')):
199                 root = int(attrs[(NS.ENC, 'root')])
200
201                 # Do some preliminary checks. First, if root="0" is present,
202                 # the element must have an id. Next, if root="n" is present,
203                 # n something other than 0 or 1, raise an exception.
204
205                 if root == 0:
206                     if idval == None:
207                         raise Error, "non-root element must have an id"
208                 elif root != 1:
209                     raise Error, "SOAP-ENC:root must be `0' or `1'"
210
211                 del attrs[(NS.ENC, 'root')]
212
213         while 1:
214             href = attrs.get((None, 'href'))
215             if href:
216                 if href[0] != '#':
217                     raise Error, "Non-local hrefs are not yet suppported."
218                 if self._data != None and \
219                    string.join(self._data, "").strip() != '':
220                     raise Error, "hrefs can't have data"
221
222                 href = href[1:]
223
224                 if self._ids.has_key(href):
225                     data = self._ids[href]
226                 else:
227                     data = RefHolder(name, self._stack[-1])
228
229                     if self._refs.has_key(href):
230                         self._refs[href].append(data)
231                     else:
232                         self._refs[href] = [data]
233
234                 del attrs[(None, 'href')]
235
236                 break
237
238             kind = None
239
240             if attrs:
241                 for i in NS.XSI_L:
242                     if attrs.has_key((i, 'type')):
243                         kind = attrs[(i, 'type')]
244                         del attrs[(i, 'type')]
245
246                 if kind != None:
247                     i = kind.find(':')
248                     if i >= 0:
249                         try:
250                             kind = (self._prem[kind[:i]], kind[i + 1:])
251                         except:
252                             kind = (None, kind)
253                     else:
254 # XXX What to do here? (None, kind) is just going to fail in convertType
255                         #print "Kind with no NS:", kind
256                         kind = (None, kind)
257
258             null = 0
259
260             if attrs:
261                 for i in (NS.XSI, NS.XSI2):
262                     if attrs.has_key((i, 'null')):
263                         null = attrs[(i, 'null')]
264                         del attrs[(i, 'null')]
265
266                 if attrs.has_key((NS.XSI3, 'nil')):
267                     null = attrs[(NS.XSI3, 'nil')]
268                     del attrs[(NS.XSI3, 'nil')]
269
270
271                 ## Check for nil
272
273                 # check for nil='true'
274                 if type(null) in (StringType, UnicodeType):
275                     if null.lower() == 'true':
276                         null = 1
277
278                 # check for nil=1, but watch out for string values
279                 try:                
280                     null = int(null)
281                 except ValueError, e:
282                     if not e[0].startswith("invalid literal for int()"):
283                         raise e
284                     null = 0
285
286                 if null:
287                     if len(cur) or \
288                         (self._data != None and string.join(self._data, "").strip() != ''):
289                         raise Error, "nils can't have data"
290
291                     data = None
292
293                     break
294
295             if len(self._stack) == 2:
296                 if (ns, name) == (NS.ENV, "Header"):
297                     self.header = data = headerType(attrs = attrs)
298                     self._next = "B"
299                     break
300                 elif (ns, name) == (NS.ENV, "Body"):
301                     self.body = data = bodyType(attrs = attrs)
302                     self._next = ""
303                     break
304             elif len(self._stack) == 3 and self._next == None:
305                 if (ns, name) == (NS.ENV, "Fault"):
306                     data = faultType()
307                     self._next = None # allow followons
308                     break
309
310             #print "\n"
311             #print "data=", self._data
312             #print "kind=", kind
313             #print "cur.kind=", cur.kind
314             #print "cur.rules=", cur.rules
315             #print "\n"
316                         
317
318             if cur.rules != None:
319                 rule = cur.rules
320
321                 if type(rule) in (StringType, UnicodeType):
322                     rule = (None, rule) # none flags special handling
323                 elif type(rule) == ListType:
324                     rule = tuple(rule)
325
326                 #print "kind=",kind
327                 #print "rule=",rule
328
329
330 # XXX What if rule != kind?
331                 if callable(rule):
332                     data = rule(string.join(self._data, ""))
333                 elif type(rule) == DictType:
334                     data = structType(name = (ns, name), attrs = attrs)
335                 elif rule[1][:9] == 'arrayType':
336                     data = self.convertType(cur.contents,
337                                             rule, attrs)
338                 else:
339                     data = self.convertType(string.join(self._data, ""),
340                                             rule, attrs)
341
342                 break
343
344             #print "No rules, using kind or cur.kind..."
345
346             if (kind == None and cur.kind != None) or \
347                 (kind == (NS.ENC, 'Array')):
348                 kind = cur.kind
349
350                 if kind == None:
351                     kind = 'ur-type[%d]' % len(cur)
352                 else:
353                     kind = kind[1]
354
355                 if len(cur.namecounts) == 1:
356                     elemsname = cur.names[0]
357                 else:
358                     elemsname = None
359
360                 data = self.startArray((ns, name), kind, attrs, elemsname)
361
362                 break
363
364             if len(self._stack) == 3 and kind == None and \
365                 len(cur) == 0 and \
366                 (self._data == None or string.join(self._data, "").strip() == ''):
367                 data = structType(name = (ns, name), attrs = attrs)
368                 break
369
370             if len(cur) == 0 and ns != NS.URN:
371                 # Nothing's been added to the current frame so it must be a
372                 # simple type.
373
374 #                 print "cur:", cur
375 #                 print "ns:", ns
376 #                 print "attrs:", attrs
377 #                 print "kind:", kind
378                 
379
380                 if kind == None:
381                     # If the current item's container is an array, it will
382                     # have a kind. If so, get the bit before the first [,
383                     # which is the type of the array, therefore the type of
384                     # the current item.
385
386                     kind = self._stack[-1].kind
387
388                     if kind != None:
389                         i = kind[1].find('[')
390                         if i >= 0:
391                             kind = (kind[0], kind[1][:i])
392                     elif ns != None:
393                         kind = (ns, name)
394
395                 if kind != None:
396                     try:
397                         data = self.convertType(string.join(self._data, ""),
398                                                 kind, attrs)
399                     except UnknownTypeError:
400                         data = None
401                 else:
402                     data = None
403
404                 if data == None:
405                     if self._data == None:
406                         data = ''
407                     else:
408                         data = string.join(self._data, "")
409
410                     if len(attrs) == 0:
411                         try: data = str(data)
412                         except: pass
413
414                 break
415
416             data = structType(name = (ns, name), attrs = attrs)
417
418             break
419
420         if isinstance(data, compoundType):
421             for i in range(len(cur)):
422                 v = cur.contents[i]
423                 data._addItem(cur.names[i], v, cur.subattrs[i])
424
425                 if isinstance(v, RefHolder):
426                     v.parent = data
427
428         if root:
429             self._stack[-1].append(name, data, attrs)
430
431         if idval != None:
432             self._ids[idval] = data
433
434             if self._refs.has_key(idval):
435                 for i in self._refs[idval]:
436                     i.parent._placeItem(i.name, data, i.pos, i.subpos, attrs)
437
438                 del self._refs[idval]
439
440         self.attrs[id(data)] = attrs
441
442         if isinstance(data, anyType):
443             data._setAttrs(attrs)
444
445         self._data = None       # Stop accumulating
446
447     def endDocument(self):
448         if len(self._refs) == 1:
449             raise Error, \
450                 "unresolved reference " + self._refs.keys()[0]
451         elif len(self._refs) > 1:
452             raise Error, \
453                 "unresolved references " + ', '.join(self._refs.keys())
454
455     def startPrefixMapping(self, prefix, uri):
456         self._prem[prefix] = uri
457         self._prem_r[uri] = prefix
458
459     def endPrefixMapping(self, prefix):
460         try:
461             del self._prem_r[self._prem[prefix]]
462             del self._prem[prefix]
463         except:
464             pass
465
466     def characters(self, c):
467         if self._data != None:
468             self._data.append(c)
469
470     arrayre = '^(?:(?P<ns>[^:]*):)?' \
471         '(?P<type>[^[]+)' \
472         '(?:\[(?P<rank>,*)\])?' \
473         '(?:\[(?P<asize>\d+(?:,\d+)*)?\])$'
474
475     def startArray(self, name, kind, attrs, elemsname):
476         if type(self.arrayre) == StringType:
477             self.arrayre = re.compile (self.arrayre)
478
479         offset = attrs.get((NS.ENC, "offset"))
480
481         if offset != None:
482             del attrs[(NS.ENC, "offset")]
483
484             try:
485                 if offset[0] == '[' and offset[-1] == ']':
486                     offset = int(offset[1:-1])
487                     if offset < 0:
488                         raise Exception
489                 else:
490                     raise Exception
491             except:
492                 raise AttributeError, "invalid Array offset"
493         else:
494             offset = 0
495
496         try:
497             m = self.arrayre.search(kind)
498
499             if m == None:
500                 raise Exception
501
502             t = m.group('type')
503
504             if t == 'ur-type':
505                 return arrayType(None, name, attrs, offset, m.group('rank'),
506                     m.group('asize'), elemsname)
507             elif m.group('ns') != None:
508                 return typedArrayType(None, name,
509                     (self._prem[m.group('ns')], t), attrs, offset,
510                     m.group('rank'), m.group('asize'), elemsname)
511             else:
512                 return typedArrayType(None, name, (None, t), attrs, offset,
513                     m.group('rank'), m.group('asize'), elemsname)
514         except:
515             raise AttributeError, "invalid Array type `%s'" % kind
516
517     # Conversion
518
519     class DATETIMECONSTS:
520         SIGNre = '(?P<sign>-?)'
521         CENTURYre = '(?P<century>\d{2,})'
522         YEARre = '(?P<year>\d{2})'
523         MONTHre = '(?P<month>\d{2})'
524         DAYre = '(?P<day>\d{2})'
525         HOURre = '(?P<hour>\d{2})'
526         MINUTEre = '(?P<minute>\d{2})'
527         SECONDre = '(?P<second>\d{2}(?:\.\d*)?)'
528         TIMEZONEre = '(?P<zulu>Z)|(?P<tzsign>[-+])(?P<tzhour>\d{2}):' \
529             '(?P<tzminute>\d{2})'
530         BOSre = '^\s*'
531         EOSre = '\s*$'
532
533         __allres = {'sign': SIGNre, 'century': CENTURYre, 'year': YEARre,
534             'month': MONTHre, 'day': DAYre, 'hour': HOURre,
535             'minute': MINUTEre, 'second': SECONDre, 'timezone': TIMEZONEre,
536             'b': BOSre, 'e': EOSre}
537
538         dateTime = '%(b)s%(sign)s%(century)s%(year)s-%(month)s-%(day)sT' \
539             '%(hour)s:%(minute)s:%(second)s(%(timezone)s)?%(e)s' % __allres
540         timeInstant = dateTime
541         timePeriod = dateTime
542         time = '%(b)s%(hour)s:%(minute)s:%(second)s(%(timezone)s)?%(e)s' % \
543             __allres
544         date = '%(b)s%(sign)s%(century)s%(year)s-%(month)s-%(day)s' \
545             '(%(timezone)s)?%(e)s' % __allres
546         century = '%(b)s%(sign)s%(century)s(%(timezone)s)?%(e)s' % __allres
547         gYearMonth = '%(b)s%(sign)s%(century)s%(year)s-%(month)s' \
548             '(%(timezone)s)?%(e)s' % __allres
549         gYear = '%(b)s%(sign)s%(century)s%(year)s(%(timezone)s)?%(e)s' % \
550             __allres
551         year = gYear
552         gMonthDay = '%(b)s--%(month)s-%(day)s(%(timezone)s)?%(e)s' % __allres
553         recurringDate = gMonthDay
554         gDay = '%(b)s---%(day)s(%(timezone)s)?%(e)s' % __allres
555         recurringDay = gDay
556         gMonth = '%(b)s--%(month)s--(%(timezone)s)?%(e)s' % __allres
557         month = gMonth
558
559         recurringInstant = '%(b)s%(sign)s(%(century)s|-)(%(year)s|-)-' \
560             '(%(month)s|-)-(%(day)s|-)T' \
561             '(%(hour)s|-):(%(minute)s|-):(%(second)s|-)' \
562             '(%(timezone)s)?%(e)s' % __allres
563
564         duration = '%(b)s%(sign)sP' \
565             '((?P<year>\d+)Y)?' \
566             '((?P<month>\d+)M)?' \
567             '((?P<day>\d+)D)?' \
568             '((?P<sep>T)' \
569             '((?P<hour>\d+)H)?' \
570             '((?P<minute>\d+)M)?' \
571             '((?P<second>\d*(?:\.\d*)?)S)?)?%(e)s' % \
572             __allres
573
574         timeDuration = duration
575
576         # The extra 31 on the front is:
577         # - so the tuple is 1-based
578         # - so months[month-1] is December's days if month is 1
579
580         months = (31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
581
582     def convertDateTime(self, value, kind):
583         def getZoneOffset(d):
584             zoffs = 0
585
586             try:
587                 if d['zulu'] == None:
588                     zoffs = 60 * int(d['tzhour']) + int(d['tzminute'])
589                     if d['tzsign'] != '-':
590                         zoffs = -zoffs
591             except TypeError:
592                 pass
593
594             return zoffs
595
596         def applyZoneOffset(months, zoffs, date, minfield, posday = 1):
597             if zoffs == 0 and (minfield > 4 or 0 <= date[5] < 60):
598                 return date
599
600             if minfield > 5: date[5] = 0
601             if minfield > 4: date[4] = 0
602
603             if date[5] < 0:
604                 date[4] += int(date[5]) / 60
605                 date[5] %= 60
606
607             date[4] += zoffs
608
609             if minfield > 3 or 0 <= date[4] < 60: return date
610
611             date[3] += date[4] / 60
612             date[4] %= 60
613
614             if minfield > 2 or 0 <= date[3] < 24: return date
615
616             date[2] += date[3] / 24
617             date[3] %= 24
618
619             if minfield > 1:
620                 if posday and date[2] <= 0:
621                     date[2] += 31       # zoffs is at most 99:59, so the
622                                         # day will never be less than -3
623                 return date
624
625             while 1:
626                 # The date[1] == 3 (instead of == 2) is because we're
627                 # going back a month, so we need to know if the previous
628                 # month is February, so we test if this month is March.
629
630                 leap = minfield == 0 and date[1] == 3 and \
631                     date[0] % 4 == 0 and \
632                     (date[0] % 100 != 0 or date[0] % 400 == 0)
633
634                 if 0 < date[2] <= months[date[1]] + leap: break
635
636                 date[2] += months[date[1] - 1] + leap
637
638                 date[1] -= 1
639
640                 if date[1] > 0: break
641
642                 date[1] = 12
643
644                 if minfield > 0: break
645
646                 date[0] -= 1
647
648             return date
649
650         try:
651             exp = getattr(self.DATETIMECONSTS, kind)
652         except AttributeError:
653             return None
654
655         if type(exp) == StringType:
656             exp = re.compile(exp)
657             setattr (self.DATETIMECONSTS, kind, exp)
658
659         m = exp.search(value)
660
661         try:
662             if m == None:
663                 raise Exception
664
665             d = m.groupdict()
666             f = ('century', 'year', 'month', 'day',
667                 'hour', 'minute', 'second')
668             fn = len(f)         # Index of first non-None value
669             r = []
670
671             if kind in ('duration', 'timeDuration'):
672                 if d['sep'] != None and d['hour'] == None and \
673                     d['minute'] == None and d['second'] == None:
674                     raise Exception
675
676                 f = f[1:]
677
678                 for i in range(len(f)):
679                     s = d[f[i]]
680
681                     if s != None:
682                         if f[i] == 'second':
683                             s = float(s)
684                         else:
685                             try: s = int(s)
686                             except ValueError: s = long(s)
687
688                         if i < fn: fn = i
689
690                     r.append(s)
691
692                 if fn > len(r):         # Any non-Nones?
693                     raise Exception
694
695                 if d['sign'] == '-':
696                     r[fn] = -r[fn]
697
698                 return tuple(r)
699
700             if kind == 'recurringInstant':
701                 for i in range(len(f)):
702                     s = d[f[i]]
703
704                     if s == None or s == '-':
705                         if i > fn:
706                             raise Exception
707                         s = None
708                     else:
709                         if i < fn:
710                             fn = i
711
712                         if f[i] == 'second':
713                             s = float(s)
714                         else:
715                             try:
716                                 s = int(s)
717                             except ValueError:
718                                 s = long(s)
719
720                     r.append(s)
721
722                 s = r.pop(0)
723
724                 if fn == 0:
725                     r[0] += s * 100
726                 else:
727                     fn -= 1
728
729                 if fn < len(r) and d['sign'] == '-':
730                     r[fn] = -r[fn]
731
732                 cleanDate(r, fn)
733
734                 return tuple(applyZoneOffset(self.DATETIMECONSTS.months,
735                     getZoneOffset(d), r, fn, 0))
736
737             r = [0, 0, 1, 1, 0, 0, 0]
738
739             for i in range(len(f)):
740                 field = f[i]
741
742                 s = d.get(field)
743
744                 if s != None:
745                     if field == 'second':
746                         s = float(s)
747                     else:
748                         try:
749                             s = int(s)
750                         except ValueError:
751                             s = long(s)
752
753                     if i < fn:
754                         fn = i
755
756                     r[i] = s
757
758             if fn > len(r):     # Any non-Nones?
759                 raise Exception
760
761             s = r.pop(0)
762
763             if fn == 0:
764                 r[0] += s * 100
765             else:
766                 fn -= 1
767
768             if d.get('sign') == '-':
769                 r[fn] = -r[fn]
770
771             cleanDate(r, fn)
772
773             zoffs = getZoneOffset(d)
774
775             if zoffs:
776                 r = applyZoneOffset(self.DATETIMECONSTS.months, zoffs, r, fn)
777
778             if kind == 'century':
779                 return r[0] / 100
780
781             s = []
782
783             for i in range(1, len(f)):
784                 if d.has_key(f[i]):
785                     s.append(r[i - 1])
786
787             if len(s) == 1:
788                 return s[0]
789             return tuple(s)
790         except Exception, e:
791             raise Error, "invalid %s value `%s' - %s" % (kind, value, e)
792
793     intlimits = \
794     {
795         'nonPositiveInteger':   (0, None, 0),
796         'non-positive-integer': (0, None, 0),
797         'negativeInteger':      (0, None, -1),
798         'negative-integer':     (0, None, -1),
799         'long':                 (1, -9223372036854775808L,
800                                     9223372036854775807L),
801         'int':                  (0, -2147483648L, 2147483647L),
802         'short':                (0, -32768, 32767),
803         'byte':                 (0, -128, 127),
804         'nonNegativeInteger':   (0, 0, None),
805         'non-negative-integer': (0, 0, None),
806         'positiveInteger':      (0, 1, None),
807         'positive-integer':     (0, 1, None),
808         'unsignedLong':         (1, 0, 18446744073709551615L),
809         'unsignedInt':          (0, 0, 4294967295L),
810         'unsignedShort':        (0, 0, 65535),
811         'unsignedByte':         (0, 0, 255),
812     }
813     floatlimits = \
814     {
815         'float':        (7.0064923216240861E-46, -3.4028234663852886E+38,
816                          3.4028234663852886E+38),
817         'double':       (2.4703282292062327E-324, -1.7976931348623158E+308,
818                          1.7976931348623157E+308),
819     }
820     zerofloatre = '[1-9]'
821
822
823     def convertType(self, d, t, attrs, config=Config):
824         if t[0] is None and t[1] is not None:
825             type = t[1].strip()
826             if type[:9] == 'arrayType':
827                 index_eq = type.find('=')
828                 index_obr = type.find('[')
829                 index_cbr = type.find(']')
830                 elemtype = type[index_eq+1:index_obr]
831                 elemnum  = type[index_obr+1:index_cbr]
832                 if elemtype=="ur-type":
833                     return(d)
834                 else:
835                     newarr = map( lambda(di):
836                                   self.convertToBasicTypes(d=di,
837                                                        t = ( NS.XSD, elemtype),
838                                                        attrs=attrs,
839                                                        config=config),
840                                   d)
841                     return newarr
842             else:
843                 t = (NS.XSD, t[1])
844
845         return self.convertToBasicTypes(d, t, attrs, config)
846
847
848     def convertToSOAPpyTypes(self, d, t, attrs, config=Config):
849         pass
850
851
852     def convertToBasicTypes(self, d, t, attrs, config=Config):
853         dnn = d or ''
854
855         #if Config.debug:
856             #print "convertToBasicTypes:"
857             #print "   requested_type=", t
858             #print "   data=", d
859
860
861 #         print "convertToBasicTypes:"
862 #         print "   requested_type=", t
863 #         print "   data=", d
864 #         print "   attrs=", attrs
865 #         print "   t[0]=", t[0]
866 #         print "   t[1]=", t[1]
867             
868 #         print "   in?", t[0] in NS.EXSD_L
869
870         if t[0] in NS.EXSD_L:
871             if t[1]=="integer": # unbounded integer type
872                 try:
873                     d = int(d)
874                     if len(attrs):
875                         d = long(d)
876                 except:
877                     d = long(d)
878                 return d
879             if self.intlimits.has_key (t[1]): # range-bounded integer types
880                 l = self.intlimits[t[1]]
881                 try: d = int(d)
882                 except: d = long(d)
883
884                 if l[1] != None and d < l[1]:
885                     raise UnderflowError, "%s too small" % d
886                 if l[2] != None and d > l[2]:
887                     raise OverflowError, "%s too large" % d
888
889                 if l[0] or len(attrs):
890                     return long(d)
891                 return d
892             if t[1] == "string":
893                 if len(attrs):
894                     return unicode(dnn)
895                 try:
896                     return str(dnn)
897                 except:
898                     return dnn
899             if t[1] in ("bool", "boolean"):
900                 d = d.strip().lower()
901                 if d in ('0', 'false'):
902                     return False
903                 if d in ('1', 'true'):
904                     return True
905                 raise AttributeError, "invalid boolean value"
906             if t[1] in ('double','float'):
907                 l = self.floatlimits[t[1]]
908                 s = d.strip().lower()
909
910                 # Explicitly check for NaN and Infinities
911                 if s == "nan":
912                     d = fpconst.NaN
913                 elif s[0:2]=="inf" or s[0:3]=="+inf":
914                     d = fpconst.PosInf
915                 elif s[0:3] == "-inf":
916                     d = fpconst.NegInf
917                 else :
918                     d = float(s)
919
920                 if config.strict_range:
921                     if fpconst.isNaN(d):
922                         if s[0:2] != 'nan':
923                             raise ValueError, "invalid %s: %s" % (t[1], s)
924                     elif fpconst.isNegInf(d):
925                         if s[0:3] != '-inf':
926                             raise UnderflowError, "%s too small: %s" % (t[1], s)
927                     elif fpconst.isPosInf(d):
928                         if s[0:2] != 'inf' and s[0:3] != '+inf':
929                             raise OverflowError, "%s too large: %s" % (t[1], s)
930                     elif d < 0 and d < l[1]:
931                             raise UnderflowError, "%s too small: %s" % (t[1], s)
932                     elif d > 0 and ( d < l[0] or d > l[2] ):
933                             raise OverflowError, "%s too large: %s" % (t[1], s)
934                     elif d == 0:
935                         if type(self.zerofloatre) == StringType:
936                             self.zerofloatre = re.compile(self.zerofloatre)
937     
938                         if self.zerofloatre.search(s):
939                             raise UnderflowError, "invalid %s: %s" % (t[1], s)
940                 return d
941             
942             if t[1] in ("dateTime", "date", "timeInstant", "time"):
943                 return self.convertDateTime(d, t[1])
944             if t[1] == "decimal":
945                 return float(d)
946             if t[1] in ("language", "QName", "NOTATION", "NMTOKEN", "Name",
947                 "NCName", "ID", "IDREF", "ENTITY"):
948                 return collapseWhiteSpace(d)
949             if t[1] in ("IDREFS", "ENTITIES", "NMTOKENS"):
950                 d = collapseWhiteSpace(d)
951                 return d.split()
952         if t[0] in NS.XSD_L:
953             if t[1] in ("base64", "base64Binary"):
954                 if d:
955                     return base64.decodestring(d)
956                 else:
957                     return ''
958             if t[1] == "hexBinary":
959                 if d:
960                     return decodeHexString(d)
961                 else:
962                     return
963             if t[1] == "anyURI":
964                 return urllib.unquote(collapseWhiteSpace(d))
965             if t[1] in ("normalizedString", "token"):
966                 return collapseWhiteSpace(d)
967         if t[0] == NS.ENC:
968             if t[1] == "base64":
969                 if d:
970                     return base64.decodestring(d)
971                 else:
972                     return ''
973         if t[0] == NS.XSD:
974             if t[1] == "binary":
975                 try:
976                     e = attrs[(None, 'encoding')]
977
978                     if d:
979                         if e == 'hex':
980                             return decodeHexString(d)
981                         elif e == 'base64':
982                             return base64.decodestring(d)
983                     else:
984                         return ''
985                 except:
986                     pass
987
988                 raise Error, "unknown or missing binary encoding"
989             if t[1] == "uri":
990                 return urllib.unquote(collapseWhiteSpace(d))
991             if t[1] == "recurringInstant":
992                 return self.convertDateTime(d, t[1])
993         if t[0] in (NS.XSD2, NS.ENC):
994             if t[1] == "uriReference":
995                 return urllib.unquote(collapseWhiteSpace(d))
996             if t[1] == "timePeriod":
997                 return self.convertDateTime(d, t[1])
998             if t[1] in ("century", "year"):
999                 return self.convertDateTime(d, t[1])
1000         if t[0] in (NS.XSD, NS.XSD2, NS.ENC):
1001             if t[1] == "timeDuration":
1002                 return self.convertDateTime(d, t[1])
1003         if t[0] == NS.XSD3:
1004             if t[1] == "anyURI":
1005                 return urllib.unquote(collapseWhiteSpace(d))
1006             if t[1] in ("gYearMonth", "gMonthDay"):
1007                 return self.convertDateTime(d, t[1])
1008             if t[1] == "gYear":
1009                 return self.convertDateTime(d, t[1])
1010             if t[1] == "gMonth":
1011                 return self.convertDateTime(d, t[1])
1012             if t[1] == "gDay":
1013                 return self.convertDateTime(d, t[1])
1014             if t[1] == "duration":
1015                 return self.convertDateTime(d, t[1])
1016         if t[0] in (NS.XSD2, NS.XSD3):
1017             if t[1] == "token":
1018                 return collapseWhiteSpace(d)
1019             if t[1] == "recurringDate":
1020                 return self.convertDateTime(d, t[1])
1021             if t[1] == "month":
1022                 return self.convertDateTime(d, t[1])
1023             if t[1] == "recurringDay":
1024                 return self.convertDateTime(d, t[1])
1025         if t[0] == NS.XSD2:
1026             if t[1] == "CDATA":
1027                 return collapseWhiteSpace(d)
1028
1029         raise UnknownTypeError, "unknown type `%s'" % (str(t[0]) + ':' + t[1])
1030
1031
1032 ################################################################################
1033 # call to SOAPParser that keeps all of the info
1034 ################################################################################
1035 def _parseSOAP(xml_str, rules = None):
1036     try:
1037         from cStringIO import StringIO
1038     except ImportError:
1039         from StringIO import StringIO
1040
1041     parser = xml.sax.make_parser()
1042     t = SOAPParser(rules = rules)
1043     parser.setContentHandler(t)
1044     e = xml.sax.handler.ErrorHandler()
1045     parser.setErrorHandler(e)
1046
1047     inpsrc = xml.sax.xmlreader.InputSource()
1048     inpsrc.setByteStream(StringIO(xml_str))
1049
1050     # turn on namespace mangeling
1051     parser.setFeature(xml.sax.handler.feature_namespaces,1)
1052
1053     try:
1054         parser.parse(inpsrc)
1055     except xml.sax.SAXParseException, e:
1056         parser._parser = None
1057         raise e
1058     
1059     return t
1060
1061 ################################################################################
1062 # SOAPParser's more public interface
1063 ################################################################################
1064 def parseSOAP(xml_str, attrs = 0):
1065     t = _parseSOAP(xml_str)
1066
1067     if attrs:
1068         return t.body, t.attrs
1069     return t.body
1070
1071
1072 def parseSOAPRPC(xml_str, header = 0, body = 0, attrs = 0, rules = None):
1073
1074     t = _parseSOAP(xml_str, rules = rules)
1075     p = t.body[0]
1076
1077     # Empty string, for RPC this translates into a void
1078     if type(p) in (type(''), type(u'')) and p in ('', u''):
1079         name = "Response"
1080         for k in t.body.__dict__.keys():
1081             if k[0] != "_":
1082                 name = k
1083         p = structType(name)
1084         
1085     if header or body or attrs:
1086         ret = (p,)
1087         if header : ret += (t.header,)
1088         if body: ret += (t.body,)
1089         if attrs: ret += (t.attrs,)
1090         return ret
1091     else:
1092         return p