I have been dabbling more and more with Python. Python is a programming language (that, FWIW, comes pre-installed on a Mac) that is used by a lot of data scientists. As programming languages go it isn’t too hard to learn and is really versatile. One of the things that makes it so versatile is that it can use outside modules and libraries easily and there seems to be a module that will do just about anything you can want.

Why

A few years back I tried to convince L to use the import function built into Blackboard (MacEwan’s LMS [Learning Management System]) to import quiz questions and move from paper-based marking to a more automated workflow. It didn’t work, but in the process I did build a php module to build quizzes.

Then COVID happened.

But, no, before you jump to conclusions, I didn’t manage to convince her to use the bulk import even though she started to use the quiz modules to deliver and mark the quiz. Baby steps.

Then Moodle happened.

MacEwan is in the process of giving up on Blackboard and transitioning to Moodle — which is an open-source LMS. As it happens L has another, temporary, teaching gig at a different institution that also uses Moodle — this was a lot of additional work and learning, so she was starting to come around to the idea I might be able to help.

As a result, eventually, with great sighs and heaving shoulders, she gave in to my pestering and allowed me to upload one of her quizzes… and lo and behold she was impressed at the ease! Minus a few technical glitches.

My End

What I had done was use the format info from my previous php project. Then I used her docx file and by saving it as a text file I was able to do a few search and replaces via regex to create an upload compatible file. Blackboard wants files to look like:

TF  True or false? For Niklas Luhmann, the individual agent is not integral to society.   TRUE
MC  In the sentence "Maybe I got mine, but you'll all get yours.", what part of speech is "yours"?  pronoun CORRECT adjective   INCORRECT   preposition INCORRECT   noun    INCORRECT

Version One

…of my process was simply to build the regex. Luckily she used a pretty standard format to write the questions so I just had to find the pattern and use it to change it to the proper format.

TRUE/FALSE

True or false? For Niklas Luhmann, the individual agent is not integral to society.

True
False

PRONOUNS

In the sentence “Maybe I got mine, but you’ll all get yours.”, what part of speech is “yours”?

pronoun
preposition
noun
adjective

Find:
(.*?)\n\n(.*?)\n(.*?)\n(.*?)\n(.*?)\n\n

Replace with:
MC\t\1\t\2\tCORRECT\t\3\tINCORRECT\t\4\tINCORRECT\t\5\tINCORRECT\n

In English this says find: anything (group1) followed by 2 line breaks followed by anything (group2) followed by 1 line break followed by anything (group3) followed by 1 line break followed by anything (group4) followed by 1 line break followed by anything (group5) followed by 2 line breaks

Replace it with: MC tab Group1 tab Group2 tab CORRECT tab Group3 tab INCORRECT tab Group4 tab INCORRECT tab Group5 tab INCORRECT line break

Easy, right? It was slightly different for the True/False questions as there was only two answers but followed the same principles.

Version Two

…added a python script that looked for the ALL CAPS and split the file into two separate files. At which point I also wrote in a search and replace of ‘’ and “” into ' and " since Blackboard didn’t like the special characters.

Version Three

…combined the original regexes with the python script and did it all in one pass. Success! I also added a randomization bit so she could leave the first Multiple Choice answer as the Correct one and the script would randomize them before upload.

Of course when I went to demo the speed and efficiency of my “wondrous creation” to L I forgot to convert the docx to a txt file and it failed. But I soldiered on, did the necessary step and then proceeded — but the “burning shame” of failure remained and so…

Version Four

…added a docx import module and that’s where I am now.

Given an Word docx file with sections delineated by titles in ALL CAPS, this script will divided it into several text files named after the sections, convert curly quotes , format the questions in Multiple Choice or True/False format, and randomize the MC answers.

The resulting files can be uploaded to Blackboard’s question pool for use in multiple quizzes. Of course now I have to do it all over agin for Moodle as it uses a completely different import format 🙂

# import necessary modules
import os
import random
import re

import pypandoc

# NOTE Sections must be in ALL CAPS. They must be named TRUE FALSE (or TRUE / FALSE)
# or else be in the Multiple Choice format.

# set directory to current path of python file
directory_path = os.path.dirname(__file__)

# get file name
print('Enter the filename (without.docx):')
filename = input()

# set docx file and output file
docxFilename = directory_path + "/" + filename + ".docx"
outputfilename = directory_path + "/" + filename + ".txt"

# use pandoc to convert docx to txt
output = pypandoc.convert_file(
    docxFilename, 'plain', outputfile=outputfilename,
    extra_args=['--wrap=preserve'])
assert output == ""

# Open the converted file
filecontents = open(
    directory_path + "/" + filename + ".txt", "r")

# Assigns the variable filecontents the contents of filename.txt, not just the location of the file
filecontents = str(filecontents.read())

# Finds all the ALL CAPS in the file and makes a list (section) of all the sections
sectionname = re.compile(
    r'\n(?=[A-Z])([/A-Z\s]+)\n')
section = re.split(sectionname, filecontents)

# Removes the preamble in list and starts with first ALL CAPS section
section.pop(0)

# Loops for the number of sections in the file, starting at the first split 
# looking for every 2nd section (the contents rather than the title)
for i in range(0, len(section)+1, 2):

    # Set section name and strip out extra characters
    sectionname = section[i-2][:-1]
    sectionname = re.sub("[/|\s]", "", sectionname)

    # Opens a file with the name of ALL CAPS sections; if it does not exist, it creates one
    writeFile = open(
        directory_path + "/" + "split-files/"+sectionname+".txt", "w+")

    # sets text to item (contents) in the list after ALL CAPS delimiter
    text = section[i-1]

    # strips out curly quotes
    text = text.replace('“', '"').replace(
        '‘', "'").replace('”', '"').replace('’', "'")

    # if the sectionname is TRUEFALSE then...
    # then use regex to  format questions as TF otherwise format as MC
    if sectionname == "TRUEFALSE":
        # Substitute all patterns in one go
        text = re.sub(r'(.*?)\n\n(.*?)\n\n(.*?)(\n\n|\n\Z|\Z)',
                      lambda x: 'TF \t' + x.group(1) + '\t' + x.group(2).upper() + '\n', text)
        writeFile.write(text)
    else:
        # else loop though text, match pattern and name capture groups. Uses ?P<name> to name groups
        for m in re.finditer(r'(?P<qq>.*?)\n\n(?P<one>.*?)\n\n(?P<two>.*?)\n\n(?P<three>.*?)\n\n(?P<four>.*?)(\n\n|\n\Z|\Z)', text):

            question = m.group('qq')
            # write capture groups of answer into a list so we can randomize it
            qlist = [m.group('one') + "\tCORRECT", m.group(
                'two') + "\tINCORRECT", m.group('three') + "\tINCORRECT", m.group('four') + "\tINCORRECT"]
            # randomize list
            random.shuffle(qlist)

            # construct question and answers
            text = "MC\t" + question + "\t" + qlist[0] + "\t" + qlist[1] + "\t" + \
                qlist[2] + "\t" + qlist[3] + "\t\n"

            # Write to file
            writeFile.write(text)

    writeFile.close()  # Finally, it closes the text file