1 """Format classes and metaclasses for binary file formats described by an xml
2 file, and xml handler for converting the xml description into Python classes.
3 """
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 import logging
43 import time
44 import types
45 import os.path
46 import sys
47 import xml.sax
48
49 import pyffi.object_models
50 from pyffi.object_models.xml.struct_ import StructBase
51 from pyffi.object_models.xml.basic import BasicBase
52 from pyffi.object_models.xml.bit_struct import BitStructBase
53 from pyffi.object_models.xml.enum import EnumBase
54 from pyffi.object_models.xml.expression import Expression
111
138
141 """Helper class to collect attribute data of struct add tags."""
142
143 name = None
144 """The name of this member variable."""
145
146 type_ = None
147 """The type of this member variable (type is ``str`` for forward
148 declarations, and resolved to :class:`BasicBase` or
149 :class:`StructBase` later).
150 """
151
152 default = None
153 """The default value of this member variable."""
154
155 template = None
156 """The template type of this member variable (initially ``str``,
157 resolved to :class:`BasicBase` or :class:`StructBase` at the end
158 of the xml parsing), and if there is no template type, then this
159 variable will equal ``type(None)``.
160 """
161
162 arg = None
163 """The argument of this member variable."""
164
165 arr1 = None
166 """The first array size of this member variable, as
167 :class:`Expression` or ``type(None)``.
168 """
169
170 arr2 = None
171 """The second array size of this member variable, as
172 :class:`Expression` or ``type(None)``.
173 """
174
175 cond = None
176 """The condition of this member variable, as
177 :class:`Expression` or ``type(None)``.
178 """
179
180 ver1 = None
181 """The first version this member exists, as ``int``, and ``None`` if
182 there is no lower limit.
183 """
184
185 ver2 = None
186 """The last version this member exists, as ``int``, and ``None`` if
187 there is no upper limit.
188 """
189
190 userver = None
191 """The user version this member exists, as ``int``, and ``None`` if
192 it exists for all user versions.
193 """
194
195 is_abstract = False
196 """Whether the attribute is abstract or not (read and written)."""
197
264
267 """Helper class to collect attribute data of bitstruct bits tags."""
268
297
300 """The XML handler will throw this exception if something goes wrong while
301 parsing."""
302 pass
303
306 """This class contains all functions for parsing the xml and converting
307 the xml structure into Python classes."""
308 tag_file = 1
309 tag_version = 2
310 tag_basic = 3
311 tag_alias = 4
312 tag_enum = 5
313 tag_option = 6
314 tag_bit_struct = 7
315 tag_struct = 8
316 tag_attribute = 9
317 tag_bits = 10
318
319 tags = {
320 "fileformat": tag_file,
321 "version": tag_version,
322 "basic": tag_basic,
323 "alias": tag_alias,
324 "enum": tag_enum,
325 "option": tag_option,
326 "bitstruct": tag_bit_struct,
327 "struct": tag_struct,
328 "bits": tag_bits,
329 "add": tag_attribute}
330
331
332 tags_niftools = {
333 "niftoolsxml": tag_file,
334 "compound": tag_struct,
335 "niobject": tag_struct,
336 "bitflags": tag_bit_struct}
337
338 - def __init__(self, cls, name, bases, dct):
339 """Set up the xml parser.
340
341 Upon instantiation this function does the following:
342 - Creates a dictionary C{cls.versions} which maps each supported
343 version strings onto a version integer.
344 - Creates a dictionary C{cls.games} which maps each supported game
345 onto a list of versions.
346 - Makes an alias C{self.cls} for C{cls}.
347 - Initializes a stack C{self.stack} of xml tags.
348 - Initializes the current tag.
349 """
350
351 xml.sax.handler.ContentHandler.__init__(self)
352
353
354 self.dct = dct
355
356
357
358 cls.versions = {}
359
360
361 cls.games = {}
362
363
364
365
366 self.stack = []
367
368
369 self.current_tag = None
370
371
372
373 self.cls = cls
374
375
376 self.class_name = None
377 self.class_dict = None
378 self.class_bases = ()
379
380
381 self.basic_class = None
382
383
384 self.version_string = None
385
387 """Push tag C{tag} on the stack and make it the current tag.
388
389 :param tag: The tag to put on the stack."""
390 self.stack.insert(0, tag)
391 self.current_tag = tag
392
394 """Pop the current tag from the stack and return it. Also update
395 the current tag.
396
397 :return: The tag popped from the stack."""
398 lasttag = self.stack.pop(0)
399 try:
400 self.current_tag = self.stack[0]
401 except IndexError:
402 self.current_tag = None
403 return lasttag
404
406 """Called when parser starts parsing an element called C{name}.
407
408 This function sets up all variables for creating the class
409 in the C{self.endElement} function. For struct elements, it will set up
410 C{self.class_name}, C{self.class_bases}, and C{self.class_dict} which
411 will be used to create the class by invokation of C{type} in
412 C{self.endElement}. For basic, enum, and bitstruct elements, it will
413 set up C{self.basic_class} to link to the proper class implemented by
414 C{self.cls}. The code also performs sanity checks on the attributes.
415
416 For xml add tags, the function will add an entry to the
417 C{self.class_dict["_attrs"]} list. Note that this list is used by the
418 struct metaclass: the class attributes are created exactly from this
419 list.
420
421 :param name: The name of the xml element.
422 :param attrs: A dictionary of attributes of the element."""
423
424 try:
425 tag = self.tags[name]
426 except KeyError:
427 try:
428 tag = self.tags_niftools[name]
429 except KeyError:
430 raise XmlError("error unknown element '%s'" % name)
431
432
433
434
435
436 if not self.stack:
437 if tag != self.tag_file:
438 raise XmlError("this is not a fileformat xml file")
439 self.pushTag(tag)
440 return
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457 if self.current_tag == self.tag_struct:
458 self.pushTag(tag)
459
460 if tag == self.tag_attribute:
461
462 self.class_dict["_attrs"].append(
463 StructAttribute(self.cls, attrs))
464
465 elif tag == self.tag_version:
466
467 self.version_string = str(attrs["num"])
468 self.cls.versions[self.version_string] = self.cls.version_number(
469 self.version_string)
470
471 else:
472 raise XmlError(
473 "only add and version tags allowed in struct declaration")
474 elif self.current_tag == self.tag_file:
475 self.pushTag(tag)
476
477
478 if tag == self.tag_struct:
479 self.class_name = attrs["name"]
480
481
482
483 class_basename = attrs.get("inherit")
484 if class_basename:
485
486
487 try:
488 self.class_bases += (
489 getattr(self.cls, class_basename), )
490 except KeyError:
491 raise XmlError(
492 "typo, or forward declaration of struct %s"
493 % class_basename)
494 else:
495 self.class_bases = (StructBase,)
496
497
498
499 self.class_dict = {
500 "_is_template": attrs.get("istemplate") == "1",
501 "_attrs": [],
502 "_games": {},
503 "__doc__": "",
504 "__module__": self.cls.__module__}
505
506
507 elif tag == self.tag_basic:
508 self.class_name = attrs["name"]
509
510
511
512 self.basic_class = getattr(self.cls, self.class_name)
513
514 is_template = (attrs.get("istemplate") == "1")
515 if self.basic_class._is_template != is_template:
516 raise XmlError(
517 'class %s should have _is_template = %s'
518 % (self.class_name, is_template))
519
520
521 elif tag == self.tag_enum:
522 self.class_bases += (EnumBase,)
523 self.class_name = attrs["name"]
524 try:
525 numbytes = int(attrs["numbytes"])
526 except KeyError:
527
528
529 typename = attrs["storage"]
530 try:
531 typ = getattr(self.cls, typename)
532 except AttributeError:
533 raise XmlError(
534 "typo, or forward declaration of type %s"
535 % typename)
536 numbytes = typ.get_size()
537 self.class_dict = {"__doc__": "",
538 "_numbytes": numbytes,
539 "_enumkeys": [], "_enumvalues": [],
540 "__module__": self.cls.__module__}
541
542
543 elif tag == self.tag_alias:
544 self.class_name = attrs["name"]
545 typename = attrs["type"]
546 try:
547 self.class_bases += (getattr(self.cls, typename),)
548 except AttributeError:
549 raise XmlError(
550 "typo, or forward declaration of type %s" % typename)
551 self.class_dict = {"__doc__": "",
552 "__module__": self.cls.__module__}
553
554
555
556
557 elif tag == self.tag_bit_struct:
558 self.class_bases += (BitStructBase,)
559 self.class_name = attrs["name"]
560 try:
561 numbytes = int(attrs["numbytes"])
562 except KeyError:
563
564 numbytes = getattr(self.cls, attrs["storage"]).get_size()
565 self.class_dict = {"_attrs": [], "__doc__": "",
566 "_numbytes": numbytes,
567 "__module__": self.cls.__module__}
568
569
570 elif tag == self.tag_version:
571 self.version_string = str(attrs["num"])
572 self.cls.versions[self.version_string] = self.cls.version_number(
573 self.version_string)
574
575
576 else:
577 raise XmlError("""
578 expected basic, alias, enum, bitstruct, struct, or version,
579 but got %s instead""" % name)
580
581 elif self.current_tag == self.tag_version:
582 raise XmlError("version tag must not contain any sub tags")
583
584 elif self.current_tag == self.tag_alias:
585 raise XmlError("alias tag must not contain any sub tags")
586
587 elif self.current_tag == self.tag_enum:
588 self.pushTag(tag)
589 if not tag == self.tag_option:
590 raise XmlError("only option tags allowed in enum declaration")
591 value = attrs["value"]
592 try:
593
594
595 value = long(value)
596 except ValueError:
597 value = long(value, 16)
598 self.class_dict["_enumkeys"].append(attrs["name"])
599 self.class_dict["_enumvalues"].append(value)
600
601 elif self.current_tag == self.tag_bit_struct:
602 self.pushTag(tag)
603 if tag == self.tag_bits:
604
605 self.class_dict["_attrs"].append(
606 BitStructAttribute(self.cls, attrs))
607 elif tag == self.tag_option:
608
609
610
611 bitpos = sum(bitattr.numbits
612 for bitattr in self.class_dict["_attrs"])
613
614 numextrabits = int(attrs["value"]) - bitpos
615 if numextrabits < 0:
616 raise XmlError("values of bitflags must be increasing")
617 if numextrabits > 0:
618 self.class_dict["_attrs"].append(
619 BitStructAttribute(
620 self.cls,
621 dict(name="Reserved Bits %i"
622 % len(self.class_dict["_attrs"]),
623 numbits=numextrabits)))
624
625 self.class_dict["_attrs"].append(
626 BitStructAttribute(
627 self.cls,
628 dict(name=attrs["name"], numbits=1)))
629 else:
630 raise XmlError(
631 "only bits tags allowed in struct type declaration")
632
633 else:
634 raise XmlError("unhandled tag %s" % name)
635
637 """Called at the end of each xml tag.
638
639 Creates classes."""
640 if not self.stack:
641 raise XmlError("mismatching end element tag for element %s" % name)
642 try:
643 tag = self.tags[name]
644 except KeyError:
645 try:
646 tag = self.tags_niftools[name]
647 except KeyError:
648 raise XmlError("error unknown element %s" % name)
649 if self.popTag() != tag:
650 raise XmlError("mismatching end element tag for element %s" % name)
651 elif tag == self.tag_attribute:
652 return
653 elif tag in (self.tag_struct,
654 self.tag_enum,
655 self.tag_alias,
656 self.tag_bit_struct):
657
658
659
660 cls_klass = getattr(self.cls, self.class_name, None)
661 if cls_klass and issubclass(cls_klass, BasicBase):
662
663 pass
664 else:
665
666 if cls_klass:
667
668 gen_klass = type(
669 "_" + str(self.class_name),
670 self.class_bases, self.class_dict)
671 setattr(self.cls, "_" + self.class_name, gen_klass)
672
673
674
675
676 cls_klass = type(
677 cls_klass.__name__,
678 (gen_klass,) + cls_klass.__bases__,
679 dict(cls_klass.__dict__))
680 setattr(self.cls, self.class_name, cls_klass)
681
682 if issubclass(
683 cls_klass,
684 pyffi.object_models.FileFormat.Data):
685 self.cls.Data = cls_klass
686
687 gen_class = cls_klass
688 else:
689
690 gen_klass = type(
691 str(self.class_name), self.class_bases, self.class_dict)
692 setattr(self.cls, self.class_name, gen_klass)
693
694 if tag == self.tag_struct:
695 self.cls.xml_struct.append(gen_klass)
696 elif tag == self.tag_enum:
697 self.cls.xml_enum.append(gen_klass)
698 elif tag == self.tag_alias:
699 self.cls.xml_alias.append(gen_klass)
700 elif tag == self.tag_bit_struct:
701 self.cls.xml_bit_struct.append(gen_klass)
702
703 self.class_name = None
704 self.class_dict = None
705 self.class_bases = ()
706 elif tag == self.tag_basic:
707
708 setattr(self.cls, self.class_name, self.basic_class)
709
710 self.basic_class = None
711 elif tag == self.tag_version:
712
713 self.version_string = None
714
716 """Called when the xml is completely parsed.
717
718 Searches and adds class customized functions.
719 For version tags, adds version to version and game lists."""
720 for obj in self.cls.__dict__.values():
721
722
723 if not (isinstance(obj, type) and issubclass(obj, StructBase)):
724 continue
725
726 for attr in obj._attrs:
727 templ = attr.template
728 if isinstance(templ, basestring):
729 attr.template = \
730 getattr(self.cls, templ) if templ != "TEMPLATE" \
731 else type(None)
732 attrtype = attr.type_
733 if isinstance(attrtype, basestring):
734 attr.type_ = getattr(self.cls, attrtype)
735
737 """Add the string C{chars} to the docstring.
738 For version tags, updates the game version list."""
739 if self.current_tag in (self.tag_attribute, self.tag_bits):
740 self.class_dict["_attrs"][-1].doc += str(chars.strip())
741 elif self.current_tag in (self.tag_struct, self.tag_enum,
742 self.tag_alias):
743 self.class_dict["__doc__"] += str(chars.strip())
744 elif self.current_tag == self.tag_version:
745
746 if self.stack[1] == self.tag_file:
747 gamesdict = self.cls.games
748
749 elif self.stack[1] == self.tag_struct:
750 gamesdict = self.class_dict["_games"]
751 else:
752 raise XmlError("version parsing error at '%s'" % chars)
753
754 for gamestr in (str(g.strip()) for g in chars.split(',')):
755 if gamestr in gamesdict:
756 gamesdict[gamestr].append(
757 self.cls.versions[self.version_string])
758 else:
759 gamesdict[gamestr] = [
760 self.cls.versions[self.version_string]]
761