# SOAPpy modules from Config import Config from Types import * from NS import NS from Utilities import * import string import fpconst import xml.sax from wstools.XMLname import fromXMLname try: from M2Crypto import SSL except: pass ident = '$Id$' from version import __version__ ################################################################################ # SOAP Parser ################################################################################ class RefHolder: def __init__(self, name, frame): self.name = name self.parent = frame self.pos = len(frame) self.subpos = frame.namecounts.get(name, 0) def __repr__(self): return "<%s %s at %d>" % (self.__class__, self.name, id(self)) def __str__(self): return "<%s %s at %d>" % (self.__class__, self.name, id(self)) class SOAPParser(xml.sax.handler.ContentHandler): class Frame: def __init__(self, name, kind = None, attrs = {}, rules = {}): self.name = name self.kind = kind self.attrs = attrs self.rules = rules self.contents = [] self.names = [] self.namecounts = {} self.subattrs = [] def append(self, name, data, attrs): self.names.append(name) self.contents.append(data) self.subattrs.append(attrs) if self.namecounts.has_key(name): self.namecounts[name] += 1 else: self.namecounts[name] = 1 def _placeItem(self, name, value, pos, subpos = 0, attrs = None): self.contents[pos] = value if attrs: self.attrs.update(attrs) def __len__(self): return len(self.contents) def __repr__(self): return "<%s %s at %d>" % (self.__class__, self.name, id(self)) def __init__(self, rules = None): xml.sax.handler.ContentHandler.__init__(self) self.body = None self.header = None self.attrs = {} self._data = None self._next = "E" # Keeping state for message validity self._stack = [self.Frame('SOAP')] # Make two dictionaries to store the prefix <-> URI mappings, and # initialize them with the default self._prem = {NS.XML_T: NS.XML} self._prem_r = {NS.XML: NS.XML_T} self._ids = {} self._refs = {} self._rules = rules def startElementNS(self, name, qname, attrs): # Workaround two sax bugs if name[0] == None and name[1][0] == ' ': name = (None, name[1][1:]) else: name = tuple(name) # First some checking of the layout of the message if self._next == "E": if name[1] != 'Envelope': raise Error, "expected `SOAP-ENV:Envelope', gto `%s:%s'" % \ (self._prem_r[name[0]], name[1]) if name[0] != NS.ENV: raise faultType, ("%s:VersionMismatch" % NS.ENV_T, "Don't understand version `%s' Envelope" % name[0]) else: self._next = "HorB" elif self._next == "HorB": if name[0] == NS.ENV and name[1] in ("Header", "Body"): self._next = None else: raise Error, \ "expected `SOAP-ENV:Header' or `SOAP-ENV:Body', " \ "got `%s'" % self._prem_r[name[0]] + ':' + name[1] elif self._next == "B": if name == (NS.ENV, "Body"): self._next = None else: raise Error, "expected `SOAP-ENV:Body', got `%s'" % \ self._prem_r[name[0]] + ':' + name[1] elif self._next == "": raise Error, "expected nothing, got `%s'" % \ self._prem_r[name[0]] + ':' + name[1] if len(self._stack) == 2: rules = self._rules else: try: rules = self._stack[-1].rules[name[1]] except: rules = None if type(rules) not in (NoneType, DictType): kind = rules else: kind = attrs.get((NS.ENC, 'arrayType')) if kind != None: del attrs._attrs[(NS.ENC, 'arrayType')] i = kind.find(':') if i >= 0: kind = (self._prem[kind[:i]], kind[i + 1:]) else: kind = None self.pushFrame(self.Frame(name[1], kind, attrs._attrs, rules)) self._data = [] # Start accumulating def pushFrame(self, frame): self._stack.append(frame) def popFrame(self): return self._stack.pop() def endElementNS(self, name, qname): # Workaround two sax bugs if name[0] == None and name[1][0] == ' ': ns, name = None, name[1][1:] else: ns, name = tuple(name) name = fromXMLname(name) # convert to SOAP 1.2 XML name encoding if self._next == "E": raise Error, "didn't get SOAP-ENV:Envelope" if self._next in ("HorB", "B"): raise Error, "didn't get SOAP-ENV:Body" cur = self.popFrame() attrs = cur.attrs idval = None if attrs.has_key((None, 'id')): idval = attrs[(None, 'id')] if self._ids.has_key(idval): raise Error, "duplicate id `%s'" % idval del attrs[(None, 'id')] root = 1 if len(self._stack) == 3: if attrs.has_key((NS.ENC, 'root')): root = int(attrs[(NS.ENC, 'root')]) # Do some preliminary checks. First, if root="0" is present, # the element must have an id. Next, if root="n" is present, # n something other than 0 or 1, raise an exception. if root == 0: if idval == None: raise Error, "non-root element must have an id" elif root != 1: raise Error, "SOAP-ENC:root must be `0' or `1'" del attrs[(NS.ENC, 'root')] while 1: href = attrs.get((None, 'href')) if href: if href[0] != '#': raise Error, "Non-local hrefs are not yet suppported." if self._data != None and string.join(self._data, "").strip() != '': raise Error, "hrefs can't have data" href = href[1:] if self._ids.has_key(href): data = self._ids[href] else: data = RefHolder(name, self._stack[-1]) if self._refs.has_key(href): self._refs[href].append(data) else: self._refs[href] = [data] del attrs[(None, 'href')] break kind = None if attrs: for i in NS.XSI_L: if attrs.has_key((i, 'type')): kind = attrs[(i, 'type')] del attrs[(i, 'type')] if kind != None: i = kind.find(':') if i >= 0: kind = (self._prem[kind[:i]], kind[i + 1:]) else: # XXX What to do here? (None, kind) is just going to fail in convertType kind = (None, kind) null = 0 if attrs: for i in (NS.XSI, NS.XSI2): if attrs.has_key((i, 'null')): null = attrs[(i, 'null')] del attrs[(i, 'null')] if attrs.has_key((NS.XSI3, 'nil')): null = attrs[(NS.XSI3, 'nil')] del attrs[(NS.XSI3, 'nil')] ## Check for nil # check for nil='true' if type(null) in (StringType, UnicodeType): if null.lower() == 'true': null = 1 # check for nil=1, but watch out for string values try: null = int(null) except ValueError, e: if not e[0].startswith("invalid literal for int()"): raise e null = 0 if null: if len(cur) or \ (self._data != None and string.join(self._data, "").strip() != ''): raise Error, "nils can't have data" data = None break if len(self._stack) == 2: if (ns, name) == (NS.ENV, "Header"): self.header = data = headerType(attrs = attrs) self._next = "B" break elif (ns, name) == (NS.ENV, "Body"): self.body = data = bodyType(attrs = attrs) self._next = "" break elif len(self._stack) == 3 and self._next == None: if (ns, name) == (NS.ENV, "Fault"): data = faultType() self._next = "" break if cur.rules != None: rule = cur.rules if type(rule) in (StringType, UnicodeType): # XXX Need a namespace here rule = (None, rule) elif type(rule) == ListType: rule = tuple(rule) # XXX What if rule != kind? if callable(rule): data = rule(string.join(self._data, "")) elif type(rule) == DictType: data = structType(name = (ns, name), attrs = attrs) else: data = self.convertType(string.join(self._data, ""), rule, attrs) break if (kind == None and cur.kind != None) or \ (kind == (NS.ENC, 'Array')): kind = cur.kind if kind == None: kind = 'ur-type[%d]' % len(cur) else: kind = kind[1] if len(cur.namecounts) == 1: elemsname = cur.names[0] else: elemsname = None data = self.startArray((ns, name), kind, attrs, elemsname) break if len(self._stack) == 3 and kind == None and \ len(cur) == 0 and \ (self._data == None or string.join(self._data, "").strip() == ''): data = structType(name = (ns, name), attrs = attrs) break if len(cur) == 0 and ns != NS.URN: # Nothing's been added to the current frame so it must be a # simple type. if kind == None: # If the current item's container is an array, it will # have a kind. If so, get the bit before the first [, # which is the type of the array, therefore the type of # the current item. kind = self._stack[-1].kind if kind != None: i = kind[1].find('[') if i >= 0: kind = (kind[0], kind[1][:i]) elif ns != None: kind = (ns, name) if kind != None: try: data = self.convertType(string.join(self._data, ""), kind, attrs) except UnknownTypeError: data = None else: data = None if data == None: if self._data == None: data = '' else: data = string.join(self._data, "") if len(attrs) == 0: try: data = str(data) except: pass break data = structType(name = (ns, name), attrs = attrs) break if isinstance(data, compoundType): for i in range(len(cur)): v = cur.contents[i] data._addItem(cur.names[i], v, cur.subattrs[i]) if isinstance(v, RefHolder): v.parent = data if root: self._stack[-1].append(name, data, attrs) if idval != None: self._ids[idval] = data if self._refs.has_key(idval): for i in self._refs[idval]: i.parent._placeItem(i.name, data, i.pos, i.subpos, attrs) del self._refs[idval] self.attrs[id(data)] = attrs if isinstance(data, anyType): data._setAttrs(attrs) self._data = None # Stop accumulating def endDocument(self): if len(self._refs) == 1: raise Error, \ "unresolved reference " + self._refs.keys()[0] elif len(self._refs) > 1: raise Error, \ "unresolved references " + ', '.join(self._refs.keys()) def startPrefixMapping(self, prefix, uri): self._prem[prefix] = uri self._prem_r[uri] = prefix def endPrefixMapping(self, prefix): try: del self._prem_r[self._prem[prefix]] del self._prem[prefix] except: pass def characters(self, c): if self._data != None: self._data.append(c) arrayre = '^(?:(?P<ns>[^:]*):)?' \ '(?P<type>[^[]+)' \ '(?:\[(?P<rank>,*)\])?' \ '(?:\[(?P<asize>\d+(?:,\d+)*)?\])$' def startArray(self, name, kind, attrs, elemsname): if type(self.arrayre) == StringType: self.arrayre = re.compile (self.arrayre) offset = attrs.get((NS.ENC, "offset")) if offset != None: del attrs[(NS.ENC, "offset")] try: if offset[0] == '[' and offset[-1] == ']': offset = int(offset[1:-1]) if offset < 0: raise Exception else: raise Exception except: raise AttributeError, "invalid Array offset" else: offset = 0 try: m = self.arrayre.search(kind) if m == None: raise Exception t = m.group('type') if t == 'ur-type': return arrayType(None, name, attrs, offset, m.group('rank'), m.group('asize'), elemsname) elif m.group('ns') != None: return typedArrayType(None, name, (self._prem[m.group('ns')], t), attrs, offset, m.group('rank'), m.group('asize'), elemsname) else: return typedArrayType(None, name, (None, t), attrs, offset, m.group('rank'), m.group('asize'), elemsname) except: raise AttributeError, "invalid Array type `%s'" % kind # Conversion class DATETIMECONSTS: SIGNre = '(?P<sign>-?)' CENTURYre = '(?P<century>\d{2,})' YEARre = '(?P<year>\d{2})' MONTHre = '(?P<month>\d{2})' DAYre = '(?P<day>\d{2})' HOURre = '(?P<hour>\d{2})' MINUTEre = '(?P<minute>\d{2})' SECONDre = '(?P<second>\d{2}(?:\.\d*)?)' TIMEZONEre = '(?P<zulu>Z)|(?P<tzsign>[-+])(?P<tzhour>\d{2}):' \ '(?P<tzminute>\d{2})' BOSre = '^\s*' EOSre = '\s*$' __allres = {'sign': SIGNre, 'century': CENTURYre, 'year': YEARre, 'month': MONTHre, 'day': DAYre, 'hour': HOURre, 'minute': MINUTEre, 'second': SECONDre, 'timezone': TIMEZONEre, 'b': BOSre, 'e': EOSre} dateTime = '%(b)s%(sign)s%(century)s%(year)s-%(month)s-%(day)sT' \ '%(hour)s:%(minute)s:%(second)s(%(timezone)s)?%(e)s' % __allres timeInstant = dateTime timePeriod = dateTime time = '%(b)s%(hour)s:%(minute)s:%(second)s(%(timezone)s)?%(e)s' % \ __allres date = '%(b)s%(sign)s%(century)s%(year)s-%(month)s-%(day)s' \ '(%(timezone)s)?%(e)s' % __allres century = '%(b)s%(sign)s%(century)s(%(timezone)s)?%(e)s' % __allres gYearMonth = '%(b)s%(sign)s%(century)s%(year)s-%(month)s' \ '(%(timezone)s)?%(e)s' % __allres gYear = '%(b)s%(sign)s%(century)s%(year)s(%(timezone)s)?%(e)s' % \ __allres year = gYear gMonthDay = '%(b)s--%(month)s-%(day)s(%(timezone)s)?%(e)s' % __allres recurringDate = gMonthDay gDay = '%(b)s---%(day)s(%(timezone)s)?%(e)s' % __allres recurringDay = gDay gMonth = '%(b)s--%(month)s--(%(timezone)s)?%(e)s' % __allres month = gMonth recurringInstant = '%(b)s%(sign)s(%(century)s|-)(%(year)s|-)-' \ '(%(month)s|-)-(%(day)s|-)T' \ '(%(hour)s|-):(%(minute)s|-):(%(second)s|-)' \ '(%(timezone)s)?%(e)s' % __allres duration = '%(b)s%(sign)sP' \ '((?P<year>\d+)Y)?' \ '((?P<month>\d+)M)?' \ '((?P<day>\d+)D)?' \ '((?P<sep>T)' \ '((?P<hour>\d+)H)?' \ '((?P<minute>\d+)M)?' \ '((?P<second>\d*(?:\.\d*)?)S)?)?%(e)s' % \ __allres timeDuration = duration # The extra 31 on the front is: # - so the tuple is 1-based # - so months[month-1] is December's days if month is 1 months = (31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) def convertDateTime(self, value, kind): def getZoneOffset(d): zoffs = 0 try: if d['zulu'] == None: zoffs = 60 * int(d['tzhour']) + int(d['tzminute']) if d['tzsign'] != '-': zoffs = -zoffs except TypeError: pass return zoffs def applyZoneOffset(months, zoffs, date, minfield, posday = 1): if zoffs == 0 and (minfield > 4 or 0 <= date[5] < 60): return date if minfield > 5: date[5] = 0 if minfield > 4: date[4] = 0 if date[5] < 0: date[4] += int(date[5]) / 60 date[5] %= 60 date[4] += zoffs if minfield > 3 or 0 <= date[4] < 60: return date date[3] += date[4] / 60 date[4] %= 60 if minfield > 2 or 0 <= date[3] < 24: return date date[2] += date[3] / 24 date[3] %= 24 if minfield > 1: if posday and date[2] <= 0: date[2] += 31 # zoffs is at most 99:59, so the # day will never be less than -3 return date while 1: # The date[1] == 3 (instead of == 2) is because we're # going back a month, so we need to know if the previous # month is February, so we test if this month is March. leap = minfield == 0 and date[1] == 3 and \ date[0] % 4 == 0 and \ (date[0] % 100 != 0 or date[0] % 400 == 0) if 0 < date[2] <= months[date[1]] + leap: break date[2] += months[date[1] - 1] + leap date[1] -= 1 if date[1] > 0: break date[1] = 12 if minfield > 0: break date[0] -= 1 return date try: exp = getattr(self.DATETIMECONSTS, kind) except AttributeError: return None if type(exp) == StringType: exp = re.compile(exp) setattr (self.DATETIMECONSTS, kind, exp) m = exp.search(value) try: if m == None: raise Exception d = m.groupdict() f = ('century', 'year', 'month', 'day', 'hour', 'minute', 'second') fn = len(f) # Index of first non-None value r = [] if kind in ('duration', 'timeDuration'): if d['sep'] != None and d['hour'] == None and \ d['minute'] == None and d['second'] == None: raise Exception f = f[1:] for i in range(len(f)): s = d[f[i]] if s != None: if f[i] == 'second': s = float(s) else: try: s = int(s) except ValueError: s = long(s) if i < fn: fn = i r.append(s) if fn > len(r): # Any non-Nones? raise Exception if d['sign'] == '-': r[fn] = -r[fn] return tuple(r) if kind == 'recurringInstant': for i in range(len(f)): s = d[f[i]] if s == None or s == '-': if i > fn: raise Exception s = None else: if i < fn: fn = i if f[i] == 'second': s = float(s) else: try: s = int(s) except ValueError: s = long(s) r.append(s) s = r.pop(0) if fn == 0: r[0] += s * 100 else: fn -= 1 if fn < len(r) and d['sign'] == '-': r[fn] = -r[fn] cleanDate(r, fn) return tuple(applyZoneOffset(self.DATETIMECONSTS.months, getZoneOffset(d), r, fn, 0)) r = [0, 0, 1, 1, 0, 0, 0] for i in range(len(f)): field = f[i] s = d.get(field) if s != None: if field == 'second': s = float(s) else: try: s = int(s) except ValueError: s = long(s) if i < fn: fn = i r[i] = s if fn > len(r): # Any non-Nones? raise Exception s = r.pop(0) if fn == 0: r[0] += s * 100 else: fn -= 1 if d.get('sign') == '-': r[fn] = -r[fn] cleanDate(r, fn) zoffs = getZoneOffset(d) if zoffs: r = applyZoneOffset(self.DATETIMECONSTS.months, zoffs, r, fn) if kind == 'century': return r[0] / 100 s = [] for i in range(1, len(f)): if d.has_key(f[i]): s.append(r[i - 1]) if len(s) == 1: return s[0] return tuple(s) except Exception, e: raise Error, "invalid %s value `%s' - %s" % (kind, value, e) intlimits = \ { 'nonPositiveInteger': (0, None, 0), 'non-positive-integer': (0, None, 0), 'negativeInteger': (0, None, -1), 'negative-integer': (0, None, -1), 'long': (1, -9223372036854775808L, 9223372036854775807L), 'int': (0, -2147483648L, 2147483647), 'short': (0, -32768, 32767), 'byte': (0, -128, 127), 'nonNegativeInteger': (0, 0, None), 'non-negative-integer': (0, 0, None), 'positiveInteger': (0, 1, None), 'positive-integer': (0, 1, None), 'unsignedLong': (1, 0, 18446744073709551615L), 'unsignedInt': (0, 0, 4294967295L), 'unsignedShort': (0, 0, 65535), 'unsignedByte': (0, 0, 255), } floatlimits = \ { 'float': (7.0064923216240861E-46, -3.4028234663852886E+38, 3.4028234663852886E+38), 'double': (2.4703282292062327E-324, -1.7976931348623158E+308, 1.7976931348623157E+308), } zerofloatre = '[1-9]' def convertType(self, d, t, attrs, config=Config): return self.convertToBasicTypes(d, t, attrs, config) def convertToSOAPpyTypes(self, d, t, attrs, config=Config): pass def convertToBasicTypes(self, d, t, attrs, config=Config): dnn = d or '' if t[0] in NS.EXSD_L: if t[1] == "integer": try: d = int(d) if len(attrs): d = long(d) except: d = long(d) return d if self.intlimits.has_key (t[1]): # integer types l = self.intlimits[t[1]] try: d = int(d) except: d = long(d) if l[1] != None and d < l[1]: raise UnderflowError, "%s too small" % d if l[2] != None and d > l[2]: raise OverflowError, "%s too large" % d if l[0] or len(attrs): return long(d) return d if t[1] == "string": if len(attrs): return unicode(dnn) try: return str(dnn) except: return dnn if t[1] == "boolean": d = d.strip().lower() if d in ('0', 'false'): return 0 if d in ('1', 'true'): return 1 raise AttributeError, "invalid boolean value" if t[1] in ('double','float'): l = self.floatlimits[t[1]] s = d.strip().lower() d = float(s) if config.strict_range: if d < l[1]: raise UnderflowError if d > l[2]: raise OverflowError else: # some older SOAP impementations (notably SOAP4J, # Apache SOAP) return "infinity" instead of "INF" # so check the first 3 characters for a match. if s == "nan": return fpconst.NaN elif s[0:3] in ("inf", "+inf"): return fpconst.PosInf elif s[0:3] == "-inf": return fpconst.NegInf if fpconst.isNaN(d): if s != 'nan': raise ValueError, "invalid %s: %s" % (t[1], s) elif fpconst.isNegInf(d): if s != '-inf': raise UnderflowError, "%s too small: %s" % (t[1], s) elif fpconst.isPosInf(d): if s != 'inf': raise OverflowError, "%s too large: %s" % (t[1], s) elif d < 0 and d < l[1]: raise UnderflowError, "%s too small: %s" % (t[1], s) elif d > 0 and ( d < l[0] or d > l[2] ): raise OverflowError, "%s too large: %s" % (t[1], s) elif d == 0: if type(self.zerofloatre) == StringType: self.zerofloatre = re.compile(self.zerofloatre) if self.zerofloatre.search(s): raise UnderflowError, "invalid %s: %s" % (t[1], s) return d if t[1] in ("dateTime", "date", "timeInstant", "time"): return self.convertDateTime(d, t[1]) if t[1] == "decimal": return float(d) if t[1] in ("language", "QName", "NOTATION", "NMTOKEN", "Name", "NCName", "ID", "IDREF", "ENTITY"): return collapseWhiteSpace(d) if t[1] in ("IDREFS", "ENTITIES", "NMTOKENS"): d = collapseWhiteSpace(d) return d.split() if t[0] in NS.XSD_L: if t[1] in ("base64", "base64Binary"): if d: return base64.decodestring(d) else: return '' if t[1] == "hexBinary": if d: return decodeHexString(d) else: return if t[1] == "anyURI": return urllib.unquote(collapseWhiteSpace(d)) if t[1] in ("normalizedString", "token"): return collapseWhiteSpace(d) if t[0] == NS.ENC: if t[1] == "base64": if d: return base64.decodestring(d) else: return '' if t[0] == NS.XSD: if t[1] == "binary": try: e = attrs[(None, 'encoding')] if d: if e == 'hex': return decodeHexString(d) elif e == 'base64': return base64.decodestring(d) else: return '' except: pass raise Error, "unknown or missing binary encoding" if t[1] == "uri": return urllib.unquote(collapseWhiteSpace(d)) if t[1] == "recurringInstant": return self.convertDateTime(d, t[1]) if t[0] in (NS.XSD2, NS.ENC): if t[1] == "uriReference": return urllib.unquote(collapseWhiteSpace(d)) if t[1] == "timePeriod": return self.convertDateTime(d, t[1]) if t[1] in ("century", "year"): return self.convertDateTime(d, t[1]) if t[0] in (NS.XSD, NS.XSD2, NS.ENC): if t[1] == "timeDuration": return self.convertDateTime(d, t[1]) if t[0] == NS.XSD3: if t[1] == "anyURI": return urllib.unquote(collapseWhiteSpace(d)) if t[1] in ("gYearMonth", "gMonthDay"): return self.convertDateTime(d, t[1]) if t[1] == "gYear": return self.convertDateTime(d, t[1]) if t[1] == "gMonth": return self.convertDateTime(d, t[1]) if t[1] == "gDay": return self.convertDateTime(d, t[1]) if t[1] == "duration": return self.convertDateTime(d, t[1]) if t[0] in (NS.XSD2, NS.XSD3): if t[1] == "token": return collapseWhiteSpace(d) if t[1] == "recurringDate": return self.convertDateTime(d, t[1]) if t[1] == "month": return self.convertDateTime(d, t[1]) if t[1] == "recurringDay": return self.convertDateTime(d, t[1]) if t[0] == NS.XSD2: if t[1] == "CDATA": return collapseWhiteSpace(d) raise UnknownTypeError, "unknown type `%s'" % (t[0] + ':' + t[1]) ################################################################################ # call to SOAPParser that keeps all of the info ################################################################################ def _parseSOAP(xml_str, rules = None): try: from cStringIO import StringIO except ImportError: from StringIO import StringIO parser = xml.sax.make_parser() t = SOAPParser(rules = rules) parser.setContentHandler(t) e = xml.sax.handler.ErrorHandler() parser.setErrorHandler(e) inpsrc = xml.sax.xmlreader.InputSource() inpsrc.setByteStream(StringIO(xml_str)) # turn on namespace mangeling parser.setFeature(xml.sax.handler.feature_namespaces,1) try: parser.parse(inpsrc) except xml.sax.SAXParseException, e: parser._parser = None raise e return t ################################################################################ # SOAPParser's more public interface ################################################################################ def parseSOAP(xml_str, attrs = 0): t = _parseSOAP(xml_str) if attrs: return t.body, t.attrs return t.body def parseSOAPRPC(xml_str, header = 0, body = 0, attrs = 0, rules = None): #config=Config, unwrap_outer=1): t = _parseSOAP(xml_str, rules = rules) p = t.body[0] # Empty string, for RPC this translates into a void if type(p) in (type(''), type(u'')) and p in ('', u''): name = "Response" for k in t.body.__dict__.keys(): if k[0] != "_": name = k p = structType(name) if header or body or attrs: ret = (p,) if header : ret += (t.header,) if body: ret += (t.body,) if attrs: ret += (t.attrs,) return ret else: return p