Package pyffi :: Package object_models :: Package xsd
[hide private]
[frames] | no frames]

Source Code for Package pyffi.object_models.xsd

  1  """This module provides a base class and a metaclass for parsing an XSD 
  2  schema and providing an interface for writing XML files that follow this 
  3  schema. 
  4  """ 
  5   
  6  # ***** BEGIN LICENSE BLOCK ***** 
  7  # 
  8  # Copyright (c) 2007-2011, Python File Format Interface 
  9  # All rights reserved. 
 10  # 
 11  # Redistribution and use in source and binary forms, with or without 
 12  # modification, are permitted provided that the following conditions 
 13  # are met: 
 14  # 
 15  #    * Redistributions of source code must retain the above copyright 
 16  #      notice, this list of conditions and the following disclaimer. 
 17  # 
 18  #    * Redistributions in binary form must reproduce the above 
 19  #      copyright notice, this list of conditions and the following 
 20  #      disclaimer in the documentation and/or other materials provided 
 21  #      with the distribution. 
 22  # 
 23  #    * Neither the name of the Python File Format Interface 
 24  #      project nor the names of its contributors may be used to endorse 
 25  #      or promote products derived from this software without specific 
 26  #      prior written permission. 
 27  # 
 28  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 29  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 30  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 31  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 32  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 33  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 34  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 35  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 36  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 37  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 38  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 39  # POSSIBILITY OF SUCH DAMAGE. 
 40  # 
 41  # ***** END LICENSE BLOCK ***** 
 42   
 43  import logging 
 44  import time 
 45  import weakref 
 46  import xml.etree.cElementTree 
 47   
 48  import pyffi.object_models 
49 50 -class Tree(object):
51 """Converts an xsd element tree into a tree of nodes that contain 52 all information and methods for creating classes. Each node has a 53 class matching the element type. The node constructor 54 :meth:`Node.__init__` extracts the required information from the 55 element and its children, thereby constructing the tree of nodes. 56 Once the tree is constructed, call the :meth:`Node.class_walker` 57 method to get a list of classes. 58 """ 59 60 logger = logging.getLogger("pyffi.object_models.xsd") 61 """For logging debug messages, warnings, and errors.""" 62
63 - class Node(object):
64 """Base class for all nodes in the tree.""" 65 66 schema = None 67 """A weak proxy of the root element of the tree.""" 68 69 parent = None 70 """A weak proxy of the parent element of this node, or None.""" 71 72 children = None 73 """The child elements of this node.""" 74 75 logger = logging.getLogger("pyffi.object_models.xsd") 76 """For logging debug messages, warnings, and errors.""" 77
78 - def __init__(self, element, parent):
79 # note: using weak references to avoid reference cycles 80 self.parent = weakref.proxy(parent) if parent else None 81 self.schema = self.parent.schema if parent else weakref.proxy(self) 82 83 # create nodes for all children 84 self.children = [Tree.node_factory(child, self) 85 for child in element.getchildren()]
86 87 # note: this corresponds roughly to the 'clsFor' method in pyxsd
88 - def class_walker(self, fileformat):
89 """Yields all classes defined under this node.""" 90 for child in self.children: 91 for class_ in child.class_walker(fileformat): 92 yield class_
93
94 - def attribute_walker(self, fileformat):
95 """Resolves all attributes.""" 96 for child in self.children: 97 child.attribute_walker(fileformat)
98
99 - def instantiate(self, inst):
100 """Create attributes on the instance *inst*.""" 101 for child in self.children: 102 child.instantiate(inst)
103 104 # helper functions 105 @staticmethod
106 - def num_occurs(num):
107 """Converts a string to an ``int`` or ``None`` (if unbounded).""" 108 if num == 'unbounded': 109 return None 110 else: 111 return int(num)
112
113 - class ClassNode(Node):
114 """A node that corresponds to a class.""" 115 116 class_ = None 117 """Generated class."""
118
119 - class All(Node):
120 pass
121
122 - class Annotation(Node):
123 pass
124
125 - class Any(Node):
126 pass
127
128 - class AnyAttribute(Node):
129 pass
130
131 - class Appinfo(Node):
132 pass
133 134 ##http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-attribute 135 ##<attribute 136 ## default = string 137 ## fixed = string 138 ## form = (qualified | unqualified) 139 ## id = ID 140 ## name = NCName 141 ## ref = QName 142 ## type = QName 143 ## use = (optional | prohibited | required) : optional 144 ## {any attributes with non-schema namespace . . .}> 145 ## Content: (annotation?, simpleType?) 146 ##</attribute>
147 - class Attribute(Node):
148 """This class implements the attribute node, and also is a 149 base class for the element node. 150 """ 151 152 name = None 153 """The name of the attribute.""" 154 155 ref = None 156 """If the attribute is declared elsewhere, then this contains the name 157 of the reference. 158 """ 159 160 type_ = None 161 """The type of the attribute. This determines the content this 162 attribute can have. 163 """ 164 165 pyname = None 166 """Name for python.""" 167 168 pytype = None 169 """Type for python.""" 170
171 - def __init__(self, element, parent):
172 Tree.Node.__init__(self, element, parent) 173 self.name = element.get("name") 174 self.ref = element.get("ref") 175 self.type_ = element.get("type") 176 if (not self.name) and (not self.ref): 177 raise ValueError("Attribute %s has neither name nor ref." 178 % element)
179
180 - def attribute_walker(self, fileformat):
181 # attributes for child nodes 182 Tree.Node.attribute_walker(self, fileformat) 183 # now set attributes for this node 184 if self.name: 185 # could have self._type or not, but should not have a self.ref 186 assert(not self.ref) # debug 187 self.pyname = fileformat.name_attribute(self.name) 188 node = self 189 elif self.ref: 190 # no name and no type should be defined 191 assert(not self.name) # debug 192 assert(not self.type_) # debug 193 self.pyname = fileformat.name_attribute(self.ref) 194 # resolve reference 195 for child in self.schema.children: 196 if (isinstance(child, self.__class__) 197 and child.name == self.ref): 198 node = child 199 break 200 else: 201 # exception for xml:base reference 202 if self.ref == "xml:base": 203 self.pytype = fileformat.XsString 204 else: 205 raise ValueError("Could not resolve reference to '%s'." 206 % self.ref) 207 # done: name and type are resolved 208 return 209 else: 210 self.logger.error("Attribute without name.") 211 if not node.type_: 212 # resolve it locally 213 for child in node.children: 214 # remember, ClassNode is simpletype or complextype 215 if isinstance(child, Tree.ClassNode): 216 self.pytype = child.class_ 217 break 218 else: 219 self.logger.warn( 220 "No type for %s '%s': falling back to xs:anyType." 221 % (self.__class__.__name__.lower(), 222 (self.name if self.name else self.ref))) 223 self.pytype = fileformat.XsAnyType 224 else: 225 # predefined type, or global type? 226 pytypename = fileformat.name_class(node.type_) 227 try: 228 self.pytype = getattr(fileformat, pytypename) 229 except AttributeError: 230 raise ValueError( 231 "Could not resolve type '%s' (%s) for %s '%s'." 232 % (self.type_, pytypename, 233 self.__class__.__name__.lower(), 234 self.name if self.name else self.ref))
235
236 - def instantiate(self, inst):
237 setattr(inst, self.pyname, self.pytype())
238
239 - class AttributeGroup(Node):
240 pass
241
242 - class Choice(Node):
243 pass
244
245 - class ComplexContent(Node):
246 pass
247 248 ##http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-complexType 249 ##<complexType 250 ## abstract = boolean : false 251 ## block = (#all | List of (extension | restriction)) 252 ## final = (#all | List of (extension | restriction)) 253 ## id = ID 254 ## mixed = boolean : false 255 ## name = NCName 256 ## {any attributes with non-schema namespace . . .}> 257 ## Content: (annotation?, (simpleContent | complexContent | ((group | all | choice | sequence)?, ((attribute | attributeGroup)*, anyAttribute?)))) 258 ##</complexType>
259 - class ComplexType(ClassNode):
260 name = None 261 """The name of the type.""" 262
263 - def __init__(self, element, parent):
264 Tree.ClassNode.__init__(self, element, parent) 265 self.name = element.get("name")
266
267 - def class_walker(self, fileformat):
268 # construct a class name 269 if self.name: 270 class_name = self.name 271 elif isinstance(self.parent, Tree.Element): 272 # find element that contains this type 273 class_name = self.parent.name 274 else: 275 raise ValueError( 276 "complexType has no name attribute and no element parent: " 277 "cannot determine name.") 278 # filter class name so it conforms naming conventions 279 class_name = fileformat.name_class(class_name) 280 # construct bases 281 class_bases = (Type,) 282 # construct class dictionary 283 class_dict = dict(_node=self, __module__=fileformat.__module__) 284 # create class 285 self.class_ = type(class_name, class_bases, class_dict) 286 # assign child classes 287 for child_class in Tree.Node.class_walker(self, fileformat): 288 setattr(self.class_, child_class.__name__, child_class) 289 # yield the generated class 290 yield self.class_
291
292 - class Documentation(Node):
293 pass
294 295 ##http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-element 296 ##<element 297 ## abstract = boolean : false 298 ## block = (#all | List of (extension | restriction | substitution)) 299 ## default = string 300 ## final = (#all | List of (extension | restriction)) 301 ## fixed = string 302 ## form = (qualified | unqualified) 303 ## id = ID 304 ## maxOccurs = (nonNegativeInteger | unbounded) : 1 305 ## minOccurs = nonNegativeInteger : 1 306 ## name = NCName 307 ## nillable = boolean : false 308 ## ref = QName 309 ## substitutionGroup = QName 310 ## type = QName 311 ## {any attributes with non-schema namespace . . .}> 312 ## Content: (annotation?, ((simpleType | complexType)?, (unique | key | keyref)*)) 313 ##</element> 314 # note: techically, an element is not quite an attribute, but for the 315 # purpose of this library, we can consider it as such
316 - class Element(Attribute):
317 min_occurs = 1 318 """Minimum number of times the element can occur. ``None`` 319 corresponds to unbounded. 320 """ 321 322 max_occurs = 1 323 """Maximum number of times the element can occur. ``None`` 324 corresponds to unbounded. 325 """ 326
327 - def __init__(self, element, parent):
328 Tree.Attribute.__init__(self, element, parent) 329 self.min_occurs = self.num_occurs(element.get("minOccurs", 330 self.min_occurs)) 331 self.max_occurs = self.num_occurs(element.get("maxOccurs", 332 self.max_occurs))
333
334 - def instantiate(self, inst):
335 if self.min_occurs == 1 and self.max_occurs == 1: 336 Tree.Attribute.instantiate(self, inst) 337 else: 338 # XXX todo 339 pass
340
341 - class Enumeration(Node):
342 pass
343
344 - class Extension(Node):
345 pass
346
347 - class Field(Node):
348 pass
349
350 - class Group(Node):
351 pass
352
353 - class Import(Node):
354 pass
355
356 - class Include(Node):
357 pass
358
359 - class Key(Node):
360 pass
361
362 - class Keyref(Node):
363 pass
364
365 - class Length(Node):
366 pass
367
368 - class List(Node):
369 pass
370
371 - class MaxExclusive(Node):
372 pass
373
374 - class MaxInclusive(Node):
375 pass
376
377 - class MaxLength(Node):
378 pass
379
380 - class MinInclusive(Node):
381 pass
382
383 - class MinLength(Node):
384 pass
385
386 - class Pattern(Node):
387 pass
388
389 - class Redefine(Node):
390 pass
391
392 - class Restriction(Node):
393 pass
394 395 ##http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-schema 396 ##<schema 397 ## attributeFormDefault = (qualified | unqualified) : unqualified 398 ## blockDefault = (#all | List of (extension | restriction | substitution)) : '' 399 ## elementFormDefault = (qualified | unqualified) : unqualified 400 ## finalDefault = (#all | List of (extension | restriction | list | union)) : '' 401 ## id = ID 402 ## targetNamespace = anyURI 403 ## version = token 404 ## xml:lang = language 405 ## {any attributes with non-schema namespace . . .}> 406 ## Content: ((include | import | redefine | annotation)*, (((simpleType | complexType | group | attributeGroup) | element | attribute | notation), annotation*)*) 407 ##</schema>
408 - class Schema(Node):
409 """Class wrapper for schema tag.""" 410 411 version = None 412 """Version attribute.""" 413
414 - def __init__(self, element, parent):
415 if parent is not None: 416 raise ValueError("Schema can only occur as root element.") 417 Tree.Node.__init__(self, element, parent) 418 self.version = element.get("version")
419
420 - class Selector(Node):
421 pass
422
423 - class Sequence(Node):
424 pass
425
426 - class SimpleContent(Node):
427 pass
428 429 ##http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-simpleType 430 ##<simpleType 431 ## final = (#all | List of (list | union | restriction)) 432 ## id = ID 433 ## name = NCName 434 ## {any attributes with non-schema namespace . . .}> 435 ## Content: (annotation?, (restriction | list | union)) 436 ##</simpleType>
437 - class SimpleType(ClassNode):
438 name = None 439 """The name of the type.""" 440
441 - def __init__(self, element, parent):
442 Tree.ClassNode.__init__(self, element, parent) 443 self.name = element.get("name")
444
445 - def class_walker(self, fileformat):
446 # construct a class name 447 if self.name: 448 class_name = self.name 449 elif isinstance(self.parent, Tree.Element): 450 # find element that contains this type 451 class_name = self.parent.name 452 else: 453 raise ValueError( 454 "simpleType has no name attribute and no element parent: " 455 "cannot determine name.") 456 # filter class name so it conforms naming conventions 457 class_name = fileformat.name_class(class_name) 458 # construct bases 459 class_bases = (Type,) 460 # construct class dictionary 461 class_dict = dict(_node=self) 462 # create class 463 self.class_ = type(class_name, class_bases, class_dict) 464 # assign child classes 465 for child_class in Tree.Node.class_walker(self, fileformat): 466 setattr(self.class_, child_class.__name__, child_class) 467 # yield the generated class 468 yield self.class_
469
470 - class Union(Node):
471 pass
472
473 - class Unique(Node):
474 pass
475 476 @classmethod 477 # note: this corresponds to the 'factory' method in pyxsd
478 - def node_factory(cls, element, parent):
479 """Create an appropriate instance for the given xsd element.""" 480 # get last part of the tag 481 class_name = element.tag.split("}")[-1] 482 class_name = class_name[0].upper() + class_name[1:] 483 try: 484 return getattr(cls, class_name)(element, parent) 485 except AttributeError: 486 cls.logger.warn("Unknown element type: making dummy node class %s." 487 % class_name) 488 class_ = type(class_name, (cls.Node,), {}) 489 setattr(cls, class_name, class_) 490 return class_(element, parent)
491
492 -class MetaFileFormat(pyffi.object_models.MetaFileFormat):
493 """The MetaFileFormat metaclass transforms the XSD description of a 494 xml format into a bunch of classes which can be directly used to 495 manipulate files in this format. 496 497 The actual implementation of the parser is delegated to 498 :class:`Tree`. 499 """ 500
501 - def __init__(cls, name, bases, dct):
502 """This function constitutes the core of the class generation 503 process. For instance, we declare DaeFormat to have metaclass 504 MetaFileFormat, so upon creation of the DaeFormat class, 505 the __init__ function is called, with 506 507 :param cls: The class created using MetaFileFormat, for example 508 DaeFormat. 509 :param name: The name of the class, for example 'DaeFormat'. 510 :param bases: The base classes, usually (object,). 511 :param dct: A dictionary of class attributes, such as 'xsdFileName'. 512 """ 513 super(MetaFileFormat, cls).__init__(name, bases, dct) 514 515 # open XSD file 516 xsdfilename = dct.get('xsdFileName') 517 if xsdfilename: 518 # open XSD file 519 xsdfile = cls.openfile(xsdfilename, cls.xsdFilePath) 520 521 # parse the XSD file 522 cls.logger.debug("Parsing %s and generating classes." % xsdfilename) 523 start = time.clock() 524 try: 525 # create nodes for every element in the XSD tree 526 schema = Tree.node_factory( 527 xml.etree.cElementTree.parse(xsdfile).getroot(), None) 528 finally: 529 xsdfile.close() 530 # generate classes 531 for class_ in schema.class_walker(cls): 532 setattr(cls, class_.__name__, class_) 533 # generate attributes 534 schema.attribute_walker(cls) 535 cls.logger.debug("Parsing finished in %.3f seconds." 536 % (time.clock() - start))
537
538 -class Type(object):
539 _node = None 540
541 - def __init__(self):
542 if self._node is None: 543 return 544 # TODO initialize all attributes 545 self._node.instantiate(self)
546
547 -class FileFormat(pyffi.object_models.FileFormat):
548 """This class can be used as a base class for file formats. It implements 549 a number of useful functions such as walking over directory trees and a 550 default attribute naming function. 551 """ 552 __metaclass__ = MetaFileFormat 553 xsdFileName = None #: Override. 554 xsdFilePath = None #: Override. 555 logger = logging.getLogger("pyffi.object_models.xsd") 556 557 @classmethod
558 - def name_parts(cls, name):
559 # introduces extra splits for some names 560 name = name.replace("NMTOKEN", "NM_TOKEN") # name token 561 name = name.replace("IDREF", "ID_REF") # identifier reference 562 # do the split 563 return pyffi.object_models.FileFormat.name_parts(name)
564 565 # built-in datatypes 566 # http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/datatypes.html#built-in-datatypes 567
568 - class XsAnyType(object):
569 """Abstract base type for all types.""" 570 pass
571
572 - class XsAnySimpleType(XsAnyType):
573 """Abstract base type for all simple types.""" 574 pass
575
576 - class XsString(XsAnySimpleType):
577 pass
578
579 - class XsBoolean(XsAnySimpleType):
580 pass
581
582 - class XsDecimal(XsAnySimpleType):
583 pass
584
585 - class XsFloat(XsAnySimpleType):
586 pass
587
588 - class XsDouble(XsAnySimpleType):
589 pass
590
591 - class XsDuration(XsAnySimpleType):
592 pass
593
594 - class XsDateTime(XsAnySimpleType):
595 pass
596
597 - class XsTime(XsAnySimpleType):
598 pass
599
600 - class XsDate(XsAnySimpleType):
601 pass
602
603 - class XsGYearMonth(XsAnySimpleType):
604 pass
605
606 - class XsGYear(XsAnySimpleType):
607 pass
608
609 - class XsGMonthDay(XsAnySimpleType):
610 pass
611
612 - class XsGDay(XsAnySimpleType):
613 pass
614
615 - class XsGMonth(XsAnySimpleType):
616 pass
617
618 - class XsHexBinary(XsAnySimpleType):
619 pass
620
621 - class XsBase64Binary(XsAnySimpleType):
622 pass
623
624 - class XsAnyURI(XsAnySimpleType):
625 pass
626
627 - class XsQName(XsAnySimpleType):
628 pass
629
630 - class XsNotation(XsAnySimpleType):
631 pass
632 633 # derived datatypes 634 # http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/datatypes.html#built-in-derived 635
636 - class XsNormalizedString(XsString):
637 pass
638
639 - class XsToken(XsNormalizedString):
640 pass
641
642 - class XsLanguage(XsToken):
643 pass
644
645 - class XsNmToken(XsToken):
646 pass
647
648 - class XsNmTokens(XsToken):
649 # list 650 pass
651
652 - class XsName(XsToken):
653 """Name represents XML Names. See 654 http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/datatypes.html#Name 655 """ 656 pass
657
658 - class XsNCName(XsName):
659 """NCName represents XML "non-colonized" Names. See 660 http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/datatypes.html#NCName 661 """ 662 pass
663
664 - class XsId(XsNCName):
665 pass
666
667 - class XsIdRef(XsNCName):
668 pass
669
670 - class XsIdRefs(XsIdRef):
671 # list 672 pass
673
674 - class XsEntity(XsNCName):
675 pass
676
677 - class XsEntities(XsEntity):
678 # list 679 pass
680
681 - class XsInteger(XsDecimal):
682 pass
683
684 - class XsNonPositiveInteger(XsInteger):
685 pass
686
687 - class XsNegativeInteger(XsInteger):
688 pass
689
690 - class XsLong(XsInteger):
691 pass
692
693 - class XsInt(XsLong):
694 pass
695
696 - class XsShort(XsInt):
697 pass
698
699 - class XsByte(XsShort):
700 pass
701
702 - class XsNonNegativeInteger(XsInteger):
703 pass
704
705 - class XsUnsignedLong(XsNonNegativeInteger):
706 pass
707
708 - class XsUnsignedInt(XsUnsignedLong):
709 pass
710
711 - class XsUnsignedShort(XsUnsignedInt):
712 pass
713
714 - class XsUnsignedByte(XsUnsignedShort):
715 pass
716
717 - class XsPositiveInteger(XsNonNegativeInteger):
718 pass
719