# Copyright (c) 2001 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# 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.,
# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

__revision__ = '$Id: Classify.py,v 1.11 2001/12/21 13:44:03 syt Exp $'

from narval.lib import *
source_vector = load_extension_module('classify', 'source_vector')
process_vectors = load_extension_module('classify', 'process_vectors')
sort_vectors_mc = load_extension_module('classify', 'sort_vectors_mc')


MOD_XML = """<?xml version="1.0" encoding="ISO-8859-1"?>
<module name='Classify' xmlns:al='%s'>"""%AL_NS
    
def calc_vector_f(args, doc) :
    """
    first step in classify:
        data list => long vector list (use_engl_dico=0, nb_mini_letters=3)
    """
    encod = get_encoding()
    output = {'output':[]}
    list_error=[]
    for s_u in args['data-unit']:
        from cStringIO import StringIO
        # FIXME: parameters use_engl_dico, nb_mini_letters, dico may be fixed
        data = StringIO(select_one(s_u, 'data/text()').data.encode(encod))
        tsv = source_vector.tranf_source_vector(use_engl_dico=1, nb_mini_letters=4)
        try:
            vector = tsv.do(data, only_dico=0)
        except source_vector.ReadError:
            # Error, Pass this data.
            list_error.append(s_u)
            continue
        v = doc.createElementNS(NO_NS, 'vector')
        for word, value in vector.items():
            l = doc.createElementNS(NO_NS, 'entry')
            l.setAttributeNS(NO_NS, 'value', str(value))
            l.appendChild(doc.createTextNode(unicode(word, encod)))
            v.appendChild(l)
        s_u.appendChild(v)
        output['output'].append(s_u)
    ## insert error with namespace != AL_NS to avoid plan failure
    if len(list_error) != 0:
        errors = doc.createElementNS(NO_NS, 'error')
        errors.appendChild(title)
        for s_u in list_error[:]:
            errors.appendChild(s_u)
        output['error'] = [errors]
    
    return output


# Sort with term freq (optional) and mobile point algorithm ###################
def sort_tf_mc_f(args, doc) :
    """
    Algo:
        data list => long vector list (use_engl_dico=0, nb_mini_letters=3)
        long vector list => vector list
        vector list => sorted tree
        sorted tree => sort-group
    """
    output = {'output':[]}
    vector_list=[]
    list_error=[]
    s_u_l = args['data-unit']
    tf_idf = args['tfidf']
    if tf_idf == 'no':
        tf_idf = 0
    elif tf_idf is None:
        if len(s_u_l) < 50:
            tf_idf = None
        else:
            tf_idf = 1
    class_nb = args['classes']
    if class_nb:
        class_nb = int(class_nb.firstChild.data)
    for s_u in s_u_l:
        tmp_url_vect = []
        from cStringIO import StringIO
        d = select_one(s_u, 'data')
        select_one(d, 'text()')
        data = StringIO(select_one(s_u, 'data/text()').data.encode('latin1'))
        # FIXME parameters use_engl_dico, nb_mini_letters, dico may be fixed
        tsv = source_vector.tranf_source_vector(use_engl_dico=1,
                                                nb_mini_letters=4)
        tmp_url_vect.append(s_u)
        try:
            tmp_url_vect.append(tsv.do(data, debug=0, only_dico=0))
        except source_vector.ReadError:
            # Error, Pass this data.
            list_error.append(s_u)
            continue
        vector_list.append(tmp_url_vect)

    ## return an empty node if no news
    if len(vector_list) == 0:
        output['output'].append(doc.createElementNS(NO_NS, 'sort-group'))
        return output
    ## search a classes number value or performs it
    if not class_nb or class_nb < 0:
        if len(vector_list) < 12:
            class_nb = 4
        else:
            class_nb = int(len(vector_list)/5)
    ## process tf-idf on vectors ?
    if tf_idf:
        p_vect = process_vectors.process_vectors(vector_list)
        vector_list = p_vect.do()
    ## vector list => sorted tree
    svt = sort_vectors_mc.sort_vectors_mc(vector_list, class_nb)
    the_tree = svt.do()
    ## sorted tree => sort-group
    encod = get_encoding()
    for classe in the_tree:
        ## Search common words in all articles and process title
        txt_title = common_words(classe, vector_list)
        s_g = doc.createElementNS(NO_NS, 'sort-group')
        title = doc.createElementNS(NO_NS, 'common')
        title.appendChild(doc.createTextNode(unicode(txt_title, encod)))
        s_g.appendChild(title)
        for index in classe:
            s_g.appendChild(vector_list[index][0])
        output['output'].append(s_g)    
    ## insert error with namespace != AL_NS to avoid plan failure
    if len(list_error) != 0:
        errors = doc.createElementNS(NO_NS, 'error')
        errors.appendChild(title)
        for s_u in list_error[:]:
            errors.appendChild(s_u)
        output['error'] = [errors]
    return output
    
# Sort with term freq (optional) and mobile point algorithm ###################
def get_classes_f(args, doc) :
    """
    Second step:
        list of data-unit with a representative vector
        => vectors list
        vectors list => sorted tree
        sorted tree => sort-group
    """
    output = {'output':[]}
    vector_list=[]
    s_u_l = args['data-unit']
    tf_idf = args['tfidf']
    if tf_idf == 'no':
        tf_idf = 0
    elif tf_idf is None:
        if len(s_u_l) < 50:
            tf_idf = None
        else:
            tf_idf = 1
    class_nb = args['classes']
    if class_nb:
        class_nb = int(class_nb.firstChild.data)
    for s_u in s_u_l:
        d = {}
        for n in select_many(s_u, 'vector/entry'):
            d[select_one(n, 'text()').data] = int(n.getAttributeNS(NO_NS, 'value'))
        vector_list.append((s_u, d))
    ## return an empty node if no news
    if len(vector_list) == 0:
        output['output'].append(doc.createElementNS(NO_NS, 'sort-group'))
        return output
    ## search a classes number value or performs it
    if not class_nb or class_nb < 0:
        if len(vector_list) < 12:
            class_nb = 4
        else:
            class_nb = int(len(vector_list)/5)
    ## process tf-idf on vectors ?
    if tf_idf:
        p_vect = process_vectors.process_vectors(vector_list)
        vector_list = p_vect.do()
    ## vector list => sorted tree
    svt = sort_vectors_mc.sort_vectors_mc(vector_list, class_nb)
    the_tree = svt.do()
    ## sorted tree => sort-group
    for classe in the_tree:
        # Search common words in all articles and process title
        txt_title = common_words(classe, vector_list)
        s_g = doc.createElementNS(NO_NS, 'sort-group')
        title = doc.createElementNS(NO_NS, 'common')
        title.appendChild(doc.createTextNode(txt_title))
        s_g.appendChild(title)
    
        for index in classe:
            s_g.appendChild(vector_list[index][0])
        output['output'].append(s_g)
    return output
    
MOD_XML = MOD_XML +  """
<al:action name='calc_vector' func='calc_vector_f'>
 <al:description>calculate a vector with the most import words present in the data
child of a data-unit element</al:description>
 <al:input id='data-unit' list='yes'>
  <al:match>data-unit[data/text()]</al:match>
  <al:description lang='en'>sort unit, with a data child containing text tp process</al:description>
 </al:input>
 <al:output id='output'  list='yes'>
  <al:description lang='en'>groups containing data-unit childs and a common child with the
common words of the group</al:description>
  <al:match>data-unit/vector</al:match>
 </al:output>
</al:action>

<al:action name='get_classes'
func='get_classes_f'>
 <al:description>classify a list of data-unit elements with a vector precalculated.
It use the mobil points method and optionaly the TF-IDF method. The TF-IDF
algorithm is used when a tf-idf element is found or automatically when there are enough
units to use this method.</al:description>
 <al:input id='data-unit'  use='yes' list='yes'>
  <al:match>data-unit[vector]</al:match>
  <al:description lang='en'>sort unit, with a data child containing text for sort</al:description>
 </al:input>
 <al:input id='classes'  optional='yes'>
  <al:description lang='en'>this element allow to specify the number of classes used by the
algorithms. If no classes number element is found, it will be adjusted according
to the number of units to sort.</al:description>
  <al:match>classes-number</al:match>
 </al:input>
 <al:input id='tfidf'  optional='yes'>
  <al:description lang='en'>the tf-idf method will be used or not if this node
contains 'yes' or 'no'. If this node isn't present, tf-idf will be automatically
used as soon as there are enough data to classify.
  </al:description>
  <al:match>tf-idf</al:match>
 </al:input>
 <al:output id='output'  list='yes'>
  <al:description lang='en'>groups containing data-unit childs and a common child with the
common words of the group</al:description>
  <al:match>sort-group</al:match>
 </al:output>
</al:action>

<al:action name='classify'
func='sort_tf_mc_f'>
 <al:description>classify a list of data-unit elements in sort-group, with the mobil
point method and optionaly the tf-idf method. The tf-idf algorithm is used when
a tf-idf element is found or automatically when there are enough units to use
this method.</al:description>
 <al:input id='data-unit'  use='yes' list='yes'>
  <al:match>data-unit[data/text()]</al:match>
  <al:description lang='en'>sort unit, with a data child containing text for sort</al:description>
 </al:input>
 <al:input id='classes'  optional='yes'>
  <al:description lang='en'>this element allow to specify the number of classes used by the
algorithms. If no classes number element is found, it will be adjusted according
to the number of units to sort.</al:description>
  <al:match>classes-number</al:match>
 </al:input>
 <al:input id='tfidf'  optional='yes'>
  <al:description lang='en'>the tf-idf method will be used or not if this node
contains 'yes' or 'no'. If this node isn't present, tf-idf will be automatically
used as soon as there are enough data to classify.
  </al:description>
  <al:match>tf-idf</al:match>
 </al:input>
 <al:output id='output'  list='yes'>
  <al:description lang='en'>groups containing data-unit childs and a common child with the
common words of the group</al:description>
  <al:match>sort-group</al:match>
 </al:output>
</al:action>
"""

MOD_XML = MOD_XML + "</module>"

def common_words(classe, vector_url_list, debug=0):
    """ search common words in all articles from classe
    return the first 5 words separated by ', '  
    """
    import types
    com_words = {}
    for index in classe:
        if type(index) == types.IntType:
            for duo in vector_url_list[index][1].items():
                if com_words.has_key(duo[0]):
                    com_words[duo[0]] = com_words[duo[0]] + duo[1]
                else:
                    com_words[duo[0]] = duo[1]
    # com_words => list and sort
    list_duo = com_words.items()
    misc = load_extension_module('classify', 'misc')
    list_duo.sort(misc.cmp_eq_words)
    txt_title = ''

    if debug:
        for i in range(10):
            if len(list_duo)-1 >= i:
                print list_duo[i]
            else:
                break
                
    for i in range(5):
        if len(list_duo)-1 >= i:
            txt_title = txt_title + list_duo[i][0] + ', '
        else:
            break
    txt_title = txt_title[:-2]
    if len(txt_title) != 0:
        return txt_title
    else:
        return 'No words'


## if __name__ == '__main__':
##     d = read_domtree_from_string('''<?xml version="1.0" encoding="ISO-8859-1"?>
##     <arguments>
##     <data-unit>
##     <data>
##   Les langages de programmation généraux ont connu une évolution laborieuse et étrange depuis 1946. Pour montrer l\'évolution de la syntaxe, des fonctionnalités, les voici classés par date d\'implémentation. Quand Pascal, C++, Python, sont apparus, qui les a imaginés, pourquoi les langages ont-ils ces différences?
##  Les dates retenues correspondent au moment ou le premier programme à été écrit et compilé dans le langage. Je fournis quand c\'est possible le site d\'un compilateur ou interpréteur pour le langage. L\'historique concerne surtout les langages d?application ou de script universels, mais je mentionne certains langages spécialisés importants et tous ceux qui ont eu une influence majeure pour la conception d\'autres langages de programmation.
##   Ce texte a été réalisé en partie de mémoire, si vous trouvez des erreurs ou des omissions, vous pouvez le dire: cliquez sur le bouton feedback ou envoyez un commentaire à: webmaster@scriptol.com.

##  Ce document est (c) 2000 par D.G Sureau. Si vous désirez l\'utiliser comme référence, vous pouvez mettre un lien sur ce site. Vous pouvez l\'imprimer et le reproduire. Vous ne pouvez placer ce document sur un autre site.


##  Les principales catégories de langages (à ce jour), sont les langages fonctionnels et procéduraux (ou impératifs), et les langages logiques..
##  Un langage est dit fonctionnel, au sens mathématique du mot fonction, si chaque opération est indépendante du contexte, et si le résultat d\'une fonction dépend exclusivement de ses arguments.
##  Haskell, Lisp, sont des langages fonctionnels de genres différents. Ils sont  destinés à l\'intelligence artificielle. Lisp utilise le principe de réduction de problème, Prolog le principe de résolution par des prédicats de premier ordre et est un langage logique.
##  Dans les langages procéduraux ou impératifs comme C, Pascal, le  du traitement dépend du fichier, tandis qu\'il dépend de l\'interpréteur avec les langages fonctionnels.
##  On oppose aussi le style impératif au style déclaratif. En fait on considère déclaratif un système qui énonce des connaissances, l\'énoncé d\'un problème, et fournit un mécanisme de résolution. Prolog se dit déclaratif.
##  Pratiquement tous les langages, fonctionnels ou impératifs sont maintenant orientés objets, et utilisent des classes décrivant des objets réels ou purement informatiques.

##  On distingue 4 générations de langages de programmation:
## 1 Langages machine.
## 2 Langages symboliques et autocodes.
## 3 Langages indépendants du matériel, comme Basic, C, Cobol, Algol...
## 4 Langages conçus pour décrire le problème, comme Simula et autres langages à objets .

## 5  Les langages à programmation logique prétendent représenter la cinquième génération, mais leur utilisation est marginale.  La cinquième génération pourrait être celle des langages Internet, donc fonctionnant sur toute machine sans compilation spécifique.
## 6 La sixième génération, c\'est une idée personnelle, pourrait être issue des langages comme ASP et PHP qui s\'intègrent directement aux données, à la page de texte.

##  Indépendamment de ces générations théoriques, les grandes dates sont les suivantes:

## - Années 50: Création des langages de haut niveau (plus proches de l\'homme).

## - Années 60: Foisonnement de langages spécialisés. Forth. Simula I. Lisp, Cobol.
##   On essaie sans succès d\'imposer des langages généraux: Algol, PL/1.

## - Années 70: Duel entre programmation structurée avec Pascal et l\'efficacité du langage C   (cela dure encore en 2000). Généralisation du Basic interprété sur les micro-ordinateurs apparus en 1977, jusqu\'à la fin des années 80.
## - Années 80: Expérimentation d\'autres voies et notamment des objets. ML. Smalltalk.
##    Sur les micro-ordinateurs, on utilise maintenant C, Pascal, Basic compilé.
## - Années 90: Généralisation de la programmation objet grâce aux performances des
##   micro-ordinateurs. Java, Perl, Python s\'ajoutent aux langages micros.
## - Années 2000: Programmation Internet (et les innovations à venir, voir en fin de texte).

##     </data>
##     </data-unit>
##     </arguments>
##     ''')
##     v = calc_vector_f(d.documentElement)
##     PrettyPrint(v)
  
  
