Home | Trees | Indices | Help |
|
---|
|
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_models51 """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.""" 6249164 """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.""" 77112 118 121 124 127 130 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>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 pyxsd89 """Yields all classes defined under this node.""" 90 for child in self.children: 91 for class_ in child.class_walker(fileformat): 92 yield class_9395 """Resolves all attributes.""" 96 for child in self.children: 97 child.attribute_walker(fileformat)98100 """Create attributes on the instance *inst*.""" 101 for child in self.children: 102 child.instantiate(inst)103 104 # helper functions 105 @staticmethod148 """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.""" 170238 241 244 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>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)179181 # 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))235260 name = None 261 """The name of the type.""" 262 266291 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 such268 # 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_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 """ 326340 343 346 349 352 355 358 361 364 367 370 373 376 379 382 385 388 391 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> 419 422 425 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>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))333335 if self.min_occurs == 1 and self.max_occurs == 1: 336 Tree.Attribute.instantiate(self, inst) 337 else: 338 # XXX todo 339 pass438 name = None 439 """The name of the type.""" 440 444469 472 475 476 @classmethod 477 # note: this corresponds to the 'factory' method in pyxsd446 # 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_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)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 """ 500537502 """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))539 _node = None 540546542 if self._node is None: 543 return 544 # TODO initialize all attributes 545 self._node.instantiate(self)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 @classmethod719559 # 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 571 575 578 581 584 587 590 593 596 599 602 605 608 611 614 617 620 623 626 629 632 633 # derived datatypes 634 # http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/datatypes.html#built-in-derived 635 638 641 644 647 651653 """Name represents XML Names. See 654 http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/datatypes.html#Name 655 """ 656 pass657659 """NCName represents XML "non-colonized" Names. See 660 http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/datatypes.html#NCName 661 """ 662 pass663 666 669 673 676 680 683 686 689 692 695 698 701 704 707 710 713 716
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Mon Oct 10 19:04:15 2011 | http://epydoc.sourceforge.net |