From c3b0ae37aa080999d6b4c878b2425e44a8e501ed Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 20 May 2013 17:32:13 +0900 Subject: [PATCH] experimental type answer refactor - when answer not correct, show both the given and correct string in separate markup. we use red/green for the given string to indicate what was correct, but we use grey rather than red on the correct string to indicate what was missing, as red is misleading - colours can now be customized in css with .typeGood, .typeBad and .typeMissing - answer now shown in monospace so given/correct lines up; can be customized with code#typeans - do away with 'correct answer was' text that people didn't like --- aqt/reviewer.py | 115 +++++++++++++++++++++++------------------------- 1 file changed, 55 insertions(+), 60 deletions(-) diff --git a/aqt/reviewer.py b/aqt/reviewer.py index 576f7e1e9..f09e8856e 100644 --- a/aqt/reviewer.py +++ b/aqt/reviewer.py @@ -320,6 +320,9 @@ body { margin:1.5em; } img { max-width: 95%; max-height: 95%; } .marked { position:absolute; right: 7px; top: 7px; display: none; } #typeans { width: 100%; } +.typeGood { background: #0f0; } +.typeBad { background: #f00; } +.typeMissed { background: #ccc; } """ def _styles(self): @@ -328,8 +331,6 @@ img { max-width: 95%; max-height: 95%; } # Type in the answer ########################################################################## - failedCharColour = "#FF0000" - passedCharColour = "#00FF00" typeAnsPat = "\[\[type:(.+?)\]\]" def typeAnsFilter(self, buf): @@ -390,11 +391,7 @@ Please run Tools>Empty Cards""") cor = parser.unescape(cor) given = self.typedAnswer # compare with typed answer - res = self.correct(cor, given) - if cor != given: - # Wrap the extra text in an id-ed span. - res += u"
{0}
{1}
".format( - _(u"Correct answer was:"), cor) + res = self.correct(given, cor, showBad=False) # and update the type answer area def repl(match): # can't pass a string in directly, and can't use re.escape as it @@ -419,62 +416,60 @@ Please run Tools>Empty Cards""") txt = matches[0] return txt - # following type answer functions thanks to Bernhard - def calculateOkBadStyle(self): - "Precalculates styles for correct and incorrect part of answer" - st = "background: %s; color: #000;" - self.styleOk = st % self.passedCharColour - self.styleBad = st % self.failedCharColour + def tokenizeComparison(self, given, correct): + s = difflib.SequenceMatcher(None, given, correct) + givenElems = [] + correctElems = [] + givenPoint = 0 + correctPoint = 0 + offby = 0 + def logBad(old, new, str, array): + if old != new: + array.append((False, str[old:new])) + def logGood(start, cnt, str, array): + if cnt: + array.append((True, str[start:start+cnt])) + for x, y, cnt in s.get_matching_blocks(): + # if anything was missed in correct, pad given + if cnt and y-offby > x: + givenElems.append((False, "-"*(y-x-offby))) + offby = y-x + # log any proceeding bad elems + logBad(givenPoint, x, given, givenElems) + logBad(correctPoint, y, correct, correctElems) + givenPoint = x+cnt + correctPoint = y+cnt + # log the match + logGood(x, cnt, given, givenElems) + logGood(y, cnt, correct, correctElems) + return givenElems, correctElems - def ok(self, a): - "returns given sring in style correct (green)" - if len(a) == 0: - return "" - return "%s" % (self.styleOk, cgi.escape(a)) - - def bad(self, a): - "returns given sring in style incorrect (red)" - if len(a) == 0: - return "" - return "%s" % (self.styleBad, cgi.escape(a)) - - def applyStyle(self, testChar, correct, wrong): - "Calculates answer fragment depending on testChar's unicode category" - ZERO_SIZE = 'Mn' - def head(a): - return a[:len(a) - 1] - def tail(a): - return a[len(a) - 1:] - if ucd.category(testChar) == ZERO_SIZE: - return self.ok(head(correct)) + self.bad(tail(correct) + wrong) - return self.ok(correct) + self.bad(wrong) - - def correct(self, a, b): + def correct(self, given, correct, showBad=True): "Diff-corrects the typed-in answer." - if b == "": - return ""; - self.calculateOkBadStyle() - ret = "" - lastEqual = "" - s = difflib.SequenceMatcher(None, b, a) - for tag, i1, i2, j1, j2 in s.get_opcodes(): - if tag == "equal": - lastEqual = b[i1:i2] - elif tag == "replace": - ret += self.applyStyle(b[i1], lastEqual, - b[i1:i2] + ("-" * ((j2 - j1) - (i2 - i1)))) - lastEqual = "" - elif tag == "delete": - ret += self.applyStyle(b[i1], lastEqual, b[i1:i2]) - lastEqual = "" - elif tag == "insert": - if ucd.category(a[j1]) != 'Mn': - dashNum = (j2 - j1) + givenElems, correctElems = self.tokenizeComparison(given, correct) + def good(s): + return ""+s+"" + def bad(s): + return ""+s+"" + def missed(s): + return ""+s+"" + if given == correct: + res = good(given) + else: + res = "" + for ok, txt in givenElems: + if ok: + res += good(txt) else: - dashNum = ((j2 - j1) - 1) - ret += self.applyStyle(a[j1], lastEqual, "-" * dashNum) - lastEqual = "" - return ret + self.ok(lastEqual) + res += bad(txt) + res += "

" + for ok, txt in correctElems: + if ok: + res += good(txt) + else: + res += missed(txt) + res = "
" + res + "
" + return res # Bottom bar ##########################################################################