#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2011-2012, 2014, Marten de Vries
# Copyright 2011, Milan Boers
#
# This file is part of OpenTeacher.
#
# OpenTeacher 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.
#
# OpenTeacher 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 OpenTeacher. If not, see <http://www.gnu.org/licenses/>.
from etree import ElementTree
import datetime
import re
import atexit
import tempfile
import os
import base64
class Teach2000LoaderModule:
def __init__(self, moduleManager, *args, **kwargs):
super().__init__(*args, **kwargs)
self._mm = moduleManager
self.type = "load"
self.priorities = {
"default": 648,
}
self.uses = (
self._mm.mods(type="translator"),
)
self.requires = (
self._mm.mods(type="mimicryTypefaceConverter"),
)
self.filesWithTranslations = ("t2k.py",)
self._tempPaths = set()
atexit.register(self._cleanUp)
@property
def _convertMimicryTypeface(self):
return self._modules.default("active", type="mimicryTypefaceConverter").convert
def _retranslate(self):
try:
translator = self._modules.default("active", type="translator")
except IndexError:
_, ngettext = str, lambda a, b, n: a if n == 1 else b
else:
_, ngettext = translator.gettextFunctions(
self._mm.resourcePath("translations")
)
#TRANSLATORS: This is the name of an application. For more info
#TRANSLATORS: about Teach2000: http://www.teach2000.org/
self.name = _("Teach2000")
def enable(self):
self._modules = set(self._mm.mods(type="modules")).pop()
try:
translator = self._modules.default("active", type="translator")
except IndexError:
pass
else:
translator.languageChanged.handle(self._retranslate)
self._retranslate()
self.mimetype = "application/x-teach2000"
self.loads = {"t2k": ["words"]}
self.active = True
def _cleanUp(self):
for path in getattr(self, "_tempPaths", []):
os.remove(path)
def disable(self):
self.active = False
del self._modules
del self.name
del self.loads
del self.mimetype
def getFileTypeOf(self, path):
if path.endswith(".t2k"):
with open(path, 'rb') as f:
root = ElementTree.parse(f).getroot()
if root.find("message_data/items//item/answers[@type='4']") is not None:
return "topo"
else:
#also support other formats in the future? Well,
#everything that's left can be opened like it's of type 'words'...
return "words"
def load(self, path):
"""Loads a .t2k file into the OpenTeacher data structure.
http://www.teach2000.nl/help/00513_advanced_file_format.htm
"""
with open(path, 'rb') as f:
root = ElementTree.parse(f).getroot()
if self.getFileTypeOf(path) == "topo":
return self._loadTopo(root)
else:
return self._loadWords(root)
def _loadResults(self, root):
#create one test to save all results in that are in the t2k file
#(t2k doesn't have enough information to know which word was
#wrong in which test, so we can have only one test.)
test = {
"finished": True,
"results": [],
}
for item in root.findall("message_data/items/item"):
#add a result for every time this word was wrong
for i in range(int(item.findtext("errors") or 0)):
test["results"].append({
"itemId": self._getId(item), "result": "wrong",
})
#add a result for every time this word was right
for i in range(int(item.findtext("correctcount") or 0)):
test["results"].append({
"itemId": self._getId(item),
"result": "right",
})
#if there were results
if test["results"]:
#get the time of the first start result and the one of the
#last for a global idea of the time range of this 'mega'
#test. Duration isn't used, since it's way off anyway.
try:
startTime = root.findall("message_data/testresults/testresult")[0].findtext("dt")
endTime = root.findall("message_data/testresults/testresult")[-1].findtext("dt")
except IndexError:
pass
else:
startTime = self._parseDt(startTime)
endTime = self._parseDt(endTime)
#store those times in the first and last result. (which may
#be the same, technically, but that doesn't matter...)
test["results"][0]["active"] = {
"start": startTime,
"end": startTime,
}
test["results"][-1]["active"] = {
"start": endTime,
"end": endTime
}
return test
def _getId(self, item):
return int(item.get("id"))
def _loadTopo(self, root):
placesList = {
"items": [],
"tests": [],
}
for item in root.findall("message_data/items/item"):
answers = item.find(".//answers")
answerTexts = [a.text for a in answers.findall(".//answer")]
place = {
"id": self._getId(item),
"x": int(answers.get("x") or 0),
"y": int(answers.get("y") or 0),
"name": self._stripBBCode(u", ".join(answerTexts)),
}
placesList["items"].append(place)
#append the test to the list
test = self._loadResults(root)
if test["results"]:
placesList["tests"] = [test]
mapElement = root.find("message_data/mapquizfile")
fd, mapPath = tempfile.mkstemp("." + mapElement.get("ext"))
os.close(fd)
self._tempPaths.add(mapPath)
with open(mapPath, "wb") as f:
f.write(base64.b64decode(mapElement.text))
return {
"resources": {
"mapPath": mapPath,
},
"list": placesList,
}
def _loadWords(self, root):
wordList = {
"items": [],
"tests": [],
}
questionFont = root.findtext("message_data/font_question") or u""
answerFont = root.findtext("message_data/answer_question") or u""
for item in root.findall("message_data/items/item"):
word = self._loadWordFromTreeItem(questionFont, answerFont, item)
wordList["items"].append(word)
#append the test to the list
test = self._loadResults(root)
if test["results"]:
wordList["tests"] = [test]
return {
"resources": {},
"list": wordList,
}
def _loadWordFromTreeItem(self, questionFont, answerFont, item):
word = {
"questions": [],
"answers": [],
}
#id
word["id"] = self._getId(item)
#questions
word["questions"].append([])
for question in item.findall(".//question"):
#strip BBCode for now
text = self._stripBBCode(question.text)
convertedText = self._convertMimicryTypeface(questionFont, text)
word["questions"][0].append(convertedText)
#answers
word["answers"].append([])
for answer in item.findall(".//answer"):
#strip BBCode for now
text = self._stripBBCode(answer.text)
convertedText = self._convertMimicryTypeface(answerFont, text)
word["answers"][0].append(convertedText)
#remarks (comment in OT)
comment = item.findtext("remarks")
if comment:
word["comment"] = comment
#remarks_aa (commentAfterAnswering in OT)
commentAfterAnswering = item.findtext("remarks_aa")
if commentAfterAnswering:
word["commentAfterAnswering"] = commentAfterAnswering
return word
def _stripBBCode(self, string):
"""Strips all BBCode tags"""
return re.sub(r"\[[\w/]*\]", u"", string)
def _parseDt(self, string):
"""Parses a date string as found in T2K files to a datetime
object."""
return datetime.datetime.strptime(string, "%Y-%m-%dT%H:%M:%S.%f")
def init(manager):
return Teach2000LoaderModule(manager)