1 |
#!/usr/bin/env python |
#!/usr/bin/env python |
2 |
# |
# |
3 |
#Minë. Mundo Interactivo-Narrativo en Español [Tierra-Media] |
#Minë. Mundo Interactivo-Narrativo en Español [Tierra-Media] |
4 |
#Copyright (C) 2002 Pablo Ruiz Múzquiz |
#Copyright (C) 2002 Pablo Ruiz Múzquiz |
5 |
# |
# |
6 |
# |
# |
7 |
#This program is free software; you can redistribute it and/or modify |
#This program is free software; you can redistribute it and/or modify |
8 |
#it under the terms of the GNU General Public License as published by |
#it under the terms of the GNU General Public License as published by |
9 |
#the Free Software Foundation; either version 2 of the License, or |
#the Free Software Foundation; either version 2 of the License, or |
10 |
#(at your option) any later version. |
#(at your option) any later version. |
11 |
# |
# |
12 |
#This program is distributed in the hope that it will be useful, |
#This program is distributed in the hope that it will be useful, |
13 |
#but WITHOUT ANY WARRANTY; without even the implied warranty of |
#but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 |
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 |
#GNU General Public License for more details. |
#GNU General Public License for more details. |
16 |
# |
# |
17 |
#You should have received a copy of the GNU General Public License |
#You should have received a copy of the GNU General Public License |
18 |
#along with this program; if not, write to the Free Software |
#along with this program; if not, write to the Free Software |
19 |
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
20 |
# |
# |
21 |
|
|
22 |
#Fichero: conversacion.py |
#Fichero: conversacion.py |
23 |
|
|
24 |
"""Clase Conversacion.""" |
"""Clase Conversacion.""" |
25 |
|
|
26 |
|
|
27 |
from xml.dom import minidom |
from xml.dom import minidom |
28 |
import random |
import random |
29 |
import sys |
import sys |
30 |
import string |
import string |
31 |
import os |
import os |
32 |
|
|
33 |
from utils import * |
from utils import * |
34 |
from bloquedialogo import * |
from bloquedialogo import * |
35 |
from contexto import * |
from contexto import * |
36 |
|
|
37 |
class Conversacion(BloqueDialogo): |
class Conversacion(BloqueDialogo): |
38 |
"""Una coleccion de contextos accesibles durante un mismo dialogo. |
"""Una coleccion de contextos accesibles durante un mismo dialogo. |
39 |
|
|
40 |
Contiene los siguientes atributos: |
Contiene los siguientes atributos: |
41 |
- id (string): identifica univocamente la conversacion. No puede contener espacios. |
- id (string): identifica univocamente la conversacion. No puede contener espacios. |
42 |
- version (string): versión del sistema de dialogos. |
- version (string): versión del sistema de dialogos. |
43 |
- autor (string): nombre del autor de la descripción. |
- autor (string): nombre del autor de la descripción. |
44 |
- pnj (string): nombre del pnj al que pertenece la conversacion. |
- pnj (string): nombre del pnj al que pertenece la conversacion. |
45 |
- comentario (string): comentario explicativo del fichero de conversacion. |
- comentario (string): comentario explicativo del fichero de conversacion. |
46 |
|
|
47 |
- _tolerancia_offtopic(int): porcentaje de rechazo de temas fuera de contexto |
- _tolerancia_offtopic(int): porcentaje de rechazo de temas fuera de contexto |
48 |
- _probabilidad_offtopic_continuar(float): probabilidad de que un offtopic permita o impida cambiar de tema |
- _probabilidad_offtopic_continuar(float): probabilidad de que un offtopic permita o impida cambiar de tema |
49 |
|
|
50 |
- _sinonimos({string: string}): diccionario de conversión de sinonimos a los nombres de tema equivalentes |
- _sinonimos({string: string}): diccionario de conversión de sinonimos a los nombres de tema equivalentes |
51 |
- _contextos_iniciales([contexto]): contextos definidos con el atributo inicial='si' |
- _contextos_iniciales([contexto]): contextos definidos con el atributo inicial='si' |
52 |
- _indice_contextos({string: contexto}): acceso a los contextos por identificador |
- _indice_contextos({string: contexto}): acceso a los contextos por identificador |
53 |
- _temas_especiales: diccionario con las lista de temas especiales por defecto para la conversación. |
- _temas_especiales: diccionario con las lista de temas especiales por defecto para la conversación. |
54 |
|
|
55 |
|
|
56 |
|
|
57 |
Invariantes: |
Invariantes: |
58 |
id != None |
id != None |
59 |
version == BloqueDialogo.Version |
version == BloqueDialogo.Version |
60 |
autor != None |
autor != None |
61 |
pnj != None |
pnj != None |
62 |
comentario != None |
comentario != None |
63 |
|
|
64 |
_tolerancia_offtopic >= 0 |
_tolerancia_offtopic >= 0 |
65 |
|
|
66 |
_sinonimos != None |
_sinonimos != None |
67 |
_contextos_iniciales != None |
_contextos_iniciales != None |
68 |
_indice_contextos != None |
_indice_contextos != None |
69 |
|
|
70 |
_sinonimos.has_key(s) <-> _sinonimos[s] == tema.nombre() \ |
_sinonimos.has_key(s) <-> _sinonimos[s] == tema.nombre() \ |
71 |
para algun tema in contexto.temas() \ |
para algun tema in contexto.temas() \ |
72 |
para algun contexto in self.contextos() |
para algun contexto in self.contextos() |
73 |
-indice_contextos.has_key(n) <-> contexto.id == n para algun contexto in self._contextos |
-indice_contextos.has_key(n) <-> contexto.id == n para algun contexto in self._contextos |
74 |
-item.relacionados().has_key(t) <-> el contexto item.relacionado(nombretema)[t] contiene un tema con nombre t |
-item.relacionados().has_key(t) <-> el contexto item.relacionado(nombretema)[t] contiene un tema con nombre t |
75 |
""" |
""" |
76 |
|
|
77 |
|
|
78 |
def __init__(self, id): |
def __init__(self, id): |
79 |
"""Crea una conversacion nueva a partir de un fichero de descripción de conversacion. |
"""Crea una conversacion nueva a partir de un fichero de descripción de conversacion. |
80 |
|
|
81 |
Requiere: |
Requiere: |
82 |
id != None |
id != None |
83 |
id corresponde con un fichero existente |
id corresponde con un fichero existente |
84 |
""" |
""" |
85 |
|
|
86 |
self.id = id |
self.id = id |
87 |
self.__inicializar_conversacion() |
self.__inicializar_conversacion() |
88 |
|
|
89 |
|
|
90 |
def __inicializar_conversacion(self): |
def __inicializar_conversacion(self): |
91 |
"""Rellena todos los datos de la conversacion a partir de su fichero |
"""Rellena todos los datos de la conversacion a partir de su fichero |
92 |
de descripcion. |
de descripcion. |
93 |
""" |
""" |
94 |
archivo = os.path.join(BloqueDialogo.DIR_DESC_DIALOGOS, self.id.split('-')[0], self.id + BloqueDialogo.EXT_DESC_DIALOGOS) |
archivo = os.path.join(BloqueDialogo.DIR_DESC_DIALOGOS, self.id.split('-')[0], self.id + BloqueDialogo.EXT_DESC_DIALOGOS) |
95 |
stream = open(archivo) |
stream = open(archivo) |
96 |
doc_xml = minidom.parse(stream).documentElement |
doc_xml = minidom.parse(stream).documentElement |
97 |
stream.close() |
stream.close() |
98 |
|
|
99 |
self.__fecha_fichero = os.path.getmtime(archivo) |
self.__fecha_fichero = os.path.getmtime(archivo) |
100 |
self._sinonimos = {} |
self._sinonimos = {} |
101 |
|
|
102 |
self._limpiar_comentarios(doc_xml) |
self._limpiar_comentarios(doc_xml) |
103 |
self.__parsear_conversacion(doc_xml) |
self.__parsear_conversacion(doc_xml) |
104 |
self.__parsear_id(doc_xml) |
self.__parsear_id(doc_xml) |
105 |
self._tolerancia_offtopic = 1 |
self._tolerancia_offtopic = 1 |
106 |
self._probabilidad_offtopic_continuar = 100 |
self._probabilidad_offtopic_continuar = 100 |
107 |
|
|
108 |
especiales_xml = doc_xml.getElementsByTagName("temasEspeciales") |
especiales_xml = doc_xml.getElementsByTagName("temasEspeciales") |
109 |
if especiales_xml: |
if especiales_xml: |
110 |
self._temas_especiales = self._parsear_temas_especiales(especiales_xml[0]) |
self._temas_especiales = self._parsear_temas_especiales(especiales_xml[0]) |
111 |
offtopic = especiales_xml[0].getElementsByTagName("temaOfftopic") |
offtopic = especiales_xml[0].getElementsByTagName("temaOfftopic") |
112 |
if offtopic and offtopic[0].getAttribute("probabilidad"): |
if offtopic and offtopic[0].getAttribute("probabilidad"): |
113 |
self._probabilidad_offtopic_continuar = string.atoi(offtopic[0].getAttribute("probabilidad_continuar")) |
self._probabilidad_offtopic_continuar = string.atoi(offtopic[0].getAttribute("probabilidad_continuar")) |
114 |
if offtopic and offtopic[0].getAttribute("tolerancia"): |
if offtopic and offtopic[0].getAttribute("tolerancia"): |
115 |
self._tolerancia_offtopic = string.atoi(offtopic[0].getAttribute("tolerancia")) |
self._tolerancia_offtopic = string.atoi(offtopic[0].getAttribute("tolerancia")) |
116 |
else: |
else: |
117 |
self._temas_especiales = {"temaAgotado": [], "temaNoEncontrado": [], "temaOfftopic" : []} |
self._temas_especiales = {"temaAgotado": [], "temaNoEncontrado": [], "temaOfftopic" : []} |
118 |
|
|
119 |
self._contextos = self._parsear_contextos(doc_xml) |
self._contextos = self._parsear_contextos(doc_xml) |
120 |
self._indice_contextos = {} |
self._indice_contextos = {} |
121 |
self.__crear_indices() |
self.__crear_indices() |
122 |
self.__revisar_integridad(doc_xml) |
self.__revisar_integridad(doc_xml) |
123 |
|
|
124 |
#Establece contextos iniciales |
#Establece contextos iniciales |
125 |
self._contextos_iniciales = [] |
self._contextos_iniciales = [] |
126 |
for nodo_contexto in doc_xml.getElementsByTagName("contextos")[0].getElementsByTagName("contexto"): |
for nodo_contexto in doc_xml.getElementsByTagName("contextos")[0].getElementsByTagName("contexto"): |
127 |
if nodo_contexto.getAttribute("inicial") == "si": |
if nodo_contexto.getAttribute("inicial") == "si": |
128 |
self._contextos_iniciales.append(self.contexto(nodo_contexto.getAttribute("id"))) |
self._contextos_iniciales.append(self.contexto(nodo_contexto.getAttribute("id"))) |
129 |
filter(None, self._contextos_iniciales) |
filter(None, self._contextos_iniciales) |
130 |
escribir("Cargada con éxito la conversación " + archivo) |
escribir("Cargada con éxito la conversación " + archivo) |
131 |
|
|
132 |
|
|
133 |
def __parsear_conversacion(self, doc_xml): |
def __parsear_conversacion(self, doc_xml): |
134 |
"""Extrae la información del elemento conversacion: |
"""Extrae la información del elemento conversacion: |
135 |
pnj: el personaje al que pertenece la conversacion |
pnj: el personaje al que pertenece la conversacion |
136 |
autor: el autor de la conversacion |
autor: el autor de la conversacion |
137 |
version: la versión del tipo de conversacion""" |
version: la versión del tipo de conversacion""" |
138 |
version_desc_conversacion = doc_xml.attributes["version"].value.encode('ISO-8859-1') |
version_desc_conversacion = doc_xml.attributes["version"].value.encode('ISO-8859-1') |
139 |
|
|
140 |
if version_desc_conversacion != BloqueDialogo.VERSION_DESC_DIALOGO: |
if version_desc_conversacion != BloqueDialogo.VERSION_DESC_DIALOGO: |
141 |
raise ValueError("Version incorrecta del fichero descriptor de conversacion " + self.id + ": " + |
raise ValueError("Version incorrecta del fichero descriptor de conversacion " + self.id + ": " + |
142 |
version_desc_conversacion + " cuando se esperaba " + BloqueDialogo.VERSION_DESC_DIALOGO) |
version_desc_conversacion + " cuando se esperaba " + BloqueDialogo.VERSION_DESC_DIALOGO) |
143 |
|
|
144 |
self.version = BloqueDialogo.VERSION_DIALOGO |
self.version = BloqueDialogo.VERSION_DIALOGO |
145 |
|
|
146 |
self.autor = doc_xml.attributes["autor"].value.encode('ISO-8859-1') |
self.autor = doc_xml.attributes["autor"].value.encode('ISO-8859-1') |
147 |
self.pnj = doc_xml.attributes["pnj"].value.encode('ISO-8859-1') |
self.pnj = doc_xml.attributes["pnj"].value.encode('ISO-8859-1') |
148 |
try: |
try: |
149 |
self.comentario = doc_xml.attributes["comentario"].value.encode('ISO-8859-1') |
self.comentario = doc_xml.attributes["comentario"].value.encode('ISO-8859-1') |
150 |
except: |
except: |
151 |
self.comentario = "nada" |
self.comentario = "nada" |
152 |
|
|
153 |
|
|
154 |
def __parsear_id(self, doc_xml): |
def __parsear_id(self, doc_xml): |
155 |
"""Extrae el id de la conversacion""" |
"""Extrae el id de la conversacion""" |
156 |
id_fichero = doc_xml.getElementsByTagName("id")[0].firstChild.nodeValue.encode('ISO-8859-1') |
id_fichero = doc_xml.getElementsByTagName("id")[0].firstChild.nodeValue.encode('ISO-8859-1') |
157 |
if id_fichero != self.id: |
if id_fichero != self.id: |
158 |
raise ValueError("El id no coincide con el nombre del fichero") |
raise ValueError("El id no coincide con el nombre del fichero") |
159 |
|
|
160 |
|
|
161 |
def __crear_indices(self): |
def __crear_indices(self): |
162 |
"""Crea las estructuras índice de contextos e índice de sinónimos.""" |
"""Crea las estructuras índice de contextos e índice de sinónimos.""" |
163 |
# Acelera la busqueda de contextos por nombre |
# Acelera la busqueda de contextos por nombre |
164 |
for contexto in self._contextos: |
for contexto in self._contextos: |
165 |
self._indice_contextos[contexto.id] = contexto |
self._indice_contextos[contexto.id] = contexto |
166 |
|
|
167 |
# Acelera la busqueda y conversion de sinonimos |
# Acelera la busqueda y conversion de sinonimos |
168 |
for contexto in self._contextos: |
for contexto in self._contextos: |
169 |
for tema in contexto.temas(): |
for tema in contexto.temas(): |
170 |
self._sinonimos[tema.nombre()] = tema.nombre() |
self._sinonimos[tema.nombre()] = tema.nombre() |
171 |
for sinonimo in tema._sinonimos: |
for sinonimo in tema._sinonimos: |
172 |
self._sinonimos[sinonimo] = tema.nombre() |
self._sinonimos[sinonimo] = tema.nombre() |
173 |
|
|
174 |
def __revisar_integridad(self, doc_xml): |
def __revisar_integridad(self, doc_xml): |
175 |
"""Asegura la integridad de los índices y temas relacionados.""" |
"""Asegura la integridad de los índices y temas relacionados.""" |
176 |
# Carga sinonimos adicionales definidos a nivel de conversacion, chequeando su integridad |
# Carga sinonimos adicionales definidos a nivel de conversacion, chequeando su integridad |
177 |
if doc_xml.getElementsByTagName("sinonimos"): |
if doc_xml.getElementsByTagName("sinonimos"): |
178 |
for nodo_tema in doc_xml.getElementsByTagName("sinonimos")[0].getElementsByTagName("tema"): |
for nodo_tema in doc_xml.getElementsByTagName("sinonimos")[0].getElementsByTagName("tema"): |
179 |
nombretema = nodo_tema.getAttribute("nombre").encode('ISO-8859-1') |
nombretema = nodo_tema.getAttribute("nombre").encode('ISO-8859-1') |
180 |
#print str(nodo_tema) + " - " + str(nodo_tema.attributes.keys()) #getAttribute("nombre") |
#print str(nodo_tema) + " - " + str(nodo_tema.attributes.keys()) #getAttribute("nombre") |
181 |
# Comprueba si el tema existe |
# Comprueba si el tema existe |
182 |
if not self._sinonimos.has_key(nombretema): |
if not self._sinonimos.has_key(nombretema): |
183 |
escribir("Error de integridad en conversacion: definido sinonimo para tema '" + nombretema + "' no existente.") |
escribir("Error de integridad en conversacion: definido sinonimo para tema '" + nombretema + "' no existente.") |
184 |
continue |
continue |
185 |
# Completa el índice de sinónimos con los sinónimos definidos en el tema. |
# Completa el índice de sinónimos con los sinónimos definidos en el tema. |
186 |
for sinonimo in self._parsear_sinonimos(nodo_tema): |
for sinonimo in self._parsear_sinonimos(nodo_tema): |
187 |
self._sinonimos[sinonimo] = nombretema |
self._sinonimos[sinonimo] = nombretema |
188 |
|
|
189 |
# Comprueba la integridad de los temas relacionados de cada item |
# Comprueba la integridad de los temas relacionados de cada item |
190 |
for contexto in self._contextos: |
for contexto in self._contextos: |
191 |
for tema in contexto.temas(): |
for tema in contexto.temas(): |
192 |
for item in tema.items(): |
for item in tema.items(): |
193 |
eliminarEnlaces = [] |
eliminarEnlaces = [] |
194 |
# Por cada enlace de cada item |
# Por cada enlace de cada item |
195 |
for enlace in item.relacionados(): |
for enlace in item.relacionados(): |
196 |
nombrecontexto = item.relacionados()[enlace] |
nombrecontexto = item.relacionados()[enlace] |
197 |
if self._indice_contextos.has_key(nombrecontexto): |
if self._indice_contextos.has_key(nombrecontexto): |
198 |
# Si el contexto destino no contiene un tema con el nombre enlazado... |
# Si el contexto destino no contiene un tema con el nombre enlazado... |
199 |
if not self._indice_contextos[nombrecontexto].tema(enlace): # not enlace in map(lambda tema: tema.nombre(), self._indice_contextos[nombrecontexto].temas()): |
if not self._indice_contextos[nombrecontexto].tema(enlace): # not enlace in map(lambda tema: tema.nombre(), self._indice_contextos[nombrecontexto].temas()): |
200 |
eliminarEnlaces.append(enlace) |
eliminarEnlaces.append(enlace) |
201 |
escribir("Error de integridad en conversacion: definido enlace para tema '" \ |
escribir("Error de integridad en conversacion: definido enlace para tema '" \ |
202 |
+ enlace + "' que no pertenece al contexto '" + nombrecontexto + "'.") |
+ enlace + "' que no pertenece al contexto '" + nombrecontexto + "'.") |
203 |
else: |
else: |
204 |
pass |
pass |
205 |
else: |
else: |
206 |
eliminarEnlaces.append(enlace) |
eliminarEnlaces.append(enlace) |
207 |
escribir("Error de integridad en conversacion: definido enlace para tema '" \ |
escribir("Error de integridad en conversacion: definido enlace para tema '" \ |
208 |
+ enlace + "' en el contexto '" + nombrecontexto + "' no existente.") |
+ enlace + "' en el contexto '" + nombrecontexto + "' no existente.") |
209 |
# Borra los enlaces incorrectos |
# Borra los enlaces incorrectos |
210 |
for enlace in eliminarEnlaces: |
for enlace in eliminarEnlaces: |
211 |
del (item._relacionados[enlace]) |
del (item._relacionados[enlace]) |
212 |
|
|
213 |
def contexto(self, nombrecontexto): |
def contexto(self, nombrecontexto): |
214 |
"""Devuelve el contexto con ese nombre.""" |
"""Devuelve el contexto con ese nombre.""" |
215 |
return self._indice_contextos[nombrecontexto] |
return self._indice_contextos[nombrecontexto] |
216 |
|
|
217 |
def contextos(self): |
def contextos(self): |
218 |
return self._contextos |
return self._contextos |
219 |
|
|
220 |
def temas_especiales(self): |
def temas_especiales(self): |
221 |
return self._temas_especiales |
return self._temas_especiales |
222 |
|
|
223 |
def tolerancia(self): |
def tolerancia(self): |
224 |
return self._tolerancia_offtopic |
return self._tolerancia_offtopic |
225 |
|
|
226 |
def probabilidad_cancelacion(self): |
def probabilidad_cancelacion(self): |
227 |
return 100 - self._probabilidad_offtopic_continuar |
return 100 - self._probabilidad_offtopic_continuar |
228 |
|
|
229 |
def __str__(self): |
def __str__(self): |
230 |
texto = '<Conversacion - "' + self.id |
texto = '<Conversacion - "' + self.id |
231 |
texto = texto + '"\n\n Contextos:\n' |
texto = texto + '"\n\n Contextos:\n' |
232 |
for contexto in self._contextos: |
for contexto in self._contextos: |
233 |
if contexto in self._contextos_iniciales: |
if contexto in self._contextos_iniciales: |
234 |
texto = texto + " (inicial)\n" |
texto = texto + " (inicial)\n" |
235 |
texto = texto + str(contexto) + "\n" |
texto = texto + str(contexto) + "\n" |
236 |
texto = texto + ' Temas especiales:\n' |
texto = texto + ' Temas especiales:\n' |
237 |
for tipo in self._temas_especiales: |
for tipo in self._temas_especiales: |
238 |
texto = texto + "\tTipo: " + tipo + "\n" |
texto = texto + "\tTipo: " + tipo + "\n" |
239 |
for tema in self._temas_especiales[tipo]: |
for tema in self._temas_especiales[tipo]: |
240 |
tema._nombre = '' |
tema._nombre = '' |
241 |
tema._sinonimos = [] |
tema._sinonimos = [] |
242 |
texto = texto + str(tema) + "\n" |
texto = texto + str(tema) + "\n" |
243 |
texto = texto + "/>\n" |
texto = texto + "/>\n" |
244 |
return texto |
return texto |
245 |
|
|
246 |
|
|
247 |
|
|
248 |
|
|
249 |
# Código para pruebas unitarias del módulo. |
# Código para pruebas unitarias del módulo. |
250 |
|
|
251 |
if (__name__ == '__main__'): |
if (__name__ == '__main__'): |
252 |
ejemplo = Conversacion("tutora") |
ejemplo = Conversacion("tutora") |
253 |
print "\n" + str(ejemplo) + "\n" |
print "\n" + str(ejemplo) + "\n" |
254 |
print ejemplo._contextos_iniciales |
print ejemplo._contextos_iniciales |
255 |
# print "No hay prueba unitaria de módulo. " |
# print "No hay prueba unitaria de módulo. " |