00001
00002
00003 '''Compound typecodes.
00004 '''
00005
00006 from ZSI import _copyright, _children, _child_elements, \
00007 _inttypes, _stringtypes, _seqtypes, _find_arraytype, _find_href, \
00008 _find_type, _find_xmlns_prefix, _get_idstr, EvaluateException, \
00009 ParseException
00010
00011 from TC import _get_element_nsuri_name, \
00012 _get_xsitype, TypeCode, Any, AnyElement, AnyType, \
00013 Nilled, UNBOUNDED
00014
00015 from schema import GED, ElementDeclaration, TypeDefinition, \
00016 _get_substitute_element, _get_type_definition, _is_substitute_element
00017
00018 from ZSI.wstools.Namespaces import SCHEMA, SOAP
00019 from ZSI.wstools.Utility import SplitQName
00020 from ZSI.wstools.logging import getLogger as _GetLogger
00021 import re, types
00022 from copy import copy as _copy
00023
00024 _find_arrayoffset = lambda E: E.getAttributeNS(SOAP.ENC, "offset")
00025 _find_arrayposition = lambda E: E.getAttributeNS(SOAP.ENC, "position")
00026
00027 _offset_pat = re.compile(r'\[[0-9]+\]')
00028 _position_pat = _offset_pat
00029
00030 def _check_typecode_list(ofwhat, tcname):
00031 '''Check a list of typecodes for compliance with Struct
00032 requirements.'''
00033 for o in ofwhat:
00034 if callable(o):
00035 continue
00036 if not isinstance(o, TypeCode):
00037 raise TypeError(
00038 tcname + ' ofwhat outside the TypeCode hierarchy, ' +
00039 str(o.__class__))
00040 if o.pname is None and not isinstance(o, AnyElement):
00041 raise TypeError(tcname + ' element ' + str(o) + ' has no name')
00042
00043
00044 def _get_type_or_substitute(typecode, pyobj, sw, elt):
00045 '''return typecode or substitute type for wildcard or
00046 derived type. For serialization only.
00047 '''
00048 sub = getattr(pyobj, 'typecode', typecode)
00049 if sub is typecode or sub is None:
00050 return typecode
00051
00052
00053 if isinstance(typecode, AnyElement):
00054 return sub
00055
00056
00057 if isinstance(sub, ElementDeclaration):
00058 if (typecode.nspname,typecode.pname) == (sub.nspname,sub.pname):
00059 raise TypeError(\
00060 'bad usage, failed to serialize element reference (%s, %s), in: %s' %
00061 (typecode.nspname, typecode.pname, sw.Backtrace(elt),))
00062
00063
00064 if _is_substitute_element(typecode, sub):
00065 return sub
00066
00067 raise TypeError(\
00068 'failed to serialize (%s, %s) illegal sub GED (%s,%s): %s' %
00069 (typecode.nspname, typecode.pname, sub.nspname, sub.pname,
00070 sw.Backtrace(elt),))
00071
00072
00073 if not isinstance(typecode, AnyType) and not isinstance(sub, typecode.__class__):
00074 raise TypeError(\
00075 'failed to serialize substitute %s for %s, not derivation: %s' %
00076 (sub, typecode, sw.Backtrace(elt),))
00077
00078
00079
00080
00081 sub = _copy(sub)
00082 sub.nspname = typecode.nspname
00083 sub.pname = typecode.pname
00084 sub.aname = typecode.aname
00085 sub.minOccurs = sub.maxOccurs = 1
00086 return sub
00087
00088
00089
00090 class ComplexType(TypeCode):
00091 '''Represents an element of complexType, potentially containing other
00092 elements.
00093 '''
00094 logger = _GetLogger('ZSI.TCcompound.ComplexType')
00095
00096 def __init__(self, pyclass, ofwhat, pname=None, inorder=False, inline=False,
00097 mutable=True, mixed=False, mixed_aname='_text', **kw):
00098 '''pyclass -- the Python class to hold the fields
00099 ofwhat -- a list of fields to be in the complexType
00100 inorder -- fields must be in exact order or not
00101 inline -- don't href/id when serializing
00102 mutable -- object could change between multiple serializations
00103 type -- the (URI,localname) of the datatype
00104 mixed -- mixed content model? True/False
00105 mixed_aname -- if mixed is True, specify text content here. Default _text
00106 '''
00107 TypeCode.__init__(self, pname, pyclass=pyclass, **kw)
00108 self.inorder = inorder
00109 self.inline = inline
00110 self.mutable = mutable
00111 self.mixed = mixed
00112 self.mixed_aname = None
00113 if mixed is True:
00114 self.mixed_aname = mixed_aname
00115
00116 if self.mutable is True: self.inline = True
00117 self.type = kw.get('type') or _get_xsitype(self)
00118 t = type(ofwhat)
00119 if t not in _seqtypes:
00120 raise TypeError(
00121 'Struct ofwhat must be list or sequence, not ' + str(t))
00122 self.ofwhat = tuple(ofwhat)
00123 if TypeCode.typechecks:
00124
00125 if self.pyclass is not None and \
00126 type(self.pyclass) is not types.ClassType and not isinstance(self.pyclass, object):
00127 raise TypeError('pyclass must be None or an old-style/new-style class, not ' +
00128 str(type(self.pyclass)))
00129 _check_typecode_list(self.ofwhat, 'ComplexType')
00130
00131 def parse(self, elt, ps):
00132 debug = self.logger.debugOn()
00133 debug and self.logger.debug('parse')
00134
00135 xtype = self.checkname(elt, ps)
00136 if self.type and xtype not in [ self.type, (None,None) ]:
00137 if not isinstance(self, TypeDefinition):
00138 raise EvaluateException(\
00139 'ComplexType for %s has wrong type(%s), looking for %s' %
00140 (self.pname, self.checktype(elt,ps), self.type),
00141 ps.Backtrace(elt))
00142 else:
00143
00144 debug and self.logger.debug('delegate to substitute type')
00145 what = TypeDefinition.getSubstituteType(self, elt, ps)
00146 return what.parse(elt, ps)
00147
00148 href = _find_href(elt)
00149 if href:
00150 if _children(elt):
00151 raise EvaluateException('Struct has content and HREF',
00152 ps.Backtrace(elt))
00153 elt = ps.FindLocalHREF(href, elt)
00154 c = _child_elements(elt)
00155 count = len(c)
00156 if self.nilled(elt, ps): return Nilled
00157
00158
00159 v = {}
00160
00161
00162
00163 attributes = self.parse_attributes(elt, ps)
00164 if attributes:
00165 v[self.attrs_aname] = attributes
00166
00167
00168 if self.mixed is True:
00169 v[self.mixed_aname] = self.simple_value(elt,ps, mixed=True)
00170
00171
00172 c, crange = c[:], range(len(c))
00173
00174
00175 if debug:
00176 self.logger.debug("ofwhat: %s",str(self.ofwhat))
00177
00178 any = None
00179 for i,what in [ (i, self.ofwhat[i]) for i in range(len(self.ofwhat)) ]:
00180
00181
00182 if callable(what): what = what()
00183
00184
00185 if debug:
00186 self.logger.debug("what: (%s,%s)", what.nspname, what.pname)
00187
00188 for j,c_elt in [ (j, c[j]) for j in crange if c[j] ]:
00189
00190 if debug:
00191 self.logger.debug("child node: (%s,%s)", c_elt.namespaceURI, c_elt.tagName)
00192
00193 match = False
00194 if what.name_match(c_elt):
00195 match = True
00196 value = what.parse(c_elt, ps)
00197 else:
00198
00199
00200 subwhat = _get_substitute_element(what, c_elt, ps)
00201 if subwhat:
00202 match = True
00203 value = subwhat.parse(c_elt, ps)
00204
00205 if debug:
00206 self.logger.debug("substitutionGroup: %s", subwhat)
00207
00208 if match:
00209 if what.maxOccurs > 1:
00210 if v.has_key(what.aname):
00211 v[what.aname].append(value)
00212 else:
00213 v[what.aname] = [value]
00214 c[j] = None
00215 continue
00216 else:
00217 v[what.aname] = value
00218 c[j] = None
00219 break
00220
00221 if debug:
00222 self.logger.debug("no element (%s,%s)", what.nspname, what.pname)
00223
00224
00225 if self.inorder is True and i == j:
00226 raise EvaluateException('Out of order complexType',
00227 ps.Backtrace(c_elt))
00228 else:
00229
00230 if isinstance(what,AnyElement):
00231 any = what
00232 elif hasattr(what, 'default'):
00233 v[what.aname] = what.default
00234 elif what.minOccurs > 0 and not v.has_key(what.aname):
00235 raise EvaluateException('Element "' + what.aname + \
00236 '" missing from complexType', ps.Backtrace(elt))
00237
00238
00239
00240 if any is not None:
00241 occurs = 0
00242 v[any.aname] = []
00243 for j,c_elt in [ (j, c[j]) for j in crange if c[j] ]:
00244 value = any.parse(c_elt, ps)
00245 if any.maxOccurs == UNBOUNDED or any.maxOccurs > 1:
00246 v[any.aname].append(value)
00247 else:
00248 v[any.aname] = value
00249
00250 occurs += 1
00251
00252
00253 if any.maxOccurs == 1 and occurs == 0:
00254 v[any.aname] = None
00255 elif occurs < any.minOccurs or (any.maxOccurs!=UNBOUNDED and any.maxOccurs<occurs):
00256 raise EvaluateException('occurances of <any> elements(#%d) bound by (%d,%s)' %(
00257 occurs, any.minOccurs,str(any.maxOccurs)), ps.Backtrace(elt))
00258
00259 if not self.pyclass:
00260 return v
00261
00262
00263
00264 try:
00265 pyobj = self.pyclass()
00266 except Exception, e:
00267 raise TypeError("Constructing element (%s,%s) with pyclass(%s), %s" \
00268 %(self.nspname, self.pname, self.pyclass.__name__, str(e)))
00269 for key in v.keys():
00270 setattr(pyobj, key, v[key])
00271 return pyobj
00272
00273 def serialize(self, elt, sw, pyobj, inline=False, name=None, **kw):
00274 if inline or self.inline:
00275 self.cb(elt, sw, pyobj, name=name, **kw)
00276 else:
00277 objid = _get_idstr(pyobj)
00278 ns,n = self.get_name(name, objid)
00279 el = elt.createAppendElement(ns, n)
00280 el.setAttributeNS(None, 'href', "#%s" %objid)
00281 sw.AddCallback(self.cb, elt, sw, pyobj)
00282
00283 def cb(self, elt, sw, pyobj, name=None, **kw):
00284 debug = self.logger.debugOn()
00285 if debug:
00286 self.logger.debug("cb: %s" %str(self.ofwhat))
00287
00288 objid = _get_idstr(pyobj)
00289 ns,n = self.get_name(name, objid)
00290 if pyobj is None:
00291 if self.nillable is True:
00292 elem = elt.createAppendElement(ns, n)
00293 self.serialize_as_nil(elem)
00294 return
00295 raise EvaluateException, 'element(%s,%s) is not nillable(%s)' %(
00296 self.nspname,self.pname,self.nillable)
00297
00298 if self.mutable is False and sw.Known(pyobj):
00299 return
00300
00301 if debug:
00302 self.logger.debug("element: (%s, %s)", str(ns), n)
00303
00304 if n is not None:
00305 elem = elt.createAppendElement(ns, n)
00306 self.set_attributes(elem, pyobj)
00307 if kw.get('typed', self.typed) is True:
00308 self.set_attribute_xsi_type(elem)
00309
00310
00311 if self.mixed is True and self.mixed_aname is not None:
00312 if hasattr(pyobj, self.mixed_aname):
00313 textContent = getattr(pyobj, self.mixed_aname)
00314 if hasattr(textContent, 'typecode'):
00315 textContent.typecode.serialize_text_node(elem, sw, textContent)
00316 elif type(textContent) in _stringtypes:
00317 if debug:
00318 self.logger.debug("mixed text content:\n\t%s",
00319 textContent)
00320 elem.createAppendTextNode(textContent)
00321 else:
00322 raise EvaluateException('mixed test content in element (%s,%s) must be a string type' %(
00323 self.nspname,self.pname), sw.Backtrace(elt))
00324 else:
00325 if debug:
00326 self.logger.debug("mixed NO text content in %s",
00327 self.mixed_aname)
00328 else:
00329
00330
00331 elem = elt
00332
00333 if self.inline:
00334 pass
00335 elif not self.inline and self.unique:
00336 raise EvaluateException('Not inline, but unique makes no sense. No href/id.',
00337 sw.Backtrace(elt))
00338 elif n is not None:
00339 self.set_attribute_id(elem, objid)
00340
00341 if self.pyclass and type(self.pyclass) is type:
00342 f = lambda attr: getattr(pyobj, attr, None)
00343 elif self.pyclass:
00344 d = pyobj.__dict__
00345 f = lambda attr: d.get(attr)
00346 else:
00347 d = pyobj
00348 f = lambda attr: pyobj.get(attr)
00349 if TypeCode.typechecks and type(d) != types.DictType:
00350 raise TypeError("Classless complexType didn't get dictionary")
00351
00352 indx, lenofwhat = 0, len(self.ofwhat)
00353 if debug:
00354 self.logger.debug('element declaration (%s,%s)', self.nspname,
00355 self.pname)
00356 if self.type:
00357 self.logger.debug('xsi:type definition (%s,%s)', self.type[0],
00358 self.type[1])
00359 else:
00360 self.logger.warning('NO xsi:type')
00361
00362 while indx < lenofwhat:
00363 occurs = 0
00364 what = self.ofwhat[indx]
00365
00366
00367 if callable(what): what = what()
00368
00369 if debug:
00370 self.logger.debug('serialize what -- %s',
00371 what.__class__.__name__)
00372
00373
00374
00375
00376
00377
00378 aname = what.aname
00379 v = f(aname)
00380 indx += 1
00381 if what.minOccurs == 0 and v is None:
00382 continue
00383
00384
00385
00386 whatTC = what
00387 if whatTC.maxOccurs > 1 and v is not None:
00388 if type(v) not in _seqtypes:
00389 raise EvaluateException('pyobj (%s,%s), aname "%s": maxOccurs %s, expecting a %s' %(
00390 self.nspname,self.pname,what.aname,whatTC.maxOccurs,_seqtypes),
00391 sw.Backtrace(elt))
00392
00393 for v2 in v:
00394 occurs += 1
00395 if occurs > whatTC.maxOccurs:
00396 raise EvaluateException('occurances (%d) exceeded maxOccurs(%d) for <%s>' %(
00397 occurs, whatTC.maxOccurs, what.pname),
00398 sw.Backtrace(elt))
00399
00400 what = _get_type_or_substitute(whatTC, v2, sw, elt)
00401 if debug and what is not whatTC:
00402 self.logger.debug('substitute derived type: %s' %
00403 what.__class__)
00404
00405 what.serialize(elem, sw, v2, **kw)
00406
00407
00408
00409
00410
00411
00412 if occurs < whatTC.minOccurs:
00413 raise EvaluateException(\
00414 'occurances(%d) less than minOccurs(%d) for <%s>' %
00415 (occurs, whatTC.minOccurs, what.pname), sw.Backtrace(elt))
00416
00417 continue
00418
00419 if v is not None or what.nillable is True:
00420 what = _get_type_or_substitute(whatTC, v, sw, elt)
00421 if debug and what is not whatTC:
00422 self.logger.debug('substitute derived type: %s' %
00423 what.__class__)
00424 what.serialize(elem, sw, v, **kw)
00425
00426
00427
00428
00429
00430
00431
00432
00433 continue
00434
00435 raise EvaluateException('Got None for nillable(%s), minOccurs(%d) element (%s,%s), %s' %
00436 (what.nillable, what.minOccurs, what.nspname, what.pname, elem),
00437 sw.Backtrace(elt))
00438
00439
00440 def setDerivedTypeContents(self, extensions=None, restrictions=None):
00441 """For derived types set appropriate parameter and
00442 """
00443 if extensions:
00444 ofwhat = list(self.ofwhat)
00445 if type(extensions) in _seqtypes:
00446 ofwhat += list(extensions)
00447 else:
00448 ofwhat.append(extensions)
00449 elif restrictions:
00450 if type(restrictions) in _seqtypes:
00451 ofwhat = restrictions
00452 else:
00453 ofwhat = (restrictions,)
00454 else:
00455 return
00456 self.ofwhat = tuple(ofwhat)
00457 self.lenofwhat = len(self.ofwhat)
00458
00459
00460 class Struct(ComplexType):
00461 '''Struct is a complex type for accessors identified by name.
00462 Constraint: No element may have the same name as any other,
00463 nor may any element have a maxOccurs > 1.
00464
00465 <xs:group name="Struct" >
00466 <xs:sequence>
00467 <xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax" />
00468 </xs:sequence>
00469 </xs:group>
00470
00471 <xs:complexType name="Struct" >
00472 <xs:group ref="tns:Struct" minOccurs="0" />
00473 <xs:attributeGroup ref="tns:commonAttributes"/>
00474 </xs:complexType>
00475 '''
00476 logger = _GetLogger('ZSI.TCcompound.Struct')
00477
00478 def __init__(self, pyclass, ofwhat, pname=None, inorder=False, inline=False,
00479 mutable=True, **kw):
00480 '''pyclass -- the Python class to hold the fields
00481 ofwhat -- a list of fields to be in the struct
00482 inorder -- fields must be in exact order or not
00483 inline -- don't href/id when serializing
00484 mutable -- object could change between multiple serializations
00485 '''
00486 ComplexType.__init__(self, pyclass, ofwhat, pname=pname,
00487 inorder=inorder, inline=inline, mutable=mutable,
00488 **kw
00489 )
00490
00491
00492 whats = map(lambda what: (what.nspname,what.pname), self.ofwhat)
00493 for idx in range(len(self.ofwhat)):
00494 what = self.ofwhat[idx]
00495 key = (what.nspname,what.pname)
00496 if not isinstance(what, AnyElement) and what.maxOccurs > 1:
00497 raise TypeError,\
00498 'Constraint: no element can have a maxOccurs>1'
00499 if key in whats[idx+1:]:
00500 raise TypeError,\
00501 'Constraint: No element may have the same name as any other'
00502
00503
00504 class Array(TypeCode):
00505 '''An array.
00506 atype -- arrayType, (namespace,ncname)
00507 mutable -- object could change between multiple serializations
00508 undeclared -- do not serialize/parse arrayType attribute.
00509 '''
00510 logger = _GetLogger('ZSI.TCcompound.Array')
00511
00512 def __init__(self, atype, ofwhat, pname=None, dimensions=1, fill=None,
00513 sparse=False, mutable=False, size=None, nooffset=0, undeclared=False,
00514 childnames=None, **kw):
00515 TypeCode.__init__(self, pname, **kw)
00516 self.dimensions = dimensions
00517 self.atype = atype
00518 if undeclared is False and self.atype[1].endswith(']') is False:
00519 self.atype = (self.atype[0], '%s[]' %self.atype[1])
00520
00521 if self.dimensions != 1:
00522 raise TypeError("Only single-dimensioned arrays supported")
00523 self.fill = fill
00524 self.sparse = sparse
00525
00526 self.mutable = mutable
00527 self.size = size
00528 self.nooffset = nooffset
00529 self.undeclared = undeclared
00530 self.childnames = childnames
00531 if self.size:
00532 t = type(self.size)
00533 if t in _inttypes:
00534 self.size = (self.size,)
00535 elif t in _seqtypes:
00536 self.size = tuple(self.size)
00537 elif TypeCode.typechecks:
00538 raise TypeError('Size must be integer or list, not ' + str(t))
00539
00540
00541 ofwhat = ofwhat or Any()
00542
00543 if TypeCode.typechecks:
00544 if self.undeclared is False and type(atype) not in _seqtypes and len(atype) == 2:
00545 raise TypeError("Array type must be a sequence of len 2.")
00546 t = type(ofwhat)
00547 if not isinstance(ofwhat, TypeCode):
00548 raise TypeError(
00549 'Array ofwhat outside the TypeCode hierarchy, ' +
00550 str(ofwhat.__class__))
00551 if self.size:
00552 if len(self.size) != self.dimensions:
00553 raise TypeError('Array dimension/size mismatch')
00554 for s in self.size:
00555 if type(s) not in _inttypes:
00556 raise TypeError('Array size "' + str(s) +
00557 '" is not an integer.')
00558 self.ofwhat = ofwhat
00559
00560 def parse_offset(self, elt, ps):
00561 o = _find_arrayoffset(elt)
00562 if not o: return 0
00563 if not _offset_pat.match(o):
00564 raise EvaluateException('Bad offset "' + o + '"',
00565 ps.Backtrace(elt))
00566 return int(o[1:-1])
00567
00568 def parse_position(self, elt, ps):
00569 o = _find_arrayposition(elt)
00570 if not o: return None
00571 if o.find(',') > -1:
00572 raise EvaluateException('Sorry, no multi-dimensional arrays',
00573 ps.Backtrace(elt))
00574 if not _position_pat.match(o):
00575 raise EvaluateException('Bad array position "' + o + '"',
00576 ps.Backtrace(elt))
00577 return int(o[1:-1])
00578
00579 def parse(self, elt, ps):
00580 href = _find_href(elt)
00581 if href:
00582 if _children(elt):
00583 raise EvaluateException('Array has content and HREF',
00584 ps.Backtrace(elt))
00585 elt = ps.FindLocalHREF(href, elt)
00586 if self.nilled(elt, ps): return Nilled
00587 if not _find_arraytype(elt) and self.undeclared is False:
00588 raise EvaluateException('Array expected', ps.Backtrace(elt))
00589 t = _find_type(elt)
00590 if t:
00591 pass
00592 offset = self.parse_offset(elt, ps)
00593 v, vlen = [], 0
00594 if offset and not self.sparse:
00595 while vlen < offset:
00596 vlen += 1
00597 v.append(self.fill)
00598 for c in _child_elements(elt):
00599 item = self.ofwhat.parse(c, ps)
00600 position = self.parse_position(c, ps) or offset
00601 if self.sparse:
00602 v.append((position, item))
00603 else:
00604 while offset < position:
00605 offset += 1
00606 v.append(self.fill)
00607 v.append(item)
00608 offset += 1
00609 return v
00610
00611 def serialize(self, elt, sw, pyobj, name=None, childnames=None, **kw):
00612 debug = self.logger.debugOn()
00613 if debug:
00614 self.logger.debug("serialize: %r" %pyobj)
00615
00616 if self.mutable is False and sw.Known(pyobj): return
00617 objid = _get_idstr(pyobj)
00618 ns,n = self.get_name(name, objid)
00619 el = elt.createAppendElement(ns, n)
00620
00621
00622 if self.nillable is True and pyobj is None:
00623 self.serialize_as_nil(el)
00624 return None
00625
00626
00627 self.set_attributes(el, pyobj)
00628
00629
00630 unique = self.unique or kw.get('unique', False)
00631 if unique is False and sw.Known(pyobj):
00632 self.set_attribute_href(el, objid)
00633 return None
00634
00635
00636 if kw.get('typed', self.typed) is True:
00637 self.set_attribute_xsi_type(el, **kw)
00638
00639
00640 if self.unique is False:
00641 self.set_attribute_id(el, objid)
00642
00643 offset = 0
00644 if self.sparse is False and self.nooffset is False:
00645 offset, end = 0, len(pyobj)
00646 while offset < end and pyobj[offset] == self.fill:
00647 offset += 1
00648 if offset:
00649 el.setAttributeNS(SOAP.ENC, 'offset', '[%d]' %offset)
00650
00651 if self.undeclared is False:
00652 el.setAttributeNS(SOAP.ENC, 'arrayType',
00653 '%s:%s' %(el.getPrefix(self.atype[0]), self.atype[1])
00654 )
00655
00656 if debug:
00657 self.logger.debug("ofwhat: %r" %self.ofwhat)
00658
00659 d = {}
00660 kn = childnames or self.childnames
00661 if kn:
00662 d['name'] = kn
00663 elif not self.ofwhat.aname:
00664 d['name'] = 'element'
00665
00666 if self.sparse is False:
00667 for e in pyobj[offset:]: self.ofwhat.serialize(el, sw, e, **d)
00668 else:
00669 position = 0
00670 for pos, v in pyobj:
00671 if pos != position:
00672 el.setAttributeNS(SOAP.ENC, 'position', '[%d]' %pos)
00673 position = pos
00674
00675 self.ofwhat.serialize(el, sw, v, **d)
00676 position += 1
00677
00678
00679 if __name__ == '__main__': print _copyright