# vPresent is an immersive presentation creation and display application.
# Copyright (C) 2007-2008 by Infiscape Corporation
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

import os, os.path, string
import tempfile
import traceback
import types
import copy

import gui.document as document


NODE_MODULES = ['config', 'dc', 'drawing', 'dr3d', 'form', 'meta', 'office',
                'presentation', 'script', 'style', 'svg', 'text', 'vpresent',
                'xlink']

gVerbose = False

def getOpenDocVersion():
   '''
   Returns the version of the OpenDocument specification implemented
   herein.
   '''
   return 1.0

def createWorkDir():
   return tempfile.mkdtemp()

def destroyWorkDir(workDir):
   for dirpath, dirs, files in os.walk(workDir, False):
      for f in files:
         os.unlink(os.path.join(dirpath, f))
      for d in dirs:
         os.rmdir(os.path.join(dirpath, d))

   if os.path.exists(workDir):
      os.rmdir(workDir)

def tagToType(tag):
   base_name = tag.split(':')
   ns_name   = base_name[0]
   # Look into this.
   #assert ALL_NAMESPACES.has_key(ns_name)

   type_name = \
      ''.join([string.capitalize(s) for s in base_name[1].split('-')])

   return '%s_%s' % (ns_name, type_name)

def tagToBaseClass(tag):
   try:
      if tag.count(':') and not tag.startswith(':'):
         (ns, ln) = tag.split(':')
         # Fake module to add class to.
         mod = getattr(bases, ns)
      else:
         ln = elm.name
         # Fake module to add class to.
         mod = bases
      cls_name = "".join([p.capitalize() for p in ln.split('-')])
      return getattr(mod, cls_name)
   except:
      raise AttributeException, "Class does not exist for tag '%s'." % tag

class Document(document.Document):
   '''
   Base class for documents that implement the OpenDocument specification.
   '''

   def __init__(self, undoStack, parent = None):
      document.Document.__init__(self, undoStack, parent)

   def getSettings(self):
      return []

   def getMetadata(self):
      return []

   def getStyles(self):
      return []

class PlainText:
   def __init__(self, content):
      self._content = content

   def acceptsChildren(self):
      return False

# XXX: This is probably going to have to derive frrom QtCore.QObject in order
# to allow it to emit signals.
class DocumentNode(object):
   #__metaclass__ = OpenDocumentMeta
   overrides = {}
   element = None

   def __init__(self, attrs = None):
      if attrs is None:
         attrs = {}

      self._attrs = attrs

      if self.element is None:
         print "WARNING: %s does not have an element." % self.__class__
         self._children = None
      elif len(self.element.childTypes) > 0:
         self._children = []
      else:
         self._children = None

      self._rootNode = None


   def __reduce_ex__(self, version):
      attrs = self._attrs.copy()
      return buildNode, (self.__class__.element.name, attrs), self.__getstate__()

   def __getstate__(self):
      r = {'_attrs': self._attrs,
           '_children': self._children}
      return r

   def __setstate__(self, d):
      self._attrs = d['_attrs']

      # Add all of our children.
      # NOTE: It is important that we call addChild instead of copying the
      #       children list because each DocumentNode type may override
      #       insertChildNode in order to get some custom behavior.
      children = d['_children']
      if children is not None:
         for c in children:
            self.addChild(c)

   def getRootNode(self):
      return self._rootNode

   def __str__(self):
      return "<%s>" % (self.element.name)

   def _ns(self, ns):
      return NSHelp(self, ns)

   def __getrealname(self, name):
      (ns, rn) = name.split('_', 1)
      rn = rn.replace('_', '-')
      return ns + ":" + rn

   def __getcdata(self):
      if len(self._children) > 0:
         assert 1 == len(self._children)
         assert isinstance(self._children[0], PlainText)
         return self._children[0]._content
      return None

   # XXX: Base class
   def __getattr__(self, name):
      if 'cdata' == name:
         # Check for a cdata override method.
         if hasattr(self, 'get_cdata'):
            return getattr(self, 'get_cdata')()
         return self.__getcdata()

      # Try to convert name to the attribute name.
      try:
         real_name = self.__getrealname(name)
      except:
         raise AttributeError, "%s got invalid attribute '%s'" % (self.__class__.__name__, name)
      #print "RN:", real_name

      if not self.__class__.element.attrs.has_key(real_name):
         raise AttributeError, "%s has no attribute '%s'" % (self.__class__.__name__, real_name)

      # Construct the method name to look for method overrides.
      get_override_name = "get_%s"%(name)
      ai = self.__class__.element.attrs[real_name]
      assert ai is not None
      if hasattr(self, get_override_name):
         getter = getattr(self, get_override_name)
         return getter(real_name, ai.default)
      if ai is not None and ai.set is not None:
         return ai.get(self, real_name, ai.default)
      if not self._attrs.has_key(real_name):
         return ai.default
      return self._attrs[real_name]

   def __setcdata(self, value):
      """ Helper method that sets the cdata for our node.
          NOTE: Creates a PlainText child if it does not already exist.
      """
      assert isinstance(value, basestring)
      if len(self._children) == 0:
         self.addChild(PlainText(value))
      else:
         assert 1 == len(self._children)
         assert PlainText == type(self._children[0])
         self._children[0]._content = value

   def __setattr__(self, name, value):
      # Allow the user to set custom members/attributes that start with '_'.
      if name.startswith('_'):
         object.__setattr__(self, name, value)
         return

      if 'cdata' == name:
         # Check for a cdata override method.
         if hasattr(self, 'set_cdata'):
            getattr(self, 'set_cdata')(value)
         else:
            self.__setcdata(value)
         return

      # Try to convert name to the attribute name.
      try:
         real_name = self.__getrealname(name)
      except:
         raise AttributeError, "%s got invalid attribute '%s'" % (self.__class__.__name__, name)
      #print "RN:", real_name

      if not self.__class__.element.attrs.has_key(real_name):
         raise AttributeError, "%s has no attribute '%s'" % (self.__class__.__name__, real_name)
   
      ai = self.__class__.element.attrs[real_name]
      set_override_name = "set_%s"%(name)
      if hasattr(self, set_override_name):
         setter = getattr(self, set_override_name)
         setter(real_name, value)
      elif ai is not None and ai.set is not None:
         ai.set(self, real_name, value)
      else:
         self._attrs[real_name] = value

   def getDocs(cls):
      doc_lines = []
      for (k, ai) in cls.element.attrs.iteritems():
         doc_lines.append("%-30s %-15s: %s" % (k, '['+str(ai.default)+']', ai.doc))
      return "\n".join(doc_lines)
   getDocs = classmethod(getDocs)

   def getRawAttributes(self):
      return self._attrs

   def setAttribute(self, name, value):
      if not self.attrs.has_key(name) or self.attrs[name] != value:
         self.attrs[name] = value
         # TODO: Emit a signal.

   def addChild(self, child):
      return self.insertChild(self.getNumChildren(), child)

   def insertChild(self, idx, child):
      if not self.acceptsChild(child):
         raise ValueError("Invalid child type %s added to %s" % (child, self))

      self._children.insert(idx, child)
      return child

   def subChild(self, indexOrObj):
      if types.IntType == type(indexOrObj):
         del self._children[indexOrObj]
      else:
         self._children.remove(indexOrObj)

   def __getitem__(self, index):
      return self._children[index]

#   def __setitem__(self, index, value):
#      self._children[index] = value

   def getChildren(self):
      return self._children

   def getNumChildren(self):
      if self._children is not None:
         return len(self._children)
      else:
         return 0

   def hasChildren(self):
      return self.getNumChildren() > 0

   def acceptsChildren(self):
      return len(self.element.childTypes) > 0

   # XXX: Depreciated
   def addChildType(self, childType):
      self.element.childTypes.add(childType)

   def acceptsChild(self, child):
      if isinstance(child, PlainText):
         return 'PlainText' in self.element.childTypes

      assert isinstance(child, DocumentNode)
      assert child.element is not None
      return child.element.name in self.element.childTypes

      #for t in self._childTypes:
      #   t_type = type(t)
      #
      #   if t_type == types.FunctionType or t_type == types.MethodType:
      #      if t(child):
      #         return True
      #   elif isinstance(child, t):
      #      return True
      #return False

   def find(self, path):
      # Handle extra cases where we are referencing local element.
      if path == '.':
         path = ''
      elif path.startswith('./'):
         path = path[2:]
      path = path.lstrip('/')

      parts = path.split('/')
      tag = parts[0]
      sub_path = '/'.join(parts[1:])

      #print "find(tag=%s, sub_path=%s)" % (tag, sub_path)

      if '' == tag:
         return self
      
      if self._children is None:
         return None

      for child in self._children:
         if child.element.name == tag:
            cr = child.find(sub_path)
            if cr is not None:
               return cr
      return None


def dictContainsDict(d1, d2):
   """ Returns true if d1 contains d2. """
   for (k,v) in d2.iteritems():
      if not d1.has_key(k) or d1[k] != d2[k]:
         return False
   return True

class NodeFilter(object):
   """
   Provides a list interface to a subset of a parent's children that
   have a given tag.
   """
   def __init__(self, parent, tag, attrs=None):
      """
      @parm parent Parent element whose children are filtered based on tag.
      @parm tag Value to use when filtering children. (ex. 'draw:page')
      """
      self.mParent = parent
      self.mTag = tag
      self.mAttrs = attrs

   def __getElms(self):
      elms = []
      for c in self.mParent._children:
         if c.element.name == self.mTag:
            if self.mAttrs is None or dictContainsDict(c._attrs, self.mAttrs):
               elms.append(c)
      return elms
      
   def __len__(self):
      return len(self.__getElms())

   def __getitem__(self, idx):
      return self.__getElms().__getitem__(idx)

   def __iter__(self):
      return self.__getElms().__iter__()

   def __contains__(self, item):
      return self.__getElms().__contains__(item)

   def __eq__(self, other):
      return other == self.__getElms()

   def index(self, obj):
      return self.__getElms().index(obj)

#################################### NEW ####################################

tag_class_map = {}

def __doCreateNode(tag, attrs, useDefaults, **kwargs):
   cls = tag_class_map[tag]
   obj = cls(attrs)
   for (k,v) in kwargs.iteritems():
      setattr(obj, k, v)

   # Get the element's attribute keys once.
   attrkeys = obj._attrs.keys()

   # Iterate over potential attributes.
   for (k,v) in cls.element.attrs.iteritems():
      # If the attribute is required and not in the element's attrs.
      if v.required and k not in attrkeys:
         if useDefaults:
            print "WARNING: Element '%s' missing required attribute '%s', using default '%s'." \
               % (obj.element.name, k, v.default)
            obj._attrs[k] = v.default
         else:
            raise AttributeError, "Element '%s' requires attribute '%s'." \
               % (cls.element.name, k)
   return obj

def createNode(tag, **kwargs):
   return __doCreateNode(tag, None, False, **kwargs)

def buildNode(tag, attrs):
   return __doCreateNode(tag, attrs, True)

class MixinMeta(type):
   def __new__(cls, classname, bases, classdict):
      obj = type.__new__(cls, classname, bases, classdict)
      overrides = {}
      child_types = set()
      for b in bases:
         if b.__dict__.has_key('overrides'):
            overrides.update(b.__dict__['overrides'])
         if b.__dict__.has_key('childTypes'):
            child_types.update(b.__dict__['childTypes'])

      # If the most derived class has overrides, use them.
      if classdict.has_key('overrides'):
         overrides.update(classdict['overrides'])
      if classdict.has_key('childTypes'):
            child_types.update(classdict['childTypes'])
      obj.overrides = overrides
      obj.otherChildTypes = child_types
      return obj

class OpenDocumentMeta(MixinMeta):
   def __new__(cls, classname, bases, classdict):
      obj = MixinMeta.__new__(cls, classname, bases, classdict)
      assert issubclass(obj, DocumentNode) or issubclass(obj, Mixin)

      # If we do not have a base class that came from the schema.
      if obj.element is None and classdict.has_key('element_name'):
         obj.element = Elem()
         obj.element.name = classdict['element_name']
      assert obj.element is not None

      # Add new derived class to tag-to-class map.
      tag_class_map[obj.element.name] = obj

      # Make a copy of the element since we are creating a new
      # class that could override the values in element.
      obj.element = copy.deepcopy(obj.element)
      for (k,v) in obj.overrides.iteritems():
         # Get the existing attribute with the given name.
         # NOTE: Creates a new attribute if it does not already have it.
         attr = obj.element.attrs.setdefault(k, Attribute(name=k))
         # Override the stored values with the user specified ones.
         if v is not None:
            attr.override(v)
      obj.element.childTypes.update(obj.otherChildTypes)

      return obj

class NSHelp(object):
   def __init__(self, obj, ns):
      self.obj = obj
      self.ns = ns

   def __getattr__(self, name):
      real_name = "%s_%s" % (self.ns, name)
      return getattr(self.obj, real_name)

# Helper converters
import re

# Group 0: The measurement
# Group 1: The units (in, cm, mm, px [pixels], pc [picas; 6 picas is one
#          inch], pt [points; 72 points is one inch])
sMeasurementRe = re.compile(r'^(\d+(?:\.\d+)?)(\w+)$')

sPercentageRe = re.compile(r'^(\d+)%$')

sValidUnits = ['in', 'cm', 'mm', 'px', 'pc', 'pt']

def isMeasurement(self, value):
   return self.sMeasurementRe.search(value) is not None

def isPercentage(self, value):
   return self.sPercentageRe.search(value) is not None

def getUnits(obj, realName, default):
   if default is None:
      default = (None, None)

   match = sMeasurementRe.search(obj._attrs.get(realName, ''))

   try:
      if match.group(2) in sValidUnits:
         return match.groups()
   except:
      pass
   return default

def setUnits(obj, realName, value):
   if type(value) not in [types.TupleType, types.ListType]:
      raise AttributeError, "Invalid units type '%s' must be tuple or list." % (type(value).__name__)
   if 2 != len(value):
      raise AttributeError, "Invalid units size '%s'" % (len(value))
   (val, unit) = value
   if type(val) not in [types.FloatType, types.IntType, types.LongType]:
      raise AttributeError, "Invalid num type '%s'" % (type(value).__name__)
   if unit not in sValidUnits:
      raise AttributeError, "Invalid unit '%s'" % (unit)

   obj._attrs[realName] = "%s%s" % (val, unit)

def getPercentage(obj, realName, default):
   if default is None:
      default = (1.0, None)

   try:
      match = sPercentageRe.search(obj._attrs.get(realName, ''))
      return (float(match.group(1)) / 100.0, None)
   except:
      return default

def setPercenage(obj, realName, value):
   val = value
   if type(value) not in [types.TupleType, types.ListType]:
      val = val[0]

   try:
      val = int(float(val) * 100.0)
   except:
      raise AttributeError, "Failed to create a percentage from '%s'" % (value)
   obj._attrs[realName] = "%s%"%val

def getUnitsOrPercentage(obj, realName, default):
   val = obj._attrs.get(realName, '')
   if isMeasurement(val):
      return getUnits(obj, realName, default)
   return getPercentage(obj, realName, default)

def setUnitsOrPercentage(obj, realName, value):
   if isMeasurement(value):
      setUnits(obj, realName, value)
   else:
      setPercentage(obj, realName, value)


def toBoolean(val):
   true_values = ["1","t","true","yes","on"]
   return val.lower() in true_values

def getBoolean(obj, realName, default):
   try:
      val = obj._attrs[realName].lower()
      return toBoolean(val)
   except KeyError:
      return default

def fromBool(value):
   if not types.BooleanType == type(value):
      raise AttributeError, "Boolean attribute '%s' can not convert '%s' of type '%s'." % (realName, value, type(value))
   if value:
      return "true"
   else:
      return "false"

def setBoolean(obj, realName, value):
   obj._attrs[realName] = fromBool(value)

############################################# RELAX NG ############################################

import Ft
from Ft.Xml import Parse
from Ft.Xml.XPath import Evaluate
from Ft.Xml.XPath.Context import Context
from Ft.Xml import EMPTY_NAMESPACE
from xml.dom import Node
import sys

if sys.version_info[0] == 2 and sys.version_info[1] < 4:
   import sets
   set = sets.Set

defines = {}
elements = {}
print_type_tree = False
debug_traverse = False

class DumbModule(object):
   pass
bases = DumbModule()

namespaces = {'relaxng':'http://relaxng.org/ns/structure/1.0',
              'a': 'http://relaxng.org/ns/compatibility/annotations/1.0'}


def combineDefine(define):
   name = define.getAttributeNS(None, 'name')
   combine = define.getAttributeNS(None, 'combine')
   if not defines.has_key(name):
      cdefine = define.cloneNode()
      cdefine.removeAttributeNS(None, 'combine')
      doc = define.ownerDocument
      if 'choice' == combine:
         new_child = doc.createElementNS(namespaces['relaxng'], 'choice')
      else:
         assert 'interleave' == combine
         new_child = doc.createElementNS(namespaces['relaxng'], 'group')
      cdefine.appendChild(new_child)
      defines[name] = cdefine

   new_define = defines[name]
   assert 1 == len(new_define.childNodes)
   new_parent = new_define.firstChild
   define.normalize()
   for c in define.childNodes:
      if Node.TEXT_NODE == c.nodeType:
         continue
      cc = c.cloneNode(True)
      new_parent.appendChild(cc)

class AttributeInfo(object):
   __slots__ = ['get', 'set', 'default', 'required', 'doc', 'data_types', 'values']

   def __init__(self, get=None, set=None, default=None, required=None,
                data_types=None, values=None, doc=''):
      self.default = default
      self.get = get
      self.set = set
      self.doc = doc
      self.required = required
      self.data_types = data_types
      self.values = values

class Attribute(object):
   def __init__(self, name):
      self.name = name
      self.default = None
      self.set = None
      self.get = None
      self.doc = None
      self.required = None

      self.data_types = []
      self.values = []

   def __eq__(self, o):
      return o.name == self.name and \
         o.default == self.default and \
         o.set == self.set and \
         o.get == self.get and \
         o.doc == self.doc and \
         o.required == self.required and \
         o.data_types == self.data_types and \
         o.values == self.values

   def __str__(self):
      #return "Attr %-30s r:%-5s d:%-15s dt:%-30s vals:%-30s get:%s set:%s" % \
      return "Attr %-20s r:%s d:%s dt:%s vals:%s get:%s set:%s" % \
         (self.name, self.required, self.default,
          self.data_types, self.values,
          self.get, self.set)

   def override(self, ai):
      """
      Override our attributes with the values in the given AttributeInfo.

      @pre All of our attrs are immutable and can not change unless replaced.

      The key to this working is that every Element has a shallow copy of
      each Attribute. It is important that this method replaces each python
      attr instead of extending it. Otherwise since each Attribute instance
      shares references to it's attrs we could modify another Elements attrs.
      """
      assert AttributeInfo == type(ai)
      for a in ['get', 'set', 'default', 'required', 'doc', 'data_types', 'values']:
         val = getattr(ai, a)
         if val is not None:
            setattr(self, a, val)

   def trav(self, node):
      global gNodeStack
      if node in gNodeStack and gVerbose:
         print "WARNING: Recursion detected"
      gNodeStack.append(node)

      pre =  "-"*len(gNodeStack)
      do_trav = True
      assert 'element' != node.localName
      if debug_traverse:
         print "%s Attr::trav %s a:%s" % (pre, node.localName, self.name)

      if 'attribute' == node.localName:
         self.name = node.getAttributeNS(None, 'name').strip()
         if node.hasAttributeNS(namespaces['a'], 'defaultValue'):
            self.default = node.getAttributeNS(namespaces['a'], 'defaultValue')

      if 'ref' == node.localName:
         ref_name = node.getAttributeNS(None, 'name').strip()
         other_node = defines[ref_name]
         #print "ref [%s]: %s" % (ref_name, other_node)
         self.trav(other_node)
      #else:
      #print "%s %s e:%s a:%s" % (pre, node.localName, elm, attr)
      #ref_name = node.getAttributeNS(None, 'name').strip()

      if 'data' == node.localName:
         data_type = node.getAttributeNS(None, 'type').strip()
         self.data_types.append(data_type)

      if 'value' == node.localName:
         value = node.childNodes[0].data
         self.values.append(value)

      if do_trav:
         for n in node.childNodes:
            if Node.TEXT_NODE != n.nodeType:
               self.trav(n)

      assert gNodeStack[-1] == node
      gNodeStack = gNodeStack[:-1]

class Elem(object):
   def __init__(self):
      self.name = None
      self.data_types = []
      self.values = []
      self.attrs = {}
      self.childTypes = set()

   def __deepcopy__(self, memo):
      e = Elem()
      e.name = self.name
      e.data_types = self.data_types
      e.values = self.values
      e.attrs = {}
      # Copy each attribute.
      for (k,v) in self.attrs.iteritems():
         nv = copy.copy(v)
         e.attrs[k] = nv
      # Create a copy of our childTypes set.
      e.childTypes = set(self.childTypes)
      return e

   def __eq__(self, o):
      return o.name == self.name and \
             o.data_types == self.data_types and \
             o.values == self.values and \
             o.attrs == self.attrs and \
             o.childTypes == self.childTypes

   def __str__(self):
      return "Elem %s t:%s v:%s c:%s" % (self.name, self.data_types, self.values, self.childTypes)

   def trav(self, node):
      """ Starts traversing an 'element' node. """
      self.optional = False
      global gNodeStack
      if node in gNodeStack and gVerbose:
         print "WARNING: Recursion detected"

      gNodeStack.append(node)

      pre =  "-"*len(gNodeStack)
      do_trav = True
      if debug_traverse:
         print "%s Elm::trav %s a:%s" % (pre, node.localName, self.name)

      if 'element' == node.localName:
         self.name = node.getAttributeNS(None, 'name').strip()

      for n in node.childNodes:
         if Node.TEXT_NODE != n.nodeType:
            self.trav_help(n)

      # If we allow data, then we can have a plain text child.
      if len(self.data_types) > 0:
         self.childTypes.add('PlainText')

      assert gNodeStack[-1] == node
      gNodeStack = gNodeStack[:-1]

   def trav_help(self, node):
      global gNodeStack
      if node in gNodeStack and gVerbose:
         print "WARNING: Recursion detected"

      if 'attribute' == node.localName:
         attr_name = node.getAttributeNS(None, 'name').strip()
         # XXX: Update an existing attribute if we already have it.
         attr = self.attrs.get(attr_name)
         if attr is None:
            attr = Attribute(name=attr_name)
            # XXX: Should we pass it to the constructor.
            attr.required = not self.optional
            self.attrs[attr_name] = attr
         attr.trav(node)
         return

      if 'element' == node.localName:
         elm_name = node.getAttributeNS(None, 'name').strip()
         self.childTypes.add(elm_name)
         return

      if 'text' == node.localName:
         self.childTypes.add('PlainText')

      gNodeStack.append(node)

      pre =  "-"*len(gNodeStack)
      do_trav = True
      if debug_traverse:
         print "%s Elm::trav %s a:%s" % (pre, node.localName, self.name)

      # XXX: Should this be put into the child iteration.
      if 'ref' == node.localName:
         ref_name = node.getAttributeNS(None, 'name').strip()
         other_node = defines[ref_name]
         #print "ref [%s]: %s" % (ref_name, other_node)
         self.trav_help(other_node)

      if 'data' == node.localName:
         data_type = node.getAttributeNS(None, 'type').strip()
         self.data_types.append(data_type)
         # XXX: Should we return here? Is it an exit condition.

      if 'value' == node.localName:
         value = node.childNodes[0].data
         self.values.append(value)
         # XXX: Should we return here? Is it an exit condition.

      # Save the optional status, and update it for the children.
      # XXX: There has to be a faster way to do this.
      old_optional = self.optional
      if node.localName in ['optional', 'choice']:
         self.optional = True

      if do_trav:
         for n in node.childNodes:
            if Node.TEXT_NODE != n.nodeType:
               self.trav_help(n)

      self.optional = old_optional

      assert gNodeStack[-1] == node
      gNodeStack = gNodeStack[:-1]


### Getters and Setters ###
def setNumber(obj, realName, value, types, min=None, max=None):
   if type(value) not in types:
      raise AttributeError, "Attribute %s [%s] must be one of %s." % (realName, value, types)
   if min is not None and value < min:
      raise AttributeError, "Attribute %s [%s] must be >= %s." % (realName, value, min)
   if max is not None and value > max:
      raise AttributeError, "Attribute %s [%s] must be <= %s." % (realName, value, max)
   obj._attrs[realName] = str(value)

def getNumber(obj, realName, default, type, min=None, max=None):
   if default and min: assert default >= min
   if default and max: assert default <= max

   value = obj._attrs.get(realName, default)
   try:
      value = type(value)
   except ValueError, ex:
      if default is None:
         default = type()
      print "Attribute %s is not an %s '%s', using default." % (realName, type, value)
      value = default

   # XXX: Should we return the default, or the min/max?
   if min is not None and value < min:
      print "Attribute %s [%s] must be >= %s." % (realName, value, min)
      if default is not None:
         value = default
      else:
         value = min
   if max is not None and value > max:
      print "Attribute %s [%s] must be <= %s." % (realName, value, max)
      if default is not None:
         value = default
      else:
         value = min
   return value
      
setInteger = lambda obj, realName, default, min=None, max=None: setNumber(obj, realName, default, [types.IntType], min, max)
getInteger = lambda obj, realName, default, min=None, max=None: getNumber(obj, realName, default, types.IntType, min, max)
setFloat = lambda obj, realName, default, min=None, max=None: setNumber(obj, realName, default, [types.FloatType], min, max)
getFloat = lambda obj, realName, default, min=None, max=None: getNumber(obj, realName, default, types.FloatType, min, max)
setLong = lambda obj, realName, default, min=None, max=None: setNumber(obj, realName, default, [types.LongType], min, max)
getLong = lambda obj, realName, default, min=None, max=None: getNumber(obj, realName, default, types.LongType, min, max)

setPositiveInteger = lambda obj, realName, default: setInteger(obj, realName, default, 0)
getPositiveInteger = lambda obj, realName, default: getInteger(obj, realName, default, 0)
setNonNegativeInteger = setPositiveInteger
getNonNegativeInteger = getPositiveInteger

setPositiveFloat = lambda obj, realName, default: setFloat(obj, realName, default, 0)
getPositiveFloat = lambda obj, realName, default: getFloat(obj, realName, default, 0)
setGTZFloat = lambda obj, realName, default: setFloat(obj, realName, default, 0.0000001)
getGTZFloat = lambda obj, realName, default: getFloat(obj, realName, default, 0.0000001)

def listSet(obj, realName, value):
   a = obj.__class__.element.attrs[realName]
   print "POSSIBLE:", a.values
   if value not in a.values:
      raise AttributeError, "Value '%s' for attribute %s not in valid list %s." % (value, realName, a.values)
   obj._attrs[realName] = value


def processRelaxNG(verbose=False, printTypeTree=False):
   """
   Parse the OpenDocument RelaxNG schema and construct a type tree
   for vPresent data structures.
   """
   gVerbose = verbose

   import gui.util as util
   filename = os.path.join(util.const.SCHEMA_DIR, 'OpenDocument-schema-v1.0-os.rng')
   uri = Ft.Lib.Uri.OsPathToUri(filename)
   doc = Parse(uri)
   context = Context(None, processorNss=namespaces)
   define_nodes = Evaluate(u'.//relaxng:define', doc, context)
   element_nodes = Evaluate(u'.//relaxng:element', doc, context)

   # Process defines.
   global gNodeStack
   gNodeStack = []
   for dn in define_nodes:
      name = dn.getAttributeNS(None, 'name')
      if dn.hasAttributeNS(None, 'combine'):
         combineDefine(dn)
      else:
         assert not defines.has_key(name)
         defines[name] = dn

   # Use strict schema by default.
   for k in defines.keys():
      strict_key = k+'-strict'
      if defines.has_key(strict_key):
         defines[k] = defines[strict_key]

   gNodeStack = []
   for node in element_nodes:
      elm_name = node.getAttributeNS(None, 'name').strip()
      if elements.has_key(elm_name) and gVerbose:
         print "WARNING: Already have an elment '%s' using the latest version." % (elm_name)
      elm = Elem()
      elements[elm_name] = elm
      elm.trav(node)

   for e in elements.values():
      for a in e.attrs.values():
         #if len(a.values) > 0:
         #   if debug_traverse and len(a.data_types):
         #      #assert 0 == len(a.data_types)
         #      print "WARNING: Element '%s' attribute '%s' has both data_types and values" % (e.name, a.name)
         #   a.set = listSet
         if len(a.data_types) == 1:
            if u'nonNegativeInteger' == a.data_types[0] or u'positiveInteger' == a.data_types[0]:
               a.set = setNonNegativeInteger
               a.get = getNonNegativeInteger
         elif len(a.values) > 0:
            if 2 == len(a.values) and u'true' in a.values and u'false' in a.values:
               a.set = setBoolean
               a.get = getBoolean

   if printTypeTree:
      print "Elements:"
      for e in elements.values():
         print e
         for a in e.attrs.values():
            print "   %s" % (a)

   # Create our bases classes.
   for elm in elements.values():
      if elm.name.count(':') and not elm.name.startswith(':'):
         (ns, ln) = elm.name.split(':')
         # If we already have fake ns module, create it.
         if not hasattr(bases, ns):
            setattr(bases, ns, DumbModule())
         # Fake module to add class to.
         mod = getattr(bases, ns)
      else:
         ns = ''
         ln = elm.name
         # Fake module to add class to.
         mod = bases

      cls_name = "".join([p.capitalize() for p in ln.split('-')])
      if cls_name != '':
         #print "Creating class", elm.name
         cls = type(str(cls_name),(DocumentNode,),{'element':elm})
         tag_class_map[elm.name] = cls
         # Ensure that we are not binding the class to a name that already exists.
         assert not hasattr(mod, cls_name)
         setattr(mod, cls_name, cls)

# TODO:
#  - paragraph-content has a empty element that has a choice of names under it.
#  - form:list-value element is defined multiple times as an embedded element.
