"""
Parsers used to load an app and to generate the code from an xml file.

NOTE: custom tag handler interface (called by XmlWidgetBuilder)::

    class CustomTagHandler:
         def start_elem(self, name, attrs):
             pass
         def end_elem(self, name):
             return True -> the handler must be removed from the Stack
         def char_data(self, data):
             return False -> no further processing needed

@copyright: 2002-2007 Alberto Griggio <agriggio@users.sourceforge.net>
@license: MIT (see license.txt) - THIS PROGRAM COMES WITH NO WARRANTY
"""

import os
from cStringIO import StringIO
from xml.sax import SAXException, make_parser
from xml.sax.handler import ContentHandler

import common
import config
import edit_sizers
import errors

# ALB 2005-03-10: importing the module here prevents a segfault with python 2.4
# hmmm... need to investigate this more (it seems that import of
# xml.sax.expatreader should happen before something else... but what?)
import xml.sax.expatreader


if common.use_gui:
    import wx


class XmlParsingError(SAXException):
    """\
    Custom exception to report problems during parsing
    """
    locator = None

    def __init__(self, msg):
        if self.locator:
            l = self.locator
            msg += ' _((line: %s, column:  %s))' % (l.getLineNumber(),
                                                 l.getColumnNumber())
        SAXException.__init__(self, msg)

# end of class XmlParsingError


class XmlParser(ContentHandler):
    """\
    'abstract' base class of the parsers used to load an app and to generate
    the code

    @ivar _curr_prop:  Name of the current property
    @ivar _curr_prop_val: Value of the current property (list into which the
                          various pieces of char data collected are inserted)
    @ivar _objects:    Stack of 'alive' objects
    @ivar _sizer_item: Stack of sizer items
    @ivar _sizers:     Stack of sizer objects
    @ivar _windows:    Stack of window objects (derived by wxWindow)
    @ivar locator:     Document locator
    """
    def __init__(self):
        self._objects = Stack()
        self._windows = Stack()
        self._sizers = Stack()
        self._sizer_item = Stack()
        self._curr_prop = None
        self._curr_prop_val = []
        self._appl_started = False
        self.top = self._objects.top
        self.parser = make_parser()
        self.parser.setContentHandler(self)
        self.locator = None

    def parse(self, source):
        # Permanent workaround for Python bug "Sax parser crashes if given
        # unicode file name" (http://bugs.python.org/issue11159).
        #
        # This bug causes a UnicodeEncodeError if the SAX XML parser wants to
        # store an unicode filename internally.
        #
        # That's not a general file handling issue because the parameter
        # source is an open file already.
        source = StringIO(source.read())
        self.parser.parse(source)
        source.close()

    def parse_string(self, source):
        source = StringIO(source)
        self.parser.parse(source)
        source.close()

    def setDocumentLocator(self, locator):
        self.locator = locator
        XmlParsingError.locator = locator

    def startElement(self, name, attrs):
        raise NotImplementedError

    def endElement(self, name, attrs):
        raise NotImplementedError

    def characters(self, data):
        raise NotImplementedError

    def pop(self):
        try:
            return self._objects.pop().pop()
        except AttributeError:
            return None

# end of class XmlParser


class XmlWidgetBuilder(XmlParser):
    """\
    parser used to build the tree of widgets from an xml file
    """
    def startElement(self, name, attrs):
        if name == 'application':
            # get properties of the app
            self._appl_started = True
            app = common.app_tree.app
            encoding = attrs.get("encoding")
            if encoding:
                try:
                    unicode('a', encoding)
                except LookupError:
                    pass
                else:
                    app.encoding = encoding
                    app.encoding_prop.set_value(encoding)
            path = attrs.get("path")
            if path:
                app.output_path = path
                app.outpath_prop.set_value(path)
            name = attrs.get("name")
            if name:
                app.name = name
                app.name_prop.toggle_active(True)
                app.name_prop.set_value(name)
            klass = attrs.get("class")
            if klass:
                app.klass = klass
                app.klass_prop.toggle_active(True)
                app.klass_prop.set_value(klass)
            option = attrs.get("option")
            if option:
                try:
                    option = int(option)
                except ValueError:
                    option = config.default_multiple_files
                app.codegen_opt = option
                app.codegen_prop.set_value(option)
            language = attrs.get('language')
            if language:
                app.codewriters_prop.set_str_value(language)
                app.set_language(language)
            top_win = attrs.get("top_window")
            if top_win:
                self.top_window = top_win
            try:
                use_gettext = int(attrs["use_gettext"])
            except (KeyError, ValueError):
                use_gettext = config.default_use_gettext
            if use_gettext:
                app.use_gettext = True
                app.use_gettext_prop.set_value(True)

            try:
                is_template = int(attrs["is_template"])
            except (KeyError, ValueError):
                is_template = False
            app.is_template = is_template

            try:
                overwrite = int(attrs['overwrite'])
            except (KeyError, ValueError):
                overwrite = config.default_overwrite
            if overwrite:
                app.overwrite = True
                app.overwrite_prop.set_value(True)
            else:
                app.overwrite = False
                app.overwrite_prop.set_value(False)

            try:
                use_new_namespace = int(attrs['use_new_namespace'])
            except (KeyError, ValueError):
                use_new_namespace = False
            app.set_use_old_namespace(not use_new_namespace)
            app.use_old_namespace_prop.set_value(not use_new_namespace)

            indent_symbol = attrs.get("indent_symbol")
            if indent_symbol == 'space':
                app.indent_mode = 1
            elif indent_symbol == 'tab':
                app.indent_mode = 0
            app.indent_mode_prop.set_value(app.indent_mode)

            indent = attrs.get("indent_amount")
            if indent:
                try:
                    indent_amount = int(indent)
                except (KeyError, ValueError):
                    indent_amount = config.default_indent_amount
                else:
                    app.indent_amount = indent_amount
                    app.indent_amount_prop.set_value(indent_amount)

            source_extension = attrs.get("source_extension")
            if source_extension and source_extension[0] == '.':
                app.source_ext = source_extension[1:]
                app.source_ext_prop.set_value(source_extension[1:])
            header_extension = attrs.get("header_extension")
            if header_extension and header_extension[0] == '.':
                app.header_ext = header_extension[1:]
                app.header_ext_prop.set_value(header_extension[1:])

            try:
                for_version = attrs['for_version']
                app.for_version = for_version
                app.for_version_prop.set_str_value(for_version)
            except KeyError:
                pass
            return
        if not self._appl_started:
            raise XmlParsingError(
                _("the root of the tree must be <application>")
                )
        if name == 'object':
            # create the object and push it on the appropriate stacks
            XmlWidgetObject(attrs, self)
        else:
            # handling of the various properties
            try:
                # look for a custom handler to push on the stack
                handler = self.top().obj.get_property_handler(name)
                if handler:
                    self.top().prop_handlers.push(handler)
                # get the top custom handler and use it if there's one
                handler = self.top().prop_handlers.top()
                if handler:
                    handler.start_elem(name, attrs)
            except AttributeError:
                pass
            self._curr_prop = name

    def endElement(self, name):
        if name == 'application':
            self._appl_started = False
            if hasattr(self, 'top_window'):
                common.app_tree.app.top_window = self.top_window
                common.app_tree.app.top_win_prop.SetStringSelection(
                    self.top_window)
            return
        if name == 'object':
            # remove last object from the stack
            obj = self.pop()
            if obj.klass in ('sizeritem', 'sizerslot'):
                return
            si = self._sizer_item.top()
            if si is not None and si.parent == obj.parent:
                sprop = obj.obj.sizer_properties
                # update the values
                sprop['option'].set_value(si.obj.option)
                sprop['flag'].set_value(si.obj.flag_str())
                sprop['border'].set_value(si.obj.border)
                # call the setter functions
                obj.obj['option'][1](si.obj.option)
                obj.obj['flag'][1](si.obj.flag_str())
                obj.obj['border'][1](si.obj.border)
        else:
            # end of a property or error
            # 1: set _curr_prop value
            data = common._encode_from_xml("".join(self._curr_prop_val))
            if data:
                try:
                    handler = self.top().prop_handlers.top()
                    if not handler or handler.char_data(data):
                        # if char_data returned False,
                        # we don't have to call add_property
                        self.top().add_property(self._curr_prop, data)
                except AttributeError:
                    pass
            # 2: call custom end_elem handler
            try:
                # if there is a custom handler installed for this property,
                # call its end_elem function: if this returns True, remove
                # the handler from the Stack
                handler = self.top().prop_handlers.top()
                if handler.end_elem(name):
                    self.top().prop_handlers.pop()
            except AttributeError:
                pass
            self._curr_prop = None
            self._curr_prop_val = []

    def characters(self, data):
        if not data or data.isspace():
            return
        if self._curr_prop is None:
            raise XmlParsingError(_("character data can be present only "
                                  "inside properties"))
        self._curr_prop_val.append(data)

# end of class XmlWidgetBuilder


class ProgressXmlWidgetBuilder(XmlWidgetBuilder):
    """\
    Adds support for a progress dialog to the widget builder parser
    """
    def __init__(self, *args, **kwds):
        self.input_file = kwds.get('input_file')
        if self.input_file:
            del kwds['input_file']
            self.size = len(self.input_file.readlines())
            self.input_file.seek(0)
            self.progress = wx.ProgressDialog(
                _("Loading..."),
                _("Please wait while loading the app"),
                20
                )
            self.step = 4
            self.i = 1
        else:
            self.size = 0
            self.progress = None
        XmlWidgetBuilder.__init__(self, *args, **kwds)

    def endElement(self, name):
        if self.progress:
            if name == 'application':
                self.progress.Destroy()
            else:
                if self.locator:
                    where = self.locator.getLineNumber()
                    value = int(round(where * 20.0 / self.size))
                else:
                    # we don't have any information, so we update the progress
                    # bar ``randomly''
                    value = (self.step * self.i) % 20
                    self.i += 1
                self.progress.Update(value)
        XmlWidgetBuilder.endElement(self, name)

    def parse(self, *args):
        try:
            XmlWidgetBuilder.parse(self, *args)
        finally:
            if self.progress:
                self.progress.Destroy()

    def parse_string(self, *args):
        try:
            XmlWidgetBuilder.parse_string(self, *args)
        finally:
            if self.progress:
                self.progress.Destroy()

# end of class ProgressXmlWidgetBuilder


class ClipboardXmlWidgetBuilder(XmlWidgetBuilder):
    """\
    Parser used to cut&paste widgets. The differences with XmlWidgetBuilder
    are:
      - No <application> tag in the piece of xml to parse
      - Fake parent, sizer and sizeritem objects to push on the three stacks:
        they keep info about the destination of the hierarchy of widgets (i.e.
        the target of the 'paste' command)
      - The first widget built must be hidden and shown again at the end of
        the operation
    """
    def __init__(self, parent, sizer, pos, option, flag, border):
        XmlWidgetBuilder.__init__(self)
        self.parent_node = parent.node

        class XmlClipboardObject(object):
            def __init__(self, **kwds):
                self.__dict__.update(kwds)

        par = XmlClipboardObject(obj=parent, parent=parent)  # fake window obj
        if sizer is not None:
            # fake sizer object
            szr = XmlClipboardObject(obj=sizer, parent=parent)
            sizeritem = Sizeritem()
            sizeritem.option = option
            sizeritem.flag = flag
            sizeritem.border = border
            sizeritem.pos = pos
            # fake sizer item
            si = XmlClipboardObject(obj=sizeritem, parent=parent)
        # push the fake objects on the stacks
        self._objects.push(par)
        self._windows.push(par)
        if sizer is not None:
            self._objects.push(szr)
            self._sizers.push(szr)
            self._objects.push(si)
            self._sizer_item.push(si)
        self.depth_level = 0
        self._appl_started = True  # no application tag when parsing from the
                                   # clipboard

    def startElement(self, name, attrs):
        if name == 'object' and attrs.has_key('name'):
            # generate a unique name for the copy
            oldname = str(attrs['name'])
            newname = oldname
            i = 0
            while common.app_tree.has_name(newname,node=self.parent_node ):
                if not i:
                    newname = '%s_copy' % oldname
                else:
                    newname = '%s_copy_%s' % (oldname, i)
                i += 1
            attrs = dict(attrs)
            attrs['name'] = newname
        XmlWidgetBuilder.startElement(self, name, attrs)
        if name == 'object':
            if not self.depth_level:
                common.app_tree.auto_expand = False
                try:
                    self.top_obj = self.top().obj
                except AttributeError:
                    common.message.exception(
                        _('Exception! obj: %s') % self.top_obj
                        )
            self.depth_level += 1

    def endElement(self, name):
        if name == 'object':
            obj = self.top()
            self.depth_level -= 1
            if not self.depth_level:
                common.app_tree.auto_expand = True
                try:
                    # show the first object and update its layout
                    common.app_tree.show_widget(self.top_obj.node)
                    self.top_obj.show_properties()
                    common.app_tree.select_item(self.top_obj.node)
                except AttributeError:
                    common.message.exception(
                        _('Exception! obj: %s') % self.top_obj
                        )
        XmlWidgetBuilder.endElement(self, name)

# end of class ClipboardXmlWidgetBuilder


class XmlWidgetObject(object):
    """\
    A class to encapsulate a widget read from an xml file: its purpose is to
    store various widget attributes until the widget can be created

    @ivar in_sizers: If True, the widget is a sizer, opposite of L{in_windows}
    @type in_sizers: Boolean
    
    @ivar in_windows: If True, the wiget is not a sizer, pposite of
                      L{in_sizers}
    @type in_windows: Boolean

    @ivar prop_handlers: Is a stack of custom handler functions to set
                         properties of this object
    """
    def __init__(self, attrs, parser):
        self.prop_handlers = Stack()
        self.parser = parser
        self.in_windows = self.in_sizers = False
        try:
            base = attrs.get('base', None)
            self.klass = attrs['class']
        except KeyError:
            raise XmlParsingError(_("'object' items must have a 'class' "
                                  "attribute"))

        if base is not None:
            # if base is not None, the object is a widget (or sizer), and
            # not a sizeritem
            sizer = self.parser._sizers.top()
            parent = self.parser._windows.top()
            if parent is not None:
                parent = self.parent = parent.obj
            else:
                self.parent = None
            sizeritem = self.parser._sizer_item.top()
            if sizeritem is not None:
                sizeritem = sizeritem.obj
            if sizer is not None:
                # we must check if the sizer on the top of the stack is
                # really the one we are looking for: to check this
                if sizer.parent != parent:
                    sizer = None
                else:
                    sizer = sizer.obj
            if hasattr(sizeritem, 'pos'):
                pos = sizeritem.pos
            else:
                pos = None

            if parent and hasattr(parent, 'virtual_sizer') and \
                parent.virtual_sizer:
                sizer = parent.virtual_sizer
                sizer.node = parent.node
                sizeritem = Sizeritem()
                if pos is None:
                    pos = sizer.get_itempos(attrs)

            # build the widget
            if pos is not None:
                pos = int(pos)
            self.obj = common.widgets_from_xml[base](attrs, parent, sizer,
                                                     sizeritem, pos)
            try:
                #self.obj.klass = self.klass
                self.obj.set_klass(self.klass)
                self.obj.klass_prop.set_value(self.klass)
            except AttributeError:
                pass

            # push the object on the appropriate stack
            if isinstance(self.obj, edit_sizers.SizerBase):
                self.parser._sizers.push(self)
                self.in_sizers = True
            else:
                self.parser._windows.push(self)
                self.in_windows = True

        elif self.klass == 'sizeritem':
            self.obj = Sizeritem()
            self.parent = self.parser._windows.top().obj
            self.parser._sizer_item.push(self)

        elif self.klass == 'sizerslot':
            sizer = self.parser._sizers.top().obj
            assert sizer is not None, \
                   _("malformed wxg file: slots can only be inside sizers!")
            sizer.add_slot()
            self.parser._sizer_item.push(self)

        # push the object on the _objects stack
        self.parser._objects.push(self)

    def pop(self):
        if self.in_windows:
            return self.parser._windows.pop()
        elif self.in_sizers:
            return self.parser._sizers.pop()
        else:
            return self.parser._sizer_item.pop()

    def add_property(self, name, val):
        """\
        adds a property to this widget. This method is not called if there
        was a custom handler for this property, and its char_data method
        returned False
        """
        if name == 'pos':  # sanity check, this shouldn't happen...
            print 'add_property pos'
            return
        try:
            self.obj[name][1](val)  # call the setter for this property
            try:
                prop = self.obj.properties[name]
                prop.set_value(val)
                prop.toggle_active(True)
            except AttributeError:
                pass
        except KeyError:
            # unknown property for this object
            # issue a warning and ignore the property
            import sys
            print >> sys.stderr, _("Warning: property '%s' not supported "
                  "by this object ('%s') ") % (name, self.obj)

#end of class XmlWidgetObject


class CodeWriter(XmlParser):
    """\
    Parser used to produce the source from a given XML file

    @ivar _toplevels: Toplevel objects, i.e. instances of a custom class
    @ivar app_attrs: Attributes of the app (name, class, top_window)
    @type app_attrs: Dictionary
    @ivar top_win: Class name of the top window of the app (if any)
    @type top_win: String
    @ivar out_path: This allows to override the output path specified in the
                    XML file
    @ivar preview: If True, we are generating the code for the preview
    @type preview: Boolean
    """
    def __init__(self, writer, input, from_string=False, out_path=None,
                 preview=False, class_names=None):
        # writer: object that actually writes the code
        XmlParser.__init__(self)
        self._toplevels = Stack()
        self.app_attrs = {}
        self.top_win = ''
        self.out_path = out_path
        self.code_writer = writer
        self.preview = preview
        # used in the CustomWidget preview code, to generate better previews
        # (see widgets/custom_widget/codegen.py)
        self.class_names = class_names
        if self.class_names is None:
            self.class_names = set()

        if from_string:
            self.parse_string(input)
        else:
            inputfile = None
            try:
                inputfile = open(input)
                self.parse(inputfile)
            finally:
                if inputfile:
                    inputfile.close()

    def startElement(self, name, attrs_impl):
        attrs = {}
        try:
            encoding = self.app_attrs['encoding']
            unicode('a', encoding)
        except (KeyError, LookupError):
            if name == 'application':
                encoding = str(attrs_impl.get(
                    'encoding',
                    config.default_encoding
                    ))
            else:
                encoding = config.default_encoding
        # turn all the attribute values from unicode to str objects
        for attr, val in attrs_impl.items():
            attrs[attr] = common._encode_from_xml(val, encoding)
        if name == 'application':
            # get the code generation options
            self._appl_started = True
            self.app_attrs = attrs
            try:
                attrs['option'] = bool(int(attrs['option']))
                use_multiple_files = attrs['option']
            except (KeyError, ValueError):
                use_multiple_files = attrs['option'] = \
                    config.default_multiple_files
            if self.out_path is None:
                try:
                    self.out_path = attrs['path']
                except KeyError:
                    raise XmlParsingError(_("'path' attribute missing: could "
                                          "not generate code"))
            else:
                attrs['path'] = self.out_path

            # Prevent empty output path
            if not self.out_path:
                raise XmlParsingError(
                    _("'path' attribute empty: could not generate code")
                    )

            # Check if the values of use_multiple_files and out_path agree
            if use_multiple_files:
                if not os.path.isdir(self.out_path):
                    raise errors.WxgOutputDirectoryNotExist(self.out_path)
                if not os.access(self.out_path, os.W_OK):
                    raise errors.WxgOutputDirectoryNotWritable(self.out_path)
            else:
                if os.path.isdir(self.out_path):
                    raise errors.WxgOutputPathIsDirectory(self.out_path)
                directory = os.path.dirname(self.out_path)
                if directory:
                    if not os.path.isdir(directory):
                        raise errors.WxgOutputDirectoryNotExist(directory)
                    if not os.access(directory, os.W_OK):
                        raise errors.WxgOutputDirectoryNotWritable(directory)

            # initialize the writer
            self.code_writer.initialize(attrs)
            return
        if not self._appl_started:
            raise XmlParsingError(
                _("the root of the tree must be <application>")
                )
        if name == 'object':
            # create the CodeObject which stores info about the current widget
            CodeObject(attrs, self, preview=self.preview)
            if attrs.has_key('name') and \
                   attrs['name'] == self.app_attrs.get('top_window', ''):
                self.top_win = attrs['class']
        else:
            # handling of the various properties
            try:
                # look for a custom handler to push on the stack
                w = self.top()
                handler = self.code_writer.get_property_handler(name, w.base)
                if handler:
                    w.prop_handlers.push(handler)
                # get the top custom handler and use it if there's one
                handler = w.prop_handlers.top()
                if handler:
                    handler.start_elem(name, attrs)
            except AttributeError:
                common.message.exception(_('ATTRIBUTE ERROR!!'))
            self._curr_prop = name

    def endElement(self, name):
        if name == 'application':
            self._appl_started = False
            if self.app_attrs:
                self.code_writer.add_app(self.app_attrs, self.top_win)
            # call the finalization function of the code writer
            self.code_writer.finalize()
            return
        if name == 'object':
            obj = self.pop()
            if obj.klass in ('sizeritem', 'sizerslot'):
                return
            # at the end of the object, we have all the information to add it
            # to its toplevel parent, or to generate the code for the custom
            # class
            if obj.is_toplevel and not obj.in_sizers:
                self.code_writer.add_class(obj)
            topl = self._toplevels.top()
            if topl:
                self.code_writer.add_object(topl, obj)
                # if the object is not a sizeritem, check whether it
                # belongs to some sizer (in this case,
                # self._sizer_item.top() doesn't return None): if so,
                # write the code to add it to the sizer at the top of
                # the stack
                si = self._sizer_item.top()
                if si is not None and si.parent == obj.parent:
                    szr = self._sizers.top()
                    if not szr:
                        return
                    self.code_writer.add_sizeritem(topl, szr, obj,
                                                   si.obj.option,
                                                   si.obj.flag_str(),
                                                   si.obj.border)
        else:
            # end of a property or error
            # 1: set _curr_prop value
            try:
                encoding = self.app_attrs['encoding']
                unicode('a', encoding)
            except (KeyError, LookupError):
                encoding = config.default_encoding
            data = common._encode_from_xml(u"".join(self._curr_prop_val),
                                           encoding)
            if data:
                handler = self.top().prop_handlers.top()
                if not handler or handler.char_data(data):
                    # if char_data returned False,
                    # we don't have to call add_property
                    self.top().add_property(self._curr_prop, data)
            # 2: call custom end_elem handler
            try:
                # if there is a custom handler installed for this property,
                # call its end_elem function: if this returns True, remove
                # the handler from the stack
                obj = self.top()
                handler = obj.prop_handlers.top()
                if handler.end_elem(name, obj):
                    obj.prop_handlers.pop()
            except AttributeError:
                pass
            self._curr_prop = None
            self._curr_prop_val = []

    def characters(self, data):
        if not data or data.isspace():
            return
        if self._curr_prop is None:
            raise XmlParsingError(_("character data can only appear inside "
                                  "properties"))
        self._curr_prop_val.append(data)

# end of class CodeWriter


class CodeObject(object):
    """\
    A class to store information needed to generate the code for a given
    object.
    
    @ivar in_sizers: If True, the widget is a sizer, opposite of L{in_windows}
    @type in_sizers: Boolean
    
    @ivar in_windows: If True, the wiget is not a sizer, pposite of
                      L{in_sizers}
    @type in_windows: Boolean

    @ivar is_container: If True, the widget is a container (frame, dialog,
                        panel, ...)
    @type is_container: Boolean

    @ivar is_toplevel: If True, the object is a toplevel one: for window
                       objects, this means that they are instances of a
                       custom class, for sizers, that they are at the top
                       of the hierarchy.
    @type is_toplevel: Boolean

    @ivar properties: Properties of the widget sizer
    @type properties: Dictionary

    @ivar prop_handlers: Is a stack of custom handler functions to set
                         properties of this object
    """
    def __init__(self, attrs, parser, preview=False):
        self.parser = parser
        self.in_windows = self.in_sizers = False
        self.is_toplevel = False
        self.is_container = False
        self.properties = {}
        self.prop_handlers = Stack()
        self.preview = preview
        try:
            base = attrs.get('base', None)
            self.klass = attrs['class']
        except KeyError:
            raise XmlParsingError(_("'object' items must have a 'class' "
                                  "attribute"))
        self.parser._objects.push(self)
        self.parent = self.parser._windows.top()
        if self.parent is not None:
            self.parent.is_container = True
        self.base = None
        if base is not None:  # this is a ``real'' object, not a sizeritem
            self.name = attrs['name']
            self.base = common.class_names[base]
            can_be_toplevel = common.toplevels.has_key(base)
            if (self.parent is None or self.klass != self.base) and \
                   can_be_toplevel:
                #self.base != 'CustomWidget':
                self.is_toplevel = True
                # ALB 2005-11-19: for panel objects, if the user sets a
                # custom class but (s)he doesn't want the code
                # to be generated...
                if int(attrs.get('no_custom_class', False)) and \
                       not self.preview:
                    self.is_toplevel = False
                    #print 'OK:', str(self)
                    #self.in_windows = True
                    #self.parser._windows.push(self)
                else:
                    self.parser._toplevels.push(self)
            elif self.preview and not can_be_toplevel and \
                     self.base != 'CustomWidget':
                # if this is a custom class, but not a toplevel one,
                # for the preview we have to use the "real" class
                #
                # ALB 2007-08-04: CustomWidgets handle this in a special way
                # (see widgets/custom_widget/codegen.py)
                self.klass = self.base

            # temporary hack: to detect a sizer, check whether the name
            # of its class contains the string 'Sizer': TODO: find a
            # better way!!
            if base.find('Sizer') != -1:
                self.in_sizers = True
                if not self.parser._sizers.count():
                    self.is_toplevel = True
                else:
                    # the sizer is a toplevel one if its parent has not a
                    # sizer yet
                    sz = self.parser._sizers.top()
                    if sz.parent != self.parent:
                        self.is_toplevel = True
                self.parser._sizers.push(self)
            else:
                self.parser._windows.push(self)
                self.in_windows = True
        else:  # the object is a sizeritem
            self.obj = Sizeritem()
            self.obj.flag_s = '0'
            self.parser._sizer_item.push(self)

    def __str__(self):
        return "<xml_code_object: %s, %s, %s>" % (self.name, self.base,
                                                  self.klass)

    def add_property(self, name, value):
        if hasattr(self, 'obj'):  # self is a sizeritem
            try:
                if name == 'flag':
##                     flag = 0
##                     for f in value.split('|'):
##                         flag |= Sizeritem.flags[f.strip()]
##                     setattr(self.obj, name, flag)
                    self.obj.flag_s = value.strip()
                else:
                    setattr(self.obj, name, int(value))
            except:
                raise XmlParsingError(_("property '%s' not supported by "
                                        "'%s' objects") % (name, self.klass))
        self.properties[name] = value

    def pop(self):
        if self.is_toplevel and not self.in_sizers:
            self.parser._toplevels.pop()
        if self.in_windows:
            return self.parser._windows.pop()
        elif self.in_sizers:
            return self.parser._sizers.pop()
        else:
            return self.parser._sizer_item.pop()

# end of class CodeObject


class Stack(object):
    def __init__(self):
        self._repr = []

    def push(self, elem):
        self._repr.append(elem)

    def pop(self):
        try:
            return self._repr.pop()
        except IndexError:
            return None

    def top(self):
        try:
            return self._repr[-1]
        except IndexError:
            return None

    def count(self):
        return len(self._repr)

# end of class Stack


class Sizeritem(object):
    if common.use_gui:
        flags = {'wxALL': wx.ALL,
                 'wxEXPAND': wx.EXPAND, 'wxALIGN_RIGHT': wx.ALIGN_RIGHT,
                 'wxALIGN_BOTTOM': wx.ALIGN_BOTTOM,
                 'wxALIGN_CENTER_HORIZONTAL': wx.ALIGN_CENTER_HORIZONTAL,
                 'wxALIGN_CENTER_VERTICAL': wx.ALIGN_CENTER_VERTICAL,
                 'wxLEFT': wx.LEFT, 'wxRIGHT': wx.RIGHT,
                 'wxTOP': wx.TOP,
                 'wxBOTTOM': wx.BOTTOM,
                 'wxSHAPED': wx.SHAPED,
                 'wxADJUST_MINSIZE': wx.ADJUST_MINSIZE, }
        flags['wxFIXED_MINSIZE'] = wx.FIXED_MINSIZE

    def __init__(self):
        self.option = self.border = 0
        self.flag = 0

    def __getitem__(self, name):
        if name != 'flag':
            return (None, lambda v: setattr(self, name, v))

        def get_flag(v):
            val = reduce(lambda a, b: a | b,
                         [Sizeritem.flags[t] for t in v.split("|")])
            setattr(self, name, val)
        return (None, get_flag)
##                 lambda v: setattr(self, name,
##                                   reduce(lambda a,b: a|b,
##                                          [Sizeritem.flags[t] for t in
##                                           v.split("|")])))

    def flag_str(self):
        """\
        Returns the flag attribute as a string of tokens separated by a '|'
        (used during the code generation)
        """

        if hasattr(self, 'flag_s'):
            return self.flag_s
        else:
            try:
                tmp = {}
                for k in self.flags:
                    if self.flags[k] & self.flag:
                        tmp[k] = 1
                # patch to make wxALL work
                remove_wxall = 4
                for k in ('wxLEFT', 'wxRIGHT', 'wxTOP', 'wxBOTTOM'):
                    if k in tmp:
                        remove_wxall -= 1
                if remove_wxall:
                    try:
                        del tmp['wxALL']
                    except KeyError:
                        pass
                else:
                    for k in ('wxLEFT', 'wxRIGHT', 'wxTOP', 'wxBOTTOM'):
                        try:
                            del tmp[k]
                        except KeyError:
                            pass
                    tmp['wxALL'] = 1
                tmp = '|'.join(tmp.keys())
            except:
                print 'EXCEPTION: self.flags = %s, self.flag = %s' % \
                      (self.flags, repr(self.flag))
                raise
            if tmp:
                return tmp
            else:
                return '0'

# end of class Sizeritem
