# -*- coding: utf-8 -*-

# Copyright (c) 2007 Stas Zykiewicz <stas.zytkiewicz@gmail.com>
#
#           pyassetml.py
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 3 of the GNU General Public License
# as published by the Free Software Foundation.  A copy of this license should
# be included in the file GPL-3.
#
# 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 Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

"""

Module pyassetml.py - Provides the parsing and searching of assetml files.
                      See (http XXX todo website ??) for info.
                      
The original concept and description of the assetml framework is by
bruno Coudoin <bruno.coudoin@free.fr> to be used in gcompris and other
games-like applications.

This module is intended to be used by applications who are using assetml
as there means of handeling the sources of images, sounds and other 'assets'.
This module is not intended to be used as a commandline tool, use 
pyassetml-query instead.

The main part of pyassetml is the parser class (AssetmlParser) which takes a
assetml file path as argument in the constructor. The constructor then turns
every assetml node into python class objects (AssetNode).
The class members are python sequences containing the nodes attributes and data.

After the parsing the whole assetml file is turned in python objects and can be
handeled like any other python object.
Searching can be done for every item in the assetml tree. Every search pattern
concist of a sequence of two, a category (assetml node) and a item (node data).

For more information, please look at the docstrings.

The usage example is a basic example from within a application, it's not
intended as a example of usage by a user, for that look at the pyassetml-query
tool.

Usage:

>>> import pyassetml
<module 'pyassetml' from '/home/stas/Projects/assetml/pyassetml.pyc'>
>>> parser = pyassetml.AssetmlParser('flags.assetml')
>>> mime = ('mimetype','image/png')
>>> cat = ('categories','flags')
>>> files = parser.find((mime,cat))
>>> from pprint import pprint
>>> pprint(files)
[u'/usr/share/assetml/gcompris/boards/flags/am.png',
 u'/usr/share/assetml/gcompris/boards/flags/ar.png',
 u'/usr/share/assetml/gcompris/boards/flags/az.png',
 u'/usr/share/assetml/gcompris/boards/flags/ca.png',
 .... etc]
>>> name = parser.find_names((('file','am.png'),('description','fr')))
>>> name
[u'Amharique']

"""
_DEBUG = 0
version = "0.2.2"
import os,pprint,sys,locale
from xml.dom import minidom
from types import ListType, DictType, StringTypes
from CPConstants import ASSETMLROOT

ASSETTAGS = ["Description","Credits","Categories"]
DEFAULTLOCALE = u'en'

class PyassetmlError(Exception):
    pass

class AssetNode:
    def __init__(self):
        """This class is used to store the assetml nodes.
          and gets it's attributes set by the parser"""
        pass
    
    def search(self,cat,pattern,strict=0):
        """Method to search the contents of this object.
          cat = assetml category
          pattern = what to search for, can be a sequence to search 
          for multiple items in one category.
          Searching on filenames is done by string.find(), so it's
          possible to search on (example) '.png' and this will return
          all matches with .png.
          If found return self, else None."""
        if _DEBUG: print "cat,pattern from node object",cat,pattern
        try:
            item = self.__dict__[cat] 
            if _DEBUG: 
                print "Item from node object"
                pprint.pprint(item)
        except KeyError, info:
            # This category doesn't excist
            return None
        if type(item) is ListType and pattern in item:
            return self
        elif type(item) is DictType:
            if item.has_key(pattern) or pattern in item.values():
                return self
        elif strict:
            if item == pattern:
                return self
        else:
            if item.find(pattern) != -1:
                return self
        return None

class RootNode(AssetNode):
    pass
          
class AssetmlParser:
    def __init__(self,path):
        """ Parse a assetml file and turn it in to python class objects"""
        file = os.path.join(ASSETMLROOT,path)
        if not os.path.exists(file):
            print >> sys.stderr, 'Can\'t find assetml file %s' % file
            #path = os.path.join(os.environ['HOME'],path)
            print >> sys.stderr, 'Trying',path
            if not os.path.exists(path):
                print >> sys.stderr, 'Nope,... exiting'
                sys.exit(1)
            else:
                file = path
        self.parserroot, spam = os.path.split(file)
        del(spam)
        self.obj_list = []
        try:
            self.xml = minidom.parse(file)
        except RuntimeError,info:
            print "Error in pyassetml",info
            print "file",file
            raise PyassetmlError,info
        self.xmlnodes = self.xml.getElementsByTagName('Asset')
        self._get_nodes()
                    
    def __del__(self):
        try:
            self.xml.unlink() # advise from python lib reference
                          # Garbage collect doesn't work in all versions
        except:
            pass
            
    def get_locale(self):
        try:
            loc = locale.getdefaultlocale()[0][:2]
        except:
            # Default to English
            return 'en'      
        return loc
        
    def _get_nodes(self):
        """ Turns the nodes of a xml file in to class objest.
          builds a list with the class objects, where the first object is the rootnode
          and the rest are the asset nodes.(self.obj_list)
        """
        rootnode = self.xml.getElementsByTagName('AssetML')[0]
        self.root_obj = RootNode()
        self.root_obj.dataset = rootnode.attributes[u'dataset'].value
        path = rootnode.attributes[u'rootdir'].value[3:] #path starts with ../, loose it
        #self.root_obj.rootdir = os.path.join(self.parserroot, path)
        self.root_obj.rootdir = self.parserroot
        self.root_obj.locale = rootnode.attributes[u'locale'].value
                
        for node in self.xmlnodes:
            obj = AssetNode()
            obj.file = node.attributes[u'file'].value
            obj.mimetype = node.attributes[u'mimetype'].value
            # set lists of data from nodes from asset as a class attributes
            for tagname in ASSETTAGS:
                if tagname == "Description": # store descriptions in a dic; {'nl':'Nederlands','fr':'francaise'}
                    dic = {}
                    for item in node.getElementsByTagName(tagname):
                        if item.firstChild: # have we a text node?
                            try:
                                dic[item.attributes[u'xml:lang'].value] = item.firstChild.data
                            except KeyError:
                                # No attribute, must be the locale default
                                dic[DEFAULTLOCALE] = item.firstChild.data
                    setattr(obj, tagname.lower(), dic)
                    continue
                l = []
                for item in node.getElementsByTagName(tagname):
                    if item.firstChild: # have we a text node?
                        l.append(item.firstChild.data)
                setattr(obj, tagname.lower() ,l)
            if _DEBUG >= 2:
                print "Object dicts"
                pprint.pprint(obj.__dict__)
            self.obj_list.append(obj)
    
    def get_XMLobjects(self):
        return self.obj_list 
        
    def find(self, cats_items, return_files = 1,strict=0):
        """ find in the categories 'cats' the items 'items' and return a list
         with the objects containing the cat and item.
         cats_items must be a sequence of at least one pair.
         like (('category','item'),('other category','item')). It's also possible
         to give one category and multiple items, like (('file',('a.png','b.png')),).
         The categories to search in are the elements(nodes) and attributes of assetml.
         look for more info at the source of pyassetml-query.
         return_files = 1, we return file paths, otherwise return the objects including
                        the root node object (last one).
         """
        if self.obj_list == []:
            print >> sys.stderr, 'method find. No objects to search'
            return None
          
        objects = self.obj_list[:]
        if _DEBUG: print "Start search with objects",objects
        for c,i in cats_items:
            if _DEBUG: print "Search loop c,i",c,i
            if type(i) in StringTypes:
                i = (i,)
            found_list = []
            for item in i:
                cat = unicode(c)
                item = unicode(item)
                if cat == u'description' and item == '':
                    item = DEFAULTLOCALE
                for obj in objects:
                    if obj.search(cat, item,strict):
                        found_list.append(obj)
            objects = found_list[:]
            
        if _DEBUG: print "found objects",objects
        if return_files:
            files = map(getattr, objects, (u'file',) * len(objects))
            found = map(os.path.join, [self.root_obj.rootdir] * len(objects), files) 
        else:
            objects.append(self.root_obj)
            found = objects
        return found
      
    def find_names(self, cats_items,strict=0):
        """ Return descriptions of the files in the found objects.
          See also pyassetml-query option -n"""
        if _DEBUG: print "cat/items",cats_items
        import operator,string
        key = DEFAULTLOCALE
        for i in cats_items:
            if i[0] == 'description':
                key = i[1]    
        objects = self.find(cats_items, return_files=0,strict=strict)[:-1]# get the objects minus the last one (rootnode)
        if _DEBUG: print "objects from find_names",objects
        if objects:
            desc_list = map(getattr, objects, (u'description',) * len(objects))
            names = map(string.strip, map(operator.getitem, desc_list, (key,) * len(desc_list)))
            if _DEBUG: print "desc_list",desc_list,"\n names",names
        else:
            return None # No objects found
        return names

        

if __name__ == '__main__':
    print __doc__
    print "\n Don't use me, use pyasstml-query.py.\n I give myself only to a other module."
    print "\n Import this, don't run it."
