Inicio de sesión

Quién está conectado

Actualmente hay 0 usuarios y 2 invitados en línea.

Sindicar

Distribuir contenido

Macro de Calc para importar cuestionarios a Moodle desde Joomla! Quiz Deluxe

En el marco de un proyecto que estamos desarrollando con Ops! Consulting para migrar unos cursos online de una institución médica desde Joomla a Moodle, nos hemos encontrado con un gran banco de cuestionarios para los diferentes cursos que se imparten.  Los cuestionarios, en Joomla, han sido desarrollados utilizando la extensión Joomla! Quiz Deluxe Component, un software no libre, que se adquiere mediante un sistema de suscripciones.

La abundancia de preguntas aconsejaba investigar un poco para ver la manera de automatizar la importación aprovechando los múltiples formatos con que Moodle puede recuperar cuestionarios, pues su migración manual podía ser larga y tediosa.

Entonces, el primer paso consistió en explorar el JquizDeluxe para ver las posibilidades de exportación de la información.  Directamente desde la aplicación, esto era tan sólo posible mediante una utilidad de copia de seguridad que genera un archivo XML, orientada tan sólo a poder recuperar los datos en otra instalación del mismo JquizDeluxe.

Si hubiésemos sido suficientemente competentes en XML, quizás nos hubiese bastado ese archivo para convertirlo a algún otro tipo de formato comprensible para Moodle, pero no es el caso.

Entonces nos planteamos estudiar como se organizan las tablas de JquizDeluxe dentro de la base de datos, para poder ejecutar una consulta que nos proporcionase la información deseada.  Para eso nos resultó muy útil, por la comodidad de su interfaz, el phpMyAdmin.

Pudimos comprobar que los nombres de las tablas involucradas comenzaban todas por 'quiz_t', precedidas de un prefijo 'j17_', que puede cambiar según se haya efectuado la instalación de Joomla.

Examinando su contenido, pudimos ver las relaciones existentes entre ellas. Y para dar forma gráfica a estas relaciones, exportamos las tablas a formato OpenDocument Spreadsheet y las copiamos en una base de datos de LibreOffice Base. En una instalación local, habría sido posible conectar LibreOffice directamente con las tablas en MySQL, pero las características del alojamiento no lo permitían. Eso nos permitió aprovechar las facilidades de la interfaz gráfica del programa para dibujar las relaciones, como se muestra en la Figura 1.

Figura 1. Relaciones entre las tablas de JQuiz DeluxeFigura 1. Relaciones entre las tablas de JQuiz Deluxe

La descripción de algunas de las tablas, con sus campos más relevantes para nuestro propósito es como sigue:

Tabla quiz_t_category: Esta tabla contiene las diferentes categorías de cuestionarios de JquizDeluxe, cada categoría corresponde a un curso diferente.

  • c_id: la clave principal de la tabla.
  • c_category: la descripción de la categoría (nombre del curso).

Tabla quiz_t_quiz: Contiene los diferentes cuestionarios, de manera que cada categoría (curso) puede contener diferentes cuestionarios.  En Moodle, sería el equivalente a las diferentes categorías en que se pueden clasificar los bancos de preguntas de cada curso.

  • c_id: la clave principal de la tabla.
  • c_category_id: la clave de la categoría (curso) a que corresponde el cuestionario.
  • c_title: nombre del cuestionario.
  • c_description: una descripción adicional para el cuestionario.
  • c_min_after: el límite de tiempo en minutos para poder repetir el cuestionario, en caso de admitir repeticiones.
  • c_passing_score: la puntuación en porcentaje necesaria para superar el cuestionario.
  • c_pass_message: el texto de retroinformación cuando se supera el cuestionario.
  • c_unpass_message: el texto de retroinformación cuando no se ha superado el examen.
  • c_number_times: el número de reintentos admitidos para superar el cuestionario.

Tabla quiz_t_qtypes: Los diferentes tipos de preguntas que admite JquizDeluxe.

  • c_id: la clave principal de la tabla
  • c_qtype: la descripción de cada tipo de pregunta (opciones múltiples, respuestas múltiples, verdadero/falso, relleno de espacios, etc...)

Tabla quiz_t_question: Contiene las diferentes preguntas de cada cuestionario.

  • c_id: la clave principal de la tabla.
  • c_quiz_id: la clave del cuestionario al cual corresponde la pregunta.
  • c_question: el texto de la pregunta, admite HTML.
  • c_type: la clave del tipo de pregunta (opción múltiple, relleno de espacios, etc).
  • Ordering: el orden en que se presentarán las preguntas dentro de cada cuestionario.

Tabla quiz_t_choice: Las opciones de respuesta para cada pregunta.

  • c_id: la clave principal de la tabla.
  • c_choice: el texto de la respuesta.
  • c_question_id: la clave de la pregunta a que corresponde esta respuesta
  • ordering: el orden en que se presentarán las respuestas dentro de cada pregunta.
  • a_point: '1' si la respuesta es correcta, '0' si es incorrecta.

Cuando tuvimos clara la información que necesitábamos llegó el momento de preparar la consulta SQL que nos permitiría recuperar los datos necesarios.  Al tratarse de una consulta algo complicada con múltiples relaciones, resultaba complicado crear la instrucción SQL directamente y aprovechamos que teníamos los datos en LibreOffice Base para usar el diseñador gráfico de consultas como se muestra en la figura 2.

Figura 2. Diseño de la consultaFigura 2. Diseño de la consulta

Realizada la consulta y pasando a la vista SQL obtenemos la instrucción que se muestra en el Listado 1.  Al respecto, tener presente que los nombres de tabla pueden presentar algún prefijo, dato a considerar en caso de querer reproducir los pasos que se están detallando.  También hemos eliminado las comillas que Base añade a los nombres de tabla y de campos, pues phpMyAdmin no las admite.

LISTADO 1

Consulta SQL, formateada para mejorar su legibilidad y en la que hemos omitido las comillas que añade Base, por no ser compatibles con la sintaxis que admite phpMyAdmin

SELECT
   quiz_t_category.c_id AS id_category,
   quiz_t_category.c_category,
   quiz_t_quiz.c_id AS id_quiz,
   quiz_t_quiz.c_title,
   quiz_t_question.c_id AS id_question,
   quiz_t_question.c_question,
   quiz_t_question.c_type,
   quiz_t_qtypes.c_qtype,
   quiz_t_question.ordering,
   quiz_t_choice.c_id AS id_choice,
   quiz_t_choice.c_choice,
   quiz_t_choice.c_right,
   quiz_t_choice.ordering 

FROM
   quiz_t_quiz,
   quiz_t_question,
   quiz_t_choice,
   quiz_t_qtypes,
   quiz_t_category 

WHERE
   quiz_t_quiz.c_id = quiz_t_question.c_quiz_id
   AND quiz_t_question.c_id = quiz_t_choice.c_question_id
   AND quiz_t_question.c_type = quiz_t_qtypes.c_id
   AND quiz_t_quiz.c_category_id = quiz_t_category.c_id 

ORDER BY
   id_quiz ASC,
   quiz_t_question.ordering ASC,
   quiz_t_choice.ordering ASC;

Esta consulta SQL la copiamos y pegamos en el generador de consultas de phpMyAdmin, obteniendo una tabla con los resultados deseados.  El propio phpMyAdmin nos ofrece la posibilidad de exportar los resultados en formato OpenDocument Spreadsheet, lo que facilitará su tratamiento posterior.

Ahora que ya tenemos los datos, la decisión que debemos tomar es en qué formato de archivo los convertiremos para que Moodle pueda recuperarlos con facilidad.  Moodle admite múltiples formatos de importación.  

Entre ellos, optamos por el formato GIFT [7], dado que se trata de un simple documento de texto con un formato como el que se muestra en el Listado 2.

LISTADO 2
Ejemplo formato GIFT

// las líneas que inician con doble barra son comentarios
// la siguiente línea determina una categoría
$CATEGORY: Examen 1

// los títulos de las preguntas (opcionales) deben ir entre dobles dos puntos
::Título de la pregunta 1.1::

// se pueden añadir delimitadores de formato como el [html] de la siguiente
[html]<p>Esto es el texto de la <strong>pregunta 1.1</strong></p>


// las respuestas se añaden entre llaves
// las respuestas incorrectas comienzan por una tilde ~
// las respuestas correctas comienzan por un signo igual =
{
~Respuesta 1.1.a - Incorrecta
~Respuesta 1.1.b - Incorrecta
=Respuesta 1.1.c - Correcta
~Respuesta 1.1.d - Incorrecta
}

De la ayuda de Moodle:

"GIFT es el formato disponible más completo para importar preguntas de cuestionario a partir de un archivo de texto. Su diseño permite escribir preguntas en un archivo de texto de forma fácil. Soporta opciones múltiples, verdadero-falso, respuesta corta, emparejamientos y preguntas numéricas, así como la inserción de _____ para el formato de rellenar huecos. Varios tipos de pregunta pueden mezclarse en un sencillo archivo de texto, dado que este formato soporta líneas de comentario, nombres de preguntas, respuesta automática al alumno y calificaciones por porcentajes de peso."

En el ejemplo del Listado 2, tan sólo se muestra una pregunta de opción múltiple, dado que es el único tipo de pregunta que tuvimos que migrar.

Ahora tan sólo necesitábamos decidir que lenguaje usaríamos para convertir la tabla con los datos en un archivo de texto con formato GIFT.  Las posibilidades son numerosas, pero optamos por hacerlo en una macro de Calc, que podemos utilizar independientemente del sistema operativo, y sin más requisitos para el usuario que disponer de Apache OpenOffice o LibreOffice.

La codificación completa de la macro se puede ver en el Listado 3.  Como está bastante autocomentada, nos limitaremos a comentar por encima su funcionalidad.

LISTADO 3 Código de la macro


sub JQ2GIFT()
' Convierte a formato GIFT los datos obtenidos desde Joomla Quiz Deluxe'
' (c) 2013 Ismael Fanlo [mailto:ifanlo@superalumnos.net]' 

' Publicado bajo licencia GPL.  Términos de licencia:
'    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 3 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, see <http://www.gnu.org/licenses/>.'

' Con agradecimiento a Mauricio Baeza y Salvador Domenech por el código 
' que ponen a disposición de la comunidad y del cual he hecho uso en 
' la presente macro.

Dim oDoc As Object ' referencia al documento desde donde se ejecuta la macr
Dim oHoja As Object ' referencia a la hoja activa
Dim i as Integer ' contador
Dim sRuta As String ' ruta completa del archivo con los datos a convertir
Dim sPath As String ' directorio del archivo
Dim sNombre As String ' nombre del archivo
Dim oOrigen As Object ' referencia a la hoja con los datos a convertir
Dim mArg() As String ' matriz de argumentos necesitada por algún método
Dim oCursor As Object ' cursor para ubicar los datos dentro de la hoja
Dim oRange As Object ' referencia a la primera celda de la hoja con los dato
Dim mDatos() ' matriz que carga los datos de la hoja
Dim mOutput() As String ' matriz que carga los datos convertidos a GIFT
Dim lFilas As Long ' número de filas de la matriz de datos
Dim lColumnas As Long ' número de columnas de la matriz de datos
' identificadores de curso, cuestionario y pregunta:
Dim idCurso As Long, idExamen As Long, idPregunta As Long
Dim idCursoAnterior As Long, idExamenAnterior As Long, idPreguntaAnterior As Long
Dim sNombreOutput As String ' nombre del archivo de texto de salida
Dim sCorrecta As String ' marca de respuesta correcta o incorrecta
Dim oBarraEstado As Object ' referencia a la barra de estado

'Necesario para utilizar ciertas funciones GlobalScope.BasicLibraries.LoadLibrary("Tools")

'Acceso al documento desde donde se llama a esta macro
oDoc = ThisComponent

'Nos aseguramos de que sea una hoja de calculo
If oDoc.supportsService("com.sun.star.sheet.SpreadsheetDocument") Then
   'Referencia a la hoja activa del documento
  oHoja = oDoc.getSheets().getByIndex(0)
  ' abrir datos desde el archivo enlazado en la celda "ruta" dentro de la hoja
  sRuta = ConvertToUrl(oHoja.getCellRangeByName("ruta").getString)
  ' abre archivo origen oOrigen = StarDesktop.loadComponentFromUrl(sRuta, "_blank", 0, mArg())
  ' carga array con datos
   oRange = oOrigen.getSheets.getByIndex(0).getCellRangeByName("A1")
  oCursor = oOrigen.getSheets.getByIndex(0).createCursorByRange(oRange)
  oCursor.collapseToCurrentRegion()
  mDatos() = oCursor.getDataArray()
  ' cierra archivo origen
  oOrigen.close(True)

  ' Recorrer filas de la hoja "datos"
  lFilas = UBound(mDatos)
  lColumnas = UBound(mDatos(1))
  sNombre = FileNameOutOfPath(sRuta)
  sPath = DirectoryNameOutOfPath(sRuta, getPathSeparator())


  'Referencia a la barra de estado del documento activo 
  '¡Gracias, Mauricio!   'http://forum.openoffice.org/es/forum/viewtopic.php?f=50&t=1916   
  oBarraEstado = ThisComponent.getCurrentController.StatusIndicator   

  ' Establecemos el texto inicial y el limite de la barra de progreso   
   oBarraEstado.start( "Ejecutando... ", lFilas )    
 
  For i = 1 to lFilas
  ' Para cada curso

    ' Establecemos el valor de la barra de progreso     
     oBarraEstado.setValue( i )

    idCurso = mDatos(i)(0)

    ' si no es el mismo curso de la fila anterior
    If idCurso <> idCursoAnterior Then

      ' sólo si ya se había procesado algún curso
      If idCursoAnterior > 0 Then
        ' cierra bloque de respuestas
        mOutput = aAdd(mOutput, "}" & chr(10))
        ' al cambiar de curso se guarda en disco la matriz de salida
         SaveDataToFile(sNombreOutput, mOutput)
       ' reinicializa variables
         reDim mOutput(0)
        idExamenAnterior = 0
        idPreguntaAnterior = 0
      Endif

      ' determinar nombre del archivo que lo compondremos con
      ' curso + id_curso + título categoria (jquizz)
      sNombreOutput = sPath & getPathSeparator() & _ "curso" & idCurso & "-" & mDatos(i)(1) & ".txt"
       idCursoAnterior = idCurso Endif

       ' Para cada examen (cuestionario)
      idExamen = mDatos(i)(2)
 
    ' si ha cambiado el cuestionario con respecto al de la fila anterior
      If idExamen <> idExamenAnterior Then

        ' si no es el primer examen
        If idExamenAnterior > 0 Then
          ' cierra bloque de respuestas
          mOutput = aAdd(mOutput, "}" & chr(10))
        Endif

        ' escribe categoria en el array
        mOutput = aAdd(mOutput, chr(10))
        mOutput = aAdd(mOutput, "$CATEGORY: " & mDatos(i)(3))
        mOutput = aAdd(mOutput, chr(10))
        idExamenAnterior = idExamen
        ' reinicializa variable
        idPreguntaAnterior = 0
      Endif

       ' Para cada pregunta
      idPregunta = mDatos(i)(4)

      ' si cambia la pregunta en relación a la de la fila anterior
      If idPregunta <> idPreguntaAnterior Then
        If idPreguntaAnterior > 0 Then
        ' sólo si ya se había procesado alguna pregunta
        ' cierra bloque de respuestas
        mOutput = aAdd(mOutput, "}" & chr(10))
      Endif

      ' escribe pregunta en el array
      mOutput = aAdd(mOutput, chr(10))
      ' título de la pregunta
      mOutput = aAdd(mOutput, "::Pregunta " & mDatos(i)(4) & "::")
      ' texto de la pregunta, ver más abajo la función FiltraTexto()
      mOutput = aAdd(mOutput, "[html]" & FiltraTexto(mDatos(i)(5)))
      ' inicia bloque de respuestas
      mOutput = aAdd(mOutput, "{")
      idPreguntaAnterior = idPregunta Endif

       ' Para cada respuesta
      If mDatos(i)(11) = "1" Then
        sCorrecta = "="
      Else
        sCorrecta = "~"
      Endif
      mOutput = aAdd(mOutput, sCorrecta & FiltraTexto(mDatos(i)(10))
    Next

    ' cierra último bloque de respuestas
    mOutput = aAdd(mOutput, "}" & chr(10))
    ' guarda en disco la matriz de salida del último curso
    SaveDataToFile(sNombreOutput, mOutput)

     'Es importante finalizar la barra de estado para devolverle el control   
     'a la aplicación  
     oBarraEstado.end()

    msgbox "Ejecución finalizada"
 
  Else
    MsgBox "No es un documento de hoja de calculo"
  End If

End Sub
' filtra caracteres especiales de las preguntas y elimina líneas vacías ' Special Characters ~ = # { } :
Function FiltraTexto(sTexto As String) As String
Dim sResultado As String
  sResultado = trim(sTexto)

  sResultado = ReplaceAll(sResultado, "~", "\~")
  sResultado = ReplaceAll(sResultado, "=", "\=")
  sResultado = ReplaceAll(sResultado, "#", "\#")
  sResultado = ReplaceAll(sResultado, "{", "\{")
  sResultado = ReplaceAll(sResultado, "}", "\}")
  sResultado = ReplaceAll(sResultado, ":", "\:")
  sResultado = ReplaceAll(sResultado, chr(10), "")
  sResultado = ReplaceAll(sResultado, chr(13), "")
  FiltraTexto = sResultado
End Function


' Función de Salva: http://wiki.open-office.es/Funciones_para_manejo_de_cadenas_en_OpenOffic...
Function ReplaceAll(cCadena As String, cBusca As String, optional cReemplaza As String) As String
'--------------------------------------------------------------------------------------------
' reemplaza en una cadena todas las ocurrencias de cBusca por cReemplaza 
  If IsMissing( cReemplaza ) Then cReemplaza = "" 
  ReplaceAll = Join( Split( cCadena, cBusca ), cReemplaza )
End Function




' Función de Salva: http://wiki.open-office.es/Funciones_para_manejo_de_matrices_arrays_en_O...
Function aAdd( Byref a(), Byval xValor )
'--------------------------------------------------------------------------------------------
' Agrega un elemento a un array y lo llena con xValor
' se puede llamar como función [ a = aadd( a, "5") ] o como subrutina  [ aadd a, "5" ]
Dim u As Long   
  u = uBound( a ) + 1   
  Redim Preserve a( u )   
  a(u) = xValor   
  aAdd = a
End Function

En primer lugar, es necesario insertar en la celda A4 de la hoja un enlace a la hoja que contiene los datos exportados desde JquizDeluxe, lo que realizaremos con Insertar > Hiperenlace.  Luego ya podemos pulsar el botón "Ejecutar macro".

 La macro leerá el nombre del archivo enlazado y cargará los datos en una matriz. Cada fila contiene todos los datos relativos a una respuesta del cuestionario, junto a la categoría, cuestionario, pregunta, etc.

Entonces la macro recorre la matriz fila a fila y para cada categoría (curso) va determinando el cuestionario (en Moodle será la categoría), la pregunta, si la respuesta es correcta o no, y va escribiendo en una matriz de salida la información convertida a GIFT.  Para cada curso, vuelca la matriz de salida en un archivo de texto, reinicializa la matriz y sigue con el curso siguiente hasta el final de la matriz de datos. 

En Windows la macro da un error tanto con Apache OpenOffice como con LibreOffice, en la función de utilidad SaveDataToFile (que viene con las macros de Open/LibreOffice).  Deberemos investigar el motivo.  En GNU/Linux, funciona completamente sin incidencias en ambos programas.

Finalmente ya tenemos el directorio de trabajo pleno con archivos de texto con el contenido de cada curso (las categorías, en JQuizDeluxe).  Dentro de cada curso, unos separadores $CATEGORY determinan los diferentes cuestionarios, que en Moodle permitirán agrupar en categorías las preguntas del banco de pregunta.

Tan sólo basta con acudir a nuestra instalación de Moodle y en el apartado de importación del banco de preguntas, seleccionar que vamos a importar un archivo GIFT, activar la casilla "Obtener categoría de archivo" y seleccionar el archivo a importar, como se puede ver en la Figura 3.  La captura de pantalla pertenece a una instalación de Moodle 1.9, la apariencia varía ligeramente en la versión 2.0 pero es el mismo concepto.

Figura 3.  Preparando la importaciónFigura 3. Preparando la importación

Tras pulsar en el botón "Subir archivo", si no hay incidencias, se nos mostrará una pantalla como la de la Figura 4.

Figura 4. Revisión final de la importación de preguntasFigura 4. Revisión final de la importación de preguntas

Pulsando el botón "Continuar" al final de la página, se efectuará la importación definitiva como podemos ya comprobar en la Figura 5.

Figura 5. Importación completada.Figura 5. Importación completada.

Esperamos que encontréis de utilidad este artículo.  Si deseáis descargar las fuentes del artículo, imágenes, datos de ejemplo y el documento con la macro, podéis hacerlo desde el adjunto al pie del presente artículo.

AdjuntoTamaño
Macro-JoomlaQuizDeluxe-a-Moodle.zip630.78 KB

Búscalo con Google

Comentarios recientes

Encuesta

¿Sobre qué programas esperas encontrar tutoriales y ejercicios en SuperAlumnos.net? :