alexp.py 5.51 KB
#! /usr/bin/env python2.6
# -*- coding: utf-8 -*-

#---------------------------------

# Editado:

#Autor: Erickson Silva 
#Email: <erickson.silva@lavid.ufpb.br> <ericksonsilva@live.com>

#LAViD - Laboratório de Aplicações de Vídeo Digital

#---------------------------------


# Donatus Brazilian Portuguese Parser
#
# Copyright (C) 2010-2013 Leonel F. de Alencar
#
# Author: Leonel F. de Alencar <leonel.de.alencar@ufc.br>
# Homepage: <http://www.leonel.profusehost.net/>
#
# Project's URL: <http://sourceforge.net/projects/donatus/>
# For license information, see LICENSE.TXT
#
# $Id: alexp.py $

"""Este módulo contém funções que permitem utilizar o Aelius para etiquetar uma sentença, construindo entradas lexicais com base nas etiquetas atribuídas às palavras da sentença. Essas entradas lexicais são integradas em uma gramática CFG dada, que é transformada em um parser, utilizado para gerar uma árvore de estrutura sintagmática da sentença. 
"""
import re,nltk,platform, time, random
from os.path import expanduser
from os import environ, path
from Aelius.Extras import carrega
from Aelius import AnotaCorpus
from unicodedata import normalize


sentenca_anotada=""
sleep_times=[0.1,0.2]

def toqueniza(s):
	"""Decodifica string utilizando utf-8, retornando uma lista de tokens em unicode.
	"""
	decodificada=s.decode("utf-8")
	return AnotaCorpus.TOK_PORT.tokenize(decodificada)

def getAnaliseMorfologica():
	return sentenca_anotada
	#return [list(x) for x in sentenca_anotada]

def etiquetaSentenca(s):
	"""Aplica um dos etiquetadores do Aelius na etiquetagem da sentença dada como lista de tokens.
	"""
	etiquetador = carrega("AeliusHunPos")
	anotada = AnotaCorpus.anota_sentencas([s],etiquetador,"hunpos")[0]
	while (anotada[0][1] is None):
		time.sleep(random.choice(sleep_times))
		anotada = AnotaCorpus.anota_sentencas([s],etiquetador,"hunpos")[0]
	#anotada[0] = (anotada[0][0].lower(), anotada[0][1])
	#return anotada
	tag_punctuation = [".",",","QT","("]
	return [[x[0].lower(),x[1]] for x in anotada if x[1] not in tag_punctuation]

def geraEntradasLexicais(lista):
	"""Gera entradas lexicais no formato CFG do NLTK a partir de lista de pares constituídos de tokens e suas etiquetas.
	"""
	entradas=[]
	for e in lista:
		# é necessário substituir símbolos como "-" e "+" do CHPTB
		# que não são aceitos pelo NLTK como símbolos não terminais
		c=re.sub(r"[-+]","_",e[1])
		c=re.sub(r"\$","_S",c)
		entradas.append("%s -> '%s'" % (c, removeAcento(e[0])))
	return entradas

def corrigeAnotacao(lista):
	"""Esta função deverá corrigir alguns dos erros de anotação mais comuns do Aelius. No momento, apenas é corrigida VB-AN depois de TR.
	"""
	i=1
	while i < len(lista):
		if lista[i][1] == "VB-AN" and lista[i-1][1].startswith("TR"):
			lista[i]=(lista[i][0],"VB-PP")
		i+=1

def encontraArquivo():
	"""Encontra arquivo na pasta vlibras-translate.
	"""
	so = platform.system()
	if so == 'Windows':
		return environ.get("HOMEDRIVE") + "\\vlibras-libs\\vlibras-translate\data\cfg.syn.nltk"
	elif "TRANSLATE_DATA" in environ:
		return os.path.join(environ("TRANSLATE_DATA"), "cfg.syn.nltk")
	return expanduser("~") + "/vlibras-translate/data/cfg.syn.nltk"

def extraiSintaxe():
	"""Extrai gramática armazenada em arquivo cujo caminho é definido relativamente ao diretório nltk_data.
	"""
	arquivo=encontraArquivo()
	if arquivo:
		f=open(arquivo,"rU")
		sintaxe=f.read()
		f.close()
		return sintaxe
	else:
		print "Arquivo %s não encontrado em nenhum dos diretórios de dados do NLTK:\n%s" % (caminho,"\n".join(nltk.data.path))

def analisaSentenca(sentenca):
	"""Retorna lista de árvores de estrutura sintagmática para a sentença dada sob a forma de uma lista de tokens, com base na gramática CFG cujo caminho é especificado como segundo argumento da função. Esse caminho é relativo à pasta nltk_data da instalação local do NLTK. A partir da etiquetagem morfossintática da sentença são geradas entradas lexicais que passam a integrar a gramática CFG. O caminho da gramática e o parser gerado são armazenados como tupla na variável ANALISADORES.
	"""
	parser=constroiAnalisador(sentenca)
	codificada=[removeAcento(w).encode("utf-8") for w in sentenca]
	trees=parser.parse_one(codificada)
	return trees

def constroiAnalisador(s):
	"""Constrói analisador a partir de uma única sentença não anotada, dada como lista de tokens, e uma lista de regras sintáticas no formato CFG, armazenadas em arquivo. Esta função tem um bug, causado pela maneira como o Aelius etiqueta sentenças usando o módulo ProcessaNomesProprios: quando a sentença se inicia por paravra com inicial minúscula, essa palavra não é incorporada ao léxico, mas a versão com inicial maiúscula.
	"""
	global sentenca_anotada
	sentenca_anotada=etiquetaSentenca(s)
	corrigeAnotacao(sentenca_anotada)
	entradas=geraEntradasLexicais(sentenca_anotada)
	lexico="\n".join(entradas)
	gramatica="%s\n%s" % (extraiSintaxe().strip(),lexico)
	cfg=nltk.CFG.fromstring(gramatica)
	return nltk.ChartParser(cfg)

def removeAcento(texto):
	try:
		return normalize('NFKD', texto.encode('utf-8').decode('utf-8')).encode('ASCII', 'ignore')
	except:
		return normalize('NFKD', texto.encode('iso-8859-1').decode('iso-8859-1')).encode('ASCII','ignore')

def exibeArvores(arvores):
	"""Função 'wrapper' para a função de exibição de árvores do NLTK"""
	nltk.draw.draw_trees(*arvores)

def run(sentenca):
	tokens=toqueniza(sentenca)
	tree=analisaSentenca(tokens)
	return tree